Skip to content

Instantly share code, notes, and snippets.

@alexeyr
Created February 16, 2011 16:34
Show Gist options
  • Select an option

  • Save alexeyr/829673 to your computer and use it in GitHub Desktop.

Select an option

Save alexeyr/829673 to your computer and use it in GitHub Desktop.

Revisions

  1. alexeyr created this gist Feb 16, 2011.
    1,239 changes: 1,239 additions & 0 deletions nextpleaseOverlay.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1239 @@
    /*********************************************************************************
    * The contents of this file are subject to the Mozilla Public License Version 1.1
    * ("License"); you may not use this file except in compliance with the License.
    * You may obtain a copy of the License at http://www.mozilla.org/MPL/.
    *
    * Software distributed under the License is distributed on an "AS IS" basis,
    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
    * the specific language governing rights and limitations under the License.
    *
    * The Initial Developer of the Original Code is Howie Wang.
    * Contributor(s): Alexey Romanov, Jesse Weinstein.
    *
    * Alternatively, the contents of this file may be used under the terms
    * of the GPL 2.0 license (http://www.gnu.org/licenses/gpl-2.0.html) or the
    * LGPL 2.1 license (http://www.gnu.org/licenses/lgpl-2.1.html), in which case
    * the provisions of this license are applicable instead of those above.
    ********************************************************************************/

    "use strict";

    window.addEventListener("load", function () { nextplease.init(); }, false);
    window.addEventListener("popupshowing", function () { nextplease.showHideMenuItems(); }, false);
    window.addEventListener("load", function (event) { nextplease.loadListener.onLoad(event); }, false);

    nextplease.setUnicharPref = function (aPrefName, aPrefValue) {
    // nextplease.logDetail("setting " + aPrefName + " to " + aPrefValue);
    if (nextplease.prefs) {
    try {
    var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
    str.data = aPrefValue;
    nextplease.prefs.setComplexValue(aPrefName, Components.interfaces.nsISupportsString, str);
    } catch (e) {
    nextplease.logError("Failed to set value of " + aPrefName + " to " + aPrefValue);
    }
    } else {
    nextplease.logError("nextplease.prefs is not defined");
    }
    };

    nextplease.getUnicharPref = function (aPrefName) {
    // nextplease.logDetail("getting " + aPrefName);
    if (nextplease.prefs) {
    try {
    return nextplease.prefs.getComplexValue(aPrefName, Components.interfaces.nsISupportsString).data;
    } catch (e) {
    nextplease.logError("Failed to get value for " + aPrefName);
    }
    } else {
    nextplease.logError("nextplease.prefs is not defined");
    }
    return "";
    };

    nextplease.gotHereUsingNextplease = false;

    nextplease.MAX_LINK_NUM = 1000;
    nextplease.MAX_GALLERY_GAP = 20;
    nextplease.MAX_LINKS_TO_CHECK = 1000;
    nextplease.SEARCH_FOR_SUBMIT = 1;

    nextplease.directions = ["Next", "Prev", "First", "Last"];

    nextplease.imageURLsCache = {first: undefined, last: undefined, size: 0, MAX_SIZE: 500, map: {}};

    nextplease.SEARCH_TYPE = {Next: 1, Prev: 2, First: 3, Last: 4};
    nextplease.ResultType = {Link: 0, URL: 1, Input: 2, History: 3};

    nextplease.PREFETCH_ENUM = {No: 0, Yes: 1, Smart: 2};

    nextplease.highlighted_old_styles = {};

    // This code was stolen from brody on the mozillazine.org forums.
    nextplease.loadListener = {
    onLoad: function (event) {
    getBrowser().addEventListener("DOMContentLoaded", function (event) {
    nextplease.loadListener.onContentLoaded(event);
    }, true);
    },

    onContentLoaded: function (event) {
    var doc = event.originalTarget;

    // When this becomes true it means that
    // all of the top level document's
    // subframes have also finished loading
    if (!this.isTopLevelDocument(doc)) { return; }

    // Log/alert
    if (nextplease.DEBUG_DETAILED) {
    nextplease.logDetail("loadListener\n" + "DOMContentLoaded\n" + doc.title + "\n" + doc.documentURI);
    }

    nextplease.clearStatusBar();

    // no effect if nextplease.clearStatusBarTimer is invalid
    clearTimeout(nextplease.clearStatusBarTimer);

    nextplease.cacheImageLocations();

    nextplease.prefetched = {};
    nextplease.unhighlight();

    if (nextplease.prefetchPref === nextplease.PREFETCH_ENUM.Yes) {
    nextplease.prefetch();
    } else if ((nextplease.prefetchPref === nextplease.PREFETCH_ENUM.Smart) && nextplease.gotHereUsingNextplease) {
    nextplease.prefetch();
    nextplease.gotHereUsingNextplease = false;
    }
    },

    isTopLevelDocument: function (aDocument) {
    return (aDocument === aDocument.defaultView.top.document);
    }
    };

    nextplease.prefObserver = {
    QueryInterface: function QueryInterface(aIID) {
    if (aIID.equals(Components.interfaces.nsIObserver) ||
    aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
    aIID.equals(Components.interfaces.nsISupports)) {
    return this;
    } else {
    throw Components.results.NS_NOINTERFACE;
    }
    },

    register: function () {
    nextplease.prefs.addObserver("", this, true);
    nextplease.accelKeyPrefs.addObserver("", this, true);
    },

    observe: function (subject, topic, data) {
    if (topic === "nsPref:changed") {
    nextplease.readPreferences();
    }
    }
    };

    nextplease.prefObserver.register();

    nextplease.checkImageCache = function () {
    var i, url, urlsCache = nextplease.imageURLsCache, cachedURLsNum = urlsCache.size;
    var messageLines = ["size=" + cachedURLsNum + "; first=" + urlsCache.first + "; last=" + urlsCache.last], message;

    if (cachedURLsNum > 0) {
    url = urlsCache.first;
    for (i = 0; i < cachedURLsNum; i++, url = urlsCache.map[url]) {
    messageLines[messageLines.length] = "i=" + i + "; url=" + url + "; urlsCache.map[url]=" + urlsCache.map[url];
    if (!url) {
    message = messageLines.join("\n\n");
    nextplease.logError(message);
    alert(message);
    return;
    }
    }
    if (url !== urlsCache.last) {
    message = messageLines.join("\n\n");
    nextplease.logError(message);
    alert(message);
    return;
    }
    } else if (urlsCache.first || urlsCache.last) {
    message = messageLines[0];
    alert(message);
    nextplease.logError(message);
    }
    };

    nextplease.addImageToCache = function (imgUrl) {
    var urlToDelete, urlsCache = nextplease.imageURLsCache, incrSize = true;
    if (imgUrl && !urlsCache.map[imgUrl]) {
    // nextplease.logDetail("adding " + imgUrl + " to image cache");
    if (urlsCache.size === 0) {
    urlsCache.first = imgUrl;
    urlsCache.map[imgUrl] = imgUrl;
    urlsCache.last = imgUrl;
    urlsCache.size = 1;
    } else {
    if (urlsCache.size >= urlsCache.MAX_SIZE) {
    urlToDelete = urlsCache.first;
    urlsCache.first = urlsCache.map[urlToDelete];
    delete urlsCache.map[urlToDelete];
    incrSize = false;
    }

    urlsCache.map[urlsCache.last] = imgUrl;
    urlsCache.map[imgUrl] = imgUrl;
    urlsCache.last = imgUrl;
    if (incrSize) {
    urlsCache.size++;
    }
    }
    } //else {
    // nextplease.logDetail("" + imgUrl + " is already in image cache");
    //}
    };

    nextplease.cacheImageLocations = function () {
    var i, urlsCache = nextplease.imageURLsCache;

    nextplease.logDetail("caching image location");
    nextplease.logDetail("there are currently " + urlsCache.size + " image URLs cached");

    var theDocument = window.content.document;

    var start = new Date();

    var imgElems = theDocument.getElementsByTagName("img"), imgElemsNum = imgElems.length;
    if (imgElems) {
    var numberOfImagesToCheck = Math.min(nextplease.MAX_LINKS_TO_CHECK, imgElemsNum);
    nextplease.logDetail("Checking " + numberOfImagesToCheck + " <img> elements out of " + imgElemsNum + " total.");
    for (i = 0; i < numberOfImagesToCheck; i++) {
    nextplease.addImageToCache(imgElems[i].src);
    }
    }

    var links = theDocument.getElementsByTagName("a"), linksNum = links.length;
    if (links) {
    var numberOfLinksToCheck = Math.min(nextplease.MAX_LINKS_TO_CHECK, linksNum);
    nextplease.logDetail("Checking " + numberOfLinksToCheck + " <a> elements out of " + linksNum + " total.");
    for (i = 0; i < numberOfLinksToCheck; i++) {
    var linkUrl = links[i].href;
    if (!urlsCache.map[linkUrl]) {
    // This is technically not correct, but performance sucks
    // if we do a proper regex check.
    if (linkUrl.length > 3) {
    var suffix = linkUrl.slice(-3).toLowerCase();

    if ((suffix === 'jpg') || (suffix === 'gif') || (suffix === 'png') || (suffix === 'bmp')) {
    nextplease.addImageToCache(linkUrl);
    }
    }
    } //else {
    // nextplease.logDetail("" + linkUrl + " is already in image cache");
    //}
    }
    }
    var end = new Date();
    nextplease.logDetail("caching took " + (end.getTime() - start.getTime()) + " ms");

    if (nextplease.DEBUG_DETAILED) {
    nextplease.logDetail("checking cache correctness again");
    nextplease.checkImageCache();
    }

    nextplease.logDetail("there are currently " + urlsCache.size + " image URLs cached");

    // nextplease.logDetail(nextplease.imageLocationArray.toString());
    };

    nextplease.initKey = function (keyId, keyPrefName, modifierPrefName) {
    var modString = nextplease.getModifierPref(modifierPrefName);
    var keyOrCharCode = nextplease.prefs.getIntPref(keyPrefName);
    if (keyOrCharCode === 0) {
    nextplease.prefs.clearUserPref(keyPrefName);
    keyOrCharCode = nextplease.prefs.getIntPref(keyPrefName);
    }
    var isKeyCodePrefName = "iskeycode." + keyPrefName;
    var isKeyCode = nextplease.prefs.getBoolPref(isKeyCodePrefName);
    var enablePrefName = "enable." + keyPrefName;
    var enable = nextplease.prefs.getBoolPref(enablePrefName);
    var keyString;

    var keyElem = document.getElementById(keyId);
    keyElem.setAttribute("modifiers", modString);

    if (isKeyCode) {
    keyString = nextplease.KeyCodeToNameMap[keyOrCharCode];
    keyElem.removeAttribute("key");
    keyElem.setAttribute("keycode", keyString);
    } else {
    keyString = String.fromCharCode(keyOrCharCode);
    keyElem.setAttribute("key", keyString);
    keyElem.removeAttribute("keycode");
    }
    keyElem.setAttribute("disabled", !enable);

    if (enable) {
    nextplease.DisableKey(modString, keyString);
    }
    };

    nextplease.initNumberKeys = function () {
    var modifier = nextplease.getModifierPref("numbermodifier");
    var numberKey, i;
    for (i = 0; i < 10; i++) {
    numberKey = document.getElementById("nextplease" + i + "key");
    numberKey.setAttribute("modifiers", modifier);
    numberKey.setAttribute("disabled", !nextplease.useNumberShortcuts);
    if (nextplease.useNumberShortcuts) {
    nextplease.DisableKey(modifier, "" + i);
    }
    }
    };

    nextplease.readPreferences = function () {
    var i;
    if (!nextplease.prefs) {
    nextplease.logError("nextplease.prefs undefined. This shouldn't happen!");
    nextplease.prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("nextplease.");
    }

    try {
    nextplease.DEBUG = nextplease.prefs.getBoolPref("log");
    nextplease.DEBUG_DETAILED = nextplease.DEBUG && nextplease.prefs.getBoolPref("log.detailed");

    // nextplease.logDetail("reading preferences");

    nextplease.initKey("nextpleasekey", "nextkey", "keymodifier");
    nextplease.initKey("nextpleaseprevkey", "prevkey", "prevkeymodifier");
    nextplease.initKey("nextpleasefirstkey", "firstkey", "firstkeymodifier");
    nextplease.initKey("nextpleaselastkey", "lastkey", "lastkeymodifier");

    // nextplease.logDetail("keys read");

    nextplease.useSubmit = nextplease.prefs.getBoolPref("allowsubmit");
    nextplease.useNumberShortcuts = nextplease.prefs.getBoolPref("allownumbershortcuts");
    nextplease.useContextMenu = nextplease.prefs.getBoolPref("allowcontextmenu");
    nextplease.useSmartNext = nextplease.prefs.getBoolPref("allowsmartnext");
    nextplease.prefetchPref = nextplease.prefs.getIntPref("prefetch");
    nextplease.useFrames = nextplease.prefs.getBoolPref("checkframes");

    // nextplease.logDetail("bools read");

    var nextRegExString = nextplease.getUnicharPref("nextregex");
    var prevRegExString = nextplease.getUnicharPref("prevregex");
    var firstRegExString = nextplease.getUnicharPref("firstregex");
    var lastRegExString = nextplease.getUnicharPref("lastregex");

    var galleryRegExString = nextplease.getUnicharPref("galleryregex");
    var galleryRegEx = new RegExp(galleryRegExString, "i");
    var matches = galleryRegEx.exec("http://nextplease.mozdev.org/test/test101.jpg");
    if (matches && (matches.length !== 4)) {
    nextplease.logError("Gallery regex test failed!");
    nextplease.prefs.clearUserPref("galleryregex");
    galleryRegExString = nextplease.getUnicharPref("galleryregex");
    galleryRegEx = new RegExp(galleryRegExString, "i");
    }

    nextplease.RegExes = {Next: new RegExp(nextRegExString, "i"),
    Prev: new RegExp(prevRegExString, "i"),
    First: new RegExp(firstRegExString, "i"),
    Last: new RegExp(lastRegExString, "i"),
    Gallery: galleryRegEx};

    // nextplease.logDetail("regexes read");

    // nextplease.logDetail("gallery regex read");

    nextplease.initNumberKeys();

    nextplease.ImageMap = {};
    nextplease.PhraseMap = {};

    // Read the phrases that specify a next link
    // by reading the preferences or defaults,
    // and put the phrases in a lookup table.
    var addPrefsToMap = function (map, phraseOrImage, direction) {
    var prefbranch = (direction + phraseOrImage).toLowerCase();
    var tempprefname = prefbranch + ".expr0";
    var prefValue = nextplease.getUnicharPref(tempprefname);
    var values = prefValue.split("|");
    var i;
    // nextplease.logDetail("initializing " + prefbranch);
    for (i = 0; i < values.length; i++) {
    var value = values[i].replace(/&pipe;/g, "|");
    if (value !== "") {
    map[value] = direction;
    // nextplease.logDetail("added "+value+" to "+direction+" "+phraseOrImage);
    }
    }
    // nextplease.logDetail("finished initializing " + prefbranch);
    };

    for (i = 0; i < nextplease.directions.length; i++) {
    var direction = nextplease.directions[i];
    addPrefsToMap(nextplease.PhraseMap, "Phrase", direction);
    addPrefsToMap(nextplease.ImageMap, "Image", direction);
    }

    nextplease.highlightColor = nextplease.prefs.getBoolPref("highlight") ?
    nextplease.prefs.getCharPref("highlight.color") :
    undefined;
    nextplease.highlightPrefetchedColor = nextplease.prefs.getBoolPref("highlight.prefetched") ?
    nextplease.prefs.getCharPref("highlight.prefetched.color") :
    undefined;

    nextplease.logDetail("preferences read");
    } catch (e) {
    if (!nextplease.retryingToReadPreferences) {
    // nextplease.prefs.resetBranch() isn't implemented according to MDC
    var prefnames = nextplease.prefs.getChildList("", {});
    for (i = 0; i < prefnames.length; i++) {
    nextplease.prefs.clearUserPref(prefnames[i]);
    }

    var errorMsg = nextplease.strings.GetStringFromName("readingPreferencesFailed");
    alert(errorMsg);
    nextplease.retryingToReadPreferences = true;
    nextplease.readPreferences();
    }
    }
    };

    nextplease.DisableKey = function (modifier, keyString) {
    var conflictingKeys, conflictingKey, conflictingId, conflictingModifier, i;
    if (keyString.indexOf("VK_") >= 0) {
    // nextplease.logDetail("disabling keys conflicting with " + modifier + "+" + nextplease.KeyCodeToNameMap[keycode]);
    conflictingKeys = document.getElementsByAttribute("keycode", keyString);
    } else {
    // nextplease.logDetail("disabling keys conflicting with " + modifier + "+" + key);
    conflictingKeys = document.getElementsByAttribute("key", keyString.toLowerCase());
    }

    var conflictingKeysLength = conflictingKeys.length;
    // nextplease.logDetail(conflictingKeysLength + " keys conflicting with " + modifier + "+" keystring);
    for (i = 0; i < conflictingKeysLength; i++) {
    conflictingKey = conflictingKeys[i];
    conflictingId = conflictingKey.getAttribute("id");
    if (!(/nextplease/.test(conflictingId)) && conflictingKey.hasAttribute("modifiers")) {
    conflictingModifier = conflictingKey.getAttribute("modifiers").replace(nextplease.accelKey, "accel");
    // nextplease.logDetail("potentially conflicting key: " + conflictingId);
    if ((/alt/.test(modifier) === /alt/.test(conflictingModifier)) &&
    (/control/.test(modifier) === /control/.test(conflictingModifier)) &&
    (/meta/.test(modifier) === /meta/.test(conflictingModifier)) &&
    (/shift/.test(modifier) === /shift/.test(conflictingModifier)) &&
    (/accel/.test(modifier) === /accel/.test(conflictingModifier))) {
    // conflictingKey.parentNode.removeChild(conflictingKey);
    conflictingKey.setAttribute("disabled", true);
    nextplease.log("Disabled conflicting key " + conflictingId);
    }
    }
    }
    };

    nextplease.init = function () {
    nextplease.log("initializing");
    nextplease.retryingToReadPreferences = false;
    nextplease.readPreferences();
    nextplease.statusBar = document.getElementById("nextplease_statusbar_panel");
    };

    nextplease.clearStatusBar = function () {
    if (nextplease.statusBar) {
    nextplease.statusBar.collapsed = true;
    nextplease.statusBar.label = "";
    }
    };

    nextplease.showInStatusBar = function (text) {
    if (nextplease.statusBar) {
    nextplease.statusBar.collapsed = false;
    nextplease.statusBar.label = text;
    }
    };

    nextplease.notifyLinkNotFound = function () {
    nextplease.showInStatusBar(nextplease.strings.GetStringFromName("linkNotFound"));

    nextplease.clearStatusBarTimer = setTimeout(nextplease.clearStatusBar, 5000);
    };

    nextplease.directionFromRel = function (link) {
    // Look for rel attributes for next/prev/first/last
    if (link.rel && link.href) {
    var rel = link.rel.toLowerCase();
    if (rel === "next") {
    nextplease.log('found rel="' + link.rel + '": ' + link.href);
    return "Next";
    } else if ((rel === "prev") || (rel === "previous")) {
    nextplease.log('found rel="' + link.rel + '": ' + link.href);
    return "Prev";
    } else if ((rel === "start") || (rel === "first")) {
    nextplease.log('found rel="' + link.rel + '": ' + link.href);
    return "First";
    } else if ((rel === "end") || (rel === "last")) {
    nextplease.log('found rel="' + link.rel + '": ' + link.href);
    return "Last";
    }
    }
    return undefined;
    };

    nextplease.directionFromText = function (text, direction, prefetching) {
    var i;
    if (text) {
    var direction1 = nextplease.PhraseMap[text];
    if (direction1) {
    nextplease.log('found text match for "' + text + '"');
    return direction1;
    } else {
    if (prefetching) {
    for (i = 0; i < nextplease.directions.length; i++) {
    var direction2 = nextplease.directions[i];
    if (!nextplease.prefetched[direction2] && nextplease.RegExes[direction2].test(text)) {
    nextplease.log('found regex match for "' + text + '"');
    return direction2;
    }
    }
    } else if (nextplease.RegExes[direction].test(text)) {
    nextplease.log('found regex match for "' + text + '"');
    return direction;
    }
    }
    }
    return undefined;
    };

    nextplease.directionFromImage = function (imageElem, direction, prefetching) {
    var imgtext = imageElem.alt ? imageElem.alt : imageElem.title;

    var direction1 = nextplease.ImageMap[imageElem.src];
    if (direction1) {
    nextplease.log("found image match with URL " + imageElem.src);
    if (!prefetching || !nextplease.prefetched[direction1]) {
    return direction1;
    } else {
    return undefined;
    }
    } else {
    return nextplease.directionFromText(imgtext, direction, prefetching);
    }
    };

    nextplease.ignoreRels = function (curWindow) {
    var url = curWindow.location.href;
    // viewtopic.php is used in PHPBB, index.php in SMF
    // both (at least some versions) use <link> tags incorrectly
    return url.match(/(viewtopic|index)\.php/);
    };

    // Looks through all the links on the page
    // and tries to look for one whose text matches
    // one of the phrases or images. If so, it goes to/
    // the corresponding link.
    // pages with frames.
    nextplease.getLink = function (curWindow, direction) {
    var doc = curWindow.document;
    var i, j;
    var prefetching = (direction === "Prefetch");

    var direction1;

    var text;
    var isInt = /^\s*\[?\s*(\d+)\s*,?\]?\s*$/;

    var pageNumLinks = {Next: null, Prev: null, First: null, Last: null, Tmp: null};

    var firstPageNum;
    var currentPageNum = 100000; // Init to arbitrarily large num
    var tmpPageNum = 100000; // Init to arbitrarily large num
    var greatestNum = 1;
    var insideNumberBlock = false;

    var link;

    var temp;

    if (prefetching) {
    nextplease.logDetail("prefetching...");
    } else {
    nextplease.logDetail("looking for a link...");
    }

    var range = doc.createRange();

    var finishPrefetch = function () {
    return prefetching && nextplease.prefetched.Next && nextplease.prefetched.Prev &&
    nextplease.prefetched.First && nextplease.prefetched.Last;
    };

    if (nextplease.useSmartNext) {
    if (getBrowser().canGoForward) {
    nextplease.log("forward in history");
    link = [nextplease.ResultType.History, 1];
    if (direction === "Next") {
    return link;
    } else if (prefetching) {
    nextplease.prefetched.Next = link;
    }
    }

    if (getBrowser().canGoBack) {
    nextplease.log("back in history");
    link = [nextplease.ResultType.History, -1];
    if (direction === "Prev") {
    return link;
    } else if (prefetching) {
    nextplease.prefetched.Prev = link;
    }
    }
    }

    if (!nextplease.ignoreRels(curWindow)) {
    // Look for <LINK> tags
    nextplease.logDetail("checking <link> tags");
    var linktags = doc.getElementsByTagName("link"), linktagsNum = linktags.length;

    for (i = 0; i < linktagsNum; i++) {
    link = linktags[i];

    direction1 = nextplease.directionFromRel(link);
    if (direction === direction1) {
    return [nextplease.ResultType.Link, link];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Link, link];
    continue;
    }
    }
    }

    if (finishPrefetch()) { return true; }

    // Look for <A HREF="..."> tags
    nextplease.logDetail("checking <a> tags");
    var alinks = doc.links, alinksNum = alinks.length;
    var curWindowUrl = doc.location.href;

    // Search through each link
    for (i = 0; i < alinksNum; i++) {
    link = alinks[i];
    if (link.href === curWindowUrl) {
    continue;
    }

    direction1 = nextplease.directionFromRel(link);
    if (direction === direction1) {
    return [nextplease.ResultType.Link, link];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Link, link];
    continue;
    }

    range.selectNode(link);
    text = nextplease.Trim(range.toString());

    if (link.href.indexOf("/dictionary") < 0) {
    direction1 = nextplease.directionFromText(text, direction, prefetching);
    if (direction === direction1) {
    return [nextplease.ResultType.Link, link];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Link, link];
    continue;
    }
    }

    direction1 = nextplease.directionFromText(link.title, direction, prefetching);
    if (direction === direction1) {
    return [nextplease.ResultType.Link, link];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Link, link];
    continue;
    }

    // See if there's an image tag
    var imgElems = link.getElementsByTagName("img");
    if (imgElems.length > 0) {
    nextplease.logDetail("checking images inside <a>...</a>");
    // If the image matches, go to the URL.
    //alert(imgElems[0].src);
    direction1 = nextplease.directionFromImage(imgElems[0], direction, prefetching);
    if (direction === direction1) {
    return [nextplease.ResultType.Link, link];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Link, link];
    continue;
    }
    }

    if (finishPrefetch()) { return true; }

    var intMatches = isInt.exec(text);
    if (intMatches) {
    var linkPageNum = parseInt(intMatches[1], 10);
    // If the number is greater than nextplease.MAX_LINK_NUM
    // it probably doesn't have anything to do with
    // a next/prev link.
    // alert(linkPageNum);
    if (linkPageNum < nextplease.MAX_LINK_NUM) {
    nextplease.logDetail("found link number " + linkPageNum + ", checking...");
    // Try to figure out what the current page and
    // next/prev links are for pages that just have
    // numbered links like 1 2 x 4 5.
    // if (linkPageNum === 1) {
    // We're seeing a number link that is smaller
    // than a previous one so assume that we're
    // starting a new set of number links, and
    // count from the beginning.
    if (linkPageNum <= tmpPageNum) {
    insideNumberBlock = true;
    // alert(linkPageNum);
    // alert(currentPageNum);
    pageNumLinks.First = link;
    firstPageNum = linkPageNum;
    greatestNum = linkPageNum;

    pageNumLinks.Prev = null;
    pageNumLinks.Next = null;
    pageNumLinks.Last = null;
    currentPageNum = linkPageNum;

    pageNumLinks.First = link;
    greatestNum = linkPageNum;
    pageNumLinks.Last = null;
    //} else if (currentPageNum === linkPageNum) {
    // currentPageNum++;
    // pageNumLinks.Prev = link;
    } else if (tmpPageNum + 1 === linkPageNum) {
    pageNumLinks.Last = link;
    } else if (tmpPageNum + 2 === linkPageNum) {
    pageNumLinks.Prev = pageNumLinks.Tmp;
    pageNumLinks.Next = link;
    pageNumLinks.Last = link;
    } else if (insideNumberBlock) {
    pageNumLinks.Last = link;
    }

    tmpPageNum = linkPageNum;
    pageNumLinks.Tmp = link;
    }
    } else {
    insideNumberBlock = false;
    }
    }

    // next and prev are null so that means
    // we have a solid block of numbers, e.g. 3,4,5,6,...
    if ((pageNumLinks.Next === null) && (pageNumLinks.Prev === null)) {

    // If we start with 1, we're probably on last page.
    // Set prev to be lastPage
    if (firstPageNum === 1) {
    pageNumLinks.Prev = pageNumLinks.Last;
    pageNumLinks.Last = null;
    }
    // If we start with 2, we're probably on first page.
    // Set next to be first page
    if (firstPageNum === 2) {
    pageNumLinks.Next = pageNumLinks.First;
    pageNumLinks.First = null;
    }
    if (firstPageNum > 2) {
    pageNumLinks.Next = pageNumLinks.First;
    pageNumLinks.Prev = pageNumLinks.Last;
    }
    }

    if (pageNumLinks.First && pageNumLinks.Last && (pageNumLinks.First.text === pageNumLinks.Last.text)) {
    pageNumLinks.First = null;
    }

    nextplease.logDetail("first page seems to be " + pageNumLinks.First);
    nextplease.logDetail("previous page seems to be " + pageNumLinks.Prev);
    nextplease.logDetail("next page seems to be " + pageNumLinks.Next);
    nextplease.logDetail("last page seems to be " + pageNumLinks.Last);

    // Try to find a match using our number algorithm
    if (prefetching) {
    for (i = 0; i < nextplease.directions.length; i++) {
    direction1 = nextplease.directions[i];
    if (!nextplease.prefetched[direction1] && pageNumLinks[direction1]) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Link, pageNumLinks[direction1]];
    }
    }
    if (finishPrefetch()) { return true; }
    } else if (pageNumLinks[direction]) { return [nextplease.ResultType.Link, pageNumLinks[direction]]; }

    // Otherwise try looking for next/prev submit buttons
    // if the user allows it.
    if (nextplease.useSubmit) {
    temp = nextplease.getForm(direction, prefetching);
    if (temp) { return temp; }
    }

    // See if we can increment the URL to get to next/prev/first
    var galleryURL = nextplease.getGalleryNumberURL(curWindow, direction);
    // alert(galleryURL);
    // alert(nextplease.imageLocationArray.length);
    // if (galleryURL) {curWindow.open(galleryURL, "_self", "");}
    if (galleryURL && !prefetching) { return [nextplease.ResultType.URL, galleryURL]; }

    // None of it worked, so make a recursive call to
    // nextplease.getLink on the frame windows.
    if (nextplease.useFrames) {
    var frames = curWindow.frames, framesNum = frames.length;
    for (j = 0; j < framesNum; j++) {
    temp = nextplease.getLink(frames[j], direction);
    if (temp) { return temp; }
    }
    }
    return finishPrefetch();
    };

    nextplease.getGalleryNumberURL = function (curWindow, direction) {
    nextplease.logDetail("trying to change the URL by a suitable number");
    var i;
    // alert(nextplease.imageLocationArray);
    var matches = nextplease.RegExes.Gallery.exec(decodeURI(curWindow.location.href));
    var prefixUrl, suffixUrl, numberUrlPartLength, curNumber, urlNumber, padStr, linkUrl;
    var urlsCache = nextplease.imageURLsCache;

    if (matches && (matches.length === 4)) {
    prefixUrl = matches[1];
    numberUrlPartLength = matches[2].length;
    suffixUrl = matches[3];
    nextplease.logDetail("URL prefix is " + prefixUrl + ", URL suffix is " + suffixUrl);
    if (direction === "Next") {
    curNumber = parseInt(matches[2], 10);
    for (i = 1; i < nextplease.MAX_GALLERY_GAP; i++) {
    urlNumber = curNumber + i;
    padStr = nextplease.padNumber(numberUrlPartLength, urlNumber);
    linkUrl = prefixUrl + padStr + suffixUrl;
    if (urlsCache.map[linkUrl]) {
    nextplease.log("gallery URL found: " + linkUrl);
    return linkUrl;
    }
    }
    urlNumber = curNumber + 1;
    padStr = nextplease.padNumber(numberUrlPartLength, urlNumber);
    linkUrl = prefixUrl + padStr + suffixUrl;
    return linkUrl;
    } else if (direction === "Prev") {
    curNumber = parseInt(matches[2], 10);
    var maxToSubtract = Math.min(curNumber, nextplease.MAX_GALLERY_GAP);
    for (i = 1; i <= maxToSubtract; i++) {
    urlNumber = curNumber - i;
    padStr = nextplease.padNumber(numberUrlPartLength, urlNumber);
    linkUrl = prefixUrl + padStr + suffixUrl;
    if (urlsCache.map[linkUrl]) {
    nextplease.log("gallery URL found: " + linkUrl);
    return linkUrl;
    }
    }
    urlNumber = curNumber - 1;
    if (urlNumber >= 0) {
    padStr = nextplease.padNumber(numberUrlPartLength, urlNumber);
    linkUrl = prefixUrl + padStr + suffixUrl;
    nextplease.log("gallery URL found: " + linkUrl);
    return linkUrl;
    }
    } else if (direction === "First") {
    urlNumber = 1;
    padStr = nextplease.padNumber(numberUrlPartLength, urlNumber);
    linkUrl = prefixUrl + padStr + suffixUrl;
    nextplease.log("gallery URL found: " + linkUrl);
    return linkUrl;
    }
    }
    return undefined;
    };

    nextplease.padNumber = function (length, newNum) {
    var padStr = "" + newNum;
    var padLen = length - padStr.length;
    var i;
    for (i = 0; i < padLen; i++) {
    padStr = "0" + padStr;
    }
    return padStr;
    };

    // Look through all the HTML inputs for submit buttons
    // that have a value that matches our phrases. If it
    // finds a match, it calls input.click()
    nextplease.getForm = function (direction, prefetching) {
    var finishPrefetch = function () {
    return prefetching && nextplease.prefetched.Next && nextplease.prefetched.Prev &&
    nextplease.prefetched.First && nextplease.prefetched.Last;
    };

    var i, text, direction1;

    nextplease.logDetail("looking for submit buttons");

    // Probably would be a little faster to
    // only check forms, but I'm getting problems
    // with them. I'm not sure if it's only on
    // malformed HTML pages, or if it's a Firefox bug.
    var inputs = window.content.document.getElementsByTagName("input"), inputsNum = inputs.length;

    for (i = 0; i < inputsNum; i++) {
    var input = inputs[i];
    text = nextplease.Trim(input.value);

    direction1 = nextplease.directionFromText(text, direction, prefetching);
    if (direction === direction1) {
    return [nextplease.ResultType.Input, input];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Input, input];
    }
    }

    var buttons = window.content.document.getElementsByTagName("button"), buttonsNum = buttons.length;
    var range = document.createRange();

    for (i = 0; i < buttonsNum; i++) {
    var button = buttons[i];
    range.selectNode(button);
    text = nextplease.Trim(range.toString());

    direction1 = nextplease.directionFromText(text, direction, prefetching);
    if (direction === direction1) {
    return [nextplease.ResultType.Input, button];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Input, button];
    }

    var imgElems = buttons[i].getElementsByTagName("img");
    if (imgElems.length > 0) {
    nextplease.logDetail("checking images inside <a>...</a>");
    // If the image matches, go to the URL.
    //alert(imgElems[0].src);
    direction1 = nextplease.directionFromImage(imgElems[0], direction, prefetching);
    if (direction === direction1) {
    return [nextplease.ResultType.Input, button];
    } else if (direction1 && prefetching) {
    nextplease.prefetched[direction1] = [nextplease.ResultType.Input, button];
    continue;
    }
    }
    }

    return finishPrefetch();
    };

    nextplease.openResult = function (curWindow, result) {
    if (nextplease.highlightColor) {
    nextplease.highlight(result, nextplease.highlightColor);
    }

    switch (result[0]) {
    case nextplease.ResultType.URL:
    var url = result[1];
    curWindow.open(url, "_self", "");
    return true;
    case nextplease.ResultType.Link:
    var linkNode = result[1];
    if (!linkNode) {
    nextplease.logError("Tried to open undefined link, this should never happen!");
    return false;
    }
    // If it's got an onclick attr, then try to
    // simulate a mouse click to activate link.
    if (linkNode.hasAttribute("onclick")) {
    // alert(linkNode.getAttribute("onclick"));
    var e = document.createEvent("MouseEvents");
    // e.initMouseEvent("click", 1, 1, window, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, linkNode);

    // From https://developer.mozilla.org/en/DOM/event.initMouseEvent
    e.initMouseEvent("click", true, true, window,
    0, 0, 0, 0, 0, false, false, false, false, 0, null);
    linkNode.dispatchEvent(e);
    } else {
    curWindow.open(linkNode.href, "_self", "");
    }
    nextplease.gotHereUsingNextplease = true;
    return true;
    case nextplease.ResultType.Input:
    var input = result[1];
    input.click();
    return true;
    case nextplease.ResultType.History:
    var num = result[1];
    curWindow.history.go(num);
    return true;
    }
    };

    // Looks through all the links and finds the link
    // that matches the linkNum (an integer between
    // 1 and 9). If it finds a match, it will go to
    // that link.
    nextplease.openNumberedLink = function (curWindow, linkNum) {
    var text;
    var isInt = /^\s*\[?\s*(\d+)\s*\]?\s*$/;
    var i, j;

    nextplease.logDetail("looking for a link numbered " + linkNum);
    var alinks = curWindow.document.links;

    // Search through each link
    for (i = alinks.length - 1; i >= 0; i--) {
    var link = alinks[i];

    text = nextplease.Trim(link.text);

    var intMatches = isInt.exec(text);
    if (intMatches) {
    var linkPageNum = parseInt(intMatches[1], 10);
    if (linkPageNum === linkNum) {
    return nextplease.openResult(curWindow, [nextplease.ResultType.Link, link]);
    //curWindow.open(link.href, "_self", "");
    //return true;
    }
    }
    }

    if (nextplease.useFrames) {
    var frames = curWindow.frames;
    for (j = 0; j < frames.length; j++) {
    if (nextplease.openNumberedLink(frames[j], linkNum)) { return true; }
    }
    }

    nextplease.notifyLinkNotFound();
    return false;
    };

    nextplease.openDirection = function (direction) {
    var result = nextplease.prefetched[direction] || nextplease.getLink(window.content, direction);
    if (result) {
    return nextplease.openResult(window.content, result);
    } else {
    nextplease.notifyLinkNotFound();
    return false;
    }
    };

    nextplease.openNextLink = function () {
    nextplease.logDetail("Looking for next link");
    return nextplease.openDirection("Next");
    };

    nextplease.openPrevLink = function () {
    nextplease.logDetail("Looking for prev link");
    return nextplease.openDirection("Prev");
    };

    nextplease.openFirstLink = function () {
    nextplease.logDetail("Looking for first link");
    return nextplease.openDirection("First");
    };

    nextplease.openLastLink = function () {
    nextplease.logDetail("Looking for last link");
    return nextplease.openDirection("Last");
    };

    nextplease.prefetch = function () {
    nextplease.getLink(window.content, "Prefetch");
    if (nextplease.highlightPrefetchedColor) {
    nextplease.highlight(nextplease.prefetched.Next, nextplease.highlightPrefetchedColor);
    nextplease.highlight(nextplease.prefetched.Prev, nextplease.highlightPrefetchedColor);
    nextplease.highlight(nextplease.prefetched.First, nextplease.highlightPrefetchedColor);
    nextplease.highlight(nextplease.prefetched.Last, nextplease.highlightPrefetchedColor);
    }
    };

    // old names retained because of http://www.mousegestures.org/exchange/details.php?mappingID=295
    nextplease.getNextLink = nextplease.openNextLink;
    nextplease.getPrevLink = nextplease.openPrevLink;
    nextplease.getFirstLink = nextplease.openFirstLink;
    nextplease.getLastLink = nextplease.openLastLink;

    nextplease.highlight = function (result, color) {
    if (result) {
    var element;
    switch (result[0]) {
    case nextplease.ResultType.URL:
    break;
    case nextplease.ResultType.Link:
    case nextplease.ResultType.Input:
    element = result[1];
    break;
    // case nextplease.ResultType.History:
    // TODO The forward button doesn't get unhighlighted correctly
    // when it becomes disabled.
    // switch (result[1]) {
    // case 1: element = document.getElementById("forward-button"); break;
    // case -1: element = document.getElementById("back-button"); break;
    // }
    }
    if (element) {
    if (!nextplease.highlighted_old_styles[element]) {
    nextplease.highlighted_old_styles[element] = element.style;
    }
    element.style.background = color;
    }
    }
    };

    nextplease.unhighlight = function () {
    var element;
    for (element in nextplease.highlighted_old_styles) {
    element.style = nextplease.highlighted_old_styles[element];
    delete nextplease.highlighted_old_styles[element];
    }
    };

    nextplease.showHideMenuItems = function () {
    var i, elem;
    var direction, numDirections = nextplease.directions.length;
    var propertyKey;

    var getCMItemByDir = function (direction) {
    var elemId = "nextPleaseAddRemove" + direction;
    return document.getElementById(elemId);
    };

    if (gContextMenu) {
    gContextMenu.showItem("nextplease.topMenu", nextplease.useContextMenu);

    if (nextplease.useContextMenu) {
    if (!gContextMenu.onLink && !gContextMenu.onImage) {
    gContextMenu.showItem("nextplease-separator", false);
    gContextMenu.showItem("nextpleaseTextOrURL", false);
    gContextMenu.showItem("nextPleaseAddRemoveNext", false);
    gContextMenu.showItem("nextPleaseAddRemovePrev", false);
    gContextMenu.showItem("nextPleaseAddRemoveFirst", false);
    gContextMenu.showItem("nextPleaseAddRemoveLast", false);
    } else {
    var textOrUrl = nextplease.getTextOrUrlUnderPopup();
    var phraseOrImage = gContextMenu.onImage ? "Image" : "Phrase";
    document.getElementById("nextpleaseTextOrURL").label = gContextMenu.onImage ? textOrUrl : '"' + textOrUrl + '"';
    var directionForItem = nextplease[phraseOrImage + "Map"][textOrUrl];
    for (i = 0; i < numDirections; i++) {
    direction = nextplease.directions[i];
    elem = getCMItemByDir(direction);
    propertyKey = (direction === directionForItem ? "remove" : "add") + phraseOrImage + "ContextMenu";
    // alert("str = " + nextplease.strings.GetStringFromName(propertyKey) + "; param = " + nextplease.getDirectionString(direction));
    elem.label = nextplease.strings.formatStringFromName(propertyKey, [nextplease.getDirectionString(direction)], 1);
    }

    gContextMenu.showItem("nextplease-separator", true);
    gContextMenu.showItem("nextpleaseTextOrURL", true);
    gContextMenu.showItem("nextPleaseAddRemoveNext", true);
    gContextMenu.showItem("nextPleaseAddRemovePrev", true);
    gContextMenu.showItem("nextPleaseAddRemoveFirst", true);
    gContextMenu.showItem("nextPleaseAddRemoveLast", true);
    }
    }
    }
    };

    nextplease.addRemoveCM = function (direction) {
    var phraseOrImage = gContextMenu.onImage ? "Image" : "Phrase";
    var textOrUrl = nextplease.getTextOrUrlUnderPopup();
    var currentDirection = nextplease[phraseOrImage + "Map"][textOrUrl];
    if (currentDirection === direction) { // removing
    if (nextplease.confirmRemove(phraseOrImage, textOrUrl, currentDirection)) {
    nextplease.addOrRemovePhraseOrImage(currentDirection, textOrUrl, phraseOrImage, "Remove");
    }
    } else if (currentDirection) { // replacing
    if (nextplease.confirmReplace(phraseOrImage, textOrUrl, currentDirection, direction)) {
    nextplease.addOrRemovePhraseOrImage(currentDirection, textOrUrl, phraseOrImage, "Remove");
    nextplease.addOrRemovePhraseOrImage(direction, textOrUrl, phraseOrImage, "Add");
    }
    } else {
    nextplease.addOrRemovePhraseOrImage(direction, textOrUrl, phraseOrImage, "Add");
    }
    };

    nextplease.addOrRemovePhraseOrImage = function (direction, textOrUrl, whichPhraseOrImage, whichAddOrRemove) {
    var prefname, map, logMessage;
    if (whichPhraseOrImage === "Phrase") {
    prefname = direction.toLowerCase() + "phrase";
    map = nextplease.PhraseMap;
    logMessage = (whichAddOrRemove === "Add") ?
    "adding phrase '" + textOrUrl + "' to " + prefname :
    "removing phrase '" + textOrUrl + "' from " + prefname;
    nextplease.logDetail(logMessage);
    } else {
    prefname = direction.toLowerCase() + "image";
    map = nextplease.ImageMap;
    logMessage = (whichAddOrRemove === "Add") ?
    "adding image URL " + textOrUrl + " to " + prefname :
    "removing image URL " + textOrUrl + " from " + prefname;
    nextplease.logDetail(logMessage);
    }

    if (whichAddOrRemove === "Add") {
    if (map[textOrUrl] !== direction) {
    map[textOrUrl] = direction;
    nextplease.addToPrefs(prefname, textOrUrl);
    } else {
    nextplease.log("error: already present in the list!");
    }
    } else {
    if (map[textOrUrl] === direction) {
    delete map[textOrUrl];
    nextplease.removeFromPrefs(prefname, textOrUrl);
    } else {
    nextplease.log("error: not present in the list!");
    }
    }
    };

    nextplease.getTextOrUrlUnderPopup = function () {
    if (gContextMenu && gContextMenu.onLink) {
    if (gContextMenu.onImage) {
    return document.popupNode.src;
    } else {
    var range = document.createRange();
    range.selectNode(document.popupNode);
    return nextplease.Trim(range.toString());
    }
    } else {
    return undefined;
    }
    };

    nextplease.addToPrefs = function (prefbranch, text) {
    nextplease.logDetail("adding " + text + " to " + prefbranch);
    var tempprefname = prefbranch + '.expr0';
    var prefvalue = nextplease.getUnicharPref(tempprefname);
    var resultprefvalue = prefvalue + "|" + text.replace(/\|/g, "&pipe;");

    nextplease.setUnicharPref(tempprefname, resultprefvalue);
    };

    nextplease.removeFromPrefs = function (prefbranch, text) {
    nextplease.logDetail("removing " + text + " from " + prefbranch);
    var tempprefname = prefbranch + '.expr0';
    var prefvalue = nextplease.getUnicharPref(tempprefname);
    var text1 = new RegExp("\\|" + text.replace(/\|/g, "&pipe;") + "(?=\\||$)", "g");
    var resultprefvalue = prefvalue.replace(text1, "");

    nextplease.setUnicharPref(tempprefname, resultprefvalue);
    };

    nextplease.linkNumber = 0;

    nextplease.handleNumberShortcut = function (digit) {
    // no effect if nextplease.NumberShortcutTimer is invalid
    clearTimeout(nextplease.NumberShortcutTimer);

    nextplease.linkNumber = nextplease.linkNumber * 10 + digit;

    nextplease.showInStatusBar(
    nextplease.strings.formatStringFromName("lookingForNumberedLink", [nextplease.linkNumber], 1));

    nextplease.NumberShortcutTimer = setTimeout(nextplease.finishNumberShortcut, 500);
    };

    nextplease.finishNumberShortcut = function () {
    nextplease.openNumberedLink(window.content, nextplease.linkNumber);
    nextplease.linkNumber = 0;
    clearTimeout(nextplease.NumberShortcutTimer);
    };