diff options
Diffstat (limited to 'linkHints.js')
| -rw-r--r-- | linkHints.js | 740 |
1 files changed, 371 insertions, 369 deletions
diff --git a/linkHints.js b/linkHints.js index 020f8fff..2b21f8c5 100644 --- a/linkHints.js +++ b/linkHints.js @@ -7,392 +7,394 @@ * The CSS which is used on the link hints is also a configurable option. */ -var hintMarkers = []; -var hintMarkerContainingDiv = null; -// The characters that were typed in while in "link hints" mode. -var hintKeystrokeQueue = []; -var linkTextKeystrokeQueue = []; -var linkHintsModeActivated = false; -var shouldOpenLinkHintInNewTab = false; -var shouldOpenLinkHintWithQueue = false; -// Whether link hint's "open in current/new tab" setting is currently toggled -var openLinkModeToggle = false; -// Whether we have added to the page the CSS needed to display link hints. -var linkHintsCssAdded = false; - -/* - * Generate an XPath describing what a clickable element is. - * The final expression will be something like "//button | //xhtml:button | ..." - */ -var clickableElementsXPath = (function() { - var clickableElements = ["a", "textarea", "button", "select", "input[not(@type='hidden')]"]; - var xpath = []; - for (var i in clickableElements) - xpath.push("//" + clickableElements[i], "//xhtml:" + clickableElements[i]); - xpath.push("//*[@onclick]"); - return xpath.join(" | ") -})(); - -function isNarrowMode() { - return settings.narrowLinkHints == "true"; -} - -// We need this as a top-level function because our command system doesn't yet support arguments. -function activateLinkHintsModeToOpenInNewTab() { activateLinkHintsMode(true, false); } - -function activateLinkHintsModeWithQueue() { activateLinkHintsMode(true, true); } - -function activateLinkHintsMode(openInNewTab, withQueue) { - if (!linkHintsCssAdded) - addCssToPage(linkHintCss); // linkHintCss is declared by vimiumFrontend.js - linkHintCssAdded = true; - linkHintsModeActivated = true; - setOpenLinkMode(openInNewTab, withQueue); - buildLinkHints(); - document.addEventListener("keydown", onKeyDownInLinkHintsMode, true); - document.addEventListener("keyup", onKeyUpInLinkHintsMode, true); -} - -function setOpenLinkMode(openInNewTab, withQueue) { - shouldOpenLinkHintInNewTab = openInNewTab; - shouldOpenLinkHintWithQueue = withQueue; - if (shouldOpenLinkHintWithQueue) { - HUD.show("Open multiple links in a new tab"); - } else { - if (shouldOpenLinkHintInNewTab) - HUD.show("Open link in new tab"); - else - HUD.show("Open link in current tab"); - } -} - -/* - * Builds and displays link hints for every visible clickable item on the page. - */ -function buildLinkHints() { - var visibleElements = getVisibleClickableElements(); - - // Initialize the number used to generate the character hints to be as many digits as we need to - // highlight all the links on the page; we don't want some link hints to have more chars than others. - var digitsNeeded = Math.ceil(logXOfBase(visibleElements.length, settings.linkHintCharacters.length)); - var linkHintNumber = 0; - for (var i = 0; i < visibleElements.length; i++) { - hintMarkers.push(createMarkerFor(visibleElements[i], linkHintNumber, digitsNeeded)); - linkHintNumber++; - } - // Note(philc): Append these markers as top level children instead of as child nodes to the link itself, - // because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat - // that if you scroll the page and the link has position=fixed, the marker will not stay fixed. - // Also note that adding these nodes to document.body all at once is significantly faster than one-by-one. - hintMarkerContainingDiv = document.createElement("div"); - hintMarkerContainingDiv.className = "internalVimiumHintMarker"; - for (var i = 0; i < hintMarkers.length; i++) - hintMarkerContainingDiv.appendChild(hintMarkers[i]); - document.body.appendChild(hintMarkerContainingDiv); -} - -function logXOfBase(x, base) { return Math.log(x) / Math.log(base); } - -/* - * 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. - */ -function getVisibleClickableElements() { - var resultSet = document.evaluate(clickableElementsXPath, document.body, - function (namespace) { - return namespace == "xhtml" ? "http://www.w3.org/1999/xhtml" : null; - }, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - - - var visibleElements = []; - - // Find all visible clickable elements. - for (var i = 0; i < resultSet.snapshotLength; i++) { - var element = resultSet.snapshotItem(i); - var clientRect = element.getClientRects()[0]; - - if (isVisible(element, clientRect)) - visibleElements.push({element: element, rect: clientRect}); - - // If the link has zero dimensions, it may be wrapping visible - // but floated elements. Check for this. - if (clientRect && (clientRect.width == 0 || clientRect.height == 0)) { - for (var j = 0; j < element.children.length; j++) { - if (window.getComputedStyle(element.children[j], null).getPropertyValue('float') != 'none') { - var childClientRect = element.children[j].getClientRects()[0]; - if (isVisible(element.children[j], childClientRect)) { - visibleElements.push({element: element.children[j], rect: childClientRect}); - break; +var linkHints = { + hintMarkers: [], + hintMarkerContainingDiv: null, + // The characters that were typed in while in "link hints" mode. + hintKeystrokeQueue: [], + linkTextKeystrokeQueue: [], + linkHintsModeActivated: false, + shouldOpenLinkHintInNewTab: false, + shouldOpenLinkHintWithQueue: false, + // Whether link hint's "open in current/new tab" setting is currently toggled + openLinkModeToggle: false, + // Whether we have added to the page the CSS needed to display link hints. + linkHintsCssAdded: false, + + /* + * Generate an XPath describing what a clickable element is. + * The final expression will be something like "//button | //xhtml:button | ..." + */ + clickableElementsXPath: (function() { + var clickableElements = ["a", "textarea", "button", "select", "input[not(@type='hidden')]"]; + var xpath = []; + for (var i in clickableElements) + xpath.push("//" + clickableElements[i], "//xhtml:" + clickableElements[i]); + xpath.push("//*[@onclick]"); + return xpath.join(" | ") + })(), + + isNarrowMode: function () { + return settings.narrowLinkHints == "true"; + }, + + // We need this as a top-level function because our command system doesn't yet support arguments. + activateLinkHintsModeToOpenInNewTab: function() { linkHints.activateLinkHintsMode(true, false); }, + + activateLinkHintsModeWithQueue: function() { linkHints.activateLinkHintsMode(true, true); }, + + activateLinkHintsMode: function (openInNewTab, withQueue) { + if (!linkHints.linkHintsCssAdded) + addCssToPage(linkHintCss); // linkHintCss is declared by vimiumFrontend.js + linkHints.linkHintCssAdded = true; + linkHints.linkHintsModeActivated = true; + linkHints.setOpenLinkMode(openInNewTab, withQueue); + linkHints.buildLinkHints(); + document.addEventListener("keydown", linkHints.onKeyDownInLinkHintsMode, true); + document.addEventListener("keyup", linkHints.onKeyUpInLinkHintsMode, true); + }, + + setOpenLinkMode: function(openInNewTab, withQueue) { + linkHints.shouldOpenLinkHintInNewTab = openInNewTab; + linkHints.shouldOpenLinkHintWithQueue = withQueue; + if (linkHints.shouldOpenLinkHintWithQueue) { + HUD.show("Open multiple links in a new tab"); + } else { + if (linkHints.shouldOpenLinkHintInNewTab) + HUD.show("Open link in new tab"); + else + HUD.show("Open link in current tab"); + } + }, + + /* + * Builds and displays link hints for every visible clickable item on the page. + */ + buildLinkHints: function() { + var visibleElements = linkHints.getVisibleClickableElements(); + + // Initialize the number used to generate the character hints to be as many digits as we need to + // highlight all the links on the page; we don't want some link hints to have more chars than others. + var digitsNeeded = Math.ceil(linkHints.logXOfBase(visibleElements.length, settings.linkHintCharacters.length)); + var linkHintNumber = 0; + for (var i = 0; i < visibleElements.length; i++) { + linkHints.hintMarkers.push(linkHints.createMarkerFor(visibleElements[i], linkHintNumber, digitsNeeded)); + linkHintNumber++; + } + // Note(philc): Append these markers as top level children instead of as child nodes to the link itself, + // because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat + // that if you scroll the page and the link has position=fixed, the marker will not stay fixed. + // Also note that adding these nodes to document.body all at once is significantly faster than one-by-one. + linkHints.hintMarkerContainingDiv = document.createElement("div"); + linkHints.hintMarkerContainingDiv.className = "internalVimiumHintMarker"; + for (var i = 0; i < linkHints.hintMarkers.length; i++) + linkHints.hintMarkerContainingDiv.appendChild(linkHints.hintMarkers[i]); + document.body.appendChild(linkHints.hintMarkerContainingDiv); + }, + + logXOfBase: function(x, base) { return Math.log(x) / Math.log(base); }, + + /* + * 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. + */ + getVisibleClickableElements: function() { + var resultSet = document.evaluate(linkHints.clickableElementsXPath, document.body, + function (namespace) { + return namespace == "xhtml" ? "http://www.w3.org/1999/xhtml" : null; + }, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + + var visibleElements = []; + + // Find all visible clickable elements. + for (var i = 0; i < resultSet.snapshotLength; i++) { + var element = resultSet.snapshotItem(i); + var clientRect = element.getClientRects()[0]; + + if (linkHints.isVisible(element, clientRect)) + visibleElements.push({element: element, rect: clientRect}); + + // If the link has zero dimensions, it may be wrapping visible + // but floated elements. Check for this. + if (clientRect && (clientRect.width == 0 || clientRect.height == 0)) { + for (var j = 0; j < element.children.length; j++) { + if (window.getComputedStyle(element.children[j], null).getPropertyValue('float') != 'none') { + var childClientRect = element.children[j].getClientRects()[0]; + if (linkHints.isVisible(element.children[j], childClientRect)) { + visibleElements.push({element: element.children[j], rect: childClientRect}); + break; + } } } } } - } - return visibleElements; -} + return visibleElements; + }, + + /* + * Returns true if element is visible. + */ + isVisible: function(element, clientRect) { + // Exclude links which have just a few pixels on screen, because the link hints won't show for them anyway. + var zoomFactor = currentZoomLevel / 100.0; + if (!clientRect || clientRect.top < 0 || clientRect.top * zoomFactor >= window.innerHeight - 4 || + clientRect.left < 0 || clientRect.left * zoomFactor >= window.innerWidth - 4) + return false; + + if (clientRect.width < 3 || clientRect.height < 3) + return false; + + // eliminate invisible elements (see test_harnesses/visibility_test.html) + var computedStyle = window.getComputedStyle(element, null); + if (computedStyle.getPropertyValue('visibility') != 'visible' || + computedStyle.getPropertyValue('display') == 'none') + return false; + + return true; + }, + + onKeyDownInLinkHintsMode: function(event) { + console.log("Key Down"); + if (event.keyCode == keyCodes.shiftKey && !linkHints.openLinkModeToggle) { + // Toggle whether to open link in a new or current tab. + linkHints.setOpenLinkMode(!linkHints.shouldOpenLinkHintInNewTab, linkHints.shouldOpenLinkHintWithQueue); + linkHints.openLinkModeToggle = true; + } -/* - * Returns true if element is visible. - */ -function isVisible(element, clientRect) { - // Exclude links which have just a few pixels on screen, because the link hints won't show for them anyway. - var zoomFactor = currentZoomLevel / 100.0; - if (!clientRect || clientRect.top < 0 || clientRect.top * zoomFactor >= window.innerHeight - 4 || - clientRect.left < 0 || clientRect.left * zoomFactor >= window.innerWidth - 4) - return false; - - if (clientRect.width < 3 || clientRect.height < 3) - return false; - - // eliminate invisible elements (see test_harnesses/visibility_test.html) - var computedStyle = window.getComputedStyle(element, null); - if (computedStyle.getPropertyValue('visibility') != 'visible' || - computedStyle.getPropertyValue('display') == 'none') - return false; - - return true; -} - -function onKeyDownInLinkHintsMode(event) { - console.log("Key Down"); - if (event.keyCode == keyCodes.shiftKey && !openLinkModeToggle) { - // Toggle whether to open link in a new or current tab. - setOpenLinkMode(!shouldOpenLinkHintInNewTab, shouldOpenLinkHintWithQueue); - openLinkModeToggle = true; - } - - var keyChar = getKeyChar(event); - if (!keyChar) - return; - - // TODO(philc): Ignore keys that have modifiers. - if (isEscape(event)) { - deactivateLinkHintsMode(); - } else { - if (isNarrowMode()) { - if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { - if (linkTextKeystrokeQueue.length == 0 && hintKeystrokeQueue.length == 0) { - deactivateLinkHintsMode(); + var keyChar = getKeyChar(event); + if (!keyChar) + return; + + // TODO(philc): Ignore keys that have modifiers. + if (isEscape(event)) { + linkHints.deactivateLinkHintsMode(); + } else { + if (linkHints.isNarrowMode()) { + if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { + if (linkHints.linkTextKeystrokeQueue.length == 0 && linkHints.hintKeystrokeQueue.length == 0) { + linkHints.deactivateLinkHintsMode(); + } else { + // backspace clears hint key queue first, then acts on link text key queue + if (linkHints.hintKeystrokeQueue.pop() === undefined) + linkHints.linkTextKeystrokeQueue.pop(); + linkHints.updateLinkHints(); + } + } else if (/[0-9]/.test(keyChar)) { + linkHints.hintKeystrokeQueue.push(keyChar); + linkHints.updateLinkHints(); } else { - // backspace clears hint key queue first, then acts on link text key queue - if (hintKeystrokeQueue.pop() === undefined) - linkTextKeystrokeQueue.pop(); - updateLinkHints(); + linkHints.linkTextKeystrokeQueue.push(keyChar); + linkHints.updateLinkHints(); } - } else if (/[0-9]/.test(keyChar)) { - hintKeystrokeQueue.push(keyChar); - updateLinkHints(); } else { - linkTextKeystrokeQueue.push(keyChar); - updateLinkHints(); - } - } else { - if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { - if (hintKeystrokeQueue.length == 0) { - deactivateLinkHintsMode(); - } else { - hintKeystrokeQueue.pop(); - updateLinkHints(); + if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) { + if (linkHints.hintKeystrokeQueue.length == 0) { + linkHints.deactivateLinkHintsMode(); + } else { + linkHints.hintKeystrokeQueue.pop(); + linkHints.updateLinkHints(); + } + } else if (settings.linkHintCharacters.indexOf(keyChar) >= 0) { + linkHints.hintKeystrokeQueue.push(keyChar); + linkHints.updateLinkHints(); } - } else if (settings.linkHintCharacters.indexOf(keyChar) >= 0) { - hintKeystrokeQueue.push(keyChar); - updateLinkHints(); } } - } - - event.stopPropagation(); - event.preventDefault(); -} - -function onKeyUpInLinkHintsMode(event) { - if (event.keyCode == keyCodes.shiftKey && openLinkModeToggle) { - // Revert toggle on whether to open link in new or current tab. - setOpenLinkMode(!shouldOpenLinkHintInNewTab, shouldOpenLinkHintWithQueue); - openLinkModeToggle = false; - } - event.stopPropagation(); - event.preventDefault(); -} -/* - * Updates the visibility of link hints on screen based on the keystrokes typed thus far. If only one - * link hint remains, click on that link and exit link hints mode. - */ -function updateLinkHints() { - var matchString = hintKeystrokeQueue.join(""); - var linksMatched = highlightLinkMatches(matchString); - if (linksMatched.length == 0) - deactivateLinkHintsMode(); - else if (linksMatched.length == 1) { - var matchedLink = linksMatched[0]; - if (isSelectable(matchedLink)) { - matchedLink.focus(); - // When focusing a textbox, put the selection caret at the end of the textbox's contents. - matchedLink.setSelectionRange(matchedLink.value.length, matchedLink.value.length); - deactivateLinkHintsMode(); - } else { - // When we're opening the link in the current tab, don't navigate to the selected link immediately; - // we want to give the user some feedback depicting which link they've selected by focusing it. - if (shouldOpenLinkHintWithQueue) { - simulateClick(matchedLink); - resetLinkHintsMode(); - } else if (shouldOpenLinkHintInNewTab) { - simulateClick(matchedLink); + event.stopPropagation(); + event.preventDefault(); + }, + + onKeyUpInLinkHintsMode: function(event) { + if (event.keyCode == keyCodes.shiftKey && linkHints.openLinkModeToggle) { + // Revert toggle on whether to open link in new or current tab. + linkHints.setOpenLinkMode(!linkHints.shouldOpenLinkHintInNewTab, linkHints.shouldOpenLinkHintWithQueue); + linkHints.openLinkModeToggle = false; + } + event.stopPropagation(); + event.preventDefault(); + }, + + /* + * Updates the visibility of link hints on screen based on the keystrokes typed thus far. If only one + * link hint remains, click on that link and exit link hints mode. + */ + updateLinkHints: function() { + var matchString = linkHints.hintKeystrokeQueue.join(""); + var linksMatched = linkHints.highlightLinkMatches(matchString); + if (linksMatched.length == 0) + linkHints.deactivateLinkHintsMode(); + else if (linksMatched.length == 1) { + var matchedLink = linksMatched[0]; + if (linkHints.isSelectable(matchedLink)) { matchedLink.focus(); - deactivateLinkHintsMode(); + // When focusing a textbox, put the selection caret at the end of the textbox's contents. + matchedLink.setSelectionRange(matchedLink.value.length, matchedLink.value.length); + linkHints.deactivateLinkHintsMode(); } else { - setTimeout(function() { simulateClick(matchedLink); }, 400); - matchedLink.focus(); - deactivateLinkHintsMode(); + // When we're opening the link in the current tab, don't navigate to the selected link immediately; + // we want to give the user some feedback depicting which link they've selected by focusing it. + if (linkHints.shouldOpenLinkHintWithQueue) { + linkHints.simulateClick(matchedLink); + linkHints.resetLinkHintsMode(); + } else if (linkHints.shouldOpenLinkHintInNewTab) { + linkHints.simulateClick(matchedLink); + matchedLink.focus(); + linkHints.deactivateLinkHintsMode(); + } else { + setTimeout(function() { linkHints.simulateClick(matchedLink); }, 400); + matchedLink.focus(); + linkHints.deactivateLinkHintsMode(); + } } } - } -} - -/* - * Selectable means the element has a text caret; this is not the same as "focusable". - */ -function isSelectable(element) { - var selectableTypes = ["search", "text", "password"]; - return (element.tagName == "INPUT" && selectableTypes.indexOf(element.type) >= 0) || - element.tagName == "TEXTAREA"; -} + }, + + /* + * Selectable means the element has a text caret; this is not the same as "focusable". + */ + isSelectable: function(element) { + var selectableTypes = ["search", "text", "password"]; + return (element.tagName == "INPUT" && selectableTypes.indexOf(element.type) >= 0) || + element.tagName == "TEXTAREA"; + }, + + /* + * Hides link hints which do not match the given search string. To allow the backspace key to work, this + * will also show link hints which do match but were previously hidden. + */ + highlightLinkMatches: function(searchString) { + var linksMatched = []; + var linkSearchString = linkHints.linkTextKeystrokeQueue.join(""); + var narrowMode = linkHints.isNarrowMode(); + var hasSearchString = searchString.length != 0; + var hasLinkSearchString = linkSearchString.length != 0; + var matchedCount = 0; + + for (var i = 0; i < linkHints.hintMarkers.length; i++) { + var linkMarker = linkHints.hintMarkers[i]; + var matchedLink = linkMarker.getAttribute("linkText").toLowerCase().indexOf(linkSearchString.toLowerCase()) >= 0; + var matchedHintStart = linkMarker.getAttribute("hintString").indexOf(searchString) == 0; + + var shouldRemoveMatch; + if (narrowMode) { + shouldRemoveMatch = + (!matchedLink && !matchedHintStart) || + (!matchedLink && hasLinkSearchString) || + (!matchedHintStart && hasSearchString) + } else { + shouldRemoveMatch = !matchedHintStart; + } -/* - * Hides link hints which do not match the given search string. To allow the backspace key to work, this - * will also show link hints which do match but were previously hidden. - */ -function highlightLinkMatches(searchString) { - var linksMatched = []; - var linkSearchString = linkTextKeystrokeQueue.join(""); - var narrowMode = isNarrowMode(); - var hasSearchString = searchString.length != 0; - var hasLinkSearchString = linkSearchString.length != 0; - var matchedCount = 0; - - for (var i = 0; i < hintMarkers.length; i++) { - var linkMarker = hintMarkers[i]; - var matchedLink = linkMarker.getAttribute("linkText").toLowerCase().indexOf(linkSearchString.toLowerCase()) >= 0; - var matchedHintStart = linkMarker.getAttribute("hintString").indexOf(searchString) == 0; - - var shouldRemoveMatch; - if (narrowMode) { - shouldRemoveMatch = - (!matchedLink && !matchedHintStart) || - (!matchedLink && hasLinkSearchString) || - (!matchedHintStart && hasSearchString) - } else { - shouldRemoveMatch = !matchedHintStart; - } + if (matchedHintStart) { + for (var j = 0; j < linkMarker.childNodes.length; j++) + linkMarker.childNodes[j].className = (j >= searchString.length) ? "" : "matchingCharacter"; + } - if (matchedHintStart) { - for (var j = 0; j < linkMarker.childNodes.length; j++) - linkMarker.childNodes[j].className = (j >= searchString.length) ? "" : "matchingCharacter"; - } + if (shouldRemoveMatch) { + linkMarker.style.display = "none"; + } else { + if (linkMarker.style.display == "none") + linkMarker.style.display = ""; + var newHint = matchedCount.toString(); + linkMarker.innerHTML = linkHints.spanWrap(newHint); + linkMarker.setAttribute("hintString", newHint); + linksMatched.push(linkMarker.clickableItem); + matchedCount++; + } - if (shouldRemoveMatch) { - linkMarker.style.display = "none"; - } else { - if (linkMarker.style.display == "none") - linkMarker.style.display = ""; - var newHint = matchedCount.toString(); - linkMarker.innerHTML = spanWrap(newHint); - linkMarker.setAttribute("hintString", newHint); - linksMatched.push(linkMarker.clickableItem); - matchedCount++; } - - } - return linksMatched; -} - -/* - * Converts a number like "8" into a hint string like "JK". This is used to sequentially generate all of - * the hint text. The hint string will be "padded with zeroes" to ensure its length is equal to numHintDigits. - */ -function numberToHintString(number, numHintDigits) { - var base = settings.linkHintCharacters.length; - var hintString = []; - var remainder = 0; - do { - remainder = number % base; - hintString.unshift(settings.linkHintCharacters[remainder]); - number -= remainder; - number /= Math.floor(base); - } while (number > 0); - - // Pad the hint string we're returning so that it matches numHintDigits. - var hintStringLength = hintString.length; - for (var i = 0; i < numHintDigits - hintStringLength; i++) - hintString.unshift(settings.linkHintCharacters[0]); - return hintString.join(""); -} - -function simulateClick(link) { - var event = document.createEvent("MouseEvents"); - // When "clicking" on a link, dispatch the event with the appropriate meta key (CMD on Mac, CTRL on windows) - // to open it in a new tab if necessary. - var metaKey = (platform == "Mac" && shouldOpenLinkHintInNewTab); - var ctrlKey = (platform != "Mac" && shouldOpenLinkHintInNewTab); - event.initMouseEvent("click", true, true, window, 1, 0, 0, 0, 0, ctrlKey, false, false, metaKey, 0, null); - - // Debugging note: Firefox will not execute the link's default action if we dispatch this click event, - // but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately - link.dispatchEvent(event); -} - -function deactivateLinkHintsMode() { - if (hintMarkerContainingDiv) - hintMarkerContainingDiv.parentNode.removeChild(hintMarkerContainingDiv); - hintMarkerContainingDiv = null; - hintMarkers = []; - hintKeystrokeQueue = []; - linkTextKeystrokeQueue = []; - document.removeEventListener("keydown", onKeyDownInLinkHintsMode, true); - document.removeEventListener("keyup", onKeyUpInLinkHintsMode, true); - linkHintsModeActivated = false; - HUD.hide(); -} - -function resetLinkHintsMode() { - deactivateLinkHintsMode(); - activateLinkHintsModeWithQueue(); -} - -/* - * Creates a link marker for the given link. - */ -function createMarkerFor(link, linkHintNumber, linkHintDigits) { - var hintString = isNarrowMode() ? - linkHintNumber.toString() : numberToHintString(linkHintNumber, linkHintDigits); - var linkText = link.element.innerHTML.toLowerCase(); - if (linkText == undefined) - linkText = ""; - var marker = document.createElement("div"); - marker.className = "internalVimiumHintMarker vimiumHintMarker"; - marker.innerHTML = spanWrap(hintString); - marker.setAttribute("hintString", hintString); - marker.setAttribute("linkText", linkText); - - // Note: this call will be expensive if we modify the DOM in between calls. - var clientRect = link.rect; - // The coordinates given by the window do not have the zoom factor included since the zoom is set only on - // the document node. - var zoomFactor = currentZoomLevel / 100.0; - marker.style.left = clientRect.left + window.scrollX / zoomFactor + "px"; - marker.style.top = clientRect.top + window.scrollY / zoomFactor + "px"; - - marker.clickableItem = link.element; - return marker; -} - -// Make each hint character a span, so that we can highlight the typed characters as you type them. -function spanWrap(hintString) { - var innerHTML = []; - for (var i = 0; i < hintString.length; i++) - innerHTML.push("<span>" + hintString[i].toUpperCase() + "</span>"); - return innerHTML.join(""); -} + return linksMatched; + }, + + /* + * Converts a number like "8" into a hint string like "JK". This is used to sequentially generate all of + * the hint text. The hint string will be "padded with zeroes" to ensure its length is equal to numHintDigits. + */ + numberToHintString: function(number, numHintDigits) { + var base = settings.linkHintCharacters.length; + var hintString = []; + var remainder = 0; + do { + remainder = number % base; + hintString.unshift(settings.linkHintCharacters[remainder]); + number -= remainder; + number /= Math.floor(base); + } while (number > 0); + + // Pad the hint string we're returning so that it matches numHintDigits. + var hintStringLength = hintString.length; + for (var i = 0; i < numHintDigits - hintStringLength; i++) + hintString.unshift(settings.linkHintCharacters[0]); + return hintString.join(""); + }, + + simulateClick: function(link) { + var event = document.createEvent("MouseEvents"); + // When "clicking" on a link, dispatch the event with the appropriate meta key (CMD on Mac, CTRL on windows) + // to open it in a new tab if necessary. + var metaKey = (platform == "Mac" && linkHints.shouldOpenLinkHintInNewTab); + var ctrlKey = (platform != "Mac" && linkHints.shouldOpenLinkHintInNewTab); + event.initMouseEvent("click", true, true, window, 1, 0, 0, 0, 0, ctrlKey, false, false, metaKey, 0, null); + + // Debugging note: Firefox will not execute the link's default action if we dispatch this click event, + // but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately + link.dispatchEvent(event); + }, + + deactivateLinkHintsMode: function() { + if (linkHints.hintMarkerContainingDiv) + linkHints.hintMarkerContainingDiv.parentNode.removeChild(linkHints.hintMarkerContainingDiv); + linkHints.hintMarkerContainingDiv = null; + linkHints.hintMarkers = []; + linkHints.hintKeystrokeQueue = []; + linkHints.linkTextKeystrokeQueue = []; + document.removeEventListener("keydown", linkHints.onKeyDownInLinkHintsMode, true); + document.removeEventListener("keyup", linkHints.onKeyUpInLinkHintsMode, true); + linkHints.linkHintsModeActivated = false; + HUD.hide(); + }, + + resetLinkHintsMode: function() { + linkHints.deactivateLinkHintsMode(); + linkHints.activateLinkHintsModeWithQueue(); + }, + + /* + * Creates a link marker for the given link. + */ + createMarkerFor: function(link, linkHintNumber, linkHintDigits) { + var hintString = linkHints.isNarrowMode() ? + linkHintNumber.toString() : linkHints.numberToHintString(linkHintNumber, linkHintDigits); + var linkText = link.element.innerHTML.toLowerCase(); + if (linkText == undefined) + linkText = ""; + var marker = document.createElement("div"); + marker.className = "internalVimiumHintMarker vimiumHintMarker"; + marker.innerHTML = linkHints.spanWrap(hintString); + marker.setAttribute("hintString", hintString); + marker.setAttribute("linkText", linkText); + + // Note: this call will be expensive if we modify the DOM in between calls. + var clientRect = link.rect; + // The coordinates given by the window do not have the zoom factor included since the zoom is set only on + // the document node. + var zoomFactor = currentZoomLevel / 100.0; + marker.style.left = clientRect.left + window.scrollX / zoomFactor + "px"; + marker.style.top = clientRect.top + window.scrollY / zoomFactor + "px"; + + marker.clickableItem = link.element; + return marker; + }, + + // Make each hint character a span, so that we can highlight the typed characters as you type them. + spanWrap: function(hintString) { + var innerHTML = []; + for (var i = 0; i < hintString.length; i++) + innerHTML.push("<span>" + hintString[i].toUpperCase() + "</span>"); + return innerHTML.join(""); + }, +}; |
