diff options
| author | Stephen Blott | 2016-04-15 16:51:18 +0100 | 
|---|---|---|
| committer | Stephen Blott | 2016-04-16 14:15:33 +0100 | 
| commit | 5bfe6dc5d1e0aeb1ab3e372821997d83ba5c9164 (patch) | |
| tree | 174c58b552cc0b869764d2dd28f14bb3f010ef2c /content_scripts/ui_component.coffee | |
| parent | 014f53fb091ac8672d3efbeca13a494c15d8afcb (diff) | |
| download | vimium-5bfe6dc5d1e0aeb1ab3e372821997d83ba5c9164.tar.bz2 | |
Rework UI component focus handling.
The code to handle the focus for UI components has been tweaked and
adapted over time, and has become quite complicated (and brittle).  This
reworks it from scratch, and co-locates similar code which does related
things.
Fixes #2099.
Diffstat (limited to 'content_scripts/ui_component.coffee')
| -rw-r--r-- | content_scripts/ui_component.coffee | 107 | 
1 files changed, 45 insertions, 62 deletions
| diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index d7bdf2a1..bb350ccc 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -2,7 +2,8 @@ class UIComponent    uiComponentIsReady: false    iframeElement: null    iframePort: null -  showing: null +  iframeFrameId: null +  showing: false    options: null    shadowDOM: null    styleSheetGetter: null @@ -27,10 +28,8 @@ class UIComponent      @shadowDOM = shadowWrapper.createShadowRoot?() ? shadowWrapper      @shadowDOM.appendChild styleSheet      @shadowDOM.appendChild @iframeElement - -    @showing = true # The iframe is visible now. -    # Hide the iframe, but don't interfere with the focus. -    @hide false +    @iframeElement.classList.remove "vimiumUIComponentVisible" +    @iframeElement.classList.add "vimiumUIComponentHidden"      # Open a port and pass it to the iframe via window.postMessage.  We use an AsyncDataFetcher to handle      # requests which arrive before the iframe (and its message handlers) have completed initialization.  See @@ -45,74 +44,59 @@ class UIComponent          # Get vimiumSecret so the iframe can determine that our message isn't the page impersonating us.          chrome.storage.local.get "vimiumSecret", ({ vimiumSecret }) =>            { port1, port2 } = new MessageChannel -          port1.onmessage = (event) => -            if event?.data == "uiComponentIsReady" then @uiComponentIsReady = true else @handleMessage event            @iframeElement.contentWindow.postMessage vimiumSecret, chrome.runtime.getURL(""), [ port2 ] -          setIframePort port1 - -    chrome.runtime.onMessage.addListener (request) => -      if @showing and request.name == "frameFocused" and request.focusFrameId != frameId -        @postMessage name: "frameFocused", focusFrameId: request.focusFrameId -      false # Free up the sendResponse handler. -  # Posts a message (if one is provided), then calls continuation (if provided).  The continuation is only -  # ever called *after* the message has been posted. +          port1.onmessage = (event) => +            switch event?.data?.name ? event?.data +              when "uiComponentIsReady" +                # If any other frame receives the focus, then hide the UI component. +                chrome.runtime.onMessage.addListener ({name, focusFrameId}) => +                  if name == "frameFocused" and @options?.focus and focusFrameId not in [frameId, @iframeFrameId] +                    @hide false +                  false # We will not be calling sendResponse. + +                # If this frame receives the focus, then hide the UI component. +                window.addEventListener "focus", (event) => +                  if event.target == window and @options?.focus and not @options?.allowBlur +                    @hide false +                  true # Continue propagating the event. + +                @uiComponentIsReady = true +                setIframePort port1 + +              when "setIframeFrameId" then @iframeFrameId = event.data.iframeFrameId +              when "hide" then @hide() +              else @handleMessage event + +  # Post a message (if provided), then call continuation (if provided).    postMessage: (message = null, continuation = null) ->      @iframePort.use (port) =>        port.postMessage message if message?        continuation?() -  activate: (@options) -> +  activate: (@options = null) ->      @postMessage @options, => -      @show() unless @showing -      @iframeElement.focus() - -  show: (message) -> -    @postMessage message, =>        @iframeElement.classList.remove "vimiumUIComponentHidden"        @iframeElement.classList.add "vimiumUIComponentVisible" -      # The window may not have the focus.  We focus it now, to prevent the "focus" listener below from firing -      # immediately. -      window.focus() -      window.addEventListener "focus", @onFocus = (event) => -        if event.target == window -          window.removeEventListener "focus", @onFocus -          @onFocus = null -          @postMessage "frameFocused" +      @iframeElement.focus() if @options?.focus        @showing = true -  hide: (focusWindow = true)-> -    @refocusSourceFrame @options?.sourceFrameId if focusWindow -    window.removeEventListener "focus", @onFocus if @onFocus -    @onFocus = null -    @iframeElement.classList.remove "vimiumUIComponentVisible" -    @iframeElement.classList.add "vimiumUIComponentHidden" -    @iframeElement.blur() -    @options = null -    @showing = false - -  # Refocus the frame from which the UI component was opened.  This may be different from the current frame. -  # After hiding the UI component, Chrome refocuses the containing frame. To avoid a race condition, we need -  # to wait until that frame first receives the focus, before then focusing the frame which should now have -  # the focus. -  refocusSourceFrame: (sourceFrameId) -> -    if @showing and sourceFrameId? and sourceFrameId != frameId -      refocusSourceFrame = -> -        chrome.runtime.sendMessage -          handler: "sendMessageToFrames" -          message: -            name: "focusFrame" -            frameId: sourceFrameId - -      if windowIsFocused() -        # We already have the focus. -        refocusSourceFrame() -      else -        # We don't yet have the focus (but we'll be getting it soon). -        window.addEventListener "focus", handler = (event) -> -          if event.target == window -            window.removeEventListener "focus", handler -            refocusSourceFrame() +  hide: (shouldRefocusOriginalFrame = true) -> +    if @showing +      @showing = false +      @iframeElement.classList.remove "vimiumUIComponentVisible" +      @iframeElement.classList.add "vimiumUIComponentHidden" +      if @options?.focus and shouldRefocusOriginalFrame +        @iframeElement.blur() +        if @options?.sourceFrameId? +          chrome.runtime.sendMessage +            handler: "sendMessageToFrames", +            message: name: "focusFrame", frameId: @options.sourceFrameId, forceFocusThisFrame: true +        else +          window.focus() +      @options = null +      # Inform the UI component that it is hidden. +      Utils.nextTick => @postMessage "hidden"    # Fetch a Vimium file/resource (such as "content_scripts/vimium.css").    # We try making an XMLHttpRequest request.  That can fail (see #1817), in which case we fetch the @@ -135,6 +119,5 @@ class UIComponent      request.open "GET", (chrome.runtime.getURL file), true      request.send() -  root = exports ? window  root.UIComponent = UIComponent | 
