aboutsummaryrefslogtreecommitdiffstats
path: root/vimiumFrontend.js
diff options
context:
space:
mode:
Diffstat (limited to 'vimiumFrontend.js')
-rw-r--r--vimiumFrontend.js444
1 files changed, 238 insertions, 206 deletions
diff --git a/vimiumFrontend.js b/vimiumFrontend.js
index 0e8bdbb5..a9dc1d37 100644
--- a/vimiumFrontend.js
+++ b/vimiumFrontend.js
@@ -1,41 +1,69 @@
/*
* This content script takes input from its webpage and executes commands locally on behalf of the background
- * page. It must be run prior to domReady so that we perform some operations very early, like setting
- * the page's zoom level. We tell the background page that we're in domReady and ready to accept normal
- * commands by connectiong to a port named "domReady".
+ * page. It must be run prior to domReady so that we perform some operations very early. We tell the
+ * background page that we're in domReady and ready to accept normal commands by connectiong to a port named
+ * "domReady".
*/
-var settings = {};
-var settingsToLoad = ["scrollStepSize", "linkHintCharacters", "previousPatterns", "nextPatterns"];
-
var getCurrentUrlHandlers = []; // function(url)
-var insertMode = false;
+var insertModeLock = null;
var findMode = false;
var findModeQuery = "";
var findModeQueryHasResults = false;
var isShowingHelpDialog = false;
+var handlerStack = [];
var keyPort;
var settingPort;
-var saveZoomLevelPort;
// Users can disable Vimium on URL patterns via the settings page.
var isEnabledForUrl = true;
// The user's operating system.
var currentCompletionKeys;
+var validFirstKeys;
var linkHintCss;
-// TODO(philc): This should be pulled from the extension's storage when the page loads.
-var currentZoomLevel = 100;
-
// The types in <input type="..."> that we consider for focusInput command. Right now this is recalculated in
// each content script. Alternatively we could calculate it once in the background page and use a request to
// fetch it each time.
//
// Should we include the HTML5 date pickers here?
-var textInputTypes = ["text", "search", "email", "url", "number"];
+
// The corresponding XPath for such elements.
-var textInputXPath = '//input[' +
- textInputTypes.map(function (type) { return '@type="' + type + '"'; }).join(" or ") +
- ' or not(@type)]';
+var textInputXPath = (function() {
+ var textInputTypes = ["text", "search", "email", "url", "number"];
+ var inputElements = ["input[" +
+ textInputTypes.map(function (type) { return '@type="' + type + '"'; }).join(" or ") + "or not(@type)]",
+ "textarea"];
+ return utils.makeXPath(inputElements);
+})();
+
+var settings = {
+ values: {},
+ loadedValues: 0,
+ valuesToLoad: ["scrollStepSize", "linkHintCharacters", "filterLinkHints", "previousPatterns", "nextPatterns"],
+
+ get: function (key) { return this.values[key]; },
+
+ load: function() {
+ for (var i in this.valuesToLoad) { this.sendMessage(this.valuesToLoad[i]); }
+ },
+
+ sendMessage: function (key) {
+ if (!settingPort)
+ settingPort = chrome.extension.connect({ name: "getSetting" });
+ settingPort.postMessage({ key: key });
+ },
+
+ receiveMessage: function (args) {
+ // not using 'this' due to issues with binding on callback
+ settings.values[args.key] = args.value;
+ if (++settings.loadedValues == settings.valuesToLoad.length)
+ settings.initializeOnReady();
+ },
+
+ initializeOnReady: function () {
+ linkHints.init();
+ }
+};
/*
* Give this frame a unique id.
@@ -43,27 +71,16 @@ var textInputXPath = '//input[' +
frameId = Math.floor(Math.random()*999999999)
var hasModifiersRegex = /^<([amc]-)+.>/;
-
-function getSetting(key) {
- if (!settingPort)
- settingPort = chrome.extension.connect({ name: "getSetting" });
- settingPort.postMessage({ key: key });
-}
-
-function setSetting(args) { settings[args.key] = args.value; }
+var googleRegex = /:\/\/[^/]*google[^/]+/;
/*
- * Complete initialization work that sould be done prior to DOMReady, like setting the page's zoom level.
+ * Complete initialization work that sould be done prior to DOMReady.
*/
function initializePreDomReady() {
- for (var i in settingsToLoad) { getSetting(settingsToLoad[i]); }
+ settings.load();
checkIfEnabledForUrl();
- var getZoomLevelPort = chrome.extension.connect({ name: "getZoomLevel" });
- if (window.self == window.parent)
- getZoomLevelPort.postMessage({ domain: window.location.host });
-
chrome.extension.sendRequest({handler: "getLinkHintCss"}, function (response) {
linkHintCss = response.linkHintCss;
});
@@ -74,35 +91,36 @@ function initializePreDomReady() {
keyPort = chrome.extension.connect({ name: "keyDown" });
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
- if (request.name == "hideUpgradeNotification")
+ if (request.name == "hideUpgradeNotification") {
HUD.hideUpgradeNotification();
- else if (request.name == "showUpgradeNotification" && isEnabledForUrl)
+ } else if (request.name == "showUpgradeNotification" && isEnabledForUrl) {
HUD.showUpgradeNotification(request.version);
- else if (request.name == "showHelpDialog")
+ } else if (request.name == "showHelpDialog") {
if (isShowingHelpDialog)
hideHelpDialog();
else
showHelpDialog(request.dialogHtml, request.frameId);
- else if (request.name == "focusFrame")
- if(frameId == request.frameId)
+ } else if (request.name == "focusFrame") {
+ if (frameId == request.frameId)
focusThisFrame(request.highlight);
- else if (request.name == "refreshCompletionKeys")
- refreshCompletionKeys(request.completionKeys);
+ } else if (request.name == "refreshCompletionKeys") {
+ refreshCompletionKeys(request);
+ }
sendResponse({}); // Free up the resources used by this open connection.
});
chrome.extension.onConnect.addListener(function(port, name) {
if (port.name == "executePageCommand") {
port.onMessage.addListener(function(args) {
- if (this[args.command] && frameId == args.frameId) {
+ if (frameId == args.frameId) {
if (args.passCountToFunction) {
- this[args.command].call(null, args.count);
+ utils.invokeCommandString(args.command, [args.count]);
} else {
- for (var i = 0; i < args.count; i++) { this[args.command].call(); }
+ for (var i = 0; i < args.count; i++) { utils.invokeCommandString(args.command); }
}
}
- refreshCompletionKeys(args.completionKeys);
+ refreshCompletionKeys(args);
});
}
else if (port.name == "getScrollPosition") {
@@ -122,14 +140,8 @@ function initializePreDomReady() {
port.onMessage.addListener(function(args) {
if (getCurrentUrlHandlers.length > 0) { getCurrentUrlHandlers.pop()(args.url); }
});
- } else if (port.name == "returnZoomLevel") {
- port.onMessage.addListener(function(args) {
- currentZoomLevel = args.zoomLevel;
- if (isEnabledForUrl)
- setPageZoomLevel(currentZoomLevel);
- });
} else if (port.name == "returnSetting") {
- port.onMessage.addListener(setSetting);
+ port.onMessage.addListener(settings.receiveMessage);
} else if (port.name == "refreshCompletionKeys") {
port.onMessage.addListener(function (args) {
refreshCompletionKeys(args.completionKeys);
@@ -144,6 +156,7 @@ function initializePreDomReady() {
function initializeWhenEnabled() {
document.addEventListener("keydown", onKeydown, true);
document.addEventListener("keypress", onKeypress, true);
+ document.addEventListener("keyup", onKeyup, true);
document.addEventListener("focus", onFocusCapturePhase, true);
document.addEventListener("blur", onBlurCapturePhase, true);
enterInsertModeIfElementIsFocused();
@@ -183,100 +196,59 @@ function initializeOnDomReady() {
};
// This is a little hacky but sometimes the size wasn't available on domReady?
-function registerFrameIfSizeAvailable (top) {
+function registerFrameIfSizeAvailable (is_top) {
if (innerWidth != undefined && innerWidth != 0 && innerHeight != undefined && innerHeight != 0)
chrome.extension.sendRequest({ handler: "registerFrame", frameId: frameId,
- area: innerWidth * innerHeight, top: top, total: frames.length + 1 });
+ area: innerWidth * innerHeight, is_top: is_top, total: frames.length + 1 });
else
- setTimeout(function () { registerFrameIfSizeAvailable(top); }, 100);
+ setTimeout(function () { registerFrameIfSizeAvailable(is_top); }, 100);
}
/*
- * Checks the currently focused element of the document and will enter insert mode if that element is focusable.
+ * Enters insert mode if the currently focused element in the DOM is focusable.
*/
function enterInsertModeIfElementIsFocused() {
- // Enter insert mode automatically if there's already a text box focused.
if (document.activeElement && isEditable(document.activeElement))
- enterInsertMode();
-}
-
-/*
- * Asks the background page to persist the zoom level for the given domain to localStorage.
- */
-function saveZoomLevel(domain, zoomLevel) {
- if (!saveZoomLevelPort)
- saveZoomLevelPort = chrome.extension.connect({ name: "saveZoomLevel" });
- saveZoomLevelPort.postMessage({ domain: domain, zoomLevel: zoomLevel });
-}
-
-/*
- * Zoom in increments of 20%; this matches chrome's CMD+ and CMD- keystrokes.
- * Set the zoom style on documentElement because document.body does not exist pre-page load.
- */
-function setPageZoomLevel(zoomLevel, showUINotification) {
- document.documentElement.style.zoom = zoomLevel + "%";
- if (document.body)
- HUD.updatePageZoomLevel(zoomLevel);
- if (showUINotification)
- HUD.showForDuration("Zoom: " + currentZoomLevel + "%", 1000);
-}
-
-function zoomIn() {
- currentZoomLevel += 20;
- setAndSaveZoom();
-}
-
-function zoomOut() {
- currentZoomLevel -= 20;
- setAndSaveZoom();
-}
-
-function zoomReset() {
- currentZoomLevel = 100;
- setAndSaveZoom();
-}
-
-function setAndSaveZoom() {
- setPageZoomLevel(currentZoomLevel, true);
- saveZoomLevel(window.location.host, currentZoomLevel);
+ enterInsertModeWithoutShowingIndicator(document.activeElement);
}
function scrollToBottom() { window.scrollTo(window.pageXOffset, document.body.scrollHeight); }
function scrollToTop() { window.scrollTo(window.pageXOffset, 0); }
function scrollToLeft() { window.scrollTo(0, window.pageYOffset); }
function scrollToRight() { window.scrollTo(document.body.scrollWidth, window.pageYOffset); }
-function scrollUp() { window.scrollBy(0, -1 * settings["scrollStepSize"]); }
-function scrollDown() { window.scrollBy(0, settings["scrollStepSize"]); }
+function scrollUp() { window.scrollBy(0, -1 * settings.get("scrollStepSize")); }
+function scrollDown() { window.scrollBy(0, settings.get("scrollStepSize")); }
function scrollPageUp() { window.scrollBy(0, -1 * window.innerHeight / 2); }
function scrollPageDown() { window.scrollBy(0, window.innerHeight / 2); }
function scrollFullPageUp() { window.scrollBy(0, -window.innerHeight); }
function scrollFullPageDown() { window.scrollBy(0, window.innerHeight); }
-function scrollLeft() { window.scrollBy(-1 * settings["scrollStepSize"], 0); }
-function scrollRight() { window.scrollBy(settings["scrollStepSize"], 0); }
+function scrollLeft() { window.scrollBy(-1 * settings.get("scrollStepSize"), 0); }
+function scrollRight() { window.scrollBy(settings.get("scrollStepSize"), 0); }
function focusInput(count) {
- var results = document.evaluate(textInputXPath,
- document.documentElement, null,
- XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
+ var results = utils.evaluateXPath(textInputXPath, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
var lastInputBox;
var i = 0;
while (i < count) {
- i += 1;
-
var currentInputBox = results.iterateNext();
if (!currentInputBox) { break; }
+ if (linkHints.getVisibleClientRect(currentInputBox) === null)
+ continue;
+
lastInputBox = currentInputBox;
+
+ i += 1;
}
if (lastInputBox) { lastInputBox.focus(); }
}
function reload() { window.location.reload(); }
-function goBack() { history.back(); }
-function goForward() { history.forward(); }
+function goBack(count) { history.go(-count); }
+function goForward(count) { history.go(count); }
function goUp(count) {
var url = window.location.href;
@@ -308,6 +280,8 @@ function copyCurrentUrl() {
// TODO(ilya): Convert to sendRequest.
var getCurrentUrlPort = chrome.extension.connect({ name: "getCurrentTabUrl" });
getCurrentUrlPort.postMessage({});
+
+ HUD.showForDuration("Yanked URL", 1000);
}
function toggleViewSourceCallback(url) {
@@ -327,18 +301,18 @@ function toggleViewSourceCallback(url) {
* Note that some keys will only register keydown events and not keystroke events, e.g. ESC.
*/
function onKeypress(event) {
- var keyChar = "";
-
- if (linkHintsModeActivated)
+ if (!bubbleEvent('keypress', event))
return;
+ var keyChar = "";
+
// Ignore modifier keys by themselves.
if (event.keyCode > 31) {
keyChar = String.fromCharCode(event.charCode);
// Enter insert mode when the user enables the native find interface.
if (keyChar == "f" && isPrimaryModifierKey(event)) {
- enterInsertMode();
+ enterInsertModeWithoutShowingIndicator();
return;
}
@@ -349,7 +323,7 @@ function onKeypress(event) {
// Don't let the space scroll us if we're searching.
if (event.keyCode == keyCodes.space)
event.preventDefault();
- } else if (!insertMode && !findMode) {
+ } else if (!isInsertMode() && !findMode) {
if (currentCompletionKeys.indexOf(keyChar) != -1) {
event.preventDefault();
event.stopPropagation();
@@ -361,18 +335,32 @@ function onKeypress(event) {
}
}
-function onKeydown(event) {
- var keyChar = "";
+/**
+ * Called whenever we receive a key event. Each individual handler has the option to stop the event's
+ * propagation by returning a falsy value.
+ */
+function bubbleEvent(type, event) {
+ for (var i = handlerStack.length-1; i >= 0; i--) {
+ // We need to check for existence of handler because the last function call may have caused the release of
+ // more than one handler.
+ if (handlerStack[i] && handlerStack[i][type] && !handlerStack[i][type](event))
+ return false;
+ }
+ return true;
+}
- if (linkHintsModeActivated)
+function onKeydown(event) {
+ if (!bubbleEvent('keydown', event))
return;
+ var keyChar = "";
+
// handle modifiers being pressed.don't handle shiftKey alone (to avoid / being interpreted as ?
- if (event.metaKey && event.keyCode > 31 || event.ctrlKey && event.keyCode > 31 || event.altKey && event.keyCode > 31) {
+ if (event.metaKey && event.keyCode > 31 || event.ctrlKey && event.keyCode > 31 ||
+ event.altKey && event.keyCode > 31) {
keyChar = getKeyChar(event);
- if (keyChar != "") // Again, ignore just modifiers. Maybe this should replace the keyCode > 31 condition.
- {
+ if (keyChar != "") { // Again, ignore just modifiers. Maybe this should replace the keyCode>31 condition.
var modifiers = [];
if (event.shiftKey)
@@ -392,44 +380,45 @@ function onKeydown(event) {
}
}
- if (insertMode && isEscape(event))
- {
+ if (isInsertMode() && isEscape(event)) {
// Note that we can't programmatically blur out of Flash embeds from Javascript.
if (!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(); }
+ // 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();
- // Added to prevent Google Instant from reclaiming the keystroke and putting us back into the search box.
- // TOOD(ilya): Revisit this. Not sure it's the absolute best approach.
- event.stopPropagation();
+ // Added to prevent Google Instant from reclaiming the keystroke and putting us back into the search
+ // box.
+ if (isGoogleSearch())
+ event.stopPropagation();
}
}
- else if (findMode)
- {
- if (isEscape(event))
+ else if (findMode) {
+ if (isEscape(event)) {
exitFindMode();
// Don't let backspace take us back in history.
- else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey)
- {
+ }
+ else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) {
handleDeleteForFindMode();
event.preventDefault();
}
- else if (event.keyCode == keyCodes.enter)
+ else if (event.keyCode == keyCodes.enter) {
handleEnterForFindMode();
+ }
}
- else if (isShowingHelpDialog && isEscape(event))
- {
+ else if (isShowingHelpDialog && isEscape(event)) {
hideHelpDialog();
}
- else if (!insertMode && !findMode) {
+ else if (!isInsertMode() && !findMode) {
if (keyChar) {
- if (currentCompletionKeys.indexOf(keyChar) != -1) {
- event.preventDefault();
- event.stopPropagation();
- }
+ if (currentCompletionKeys.indexOf(keyChar) != -1) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
- keyPort.postMessage({keyChar:keyChar, frameId:frameId});
+ keyPort.postMessage({keyChar:keyChar, frameId:frameId});
}
else if (isEscape(event)) {
keyPort.postMessage({keyChar:"<ESC>", frameId:frameId});
@@ -443,40 +432,55 @@ function onKeydown(event) {
// Subject to internationalization issues since we're using keyIdentifier instead of charCode (in keypress).
//
// TOOD(ilya): Revisit this. Not sure it's the absolute best approach.
- if (keyChar == "" && !insertMode && currentCompletionKeys.indexOf(getKeyChar(event)) != -1)
+ if (keyChar == "" && !isInsertMode() && (currentCompletionKeys.indexOf(getKeyChar(event)) != -1 ||
+ validFirstKeys[getKeyChar(event)]))
event.stopPropagation();
}
+function onKeyup() {
+ if (!bubbleEvent('keyup', event))
+ return;
+}
+
function checkIfEnabledForUrl() {
- var url = window.location.toString();
-
- chrome.extension.sendRequest({ handler: "isEnabledForUrl", url: url }, function (response) {
- isEnabledForUrl = response.isEnabledForUrl;
- if (isEnabledForUrl)
- initializeWhenEnabled();
- else if (HUD.isReady())
- // Quickly hide any HUD we might already be showing, e.g. if we entered insertMode on page load.
- HUD.hide();
- });
+ var url = window.location.toString();
+
+ chrome.extension.sendRequest({ handler: "isEnabledForUrl", url: url }, function (response) {
+ isEnabledForUrl = response.isEnabledForUrl;
+ if (isEnabledForUrl)
+ initializeWhenEnabled();
+ else if (HUD.isReady())
+ // Quickly hide any HUD we might already be showing, e.g. if we entered insert mode on page load.
+ HUD.hide();
+ });
}
-function refreshCompletionKeys(completionKeys) {
- if (completionKeys)
- currentCompletionKeys = completionKeys;
- else
- chrome.extension.sendRequest({handler: "getCompletionKeys"}, function (response) {
- currentCompletionKeys = response.completionKeys;
- });
+// TODO(ilya): This just checks if "google" is in the domain name. Probably should be more targeted.
+function isGoogleSearch() {
+ var url = window.location.toString();
+ return !!url.match(googleRegex);
+}
+
+function refreshCompletionKeys(response) {
+ if (response) {
+ currentCompletionKeys = response.completionKeys;
+
+ if (response.validFirstKeys)
+ validFirstKeys = response.validFirstKeys;
+ }
+ else {
+ chrome.extension.sendRequest({ handler: "getCompletionKeys" }, refreshCompletionKeys);
+ }
}
function onFocusCapturePhase(event) {
if (isFocusable(event.target))
- enterInsertMode();
+ enterInsertModeWithoutShowingIndicator(event.target);
}
function onBlurCapturePhase(event) {
if (isFocusable(event.target))
- exitInsertMode();
+ exitInsertMode(event.target);
}
/*
@@ -488,32 +492,52 @@ function isFocusable(element) { return isEditable(element) || isEmbed(element);
* Embedded elements like Flash and quicktime players can obtain focus but cannot be programmatically
* unfocused.
*/
-function isEmbed(element) { return ["EMBED", "OBJECT"].indexOf(element.tagName) > 0; }
+function isEmbed(element) { return ["embed", "object"].indexOf(element.nodeName.toLowerCase()) > 0; }
/*
* Input or text elements are considered focusable and able to receieve their own keyboard events,
* and will enter enter mode if focused. Also note that the "contentEditable" attribute can be set on
* any element which makes it a rich text editor, like the notes on jjot.com.
- * Note: we used to discriminate for text-only inputs, but this is not accurate since all input fields
- * can be controlled via the keyboard, particuarlly SELECT combo boxes.
*/
function isEditable(target) {
- if (target.getAttribute("contentEditable") == "true")
+ if (target.isContentEditable)
+ return true;
+ var nodeName = target.nodeName.toLowerCase();
+ // use a blacklist instead of a whitelist because new form controls are still being implemented for html5
+ var noFocus = ["radio", "checkbox"];
+ if (nodeName == "input" && noFocus.indexOf(target.type) == -1)
return true;
- var focusableInputs = ["input", "textarea", "select", "button"];
- return focusableInputs.indexOf(target.tagName.toLowerCase()) >= 0;
+ var focusableElements = ["textarea", "select"];
+ return focusableElements.indexOf(nodeName) >= 0;
}
-function enterInsertMode() {
- insertMode = true;
+/*
+ * Enters insert mode and show an "Insert mode" message. Showing the UI is only useful when entering insert
+ * mode manually by pressing "i". In most cases we do not show any UI (enterInsertModeWithoutShowingIndicator)
+ */
+function enterInsertMode(target) {
+ enterInsertModeWithoutShowingIndicator(target);
HUD.show("Insert mode");
}
-function exitInsertMode() {
- insertMode = false;
- HUD.hide();
+/*
+ * We cannot count on 'focus' and 'blur' events to happen sequentially. For example, if blurring element A
+ * causes element B to come into focus, we may get "B focus" before "A blur". Thus we only leave insert mode
+ * when the last editable element that came into focus -- which insertModeLock points to -- has been blurred.
+ * If insert mode is entered manually (via pressing 'i'), then we set insertModeLock to 'undefined', and only
+ * leave insert mode when the user presses <ESC>.
+ */
+function enterInsertModeWithoutShowingIndicator(target) { insertModeLock = target; }
+
+function exitInsertMode(target) {
+ if (target === undefined || insertModeLock === target) {
+ insertModeLock = null;
+ HUD.hide();
+ }
}
+function isInsertMode() { return insertModeLock !== null; }
+
function handleKeyCharForFindMode(keyChar) {
findModeQuery = findModeQuery + keyChar;
performFindInPlace();
@@ -521,13 +545,11 @@ function handleKeyCharForFindMode(keyChar) {
}
function handleDeleteForFindMode() {
- if (findModeQuery.length == 0)
- {
+ if (findModeQuery.length == 0) {
exitFindMode();
performFindInPlace();
}
- else
- {
+ else {
findModeQuery = findModeQuery.substring(0, findModeQuery.length - 1);
performFindInPlace();
showFindModeHUDForQuery();
@@ -551,31 +573,51 @@ function performFindInPlace() {
// backwards.
window.scrollTo(cachedScrollX, cachedScrollY);
- performFind();
+ executeFind();
}
-function performFind() {
- findModeQueryHasResults = window.find(findModeQuery, false, false, true, false, true, false);
+function executeFind(backwards) {
+ findModeQueryHasResults = window.find(findModeQuery, false, backwards, true, false, true, false);
}
-function performBackwardsFind() {
- findModeQueryHasResults = window.find(findModeQuery, false, true, true, false, true, false);
+function focusFoundLink() {
+ if (findModeQueryHasResults) {
+ var link = getLinkFromSelection();
+ if (link)
+ link.focus();
+ }
+}
+
+function findAndFocus(backwards) {
+ executeFind(backwards);
+ focusFoundLink();
+}
+
+function performFind() { findAndFocus(); }
+
+function performBackwardsFind() { findAndFocus(true); }
+
+function getLinkFromSelection() {
+ var node = window.getSelection().anchorNode;
+ while (node.nodeName.toLowerCase() !== 'body') {
+ if (node.nodeName.toLowerCase() === 'a') return node;
+ node = node.parentNode;
+ }
+ return null;
}
function findAndFollowLink(linkStrings) {
for (i = 0; i < linkStrings.length; i++) {
- var findModeQueryHasResults = window.find(linkStrings[i], false, true, true, false, true, false);
- if (findModeQueryHasResults) {
- var node = window.getSelection().anchorNode;
- while (node.nodeName != 'BODY') {
- if (node.nodeName == 'A') {
- window.location = node.href;
- return true;
- }
- node = node.parentNode;
+ var hasResults = window.find(linkStrings[i], false, true, true, false, true, false);
+ if (hasResults) {
+ var link = getLinkFromSelection();
+ if (link) {
+ window.location = link.href;
+ return true;
}
}
}
+ return false;
}
function findAndFollowRel(value) {
@@ -592,13 +634,13 @@ function findAndFollowRel(value) {
}
function goPrevious() {
- var previousPatterns = settings["previousPatterns"] || "";
+ var previousPatterns = settings.get("previousPatterns") || "";
var previousStrings = previousPatterns.split(",");
findAndFollowRel('prev') || findAndFollowLink(previousStrings);
}
function goNext() {
- var nextPatterns = settings["nextPatterns"] || "";
+ var nextPatterns = settings.get("nextPatterns") || "";
var nextStrings = nextPatterns.split(",");
findAndFollowRel('next') || findAndFollowLink(nextStrings);
}
@@ -616,12 +658,11 @@ function showFindModeHUDForQuery() {
function insertSpaces(query) {
var newQuery = "";
- for (var i = 0; i < query.length; i++)
- {
+ for (var i = 0; i < query.length; i++) {
if (query[i] == " " || (i + 1 < query.length && query[i + 1] == " "))
newQuery = newQuery + query[i];
- else
- newQuery = newQuery + query[i] + "<span style=\"font-size: 0px;\"> </span>";
+ else // &#8203; is a zero-width space
+ newQuery = newQuery + query[i] + "<span>&#8203;</span>";
}
return newQuery;
@@ -635,6 +676,7 @@ function enterFindMode() {
function exitFindMode() {
findMode = false;
+ focusFoundLink();
HUD.hide();
}
@@ -648,13 +690,14 @@ function showHelpDialog(html, fid) {
document.body.appendChild(container);
container.innerHTML = html;
+ container.getElementsByClassName("closeButton")[0].addEventListener("click", hideHelpDialog, false);
+ container.getElementsByClassName("optionsPage")[0].addEventListener("click",
+ function() { chrome.extension.sendRequest({ handler: "openOptionsPageInNewTab" }); }, false);
+
// This is necessary because innerHTML does not evaluate javascript embedded in <script> tags.
var scripts = Array.prototype.slice.call(container.getElementsByTagName("script"));
scripts.forEach(function(script) { eval(script.text); });
- container.getElementsByClassName("closeButton")[0].addEventListener("click", hideHelpDialog, false);
- container.getElementsByClassName("optionsPage")[0].addEventListener("click",
- function() { chrome.extension.sendRequest({ handler: "openOptionsPageInNewTab" }); }, false);
}
function hideHelpDialog(clickEvent) {
@@ -763,15 +806,6 @@ HUD = {
function() { HUD.upgradeNotificationElement().style.display = "none"; });
},
- updatePageZoomLevel: function(pageZoomLevel) {
- // Since the chrome HUD does not scale with the page's zoom level, neither will this HUD.
- var inverseZoomLevel = (100.0 / pageZoomLevel) * 100;
- if (HUD._displayElement)
- HUD.displayElement().style.zoom = inverseZoomLevel + "%";
- if (HUD._upgradeNotificationElement)
- HUD.upgradeNotificationElement().style.zoom = inverseZoomLevel + "%";
- },
-
/*
* Retrieves the HUD HTML element.
*/
@@ -780,7 +814,6 @@ HUD = {
HUD._displayElement = HUD.createHudElement();
// Keep this far enough to the right so that it doesn't collide with the "popups blocked" chrome HUD.
HUD._displayElement.style.right = "150px";
- HUD.updatePageZoomLevel(currentZoomLevel);
}
return HUD._displayElement;
},
@@ -790,7 +823,6 @@ HUD = {
HUD._upgradeNotificationElement = HUD.createHudElement();
// Position this just to the left of our normal HUD.
HUD._upgradeNotificationElement.style.right = "315px";
- HUD.updatePageZoomLevel(currentZoomLevel);
}
return HUD._upgradeNotificationElement;
},