From 5885bc779bed46dfcff4b1e82968151448569f9f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 19 Aug 2014 18:15:13 +0100 Subject: Move the vomnibar to an iframe --- content_scripts/vimium.css | 124 ++------------- content_scripts/vimium_frontend.coffee | 2 + content_scripts/vomnibar.coffee | 273 ++++++--------------------------- 3 files changed, 54 insertions(+), 345 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium.css b/content_scripts/vimium.css index 7998fe5c..fd613b31 100644 --- a/content_scripts/vimium.css +++ b/content_scripts/vimium.css @@ -266,136 +266,30 @@ div.vimiumHUD a.close-button:hover { body.vimiumFindMode ::selection { background: #ff9632; -}; +} -/* Vomnibar CSS */ +/* Vomnibar Frame CSS */ -#vomnibar ol, #vomnibar ul { - list-style: none; - display: block; -} +iframe.vomnibarFrame { + background-color: transparent; + padding: 0px; + overflow: hidden; -#vomnibar { display: block; position: fixed; - width: 80%; + width: calc(80% + 20px); /* same adjustment as in pages/vomnibar.coffee */ min-width: 400px; + height: calc(100% - 70px); top: 70px; left: 50%; margin: 0 0 0 -40%; + border: none; font-family: sans-serif; - background: #F1F1F1; - text-align: left; - border-radius: 4px; - box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.8); - border: 1px solid #aaa; /* One less than hint markers and the help dialog. */ z-index: 99999996; } -#vomnibar input { - color: #000; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 20px; - height: 34px; - margin-bottom: 0; - padding: 4px; - background-color: white; - border-radius: 3px; - border: 1px solid #E8E8E8; - box-shadow: #444 0px 0px 1px; - width: 100%; - outline: none; - box-sizing: border-box; -} - -#vomnibar .vomnibarSearchArea { - display: block; - padding: 10px; - background-color: #F1F1F1; - border-radius: 4px 4px 0 0; - border-bottom: 1px solid #C6C9CE; -} - -#vomnibar ul { - background-color: white; - border-radius: 0 0 4px 4px; - list-style: none; - padding: 10px 0; - padding-top: 0; -} - -#vomnibar li { - border-bottom: 1px solid #ddd; - line-height: 1.1em; - padding: 7px 10px; - font-size: 16px; - color: black; - position: relative; - display: list-item; - margin: auto; -} - -#vomnibar li:last-of-type { - border-bottom: none; -} - -#vomnibar li .vomnibarTopHalf, #vomnibar li .vomnibarBottomHalf { - display: block; - overflow: hidden; -} - -#vomnibar li .vomnibarBottomHalf { - font-size: 15px; - margin-top: 3px; - padding: 2px 0; -} - -#vomnibar li .vomnibarSource { - color: #777; - margin-right: 4px; -} -#vomnibar li .vomnibarRelevancy { - position: absolute; - right: 0; - top: 0; - padding: 5px; - background-color: white; - color: black; - font-family: monospace; - width: 100px; - overflow: hidden; -} - -#vomnibar li .vomnibarUrl { - white-space: nowrap; - color: #224684; -} - -#vomnibar li .vomnibarMatch { - font-weight: bold; - color: black; -} - -#vomnibar li em, #vomnibar li .vomnibarTitle { - color: black; - margin-left: 4px; - font-weight: normal; -} -#vomnibar li em { font-style: italic; } -#vomnibar li em .vomnibarMatch, #vomnibar li .vomnibarTitle .vomnibarMatch { - color: #333; - text-decoration: underline; -} - -#vomnibar li.vomnibarSelected { - background-color: #BBCEE9; - font-weight: normal; -} - - - div#vimiumFlash { box-shadow: 0px 0px 4px 2px #4183C4; padding: 1px; diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 137b9d1a..0a835de9 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -121,6 +121,8 @@ initializePreDomReady = -> getActiveState: -> { enabled: isEnabledForUrl, passKeys: passKeys } setState: setState currentKeyQueue: (request) -> keyQueue = request.keyQueue + vomnibarShow: -> Vomnibar.show() + vomnibarClose: -> Vomnibar.close() chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> # In the options page, we will receive requests from both content and background scripts. ignore those diff --git a/content_scripts/vomnibar.coffee b/content_scripts/vomnibar.coffee index 9c13cd6d..0e19f194 100644 --- a/content_scripts/vomnibar.coffee +++ b/content_scripts/vomnibar.coffee @@ -1,243 +1,56 @@ Vomnibar = - vomnibarUI: null # the dialog instance for this window - completers: {} - - getCompleter: (name) -> - if (!(name of @completers)) - @completers[name] = new BackgroundCompleter(name) - @completers[name] - - # - # Activate the Vomnibox. - # - activateWithCompleter: (completerName, refreshInterval, initialQueryValue, selectFirstResult, forceNewTab) -> - completer = @getCompleter(completerName) - @vomnibarUI = new VomnibarUI() unless @vomnibarUI - completer.refresh() - @vomnibarUI.setInitialSelectionValue(if selectFirstResult then 0 else -1) - @vomnibarUI.setCompleter(completer) - @vomnibarUI.setRefreshInterval(refreshInterval) - @vomnibarUI.setForceNewTab(forceNewTab) - @vomnibarUI.show() - if (initialQueryValue) - @vomnibarUI.setQuery(initialQueryValue) - @vomnibarUI.update() - - activate: -> @activateWithCompleter("omni", 100) - activateInNewTab: -> @activateWithCompleter("omni", 100, null, false, true) - activateTabSelection: -> @activateWithCompleter("tabs", 0, null, true) - activateBookmarks: -> @activateWithCompleter("bookmarks", 0, null, true) - activateBookmarksInNewTab: -> @activateWithCompleter("bookmarks", 0, null, true, true) - getUI: -> @vomnibarUI - - -class VomnibarUI - constructor: -> - @refreshInterval = 0 - @initDom() + vomnibarElement: null + + activate: -> @open {completer:"omni"} + activateInNewTab: -> @open { + completer: "omni" + selectFirst: false + newTab: true + } + activateTabSelection: -> @open { + completer: "tabs" + selectFirst: true + } + activateBookmarks: -> @open { + completer: "bookmarks" + selectFirst: true + } + activateBookmarksInNewTab: -> @open { + completer: "bookmarks" + selectFirst: true + newTab: true + } + + open: (options) -> + unless @vomnibarElement? + @vomnibarElement = document.createElement "iframe" + @vomnibarElement.className = "vomnibarFrame" + @vomnibarElement.seamless = "seamless" + @hide() - setQuery: (query) -> @input.value = query + options.frameId = frameId - setInitialSelectionValue: (initialSelectionValue) -> - @initialSelectionValue = initialSelectionValue + optionStrings = [] + for option of options + if typeof options[option] == "boolean" + optionStrings.push option if options[option] + else + optionStrings.push "#{option}=#{escape(options[option])}" - setCompleter: (completer) -> - @completer = completer - @reset() + @vomnibarElement.src = "#{chrome.runtime.getURL "pages/vomnibar.html"}?#{optionStrings.join "&"}" + document.documentElement.appendChild @vomnibarElement - setRefreshInterval: (refreshInterval) -> @refreshInterval = refreshInterval + @vomnibarElement.focus() - setForceNewTab: (forceNewTab) -> @forceNewTab = forceNewTab + close: -> + @hide() + @vomnibarElement?.remove() show: -> - @box.style.display = "block" - @input.focus() - @handlerId = handlerStack.push keydown: @onKeydown.bind @ + @vomnibarElement?.style.display = "block" hide: -> - @box.style.display = "none" - @completionList.style.display = "none" - @input.blur() - handlerStack.remove @handlerId - - reset: -> - @input.value = "" - @updateTimer = null - @completions = [] - @selection = @initialSelectionValue - @update(true) - - updateSelection: -> - # We have taken the option to add some global state here (previousCompletionType) to tell if a search - # item has just appeared or disappeared, if that happens we either set the initialSelectionValue to 0 or 1 - # I feel that this approach is cleaner than bubbling the state up from the suggestion level - # so we just inspect it afterwards - if @completions[0] - if @previousCompletionType != "search" && @completions[0].type == "search" - @selection = 0 - else if @previousCompletionType == "search" && @completions[0].type != "search" - @selection = -1 - for i in [0...@completionList.children.length] - @completionList.children[i].className = (if i == @selection then "vomnibarSelected" else "") - @previousCompletionType = @completions[0].type if @completions[0] - - # - # Returns the user's action ("up", "down", "enter", "dismiss" or null) based on their keypress. - # We support the arrow keys and other shortcuts for moving, so this method hides that complexity. - # - actionFromKeyEvent: (event) -> - key = KeyboardUtils.getKeyChar(event) - if (KeyboardUtils.isEscape(event)) - return "dismiss" - else if (key == "up" || - (event.shiftKey && event.keyCode == keyCodes.tab) || - (event.ctrlKey && (key == "k" || key == "p"))) - return "up" - else if (key == "down" || - (event.keyCode == keyCodes.tab && !event.shiftKey) || - (event.ctrlKey && (key == "j" || key == "n"))) - return "down" - else if (event.keyCode == keyCodes.enter) - return "enter" - - onKeydown: (event) -> - action = @actionFromKeyEvent(event) - return true unless action # pass through - - openInNewTab = @forceNewTab || - (event.shiftKey || event.ctrlKey || KeyboardUtils.isPrimaryModifierKey(event)) - if (action == "dismiss") - @hide() - else if (action == "up") - @selection -= 1 - @selection = @completions.length - 1 if @selection < @initialSelectionValue - @input.value = @completions[@selection].url - @updateSelection() - else if (action == "down") - @selection += 1 - @selection = @initialSelectionValue if @selection == @completions.length - @input.value = @completions[@selection].url - @updateSelection() - else if (action == "enter") - # If they type something and hit enter without selecting a completion from our list of suggestions, - # try to open their query as a URL directly. If it doesn't look like a URL, we will search using - # google. - if (@selection == -1) - query = @input.value.trim() - # on an empty vomnibar is a no-op. - return unless 0 < query.length - @hide() - chrome.runtime.sendMessage({ - handler: if openInNewTab then "openUrlInNewTab" else "openUrlInCurrentTab" - url: query }) - else - @update true, => - # Shift+Enter will open the result in a new tab instead of the current tab. - @completions[@selection].performAction(openInNewTab) - @hide() - - # It seems like we have to manually suppress the event here and still return true. - event.stopPropagation() - event.preventDefault() - true - - updateCompletions: (callback) -> - query = @input.value.trim() - - @completer.filter query, (completions) => - @completions = completions - @populateUiWithCompletions(completions) - callback() if callback - - populateUiWithCompletions: (completions) -> - # update completion list with the new data - @completionList.innerHTML = completions.map((completion) -> "
  • #{completion.html}
  • ").join("") - @completionList.style.display = if completions.length > 0 then "block" else "none" - @selection = Math.min(Math.max(@initialSelectionValue, @selection), @completions.length - 1) - @updateSelection() - - update: (updateSynchronously, callback) -> - if (updateSynchronously) - # cancel scheduled update - if (@updateTimer != null) - window.clearTimeout(@updateTimer) - @updateCompletions(callback) - else if (@updateTimer != null) - # an update is already scheduled, don't do anything - return - else - # always update asynchronously for better user experience and to take some load off the CPU - # (not every keystroke will cause a dedicated update) - @updateTimer = setTimeout(=> - @updateCompletions(callback) - @updateTimer = null - @refreshInterval) - - initDom: -> - @box = Utils.createElementFromHtml( - """ -
    -
    - -
    - -
    - """) - @box.style.display = "none" - document.body.appendChild(@box) - - @input = document.querySelector("#vomnibar input") - @input.addEventListener "input", => @update() - @completionList = document.querySelector("#vomnibar ul") - @completionList.style.display = "none" - -# -# Sends filter and refresh requests to a Vomnibox completer on the background page. -# -class BackgroundCompleter - # - name: The background page completer that you want to interface with. Either "omni", "tabs", or - # "bookmarks". */ - constructor: (@name) -> - @filterPort = chrome.runtime.connect({ name: "filterCompleter" }) - - refresh: -> chrome.runtime.sendMessage({ handler: "refreshCompleter", name: @name }) - - filter: (query, callback) -> - id = Utils.createUniqueId() - @filterPort.onMessage.addListener (msg) => - @filterPort.onMessage.removeListener(arguments.callee) - # The result objects coming from the background page will be of the form: - # { html: "", type: "", url: "" } - # type will be one of [tab, bookmark, history, domain]. - results = msg.results.map (result) -> - functionToCall = if (result.type == "tab") - BackgroundCompleter.completionActions.switchToTab.curry(result.tabId) - else - BackgroundCompleter.completionActions.navigateToUrl.curry(result.url) - result.performAction = functionToCall - result - callback(results) - - @filterPort.postMessage({ id: id, name: @name, query: query }) - -extend BackgroundCompleter, - # - # These are the actions we can perform when the user selects a result in the Vomnibox. - # - completionActions: - navigateToUrl: (url, openInNewTab) -> - # If the URL is a bookmarklet prefixed with javascript:, we shouldn't open that in a new tab. - if url.startsWith "javascript:" - script = document.createElement 'script' - script.textContent = decodeURIComponent(url["javascript:".length..]) - (document.head || document.documentElement).appendChild script - else - chrome.runtime.sendMessage( - handler: if openInNewTab then "openUrlInNewTab" else "openUrlInCurrentTab" - url: url, - selected: openInNewTab) - - switchToTab: (tabId) -> chrome.runtime.sendMessage({ handler: "selectSpecificTab", id: tabId }) + @vomnibarElement?.style.display = "none" root = exports ? window root.Vomnibar = Vomnibar -- cgit v1.2.3 From d65f265a6ad137be0db4d8c86879e5123a10087b Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 2 Sep 2014 17:43:41 +0100 Subject: Add comments about moving the Vomnibar to an iframe --- content_scripts/vomnibar.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vomnibar.coffee b/content_scripts/vomnibar.coffee index 0e19f194..7b3cfdbe 100644 --- a/content_scripts/vomnibar.coffee +++ b/content_scripts/vomnibar.coffee @@ -1,3 +1,6 @@ +# +# This wraps the vomnibar iframe, which we inject into the page to provide the vomnibar. +# Vomnibar = vomnibarElement: null @@ -21,6 +24,11 @@ Vomnibar = newTab: true } + # This function opens the vomnibar. It accepts options, a map with the values: + # completer - The completer to fetch results from. + # query - Optional. Text to prefill the Vomnibar with. + # selectFirst - Optional, boolean. Whether to select the first entry. + # newTab - Optional, boolean. Whether to open the result in a new tab. open: (options) -> unless @vomnibarElement? @vomnibarElement = document.createElement "iframe" -- cgit v1.2.3 From 1c33590eb994403ec8520c1c02bfb4fe63831745 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 8 Dec 2014 01:15:06 +0000 Subject: Detect port disconnects (ie. extension is disabled) and disable frontend --- content_scripts/vimium_frontend.coffee | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 469afe71..f7bd805d 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -57,6 +57,16 @@ settings = @port = chrome.runtime.connect({ name: "settings" }) @port.onMessage.addListener(@receiveMessage) + # If the port is closed, the background page has gone away (since we never close it ourselves). Stub the + # settings object so we don't keep trying to connect to the extension even though it's gone away. + @port.onDisconnect.addListener => + @port = null + _get = @get # @get doesn't depend on @port, so we can continue to support it to try and reduce errors. + for own property, value of this + @[property] = (->) if "function" == typeof value + @get = _get + + get: (key) -> @values[key] set: (key, value) -> @@ -109,6 +119,13 @@ initializePreDomReady = -> # Send the key to the key handler in the background page. keyPort = chrome.runtime.connect({ name: "keyDown" }) + # If the port is closed, the background page has gone away (since we never close it ourselves). Disable all + # our event listeners, and stub out chrome.runtime.sendMessage/connect (to prevent errors). + # TODO(mrmr1993): Do some actual cleanup to free resources, hide UI, etc. + keyPort.onDisconnect.addListener -> + isEnabledForUrl = false + chrome.runtime.sendMessage = -> + chrome.runtime.connect = -> requestHandlers = hideUpgradeNotification: -> HUD.hideUpgradeNotification() -- cgit v1.2.3 From fa06a27b906a83c01ded4e0fe3b96e8e51a1a52d Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sat, 13 Dec 2014 17:18:52 +0000 Subject: Simplify settings port cleanup logic --- content_scripts/vimium_frontend.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index f7bd805d..62247c5c 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -61,10 +61,9 @@ settings = # settings object so we don't keep trying to connect to the extension even though it's gone away. @port.onDisconnect.addListener => @port = null - _get = @get # @get doesn't depend on @port, so we can continue to support it to try and reduce errors. for own property, value of this - @[property] = (->) if "function" == typeof value - @get = _get + # @get doesn't depend on @port, so we can continue to support it to try and reduce errors. + @[property] = (->) if "function" == typeof value and property != "get" get: (key) -> @values[key] -- cgit v1.2.3 From 82413203d258fafc0fc8da0d9b2daf760b46017b Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 19 May 2014 15:40:40 +0100 Subject: Implement cursor hiding on scroll This begins to address #662, by hiding the cursor when the page is scrolled to prevent a distracting cursor and accidentally triggering hover effects. --- content_scripts/vimium_frontend.coffee | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 469afe71..98e9a172 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -183,6 +183,7 @@ initializeOnDomReady = -> # Tell the background page we're in the dom ready state. chrome.runtime.connect({ name: "domReady" }) + CursorHider.init() registerFrame = -> # Don't register frameset containers; focusing them is no use. @@ -1067,6 +1068,59 @@ Tween = value = (elapsed / state.duration) * (state.to - state.from) + state.from state.onUpdate(value) +CursorHider = + # + # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible + # + cursorHidden: false + hoverBlockElement: undefined + + hideCursor: -> + unless @cursorHidden + @hoverBlockElement.style.display = "block" + @cursorHidden = true + + showCursor: -> + if @cursorHidden + @hoverBlockElement.style.display = "none" + @cursorHidden = false + + onMouseMove: (event) -> + CursorHider.showCursor() + + onScroll: -> + CursorHider.hideCursor() + + # Ignore next mousemove, caused by the scrolling, so the mouse doesn't re-show straight away. + window.removeEventListener "mousemove", CursorHider.onMouseMove + window.addEventListener "mousemove", -> + window.addEventListener "mousemove", CursorHider.onMouseMove + window.removeEventListener "mousemove", arguments.callee + + init: -> + # cover the element entirely by a div with cursor: none + @hoverBlockElement = document.createElement("div") + @hoverBlockElement.style.position = "fixed" + @hoverBlockElement.style.width = "104%" + @hoverBlockElement.style.height = "104%" + @hoverBlockElement.style.zIndex = "2147483647" # Maximum value of z-index + @hoverBlockElement.style.left = "0px" + @hoverBlockElement.style.top = "0px" + @hoverBlockElement.style.opacity = "0.0" + @hoverBlockElement.style.display = "none" + @hoverBlockElement.style.cursor = "none" + @hoverBlockElement.style.border = "none" + @hoverBlockElement.style.margin = "-2% -2% -2% -2%" + document.body.appendChild(@hoverBlockElement) + + window.addEventListener "mousemove", @onMouseMove + window.addEventListener "scroll", @onScroll + + deinit: -> + window.removeEventListener "mousemove", @onMouseMove + window.removeEventListener "scroll", @onScroll + @showCursor() + initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) window.addEventListener("unload", unregisterFrame) -- cgit v1.2.3 From 70dcf4131a83a51e503d929438ba9eb887cbf0bd Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sun, 14 Dec 2014 09:58:30 +0000 Subject: Use cursor: none and pointer-events: none to hide the cursor on scroll --- content_scripts/vimium_frontend.coffee | 40 ++++++++++------------------------ 1 file changed, 11 insertions(+), 29 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 98e9a172..f6a60c9c 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1072,23 +1072,15 @@ CursorHider = # # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible # - cursorHidden: false - hoverBlockElement: undefined + cursorHideStyle: null + cursorHideTimeout: 5000 + showCursor: -> @cursorHideStyle.remove() hideCursor: -> - unless @cursorHidden - @hoverBlockElement.style.display = "block" - @cursorHidden = true + document.head.appendChild @cursorHideStyle unless @cursorHideStyle.parentElement - showCursor: -> - if @cursorHidden - @hoverBlockElement.style.display = "none" - @cursorHidden = false - - onMouseMove: (event) -> - CursorHider.showCursor() - - onScroll: -> + onMouseMove: (event) -> CursorHider.showCursor() + onScroll: (event) -> CursorHider.hideCursor() # Ignore next mousemove, caused by the scrolling, so the mouse doesn't re-show straight away. @@ -1098,21 +1090,11 @@ CursorHider = window.removeEventListener "mousemove", arguments.callee init: -> - # cover the element entirely by a div with cursor: none - @hoverBlockElement = document.createElement("div") - @hoverBlockElement.style.position = "fixed" - @hoverBlockElement.style.width = "104%" - @hoverBlockElement.style.height = "104%" - @hoverBlockElement.style.zIndex = "2147483647" # Maximum value of z-index - @hoverBlockElement.style.left = "0px" - @hoverBlockElement.style.top = "0px" - @hoverBlockElement.style.opacity = "0.0" - @hoverBlockElement.style.display = "none" - @hoverBlockElement.style.cursor = "none" - @hoverBlockElement.style.border = "none" - @hoverBlockElement.style.margin = "-2% -2% -2% -2%" - document.body.appendChild(@hoverBlockElement) - + @cursorHideStyle = document.createElement("style") + @cursorHideStyle.innerHTML = """ + body * {pointer-events: none !important; cursor: none !important;} + body {cursor: none !important;} + """ window.addEventListener "mousemove", @onMouseMove window.addEventListener "scroll", @onScroll -- cgit v1.2.3 From 9b9cee761f4c718f08dec92c46027a7effe39991 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sun, 14 Dec 2014 11:19:36 +0000 Subject: Remove unused code in CursorHider --- content_scripts/vimium_frontend.coffee | 6 ------ 1 file changed, 6 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index f6a60c9c..23e320c0 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1073,7 +1073,6 @@ CursorHider = # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible # cursorHideStyle: null - cursorHideTimeout: 5000 showCursor: -> @cursorHideStyle.remove() hideCursor: -> @@ -1098,11 +1097,6 @@ CursorHider = window.addEventListener "mousemove", @onMouseMove window.addEventListener "scroll", @onScroll - deinit: -> - window.removeEventListener "mousemove", @onMouseMove - window.removeEventListener "scroll", @onScroll - @showCursor() - initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) window.addEventListener("unload", unregisterFrame) -- cgit v1.2.3 From aa190905bff04bd83438960779ce912b048b1f5f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sun, 14 Dec 2014 11:51:42 +0000 Subject: Use a boolean to track whether we are scrolling in CursorHider --- content_scripts/vimium_frontend.coffee | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 23e320c0..30d76523 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1073,21 +1073,21 @@ CursorHider = # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible # cursorHideStyle: null + isScrolling: false showCursor: -> @cursorHideStyle.remove() hideCursor: -> document.head.appendChild @cursorHideStyle unless @cursorHideStyle.parentElement - onMouseMove: (event) -> CursorHider.showCursor() + onMouseMove: (event) -> + if CursorHider.isScrolling # This event was caused by scrolling, don't show the cursor. + CursorHider.isScrolling = false + else + CursorHider.showCursor() onScroll: (event) -> + CursorHider.isScrolling = true CursorHider.hideCursor() - # Ignore next mousemove, caused by the scrolling, so the mouse doesn't re-show straight away. - window.removeEventListener "mousemove", CursorHider.onMouseMove - window.addEventListener "mousemove", -> - window.addEventListener "mousemove", CursorHider.onMouseMove - window.removeEventListener "mousemove", arguments.callee - init: -> @cursorHideStyle = document.createElement("style") @cursorHideStyle.innerHTML = """ -- cgit v1.2.3 From 69c117102eb419baa649cccc770bffea5177b1e1 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sun, 14 Dec 2014 11:57:19 +0000 Subject: Check if cursorHideStyle is in the document before remove()-ing it --- content_scripts/vimium_frontend.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 30d76523..639a4041 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1075,7 +1075,8 @@ CursorHider = cursorHideStyle: null isScrolling: false - showCursor: -> @cursorHideStyle.remove() + showCursor: -> + @cursorHideStyle.remove() if @cursorHideStyle.parentElement hideCursor: -> document.head.appendChild @cursorHideStyle unless @cursorHideStyle.parentElement -- cgit v1.2.3 From 9c677fc91cd2641c709bb1afe03466c92b0ee0ea Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 14 Dec 2014 15:36:15 +0000 Subject: Minor refactoring of CursorHider. --- content_scripts/vimium_frontend.coffee | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index aeb74dcd..9478a7d5 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1089,23 +1089,20 @@ Tween = CursorHider = # # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible + # NOTE(smblott) onScroll and onMouseMove events come in pairs. # cursorHideStyle: null isScrolling: false - showCursor: -> - @cursorHideStyle.remove() if @cursorHideStyle.parentElement - hideCursor: -> - document.head.appendChild @cursorHideStyle unless @cursorHideStyle.parentElement - - onMouseMove: (event) -> - if CursorHider.isScrolling # This event was caused by scrolling, don't show the cursor. - CursorHider.isScrolling = false - else - CursorHider.showCursor() onScroll: (event) -> CursorHider.isScrolling = true - CursorHider.hideCursor() + unless CursorHider.cursorHideStyle.parentElement + document.head.appendChild CursorHider.cursorHideStyle + + onMouseMove: (event) -> + if CursorHider.cursorHideStyle.parentElement and not CursorHider.isScrolling + CursorHider.cursorHideStyle.remove() + CursorHider.isScrolling = false init: -> @cursorHideStyle = document.createElement("style") -- cgit v1.2.3 From 886e94e5c1563483702208675c5d12735f007938 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 14 Dec 2014 15:58:22 +0000 Subject: FRAMESET frames should not be registered. Somewhere along the road, this got goofed up. This reverts to the intended behaviour. --- content_scripts/vimium_frontend.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 9478a7d5..fc65f6c0 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -205,7 +205,7 @@ initializeOnDomReady = -> registerFrame = -> # Don't register frameset containers; focusing them is no use. - if document.body.tagName != "FRAMESET" + if document.body.tagName.toLowerCase() != "frameset" chrome.runtime.sendMessage handler: "registerFrame" frameId: frameId -- cgit v1.2.3 From a199335790aec50cf3ed7cc27c5b407875c37107 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 10:15:54 +0000 Subject: Use the DOM rather than XPath to detect clickable elements --- content_scripts/link_hints.coffee | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 24bd7126..2ffe818f 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -36,17 +36,6 @@ LinkHints = # init: -> - # - # Generate an XPath describing what a clickable element is. - # The final expression will be something like "//button | //xhtml:button | ..." - # We use translate() instead of lower-case() because Chrome only supports XPath 1.0. - # - clickableElementsXPath: DomUtils.makeXPath( - ["a", "area[@href]", "textarea", "button", "select", - "input[not(@type='hidden' or @disabled or @readonly)]", - "*[@onclick or @tabindex or @role='link' or @role='button' or contains(@class, 'button') or " + - "@contenteditable='' or translate(@contenteditable, 'TRUE', 'true')='true']"]) - # We need this as a top-level function because our command system doesn't yet support arguments. activateModeToOpenInNewTab: -> @activateMode(OPEN_IN_NEW_BG_TAB) activateModeToOpenInNewForegroundTab: -> @activateMode(OPEN_IN_NEW_FG_TAB) @@ -141,13 +130,12 @@ LinkHints = # of digits needed to enumerate all of the links on screen. # getVisibleClickableElements: -> - resultSet = DomUtils.evaluateXPath(@clickableElementsXPath, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE) + resultSet = DomUtils.getClickableElements() visibleElements = [] # Find all visible clickable elements. - for i in [0...resultSet.snapshotLength] by 1 - element = resultSet.snapshotItem(i) + for element in resultSet clientRect = DomUtils.getVisibleClientRect(element, clientRect) if (clientRect != null) visibleElements.push({element: element, rect: clientRect}) -- cgit v1.2.3 From c7e2f1cdef2d5e99761d7bb8ecbad91f89de6958 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 11:12:58 +0000 Subject: Inline DomUtils.getClickableElements --- content_scripts/link_hints.coffee | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 2ffe818f..fe36cee0 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -130,7 +130,41 @@ LinkHints = # of digits needed to enumerate all of the links on screen. # getVisibleClickableElements: -> - resultSet = DomUtils.getClickableElements() + elements = Array::slice.call(document.documentElement.getElementsByTagName "*") + resultSet = [] + + for element in elements + isClickable = false + tagName = element.tagName.toLowerCase() + isClickable = (-> + if element.hasAttribute "onclick" + true + else if element.hasAttribute "tabindex" + true + else if element.getAttribute "role" in ["button", "link"] + true + else if element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 + true + else if element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"] + true + else if tagName == "a" + true + else if tagName == "img" + mapName = element.getAttribute "usemap" + if mapName + map = document.querySelector(mapName.replace /^#/, "") + areas = Array::slice.call(map.getElementsByTagName "area") + resultSet.concat areas + false + else if (tagName == "input" and DomUtils.isSelectable element) or tagName == "textarea" + not (element.disabled or element.readOnly) + else if (tagName == "input" and element.getAttribute("type")?.toLowerCase() != "hidden") or + tagName in ["button", "select"] + not element.disabled + else + false + )() + resultSet.push element if isClickable visibleElements = [] -- cgit v1.2.3 From 28c275ae4128f0a2907c7ad3d27cedc81efe129a Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 11:41:10 +0000 Subject: Simplify finding clickable elements --- content_scripts/link_hints.coffee | 58 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 30 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index fe36cee0..18e9741d 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -134,37 +134,35 @@ LinkHints = resultSet = [] for element in elements - isClickable = false tagName = element.tagName.toLowerCase() - isClickable = (-> - if element.hasAttribute "onclick" - true - else if element.hasAttribute "tabindex" - true - else if element.getAttribute "role" in ["button", "link"] - true - else if element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 - true - else if element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"] - true - else if tagName == "a" - true - else if tagName == "img" - mapName = element.getAttribute "usemap" - if mapName - map = document.querySelector(mapName.replace /^#/, "") - areas = Array::slice.call(map.getElementsByTagName "area") - resultSet.concat areas - false - else if (tagName == "input" and DomUtils.isSelectable element) or tagName == "textarea" - not (element.disabled or element.readOnly) - else if (tagName == "input" and element.getAttribute("type")?.toLowerCase() != "hidden") or - tagName in ["button", "select"] - not element.disabled - else - false - )() - resultSet.push element if isClickable + + # Insert area elements that provide click functionality to an img. + if tagName == "img" + mapName = element.getAttribute "usemap" + if mapName + mapName = mapName.replace(/^#/, "").replace("\"", "\\\"") + map = document.querySelector "map[name=\"#{mapName}\"]" + areas = if map then Array::slice.call(map.getElementsByTagName "area") else [] + resultSet = resultSet.concat areas + + # Check for attributes that make an element clickable regardless of its tagName. + if (element.hasAttribute "onclick" or + element.hasAttribute "tabindex" or + element.getAttribute "role" in ["button", "link"] or + element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or + element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]) + resultSet.push element + continue + + switch tagName + when "a" + resultSet.push element + when "textarea", "input" + unless (tagName == "input" and element.getAttribute("type")?.toLowerCase() == "hidden") or + element.disabled or (element.readOnly and DomUtils.isSelectable element) + resultSet.push element + when "button", "select" + resultSet.push element unless element.disabled visibleElements = [] -- cgit v1.2.3 From 5f9290693ab0f35c46cea6cea0a9f5c06b4ee0ad Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 12:27:40 +0000 Subject: Combine rectangle calculation and clickable element detection --- content_scripts/link_hints.coffee | 51 ++++++++++++--------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 18e9741d..dd359a70 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -131,19 +131,23 @@ LinkHints = # getVisibleClickableElements: -> elements = Array::slice.call(document.documentElement.getElementsByTagName "*") - resultSet = [] + visibleElements = [] for element in elements tagName = element.tagName.toLowerCase() + isClickable = false # Insert area elements that provide click functionality to an img. if tagName == "img" mapName = element.getAttribute "usemap" if mapName + imgClientRects = element.getClientRects() mapName = mapName.replace(/^#/, "").replace("\"", "\\\"") map = document.querySelector "map[name=\"#{mapName}\"]" - areas = if map then Array::slice.call(map.getElementsByTagName "area") else [] - resultSet = resultSet.concat areas + if map and imgClientRects.length > 0 + areas = map.getElementsByTagName "area" + areaRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas + visibleElements = visibleElements.concat areaRects # Check for attributes that make an element clickable regardless of its tagName. if (element.hasAttribute "onclick" or @@ -151,46 +155,23 @@ LinkHints = element.getAttribute "role" in ["button", "link"] or element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]) - resultSet.push element - continue + isClickable = true + # Check for tagNames which are natively clickable. switch tagName when "a" - resultSet.push element + isClickable = true when "textarea", "input" unless (tagName == "input" and element.getAttribute("type")?.toLowerCase() == "hidden") or element.disabled or (element.readOnly and DomUtils.isSelectable element) - resultSet.push element + isClickable = true when "button", "select" - resultSet.push element unless element.disabled - - visibleElements = [] + isClickable = not element.disabled - # Find all visible clickable elements. - for element in resultSet - clientRect = DomUtils.getVisibleClientRect(element, clientRect) - if (clientRect != null) - visibleElements.push({element: element, rect: clientRect}) - - if (element.localName == "area") - map = element.parentElement - continue unless map - img = document.querySelector("img[usemap='#" + map.getAttribute("name") + "']") - continue unless img - imgClientRects = img.getClientRects() - continue if (imgClientRects.length == 0) - c = element.coords.split(/,/) - coords = [parseInt(c[0], 10), parseInt(c[1], 10), parseInt(c[2], 10), parseInt(c[3], 10)] - rect = { - top: imgClientRects[0].top + coords[1], - left: imgClientRects[0].left + coords[0], - right: imgClientRects[0].left + coords[2], - bottom: imgClientRects[0].top + coords[3], - width: coords[2] - coords[0], - height: coords[3] - coords[1] - } - - visibleElements.push({element: element, rect: rect}) + continue unless isClickable # If the element isn't clickable, do nothing. + clientRect = DomUtils.getVisibleClientRect element + if clientRect != null + visibleElements.push {element: element, rect: clientRect} visibleElements -- cgit v1.2.3 From 82beb23ce138505f0358ec8e15a56d20db6846dd Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 12:42:31 +0000 Subject: Remove redundant array conversion --- content_scripts/link_hints.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index dd359a70..4b039935 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -130,7 +130,7 @@ LinkHints = # of digits needed to enumerate all of the links on screen. # getVisibleClickableElements: -> - elements = Array::slice.call(document.documentElement.getElementsByTagName "*") + elements = document.documentElement.getElementsByTagName "*" visibleElements = [] for element in elements -- cgit v1.2.3 From 932652086c9ad601c92ef4aae250f9e0b6ad51a6 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 14:51:25 +0000 Subject: Ensure we only init CursorHider once --- content_scripts/vimium_frontend.coffee | 1 + 1 file changed, 1 insertion(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index e4680ff7..78014490 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1124,6 +1124,7 @@ CursorHider = CursorHider.isScrolling = false init: -> + return unless @cursorHideStyle? @cursorHideStyle = document.createElement("style") @cursorHideStyle.innerHTML = """ body * {pointer-events: none !important; cursor: none !important;} -- cgit v1.2.3 From 6ceb3bcc612cd4478187610cf61a82a8f95534ac Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 17 Dec 2014 15:37:57 +0000 Subject: Revert 932652086c9ad601c92ef4aae250f9e0b6ad51a6. --- content_scripts/vimium_frontend.coffee | 1 - 1 file changed, 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 78014490..e4680ff7 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1124,7 +1124,6 @@ CursorHider = CursorHider.isScrolling = false init: -> - return unless @cursorHideStyle? @cursorHideStyle = document.createElement("style") @cursorHideStyle.innerHTML = """ body * {pointer-events: none !important; cursor: none !important;} -- cgit v1.2.3 From 3b25fd52431130aa5c3e9359075574458c307ef2 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 17 Dec 2014 15:49:57 +0000 Subject: Disable CursorHider pending fix for #1345. --- content_scripts/vimium_frontend.coffee | 2 ++ 1 file changed, 2 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index e4680ff7..6dd244de 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1124,6 +1124,8 @@ CursorHider = CursorHider.isScrolling = false init: -> + # NOTE(smblott) CursorHider is currently disabled pending a fix for #1345. + return @cursorHideStyle = document.createElement("style") @cursorHideStyle.innerHTML = """ body * {pointer-events: none !important; cursor: none !important;} -- cgit v1.2.3 From c972978b43b943a1ad8709992d080bedbbe12ae2 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 17 Dec 2014 16:44:47 +0000 Subject: Add comment re. Math.sign(). --- content_scripts/scroller.coffee | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index 09470158..2f69fc7d 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -5,6 +5,10 @@ activatedElement = null # Return 0, -1 or 1: the sign of the argument. +# NOTE(smblott; 2014/12/17) We would like to use Math.sign(). However, according to this site +# (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign) Math.sign() was +# only introduced in Chrome 38. This caused problems in R1.48 for users with old Chrome installations. We +# can replace this with Math.sign() at some point. getSign = (val) -> if not val 0 -- cgit v1.2.3 From 2424b8ecc1eeb3b1728f7d4a30db77fccdc8fa05 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 14:39:52 +0000 Subject: Ensure cursor is hidden on scroll --- content_scripts/vimium_frontend.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 6dd244de..fcf5dac1 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1129,7 +1129,7 @@ CursorHider = @cursorHideStyle = document.createElement("style") @cursorHideStyle.innerHTML = """ body * {pointer-events: none !important; cursor: none !important;} - body {cursor: none !important;} + body, html {cursor: none !important;} """ window.addEventListener "mousemove", @onMouseMove window.addEventListener "scroll", @onScroll -- cgit v1.2.3 From 107cef3ecd0bdbcaba157b53cc3d20ff5e06b94f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 17 Dec 2014 20:21:33 +0000 Subject: Disable cursor hiding for Chrome versions < 39.0.2171.71 There is a bug for earlier versions of Chrome which leaves `pointer-events: none` stuck enabled for Google+ after a certain amount of scrolling. --- content_scripts/vimium_frontend.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index fcf5dac1..6f099e54 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1107,7 +1107,8 @@ Tween = CursorHider = # - # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible + # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible. + # Disabled for Chrome versions less than 39.0.2171.71 due to a browser error. # NOTE(smblott) onScroll and onMouseMove events come in pairs. # cursorHideStyle: null @@ -1124,8 +1125,10 @@ CursorHider = CursorHider.isScrolling = false init: -> - # NOTE(smblott) CursorHider is currently disabled pending a fix for #1345. - return + # Disable for Chrome versions less than 39.0.2171.71 due to a browser error. + chromeVersion = navigator.appVersion.match(/Chrome\/(.*?) /)?[1] || "" + return if 0 <= Utils.compareVersions "39.0.2171.71", chromeVersion + @cursorHideStyle = document.createElement("style") @cursorHideStyle.innerHTML = """ body * {pointer-events: none !important; cursor: none !important;} -- cgit v1.2.3 From 855e9a4e19ab0926f5531c37272f00a715f45ed8 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 10:33:09 +0000 Subject: Remove overlapping rects from link hints --- content_scripts/link_hints.coffee | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 4b039935..721070bb 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -173,7 +173,35 @@ LinkHints = if clientRect != null visibleElements.push {element: element, rect: clientRect} - visibleElements + # TODO(mrmr1993): Consider z-index. z-index affects behviour as follows: + # * The document has a local stacking context. + # * An element with z-index specified + # - sets its z-order position in the containing stacking context, and + # - creates a local stacking context containing its children. + # * An element (1) is shown above another element (2) if either + # - in the last stacking context which contains both an ancestor of (1) and an ancestor of (2), the + # ancestor of (1) has a higher z-index than the ancestor of (2); or + # - in the last stacking context which contains both an ancestor of (1) and an ancestor of (2), + # + the ancestors of (1) and (2) have equal z-index, and + # + the ancestor of (1) appears later in the DOM than the ancestor of (2). + # + # Remove rects from + nonOverlappingElements = [] + visibleElements = visibleElements.reverse() + while visibleElement = visibleElements.pop() + rects = [visibleElement.rect] + for {rect: negativeRect} in visibleElements + rects = Array::concat.apply [], (rects.map (rect) -> Utils.subtractRect rect, negativeRect) + if rects.length > 0 + nonOverlappingElements.push {element: visibleElement.element, rect: rects[0]} + else + # Every part of the element is covered by some other element, so just insert the whole element's + # rect. + # TODO(mrmr1993): This is probably the wrong thing to do, but we don't want to stop being able to + # click some elements that we could click before. + nonOverlappingElements.push visibleElement + + nonOverlappingElements # # Handles shift and esc keys. The other keys are passed to getMarkerMatcher().matchHintsByKey. -- cgit v1.2.3 From 4e6513c47b6be2e771b2a8db6d5506d157368602 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 10:40:33 +0000 Subject: Add link hint support for jsaction event listeners This was adapted from PR #1316, commit 846a19efe51bfc639ae1ee84e18a5f2d3e12aaff --- content_scripts/link_hints.coffee | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 721070bb..231e4ecd 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -157,6 +157,13 @@ LinkHints = element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]) isClickable = true + # Check for jsaction event listeners on the element. + if element.hasAttribute "jsaction" + jsactionRules = element.getAttribute("jsaction").split(";") + for jsactionRule in jsactionRules + ruleSplit = jsactionRule.split ":" + isClickable = true if ruleSplit[0] == "click" or (ruleSplit.length == 1 and ruleSplit[0] != "none") + # Check for tagNames which are natively clickable. switch tagName when "a" -- cgit v1.2.3 From 3132ae601b2de787f9cddd3fd77b36767e2e467e Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 11:00:15 +0000 Subject: Complete a partially written comment --- content_scripts/link_hints.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 231e4ecd..57b46e6b 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -192,7 +192,7 @@ LinkHints = # + the ancestors of (1) and (2) have equal z-index, and # + the ancestor of (1) appears later in the DOM than the ancestor of (2). # - # Remove rects from + # Remove rects from elements where another clickable element lies above it. nonOverlappingElements = [] visibleElements = visibleElements.reverse() while visibleElement = visibleElements.pop() -- cgit v1.2.3 From 9c9c48598534c2a0cd8aec28a4a806d74f28e090 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 11:56:53 +0000 Subject: Move rect functions to their own file --- content_scripts/link_hints.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 57b46e6b..27402250 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -198,7 +198,7 @@ LinkHints = while visibleElement = visibleElements.pop() rects = [visibleElement.rect] for {rect: negativeRect} in visibleElements - rects = Array::concat.apply [], (rects.map (rect) -> Utils.subtractRect rect, negativeRect) + rects = Array::concat.apply [], (rects.map (rect) -> Rect.subtract rect, negativeRect) if rects.length > 0 nonOverlappingElements.push {element: visibleElement.element, rect: rects[0]} else -- cgit v1.2.3 From 93a5a571cc06087b2abfe383424c2fcc6ef02358 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 13:29:46 +0000 Subject: Split textarea and input detection in link hints --- content_scripts/link_hints.coffee | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 27402250..95026cba 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -168,12 +168,15 @@ LinkHints = switch tagName when "a" isClickable = true - when "textarea", "input" - unless (tagName == "input" and element.getAttribute("type")?.toLowerCase() == "hidden") or - element.disabled or (element.readOnly and DomUtils.isSelectable element) + when "textarea" + isClickable = not element.disabled and not element.readOnly + when "input" + unless element.getAttribute("type")?.toLowerCase() == "hidden" or + element.disabled or + (element.readOnly and DomUtils.isSelectable element) isClickable = true when "button", "select" - isClickable = not element.disabled + isClickable = true unless element.disabled continue unless isClickable # If the element isn't clickable, do nothing. clientRect = DomUtils.getVisibleClientRect element -- cgit v1.2.3 From aa047d5b4b6124f7e2d1230ab590b9244db6ebb3 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 13:39:22 +0000 Subject: Improve comments for LinkHints.getVisibleClickableElements --- content_scripts/link_hints.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 95026cba..b1c44e42 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -125,9 +125,11 @@ LinkHints = marker # - # Returns all clickable elements that are not hidden and are in the current viewport. - # We prune invisible elements partly for performance reasons, but moreso it's to decrease the number - # of digits needed to enumerate all of the links on screen. + # Returns all clickable elements that are not hidden and are in the current viewport, along with rectangles + # at which (parts of) the elements are displayed. + # In the process, we try to find rects where elements do not overlap so that link hints are unambiguous. + # Because of this, the rects returned will frequently *NOT* be equivalent to the rects for the whole + # element. # getVisibleClickableElements: -> elements = document.documentElement.getElementsByTagName "*" @@ -197,6 +199,7 @@ LinkHints = # # Remove rects from elements where another clickable element lies above it. nonOverlappingElements = [] + # Traverse the DOM from first to last, since later elements show above earlier elements. visibleElements = visibleElements.reverse() while visibleElement = visibleElements.pop() rects = [visibleElement.rect] -- cgit v1.2.3 From e56dea52d4e0eead061f676891c04cfc07336194 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 15:08:59 +0000 Subject: Add brackets so the code compiles as expected --- content_scripts/link_hints.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index b1c44e42..ba1603e4 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -152,9 +152,9 @@ LinkHints = visibleElements = visibleElements.concat areaRects # Check for attributes that make an element clickable regardless of its tagName. - if (element.hasAttribute "onclick" or - element.hasAttribute "tabindex" or - element.getAttribute "role" in ["button", "link"] or + if (element.hasAttribute("onclick") or + element.hasAttribute("tabindex") or + element.getAttribute("role")?.toLowerCase() in ["button", "link"] or element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]) isClickable = true -- cgit v1.2.3 From 4cfdc55b2054f3b00daf2aa2d8ffd482b4e3aaf9 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Thu, 18 Dec 2014 22:46:17 +0000 Subject: Don't show a link hint for certain link hint elements Disables showing link hint for elements which * are identified as clickableonly by the tabindex attribute, and * have the entirety of their contents overlapped by other clickable elements. This removes some redundant link hints that were visible on Google+, and hopefully shouldn't remove any useful link hints. --- content_scripts/link_hints.coffee | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index ba1603e4..6934e5b8 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -138,6 +138,7 @@ LinkHints = for element in elements tagName = element.tagName.toLowerCase() isClickable = false + onlyHasTabIndex = false # Insert area elements that provide click functionality to an img. if tagName == "img" @@ -153,7 +154,6 @@ LinkHints = # Check for attributes that make an element clickable regardless of its tagName. if (element.hasAttribute("onclick") or - element.hasAttribute("tabindex") or element.getAttribute("role")?.toLowerCase() in ["button", "link"] or element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]) @@ -180,10 +180,15 @@ LinkHints = when "button", "select" isClickable = true unless element.disabled + # Elements with tabindex are sometimes useful, but usually not. We can treat them as second class + # citizens when it improves UX, so take special note of them. + if element.hasAttribute("tabindex") and not isClickable + isClickable = onlyHasTabIndex = true + continue unless isClickable # If the element isn't clickable, do nothing. clientRect = DomUtils.getVisibleClientRect element if clientRect != null - visibleElements.push {element: element, rect: clientRect} + visibleElements.push {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex} # TODO(mrmr1993): Consider z-index. z-index affects behviour as follows: # * The document has a local stacking context. @@ -209,10 +214,10 @@ LinkHints = nonOverlappingElements.push {element: visibleElement.element, rect: rects[0]} else # Every part of the element is covered by some other element, so just insert the whole element's - # rect. + # rect. Except for elements with tabIndex set; these are often more trouble than they're worth. # TODO(mrmr1993): This is probably the wrong thing to do, but we don't want to stop being able to # click some elements that we could click before. - nonOverlappingElements.push visibleElement + nonOverlappingElements.push visibleElement unless visibleElement.onlyHasTabIndex nonOverlappingElements -- cgit v1.2.3 From 1059f98d5c9a552b2fa3fbcdddc7e44d0676056e Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Fri, 19 Dec 2014 01:10:41 +0000 Subject: Detect aria properties for disabling/hiding elements in link hints --- content_scripts/link_hints.coffee | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 6934e5b8..fa7fa937 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -152,6 +152,11 @@ LinkHints = areaRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas visibleElements = visibleElements.concat areaRects + # Check aria properties to see if the element should be ignored. + if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or + element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"]) + continue # No point continuing the loop; this element should never have a link hint + # Check for attributes that make an element clickable regardless of its tagName. if (element.hasAttribute("onclick") or element.getAttribute("role")?.toLowerCase() in ["button", "link"] or -- cgit v1.2.3 From 226cfb8011e83e48b9c723ebc891ad0e9a7c82ec Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 19 Dec 2014 05:38:29 +0000 Subject: Tidy cursor hider, move haveChromeVersion to utils. --- content_scripts/vimium_frontend.coffee | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 6f099e54..21a05de6 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1107,9 +1107,7 @@ Tween = CursorHider = # - # Hides the cursor when the browser scrolls, and prevent mouse from hovering while invisible. - # Disabled for Chrome versions less than 39.0.2171.71 due to a browser error. - # NOTE(smblott) onScroll and onMouseMove events come in pairs. + # Hide the cursor when the browser scrolls, and prevent mouse from hovering while invisible. # cursorHideStyle: null isScrolling: false @@ -1125,9 +1123,9 @@ CursorHider = CursorHider.isScrolling = false init: -> - # Disable for Chrome versions less than 39.0.2171.71 due to a browser error. - chromeVersion = navigator.appVersion.match(/Chrome\/(.*?) /)?[1] || "" - return if 0 <= Utils.compareVersions "39.0.2171.71", chromeVersion + # Disable cursor hiding for Chrome versions less than 39.0.2171.71 due to a suspected browser error. + # See #1345 and #1348. + return unless Utils.haveChromeVersion "39.0.2171.71" @cursorHideStyle = document.createElement("style") @cursorHideStyle.innerHTML = """ -- cgit v1.2.3 From 7a99764137d600dc65e76da5093e91d2ecd5eaeb Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 19 Dec 2014 06:37:14 +0000 Subject: Disable cursor hider. See #1359. There appear to be common cases where the cursor hider doesn't hide the cursor. So the UX is inconsistent. We need to consider whether this is fixable or acceptable. --- content_scripts/vimium_frontend.coffee | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 21a05de6..8d8a67e4 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1123,6 +1123,10 @@ CursorHider = CursorHider.isScrolling = false init: -> + # Temporarily disabled pending consideration of #1359 (in particular, whether cursor hiding is too fragile + # as to provide a consistent UX). + return + # Disable cursor hiding for Chrome versions less than 39.0.2171.71 due to a suspected browser error. # See #1345 and #1348. return unless Utils.haveChromeVersion "39.0.2171.71" -- cgit v1.2.3 From 0506ea78ebcd003b47db4f5587e07251f7c8682b Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 19 Dec 2014 07:10:02 +0000 Subject: Initialize scroller to first scrollable element. See #1358. --- content_scripts/scroller.coffee | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index 2f69fc7d..48b99cff 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -84,6 +84,16 @@ findScrollableElement = (element, direction, amount, factor) -> element = element.parentElement || document.body element +# On some pages, document.body is not scrollable. Here, we search the document for the first visible element +# which does scroll vertically. This is used to initialize activatedElement. See #1358. +firstScrollableElement = (element=document.body) -> + if doesScroll element, "y", 1, 1 + element + else + for child in element.children + return ele if DomUtils.getVisibleClientRect(child) and ele = firstScrollableElement child + null + checkVisibility = (element) -> # If the activated element has been scrolled completely offscreen, then subsequent changes in its scroll # position will not provide any more visual feedback to the user. Therefore, we deactivate it so that @@ -206,7 +216,7 @@ Scroller = window.scrollBy(0, amount) return - activatedElement ||= document.body + activatedElement ||= firstScrollableElement() || document.body return unless activatedElement # Avoid the expensive scroll calculation if it will not be used. This reduces costs during smooth, @@ -218,7 +228,7 @@ Scroller = scrollTo: (direction, pos) -> return unless document.body or activatedElement - activatedElement ||= document.body + activatedElement ||= firstScrollableElement() || document.body element = findScrollableElement activatedElement, direction, pos, 1 amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName] -- cgit v1.2.3 From ac2e1077107d72ef82e1424634aa86945696e6b4 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 19 Dec 2014 08:35:03 +0000 Subject: Initialize scroller to *largest* visible scrollable element. --- content_scripts/scroller.coffee | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index 48b99cff..dec817a1 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -84,14 +84,17 @@ findScrollableElement = (element, direction, amount, factor) -> element = element.parentElement || document.body element -# On some pages, document.body is not scrollable. Here, we search the document for the first visible element -# which does scroll vertically. This is used to initialize activatedElement. See #1358. +# On some pages, document.body is not scrollable. Here, we search the document for the largest visible +# element which does scroll vertically. This is used to initialize activatedElement. See #1358. firstScrollableElement = (element=document.body) -> if doesScroll element, "y", 1, 1 element else - for child in element.children - return ele if DomUtils.getVisibleClientRect(child) and ele = firstScrollableElement child + children = ({element: child, rect: DomUtils.getVisibleClientRect(child)} for child in element.children) + children = children.filter (child) -> child.rect # Filter out non-visible elements. + children.map (child) -> child.area = child.rect.width * child.rect.height + for child in children.sort((a,b) -> b.area - a.area) # Largest to smallest by visible area. + return ele if ele = firstScrollableElement child.element null checkVisibility = (element) -> -- cgit v1.2.3 From 24b968943acddd224dd795c1e26425d3b75520e2 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 19 Dec 2014 08:49:01 +0000 Subject: Delay initialization of activeElement. We could incorrectly initialize activeElement to document.body if the scroller is called too early; so delay initialization. It's safe to leave activeElement as null. --- content_scripts/scroller.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index dec817a1..3692a002 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -219,7 +219,7 @@ Scroller = window.scrollBy(0, amount) return - activatedElement ||= firstScrollableElement() || document.body + activatedElement ||= firstScrollableElement() return unless activatedElement # Avoid the expensive scroll calculation if it will not be used. This reduces costs during smooth, @@ -230,8 +230,8 @@ Scroller = CoreScroller.scroll element, direction, elementAmount scrollTo: (direction, pos) -> - return unless document.body or activatedElement - activatedElement ||= firstScrollableElement() || document.body + activatedElement ||= firstScrollableElement() + return unless activatedElement element = findScrollableElement activatedElement, direction, pos, 1 amount = getDimension(element,direction,pos) - element[scrollProperties[direction].axisName] -- cgit v1.2.3 From 5f4ae0f11340c0c7385d8a91228941128a9731da Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 19 Dec 2014 11:25:51 +0000 Subject: Also test negative direction when initializing scroller. The right scrollable element to choose may be scrolled to the bottom, so we won't find it if we only test scrolling down. We need to test scrolling up as well. --- content_scripts/scroller.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index 3692a002..fdfb7ddc 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -67,7 +67,7 @@ shouldScroll = (element, direction) -> # Instead, we scroll the element by 1 or -1 and see if it moved (then put it back). :factor is the factor by # which :scrollBy and :scrollTo will later scale the scroll amount. :factor can be negative, so we need it # here in order to decide whether we should test a forward scroll or a backward scroll. -# Bug verified in Chrome 38.0.2125.104. +# Bug last verified in Chrome 38.0.2125.104. doesScroll = (element, direction, amount, factor) -> # amount is treated as a relative amount, which is correct for relative scrolls. For absolute scrolls (only # gg, G, and friends), amount can be either a string ("max" or "viewSize") or zero. In the former case, @@ -87,7 +87,7 @@ findScrollableElement = (element, direction, amount, factor) -> # On some pages, document.body is not scrollable. Here, we search the document for the largest visible # element which does scroll vertically. This is used to initialize activatedElement. See #1358. firstScrollableElement = (element=document.body) -> - if doesScroll element, "y", 1, 1 + if doesScroll(element, "y", 1, 1) or doesScroll(element, "y", -1, 1) element else children = ({element: child, rect: DomUtils.getVisibleClientRect(child)} for child in element.children) -- cgit v1.2.3 From 3a688f754ebd647ce56b33d18c5744759c5efe95 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sat, 20 Dec 2014 16:18:28 +0000 Subject: Use ||= to not ignore some clickable elements, no negative tabindex Elements with `tabindex="n"` for parseInt(n) < 0 cannot be selected by pressing the tab key, according to the spec. If we have no other reason to suspect that the element is clickable, we may as well ignore them. --- content_scripts/link_hints.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index fa7fa937..9eb7b87c 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -176,18 +176,19 @@ LinkHints = when "a" isClickable = true when "textarea" - isClickable = not element.disabled and not element.readOnly + isClickable ||= not element.disabled and not element.readOnly when "input" - unless element.getAttribute("type")?.toLowerCase() == "hidden" or - element.disabled or - (element.readOnly and DomUtils.isSelectable element) - isClickable = true + isClickable ||= not (element.getAttribute("type")?.toLowerCase() == "hidden" or + element.disabled or + (element.readOnly and DomUtils.isSelectable element)) when "button", "select" - isClickable = true unless element.disabled + isClickable ||= not element.disabled # Elements with tabindex are sometimes useful, but usually not. We can treat them as second class # citizens when it improves UX, so take special note of them. - if element.hasAttribute("tabindex") and not isClickable + tabIndexValue = element.getAttribute("tabindex") + tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue + unless isClickable or isNaN(tabIndex) or tabIndex < 0 isClickable = onlyHasTabIndex = true continue unless isClickable # If the element isn't clickable, do nothing. -- cgit v1.2.3 From 15d094675c4e354becf58b5f66754dd939d5daeb Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 22 Dec 2014 11:54:49 +0000 Subject: Rename a poorly named variable --- content_scripts/link_hints.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 9eb7b87c..616d40ee 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -149,8 +149,8 @@ LinkHints = map = document.querySelector "map[name=\"#{mapName}\"]" if map and imgClientRects.length > 0 areas = map.getElementsByTagName "area" - areaRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas - visibleElements = visibleElements.concat areaRects + areasAndRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas + visibleElements = visibleElements.concat areasAndRects # Check aria properties to see if the element should be ignored. if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or -- cgit v1.2.3 From 76af56da84753163adc4dbf943374a10f0cb8321 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 22 Dec 2014 11:56:18 +0000 Subject: Use push with a splat rather than concat --- content_scripts/link_hints.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 616d40ee..c4bf3f96 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -150,7 +150,7 @@ LinkHints = if map and imgClientRects.length > 0 areas = map.getElementsByTagName "area" areasAndRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas - visibleElements = visibleElements.concat areasAndRects + visibleElements.push areasAndRects... # Check aria properties to see if the element should be ignored. if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or -- cgit v1.2.3 From c1ffbc88ed1e340a7a046e1d75499642bf220e7f Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 22 Dec 2014 12:35:13 +0000 Subject: Prefer `||=` to `= true if` --- content_scripts/link_hints.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index c4bf3f96..df442ea6 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -169,7 +169,7 @@ LinkHints = jsactionRules = element.getAttribute("jsaction").split(";") for jsactionRule in jsactionRules ruleSplit = jsactionRule.split ":" - isClickable = true if ruleSplit[0] == "click" or (ruleSplit.length == 1 and ruleSplit[0] != "none") + isClickable ||= ruleSplit[0] == "click" or (ruleSplit.length == 1 and ruleSplit[0] != "none") # Check for tagNames which are natively clickable. switch tagName -- cgit v1.2.3 From 278820f544d201b975a73ebe82d4a67792b967df Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 22 Dec 2014 12:39:47 +0000 Subject: Use a splat instead of apply --- content_scripts/link_hints.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index df442ea6..3a9e2027 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -215,7 +215,8 @@ LinkHints = while visibleElement = visibleElements.pop() rects = [visibleElement.rect] for {rect: negativeRect} in visibleElements - rects = Array::concat.apply [], (rects.map (rect) -> Rect.subtract rect, negativeRect) + # Subtract negativeRect from every rect in rects, and concatenate the arrays of rects that result. + rects = [].concat (rects.map (rect) -> Rect.subtract rect, negativeRect)... if rects.length > 0 nonOverlappingElements.push {element: visibleElement.element, rect: rects[0]} else -- cgit v1.2.3 From d7597f407ea7932b1f985160c3f688801d035fcc Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Wed, 29 Oct 2014 21:34:32 +0000 Subject: Reintroduce 2c7bebb5f2c873850c2b2d82013cab4eb3d4913c --- content_scripts/vimium_frontend.coffee | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 2de612d4..90ac227a 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -436,15 +436,13 @@ onKeydown = (event) -> keyChar = "<" + keyChar + ">" if (isInsertMode() && KeyboardUtils.isEscape(event)) - # Note that we can't programmatically blur out of Flash embeds from Javascript. - if (!isEmbed(event.srcElement)) + if isEditable(event.srcElement) or isEmbed(event.srcElement) # Remove focus so the user can't just get himself back into insert mode by typing in the same input # box. - if (isEditable(event.srcElement)) - event.srcElement.blur() - exitInsertMode() - DomUtils.suppressEvent event - KeydownEvents.push event + event.srcElement.blur() + exitInsertMode() + DomUtils.suppressEvent event + handledKeydownEvents.push event else if (findMode) if (KeyboardUtils.isEscape(event)) -- cgit v1.2.3 From 99d784ff593378be41b5e6eb87867826fee9e921 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 22 Dec 2014 16:56:29 +0000 Subject: Add comment regarding .blur() on embeds. Including embeds for .blur() etc. 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. --- content_scripts/vimium_frontend.coffee | 3 +++ 1 file changed, 3 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index eacba682..071e87a6 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -458,6 +458,9 @@ onKeydown = (event) -> if isEditable(event.srcElement) or isEmbed(event.srcElement) # Remove 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() etc. 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. event.srcElement.blur() exitInsertMode() DomUtils.suppressEvent event -- cgit v1.2.3 From b26f4aed8585418d18dfc43262070c8c8741e5e3 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sun, 28 Dec 2014 19:24:33 +0000 Subject: Add UIComponent code for iframes --- content_scripts/ui_component.coffee | 66 +++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 content_scripts/ui_component.coffee (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee new file mode 100644 index 00000000..c0889e7f --- /dev/null +++ b/content_scripts/ui_component.coffee @@ -0,0 +1,66 @@ +class UIComponent + iframeElement: null + iframePort: null + messageEventListeners: [] + showStyle: "" + hideStyle: "" + + constructor: (iframeUrl, className) -> + @iframeElement = document.createElement "iframe" + @iframeElement.className = className + @iframeElement.seamless = "seamless" + @iframeElement.src = chrome.runtime.getURL iframeUrl + @iframeElement.addEventListener "load", => @openPort() + document.documentElement.appendChild @iframeElement + @hide() + + # Open a port and pass it to the iframe via window.postMessage. + openPort: -> + messageChannel = new MessageChannel() + @iframePort = messageChannel.port1 + @iframePort.onmessage = (event) => @handleMessage event + + # Get iframeMessageSecret so the iframe can determine that our message isn't the page impersonating us. + chrome.storage.local.get "iframeMessageSecret", ({iframeMessageSecret: secret}) => + @iframeElement.contentWindow.postMessage secret, chrome.runtime.getURL(""), [messageChannel.port2] + + postMessage: (data) -> @iframePort.postMessage data + + # Execute each event listener on the current event until we get a falsy return value. + handleMessage: (event) -> + for listener in @messageEventListeners + retVal = listener.call this, event + return false unless retVal + true + + addEventListener: (type, listener) -> + if type == "message" + @messageEventListeners.push listener + undefined + + removeEventListener: (type, listener) -> + if type == "message" + listenerIndex = @messageEventListeners.indexOf listener + if listenerIndex > -1 + @messageEventListeners = @messageEventListeners.splice listenerIndex, 1 + undefined + + setHideStyle: (@hideStyle) -> + @hide() if @showing == false + + setShowStyle: (@showStyle) -> + @show() if @showing == true + + show: -> + return unless @iframeElement? + @iframeElement.setAttribute "style", @showStyle + @iframeElement.focus() + @showing = true + + hide: -> + return unless @iframeElement? + @iframeElement.setAttribute "style", @hideStyle + @showing = false + +root = exports ? window +root.UIComponent = UIComponent -- cgit v1.2.3 From d20caa49075c0605ec2196416a5633d5e205b615 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Sun, 28 Dec 2014 19:53:06 +0000 Subject: Add an example test for UIComponent --- content_scripts/vimium_frontend.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 1f116f88..245e481a 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -181,6 +181,8 @@ window.addEventListener "focus", -> initializeOnDomReady = -> enterInsertModeIfElementIsFocused() if isEnabledForUrl + testUIComponentSetup() + # Tell the background page we're in the dom ready state. chrome.runtime.connect({ name: "domReady" }) @@ -920,6 +922,10 @@ exitFindMode = -> window.showHelpDialog = (html, fid) -> return if (isShowingHelpDialog || !document.body || fid != frameId) + + testUIComponent.show() + testUIComponent.postMessage "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" + isShowingHelpDialog = true container = document.createElement("div") container.id = "vimiumHelpDialogContainer" @@ -966,6 +972,9 @@ window.showHelpDialog = (html, fid) -> hideHelpDialog = (clickEvent) -> + + testUIComponent.hide() + isShowingHelpDialog = false helpDialog = document.getElementById("vimiumHelpDialogContainer") if (helpDialog) @@ -1086,6 +1095,12 @@ Tween = value = (elapsed / state.duration) * (state.to - state.from) + state.from state.onUpdate(value) +testUIComponent = null +testUIComponentSetup = -> + testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent" + testUIComponent.setHideStyle "display: none;" + testUIComponent.setShowStyle "display: block;" + initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) window.addEventListener("unload", unregisterFrame) -- cgit v1.2.3 From 6c1bbf0ab5781951364464c5fa68ad22f74c9fee Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Dec 2014 07:15:44 +0000 Subject: Scroller; check document.body exists. --- content_scripts/scroller.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee index fdfb7ddc..889dc042 100644 --- a/content_scripts/scroller.coffee +++ b/content_scripts/scroller.coffee @@ -219,7 +219,7 @@ Scroller = window.scrollBy(0, amount) return - activatedElement ||= firstScrollableElement() + activatedElement ||= document.body and firstScrollableElement() return unless activatedElement # Avoid the expensive scroll calculation if it will not be used. This reduces costs during smooth, @@ -230,7 +230,7 @@ Scroller = CoreScroller.scroll element, direction, elementAmount scrollTo: (direction, pos) -> - activatedElement ||= firstScrollableElement() + activatedElement ||= document.body and firstScrollableElement() return unless activatedElement element = findScrollableElement activatedElement, direction, pos, 1 -- cgit v1.2.3 From 71af7f016f51e3c8b9c1fcfba46cb8289c91e030 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Dec 2014 08:12:59 +0000 Subject: IFrame framework; develop demo. --- content_scripts/vimium_frontend.coffee | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 245e481a..5c166ff0 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -923,9 +923,6 @@ exitFindMode = -> window.showHelpDialog = (html, fid) -> return if (isShowingHelpDialog || !document.body || fid != frameId) - testUIComponent.show() - testUIComponent.postMessage "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" - isShowingHelpDialog = true container = document.createElement("div") container.id = "vimiumHelpDialogContainer" @@ -972,9 +969,6 @@ window.showHelpDialog = (html, fid) -> hideHelpDialog = (clickEvent) -> - - testUIComponent.hide() - isShowingHelpDialog = false helpDialog = document.getElementById("vimiumHelpDialogContainer") if (helpDialog) @@ -1101,6 +1095,10 @@ testUIComponentSetup = -> testUIComponent.setHideStyle "display: none;" testUIComponent.setShowStyle "display: block;" +window.activateTestUIComponent = -> + testUIComponent.show() + testUIComponent.postMessage "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" + initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) window.addEventListener("unload", unregisterFrame) -- cgit v1.2.3 From 5ea0f75a00b592956981bf8f6f7a0d2fa89620ae Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 09:33:34 +0000 Subject: Close UIComponent iframes when pressing esc by default --- content_scripts/ui_component.coffee | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index c0889e7f..f47719e5 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -14,6 +14,8 @@ class UIComponent document.documentElement.appendChild @iframeElement @hide() + @addEventListener "message", handleHideMessage + # Open a port and pass it to the iframe via window.postMessage. openPort: -> messageChannel = new MessageChannel() @@ -30,6 +32,7 @@ class UIComponent handleMessage: (event) -> for listener in @messageEventListeners retVal = listener.call this, event + retVal ?= true return false unless retVal true @@ -60,7 +63,15 @@ class UIComponent hide: -> return unless @iframeElement? @iframeElement.setAttribute "style", @hideStyle + window.focus() @showing = false +handleHideMessage = (event) -> + if event.data == "hide" + @hide() + false + else + true + root = exports ? window root.UIComponent = UIComponent -- cgit v1.2.3 From e5e0ddf24c7975e994b816651beaa4d0cefb94b7 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 10:07:25 +0000 Subject: Initialise hide/show styles for UIComponent --- content_scripts/ui_component.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index f47719e5..ce1af082 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -2,8 +2,8 @@ class UIComponent iframeElement: null iframePort: null messageEventListeners: [] - showStyle: "" - hideStyle: "" + showStyle: "display: block;" + hideStyle: "display: none;" constructor: (iframeUrl, className) -> @iframeElement = document.createElement "iframe" -- cgit v1.2.3 From 4e3ef0b401cfb4682a17a1ee88058ed76d64be20 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 10:29:09 +0000 Subject: Small changes to UIComponent --- content_scripts/ui_component.coffee | 17 +++++++++++------ content_scripts/vimium_frontend.coffee | 1 - 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index ce1af082..b0e4f71c 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -5,13 +5,16 @@ class UIComponent showStyle: "display: block;" hideStyle: "display: none;" - constructor: (iframeUrl, className) -> + constructor: (iframeUrl, className, showStyle, hideStyle) -> @iframeElement = document.createElement "iframe" @iframeElement.className = className @iframeElement.seamless = "seamless" @iframeElement.src = chrome.runtime.getURL iframeUrl @iframeElement.addEventListener "load", => @openPort() document.documentElement.appendChild @iframeElement + + @setShowStyle showStyle if showStyle? + @setHideStyle hideStyle if showStyle? @hide() @addEventListener "message", handleHideMessage @@ -43,9 +46,7 @@ class UIComponent removeEventListener: (type, listener) -> if type == "message" - listenerIndex = @messageEventListeners.indexOf listener - if listenerIndex > -1 - @messageEventListeners = @messageEventListeners.splice listenerIndex, 1 + @messageEventListeners = @messageEventListeners.filter (f) -> f != listener undefined setHideStyle: (@hideStyle) -> @@ -54,14 +55,18 @@ class UIComponent setShowStyle: (@showStyle) -> @show() if @showing == true + setStyles: (@showStyle = @showStyle, @hideStyle = @hideStyle) -> + if @showing + @show() + else + @hide() + show: -> - return unless @iframeElement? @iframeElement.setAttribute "style", @showStyle @iframeElement.focus() @showing = true hide: -> - return unless @iframeElement? @iframeElement.setAttribute "style", @hideStyle window.focus() @showing = false diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 5c166ff0..a505a84a 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -922,7 +922,6 @@ exitFindMode = -> window.showHelpDialog = (html, fid) -> return if (isShowingHelpDialog || !document.body || fid != frameId) - isShowingHelpDialog = true container = document.createElement("div") container.id = "vimiumHelpDialogContainer" -- cgit v1.2.3 From 0433dd338258dc39466593e0bce5fa3253f8d6d5 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 10:38:25 +0000 Subject: Allow message passing directly from UIComponent.show --- content_scripts/ui_component.coffee | 7 ++++--- content_scripts/vimium_frontend.coffee | 2 -- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index b0e4f71c..f0593f1b 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -29,9 +29,9 @@ class UIComponent chrome.storage.local.get "iframeMessageSecret", ({iframeMessageSecret: secret}) => @iframeElement.contentWindow.postMessage secret, chrome.runtime.getURL(""), [messageChannel.port2] - postMessage: (data) -> @iframePort.postMessage data + postMessage: (message) -> @iframePort.postMessage message - # Execute each event listener on the current event until we get a falsy return value. + # Execute each event listener on the current event until we get a non-null falsy return value. handleMessage: (event) -> for listener in @messageEventListeners retVal = listener.call this, event @@ -61,7 +61,8 @@ class UIComponent else @hide() - show: -> + show: (message) -> + @postMessage message if message? @iframeElement.setAttribute "style", @showStyle @iframeElement.focus() @showing = true diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index a505a84a..beb6c528 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1091,8 +1091,6 @@ Tween = testUIComponent = null testUIComponentSetup = -> testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent" - testUIComponent.setHideStyle "display: none;" - testUIComponent.setShowStyle "display: block;" window.activateTestUIComponent = -> testUIComponent.show() -- cgit v1.2.3 From f53af786275c98ce08f51936b165d799a41de024 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 10:39:44 +0000 Subject: Update test for UIComponent --- content_scripts/vimium_frontend.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index beb6c528..502ecaef 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1093,8 +1093,7 @@ testUIComponentSetup = -> testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent" window.activateTestUIComponent = -> - testUIComponent.show() - testUIComponent.postMessage "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" + testUIComponent.show "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) -- cgit v1.2.3 From 26f37bb0dbda2d833f508db373629408c7ed6e09 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 10:42:28 +0000 Subject: Remove key handling in UIComponentServer/UIComponent code --- content_scripts/ui_component.coffee | 2 -- 1 file changed, 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index f0593f1b..12a024e4 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -17,8 +17,6 @@ class UIComponent @setHideStyle hideStyle if showStyle? @hide() - @addEventListener "message", handleHideMessage - # Open a port and pass it to the iframe via window.postMessage. openPort: -> messageChannel = new MessageChannel() -- cgit v1.2.3 From 4af43db2e65af772787ab55c44604f4760eaee3d Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 10:46:42 +0000 Subject: Update UIComponent test --- content_scripts/vimium_frontend.coffee | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 502ecaef..a0623d32 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1091,6 +1091,13 @@ Tween = testUIComponent = null testUIComponentSetup = -> testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent" + testUIComponent.addEventListener "message", (event) -> + if event.data == "hide" + @hide() + window.focus() + false + else + true window.activateTestUIComponent = -> testUIComponent.show "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" -- cgit v1.2.3 From a78d49c8a9cac57492f78a90246ce7695cf8e036 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 11:26:30 +0000 Subject: Add a comment clarifying why we no longer use XPath for link hints --- content_scripts/link_hints.coffee | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 3a9e2027..b605c2ec 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -135,6 +135,11 @@ LinkHints = elements = document.documentElement.getElementsByTagName "*" visibleElements = [] + # The order of elements here is important; they should appear in the order they are in the DOM, so that + # we can work out which element is on top when multiple elements overlap. Detecting elements in this loop + # is the sensible, efficient way to ensure this happens. + # NOTE(mrmr1993): Our previous method (combined XPath and DOM traversal for jsaction) couldn't provide + # this, so it's necessary to check whether elements are clickable in order, as we do below. for element in elements tagName = element.tagName.toLowerCase() isClickable = false -- cgit v1.2.3 From f38a834cc38d17b086253dea197e6a8945551377 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 11:29:12 +0000 Subject: Add UIComponent.active, so we can specify whether to focus the frame --- content_scripts/ui_component.coffee | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index 12a024e4..10450778 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -59,10 +59,14 @@ class UIComponent else @hide() + activate: (message) -> + @postMessage message if message? + @show() unless @showing + @iframeElement.focus() + show: (message) -> @postMessage message if message? @iframeElement.setAttribute "style", @showStyle - @iframeElement.focus() @showing = true hide: -> -- cgit v1.2.3 From a6dc63fd7c49926e6ad32174621b32eeb3fd9283 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 11:30:41 +0000 Subject: Update UIComponent test --- content_scripts/vimium_frontend.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index a0623d32..3f898b74 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1100,7 +1100,7 @@ testUIComponentSetup = -> true window.activateTestUIComponent = -> - testUIComponent.show "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" + testUIComponent.activate "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) -- cgit v1.2.3 From da04ee17472177b7ae0474712090d0604db2556e Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 11:46:03 +0000 Subject: Move link hint clickable element detection to its own function --- content_scripts/link_hints.coffee | 127 ++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 59 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index b605c2ec..ea4be397 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -124,6 +124,72 @@ LinkHints = marker + # + # Determine whether the element is visible and clickable. If it is, return the element and the rect + # bounding the element in the viewport. + getVisibleClickable: (element) -> + tagName = element.tagName.toLowerCase() + isClickable = false + onlyHasTabIndex = false + + # Insert area elements that provide click functionality to an img. + if tagName == "img" + mapName = element.getAttribute "usemap" + if mapName + imgClientRects = element.getClientRects() + mapName = mapName.replace(/^#/, "").replace("\"", "\\\"") + map = document.querySelector "map[name=\"#{mapName}\"]" + if map and imgClientRects.length > 0 + areas = map.getElementsByTagName "area" + areasAndRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas + visibleElements.push areasAndRects... + + # Check aria properties to see if the element should be ignored. + if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or + element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"]) + return null # This element should never have a link hint. + + # Check for attributes that make an element clickable regardless of its tagName. + if (element.hasAttribute("onclick") or + element.getAttribute("role")?.toLowerCase() in ["button", "link"] or + element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or + element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]) + isClickable = true + + # Check for jsaction event listeners on the element. + if element.hasAttribute "jsaction" + jsactionRules = element.getAttribute("jsaction").split(";") + for jsactionRule in jsactionRules + ruleSplit = jsactionRule.split ":" + isClickable ||= ruleSplit[0] == "click" or (ruleSplit.length == 1 and ruleSplit[0] != "none") + + # Check for tagNames which are natively clickable. + switch tagName + when "a" + isClickable = true + when "textarea" + isClickable ||= not element.disabled and not element.readOnly + when "input" + isClickable ||= not (element.getAttribute("type")?.toLowerCase() == "hidden" or + element.disabled or + (element.readOnly and DomUtils.isSelectable element)) + when "button", "select" + isClickable ||= not element.disabled + + # Elements with tabindex are sometimes useful, but usually not. We can treat them as second class + # citizens when it improves UX, so take special note of them. + tabIndexValue = element.getAttribute("tabindex") + tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue + unless isClickable or isNaN(tabIndex) or tabIndex < 0 + isClickable = onlyHasTabIndex = true + + return null unless isClickable # The element isn't clickable. + clientRect = DomUtils.getVisibleClientRect element + if clientRect == null + null + else + {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex} + # # Returns all clickable elements that are not hidden and are in the current viewport, along with rectangles # at which (parts of) the elements are displayed. @@ -141,65 +207,8 @@ LinkHints = # NOTE(mrmr1993): Our previous method (combined XPath and DOM traversal for jsaction) couldn't provide # this, so it's necessary to check whether elements are clickable in order, as we do below. for element in elements - tagName = element.tagName.toLowerCase() - isClickable = false - onlyHasTabIndex = false - - # Insert area elements that provide click functionality to an img. - if tagName == "img" - mapName = element.getAttribute "usemap" - if mapName - imgClientRects = element.getClientRects() - mapName = mapName.replace(/^#/, "").replace("\"", "\\\"") - map = document.querySelector "map[name=\"#{mapName}\"]" - if map and imgClientRects.length > 0 - areas = map.getElementsByTagName "area" - areasAndRects = DomUtils.getClientRectsForAreas imgClientRects[0], areas - visibleElements.push areasAndRects... - - # Check aria properties to see if the element should be ignored. - if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or - element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"]) - continue # No point continuing the loop; this element should never have a link hint - - # Check for attributes that make an element clickable regardless of its tagName. - if (element.hasAttribute("onclick") or - element.getAttribute("role")?.toLowerCase() in ["button", "link"] or - element.getAttribute("class")?.toLowerCase().indexOf("button") >= 0 or - element.getAttribute("contentEditable")?.toLowerCase() in ["", "contentEditable", "true"]) - isClickable = true - - # Check for jsaction event listeners on the element. - if element.hasAttribute "jsaction" - jsactionRules = element.getAttribute("jsaction").split(";") - for jsactionRule in jsactionRules - ruleSplit = jsactionRule.split ":" - isClickable ||= ruleSplit[0] == "click" or (ruleSplit.length == 1 and ruleSplit[0] != "none") - - # Check for tagNames which are natively clickable. - switch tagName - when "a" - isClickable = true - when "textarea" - isClickable ||= not element.disabled and not element.readOnly - when "input" - isClickable ||= not (element.getAttribute("type")?.toLowerCase() == "hidden" or - element.disabled or - (element.readOnly and DomUtils.isSelectable element)) - when "button", "select" - isClickable ||= not element.disabled - - # Elements with tabindex are sometimes useful, but usually not. We can treat them as second class - # citizens when it improves UX, so take special note of them. - tabIndexValue = element.getAttribute("tabindex") - tabIndex = if tabIndexValue == "" then 0 else parseInt tabIndexValue - unless isClickable or isNaN(tabIndex) or tabIndex < 0 - isClickable = onlyHasTabIndex = true - - continue unless isClickable # If the element isn't clickable, do nothing. - clientRect = DomUtils.getVisibleClientRect element - if clientRect != null - visibleElements.push {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex} + visibleElement = @getVisibleClickable element + visibleElements.push visibleElement if visibleElement? # TODO(mrmr1993): Consider z-index. z-index affects behviour as follows: # * The document has a local stacking context. -- cgit v1.2.3 From 0524bdc3f76279e8930bfe4b1b42d93e0e9bf6e4 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Dec 2014 14:22:53 +0000 Subject: Refactor UIComponent, etc., and demo. - Simplify component API. - Iframe flashes on re-focus. - Probably some other stuff which I've forgotten. --- content_scripts/ui_component.coffee | 79 ++++++++++------------------------ content_scripts/vimium_frontend.coffee | 10 ++--- 2 files changed, 28 insertions(+), 61 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index 10450778..a898d525 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -1,21 +1,19 @@ class UIComponent iframeElement: null iframePort: null - messageEventListeners: [] + showing: true showStyle: "display: block;" hideStyle: "display: none;" - constructor: (iframeUrl, className, showStyle, hideStyle) -> + constructor: (iframeUrl, className, @handleMessage) -> @iframeElement = document.createElement "iframe" @iframeElement.className = className @iframeElement.seamless = "seamless" @iframeElement.src = chrome.runtime.getURL iframeUrl @iframeElement.addEventListener "load", => @openPort() document.documentElement.appendChild @iframeElement - - @setShowStyle showStyle if showStyle? - @setHideStyle hideStyle if showStyle? - @hide() + # Hide iframe, but don't interfere with the focus. + @hide false # Open a port and pass it to the iframe via window.postMessage. openPort: -> @@ -23,63 +21,32 @@ class UIComponent @iframePort = messageChannel.port1 @iframePort.onmessage = (event) => @handleMessage event - # Get iframeMessageSecret so the iframe can determine that our message isn't the page impersonating us. - chrome.storage.local.get "iframeMessageSecret", ({iframeMessageSecret: secret}) => + # Get vimiumSecret so the iframe can determine that our message isn't the page impersonating us. + chrome.storage.local.get "vimiumSecret", ({vimiumSecret: secret}) => @iframeElement.contentWindow.postMessage secret, chrome.runtime.getURL(""), [messageChannel.port2] - postMessage: (message) -> @iframePort.postMessage message - - # Execute each event listener on the current event until we get a non-null falsy return value. - handleMessage: (event) -> - for listener in @messageEventListeners - retVal = listener.call this, event - retVal ?= true - return false unless retVal - true - - addEventListener: (type, listener) -> - if type == "message" - @messageEventListeners.push listener - undefined - - removeEventListener: (type, listener) -> - if type == "message" - @messageEventListeners = @messageEventListeners.filter (f) -> f != listener - undefined - - setHideStyle: (@hideStyle) -> - @hide() if @showing == false - - setShowStyle: (@showStyle) -> - @show() if @showing == true - - setStyles: (@showStyle = @showStyle, @hideStyle = @hideStyle) -> - if @showing - @show() - else - @hide() + postMessage: (message) -> + @iframePort.postMessage message activate: (message) -> @postMessage message if message? - @show() unless @showing + if @showing + # NOTE(smblott) Experimental. Not sure this is a great idea. If the iframe was already showing, then + # the user gets no visual feedback when it is re-focused. So flash its border. + borderWas = @iframeElement.style.border + @iframeElement.style.border = '5px solid yellow' + setTimeout((=> @iframeElement.style.border = borderWas), 200) + else + @iframeElement.setAttribute "style", @showStyle + @showing = true @iframeElement.focus() - show: (message) -> - @postMessage message if message? - @iframeElement.setAttribute "style", @showStyle - @showing = true - - hide: -> - @iframeElement.setAttribute "style", @hideStyle - window.focus() - @showing = false - -handleHideMessage = (event) -> - if event.data == "hide" - @hide() - false - else - true + hide: (focusWindow=true)-> + if @showing + @iframeElement.setAttribute "style", @hideStyle + # TODO(smblott) Is window always the right thing to focus, here? + window.focus() if focusWindow + @showing = false root = exports ? window root.UIComponent = UIComponent diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 3f898b74..8fcb16ed 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -1090,17 +1090,17 @@ Tween = testUIComponent = null testUIComponentSetup = -> - testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent" - testUIComponent.addEventListener "message", (event) -> + testUIComponent = new UIComponent "pages/test_ui_component.html", "testUIComponent", (event) -> if event.data == "hide" @hide() window.focus() - false else - true + # ... And we can get data back! + console.log event.data window.activateTestUIComponent = -> - testUIComponent.activate "version: #{chrome.runtime.getManifest().version}; random number: #{Math.random()}" + chrome.storage.local.get "vimiumSecret", ({vimiumSecret: secret}) -> + testUIComponent.activate [chrome.runtime.getManifest().version, secret, Math.random(), ].join "
    " initializePreDomReady() window.addEventListener("DOMContentLoaded", registerFrame) -- cgit v1.2.3 From 7499675455941251eaa69c93e7c66bfb1c6ae35c Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Dec 2014 17:08:39 +0000 Subject: Clearer handling of @showing in UI component. --- content_scripts/ui_component.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index a898d525..d89f0cc8 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -1,7 +1,7 @@ class UIComponent iframeElement: null iframePort: null - showing: true + showing: null showStyle: "display: block;" hideStyle: "display: none;" @@ -12,7 +12,8 @@ class UIComponent @iframeElement.src = chrome.runtime.getURL iframeUrl @iframeElement.addEventListener "load", => @openPort() document.documentElement.appendChild @iframeElement - # Hide iframe, but don't interfere with the focus. + @showing = true # The iframe is visible now. + # Hide the iframe, but don't interfere with the focus. @hide false # Open a port and pass it to the iframe via window.postMessage. @@ -44,7 +45,6 @@ class UIComponent hide: (focusWindow=true)-> if @showing @iframeElement.setAttribute "style", @hideStyle - # TODO(smblott) Is window always the right thing to focus, here? window.focus() if focusWindow @showing = false -- cgit v1.2.3 From 2dc855abaeeda8ad74c3179f7224858860672338 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Mon, 29 Dec 2014 17:32:03 +0000 Subject: Remove UI component demo. --- content_scripts/vimium_frontend.coffee | 1 + 1 file changed, 1 insertion(+) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 114786e8..2de08c39 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -198,6 +198,7 @@ window.addEventListener "focus", -> # initializeOnDomReady = -> enterInsertModeIfElementIsFocused() if isEnabledForUrl + # Tell the background page we're in the dom ready state. chrome.runtime.connect({ name: "domReady" }) CursorHider.init() -- cgit v1.2.3 From ecdf878c890bcc4ac67d2bb147dcca2e5c20dd27 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 18:01:42 +0000 Subject: Return an array from getVisibleClickable, to restore img map support --- content_scripts/link_hints.coffee | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index ea4be397..70e6a626 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -131,6 +131,7 @@ LinkHints = tagName = element.tagName.toLowerCase() isClickable = false onlyHasTabIndex = false + visibleElements = [] # Insert area elements that provide click functionality to an img. if tagName == "img" @@ -183,12 +184,12 @@ LinkHints = unless isClickable or isNaN(tabIndex) or tabIndex < 0 isClickable = onlyHasTabIndex = true - return null unless isClickable # The element isn't clickable. - clientRect = DomUtils.getVisibleClientRect element - if clientRect == null - null - else - {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex} + if isClickable + clientRect = DomUtils.getVisibleClientRect element + if clientRect != null + visibleElements.push {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex} + + visibleElements # # Returns all clickable elements that are not hidden and are in the current viewport, along with rectangles @@ -208,7 +209,7 @@ LinkHints = # this, so it's necessary to check whether elements are clickable in order, as we do below. for element in elements visibleElement = @getVisibleClickable element - visibleElements.push visibleElement if visibleElement? + visibleElements.push visibleElement... # TODO(mrmr1993): Consider z-index. z-index affects behviour as follows: # * The document has a local stacking context. -- cgit v1.2.3 From 57418a9ad6104c487b67fcfd27ec8503858e5a14 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Mon, 29 Dec 2014 18:55:06 +0000 Subject: Use UIComponent for Vomnibar iframe --- content_scripts/ui_component.coffee | 2 +- content_scripts/vimium_frontend.coffee | 3 +-- content_scripts/vomnibar.coffee | 42 ++++++++++------------------------ 3 files changed, 14 insertions(+), 33 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index d89f0cc8..696cb42c 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -42,7 +42,7 @@ class UIComponent @showing = true @iframeElement.focus() - hide: (focusWindow=true)-> + hide: (focusWindow = true)-> if @showing @iframeElement.setAttribute "style", @hideStyle window.focus() if focusWindow diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 2de08c39..ae275f0c 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -139,8 +139,6 @@ initializePreDomReady = -> getActiveState: -> { enabled: isEnabledForUrl, passKeys: passKeys } setState: setState currentKeyQueue: (request) -> keyQueue = request.keyQueue - vomnibarShow: -> Vomnibar.show() - vomnibarClose: -> Vomnibar.close() chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> # In the options page, we will receive requests from both content and background scripts. ignore those @@ -202,6 +200,7 @@ initializeOnDomReady = -> # Tell the background page we're in the dom ready state. chrome.runtime.connect({ name: "domReady" }) CursorHider.init() + Vomnibar.init() registerFrame = -> # Don't register frameset containers; focusing them is no use. diff --git a/content_scripts/vomnibar.coffee b/content_scripts/vomnibar.coffee index 10f75652..6b82d31c 100644 --- a/content_scripts/vomnibar.coffee +++ b/content_scripts/vomnibar.coffee @@ -2,7 +2,7 @@ # This wraps the vomnibar iframe, which we inject into the page to provide the vomnibar. # Vomnibar = - vomnibarElement: null + vomnibarUI: null activate: -> @open {completer:"omni"} activateInNewTab: -> @open { @@ -35,41 +35,23 @@ Vomnibar = newTab: true } + init: -> + unless @vomnibarUI? + @vomnibarUI = new UIComponent "pages/vomnibar.html", "vomnibarFrame", @handleMessage.bind this + + handleMessage: (event) -> + if event.data == "hide" + @hide() + + # This function opens the vomnibar. It accepts options, a map with the values: # completer - The completer to fetch results from. # query - Optional. Text to prefill the Vomnibar with. # selectFirst - Optional, boolean. Whether to select the first entry. # newTab - Optional, boolean. Whether to open the result in a new tab. - open: (options) -> - unless @vomnibarElement? - @vomnibarElement = document.createElement "iframe" - @vomnibarElement.className = "vomnibarFrame" - @vomnibarElement.seamless = "seamless" - @hide() - - options.frameId = frameId - - optionStrings = [] - for option of options - if typeof options[option] == "boolean" - optionStrings.push option if options[option] - else - optionStrings.push "#{option}=#{escape(options[option])}" - - @vomnibarElement.src = "#{chrome.runtime.getURL "pages/vomnibar.html"}?#{optionStrings.join "&"}" - document.documentElement.appendChild @vomnibarElement - - @vomnibarElement.focus() - - close: -> - @hide() - @vomnibarElement?.remove() - - show: -> - @vomnibarElement?.style.display = "block" + open: (options) -> @vomnibarUI.activate options - hide: -> - @vomnibarElement?.style.display = "none" + hide: -> @vomnibarUI?.hide() root = exports ? window root.Vomnibar = Vomnibar -- cgit v1.2.3 From 774915f3967655ab800cc3c1ac73f0746618d3de Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Tue, 30 Dec 2014 06:07:20 +0000 Subject: Minor changes to vomnibar-in-iframe. From top to bottom on the diff: - The echo handler on the background page is no longer required. - Simplify/refactor vomnibarUI message handler. - Initialise vomnibar query to "" (rather than null) and simplify. - No need to focus parent window when vomnibar closes; that's handled by the iframe framework. Also no need to blur. --- content_scripts/vomnibar.coffee | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vomnibar.coffee b/content_scripts/vomnibar.coffee index 6b82d31c..0d5197a5 100644 --- a/content_scripts/vomnibar.coffee +++ b/content_scripts/vomnibar.coffee @@ -37,12 +37,8 @@ Vomnibar = init: -> unless @vomnibarUI? - @vomnibarUI = new UIComponent "pages/vomnibar.html", "vomnibarFrame", @handleMessage.bind this - - handleMessage: (event) -> - if event.data == "hide" - @hide() - + @vomnibarUI = new UIComponent "pages/vomnibar.html", "vomnibarFrame", => + @vomnibarUI.hide() # This function opens the vomnibar. It accepts options, a map with the values: # completer - The completer to fetch results from. @@ -51,7 +47,5 @@ Vomnibar = # newTab - Optional, boolean. Whether to open the result in a new tab. open: (options) -> @vomnibarUI.activate options - hide: -> @vomnibarUI?.hide() - root = exports ? window root.Vomnibar = Vomnibar -- cgit v1.2.3 From a4a591156f451c1d360530fce6674189f384b452 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Tue, 30 Dec 2014 14:35:19 +0000 Subject: Vomnibar; reinstate test on event.data. Accidentally dropped in 1bc415536fe45ad40ac37282ea54644361ec73f7. --- content_scripts/vomnibar.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/vomnibar.coffee b/content_scripts/vomnibar.coffee index 0d5197a5..6381fd7f 100644 --- a/content_scripts/vomnibar.coffee +++ b/content_scripts/vomnibar.coffee @@ -37,8 +37,8 @@ Vomnibar = init: -> unless @vomnibarUI? - @vomnibarUI = new UIComponent "pages/vomnibar.html", "vomnibarFrame", => - @vomnibarUI.hide() + @vomnibarUI = new UIComponent "pages/vomnibar.html", "vomnibarFrame", (event) => + @vomnibarUI.hide() if event.data == "hide" # This function opens the vomnibar. It accepts options, a map with the values: # completer - The completer to fetch results from. -- cgit v1.2.3 From c3df7699527f88c660e0d61fafdd1ad334236d77 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Tue, 30 Dec 2014 15:09:53 +0000 Subject: Minor changes to link-hint code. --- content_scripts/link_hints.coffee | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 70e6a626..8d476529 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -125,8 +125,10 @@ LinkHints = marker # - # Determine whether the element is visible and clickable. If it is, return the element and the rect - # bounding the element in the viewport. + # Determine whether the element is visible and clickable. If it is, return the element and the rect bounding + # the element in the viewport. There may be more than one part of element which is clickable (for example, + # if it's an image), therefore we return a list of element/rect pairs. + # getVisibleClickable: (element) -> tagName = element.tagName.toLowerCase() isClickable = false @@ -148,7 +150,7 @@ LinkHints = # Check aria properties to see if the element should be ignored. if (element.getAttribute("aria-hidden")?.toLowerCase() in ["", "true"] or element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"]) - return null # This element should never have a link hint. + return [] # This element should never have a link hint. # Check for attributes that make an element clickable regardless of its tagName. if (element.hasAttribute("onclick") or @@ -187,7 +189,7 @@ LinkHints = if isClickable clientRect = DomUtils.getVisibleClientRect element if clientRect != null - visibleElements.push {element: element, rect: clientRect, onlyHasTabIndex: onlyHasTabIndex} + visibleElements.push {element: element, rect: clientRect, secondClassCitizen: onlyHasTabIndex} visibleElements @@ -236,10 +238,11 @@ LinkHints = nonOverlappingElements.push {element: visibleElement.element, rect: rects[0]} else # Every part of the element is covered by some other element, so just insert the whole element's - # rect. Except for elements with tabIndex set; these are often more trouble than they're worth. + # rect. Except for elements with tabIndex set (second class citizens); these are often more trouble + # than they're worth. # TODO(mrmr1993): This is probably the wrong thing to do, but we don't want to stop being able to # click some elements that we could click before. - nonOverlappingElements.push visibleElement unless visibleElement.onlyHasTabIndex + nonOverlappingElements.push visibleElement unless visibleElement.secondClassCitizen nonOverlappingElements -- cgit v1.2.3 From f946d23125a80233799564b57253ace2c44b8994 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 30 Dec 2014 15:20:49 +0000 Subject: Reinstate UIComponent.show --- content_scripts/ui_component.coffee | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index 696cb42c..8b229725 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -38,15 +38,18 @@ class UIComponent @iframeElement.style.border = '5px solid yellow' setTimeout((=> @iframeElement.style.border = borderWas), 200) else - @iframeElement.setAttribute "style", @showStyle - @showing = true + @show() @iframeElement.focus() + show: (message) -> + @postMessage message if message? + @iframeElement.setAttribute "style", @showStyle + @showing = true + hide: (focusWindow = true)-> - if @showing - @iframeElement.setAttribute "style", @hideStyle - window.focus() if focusWindow - @showing = false + @iframeElement.setAttribute "style", @hideStyle + window.focus() if focusWindow + @showing = false root = exports ? window root.UIComponent = UIComponent -- cgit v1.2.3 From 7e6b2c5a8439cf8c1e861e3f596915a75ecb9644 Mon Sep 17 00:00:00 2001 From: mrmr1993 Date: Tue, 30 Dec 2014 15:31:30 +0000 Subject: Use classes and a stylesheet for UIComponen --- content_scripts/ui_component.coffee | 13 ++++++------- content_scripts/vimium.css | 13 +++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee index 8b229725..c4ed3bf6 100644 --- a/content_scripts/ui_component.coffee +++ b/content_scripts/ui_component.coffee @@ -2,8 +2,6 @@ class UIComponent iframeElement: null iframePort: null showing: null - showStyle: "display: block;" - hideStyle: "display: none;" constructor: (iframeUrl, className, @handleMessage) -> @iframeElement = document.createElement "iframe" @@ -34,20 +32,21 @@ class UIComponent if @showing # NOTE(smblott) Experimental. Not sure this is a great idea. If the iframe was already showing, then # the user gets no visual feedback when it is re-focused. So flash its border. - borderWas = @iframeElement.style.border - @iframeElement.style.border = '5px solid yellow' - setTimeout((=> @iframeElement.style.border = borderWas), 200) + @iframeElement.classList.add "vimiumUIComponentReactivated" + setTimeout((=> @iframeElement.classList.remove "vimiumUIComponentReactivated"), 200) else @show() @iframeElement.focus() show: (message) -> @postMessage message if message? - @iframeElement.setAttribute "style", @showStyle + @iframeElement.classList.remove "vimiumUIComponentHidden" + @iframeElement.classList.add "vimiumUIComponentShowing" @showing = true hide: (focusWindow = true)-> - @iframeElement.setAttribute "style", @hideStyle + @iframeElement.classList.remove "vimiumUIComponentShowing" + @iframeElement.classList.add "vimiumUIComponentHidden" window.focus() if focusWindow @showing = false diff --git a/content_scripts/vimium.css b/content_scripts/vimium.css index f582824a..ec1a09e6 100644 --- a/content_scripts/vimium.css +++ b/content_scripts/vimium.css @@ -302,3 +302,16 @@ div#vimiumFlash { position: absolute; z-index: 2147483648; } + +/* UIComponent CSS */ +iframe.vimiumUIComponentHidden { + display: none; +} + +iframe.vimiumUIComponentVisible { + display: block; +} + +iframe.vimiumUIComponentReactivated { + border: 5px solid yellow; +} -- cgit v1.2.3 From 8f998f5b4cd1d8600b62ae7faac8afb91c4d2dab Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Tue, 30 Dec 2014 15:59:26 +0000 Subject: Update comment in getVisibleClickable. --- content_scripts/link_hints.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 8d476529..9f21d109 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -125,9 +125,9 @@ LinkHints = marker # - # Determine whether the element is visible and clickable. If it is, return the element and the rect bounding - # the element in the viewport. There may be more than one part of element which is clickable (for example, - # if it's an image), therefore we return a list of element/rect pairs. + # Determine whether the element is visible and clickable. If it is, find the rect bounding the element in + # the viewport. There may be more than one part of element which is clickable (for example, if it's an + # image), therefore we always return a array of element/rect pairs (which may also be a singleton or empty). # getVisibleClickable: (element) -> tagName = element.tagName.toLowerCase() -- cgit v1.2.3 From fc2201b996e47ca06090e10e4ebfcd9f4b345fde Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 4 Jan 2015 06:43:06 +0000 Subject: Catch undefined reference to handledKeydownEvents. --- content_scripts/vimium_frontend.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index ae275f0c..351a2690 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -463,7 +463,7 @@ onKeydown = (event) -> event.srcElement.blur() exitInsertMode() DomUtils.suppressEvent event - handledKeydownEvents.push event + KeydownEvents.push event else if (findMode) if (KeyboardUtils.isEscape(event)) -- cgit v1.2.3