#
# This content script must be run prior to domReady so that we perform some operations very early.
#
isEnabledForUrl = true
isIncognitoMode = chrome.extension.inIncognitoContext
normalMode = null
# We track whther the current window has the focus or not.
windowIsFocused = do ->
windowHasFocus = null
DomUtils.documentReady -> windowHasFocus = document.hasFocus()
window.addEventListener "focus", (event) -> windowHasFocus = true if event.target == window; true
window.addEventListener "blur", (event) -> windowHasFocus = false if event.target == window; true
-> windowHasFocus
# The types in that we consider for focusInput command. Right now this is recalculated in
# each content script. Alternatively we could calculate it once in the background page and use a request to
# fetch it each time.
# Should we include the HTML5 date pickers here?
# The corresponding XPath for such elements.
textInputXPath = (->
textInputTypes = [ "text", "search", "email", "url", "number", "password", "date", "tel" ]
inputElements = ["input[" +
"(" + textInputTypes.map((type) -> '@type="' + type + '"').join(" or ") + "or not(@type))" +
" and not(@disabled or @readonly)]",
"textarea", "*[@contenteditable='' or translate(@contenteditable, 'TRUE', 'true')='true']"]
DomUtils.makeXPath(inputElements)
)()
# This is set by Frame.registerFrameId(). A frameId of 0 indicates that this is the top frame in the tab.
frameId = null
# For debugging only. This logs to the console on the background page.
bgLog = (args...) ->
args = (arg.toString() for arg in args)
chrome.runtime.sendMessage handler: "log", message: args.join " "
# If an input grabs the focus before the user has interacted with the page, then grab it back (if the
# grabBackFocus option is set).
class GrabBackFocus extends Mode
constructor: ->
super
name: "grab-back-focus"
keydown: => @alwaysContinueBubbling => @exit()
@push
_name: "grab-back-focus-mousedown"
mousedown: => @alwaysContinueBubbling => @exit()
Settings.use "grabBackFocus", (grabBackFocus) =>
# It is possible that this mode exits (e.g. due to a key event) before the settings are ready -- in
# which case we should not install this grab-back-focus watcher.
if @modeIsActive
if grabBackFocus
@push
_name: "grab-back-focus-focus"
focus: (event) => @grabBackFocus event.target
# An input may already be focused. If so, grab back the focus.
@grabBackFocus document.activeElement if document.activeElement
else
@exit()
grabBackFocus: (element) ->
return @continueBubbling unless DomUtils.isEditable element
element.blur()
@suppressEvent
# Pages can load new content dynamically and change the displayed URL using history.pushState. Since this can
# often be indistinguishable from an actual new page load for the user, we should also re-start GrabBackFocus
# for these as well. This fixes issue #1622.
handlerStack.push
_name: "GrabBackFocus-pushState-monitor"
click: (event) ->
# If a focusable element is focused, the user must have clicked on it. Retain focus and bail.
return true if DomUtils.isFocusable document.activeElement
target = event.target
while target
# Often, a link which triggers a content load and url change with javascript will also have the new
# url as it's href attribute.
if target.tagName == "A" and
target.origin == document.location.origin and
# Clicking the link will change the url of this frame.
(target.pathName != document.location.pathName or
target.search != document.location.search) and
(target.target in ["", "_self"] or
(target.target == "_parent" and window.parent == window) or
(target.target == "_top" and window.top == window))
return new GrabBackFocus()
else
target = target.parentElement
true
class NormalMode extends KeyHandlerMode
constructor: (options = {}) ->
super extend options,
name: "normal"
indicator: false # There is no mode indicator in normal mode.
commandHandler: @commandHandler.bind this
chrome.storage.local.get "normalModeKeyStateMapping", (items) =>
@setKeyMapping items.normalModeKeyStateMapping
chrome.storage.onChanged.addListener (changes, area) =>
if area == "local" and changes.normalModeKeyStateMapping?.newValue
@setKeyMapping changes.normalModeKeyStateMapping.newValue
# Initialize components which normal mode depends upon.
Scroller.init()
FindModeHistory.init()
commandHandler: ({command: registryEntry, count}) ->
count *= registryEntry.options.count ? 1
count = 1 if registryEntry.noRepeat
if registryEntry.repeatLimit? and registryEntry.repeatLimit < count
return unless confirm """
You have asked Vimium to perform #{count} repetitions of the command: #{registryEntry.description}.\n
Are you sure you want to continue?"""
if registryEntry.topFrame
# We never return to a UI-component frame (e.g. the help dialog), it might have lost the focus.
sourceFrameId = if window.isVimiumUIComponent then 0 else frameId
chrome.runtime.sendMessage
handler: "sendMessageToFrames", message: {name: "runInTopFrame", sourceFrameId, registryEntry}
else if registryEntry.background
chrome.runtime.sendMessage {handler: "runBackgroundCommand", registryEntry, count}
else
Utils.invokeCommandString registryEntry.command, count
installModes = ->
# Install the permanent modes. The permanently-installed insert mode tracks focus/blur events, and
# activates/deactivates itself accordingly.
normalMode = new NormalMode
new InsertMode permanent: true
new GrabBackFocus if isEnabledForUrl
normalMode # Return the normalMode object (for the tests).
initializeOnEnabledStateKnown = (isEnabledForUrl) ->
installModes() unless normalMode
if isEnabledForUrl
# We only initialize (and activate) the Vomnibar in the top frame. Also, we do not initialize the
# Vomnibar until we know that Vimium is enabled. Thereafter, there's no more initialization to do.
Vomnibar.init() if DomUtils.isTopFrame()
initializeOnEnabledStateKnown = ->
#
# Complete initialization work that should be done prior to DOMReady.
#
initializePreDomReady = ->
installListeners()
Frame.init()
checkIfEnabledForUrl document.hasFocus()
requestHandlers =
focusFrame: (request) -> if (frameId == request.frameId) then focusThisFrame request
getScrollPosition: (ignoredA, ignoredB, sendResponse) ->
sendResponse scrollX: window.scrollX, scrollY: window.scrollY if frameId == 0
setScrollPosition: setScrollPosition
frameFocused: -> # A frame has received the focus; we don't care here (UI components handle this).
checkEnabledAfterURLChange: checkEnabledAfterURLChange
runInTopFrame: ({sourceFrameId, registryEntry}) ->
Utils.invokeCommandString registryEntry.command, sourceFrameId, registryEntry if DomUtils.isTopFrame()
linkHintsMessage: (request) -> HintCoordinator[request.messageType] request
chrome.runtime.onMessage.addListener (request, sender, sendResponse) ->
# These requests are intended for the background page, but they're delivered to the options page too.
unless request.handler and not request.name
if isEnabledForUrl or request.name in ["checkEnabledAfterURLChange", "runInTopFrame"]
requestHandlers[request.name] request, sender, sendResponse
false # Ensure that the sendResponse callback is freed.
# Wrapper to install event listeners. Syntactic sugar.
installListener = (element, event, callback) ->
element.addEventListener(event, ->
if isEnabledForUrl then callback.apply(this, arguments) else true
, true)
#
# Installing or uninstalling listeners is error prone. Instead we elect to check isEnabledForUrl each time so
# we know whether the listener should run or not.
# Run this as early as possible, so the page can't register any event handlers before us.
# Note: We install the listeners even if Vimium is disabled. See comment in commit
# 6446cf04c7b44c3d419dc450a73b60bcaf5cdf02.
#
installListeners = Utils.makeIdempotent ->
# Key event handlers fire on window before they do on document. Prefer window for key events so the page
# can't set handlers to grab the keys before us.
for type in ["keydown", "keypress", "keyup", "click", "focus", "blur", "mousedown", "scroll"]
do (type) -> installListener window, type, (event) -> handlerStack.bubbleEvent type, event
installListener document, "DOMActivate", (event) -> handlerStack.bubbleEvent 'DOMActivate', event
#
# Whenever we get the focus:
# - Tell the background page this frame's URL.
# - Check if we should be enabled.
#
onFocus = (event) ->
if event.target == window
chrome.runtime.sendMessage handler: "frameFocused"
checkIfEnabledForUrl true
# 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
initializeOnDomReady = ->
# Tell the background page we're in the domReady state.
Frame.postMessage "domReady"
Frame =
port: null
listeners: {}
addEventListener: (handler, callback) -> @listeners[handler] = callback
postMessage: (handler, request = {}) -> @port.postMessage extend request, {handler}
linkHintsMessage: (request) -> HintCoordinator[request.messageType] request
registerFrameId: ({chromeFrameId}) ->
frameId = window.frameId = chromeFrameId
# We register a frame immediately only if it is focused or its window isn't tiny. We register tiny
# frames later, when necessary. This affects focusFrame() and link hints.
if windowIsFocused() or not DomUtils.windowIsTooSmall()
Frame.postMessage "registerFrame"
else
postRegisterFrame = ->
window.removeEventListener "focus", focusHandler
window.removeEventListener "resize", resizeHandler
Frame.postMessage "registerFrame"
window.addEventListener "focus", focusHandler = ->
postRegisterFrame() if event.target == window
window.addEventListener "resize", resizeHandler = ->
postRegisterFrame() unless DomUtils.windowIsTooSmall()
init: ->
@port = chrome.runtime.connect name: "frames"
@port.onMessage.addListener (request) =>
(@listeners[request.handler] ? this[request.handler]) request
# We disable the content scripts when we lose contact with the background page, or on unload.
@port.onDisconnect.addListener disconnect = Utils.makeIdempotent => @disconnect()
window.addEventListener "unload", disconnect
disconnect: ->
try @postMessage "unregisterFrame"
try @port.disconnect()
@postMessage = @disconnect = ->
@port = null
@listeners = {}
HintCoordinator.exit isSuccess: false
handlerStack.reset()
isEnabledForUrl = false
window.removeEventListener "focus", onFocus
window.removeEventListener "hashchange", onFocus
setScrollPosition = ({ scrollX, scrollY }) ->
if DomUtils.isTopFrame()
DomUtils.documentReady ->
window.focus()
document.body.focus()
if 0 < scrollX or 0 < scrollY
Marks.setPreviousPosition()
window.scrollTo scrollX, scrollY
flashFrame = ->
DomUtils.documentReady ->
# Create a shadow DOM wrapping the frame so the page's styles don't interfere with ours.
highlightedFrameElement = DomUtils.createElement "div"
# PhantomJS doesn't support createShadowRoot, so guard against its non-existance.
_shadowDOM = highlightedFrameElement.createShadowRoot?() ? highlightedFrameElement
# Inject stylesheet.
_styleSheet = DomUtils.createElement "style"
_styleSheet.innerHTML = "@import url(\"#{chrome.runtime.getURL("content_scripts/vimium.css")}\");"
_shadowDOM.appendChild _styleSheet
_frameEl = DomUtils.createElement "div"
_frameEl.className = "vimiumReset vimiumHighlightedFrame"
_shadowDOM.appendChild _frameEl
flashFrame = ->
document.documentElement.appendChild highlightedFrameElement
setTimeout (-> highlightedFrameElement.remove()), 200
#
# Called from the backend in order to change frame focus.
#
focusThisFrame = (request) ->
unless request.forceFocusThisFrame
if DomUtils.windowIsTooSmall() or document.body?.tagName.toLowerCase() == "frameset"
# This frame is too small to focus or it's a frameset. Cancel and tell the background page to focus the
# next frame instead. This affects sites like Google Inbox, which have many tiny iframes. See #1317.
chrome.runtime.sendMessage handler: "nextFrame"
return
window.focus()
flashFrame() if request.highlight
extend window,
scrollToBottom: ->
Marks.setPreviousPosition()
Scroller.scrollTo "y", "max"
scrollToTop: (count) ->
Marks.setPreviousPosition()
Scroller.scrollTo "y", (count - 1) * Settings.get("scrollStepSize")
scrollToLeft: -> Scroller.scrollTo "x", 0
scrollToRight: -> Scroller.scrollTo "x", "max"
scrollUp: (count) -> Scroller.scrollBy "y", -1 * Settings.get("scrollStepSize") * count
scrollDown: (count) -> Scroller.scrollBy "y", Settings.get("scrollStepSize") * count
scrollPageUp: (count) -> Scroller.scrollBy "y", "viewSize", -1/2 * count
scrollPageDown: (count) -> Scroller.scrollBy "y", "viewSize", 1/2 * count
scrollFullPageUp: (count) -> Scroller.scrollBy "y", "viewSize", -1 * count
scrollFullPageDown: (count) -> Scroller.scrollBy "y", "viewSize", 1 * count
scrollLeft: (count) -> Scroller.scrollBy "x", -1 * Settings.get("scrollStepSize") * count
scrollRight: (count) -> Scroller.scrollBy "x", Settings.get("scrollStepSize") * count
extend window,
reload: -> window.location.reload()
goBack: (count) -> history.go(-count)
goForward: (count) -> history.go(count)
goUp: (count) ->
url = window.location.href
if (url[url.length - 1] == "/")
url = url.substring(0, url.length - 1)
urlsplit = url.split("/")
# make sure we haven't hit the base domain yet
if (urlsplit.length > 3)
urlsplit = urlsplit.slice(0, Math.max(3, urlsplit.length - count))
window.location.href = urlsplit.join('/')
goToRoot: ->
window.location.href = window.location.origin
mainFrame: -> focusThisFrame highlight: true, forceFocusThisFrame: true
toggleViewSource: ->
chrome.runtime.sendMessage { handler: "getCurrentTabUrl" }, (url) ->
if (url.substr(0, 12) == "view-source:")
url = url.substr(12, url.length - 12)
else
url = "view-source:" + url
chrome.runtime.sendMessage {handler: "openUrlInNewTab", url}
copyCurrentUrl: ->
# TODO(ilya): When the following bug is fixed, revisit this approach of sending back to the background
# page to copy.
# http://code.google.com/p/chromium/issues/detail?id=55188
chrome.runtime.sendMessage { handler: "getCurrentTabUrl" }, (url) ->
chrome.runtime.sendMessage { handler: "copyToClipboard", data: url }
url = url[0..25] + "...." if 28 < url.length
HUD.showForDuration("Yanked #{url}", 2000)
enterInsertMode: ->
# If a focusable element receives the focus, then we exit and leave the permanently-installed insert-mode
# instance to take over.
new InsertMode global: true, exitOnFocus: true
enterVisualMode: ->
new VisualMode userLaunchedMode: true
enterVisualLineMode: ->
new VisualLineMod userLaunchedMode: true
passNextKey: (count) ->
new PassNextKeyMode count
focusInput: do ->
# Track the most recently focused input element.
recentlyFocusedElement = null
window.addEventListener "focus",
(event) -> recentlyFocusedElement = event.target if DomUtils.isEditable event.target
, true
(count, mode = InsertMode) ->
# Focus the first input element on the page, and create overlays to highlight all the input elements, with
# the currently-focused element highlighted specially. Tabbing will shift focus to the next input element.
# Pressing any other key will remove the overlays and the special tab behavior.
# The mode argument is the mode to enter once an input is selected.
resultSet = DomUtils.evaluateXPath textInputXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
visibleInputs =
for i in [0...resultSet.snapshotLength] by 1
element = resultSet.snapshotItem i
continue unless DomUtils.getVisibleClientRect element, true
{ element, rect: Rect.copy element.getBoundingClientRect() }
if visibleInputs.length == 0
HUD.showForDuration("There are no inputs to focus.", 1000)
return
# 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 ?= document.getElementById "keyMappings" if window.isVimiumOptionsPage
selectedInputIndex =
if count == 1
# As the starting index, we pick that of the most recently focused input element (or 0).
elements = visibleInputs.map (visibleInput) -> visibleInput.element
Math.max 0, elements.indexOf recentlyFocusedElement
else
Math.min(count, visibleInputs.length) - 1
hints = for tuple in visibleInputs
hint = DomUtils.createElement "div"
hint.className = "vimiumReset internalVimiumInputHint vimiumInputHint"
# minus 1 for the border
hint.style.left = (tuple.rect.left - 1) + window.scrollX + "px"
hint.style.top = (tuple.rect.top - 1) + window.scrollY + "px"
hint.style.width = tuple.rect.width + "px"
hint.style.height = tuple.rect.height + "px"
hint
new class FocusSelector extends Mode
constructor: ->
super
name: "focus-selector"
exitOnClick: true
keydown: (event) =>
if event.keyCode == KeyboardUtils.keyCodes.tab
hints[selectedInputIndex].classList.remove 'internalVimiumSelectedInputHint'
selectedInputIndex += hints.length + (if event.shiftKey then -1 else 1)
selectedInputIndex %= hints.length
hints[selectedInputIndex].classList.add 'internalVimiumSelectedInputHint'
DomUtils.simulateSelect visibleInputs[selectedInputIndex].element
@suppressEvent
else unless event.keyCode == KeyboardUtils.keyCodes.shiftKey
@exit()
# Give the new mode the opportunity to handle the event.
@restartBubbling
@hintContainingDiv = DomUtils.addElementList hints,
id: "vimiumInputMarkerContainer"
className: "vimiumReset"
DomUtils.simulateSelect visibleInputs[selectedInputIndex].element
if visibleInputs.length == 1
@exit()
return
else
hints[selectedInputIndex].classList.add 'internalVimiumSelectedInputHint'
exit: ->
super()
DomUtils.removeElement @hintContainingDiv
if mode and document.activeElement and DomUtils.isEditable document.activeElement
new mode
singleton: "post-find-mode/focus-input"
targetElement: document.activeElement
indicator: false
# Checks if Vimium should be enabled or not in this frame. As a side effect, it also informs the background
# page whether this frame has the focus, allowing the background page to track the active frame's URL and set
# the page icon.
checkIfEnabledForUrl = do ->
Frame.addEventListener "isEnabledForUrl", (response) ->
{isEnabledForUrl, passKeys, frameIsFocused} = response
initializeOnEnabledStateKnown isEnabledForUrl
normalMode.setPassKeys passKeys
# Hide the HUD if we're not enabled.
HUD.hide true, false unless isEnabledForUrl
(frameIsFocused = windowIsFocused()) ->
Frame.postMessage "isEnabledForUrl", {frameIsFocused, url: window.location.toString()}
# When we're informed by the background page that a URL in this tab has changed, we check if we have the
# correct enabled state (but only if this frame has the focus).
checkEnabledAfterURLChange = ->
checkIfEnabledForUrl() if windowIsFocused()
handleEscapeForFindMode = ->
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.
selection = window.getSelection()
unless selection.isCollapsed
range = window.getSelection().getRangeAt(0)
window.getSelection().removeAllRanges()
window.getSelection().addRange(range)
focusFoundLink() || selectFoundInputElement()
# sends us into insert mode if possible, but does not.
# corresponds approximately to 'nevermind, I have found it already' while means 'I want to save
# this query and do more searches with it'
handleEnterForFindMode = ->
focusFoundLink()
document.body.classList.add("vimiumFindMode")
FindMode.saveQuery()
focusFoundLink = ->
if (FindMode.query.hasResults)
link = getLinkFromSelection()
link.focus() if link
selectFoundInputElement = ->
# Since the last focused element might not be the one currently pointed to by find (e.g. the current one
# might be disabled and therefore unable to receive focus), we use the approximate heuristic of checking
# that the last anchor node is an ancestor of our element.
findModeAnchorNode = document.getSelection().anchorNode
if (FindMode.query.hasResults && document.activeElement &&
DomUtils.isSelectable(document.activeElement) &&
DomUtils.isDOMDescendant(findModeAnchorNode, document.activeElement))
DomUtils.simulateSelect(document.activeElement)
findAndFocus = (backwards) ->
Marks.setPreviousPosition()
FindMode.query.hasResults = FindMode.execute null, {backwards}
if FindMode.query.hasResults
focusFoundLink()
new PostFindMode()
else
HUD.showForDuration("No matches for '#{FindMode.query.rawQuery}'", 1000)
performFind = (count) -> findAndFocus false for [0...count] by 1
performBackwardsFind = (count) -> findAndFocus true for [0...count] by 1
getLinkFromSelection = ->
node = window.getSelection().anchorNode
while (node && node != document.body)
return node if (node.nodeName.toLowerCase() == "a")
node = node.parentNode
null
# used by the findAndFollow* functions.
followLink = (linkElement) ->
if (linkElement.nodeName.toLowerCase() == "link")
window.location.href = linkElement.href
else
# if we can click on it, don't simply set location.href: some next/prev links are meant to trigger AJAX
# calls, like the 'more' button on GitHub's newsfeed.
linkElement.scrollIntoView()
DomUtils.simulateClick(linkElement)
#
# Find and follow a link which matches any one of a list of strings. If there are multiple such links, they
# are prioritized for shortness, by their position in :linkStrings, how far down the page they are located,
# and finally by whether the match is exact. Practically speaking, this means we favor 'next page' over 'the
# next big thing', and 'more' over 'nextcompany', even if 'next' occurs before 'more' in :linkStrings.
#
findAndFollowLink = (linkStrings) ->
linksXPath = DomUtils.makeXPath(["a", "button", "*[@onclick or @role='link' or contains(@class, 'button')]"])
links = DomUtils.evaluateXPath(linksXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE)
candidateLinks = []
# at the end of this loop, candidateLinks will contain all visible links that match our patterns
# links lower in the page are more likely to be the ones we want, so we loop through the snapshot backwards
for i in [(links.snapshotLength - 1)..0] by -1
link = links.snapshotItem(i)
# ensure link is visible (we don't mind if it is scrolled offscreen)
boundingClientRect = link.getBoundingClientRect()
if (boundingClientRect.width == 0 || boundingClientRect.height == 0)
continue
computedStyle = window.getComputedStyle(link, null)
if (computedStyle.getPropertyValue("visibility") != "visible" ||
computedStyle.getPropertyValue("display") == "none")
continue
linkMatches = false
for linkString in linkStrings
if (link.innerText.toLowerCase().indexOf(linkString) != -1)
linkMatches = true
break
continue unless linkMatches
candidateLinks.push(link)
return if (candidateLinks.length == 0)
for link in candidateLinks
link.wordCount = link.innerText.trim().split(/\s+/).length
# We can use this trick to ensure that Array.sort is stable. We need this property to retain the reverse
# in-page order of the links.
candidateLinks.forEach((a,i) -> a.originalIndex = i)
# favor shorter links, and ignore those that are more than one word longer than the shortest link
candidateLinks =
candidateLinks
.sort((a, b) ->
if (a.wordCount == b.wordCount) then a.originalIndex - b.originalIndex else a.wordCount - b.wordCount
)
.filter((a) -> a.wordCount <= candidateLinks[0].wordCount + 1)
for linkString in linkStrings
exactWordRegex =
if /\b/.test(linkString[0]) or /\b/.test(linkString[linkString.length - 1])
new RegExp "\\b" + linkString + "\\b", "i"
else
new RegExp linkString, "i"
for candidateLink in candidateLinks
if (exactWordRegex.test(candidateLink.innerText))
followLink(candidateLink)
return true
false
findAndFollowRel = (value) ->
relTags = ["link", "a", "area"]
for tag in relTags
elements = document.getElementsByTagName(tag)
for element in elements
if (element.hasAttribute("rel") && element.rel.toLowerCase() == value)
followLink(element)
return true
window.goPrevious = ->
previousPatterns = Settings.get("previousPatterns") || ""
previousStrings = previousPatterns.split(",").filter( (s) -> s.trim().length )
findAndFollowRel("prev") || findAndFollowLink(previousStrings)
window.goNext = ->
nextPatterns = Settings.get("nextPatterns") || ""
nextStrings = nextPatterns.split(",").filter( (s) -> s.trim().length )
findAndFollowRel("next") || findAndFollowLink(nextStrings)
# Enters find mode. Returns the new find-mode instance.
enterFindMode = ->
Marks.setPreviousPosition()
new FindMode()
window.showHelp = (sourceFrameId) ->
chrome.runtime.sendMessage handler: "getHelpDialogHtml", (response) ->
HelpDialog.toggle {sourceFrameId, html: response}
# If we are in the help dialog iframe, then HelpDialog is already defined with the necessary functions.
window.HelpDialog ?=
helpUI: null
isShowing: -> @helpUI?.showing
abort: -> @helpUI.hide false if @isShowing()
toggle: (request) ->
@helpUI ?= new UIComponent "pages/help_dialog.html", "vimiumHelpDialogFrame", ->
if @isShowing()
@helpUI.hide()
else
@helpUI.activate extend request,
name: "activate", focus: true
initializePreDomReady()
DomUtils.documentReady initializeOnDomReady
root = exports ? window
root.handlerStack = handlerStack
root.frameId = frameId
root.Frame = Frame
root.windowIsFocused = windowIsFocused
root.bgLog = bgLog
# These are exported for find mode and link-hints mode.
extend root, {handleEscapeForFindMode, handleEnterForFindMode, performFind, performBackwardsFind,
enterFindMode, focusThisFrame}
# These are exported only for the tests.
extend root, {installModes, installListeners}