aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CREDITS1
-rw-r--r--README.md2
-rw-r--r--background_scripts/completion.coffee26
-rw-r--r--background_scripts/main.coffee129
-rw-r--r--background_scripts/settings.coffee23
-rw-r--r--content_scripts/link_hints.coffee95
-rw-r--r--content_scripts/mode.coffee68
-rw-r--r--content_scripts/mode_find.coffee9
-rw-r--r--content_scripts/mode_insert.coffee8
-rw-r--r--content_scripts/mode_passkeys.coffee4
-rw-r--r--content_scripts/mode_visual_edit.coffee23
-rw-r--r--content_scripts/scroller.coffee3
-rw-r--r--content_scripts/vimium_frontend.coffee123
-rw-r--r--lib/handler_stack.coffee6
-rw-r--r--manifest.json1
-rw-r--r--pages/help_dialog.html1
-rw-r--r--pages/options.coffee10
-rw-r--r--tests/dom_tests/dom_tests.coffee56
-rw-r--r--tests/unit_tests/completion_test.coffee8
-rw-r--r--tests/unit_tests/settings_test.coffee2
20 files changed, 223 insertions, 375 deletions
diff --git a/CREDITS b/CREDITS
index 60a5acaa..539b998c 100644
--- a/CREDITS
+++ b/CREDITS
@@ -42,5 +42,6 @@ Contributors:
Werner Laurensse (github: ab3)
Timo Sand <timo.j.sand@gmail.com> (github: deiga)
Shiyong Chen <billbill290@gmail.com> (github: UncleBill)
+ Utkarsh Upadhyay <musically.ut@gmail.com) (github: musically-ut)
Feel free to add real names in addition to GitHub usernames.
diff --git a/README.md b/README.md
index 36d176e6..96cde9bb 100644
--- a/README.md
+++ b/README.md
@@ -174,7 +174,7 @@ Release Notes
- Added `gU`, which goes to the root of the current URL.
- Added `yt`, which duplicates the current tab.
- Added `W`, which moves the current tab to a new window.
-- Added marks for saving and jumping to sections of a page. `mX` to set a mark and `X` to return to it.
+- Added marks for saving and jumping to sections of a page. `mX` to set a mark and `` `X`` to return to it.
- Added "LinkHints.activateModeToOpenIncognito", currently an advanced, unbound command.
- Disallowed repeat tab closings, since this causes trouble for many people.
- Update our Chrome APIs so Vimium works on Chrome 28+.
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee
index 177892fb..6a1c0d30 100644
--- a/background_scripts/completion.coffee
+++ b/background_scripts/completion.coffee
@@ -204,7 +204,7 @@ class DomainCompleter
domains: null
filter: (queryTerms, onComplete) ->
- return onComplete([]) if queryTerms.length > 1
+ return onComplete([]) unless queryTerms.length == 1
if @domains
@performSearch(queryTerms, onComplete)
else
@@ -344,11 +344,33 @@ class SearchEngineCompleter
computeRelevancy: -> 1
refresh: ->
- this.searchEngines = root.Settings.getSearchEngines()
+ @searchEngines = SearchEngineCompleter.getSearchEngines()
getSearchEngineMatches: (queryTerms) ->
(1 < queryTerms.length and @searchEngines[queryTerms[0]]) or {}
+ # Static data and methods for parsing the configured search engines. We keep a cache of the search-engine
+ # mapping in @searchEnginesMap.
+ @searchEnginesMap: null
+
+ # Parse the custom search engines setting and cache it in SearchEngineCompleter.searchEnginesMap.
+ @parseSearchEngines: (searchEnginesText) ->
+ searchEnginesMap = SearchEngineCompleter.searchEnginesMap = {}
+ for line in searchEnginesText.split /\n/
+ tokens = line.trim().split /\s+/
+ continue if tokens.length < 2 or tokens[0].startsWith('"') or tokens[0].startsWith("#")
+ keywords = tokens[0].split ":"
+ continue unless keywords.length == 2 and not keywords[1] # So, like: [ "w", "" ].
+ searchEnginesMap[keywords[0]] =
+ url: tokens[1]
+ description: tokens[2..].join(" ")
+
+ # Fetch the search-engine map, building it if necessary.
+ @getSearchEngines: ->
+ unless SearchEngineCompleter.searchEnginesMap?
+ SearchEngineCompleter.parseSearchEngines Settings.get "searchEngines"
+ SearchEngineCompleter.searchEnginesMap
+
# A completer which calls filter() on many completers, aggregates the results, ranks them, and returns the top
# 10. Queries from the vomnibar frontend script come through a multi completer.
class MultiCompleter
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index 0c7d9343..a26fd8a8 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -26,6 +26,7 @@ validFirstKeys = {}
singleKeyCommands = []
focusedFrame = null
frameIdsForTab = {}
+root.urlForTab = {}
# Keys are either literal characters, or "named" - for example <a-b> (alt+b), <left> (left arrow) or <f12>
# This regular expression captures two groups: the first is a named key, the second is the remainder of
@@ -57,7 +58,7 @@ completers =
bookmarks: new MultiCompleter([completionSources.bookmarks])
tabs: new MultiCompleter([completionSources.tabs])
-chrome.runtime.onConnect.addListener((port, name) ->
+chrome.runtime.onConnect.addListener (port, name) ->
senderTabId = if port.sender.tab then port.sender.tab.id else null
# If this is a tab we've been waiting to open, execute any "tab loaded" handlers, e.g. to restore
# the tab's scroll position. Wait until domReady before doing this; otherwise operations like restoring
@@ -69,14 +70,8 @@ chrome.runtime.onConnect.addListener((port, name) ->
delete tabLoadedHandlers[senderTabId]
toCall.call()
- # domReady is the appropriate time to show the "vimium has been upgraded" message.
- # TODO: This might be broken on pages with frames.
- if (shouldShowUpgradeMessage())
- chrome.tabs.sendMessage(senderTabId, { name: "showUpgradeNotification", version: currentVersion })
-
if (portHandlers[port.name])
port.onMessage.addListener(portHandlers[port.name])
-)
chrome.runtime.onMessage.addListener((request, sender, sendResponse) ->
if (sendRequestHandlers[request.handler])
@@ -183,14 +178,6 @@ openUrlInIncognito = (request) ->
chrome.windows.create({ url: Utils.convertToUrl(request.url), incognito: true})
#
-# Called when the user has clicked the close icon on the "Vimium has been updated" message.
-# We should now dismiss that message in all tabs.
-#
-upgradeNotificationClosed = (request) ->
- Settings.set("previousVersion", currentVersion)
- sendRequestToAllTabs({ name: "hideUpgradeNotification" })
-
-#
# Copies or pastes some data (request.data) to/from the clipboard.
# We return null to avoid the return value from the copy operations being passed to sendResponse.
#
@@ -368,56 +355,23 @@ updateOpenTabs = (tab, deleteFrames = false) ->
# Frames are recreated on refresh
delete frameIdsForTab[tab.id] if deleteFrames
-setBrowserActionIcon = (tabId,path) ->
- chrome.browserAction.setIcon({ tabId: tabId, path: path })
-
-chrome.browserAction.setBadgeBackgroundColor
- # This is Vimium blue (from the icon).
- # color: [102, 176, 226, 255]
- # This is a slightly darker blue. It makes the badge more striking in the corner of the eye, and the symbol
- # easier to read.
- color: [82, 156, 206, 255]
-
-setBadge = do ->
- current = null
- timer = null
- updateBadge = (badge, tabId) -> -> chrome.browserAction.setBadgeText text: badge, tabId: tabId
- (request, sender) ->
- badge = request.badge
- if badge? and badge != current
- current = badge
- clearTimeout timer if timer
- # We wait a few moments. This avoids badge flicker when there are rapid changes.
- timer = setTimeout updateBadge(badge, sender.tab.id), 50
-
-# Updates the browserAction icon to indicate whether Vimium is enabled or disabled on the current page.
-# Also propagates new enabled/disabled/passkeys state to active window, if necessary.
-# This lets you disable Vimium on a page without needing to reload.
-# Exported via root because it's called from the page popup.
-root.updateActiveState = updateActiveState = (tabId) ->
- enabledIcon = "icons/browser_action_enabled.png"
- disabledIcon = "icons/browser_action_disabled.png"
- partialIcon = "icons/browser_action_partial.png"
- chrome.tabs.get tabId, (tab) ->
- setBadge { badge: "" }, tab: { id: tabId }
- chrome.tabs.sendMessage tabId, { name: "getActiveState" }, (response) ->
- if response
- isCurrentlyEnabled = response.enabled
- currentPasskeys = response.passKeys
- config = isEnabledForUrl { url: tab.url }, { tab: tab }
- enabled = config.isEnabledForUrl
- passKeys = config.passKeys
- if (enabled and passKeys)
- setBrowserActionIcon(tabId,partialIcon)
- else if (enabled)
- setBrowserActionIcon(tabId,enabledIcon)
- else
- setBrowserActionIcon(tabId,disabledIcon)
- # Propagate the new state only if it has changed.
- if (isCurrentlyEnabled != enabled || currentPasskeys != passKeys)
- chrome.tabs.sendMessage(tabId, { name: "setState", enabled: enabled, passKeys: passKeys, incognito: tab.incognito })
- else
- setBrowserActionIcon tabId, disabledIcon
+# Here's how we set the page icon. The default is "disabled", so if we do nothing else, then we get the
+# grey-out disabled icon. Thereafter, we only set tab-specific icons, so there's no need to update the icon
+# when we visit a tab on which Vimium isn't running.
+#
+# For active tabs, when a frame starts, it requests its active state via isEnabledForUrl. We also check the
+# state every time a frame gets the focus. In both cases, the frame then updates the tab's icon accordingly.
+#
+# Exclusion rule changes (from either the options page or the page popup) propagate via the subsequent focus
+# change. In particular, whenever a frame next gets the focus, it requests its new state and sets the icon
+# accordingly.
+#
+setIcon = (request, sender) ->
+ path = switch request.icon
+ when "enabled" then "icons/browser_action_enabled.png"
+ when "partial" then "icons/browser_action_partial.png"
+ when "disabled" then "icons/browser_action_disabled.png"
+ chrome.browserAction.setIcon tabId: sender.tab.id, path: path
handleUpdateScrollPosition = (request, sender) ->
updateScrollPosition(sender.tab, request.scrollX, request.scrollY)
@@ -434,7 +388,6 @@ chrome.tabs.onUpdated.addListener (tabId, changeInfo, tab) ->
runAt: "document_start"
chrome.tabs.insertCSS tabId, cssConf, -> chrome.runtime.lastError
updateOpenTabs(tab) if changeInfo.url?
- updateActiveState(tabId)
chrome.tabs.onAttached.addListener (tabId, attachedInfo) ->
# We should update all the tabs in the old window and the new window.
@@ -469,8 +422,7 @@ chrome.tabs.onRemoved.addListener (tabId) ->
tabInfoMap.deletor = -> delete tabInfoMap[tabId]
setTimeout tabInfoMap.deletor, 1000
delete frameIdsForTab[tabId]
-
-chrome.tabs.onActiveChanged.addListener (tabId, selectInfo) -> updateActiveState(tabId)
+ delete urlForTab[tabId]
unless chrome.sessions
chrome.windows.onRemoved.addListener (windowId) -> delete tabQueue[windowId]
@@ -627,16 +579,6 @@ sendRequestToAllTabs = (args) ->
for tab in window.tabs
chrome.tabs.sendMessage(tab.id, args, null))
-#
-# Returns true if the current extension version is greater than the previously recorded version in
-# localStorage, and false otherwise.
-#
-shouldShowUpgradeMessage = ->
- # Avoid showing the upgrade notification when previousVersion is undefined, which is the case for new
- # installs.
- Settings.set("previousVersion", currentVersion) unless Settings.get("previousVersion")
- Utils.compareVersions(currentVersion, Settings.get("previousVersion")) == 1
-
openOptionsPageInNewTab = ->
chrome.tabs.getSelected(null, (tab) ->
chrome.tabs.create({ url: chrome.runtime.getURL("pages/options.html"), index: tab.index + 1 }))
@@ -654,6 +596,7 @@ unregisterFrame = (request, sender) ->
handleFrameFocused = (request, sender) ->
tabId = sender.tab.id
+ urlForTab[tabId] = request.url
if frameIdsForTab[tabId]?
frameIdsForTab[tabId] =
[request.frameId, (frameIdsForTab[tabId].filter (id) -> id != request.frameId)...]
@@ -687,7 +630,6 @@ sendRequestHandlers =
unregisterFrame: unregisterFrame
frameFocused: handleFrameFocused
nextFrame: (request) -> BackgroundCommands.nextFrame 1, request.frameId
- upgradeNotificationClosed: upgradeNotificationClosed
updateScrollPosition: handleUpdateScrollPosition
copyToClipboard: copyToClipboard
pasteFromClipboard: pasteFromClipboard
@@ -696,7 +638,7 @@ sendRequestHandlers =
refreshCompleter: refreshCompleter
createMark: Marks.create.bind(Marks)
gotoMark: Marks.goto.bind(Marks)
- setBadge: setBadge
+ setIcon: setIcon
sendMessageToFrames: sendMessageToFrames
log: bgLog
@@ -727,8 +669,30 @@ if Settings.has("keyMappings")
populateValidFirstKeys()
populateSingleKeyCommands()
-if shouldShowUpgradeMessage()
- sendRequestToAllTabs({ name: "showUpgradeNotification", version: currentVersion })
+
+# Show notification on upgrade.
+showUpgradeMessage = ->
+ # Avoid showing the upgrade notification when previousVersion is undefined, which is the case for new
+ # installs.
+ Settings.set "previousVersion", currentVersion unless Settings.get "previousVersion"
+ if Utils.compareVersions(currentVersion, Settings.get "previousVersion" ) == 1
+ notificationId = "VimiumUpgradeNotification"
+ notification =
+ type: "basic"
+ iconUrl: chrome.runtime.getURL "icons/vimium.png"
+ title: "Vimium Upgrade"
+ message: "Vimium has been upgraded to version #{currentVersion}. Click here for more information."
+ isClickable: true
+ if chrome.notifications?.create?
+ chrome.notifications.create notificationId, notification, ->
+ unless chrome.runtime.lastError
+ Settings.set "previousVersion", currentVersion
+ chrome.notifications.onClicked.addListener (id) ->
+ if id == notificationId
+ openUrlInNewTab url: "https://github.com/philc/vimium#release-notes"
+ else
+ # We need to wait for the user to accept the "notifications" permission.
+ chrome.permissions.onAdded.addListener showUpgradeMessage
# Ensure that tabInfoMap is populated when Vimium is installed.
chrome.windows.getAll { populate: true }, (windows) ->
@@ -741,3 +705,4 @@ chrome.windows.getAll { populate: true }, (windows) ->
# Start pulling changes from synchronized storage.
Sync.init()
+showUpgradeMessage()
diff --git a/background_scripts/settings.coffee b/background_scripts/settings.coffee
index 3528e8a9..a4d95c81 100644
--- a/background_scripts/settings.coffee
+++ b/background_scripts/settings.coffee
@@ -33,7 +33,7 @@ root.Settings = Settings =
root.refreshCompletionKeysAfterMappingSave()
searchEngines: (value) ->
- root.Settings.parseSearchEngines value
+ root.SearchEngineCompleter.parseSearchEngines value
exclusionRules: (value) ->
root.Exclusions.postUpdateHook value
@@ -42,27 +42,6 @@ root.Settings = Settings =
performPostUpdateHook: (key, value) ->
@postUpdateHooks[key] value if @postUpdateHooks[key]
- # Here we have our functions that parse the search engines
- # this is a map that we use to store our search engines for use.
- searchEnginesMap: {}
-
- # Parse the custom search engines setting and cache it.
- parseSearchEngines: (searchEnginesText) ->
- @searchEnginesMap = {}
- for line in searchEnginesText.split /\n/
- tokens = line.trim().split /\s+/
- continue if tokens.length < 2 or tokens[0].startsWith('"') or tokens[0].startsWith("#")
- keywords = tokens[0].split ":"
- continue unless keywords.length == 2 and not keywords[1] # So, like: [ "w", "" ].
- @searchEnginesMap[keywords[0]] =
- url: tokens[1]
- description: tokens[2..].join(" ")
-
- # Fetch the search-engine map, building it if necessary.
- getSearchEngines: ->
- this.parseSearchEngines(@get("searchEngines") || "") if Object.keys(@searchEnginesMap).length == 0
- @searchEnginesMap
-
# options.coffee and options.html only handle booleans and strings; therefore all defaults must be booleans
# or strings
defaults:
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='#'>&times;</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
diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee
index b0fefc7d..b09d3183 100644
--- a/lib/handler_stack.coffee
+++ b/lib/handler_stack.coffee
@@ -84,10 +84,8 @@ class HandlerStack
# Debugging.
logResult: (eventNumber, type, event, handler, result) ->
- # FIXME(smblott). Badge updating is too noisy, so we filter it out. However, we do need to look at how
- # many badge update events are happening. It seems to be more than necessary. We also filter out
- # registerKeyQueue as unnecessarily noisy and not particularly helpful.
- return if type in [ "updateBadge", "registerKeyQueue" ]
+ # Key queue events aren't usually useful for debugging, so we filter them out.
+ return if type in [ "registerKeyQueue" ]
label =
switch result
when @stopBubblingAndTrue then "stop/true"
diff --git a/manifest.json b/manifest.json
index beb68530..4ba222fb 100644
--- a/manifest.json
+++ b/manifest.json
@@ -27,6 +27,7 @@
"clipboardRead",
"storage",
"sessions",
+ "notifications",
"<all_urls>"
],
"content_scripts": [
diff --git a/pages/help_dialog.html b/pages/help_dialog.html
index 0884f2cd..77c3e2bf 100644
--- a/pages/help_dialog.html
+++ b/pages/help_dialog.html
@@ -46,6 +46,7 @@
</div>
<div class="vimiumReset vimiumColumn" style="text-align:right">
<span class="vimiumReset">Version {{version}}</span><br/>
+ <a href="https://github.com/philc/vimium#release-notes" class="vimiumReset">What's new?</a>
</div>
</div>
</div>
diff --git a/pages/options.coffee b/pages/options.coffee
index d2950348..6545189b 100644
--- a/pages/options.coffee
+++ b/pages/options.coffee
@@ -271,8 +271,12 @@ initPopupPage = ->
exclusions = null
document.getElementById("optionsLink").setAttribute "href", chrome.runtime.getURL("pages/options.html")
+ # As the active URL, we choose the most recently registered URL from a frame in the tab, or the tab's own
+ # URL.
+ url = chrome.extension.getBackgroundPage().urlForTab[tab.id] || tab.url
+
updateState = ->
- rule = bgExclusions.getRule tab.url, exclusions.readValueFromElement()
+ rule = bgExclusions.getRule url, exclusions.readValueFromElement()
$("state").innerHTML = "Vimium will " +
if rule and rule.passKeys
"exclude <span class='code'>#{rule.passKeys}</span>"
@@ -291,8 +295,6 @@ initPopupPage = ->
Option.saveOptions()
$("saveOptions").innerHTML = "Saved"
$("saveOptions").disabled = true
- chrome.tabs.query { windowId: chrome.windows.WINDOW_ID_CURRENT, active: true }, (tabs) ->
- chrome.extension.getBackgroundPage().updateActiveState(tabs[0].id)
$("saveOptions").addEventListener "click", saveOptions
@@ -302,7 +304,7 @@ initPopupPage = ->
window.close()
# Populate options. Just one, here.
- exclusions = new ExclusionRulesOnPopupOption(tab.url, "exclusionRules", onUpdated)
+ exclusions = new ExclusionRulesOnPopupOption url, "exclusionRules", onUpdated
updateState()
document.addEventListener "keyup", updateState
diff --git a/tests/dom_tests/dom_tests.coffee b/tests/dom_tests/dom_tests.coffee
index 4afa9d7d..f81982ac 100644
--- a/tests/dom_tests/dom_tests.coffee
+++ b/tests/dom_tests/dom_tests.coffee
@@ -495,10 +495,6 @@ context "PostFindMode",
testContent = "<input type='text' id='first'/>"
document.getElementById("test-div").innerHTML = testContent
document.getElementById("first").focus()
- # For these tests, we need to push GrabBackFocus out of the way. When it exits, it updates the badge,
- # which interferes with event suppression within insert mode. This cannot happen in normal operation,
- # because GrabBackFocus exits on the first keydown.
- Mode.top().exit()
@postFindMode = new PostFindMode
tearDown ->
@@ -527,55 +523,3 @@ context "PostFindMode",
sendKeyboardEvent "escape"
assert.isTrue @postFindMode.modeIsActive
-context "Mode badges",
- setup ->
- initializeModeState()
- testContent = "<input type='text' id='first'/>"
- document.getElementById("test-div").innerHTML = testContent
-
- tearDown ->
- document.getElementById("test-div").innerHTML = ""
-
- should "have no badge in normal mode", ->
- Mode.updateBadge()
- assert.isTrue chromeMessages[0].badge == ""
-
- should "have an I badge in insert mode by focus", ->
- document.getElementById("first").focus()
- # Focus triggers an event in the handler stack, so we check element "1", here.
- assert.isTrue chromeMessages[1].badge == "I"
-
- should "have no badge after leaving insert mode by focus", ->
- document.getElementById("first").focus()
- document.getElementById("first").blur()
- assert.isTrue chromeMessages[0].badge == ""
-
- should "have an I badge in global insert mode", ->
- new InsertMode global: true
- assert.isTrue chromeMessages[0].badge == "I"
-
- should "have no badge after leaving global insert mode", ->
- mode = new InsertMode global: true
- mode.exit()
- assert.isTrue chromeMessages[0].badge == ""
-
- should "have a ? badge in PostFindMode (immediately)", ->
- document.getElementById("first").focus()
- new PostFindMode
- assert.isTrue chromeMessages[0].badge == "?"
-
- should "have no badge in PostFindMode (subsequently)", ->
- document.getElementById("first").focus()
- new PostFindMode
- sendKeyboardEvent "a"
- assert.isTrue chromeMessages[0].badge == ""
-
- should "have no badge when disabled", ->
- handlerStack.bubbleEvent "registerStateChange",
- enabled: false
- passKeys: ""
-
- document.getElementById("first").focus()
- # Focus triggers an event in the handler stack, so we check element "1", here.
- assert.isTrue chromeMessages[1].badge == ""
-
diff --git a/tests/unit_tests/completion_test.coffee b/tests/unit_tests/completion_test.coffee
index b7b73cc2..56fcc456 100644
--- a/tests/unit_tests/completion_test.coffee
+++ b/tests/unit_tests/completion_test.coffee
@@ -152,8 +152,9 @@ context "domain completer",
setup ->
@history1 = { title: "history1", url: "http://history1.com", lastVisitTime: hours(1) }
@history2 = { title: "history2", url: "http://history2.com", lastVisitTime: hours(1) }
+ @undef = { title: "history2", url: "http://undefined.net", lastVisitTime: hours(1) }
- stub(HistoryCache, "use", (onComplete) => onComplete([@history1, @history2]))
+ stub(HistoryCache, "use", (onComplete) => onComplete([@history1, @history2, @undef]))
global.chrome.history =
onVisited: { addListener: -> }
onVisitRemoved: { addListener: -> }
@@ -174,6 +175,9 @@ context "domain completer",
should "returns no results when there's more than one query term, because clearly it's not a domain", ->
assert.arrayEqual [], filterCompleter(@completer, ["his", "tory"])
+ should "not return any results for empty queries", ->
+ assert.arrayEqual [], filterCompleter(@completer, [])
+
context "domain completer (removing entries)",
setup ->
@history1 = { title: "history1", url: "http://history1.com", lastVisitTime: hours(2) }
@@ -238,7 +242,7 @@ context "search engines",
@completer = new SearchEngineCompleter()
# note, I couldn't just call @completer.refresh() here as I couldn't set root.Settings without errors
# workaround is below, would be good for someone that understands the testing system better than me to improve
- @completer.searchEngines = Settings.getSearchEngines()
+ @completer.searchEngines = SearchEngineCompleter.getSearchEngines()
should "return search engine suggestion without description", ->
results = filterCompleter(@completer, ["foo", "hello"])
diff --git a/tests/unit_tests/settings_test.coffee b/tests/unit_tests/settings_test.coffee
index afe862a4..346c98da 100644
--- a/tests/unit_tests/settings_test.coffee
+++ b/tests/unit_tests/settings_test.coffee
@@ -73,7 +73,7 @@ context "settings",
should "set search engines, retrieve them correctly and check that they have been parsed correctly", ->
searchEngines = "foo: bar?q=%s\n# comment\nbaz: qux?q=%s baz description"
Settings.set 'searchEngines', searchEngines
- result = Settings.getSearchEngines()
+ result = SearchEngineCompleter.getSearchEngines()
assert.equal Object.keys(result).length, 2
assert.equal "bar?q=%s", result["foo"].url
assert.isFalse result["foo"].description