diff options
| -rw-r--r-- | background_scripts/main.coffee | 5 | ||||
| -rw-r--r-- | content_scripts/ui_component.coffee | 79 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 10 | ||||
| -rw-r--r-- | pages/test_ui_component.coffee | 4 | ||||
| -rw-r--r-- | pages/ui_component_server.coffee | 44 | ||||
| -rw-r--r-- | tests/dom_tests/chrome.coffee | 1 | ||||
| -rw-r--r-- | tests/unit_tests/test_chrome_stubs.coffee | 4 |
7 files changed, 56 insertions, 91 deletions
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 647923c0..b85ea844 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -19,6 +19,11 @@ namedKeyRegex = /^(<(?:[amc]-.|(?:[amc]-)?[a-z0-9]{2,5})>)(.*)$/ selectionChangedHandlers = [] tabLoadedHandlers = {} # tabId -> function() +# A secret, available only within the current instantiation of Vimium. The secret is big, likely unguessable +# in practice, but less than 2^31. +chrome.storage.local.set + vimiumSecret: Math.floor Math.random() * 2000000000 + completionSources = bookmarks: new BookmarkCompleter() history: new HistoryCompleter() diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index 10450778..a898d525 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -1,21 +1,19 @@ class UIComponent iframeElement: null iframePort: null - messageEventListeners: [] + showing: true showStyle: "display: block;" hideStyle: "display: none;" - constructor: (iframeUrl, className, showStyle, hideStyle) -> + constructor: (iframeUrl, className, @handleMessage) -> @iframeElement = document.createElement "iframe" @iframeElement.className = className @iframeElement.seamless = "seamless" @iframeElement.src = chrome.runtime.getURL iframeUrl @iframeElement.addEventListener "load", => @openPort() document.documentElement.appendChild @iframeElement - - @setShowStyle showStyle if showStyle? - @setHideStyle hideStyle if showStyle? - @hide() + # Hide iframe, but don't interfere with the focus. + @hide false # Open a port and pass it to the iframe via window.postMessage. openPort: -> @@ -23,63 +21,32 @@ class UIComponent @iframePort = messageChannel.port1 @iframePort.onmessage = (event) => @handleMessage event - # Get iframeMessageSecret so the iframe can determine that our message isn't the page impersonating us. - chrome.storage.local.get "iframeMessageSecret", ({iframeMessageSecret: secret}) => + # Get vimiumSecret so the iframe can determine that our message isn't the page impersonating us. + chrome.storage.local.get "vimiumSecret", ({vimiumSecret: secret}) => @iframeElement.contentWindow.postMessage secret, chrome.runtime.getURL(""), [messageChannel.port2] - postMessage: (message) -> @iframePort.postMessage message - - # Execute each event listener on the current event until we get a non-null falsy return value. - handleMessage: (event) -> - for listener in @messageEventListeners - retVal = listener.call this, event - retVal ?= true - return false unless retVal - true - - addEventListener: (type, listener) -> - if type == "message" - @messageEventListeners.push listener - undefined - - removeEventListener: (type, listener) -> - if type == "message" - @messageEventListeners = @messageEventListeners.filter (f) -> f != listener - undefined - - setHideStyle: (@hideStyle) -> - @hide() if @showing == false - - setShowStyle: (@showStyle) -> - @show() if @showing == true - - setStyles: (@showStyle = @showStyle, @hideStyle = @hideStyle) -> - if @showing - @show() - else - @hide() + postMessage: (message) -> + @iframePort.postMessage message activate: (message) -> @postMessage message if message? - @show() unless @showing + if @showing + # NOTE(smblott) Experimental. Not sure this is a great idea. If the iframe was already showing, then + # the user gets no visual feedback when it is re-focused. So flash its border. + borderWas = @iframeElement.style.border + @iframeElement.style.border = '5px solid yellow' + setTimeout((=> @iframeElement.style.border = borderWas), 200) + else + @iframeElement.setAttribute "style", @showStyle + @showing = true @iframeElement.focus() - show: (message) -> - @postMessage message if message? - @iframeElement.setAttribute "style", @showStyle - @showing = true - - hide: -> - @iframeElement.setAttribute "style", @hideStyle - window.focus() - @showing = false - -handleHideMessage = (event) -> - if event.data == "hide" - @hide() - false - else - true + hide: (focusWindow=true)-> + if @showing + @iframeElement.setAttribute "style", @hideStyle + # TODO(smblott) Is window always the right thing to focus, here? + window.focus() if focusWindow + @showing = false root = exports ? window root.UIComponent = UIComponent diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 3f898b74..8fcb16ed 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1090,17 +1090,17 @@ Tween = testUIComponent = null testUIComponentSetup = -> - testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent" - testUIComponent.addEventListener "message", (event) -> + testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent", (event) -> if event.data == "hide" @hide() window.focus() - false else - true + # ... And we can get data back! + console.log event.data window.activateTestUIComponent = -> - testUIComponent.activate "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" + chrome.storage.local.get "vimiumSecret", ({vimiumSecret: secret}) -> + testUIComponent.activate [chrome.runtime.getManifest().version, secret, Math.random(), ].join "<br/>" initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) diff --git a/pages/test_ui_component.coffee b/pages/test_ui_component.coffee index 886a3470..e140fb14 100644 --- a/pages/test_ui_component.coffee +++ b/pages/test_ui_component.coffee @@ -1,6 +1,8 @@ -UIComponentServer.addEventListener "message", (event) -> +UIComponentServer.registerHandler (event) -> document.body.innerHTML = event.data window.addEventListener "keydown", (event) -> if KeyboardUtils.isEscape event UIComponentServer.postMessage "hide" + else + UIComponentServer.postMessage event.keyCode diff --git a/pages/ui_component_server.coffee b/pages/ui_component_server.coffee index ccbb9e2b..8b43095b 100644 --- a/pages/ui_component_server.coffee +++ b/pages/ui_component_server.coffee @@ -1,41 +1,27 @@ -# Register the port recieved from the parent window, and stop listening for messages on the window object. -window.addEventListener "message", (event) -> - return unless event.source == window.parent - currentFunction = arguments.callee - # Check event.data against iframeMessageSecret so we can determine that this message hasn't been spoofed. - chrome.storage.local.get "iframeMessageSecret", ({iframeMessageSecret: secret}) -> - return unless event.data == secret +# Fetch the Vimium secret, register the port recieved from the parent window, and stop listening for messages +# on the window object. vimiumSecret is accessible only within the current instantion of Vimium. So a +# malicious host page trying to register its own port can do no better than guessing. +registerPort = (event) -> + chrome.storage.local.get "vimiumSecret", ({vimiumSecret: secret}) -> + return unless event.source == window.parent and event.data == secret UIComponentServer.portOpen event.ports[0] - window.removeEventListener "message", currentFunction # Stop listening for message events. + window.removeEventListener "message", registerPort + +window.addEventListener "message", registerPort UIComponentServer = ownerPagePort: null - messageEventListeners: [] - exitOnEsc: true + handleMessage: null portOpen: (@ownerPagePort) -> - @ownerPagePort.onmessage = (event) => @handleMessage event - - postMessage: (message) -> @ownerPagePort.postMessage message - - # Execute each event listener on the current event until we get a non-null falsy return value. - handleMessage: (event) -> - for listener in @messageEventListeners - retVal = listener.call this, event - retVal ?= true - return false unless retVal - true + @ownerPagePort.onmessage = (event) => + @handleMessage event if @handleMessage - addEventListener: (type, listener) -> - if type == "message" - @messageEventListeners.push listener - undefined + registerHandler: (@handleMessage) -> - removeEventListener: (type, listener) -> - if type == "message" - @messageEventListeners = @messageEventListeners.filter (f) -> f != listener - undefined + postMessage: (message) -> + @ownerPagePort.postMessage message if @ownerPagePort root = exports ? window root.UIComponentServer = UIComponentServer diff --git a/tests/dom_tests/chrome.coffee b/tests/dom_tests/chrome.coffee index 2695ef20..a49a565a 100644 --- a/tests/dom_tests/chrome.coffee +++ b/tests/dom_tests/chrome.coffee @@ -22,4 +22,5 @@ root.chrome = { storage: local: get: -> + set: -> } diff --git a/tests/unit_tests/test_chrome_stubs.coffee b/tests/unit_tests/test_chrome_stubs.coffee index 80750337..3258bcd6 100644 --- a/tests/unit_tests/test_chrome_stubs.coffee +++ b/tests/unit_tests/test_chrome_stubs.coffee @@ -42,6 +42,10 @@ exports.chrome = getAll: () -> true storage: + # chrome.storage.local + local: + set: -> + # chrome.storage.onChanged onChanged: addListener: (func) -> @func = func |
