aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Blott2015-01-04 09:29:36 +0000
committerStephen Blott2015-01-04 13:00:50 +0000
commit9ae4b6c10d53153929d905f28bc7de57c0ba6dfe (patch)
treee6243e08f2f4e0925c3960dd68381d917d46a510
parent615f8a79f91f1d868465a6dae903c6710103515f (diff)
downloadvimium-9ae4b6c10d53153929d905f28bc7de57c0ba6dfe.tar.bz2
Modes; various improvements.
- Add StateMode. - PasskeysMode is a StateMode. - BadgeUpdateMode is a StateMode. - Improve badge handling. - Add push method to Mode. - Document how modes work. - Cache badge on background page to reduce the number of updates. - Remove badge restriction on document.body?.tagName.toLowerCase() == "frameset". - Add ExitOnEscape mode, use it for ConstrainedMode and FindMode. - Move PostFindMode to its own file.
-rw-r--r--background_scripts/main.coffee9
-rw-r--r--content_scripts/mode.coffee155
-rw-r--r--content_scripts/mode_find.coffee59
-rw-r--r--content_scripts/mode_insert.coffee18
-rw-r--r--content_scripts/mode_passkeys.coffee17
-rw-r--r--content_scripts/mode_visual.coffee8
-rw-r--r--content_scripts/vimium_frontend.coffee106
-rw-r--r--lib/dom_utils.coffee7
-rw-r--r--manifest.json1
9 files changed, 231 insertions, 149 deletions
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index fc0a792f..008eb89f 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -342,8 +342,13 @@ setBrowserActionIcon = (tabId,path) ->
# This color should match the blue of the Vimium browser popup (although it looks a little darker, to me?).
chrome.browserAction.setBadgeBackgroundColor {color: [102, 176, 226, 255]}
-setBadge = (request) ->
- chrome.browserAction.setBadgeText {text: request.badge || ""}
+setBadge = do ->
+ current = ""
+ (request) ->
+ badge = request.badge
+ if badge? and badge != current
+ chrome.browserAction.setBadgeText {text: badge || ""}
+ current = badge
# 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.
diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee
index 64001eaa..10b7bb2a 100644
--- a/content_scripts/mode.coffee
+++ b/content_scripts/mode.coffee
@@ -1,4 +1,43 @@
-
+# Modes.
+#
+# A mode implements a number of event handlers which are pushed onto the handler stack when the mode starts,
+# and poped when the mode exits. The Mode base takes as single argument options which can defined:
+#
+# name:
+# A name for this mode.
+#
+# badge:
+# A badge (to appear on the browser popup) for this mode.
+# Optional. Define a badge is the badge is constant. Otherwise, do not set a badge and override the
+# chooseBadge method instead. Or, if the mode *never* shows a bade, then do neither.
+#
+# keydown:
+# keypress:
+# keyup:
+# Key handlers. Optional: provide these as required. The default is to continue bubbling all key events.
+#
+# Additional handlers associated with the mode can be added by using the push method. For example, if a mode
+# responds to "focus" events, then push an additional handler:
+# @push
+# "focus": (event) => ....
+# Any such additional handlers are removed when the mode exits.
+#
+# New mode types are created by inheriting from Mode or one of its sub-classes. Some generic cub-classes are
+# provided below:
+# SingletonMode: ensures that at most one instance of the mode should be active at any time.
+# ConstrainedMode: exits the mode if the user clicks outside of the given element.
+# ExitOnEscapeMode: exits the mode if the user types Esc.
+# StateMode: tracks the current Vimium state in @enabled and @passKeys.
+#
+# To install and existing mode, use:
+# myMode = new MyMode()
+#
+# To remove a mode, use:
+# myMode.exit() # externally triggered.
+# @exit() # internally triggered (more common).
+#
+
+# Debug only; to be stripped out.
count = 0
class Mode
@@ -15,23 +54,26 @@ class Mode
# Default values.
name: ""
badge: ""
- keydown: (event) => @continueBubbling
- keypress: (event) => @continueBubbling
- keyup: (event) => @continueBubbling
+ keydown: null
+ keypress: null
+ keyup: null
- constructor: (options) ->
+ constructor: (options={}) ->
Mode.modes.unshift @
extend @, options
@count = ++count
console.log @count, "create:", @name
@handlers = []
- @handlers.push handlerStack.push
+ @push
keydown: @keydown
keypress: @keypress
keyup: @keyup
updateBadge: (badge) => handlerStack.alwaysContinueBubbling => @chooseBadge badge
+ push: (handlers) ->
+ @handlers.push handlerStack.push handlers
+
exit: ->
console.log @count, "exit:", @name
handlerStack.remove handlerId for handlerId in @handlers
@@ -45,14 +87,13 @@ class Mode
badge.badge ||= @badge
# 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 has the focus, and
- # the document's body isn't a frameset.
+ # the resulting badge to the background page. We only update the badge if this document has the focus.
@updateBadge: ->
if document.hasFocus()
- unless document.body?.tagName.toLowerCase() == "frameset"
- badge = {badge: ""}
- handlerStack.bubbleEvent "updateBadge", badge
- chrome.runtime.sendMessage({ handler: "setBadge", badge: badge.badge })
+ handlerStack.bubbleEvent "updateBadge", badge = {badge: ""}
+ chrome.runtime.sendMessage
+ handler: "setBadge"
+ badge: badge.badge
# Temporarily install a mode.
@runIn: (mode, func) ->
@@ -60,10 +101,6 @@ class Mode
func()
mode.exit()
-# We need to detect when the focused frame/tab changes, and update the badge.
-handlerStack.push
- "focus": -> handlerStack.alwaysContinueBubbling -> Mode.updateBadge()
-
# A SingletonMode is a Mode of which there may be at most one instance (of @singleton) active at any one time.
# New instances cancel previous instances on startup.
class SingletonMode extends Mode
@@ -74,26 +111,35 @@ class SingletonMode extends Mode
super()
constructor: (@singleton, options={}) ->
- SingletonMode.instances[@singleton].exit() if SingletonMode.instances[@singleton]
+ SingletonMode.kill @singleton
SingletonMode.instances[@singleton] = @
super options
-# # MultiMode is a collection of modes which are installed or uninstalled together.
-# class MultiMode extends Mode
-# constructor: (modes...) ->
-# @modes = (new mode() for mode in modes)
-# super {name: "multimode"}
-#
-# exit: ->
-# mode.exit() for mode in modes
+ # Static method. If there's a singleton instance running, then kill it.
+ @kill: (singleton) ->
+ SingletonMode.instances[singleton].exit() if SingletonMode.instances[singleton]
+
+# The mode exits when the user hits Esc.
+class ExitOnEscapeMode extends Mode
+ constructor: (options) ->
+ super options
+
+ # This handler ends up above the mode's own key handlers on the handler stack, so it takes priority.
+ @push
+ "keydown": (event) =>
+ return @continueBubbling unless KeyboardUtils.isEscape event
+ @exit
+ source: ExitOnEscapeMode
+ event: event
+ @suppressEvent
# When the user clicks anywhere outside of the given element, the mode is exited.
-class ConstrainedMode extends Mode
+class ConstrainedMode extends ExitOnEscapeMode
constructor: (@element, options) ->
options.name = if options.name? then "constrained-#{options.name}" else "constrained"
super options
- @handlers.push handlerStack.push
+ @push
"click": (event) =>
@exit() unless @isDOMDescendant @element, event.srcElement
@continueBubbling
@@ -105,19 +151,52 @@ class ConstrainedMode extends Mode
node = node.parentNode
false
-# # The mode exits when the user hits Esc.
-# class ExitOnEscapeMode extends Mode
-# constructor: (options) ->
-# super options
-#
-# # This handler ends up above the mode's own handlers on the handler stack, so it takes priority.
-# @handlers.push handlerStack.push
-# "keydown": (event) =>
-# return @continueBubbling unless KeyboardUtils.isEscape event
-# @exit()
-# @suppressEvent
+# The state mode tracks the enabled state in @enabled and @passKeys, and its initialized state in
+# @initialized. It calls @registerStateChange() whenever the state changes.
+class StateMode extends Mode
+ constructor: (options) ->
+ @stateInitialized = false
+ @enabled = false
+ @passKeys = ""
+ super options
+
+ @push
+ "registerStateChange": ({enabled: enabled, passKeys: passKeys}) =>
+ handlerStack.alwaysContinueBubbling =>
+ if enabled != @enabled or passKeys != @passKeys or not @stateInitialized
+ @stateInitialized = true
+ @enabled = enabled
+ @passKeys = passKeys
+ @registerStateChange()
+
+ # Overridden by sub-classes.
+ registerStateChange: ->
+
+# BadgeMode is a psuedo mode for managing 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.
+class BadgeMode extends StateMode
+ constructor: (options) ->
+ options.name ||= "badge"
+ super options
+
+ @push
+ "focus": =>
+ handlerStack.alwaysContinueBubbling =>
+ Mode.updateBadge()
+
+ chooseBadge: (badge) ->
+ # If we're not enabled, then post an empty badge (so, no badge at all).
+ badge.badge = "" unless @enabled
+
+ registerStateChange: ->
+ Mode.updateBadge()
+
+# Install a single BadgeMode instance.
+new BadgeMode {}
root = exports ? window
root.Mode = Mode
root.SingletonMode = SingletonMode
root.ConstrainedMode = ConstrainedMode
+root.StateMode = StateMode
+root.ExitOnEscapeMode = ExitOnEscapeMode
diff --git a/content_scripts/mode_find.coffee b/content_scripts/mode_find.coffee
new file mode 100644
index 00000000..d6380682
--- /dev/null
+++ b/content_scripts/mode_find.coffee
@@ -0,0 +1,59 @@
+# NOTE(smblott). Ultimately, all of the FindMode-related code should be moved to this file.
+
+# When we use find mode, the selection/focus can end up in a focusable/editable element. Subsequent keyboard
+# events could drop us into insert mode, which is a bad user experience. The PostFindMode mode is installed
+# after find events to prevent this.
+#
+# PostFindMode also maps Esc (on the next keystroke) to immediately drop into insert mode.
+class PostFindMode extends SingletonMode
+ constructor: (insertMode, findModeAnchorNode) ->
+ element = document.activeElement
+ return unless element
+
+ # Special cases only arise if the active element is focusable. So, exit immediately if it is not.
+ canTakeInput = DomUtils.isSelectable(element) and DomUtils.isDOMDescendant findModeAnchorNode, element
+ canTakeInput ||= element?.isContentEditable
+ return unless canTakeInput
+
+ super PostFindMode,
+ name: "post-find"
+
+ # If the very next key is Esc, then drop straight into insert mode.
+ do =>
+ self = @
+ @push
+ keydown: (event) ->
+ @remove()
+ if element == document.activeElement and KeyboardUtils.isEscape event
+ self.exit()
+ # NOTE(smblott). The legacy code (2015/1/4) uses DomUtils.simulateSelect() here. But this moves
+ # the selection. It's better to leave the selection where it is.
+ insertMode.activate element
+ return false
+ true
+
+ if element.isContentEditable
+ # Prevent InsertMode from activating on keydown.
+ @push
+ keydown: (event) -> handlerStack.alwaysContinueBubbling -> InsertMode.suppressKeydownTrigger event
+
+ # Install various ways in which we can leave this mode.
+ @push
+ DOMActive: (event) => handlerStack.alwaysContinueBubbling => @exit()
+ click: (event) => handlerStack.alwaysContinueBubbling => @exit()
+ focus: (event) => handlerStack.alwaysContinueBubbling => @exit()
+ blur: (event) => handlerStack.alwaysContinueBubbling => @exit()
+ keydown: (event) => handlerStack.alwaysContinueBubbling => @exit() if document.activeElement != element
+
+ # There's feature interference between PostFindMode, InsertMode and focusInput. PostFindMode prevents
+ # InsertMode from triggering on keyboard events. And FindMode prevents InsertMode from triggering on focus
+ # events. This means that an input element can already be focused, but InsertMode is not active. When that
+ # element is then (again) focused by focusInput, no new focus event is generated, so we don't drop into
+ # InsertMode as expected.
+ # This hack fixes this.
+ @exitModeAndEnterInsert: (element) ->
+ SingletonMode.kill PostFindMode
+ insertMode.activate element
+
+root = exports ? window
+root.PostFindMode = PostFindMode
diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee
index cffb8735..5a0ac9eb 100644
--- a/content_scripts/mode_insert.coffee
+++ b/content_scripts/mode_insert.coffee
@@ -46,6 +46,18 @@ class InsertMode extends Mode
@badge = ""
Mode.updateBadge()
+ exit: (event) ->
+ if event?.source == ExitOnEscapeMode
+ element = event?.event?.srcElement
+ if element? and @isFocusable element
+ # Remove the focus so the user can't just get himself back into insert mode by typing in the same
+ # input box.
+ # NOTE(smblott, 2014/12/22) Including embeds for .blur() here is experimental. It appears to be the
+ # right thing to do for most common use cases. However, it could also cripple flash-based sites and
+ # games. See discussion in #1211 and #1194.
+ element.blur()
+ @deactivate()
+
constructor: ->
super
name: "insert"
@@ -65,7 +77,7 @@ class InsertMode extends Mode
keypress: => if @insertModeActive then @stopBubblingAndTrue else @continueBubbling
keyup: => if @insertModeActive then @stopBubblingAndTrue else @continueBubbling
- @handlers.push handlerStack.push
+ @push
focus: (event) =>
handlerStack.alwaysContinueBubbling =>
if not @insertModeActive and @isFocusable event.target
@@ -86,8 +98,8 @@ class InsertMode extends Mode
# Activate this mode to prevent a focused, editable element from triggering insert mode.
class InsertModeSuppressFocusTrigger extends Mode
constructor: ->
- super {name: "suppress-focus-trigger"}
- @handlers.push handlerStack.push
+ super {name: "suppress-insert-mode-focus-trigger"}
+ @push
focus: => @suppressEvent
root = exports ? window
diff --git a/content_scripts/mode_passkeys.coffee b/content_scripts/mode_passkeys.coffee
index 4c4d7d41..c8afed39 100644
--- a/content_scripts/mode_passkeys.coffee
+++ b/content_scripts/mode_passkeys.coffee
@@ -1,20 +1,7 @@
-class PassKeysMode extends Mode
- keyQueue: ""
- passKeys: ""
-
- # This is called to set the passKeys configuration and state with various types of request from various
- # sources, so we handle several cases here.
- # TODO(smblott) Rationalize this.
+class PassKeysMode extends StateMode
configure: (request) ->
- if request.isEnabledForUrl?
- @passKeys = (request.isEnabledForUrl and request.passKeys) or ""
- Mode.updateBadge()
- if request.enabled?
- @passKeys = (request.enabled and request.passKeys) or ""
- Mode.updateBadge()
- if request.keyQueue?
- @keyQueue = request.keyQueue
+ @keyQueue = request.keyQueue if request.keyQueue?
# Decide whether this event should be passed to the underlying page. Keystrokes are *never* considered
# passKeys if the keyQueue is not empty. So, for example, if 't' is a passKey, then 'gt' and '99t' will
diff --git a/content_scripts/mode_visual.coffee b/content_scripts/mode_visual.coffee
index 07530e94..b07d784e 100644
--- a/content_scripts/mode_visual.coffee
+++ b/content_scripts/mode_visual.coffee
@@ -1,19 +1,13 @@
+# Note. ConstrainedMode extends extends ExitOnEscapeMode. So exit-on-escape is handled there.
class VisualMode extends ConstrainedMode
- # Proposal... The visual selection must stay within element. This will become relevant if we ever get so
- # far as implementing a vim-like editing mode for text areas/content editable.
- #
constructor: (element=document.body) ->
super element,
name: "visual"
badge: "V"
keydown: (event) =>
- if KeyboardUtils.isEscape event
- @exit()
- return Mode.suppressEvent
-
return Mode.suppressEvent
keypress: (event) =>
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index d1ada884..9d539956 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -133,8 +133,6 @@ initializePreDomReady = ->
Scroller.init settings
# Install passKeys and insert modes. These too are permanently on the stack (although not always active).
- # Note. There's no need to explicitly Mode.updateBadge(). The new InsertMode() updates the badge.
- # Note. There's no need to explicitly Mode.updateBadge(). The new InsertMode() updates the badge.
passKeysMode = new PassKeysMode()
insertMode = new InsertMode()
Mode.updateBadge()
@@ -163,9 +161,7 @@ initializePreDomReady = ->
getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY
setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY
executePageCommand: executePageCommand
- getActiveState: ->
- Mode.updateBadge()
- return { enabled: isEnabledForUrl, passKeys: passKeys }
+ getActiveState: getActiveState
setState: setState
currentKeyQueue: (request) ->
keyQueue = request.keyQueue
@@ -210,7 +206,13 @@ setState = (request) ->
initializeWhenEnabled(request.passKeys) if request.enabled
isEnabledForUrl = request.enabled
passKeys = request.passKeys
- passKeysMode.configure request
+ handlerStack.bubbleEvent "registerStateChange",
+ enabled: request.enabled
+ passKeys: request.passKeys
+
+getActiveState = ->
+ Mode.updateBadge()
+ return { enabled: isEnabledForUrl, passKeys: passKeys }
#
# The backend needs to know which frame has focus.
@@ -281,7 +283,6 @@ window.focusThisFrame = (shouldHighlight) ->
chrome.runtime.sendMessage({ handler: "nextFrame", frameId: frameId })
return
window.focus()
- Mode.updateBadge()
if (document.body && shouldHighlight)
borderWas = document.body.style.border
document.body.style.border = '5px solid yellow'
@@ -359,13 +360,9 @@ extend window,
selectedInputIndex = Math.min(count - 1, visibleInputs.length - 1)
- # We need to make sure that the following .focus() actually does generate a "focus" event. We need such
- # an event:
- # - to trigger insert mode, and
- # - to kick any PostFindMode listeners out of the way.
- # Unfortunately, if the element is already focused (as may happen following a find), then no "focus" event
- # is generated. So, here, we first generate a psuedo "focus" event.
- PostFindMode.fakeFocus visibleInputs[selectedInputIndex].element
+ # See the definition of PostFindMode.exitModeAndEnterInsert for an explanation of why this is needed.
+ PostFindMode.exitModeAndEnterInsert visibleInputs[selectedInputIndex].element
+
visibleInputs[selectedInputIndex].element.focus()
return if visibleInputs.length == 1
@@ -578,8 +575,9 @@ checkIfEnabledForUrl = ->
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()
- passKeysMode.configure response
- Mode.updateBadge()
+ handlerStack.bubbleEvent "registerStateChange",
+ enabled: response.isEnabledForUrl
+ passKeys: response.passKeys
refreshCompletionKeys = (response) ->
if (response)
@@ -733,20 +731,16 @@ handleEnterForFindMode = ->
document.body.classList.add("vimiumFindMode")
settings.set("findModeRawQuery", findModeQuery.rawQuery)
# If we have found an input element, the pressing <esc> immediately afterwards sends us into insert mode.
- new PostFindMode()
+ new PostFindMode insertMode, findModeAnchorNode
-class FindMode extends Mode
+class FindMode extends ExitOnEscapeMode
constructor: (badge="F") ->
super
name: "find"
badge: badge
keydown: (event) =>
- if KeyboardUtils.isEscape event
- handleEscapeForFindMode()
- @exit()
- @suppressEvent
- else if event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey
+ if event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey
handleDeleteForFindMode()
@suppressEvent
else if event.keyCode == keyCodes.enter
@@ -767,56 +761,9 @@ class FindMode extends Mode
Mode.updateBadge()
-# Handle various special cases which arise when find finds a match within a focusable element.
-class PostFindMode extends SingletonMode
- constructor: ->
- element = document.activeElement
-
- # Special cases only arise if the active element is focusable. So, exit immediately if it is not.
- canTakeInput = element and DomUtils.isSelectable(element) and isDOMDescendant findModeAnchorNode, element
- canTakeInput ||= element?.isContentEditable
- return unless canTakeInput
-
- super PostFindMode, {name: "post-find-mode"}
-
- if element.isContentEditable
- # Prevent InsertMode from activating on keydown.
- @handlers.push handlerStack.push
- keydown: (event) =>
- InsertMode.suppressKeydownTrigger event
- @continueBubbling
-
- # If the next key is Esc, then drop into insert mode.
- @handlers.push handlerStack.push
- keydown: (event) ->
- @remove()
- return true unless KeyboardUtils.isEscape event
- DomUtils.simulateSelect document.activeElement
- insertMode.activate element
- return false
-
- # We can stop watching on any change of focus or user click.
- # FIXME(smblott). This is broken. If there is a text area, and the text area is focused with
- # find, then clicking within that text area does *not* generate a useful event, and therefore does not
- # disable PostFindMode mode, and therefore does not allow us to enter insert mode.
- @handlers.push handlerStack.push
- DOMActive: (event) => handlerStack.alwaysContinueBubbling =>
- console.log "ACTIVATE"
- @exit()
- click: (event) =>
- handlerStack.alwaysContinueBubbling =>
- console.log "CLICK"
- @exit()
- focus: (event) => handlerStack.alwaysContinueBubbling =>
- console.log "FOCUS"
- @exit()
- blur: (event) =>
- console.log "BLUR"
- handlerStack.alwaysContinueBubbling => @exit()
-
- # This removes any PostFindMode modes on the stack and triggers a "focus" event for InsertMode.
- @fakeFocus: (element) ->
- handlerStack.bubbleEvent "focus", {target: element, note: "generated by PostFindMode.fakeFocus()"}
+ exit: (event) ->
+ handleEscapeForFindMode() if event?.source == ExitOnEscapeMode
+ super()
performFindInPlace = ->
cachedScrollX = window.scrollX
@@ -863,13 +810,6 @@ focusFoundLink = ->
link = getLinkFromSelection()
link.focus() if link
-isDOMDescendant = (parent, child) ->
- node = child
- while (node != null)
- return true if (node == parent)
- node = node.parentNode
- false
-
selectFoundInputElement = ->
# if the found text is in an input element, getSelection().anchorNode will be null, so we use activeElement
# instead. however, since the last focused element might not be the one currently pointed to by find (e.g.
@@ -877,7 +817,7 @@ selectFoundInputElement = ->
# heuristic of checking that the last anchor node is an ancestor of our element.
if (findModeQueryHasResults && document.activeElement &&
DomUtils.isSelectable(document.activeElement) &&
- isDOMDescendant(findModeAnchorNode, document.activeElement))
+ DomUtils.isDOMDescendant(findModeAnchorNode, document.activeElement))
DomUtils.simulateSelect(document.activeElement)
# the element has already received focus via find(), so invoke insert mode manually
enterInsertModeWithoutShowingIndicator(document.activeElement)
@@ -914,7 +854,7 @@ findAndFocus = (backwards) ->
# if we have found an input element via 'n', pressing <esc> immediately afterwards sends us into insert
# mode
- new PostFindMode()
+ new PostFindMode insertMode, findModeAnchorNode
focusFoundLink()
@@ -1033,9 +973,7 @@ window.enterFindMode = ->
findModeQuery = { rawQuery: "" }
# window.findMode = true # Same hack, see comment at window.findMode definition.
HUD.show("/")
- console.log "aaa"
new FindMode()
- console.log "bbb"
exitFindMode = ->
window.findMode = false # Same hack, see comment at window.findMode definition.
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee
index ba5e279f..ec846e44 100644
--- a/lib/dom_utils.coffee
+++ b/lib/dom_utils.coffee
@@ -141,6 +141,13 @@ DomUtils =
(element.nodeName.toLowerCase() == "input" && unselectableTypes.indexOf(element.type) == -1) ||
element.nodeName.toLowerCase() == "textarea"
+ isDOMDescendant: (parent, child) ->
+ node = child
+ while (node != null)
+ return true if (node == parent)
+ node = node.parentNode
+ false
+
simulateSelect: (element) ->
element.focus()
# When focusing a textbox, put the selection caret at the end of the textbox's contents.
diff --git a/manifest.json b/manifest.json
index 2d01ad50..a04d8c0e 100644
--- a/manifest.json
+++ b/manifest.json
@@ -46,6 +46,7 @@
"content_scripts/mode.js",
"content_scripts/mode_insert.js",
"content_scripts/mode_passkeys.js",
+ "content_scripts/mode_find.js",
"content_scripts/mode_visual.js",
"content_scripts/vimium_frontend.js"
],