diff options
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | background_scripts/commands.coffee | 4 | ||||
| -rw-r--r-- | background_scripts/completion_engines.coffee | 12 | ||||
| -rw-r--r-- | background_scripts/main.coffee | 29 | ||||
| -rw-r--r-- | content_scripts/hud.coffee | 29 | ||||
| -rw-r--r-- | content_scripts/link_hints.coffee | 67 | ||||
| -rw-r--r-- | content_scripts/mode_normal.coffee | 29 | ||||
| -rw-r--r-- | content_scripts/mode_visual.coffee | 2 | ||||
| -rw-r--r-- | content_scripts/scroller.coffee | 9 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 2 | ||||
| -rw-r--r-- | lib/clipboard.coffee | 9 | ||||
| -rw-r--r-- | lib/dom_utils.coffee | 15 | ||||
| -rw-r--r-- | lib/handler_stack.coffee | 5 | ||||
| -rw-r--r-- | lib/keyboard_utils.coffee | 5 | ||||
| -rw-r--r-- | manifest.json | 3 | ||||
| -rw-r--r-- | pages/help_dialog.coffee | 2 | ||||
| -rw-r--r-- | pages/hud.coffee | 14 | ||||
| -rw-r--r-- | pages/hud.html | 1 | ||||
| -rw-r--r-- | pages/options.html | 7 | ||||
| -rw-r--r-- | tests/unit_tests/handler_stack_test.coffee | 1 | 
20 files changed, 179 insertions, 71 deletions
| @@ -173,6 +173,11 @@ In `master` (not yet released)  - Backup and restore Vimium options (see the very bottom of the options page, below *Advanced Options*).  - It is now possible to map `<tab>`, `<enter>`, `<delete>`, `<insert>`, `<home>` and `<end>`. +- New command options for `createTab` to create create new normal and incognito windows +  ([examples](https://github.com/philc/vimium/wiki/Tips-and-Tricks#creating-tabs-with-urls-and-windows)). +- When upgrading, you will be asked to re-validate permissions.  The only new +  permission is "copy and paste to/from clipboard" (the `clipboardWrite` +  permission).  This is necessary to support copy/paste on Firefox.  1.61 (2017-10-27) diff --git a/background_scripts/commands.coffee b/background_scripts/commands.coffee index 7e171d31..4d2e1606 100644 --- a/background_scripts/commands.coffee +++ b/background_scripts/commands.coffee @@ -340,8 +340,8 @@ commandDescriptions =    toggleViewSource: ["View page source", { noRepeat: true }]    copyCurrentUrl: ["Copy the current URL to the clipboard", { noRepeat: true }] -  openCopiedUrlInCurrentTab: ["Open the clipboard's URL in the current tab", { background: true, noRepeat: true }] -  openCopiedUrlInNewTab: ["Open the clipboard's URL in a new tab", { background: true, repeatLimit: 20 }] +  openCopiedUrlInCurrentTab: ["Open the clipboard's URL in the current tab", { noRepeat: true }] +  openCopiedUrlInNewTab: ["Open the clipboard's URL in a new tab", { repeatLimit: 20 }]    enterInsertMode: ["Enter insert mode", { noRepeat: true }]    passNextKey: ["Pass the next key to the page"] diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 0a53ad14..a6ff6dc3 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -163,6 +163,17 @@ class Qwant extends BaseEngine    parse: (xhr) ->      suggestion.value for suggestion in JSON.parse(xhr.responseText).data.items +class UpToDate extends BaseEngine +  constructor: -> +    super +      engineUrl: "https://www.uptodate.com/services/app/contents/search/autocomplete/json?term=%s&limit=10" +      regexps: "^https?://www\\.uptodate\\.com/" +      example: +        searchUrl: "https://www.uptodate.com/contents/search?search=%s&searchType=PLAIN_TEXT&source=USER_INPUT&searchControl=TOP_PULLDOWN&autoComplete=false" +        keyword: "upto" + +  parse: (xhr) -> JSON.parse(xhr.responseText).data.searchTerms +  # A dummy search engine which is guaranteed to match any search URL, but never produces completions.  This  # allows the rest of the logic to be written knowing that there will always be a completion engine match.  class DummyCompletionEngine extends BaseEngine @@ -183,6 +194,7 @@ CompletionEngines = [    AmazonJapan    Webster    Qwant +  UpToDate    DummyCompletionEngine  ] diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 8f095ef1..8220545d 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -114,7 +114,7 @@ TabOperations =      canUseOpenerTabId = not (Utils.isFirefox() and Utils.compareVersions(Utils.firefoxVersion(), "57") < 0)      tabConfig.openerTabId = request.tab.id if canUseOpenerTabId -    chrome.tabs.create tabConfig, callback +    chrome.tabs.create tabConfig, -> callback request    # Opens request.url in new window and switches to it.    openUrlInNewWindow: (request, callback = (->)) -> @@ -188,13 +188,20 @@ BackgroundCommands =              [if request.tab.incognito then "chrome://newtab" else chrome.runtime.getURL newTabUrl]            else              [newTabUrl] -    urls = request.urls[..].reverse() -    do openNextUrl = (request) -> -      if 0 < urls.length -        TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), (tab) -> -          openNextUrl extend request, {tab, tabId: tab.id} -      else -        callback request +    if request.registryEntry.options.incognito or request.registryEntry.options.window +      windowConfig = +        url: request.urls +        focused: true +        incognito: request.registryEntry.options.incognito ? false +      chrome.windows.create windowConfig, -> callback request +    else +      urls = request.urls[..].reverse() +      do openNextUrl = (request) -> +        if 0 < urls.length +          TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), (tab) -> +            openNextUrl extend request, {tab, tabId: tab.id} +        else +          callback request    duplicateTab: mkRepeatCommand (request, callback) ->      chrome.tabs.duplicate request.tabId, (tab) -> callback extend request, {tab, tabId: tab.id}    moveTabToNewWindow: ({count, tab}) -> @@ -214,8 +221,6 @@ BackgroundCommands =        startTabIndex = Math.max 0, Math.min activeTabIndex, tabs.length - count        chrome.tabs.remove (tab.id for tab in tabs[startTabIndex...startTabIndex + count])    restoreTab: mkRepeatCommand (request, callback) -> chrome.sessions.restore null, callback request -  openCopiedUrlInCurrentTab: (request) -> TabOperations.openUrlInCurrentTab extend request, url: Clipboard.paste() -  openCopiedUrlInNewTab: (request) -> @createTab extend request, url: Clipboard.paste()    togglePinTab: ({tab}) -> chrome.tabs.update tab.id, {pinned: !tab.pinned}    toggleMuteTab: toggleMuteTab    moveTabLeft: moveTab @@ -423,7 +428,7 @@ sendRequestHandlers =    # getCurrentTabUrl is used by the content scripts to get their full URL, because window.location cannot help    # with Chrome-specific URLs like "view-source:http:..".    getCurrentTabUrl: ({tab}) -> tab.url -  openUrlInNewTab: (request) -> TabOperations.openUrlInNewTab request +  openUrlInNewTab: mkRepeatCommand (request, callback) -> TabOperations.openUrlInNewTab request, callback    openUrlInNewWindow: (request) -> TabOperations.openUrlInNewWindow request    openUrlInIncognito: (request) -> chrome.windows.create incognito: true, url: Utils.convertToUrl request.url    openUrlInCurrentTab: TabOperations.openUrlInCurrentTab @@ -431,8 +436,6 @@ sendRequestHandlers =      chrome.tabs.create url: chrome.runtime.getURL("pages/options.html"), index: request.tab.index + 1    frameFocused: handleFrameFocused    nextFrame: BackgroundCommands.nextFrame -  copyToClipboard: Clipboard.copy.bind Clipboard -  pasteFromClipboard: Clipboard.paste.bind Clipboard    selectSpecificTab: selectSpecificTab    createMark: Marks.create.bind(Marks)    gotoMark: Marks.goto.bind(Marks) diff --git a/content_scripts/hud.coffee b/content_scripts/hud.coffee index 7c983cfa..42a960da 100644 --- a/content_scripts/hud.coffee +++ b/content_scripts/hud.coffee @@ -9,6 +9,8 @@ HUD =    findMode: null    abandon: -> @hudUI?.hide false +  pasteListener: null # Set by @pasteFromClipboard to handle the value returned by pasteResponse +    # 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    # it doesn't sit on top of horizontal scrollbars like Chrome's HUD does. @@ -82,6 +84,33 @@ HUD =      @findMode.exit()      postExit?() +  # These commands manage copying and pasting from the clipboard in the HUD frame. +  # NOTE(mrmr1993): We need this to copy and paste on Firefox: +  # * an element can't be focused in the background page, so copying/pasting doesn't work +  # * we don't want to disrupt the focus in the page, in case the page is listening for focus/blur events. +  # * the HUD shouldn't be active for this frame while any of the copy/paste commands are running. +  copyToClipboard: (text) -> +    DomUtils.documentComplete => +      @init() +      @hudUI?.postMessage {name: "copyToClipboard", data: text} + +  pasteFromClipboard: (@pasteListener) -> +    DomUtils.documentComplete => +      @init() +      # Show the HUD frame, so Firefox will actually perform the paste. +      @hudUI.toggleIframeElementClasses "vimiumUIComponentHidden", "vimiumUIComponentVisible" +      @tween.fade 0, 0 +      @hudUI.postMessage {name: "pasteFromClipboard"} + +  pasteResponse: ({data}) -> +    # Hide the HUD frame again. +    @hudUI.toggleIframeElementClasses "vimiumUIComponentVisible", "vimiumUIComponentHidden" +    @unfocusIfFocused() +    @pasteListener data + +  unfocusIfFocused: -> +    document.activeElement.blur() if document.activeElement == @hudUI?.iframeElement +  class Tween    opacity: 0    intervalId: -1 diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 2fa27d26..6be163ef 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -31,7 +31,7 @@ COPY_LINK_URL =    indicator: "Copy link URL to Clipboard"    linkActivator: (link) ->      if link.href? -      chrome.runtime.sendMessage handler: "copyToClipboard", data: link.href +      HUD.copyToClipboard link.href        url = link.href        url = url[0..25] + "...." if 28 < url.length        HUD.showForDuration "Yanked #{url}", 2000 @@ -232,13 +232,9 @@ class LinkHintsMode    onKeyDownInMode: (event) ->      return if event.repeat -    previousTabCount = @tabCount -    @tabCount = 0 -      # NOTE(smblott) The modifier behaviour here applies only to alphabet hints.      if event.key in ["Control", "Shift"] and not Settings.get("filterLinkHints") and        @mode in [ OPEN_IN_CURRENT_TAB, OPEN_WITH_QUEUE, OPEN_IN_NEW_BG_TAB, OPEN_IN_NEW_FG_TAB ] -        @tabCount = previousTabCount          # Toggle whether to open the link in a new or current tab.          previousMode = @mode          key = event.key @@ -249,19 +245,16 @@ class LinkHintsMode            when "Control"              @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB) -        handlerId = handlerStack.push +        handlerId = @hintMode.push            keyup: (event) =>              if event.key == key                handlerStack.remove()                @setOpenLinkMode previousMode              true # Continue bubbling the event. -        # For some (unknown) reason, we don't always receive the keyup event needed to remove this handler. -        # Therefore, we ensure that it's always removed when hint mode exits.  See #1911 and #1926. -        @hintMode.onExit -> handlerStack.remove handlerId -      else if KeyboardUtils.isBackspace event        if @markerMatcher.popKeyChar() +        @tabCount = 0          @updateVisibleMarkers()        else          # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and @@ -273,15 +266,13 @@ class LinkHintsMode        HintCoordinator.sendMessage "activateActiveHintMarker" if @markerMatcher.activeHintMarker      else if event.key == "Tab" -      @tabCount = previousTabCount + (if event.shiftKey then -1 else 1) -      @updateVisibleMarkers @tabCount +      if event.shiftKey then @tabCount-- else @tabCount++ +      @updateVisibleMarkers()      else if event.key == " " and @markerMatcher.shouldRotateHints event -      @tabCount = previousTabCount        HintCoordinator.sendMessage "rotateHints"      else -      @tabCount = previousTabCount if event.ctrlKey or event.metaKey or event.altKey        unless event.repeat          keyChar =            if Settings.get "filterLinkHints" @@ -291,17 +282,18 @@ class LinkHintsMode          if keyChar            keyChar = " " if keyChar == "space"            if keyChar.length == 1 +            @tabCount = 0              @markerMatcher.pushKeyChar keyChar              @updateVisibleMarkers() -          handlerStack.suppressEvent -      return +          else +            return handlerStack.suppressPropagation -    # We've handled the event, so suppress it and update the mode indicator. -    DomUtils.suppressEvent event +    handlerStack.suppressEvent -  updateVisibleMarkers: (tabCount = 0) -> +  updateVisibleMarkers: ->      {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher -    HintCoordinator.sendMessage "updateKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount} +    HintCoordinator.sendMessage "updateKeyState", +      {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount: @tabCount}    updateKeyState: ({hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->      extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue} @@ -310,7 +302,7 @@ class LinkHintsMode      if linksMatched.length == 0        @deactivateMode()      else if linksMatched.length == 1 -      @activateLink linksMatched[0], userMightOverType ? false +      @activateLink linksMatched[0], userMightOverType      else        @hideMarker marker for marker in @hintMarkers        @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched @@ -364,7 +356,7 @@ class LinkHintsMode    # When only one hint remains, activate it in the appropriate way.  The current frame may or may not contain    # the matched link, and may or may not have the focus.  The resulting four cases are accounted for here by    # selectively pushing the appropriate HintCoordinator.onExit handlers. -  activateLink: (linkMatched, userMightOverType=false) -> +  activateLink: (linkMatched, userMightOverType = false) ->      @removeHintMarkers()      if linkMatched.isLocalMarker @@ -390,25 +382,26 @@ class LinkHintsMode                clickEl.focus()              linkActivator clickEl -    installKeyboardBlocker = (startKeyboardBlocker) -> -      if linkMatched.isLocalMarker -        {top: viewportTop, left: viewportLeft} = DomUtils.getViewportTopLeft() -        for rect in (Rect.copy rect for rect in clickEl.getClientRects()) -          extend rect, top: rect.top + viewportTop, left: rect.left + viewportLeft -          flashEl = DomUtils.addFlashRect rect -          do (flashEl) -> HintCoordinator.onExit.push -> DomUtils.removeElement flashEl - -      if windowIsFocused() -        startKeyboardBlocker (isSuccess) -> HintCoordinator.sendMessage "exit", {isSuccess} +    # If flash elements are created, then this function can be used later to remove them. +    removeFlashElements = -> +    if linkMatched.isLocalMarker +      {top: viewportTop, left: viewportLeft} = DomUtils.getViewportTopLeft() +      flashElements = for rect in clickEl.getClientRects() +        DomUtils.addFlashRect Rect.translate rect, viewportLeft, viewportTop +      removeFlashElements = -> DomUtils.removeElement flashEl for flashEl in flashElements      # If we're using a keyboard blocker, then the frame with the focus sends the "exit" message, otherwise the      # frame containing the matched link does. -    if userMightOverType and Settings.get "waitForEnterForFilteredHints" -      installKeyboardBlocker (callback) -> new WaitForEnter callback -    else if userMightOverType -      installKeyboardBlocker (callback) -> new TypingProtector 200, callback +    if userMightOverType +      HintCoordinator.onExit.push removeFlashElements +      if windowIsFocused() +        callback = (isSuccess) -> HintCoordinator.sendMessage "exit", {isSuccess} +        if Settings.get "waitForEnterForFilteredHints" +          new WaitForEnter callback +        else +          new TypingProtector 200, callback      else if linkMatched.isLocalMarker -      DomUtils.flashRect linkMatched.rect +      Utils.setTimeout 400, removeFlashElements        HintCoordinator.sendMessage "exit", isSuccess: true    # diff --git a/content_scripts/mode_normal.coffee b/content_scripts/mode_normal.coffee index ee05f4b0..1fe0618e 100644 --- a/content_scripts/mode_normal.coffee +++ b/content_scripts/mode_normal.coffee @@ -91,10 +91,18 @@ NormalModeCommands =    copyCurrentUrl: ->      chrome.runtime.sendMessage { handler: "getCurrentTabUrl" }, (url) -> -      chrome.runtime.sendMessage { handler: "copyToClipboard", data: url } +      HUD.copyToClipboard url        url = url[0..25] + "...." if 28 < url.length        HUD.showForDuration("Yanked #{url}", 2000) +  openCopiedUrlInNewTab: (count) -> +    HUD.pasteFromClipboard (url) -> +      chrome.runtime.sendMessage { handler: "openUrlInNewTab", url, count } + +  openCopiedUrlInCurrentTab: -> +    HUD.pasteFromClipboard (url) -> +      chrome.runtime.sendMessage { handler: "openUrlInCurrentTab", url } +    # Mode changes.    enterInsertMode: ->      # If a focusable element receives the focus, then we exit and leave the permanently-installed insert-mode @@ -144,7 +152,23 @@ NormalModeCommands =        for i in [0...resultSet.snapshotLength] by 1          element = resultSet.snapshotItem i          continue unless DomUtils.getVisibleClientRect element, true -        { element, rect: Rect.copy element.getBoundingClientRect() } +        { element, index: i, rect: Rect.copy element.getBoundingClientRect() } + +    visibleInputs.sort ({element: element1, index: i1}, {element: element2, index: i2}) -> +      # Put elements with a lower positive tabIndex first, keeping elements in DOM order. +      if element1.tabIndex > 0 +        if element2.tabIndex > 0 +          tabDifference = element1.tabIndex - element2.tabIndex +          if tabDifference != 0 +            tabDifference +          else +            i1 - i2 +        else +          -1 +      else if element2.tabIndex > 0 +        1 +      else +        i1 - i2      if visibleInputs.length == 0        HUD.showForDuration("There are no inputs to focus.", 1000) @@ -153,7 +177,6 @@ NormalModeCommands =      # This is a hack to improve usability on the Vimium options page.  We prime the recently-focused input      # to be the key-mappings input.  Arguably, this is the input that the user is most likely to use.      recentlyFocusedElement = lastFocusedInput() -    recentlyFocusedElement ?= document.getElementById "keyMappings" if window.isVimiumOptionsPage      selectedInputIndex =        if count == 1 diff --git a/content_scripts/mode_visual.coffee b/content_scripts/mode_visual.coffee index f99e42f9..4c6578cd 100644 --- a/content_scripts/mode_visual.coffee +++ b/content_scripts/mode_visual.coffee @@ -312,7 +312,7 @@ class VisualMode extends KeyHandlerMode    yank: (args = {}) ->      @yankedText = @selection.toString()      @exit() -    chrome.runtime.sendMessage handler: "copyToClipboard", data: @yankedText +    HUD.copyToClipboard @yankedText      message = @yankedText.replace /\s+/g, " "      message = message[...12] + "..." if 15 < @yankedText.length diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index 4a6c7edf..f65062e4 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -95,7 +95,14 @@ findScrollableElement = (element, direction, amount, factor) ->  # On some pages, the scrolling element is not actually scrollable.  Here, we search the document for the  # largest visible element which does scroll vertically. This is used to initialize activatedElement. See  # #1358. -firstScrollableElement = (element=getScrollingElement()) -> +firstScrollableElement = (element = null) -> +  unless element +    scrollingElement = getScrollingElement() +    if doesScroll(scrollingElement, "y", 1, 1) or doesScroll(scrollingElement, "y", -1, 1) +      return scrollingElement +    else +      element = document.body ? getScrollingElement() +    if doesScroll(element, "y", 1, 1) or doesScroll(element, "y", -1, 1)      element    else diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 492a82e5..432fa7a2 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -157,6 +157,7 @@ initializePreDomReady = ->  # Wrapper to install event listeners.  Syntactic sugar.  installListener = (element, event, callback) ->    element.addEventListener(event, forTrusted(-> +    root.extend window, root unless extend? # See #2800.      if isEnabledForUrl then callback.apply(this, arguments) else true    ), true) @@ -220,6 +221,7 @@ Frame =      @port = chrome.runtime.connect name: "frames"      @port.onMessage.addListener (request) => +      root.extend window, root unless extend? # See #2800 and #2831.        (@listeners[request.handler] ? this[request.handler]) request      # We disable the content scripts when we lose contact with the background page, or on unload. diff --git a/lib/clipboard.coffee b/lib/clipboard.coffee index 1d378e76..a9e2e82e 100644 --- a/lib/clipboard.coffee +++ b/lib/clipboard.coffee @@ -1,8 +1,9 @@  Clipboard = -  _createTextArea: -> -    textArea = document.createElement "textarea" +  _createTextArea: (tagName = "textarea") -> +    textArea = document.createElement tagName      textArea.style.position = "absolute"      textArea.style.left = "-100%" +    textArea.contentEditable = "true"      textArea    # http://groups.google.com/group/chromium-extensions/browse_thread/thread/49027e7f3b04f68/f6ab2457dee5bf55 @@ -16,11 +17,11 @@ Clipboard =      document.body.removeChild(textArea)    paste: -> -    textArea = @_createTextArea() +    textArea = @_createTextArea "div" # Use a <div> so Firefox pastes rich text.      document.body.appendChild(textArea)      textArea.focus()      document.execCommand("Paste") -    value = textArea.value +    value = textArea.innerText      document.body.removeChild(textArea)      value diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index d8a5d203..67d5a44c 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -344,7 +344,7 @@ DomUtils =    consumeKeyup: do ->      handlerId = null -    (event, callback = null) -> +    (event, callback = null, suppressPropagation) ->        unless event.repeat          handlerStack.remove handlerId if handlerId?          code = event.code @@ -353,15 +353,22 @@ DomUtils =            keyup: (event) ->              return handlerStack.continueBubbling unless event.code == code              @remove() -            DomUtils.suppressEvent event +            if suppressPropagation +              DomUtils.suppressPropagation event +            else +              DomUtils.suppressEvent event              handlerStack.continueBubbling            # We cannot track keyup events if we lose the focus.            blur: (event) ->              @remove() if event.target == window              handlerStack.continueBubbling        callback?() -      @suppressEvent event -      handlerStack.suppressEvent +      if suppressPropagation +        DomUtils.suppressPropagation event +        handlerStack.suppressPropagation +      else +        DomUtils.suppressEvent event +        handlerStack.suppressEvent    # Polyfill for selection.type (which is not available in Firefox).    getSelectionType: (selection = document.getSelection()) -> diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 646ddfbd..a43fc356 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -57,7 +57,10 @@ class HandlerStack          if result == @passEventToPage            return true          else if result == @suppressPropagation -          DomUtils.suppressPropagation event +          if type == "keydown" +            DomUtils.consumeKeyup event, null, true +          else +            DomUtils.suppressPropagation event            return false          else if result == @restartBubbling            return @bubbleEvent type, event diff --git a/lib/keyboard_utils.coffee b/lib/keyboard_utils.coffee index 70aa5a95..09623e50 100644 --- a/lib/keyboard_utils.coffee +++ b/lib/keyboard_utils.coffee @@ -36,6 +36,8 @@ KeyboardUtils =        ""      else if key of @keyNames        @keyNames[key] +    else if @isModifier event +      "" # Don't resolve modifier keys.      else if key.length == 1        key      else @@ -70,6 +72,9 @@ KeyboardUtils =    isPrintable: (event) ->      @getKeyCharString(event)?.length == 1 +  isModifier: (event) -> +    event.key in ["Control", "Shift", "Alt", "OS", "AltGraph", "Meta"] +    enUsTranslations:      "Backquote":     ["`", "~"]      "Minus":         ["-", "_"] diff --git a/manifest.json b/manifest.json index 71d7caba..ee5b0655 100644 --- a/manifest.json +++ b/manifest.json @@ -6,13 +6,13 @@    "icons": {  "16": "icons/icon16.png",                "48": "icons/icon48.png",               "128": "icons/icon128.png" }, +  "minimum_chrome_version": "51.0",    "background": {      "scripts": [        "lib/utils.js",        "lib/settings.js",        "background_scripts/bg_utils.js",        "background_scripts/commands.js", -      "lib/clipboard.js",        "background_scripts/exclusions.js",        "background_scripts/completion_engines.js",        "background_scripts/completion_search.js", @@ -31,6 +31,7 @@      "bookmarks",      "history",      "clipboardRead", +    "clipboardWrite",      "storage",      "sessions",      "notifications", diff --git a/pages/help_dialog.coffee b/pages/help_dialog.coffee index f36155e4..08180a72 100644 --- a/pages/help_dialog.coffee +++ b/pages/help_dialog.coffee @@ -83,7 +83,7 @@ HelpDialog =                commandNameElement.textContent = command.command                commandNameElement.title = "Click to copy \"#{command.command}\" to clipboard."                commandNameElement.addEventListener "click", -> -                chrome.runtime.sendMessage handler: "copyToClipboard", data: commandNameElement.textContent +                HUD.copyToClipboard commandNameElement.textContent                  HUD.showForDuration("Yanked #{commandNameElement.textContent}.", 2000)        @showAdvancedCommands(@getShowAdvancedCommands()) diff --git a/pages/hud.coffee b/pages/hud.coffee index 0d2ec2f7..5ff2e07e 100644 --- a/pages/hud.coffee +++ b/pages/hud.coffee @@ -95,5 +95,19 @@ handlers =        " (No matches)"      countElement.textContent = if showMatchText then countText else "" +  copyToClipboard: (data) -> +    focusedElement = document.activeElement +    Clipboard.copy data +    focusedElement?.focus() +    window.parent.focus() +    UIComponentServer.postMessage {name: "unfocusIfFocused"} + +  pasteFromClipboard: -> +    focusedElement = document.activeElement +    data = Clipboard.paste() +    focusedElement?.focus() +    window.parent.focus() +    UIComponentServer.postMessage {name: "pasteResponse", data} +  UIComponentServer.registerHandler ({data}) -> handlers[data.name ? data]? data  FindModeHistory.init() diff --git a/pages/hud.html b/pages/hud.html index 3e8cf976..7bd27171 100644 --- a/pages/hud.html +++ b/pages/hud.html @@ -7,6 +7,7 @@      <script type="text/javascript" src="../lib/settings.js"></script>      <script type="text/javascript" src="../lib/keyboard_utils.js"></script>      <script type="text/javascript" src="../lib/find_mode_history.js"></script> +    <script type="text/javascript" src="../lib/clipboard.js"></script>      <script type="text/javascript" src="ui_component_server.js"></script>      <script type="text/javascript" src="hud.js"></script>    </head> diff --git a/pages/options.html b/pages/options.html index ec940cb2..b118bbd9 100644 --- a/pages/options.html +++ b/pages/options.html @@ -67,7 +67,7 @@ unmapAll                  <a href="#" id="showCommands">Show available commands</a>.                </div>              </div> -            <textarea id="keyMappings" type="text"></textarea> +            <textarea id="keyMappings" type="text" tabIndex="1"></textarea>            </td>          </tr>          <tr> @@ -272,11 +272,12 @@ b: http://b.com/?q=%s description              </td>            </tr>            <tr> -            <td class="caption">CSS for link hints</td> +            <td class="caption">CSS for Vimium UI</td>              <td verticalAlign="top">                <div class="help">                  <div class="example"> -                  The CSS used to style the characters next to each link hint.<br/><br/> +                  These styles are applied to link hints, the Vomnibar, the help dialog, the exclusions pop-up and the HUD.<br /> +                  By default, this CSS is used to style the characters next to each link hint.<br/><br/>                    These styles are used in addition to and take precedence over Vimium's                    default styles.                  </div> diff --git a/tests/unit_tests/handler_stack_test.coffee b/tests/unit_tests/handler_stack_test.coffee index 7b62af07..374c235b 100644 --- a/tests/unit_tests/handler_stack_test.coffee +++ b/tests/unit_tests/handler_stack_test.coffee @@ -4,6 +4,7 @@ extend(global, require "../../lib/handler_stack.js")  context "handlerStack",    setup ->      stub global, "DomUtils", {} +    stub DomUtils, "consumeKeyup", ->      stub DomUtils, "suppressEvent", ->      stub DomUtils, "suppressPropagation", ->      @handlerStack = new HandlerStack | 
