diff options
Diffstat (limited to 'content_scripts/vimium_frontend.coffee')
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 141 |
1 files changed, 100 insertions, 41 deletions
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index a2139df6..6db0d830 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -8,13 +8,18 @@ window.handlerStack = new HandlerStack insertModeLock = null findMode = false -findModeQuery = { rawQuery: "" } +findModeQuery = { rawQuery: "", matchCount: 0 } findModeQueryHasResults = false findModeAnchorNode = null isShowingHelpDialog = false keyPort = null -# Users can disable Vimium on URL patterns via the settings page. +# Users can disable Vimium on URL patterns via the settings page. The following two variables +# (isEnabledForUrl and passKeys) control Vimium's enabled/disabled behaviour. +# "passKeys" are keys which would normally be handled by Vimium, but are disabled on this tab, and therefore +# are passed through to the underlying page. isEnabledForUrl = true +passKeys = null +keyQueue = null # The user's operating system. currentCompletionKeys = null validFirstKeys = null @@ -115,42 +120,47 @@ initializePreDomReady = -> getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY executePageCommand: executePageCommand - getActiveState: -> { enabled: isEnabledForUrl } - disableVimium: disableVimium + getActiveState: -> { enabled: isEnabledForUrl, passKeys: passKeys } + setState: setState + currentKeyQueue: (request) -> keyQueue = request.keyQueue chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> - # in the options page, we will receive requests from both content and background scripts. ignore those + # In the options page, we will receive requests from both content and background scripts. ignore those # from the former. return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://' - return unless isEnabledForUrl or request.name == 'getActiveState' + return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState' + # These requests are delivered to the options page, but there are no handlers there. + return if request.handler == "registerFrame" or request.handler == "frameFocused" sendResponse requestHandlers[request.name](request, sender) # Ensure the sendResponse callback is freed. false -# -# This is called once the background page has told us that Vimium should be enabled for the current URL. -# -initializeWhenEnabled = -> - document.addEventListener("keydown", onKeydown, true) - document.addEventListener("keypress", onKeypress, true) - document.addEventListener("keyup", onKeyup, true) - document.addEventListener("focus", onFocusCapturePhase, true) - document.addEventListener("blur", onBlurCapturePhase, true) - document.addEventListener("DOMActivate", onDOMActivate, true) - enterInsertModeIfElementIsFocused() +# Wrapper to install event listeners. Syntactic sugar. +installListener = (event, callback) -> document.addEventListener(event, callback, true) # -# Used to disable Vimium without needing to reload the page. -# This is called if the current page's url is blacklisted using the popup UI. +# This is called once the background page has told us that Vimium should be enabled for the current URL. +# We enable/disable Vimium by toggling isEnabledForUrl. The alternative, installing or uninstalling +# listeners, is error prone. It's more difficult to keep track of the state. # -disableVimium = -> - document.removeEventListener("keydown", onKeydown, true) - document.removeEventListener("keypress", onKeypress, true) - document.removeEventListener("keyup", onKeyup, true) - document.removeEventListener("focus", onFocusCapturePhase, true) - document.removeEventListener("blur", onBlurCapturePhase, true) - document.removeEventListener("DOMActivate", onDOMActivate, true) - isEnabledForUrl = false +installedListeners = false +initializeWhenEnabled = (newPassKeys) -> + isEnabledForUrl = true + passKeys = newPassKeys + if (!installedListeners) + installListener "keydown", (event) -> if isEnabledForUrl then onKeydown(event) else true + installListener "keypress", (event) -> if isEnabledForUrl then onKeypress(event) else true + installListener "keyup", (event) -> if isEnabledForUrl then onKeyup(event) else true + installListener "focus", (event) -> if isEnabledForUrl then onFocusCapturePhase(event) else true + installListener "blur", (event) -> if isEnabledForUrl then onBlurCapturePhase(event) + installListener "DOMActivate", (event) -> if isEnabledForUrl then onDOMActivate(event) + enterInsertModeIfElementIsFocused() + installedListeners = true + +setState = (request) -> + isEnabledForUrl = request.enabled + passKeys = request.passKeys + initializeWhenEnabled(passKeys) if isEnabledForUrl and !installedListeners # # The backend needs to know which frame has focus. @@ -321,6 +331,14 @@ extend window, false +# Decide whether this keyChar 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 neverthless be handled by vimium. +isPassKey = ( keyChar ) -> + return !keyQueue and passKeys and 0 <= passKeys.indexOf(keyChar) + +handledKeydownEvents = [] + # # Sends everything except i & ESC to the handler in background_page. i & ESC are special because they control # insert mode which is local state to the page. The key will be are either a single ascii letter or a @@ -347,6 +365,8 @@ onKeypress = (event) -> handleKeyCharForFindMode(keyChar) DomUtils.suppressEvent(event) else if (!isInsertMode() && !findMode) + if (isPassKey keyChar) + return undefined if (currentCompletionKeys.indexOf(keyChar) != -1) DomUtils.suppressEvent(event) @@ -390,37 +410,48 @@ onKeydown = (event) -> if (isEditable(event.srcElement)) event.srcElement.blur() exitInsertMode() - DomUtils.suppressEvent(event) + DomUtils.suppressEvent event + handledKeydownEvents.push event else if (findMode) if (KeyboardUtils.isEscape(event)) handleEscapeForFindMode() - DomUtils.suppressEvent(event) + DomUtils.suppressEvent event + handledKeydownEvents.push event else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) handleDeleteForFindMode() - DomUtils.suppressEvent(event) + DomUtils.suppressEvent event + handledKeydownEvents.push event else if (event.keyCode == keyCodes.enter) handleEnterForFindMode() - DomUtils.suppressEvent(event) + DomUtils.suppressEvent event + handledKeydownEvents.push event else if (!modifiers) event.stopPropagation() + handledKeydownEvents.push event else if (isShowingHelpDialog && KeyboardUtils.isEscape(event)) hideHelpDialog() + DomUtils.suppressEvent event + handledKeydownEvents.push event else if (!isInsertMode() && !findMode) if (keyChar) if (currentCompletionKeys.indexOf(keyChar) != -1) - DomUtils.suppressEvent(event) + DomUtils.suppressEvent event + handledKeydownEvents.push event keyPort.postMessage({ keyChar:keyChar, frameId:frameId }) else if (KeyboardUtils.isEscape(event)) keyPort.postMessage({ keyChar:"<ESC>", frameId:frameId }) + else if isPassKey KeyboardUtils.getKeyChar(event) + return undefined + # Added to prevent propagating this event to other listeners if it's one that'll trigger a Vimium command. # The goal is to avoid the scenario where Google Instant Search uses every keydown event to dump us # back into the search box. As a side effect, this should also prevent overriding by other sites. @@ -432,8 +463,23 @@ onKeydown = (event) -> (currentCompletionKeys.indexOf(KeyboardUtils.getKeyChar(event)) != -1 || isValidFirstKey(KeyboardUtils.getKeyChar(event)))) event.stopPropagation() + handledKeydownEvents.push event + +onKeyup = (event) -> + return unless handlerStack.bubbleEvent("keyup", event) + return if isInsertMode() -onKeyup = (event) -> return unless handlerStack.bubbleEvent('keyup', event) + # Don't propagate the keyup to the underlying page if Vimium has handled it. See #733. + for keydown, i in handledKeydownEvents + if event.metaKey == keydown.metaKey and + event.altKey == keydown.altKey and + event.ctrlKey == keydown.ctrlKey and + event.keyIdentifier == keydown.keyIdentifier and + event.keyCode == keydown.keyCode + + handledKeydownEvents.splice i, 1 + event.stopPropagation() + break checkIfEnabledForUrl = -> url = window.location.toString() @@ -441,7 +487,7 @@ checkIfEnabledForUrl = -> chrome.runtime.sendMessage { handler: "isEnabledForUrl", url: url }, (response) -> isEnabledForUrl = response.isEnabledForUrl if (isEnabledForUrl) - initializeWhenEnabled() + initializeWhenEnabled(response.passKeys) 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() @@ -554,6 +600,18 @@ updateFindModeQuery = -> text = document.body.innerText findModeQuery.regexMatches = text.match(pattern) findModeQuery.activeRegexIndex = 0 + findModeQuery.matchCount = findModeQuery.regexMatches?.length + # if we are doing a basic plain string match, we still want to grep for matches of the string, so we can + # show a the number of results. We can grep on document.body.innerText, as it should be indistinguishable + # from the internal representation used by window.find. + else + # escape all special characters, so RegExp just parses the string 'as is'. + # Taken from http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + escapeRegExp = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g + parsedNonRegexQuery = findModeQuery.parsedQuery.replace(escapeRegExp, (char) -> "\\" + char) + pattern = new RegExp(parsedNonRegexQuery, "g" + (if findModeQuery.ignoreCase then "i" else "")) + text = document.body.innerText + findModeQuery.matchCount = text.match(pattern)?.length handleKeyCharForFindMode = (keyChar) -> findModeQuery.rawQuery += keyChar @@ -799,7 +857,7 @@ findAndFollowRel = (value) -> for tag in relTags elements = document.getElementsByTagName(tag) for element in elements - if (element.hasAttribute("rel") && element.rel == value) + if (element.hasAttribute("rel") && element.rel.toLowerCase() == value) followLink(element) return true @@ -815,7 +873,7 @@ window.goNext = -> showFindModeHUDForQuery = -> if (findModeQueryHasResults || findModeQuery.parsedQuery.length == 0) - HUD.show("/" + findModeQuery.rawQuery) + HUD.show("/" + findModeQuery.rawQuery + " (" + findModeQuery.matchCount + " Matches)") else HUD.show("/" + findModeQuery.rawQuery + " (No Matches)") @@ -839,7 +897,7 @@ window.showHelpDialog = (html, fid) -> container.innerHTML = html container.getElementsByClassName("closeButton")[0].addEventListener("click", hideHelpDialog, false) - + VimiumHelpDialog = # This setting is pulled out of local storage. It's false by default. getShowAdvancedCommands: -> settings.get("helpDialog_showAdvancedCommands") @@ -869,8 +927,9 @@ window.showHelpDialog = (html, fid) -> VimiumHelpDialog.init() - container.getElementsByClassName("optionsPage")[0].addEventListener("click", - -> chrome.runtime.sendMessage({ handler: "openOptionsPageInNewTab" }) + container.getElementsByClassName("optionsPage")[0].addEventListener("click", (clickEvent) -> + clickEvent.preventDefault() + chrome.runtime.sendMessage({handler: "openOptionsPageInNewTab"}) false) @@ -908,7 +967,7 @@ HUD = show: (text) -> return unless HUD.enabled() clearTimeout(HUD._showForDurationTimerId) - HUD.displayElement().innerHTML = text + HUD.displayElement().innerText = text clearInterval(HUD._tweenId) HUD._tweenId = Tween.fade(HUD.displayElement(), 1.0, 150) HUD.displayElement().style.display = "" @@ -917,7 +976,7 @@ HUD = HUD.upgradeNotificationElement().innerHTML = "Vimium has been updated to <a class='vimiumReset' href='https://chrome.google.com/extensions/detail/dbepggeogbaibhgnhhndojpepiihcmeb'> - #{version}</a>.<a class='vimiumReset close-button' href='#'>x</a>" + #{version}</a>.<a class='vimiumReset close-button' href='#'>×</a>" links = HUD.upgradeNotificationElement().getElementsByTagName("a") links[0].addEventListener("click", HUD.onUpdateLinkClicked, false) links[1].addEventListener "click", (event) -> |
