diff options
Diffstat (limited to 'content_scripts')
| -rw-r--r-- | content_scripts/link_hints.coffee | 95 | ||||
| -rw-r--r-- | content_scripts/mode.coffee | 68 | ||||
| -rw-r--r-- | content_scripts/mode_find.coffee | 9 | ||||
| -rw-r--r-- | content_scripts/mode_insert.coffee | 8 | ||||
| -rw-r--r-- | content_scripts/mode_passkeys.coffee | 4 | ||||
| -rw-r--r-- | content_scripts/mode_visual_edit.coffee | 23 | ||||
| -rw-r--r-- | content_scripts/scroller.coffee | 3 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 123 | 
8 files changed, 132 insertions, 201 deletions
| diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 72fde9e1..6bc37aaf 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -8,16 +8,15 @@  # In 'filter' mode, our link hints are numbers, and the user can narrow down the range of possibilities by  # typing the text of the link itself.  # -# The "name" property below is a short-form name to appear in the link-hints mode name.  Debugging only.  The -# key appears in the mode's badge. +# The "name" property below is a short-form name to appear in the link-hints mode's name.  It's for debug only.  # -OPEN_IN_CURRENT_TAB = { name: "curr-tab", key: "" } -OPEN_IN_NEW_BG_TAB = { name: "bg-tab", key: "B" } -OPEN_IN_NEW_FG_TAB = { name: "fg-tab", key: "F" } -OPEN_WITH_QUEUE = { name: "queue", key: "Q" } -COPY_LINK_URL = { name: "link", key: "C" } -OPEN_INCOGNITO = { name: "incognito", key: "I" } -DOWNLOAD_LINK_URL = { name: "download", key: "D" } +OPEN_IN_CURRENT_TAB = name: "curr-tab" +OPEN_IN_NEW_BG_TAB = name: "bg-tab" +OPEN_IN_NEW_FG_TAB = name: "fg-tab" +OPEN_WITH_QUEUE = name: "queue" +COPY_LINK_URL = name: "link" +OPEN_INCOGNITO = name: "incognito" +DOWNLOAD_LINK_URL = name: "download"  LinkHints =    hintMarkerContainingDiv: null @@ -33,6 +32,8 @@ LinkHints =      if settings.get("filterLinkHints") then filterHints else alphabetHints    # lock to ensure only one instance runs at a time    isActive: false +  # Call this function on exit (if defined). +  onExit: null    #    # To be called after linkHints has been generated from linkHintsBase. @@ -55,61 +56,66 @@ LinkHints =        return      @isActive = true -    @setOpenLinkMode(mode) -    hintMarkers = (@createMarkerFor(el) for el in @getVisibleClickableElements()) +    elements = @getVisibleClickableElements() +    # For these modes, we filter out those elements which don't have an HREF (since there's nothing we can do +    # with them). +    elements = (el for el in elements when el.element.href?) if mode in [ COPY_LINK_URL, OPEN_INCOGNITO ] +    hintMarkers = (@createMarkerFor(el) for el in elements)      @getMarkerMatcher().fillInMarkers(hintMarkers) -    # Note(philc): Append these markers as top level children instead of as child nodes to the link itself, -    # because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat -    # that if you scroll the page and the link has position=fixed, the marker will not stay fixed. -    @hintMarkerContainingDiv = DomUtils.addElementList(hintMarkers, -      { id: "vimiumHintMarkerContainer", className: "vimiumReset" }) -      @hintMode = new Mode        name: "hint/#{mode.name}" -      badge: "#{mode.key}?" +      indicator: false        passInitialKeyupEvents: true -      keydown: @onKeyDownInMode.bind(this, hintMarkers), -      # trap all key events +      keydown: @onKeyDownInMode.bind this, hintMarkers +      # Trap all other key events.        keypress: -> false        keyup: -> false +    @setOpenLinkMode mode + +    # Note(philc): Append these markers as top level children instead of as child nodes to the link itself, +    # because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat +    # that if you scroll the page and the link has position=fixed, the marker will not stay fixed. +    @hintMarkerContainingDiv = DomUtils.addElementList(hintMarkers, +      { id: "vimiumHintMarkerContainer", className: "vimiumReset" }) +    setOpenLinkMode: (@mode) ->      if @mode is OPEN_IN_NEW_BG_TAB or @mode is OPEN_IN_NEW_FG_TAB or @mode is OPEN_WITH_QUEUE        if @mode is OPEN_IN_NEW_BG_TAB -        HUD.show("Open link in new tab") +        @hintMode.setIndicator "Open link in new tab"        else if @mode is OPEN_IN_NEW_FG_TAB -        HUD.show("Open link in new tab and switch to it") +        @hintMode.setIndicator "Open link in new tab and switch to it"        else -        HUD.show("Open multiple links in a new tab") +        @hintMode.setIndicator "Open multiple links in a new tab"        @linkActivator = (link) ->          # When "clicking" on a link, dispatch the event with the appropriate meta key (CMD on Mac, CTRL on          # windows) to open it in a new tab if necessary. -        DomUtils.simulateClick(link, { -          shiftKey: @mode is OPEN_IN_NEW_FG_TAB, -          metaKey: KeyboardUtils.platform == "Mac", -          ctrlKey: KeyboardUtils.platform != "Mac", -          altKey: false}) +        DomUtils.simulateClick link, +          shiftKey: @mode is OPEN_IN_NEW_FG_TAB +          metaKey: KeyboardUtils.platform == "Mac" +          ctrlKey: KeyboardUtils.platform != "Mac" +          altKey: false      else if @mode is COPY_LINK_URL -      HUD.show("Copy link URL to Clipboard") -      @linkActivator = (link) -> -        chrome.runtime.sendMessage({handler: "copyToClipboard", data: link.href}) +      @hintMode.setIndicator "Copy link URL to Clipboard" +      @linkActivator = (link) => +        if link.href? +          chrome.runtime.sendMessage handler: "copyToClipboard", data: link.href +          url = link.href +          url = url[0..25] + "...." if 28 < url.length +          @onExit = -> HUD.showForDuration "Yanked #{url}", 2000 +        else +          @onExit = -> HUD.showForDuration "No link to yank.", 2000      else if @mode is OPEN_INCOGNITO -      HUD.show("Open link in incognito window") - +      @hintMode.setIndicator "Open link in incognito window"        @linkActivator = (link) -> -        chrome.runtime.sendMessage( -          handler: 'openUrlInIncognito' -          url: link.href) +        chrome.runtime.sendMessage handler: 'openUrlInIncognito', url: link.href      else if @mode is DOWNLOAD_LINK_URL -      HUD.show("Download link URL") +      @hintMode.setIndicator "Download link URL"        @linkActivator = (link) -> -        DomUtils.simulateClick(link, { -          altKey: true, -          ctrlKey: false, -          metaKey: false }) +        DomUtils.simulateClick link, altKey: true, ctrlKey: false, metaKey: false      else # OPEN_IN_CURRENT_TAB -      HUD.show("Open link in current tab") +      @hintMode.setIndicator "Open link in current tab"        @linkActivator = (link) -> DomUtils.simulateClick.bind(DomUtils, link)()    # @@ -276,8 +282,8 @@ LinkHints =        handlerStack.push          keyup: (event) =>            if event.keyCode == keyCode -            @setOpenLinkMode previousMode if @isActive              handlerStack.remove() +            @setOpenLinkMode previousMode if @isActive            true      # TODO(philc): Ignore keys that have modifiers. @@ -347,7 +353,8 @@ LinkHints =          DomUtils.removeElement LinkHints.hintMarkerContainingDiv        LinkHints.hintMarkerContainingDiv = null        @hintMode.exit() -      HUD.hide() +      @onExit?() +      @onExit = null        @isActive = false      # we invoke the deactivate() function directly instead of using setTimeout(callback, 0) so that diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee index bded402c..a2ac5b8c 100644 --- a/content_scripts/mode.coffee +++ b/content_scripts/mode.coffee @@ -6,13 +6,6 @@  # name:  #   A name for this mode.  # -# badge: -#   A badge (to appear on the browser popup). -#   Optional.  Define a badge if the badge is constant; for example, in find mode the badge is always "/". -#   Otherwise, do not define a badge, but instead override the updateBadge method; for example, in passkeys -#   mode, the badge may be "P" or "", depending on the configuration state.  Or, if the mode *never* shows a -#   badge, then do neither. -#  # keydown:  # keypress:  # keyup: @@ -48,7 +41,6 @@ class Mode      @handlers = []      @exitHandlers = []      @modeIsActive = true -    @badge = @options.badge || ""      @name = @options.name || "anonymous"      @count = ++count @@ -59,7 +51,15 @@ class Mode        keydown: @options.keydown || null        keypress: @options.keypress || null        keyup: @options.keyup || null -      updateBadge: (badge) => @alwaysContinueBubbling => @updateBadge badge +      indicator: => +        # Update the mode indicator.  Setting @options.indicator to a string shows a mode indicator in the +        # HUD.  Setting @options.indicator to 'false' forces no mode indicator.  If @options.indicator is +        # undefined, then the request propagates to the next mode. +        # The active indicator can also be changed with @setIndicator(). +        if @options.indicator? +          if @options.indicator then HUD?.show @options.indicator else HUD?.hide true, false +          @stopBubblingAndTrue +        else @continueBubbling      # If @options.exitOnEscape is truthy, then the mode will exit when the escape key is pressed.      if @options.exitOnEscape @@ -131,10 +131,17 @@ class Mode            if KeyboardUtils.isPrintable event then @stopBubblingAndFalse else @stopBubblingAndTrue      Mode.modes.push @ -    Mode.updateBadge() +    @setIndicator()      @logModes()      # End of Mode constructor. +  setIndicator: (indicator = @options.indicator) -> +    @options.indicator = indicator +    Mode.setIndicator() + +  @setIndicator: -> +    handlerStack.bubbleEvent "indicator" +    push: (handlers) ->      handlers._name ||= "mode-#{@id}"      @handlers.push handlerStack.push handlers @@ -152,17 +159,12 @@ class Mode        handler() for handler in @exitHandlers        handlerStack.remove handlerId for handlerId in @handlers        Mode.modes = Mode.modes.filter (mode) => mode != @ -      Mode.updateBadge()        @modeIsActive = false +      @setIndicator()    deactivateSingleton: (singleton) ->      Mode.singletons?[Utils.getIdentity singleton]?.exit() -  # The badge is chosen by bubbling an "updateBadge" event down the handler stack allowing each mode the -  # opportunity to choose a badge. This is overridden in sub-classes. -  updateBadge: (badge) -> -    badge.badge ||= @badge -    # Shorthand for an otherwise long name.  This wraps a handler with an arbitrary return value, and always    # yields @continueBubbling instead.  This simplifies handlers if they always continue bubbling (a common    # case), because they do not need to be concerned with the value they yield. @@ -174,14 +176,6 @@ class Mode      delete @options[key] for key in [ "keydown", "keypress", "keyup" ]      new @constructor @options -  # Static method.  Used externally and internally to initiate bubbling of an updateBadge event and to send -  # the resulting badge to the background page.  We only update the badge if this document (hence this frame) -  # has the focus. -  @updateBadge: -> -    if document.hasFocus() -      handlerStack.bubbleEvent "updateBadge", badge = badge: "" -      chrome.runtime.sendMessage { handler: "setBadge", badge: badge.badge }, -> -    # Debugging routines.    logModes: ->      if @debug @@ -200,31 +194,5 @@ class Mode      mode.exit() for mode in @modes      @modes = [] -# BadgeMode is a pseudo mode for triggering badge updates on focus changes and state updates. It sits at the -# bottom of the handler stack, and so it receives state changes *after* all other modes, and can override the -# badge choice of the other modes. -class BadgeMode extends Mode -  constructor: () -> -    super -      name: "badge" -      trackState: true - -    # FIXME(smblott) BadgeMode is currently triggering an updateBadge event on every focus event.  That's a -    # lot, considerably more than necessary.  Really, it only needs to trigger when we change frame, or when -    # we change tab. -    @push -      _name: "mode-#{@id}/focus" -      "focus": => @alwaysContinueBubbling -> Mode.updateBadge() - -  updateBadge: (badge) -> -    # If we're not enabled, then post an empty badge. -    badge.badge = "" unless @enabled - -  # When the registerStateChange event bubbles to the bottom of the stack, all modes have been notified.  So -  # it's now time to update the badge. -  registerStateChange: -> -    Mode.updateBadge() -  root = exports ? window  root.Mode = Mode -root.BadgeMode = BadgeMode diff --git a/content_scripts/mode_find.coffee b/content_scripts/mode_find.coffee index 67f2a7dc..ed08fbd5 100644 --- a/content_scripts/mode_find.coffee +++ b/content_scripts/mode_find.coffee @@ -33,8 +33,6 @@ class PostFindMode extends SuppressPrintable      super        name: "post-find" -      # We show a "?" badge, but only while an Escape activates insert mode. -      badge: "?"        # PostFindMode shares a singleton with the modes launched by focusInput; each displaces the other.        singleton: element        exitOnBlur: element @@ -54,14 +52,7 @@ class PostFindMode extends SuppressPrintable            @suppressEvent          else            handlerStack.remove() -          @badge = "" -          Mode.updateBadge()            @continueBubbling -  updateBadge: (badge) -> -    badge.badge ||= @badge -    # Suppress the "I" badge from insert mode. -    InsertMode.suppressEvent badge # Always truthy. -  root = exports ? window  root.PostFindMode = PostFindMode diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee index 90162d5a..7ca2e561 100644 --- a/content_scripts/mode_insert.coffee +++ b/content_scripts/mode_insert.coffee @@ -24,6 +24,7 @@ class InsertMode extends Mode      defaults =        name: "insert" +      indicator: if @permanent then null else "Insert mode"        keypress: handleKeyEvent        keyup: handleKeyEvent        keydown: handleKeyEvent @@ -68,18 +69,13 @@ class InsertMode extends Mode    activateOnElement: (element) ->      @log "#{@id}: activating (permanent)" if @debug and @permanent      @insertModeLock = element -    Mode.updateBadge()    exit: (_, target)  ->      if (target and target == @insertModeLock) or @global or target == undefined        @log "#{@id}: deactivating (permanent)" if @debug and @permanent and @insertModeLock        @insertModeLock = null        # Exit, but only if this isn't the permanently-installed instance. -      if @permanent then Mode.updateBadge() else super() - -  updateBadge: (badge) -> -    badge.badge ||= @badge if @badge -    badge.badge ||= "I" if @isActive badge +      super() unless @permanent    # Static stuff. This allows PostFindMode to suppress the permanently-installed InsertMode instance.    @suppressedEvent: null diff --git a/content_scripts/mode_passkeys.coffee b/content_scripts/mode_passkeys.coffee index 64db5447..cf74a844 100644 --- a/content_scripts/mode_passkeys.coffee +++ b/content_scripts/mode_passkeys.coffee @@ -16,9 +16,5 @@ class PassKeysMode extends Mode      else        @continueBubbling -  # Disabled, pending experimentation with how/whether to use badges (smblott, 2015/01/17). -  # updateBadge: (badge) -> -  #   badge.badge ||= "P" if @passKeys and not @keyQueue -  root = exports ? window  root.PassKeysMode = PassKeysMode diff --git a/content_scripts/mode_visual_edit.coffee b/content_scripts/mode_visual_edit.coffee index a5758a64..9c599959 100644 --- a/content_scripts/mode_visual_edit.coffee +++ b/content_scripts/mode_visual_edit.coffee @@ -361,30 +361,30 @@ class Movement extends CountPrefix          @movements.n = (count) -> executeFind count, false          @movements.N = (count) -> executeFind count, true          @movements["/"] = -> -          @findMode = window.enterFindMode() +          @findMode = window.enterFindMode returnToViewport: true            @findMode.onExit => @changeMode VisualMode      #      # End of Movement constructor. -  # Yank the selection; always exits; either deletes the selection or removes it; set @yankedText and return +  # Yank the selection; always exits; either deletes the selection or collapses it; set @yankedText and return    # it.    yank: (args = {}) ->      @yankedText = @selection.toString()      @selection.deleteFromDocument() if @options.deleteFromDocument or args.deleteFromDocument -    @selection.removeAllRanges() unless @options.parentMode +    @selection.collapseToStart() unless @options.parentMode      message = @yankedText.replace /\s+/g, " "      message = message[...12] + "..." if 15 < @yankedText.length      plural = if @yankedText.length == 1 then "" else "s" -    HUD.showForDuration "Yanked #{@yankedText.length} character#{plural}: \"#{message}\".", 2500      @options.onYank?.call @, @yankedText      @exit() +    HUD.showForDuration "Yanked #{@yankedText.length} character#{plural}: \"#{message}\".", 2500      @yankedText    exit: (event, target) ->      unless @options.parentMode or @options.oneMovementOnly -      @selection.removeAllRanges() if event?.type == "keydown" and KeyboardUtils.isEscape event +      @selection.collapseToStart() if event?.type == "keydown" and KeyboardUtils.isEscape event        # Disabled, pending discussion of fine-tuning the UX.  Simpler alternative is implemented above.        # # If we're exiting on escape and there is a range selection, then we leave it in place.  However, an @@ -466,7 +466,7 @@ class VisualMode extends Movement      defaults =        name: "visual" -      badge: "V" +      indicator: if options.indicator? then options.indicator else "Visual mode"        singleton: VisualMode        exitOnEscape: true      super extend defaults, options @@ -489,8 +489,8 @@ class VisualMode extends Movement              @selection.removeAllRanges()          if @selection.type != "Range" -          HUD.showForDuration "No usable selection, entering caret mode...", 2500            @changeMode CaretMode +          HUD.showForDuration "No usable selection, entering caret mode...", 2500            return      @push @@ -567,7 +567,7 @@ class VisualMode extends Movement  class VisualLineMode extends VisualMode    constructor: (options = {}) -> -    super extend { name: "visual/line" }, options +    super extend { name: "visual/line", indicator: "Visual mode (line)" }, options      @extendSelection()      @commands.v = -> @changeMode VisualMode @@ -587,7 +587,7 @@ class CaretMode extends Movement      defaults =        name: "caret" -      badge: "C" +      indicator: "Caret mode"        singleton: VisualMode        exitOnEscape: true      super extend defaults, options @@ -597,8 +597,8 @@ class CaretMode extends Movement        when "None"          @establishInitialSelectionAnchor()          if @selection.type == "None" -          HUD.showForDuration "Create a selection before entering visual mode.", 2500            @exit() +          HUD.showForDuration "Create a selection before entering visual mode.", 2500            return        when "Range"          @collapseSelectionToAnchor() @@ -652,7 +652,7 @@ class EditMode extends Movement      defaults =        name: "edit" -      badge: "E" +      indicator: "Edit mode"        exitOnEscape: true        exitOnBlur: @element      super extend defaults, options @@ -748,7 +748,6 @@ class EditMode extends Movement    # and (possibly) deletes it.    enterVisualModeForMovement: (count, options = {}) ->      @launchSubMode VisualMode, extend options, -      badge: "M"        initialCountPrefix: count        oneMovementOnly: true diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index 5cc3fd82..b7de5140 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -123,6 +123,9 @@ CoreScroller =      @lastEvent = null      @keyIsDown = false +    # NOTE(smblott) With extreme keyboard configurations, Chrome sometimes does not get a keyup event for +    # every keydown, in which case tapping "j" scrolls indefinitely.  This appears to be a Chrome/OS/XOrg bug +    # of some kind.  See #1549.      handlerStack.push        _name: 'scroller/track-key-status'        keydown: (event) => diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 931b8edf..b5a5f51e 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -141,13 +141,13 @@ window.initializeModes = ->      constructor: ->        super          name: "normal" +        indicator: false # There is no mode indicator in normal mode.          keydown: (event) => onKeydown.call @, event          keypress: (event) => onKeypress.call @, event          keyup: (event) => onKeyup.call @, event    # Install the permanent modes.  The permanently-installed insert mode tracks focus/blur events, and    # activates/deactivates itself accordingly. -  new BadgeMode    new NormalMode    new PassKeysMode    new InsertMode permanent: true @@ -174,10 +174,9 @@ initializePreDomReady = ->      isEnabledForUrl = false      chrome.runtime.sendMessage = ->      chrome.runtime.connect = -> +    window.removeEventListener "focus", onFocus    requestHandlers = -    hideUpgradeNotification: -> HUD.hideUpgradeNotification() -    showUpgradeNotification: (request) -> HUD.showUpgradeNotification(request.version)      showHUDforDuration: (request) -> HUD.showForDuration request.text, request.duration      toggleHelpDialog: (request) -> toggleHelpDialog(request.dialogHtml, request.frameId)      focusFrame: (request) -> if (frameId == request.frameId) then focusThisFrame request @@ -185,8 +184,6 @@ initializePreDomReady = ->      getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY      setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY      executePageCommand: executePageCommand -    getActiveState: getActiveState -    setState: setState      currentKeyQueue: (request) ->        keyQueue = request.keyQueue        handlerStack.bubbleEvent "registerKeyQueue", { keyQueue: keyQueue } @@ -197,7 +194,7 @@ initializePreDomReady = ->      # from the former.      return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://'      # We handle the message if we're enabled, or if it's one of these listed message types. -    return unless isEnabledForUrl or request.name in [ "getActiveState", "setState", "executePageCommand" ] +    return unless isEnabledForUrl or request.name in [ "executePageCommand" ]      # These requests are delivered to the options page, but there are no handlers there.      return if request.handler in [ "registerFrame", "unregisterFrame" ]      # We don't handle these here.  They're handled elsewhere (e.g. in the vomnibar/UI component). @@ -226,34 +223,25 @@ window.initializeWhenEnabled = ->      for type in [ "keydown", "keypress", "keyup", "click", "focus", "blur", "mousedown" ]        do (type) -> installListener window, type, (event) -> handlerStack.bubbleEvent type, event      installListener document, "DOMActivate", (event) -> handlerStack.bubbleEvent 'DOMActivate', event -    installListener window, "focus", registerFocus      installedListeners = true      FindModeHistory.init() -setState = (request) -> -  isEnabledForUrl = request.enabled -  passKeys = request.passKeys -  isIncognitoMode = request.incognito -  initializeWhenEnabled() if isEnabledForUrl -  handlerStack.bubbleEvent "registerStateChange", -    enabled: isEnabledForUrl -    passKeys: passKeys - -getActiveState = -> -  Mode.updateBadge() -  return { enabled: isEnabledForUrl, passKeys: passKeys } -  # -# The backend needs to know which frame has focus. +# Whenever we get the focus: +# - Reload settings (they may have changed). +# - Tell the background page this frame's URL. +# - Check if we should be enabled.  # -registerFocus = (event) -> +onFocus = (event) ->    if event.target == window -    # Settings may have changed since the frame last had focus.      settings.load() +    chrome.runtime.sendMessage handler: "frameFocused", frameId: frameId, url: window.location.toString()      checkIfEnabledForUrl() -    # Don't register frameset containers; focusing them is no use. -    unless document.body?.tagName.toLowerCase() == "frameset" -      chrome.runtime.sendMessage handler: "frameFocused", frameId: frameId + +# We install these listeners directly (that is, we don't use installListener) because we still need to receive +# events when Vimium is not enabled. +window.addEventListener "focus", onFocus +window.addEventListener "hashchange", onFocus  #  # Initialization tasks that must wait for the document to be ready. @@ -434,7 +422,6 @@ extend window,          constructor: ->            super              name: "focus-selector" -            badge: "?"              exitOnClick: true              keydown: (event) =>                if event.keyCode == KeyboardUtils.keyCodes.tab @@ -600,12 +587,21 @@ checkIfEnabledForUrl = ->      isIncognitoMode = response.incognito      if isEnabledForUrl        initializeWhenEnabled() -    else if (HUD.isReady()) +    else if HUD.isReady()        # Quickly hide any HUD we might already be showing, e.g. if we entered insert mode on page load.        HUD.hide()      handlerStack.bubbleEvent "registerStateChange",        enabled: isEnabledForUrl        passKeys: passKeys +    # Update the page icon, if necessary. +    if document.hasFocus() +      chrome.runtime.sendMessage +        handler: "setIcon" +        icon: +          if isEnabledForUrl and not passKeys then "enabled" +          else if isEnabledForUrl then "partial" +          else "disabled" +  # Exported to window, but only for DOM tests.  window.refreshCompletionKeys = (response) -> @@ -727,7 +723,6 @@ handleKeyCharForFindMode = (keyChar) ->    updateQueryForFindMode findModeQuery.rawQuery + keyChar  handleEscapeForFindMode = -> -  exitFindMode()    document.body.classList.remove("vimiumFindMode")    # removing the class does not re-color existing selections. we recreate the current selection so it reverts    # back to the default color. @@ -741,8 +736,7 @@ handleEscapeForFindMode = ->  # Return true if character deleted, false otherwise.  handleDeleteForFindMode = ->    if findModeQuery.rawQuery.length == 0 -    exitFindMode() -    performFindInPlace() +    HUD.hide()      false    else      updateQueryForFindMode findModeQuery.rawQuery.substring(0, findModeQuery.rawQuery.length - 1) @@ -752,22 +746,25 @@ handleDeleteForFindMode = ->  # <esc> corresponds approximately to 'nevermind, I have found it already' while <cr> means 'I want to save  # this query and do more searches with it'  handleEnterForFindMode = -> -  exitFindMode()    focusFoundLink()    document.body.classList.add("vimiumFindMode")    FindModeHistory.saveQuery findModeQuery.rawQuery  class FindMode extends Mode -  constructor: -> +  constructor: (options = {}) ->      @historyIndex = -1      @partialQuery = "" +    if options.returnToViewport +      @scrollX = window.scrollX +      @scrollY = window.scrollY      super        name: "find" -      badge: "/" +      indicator: false        exitOnEscape: true        exitOnClick: true        keydown: (event) => +        window.scrollTo @scrollX, @scrollY if options.returnToViewport          if event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey            @exit() unless handleDeleteForFindMode()            @suppressEvent @@ -790,8 +787,8 @@ class FindMode extends Mode            DomUtils.suppressPropagation(event)            handlerStack.stopBubblingAndFalse -      keypress: (event) -> -        handlerStack.neverContinueBubbling -> +      keypress: (event) => +        handlerStack.neverContinueBubbling =>            if event.keyCode > 31              keyChar = String.fromCharCode event.charCode              handleKeyCharForFindMode keyChar if keyChar @@ -1024,15 +1021,13 @@ findModeRestoreSelection = (range = findModeInitialRange) ->    selection.addRange range  # Enters find mode.  Returns the new find-mode instance. -window.enterFindMode = -> +window.enterFindMode = (options = {}) ->    # Save the selection, so performFindInPlace can restore it.    findModeSaveSelection() -  findModeQuery = { rawQuery: "" } -  HUD.show("/") -  new FindMode() - -exitFindMode = -> -  HUD.hide() +  findModeQuery = rawQuery: "" +  findMode = new FindMode options +  HUD.show "/" +  findMode  window.showHelpDialog = (html, fid) ->    return if (isShowingHelpDialog || !document.body || fid != frameId) @@ -1102,7 +1097,6 @@ toggleHelpDialog = (html, fid) ->  HUD =    _tweenId: -1    _displayElement: null -  _upgradeNotificationElement: null    # This HUD is styled to precisely mimick the chrome HUD on Mac. Use the "has_popup_and_link_hud.html"    # test harness to tweak these styles to match Chrome's. One limitation of our HUD display is that @@ -1120,26 +1114,6 @@ HUD =      HUD._tweenId = Tween.fade(HUD.displayElement(), 1.0, 150)      HUD.displayElement().style.display = "" -  showUpgradeNotification: (version) -> -    HUD.upgradeNotificationElement().innerHTML = "Vimium has been upgraded to #{version}. See -      <a class='vimiumReset' target='_blank' -      href='https://github.com/philc/vimium#release-notes'> -      what's new</a>.<a class='vimiumReset close-button' href='#'>×</a>" -    links = HUD.upgradeNotificationElement().getElementsByTagName("a") -    links[0].addEventListener("click", HUD.onUpdateLinkClicked, false) -    links[1].addEventListener "click", (event) -> -      event.preventDefault() -      HUD.onUpdateLinkClicked() -    Tween.fade(HUD.upgradeNotificationElement(), 1.0, 150) - -  onUpdateLinkClicked: (event) -> -    HUD.hideUpgradeNotification() -    chrome.runtime.sendMessage({ handler: "upgradeNotificationClosed" }) - -  hideUpgradeNotification: (clickEvent) -> -    Tween.fade(HUD.upgradeNotificationElement(), 0, 150, -      -> HUD.upgradeNotificationElement().style.display = "none") -    #    # Retrieves the HUD HTML element.    # @@ -1150,26 +1124,23 @@ HUD =        HUD._displayElement.style.right = "150px"      HUD._displayElement -  upgradeNotificationElement: -> -    if (!HUD._upgradeNotificationElement) -      HUD._upgradeNotificationElement = HUD.createHudElement() -      # Position this just to the left of our normal HUD. -      HUD._upgradeNotificationElement.style.right = "315px" -    HUD._upgradeNotificationElement -    createHudElement: ->      element = document.createElement("div")      element.className = "vimiumReset vimiumHUD"      document.body.appendChild(element)      element -  hide: (immediate) -> +  # Hide the HUD. +  # If :immediate is falsy, then the HUD is faded out smoothly (otherwise it is hidden immediately). +  # If :updateIndicator is truthy, then we also refresh the mode indicator.  The only time we don't update the +  # mode indicator, is when hide() is called for the mode indicator itself. +  hide: (immediate = false, updateIndicator = true) ->      clearInterval(HUD._tweenId) -    if (immediate) -      HUD.displayElement().style.display = "none" +    if immediate +      HUD.displayElement().style.display = "none" unless updateIndicator +      Mode.setIndicator() if updateIndicator      else -      HUD._tweenId = Tween.fade(HUD.displayElement(), 0, 150, -        -> HUD.displayElement().style.display = "none") +      HUD._tweenId = Tween.fade HUD.displayElement(), 0, 150, -> HUD.hide true, updateIndicator    isReady: -> document.body != null | 
