aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--background_scripts/commands.coffee4
-rw-r--r--background_scripts/completion_engines.coffee12
-rw-r--r--background_scripts/main.coffee29
-rw-r--r--content_scripts/hud.coffee29
-rw-r--r--content_scripts/link_hints.coffee67
-rw-r--r--content_scripts/mode_normal.coffee29
-rw-r--r--content_scripts/mode_visual.coffee2
-rw-r--r--content_scripts/scroller.coffee9
-rw-r--r--content_scripts/vimium_frontend.coffee2
-rw-r--r--lib/clipboard.coffee9
-rw-r--r--lib/dom_utils.coffee15
-rw-r--r--lib/handler_stack.coffee5
-rw-r--r--lib/keyboard_utils.coffee5
-rw-r--r--manifest.json3
-rw-r--r--pages/help_dialog.coffee2
-rw-r--r--pages/hud.coffee14
-rw-r--r--pages/hud.html1
-rw-r--r--pages/options.html7
-rw-r--r--tests/unit_tests/handler_stack_test.coffee1
20 files changed, 179 insertions, 71 deletions
diff --git a/README.md b/README.md
index 5e0d18d7..4eef7cb0 100644
--- a/README.md
+++ b/README.md
@@ -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