Show
Ignore:
Timestamp:
11/29/08 06:52:31 (6 weeks ago)
Author:
hogelog
Message:
  • follow 2.0pre Hints mode.
Files:
1 modified

Legend:

Unmodified
Added
Removed
  • lang/javascript/vimperator-plugins/trunk/char-hints-mod.js

    r22654 r25307  
    1 // Vimperator plugin: 'Char Hints Mod' 
    2 // Last Change: 06-Apr-2008. Jan 2008 
    3 // License: GPL 
    4 // Version: 0.3 
    5 // Maintainer: Trapezoid <trapezoid.g@gmail.com> 
    6  
    7 // This file is a tweak based on char-hints.js by: 
    8 // (c) 2008: marco candrian <mac@calmar.ws> 
    9 // This file is a tweak based on hints.js by: 
    10 // (c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net> 
    11  
    12 // Tested with vimperator 0.6pre from 2008-03-07 
    13 // (won't work with older versions) 
    14  
    15 // INSTALL: put this file into ~/.vimperator/plugin/  (create folders if necessary) 
    16 // and restart firefox or :source that file 
    17  
    18 // plugin-setup 
    19 liberator.plugins.charhints = {}; 
    20 var chh = liberator.plugins.charhints; 
    21  
    22 //<<<<<<<<<<<<<<<< EDIT USER SETTINGS HERE 
    23  
    24 //chh.hintchars = "asdfjkl";      // chars to use for generating hints 
    25 chh.hintchars = "hjklasdfgyuiopqwertnmzxcvb";      // chars to use for generating hints 
    26  
    27 chh.showcapitals = true;        // show capital letters, even with lowercase hintchars 
    28 chh.timeout = 500;              // in 1/000sec; when set to 0, press <RET> to follow 
    29  
    30 chh.fgcolor = "black";          // hints foreground color 
    31 chh.bgcolor = "yellow";         // hints background color 
    32 chh.selcolor = "#99FF00";       // selected/active hints background color 
    33  
    34 chh.mapNormal = "f";            // trigger normal mode with... 
    35 chh.mapNormalNewTab = "F";      // trigger and open in new tab 
    36 chh.mapExtended = ";";          // open in extended mode (see notes below) 
    37  
    38 chh.hinttags = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " + 
    39 "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " + 
    40 "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " + 
    41 "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | " + 
    42 "//xhtml:button | //xhtml:select"; 
    43  
    44 //======================================== 
    45 //  extended hints mode arguments 
    46 // 
    47 // ; to focus a link and hover it with the mouse 
    48 // a to save its destination (prompting for save location) 
    49 // s to save its destination 
    50 // o to open its location in the current tab 
    51 // t to open its location in a new tab 
    52 // O to open its location in an :open query 
    53 // T to open its location in a :tabopen query 
    54 // v to view its destination source 
    55 // w to open its destination in a new window 
    56 // W to open its location in a :winopen query 
    57 // y to yank its location 
    58 // Y to yank its text description 
    59  
    60 // variables etc//{{{ 
    61  
    62  
    63 // ignorecase when showcapitals = true 
    64 // (input keys on onEvent gets lowercased too 
    65  
    66 if (chh.showcapitals) 
    67     chh.hintchars = chh.hintchars.toLowerCase(); 
    68  
    69  
    70 chh.submode    = ""; // used for extended mode, can be "o", "t", "y", etc. 
    71 chh.hintString = ""; // the typed string part of the hint is in this string 
    72 chh.hintNumber = 0;  // only the numerical part of the hint 
    73 chh.usedTabKey = false; // when we used <Tab> to select an element 
    74  
    75 chh.hints = []; 
    76 chh.validHints = []; // store the indices of the "hints" array with valid elements 
    77  
    78 chh.activeTimeout = null;  // needed for hinttimeout > 0 
    79 chh.canUpdate = false; 
    80  
    81 // used in number2hintchars 
    82 chh.transval = {"0":0,  "1":1, "2":2,  "3":3,  "4":4,  "5":5,  "6":6,  "7":7,  "8":8,  "9":9,  "a":10, "b":11, 
    83                 "c":12, "d":13,"e":14, "f":15, "g":16, "h":17, "i":18, "j":19, "k":20, "l":21, "m":22, "n":23, 
    84                 "o":24, "p":25,"q":26, "r":27, "s":28, "t":29, "u":30, "v":31, "w":32, "x":33, "y":34, "z":35}; 
    85  
    86 // used in hintchars2number 
    87 chh.conversion = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
    88  
    89 // keep track of the documents which we generated the hints for 
    90 // docs = { doc: document, start: start_index in hints[], end: end_index in hints[] } 
    91 chh.docs = []; 
    92 //}}} 
    93 // reset all important variables 
    94 chh.reset = function ()//{{{ 
     1/***** BEGIN LICENSE BLOCK ***** {{{ 
     2Version: MPL 1.1/GPL 2.0/LGPL 2.1 
     3 
     4The contents of this file are subject to the Mozilla Public License Version 
     51.1 (the "License"); you may not use this file except in compliance with 
     6the License. You may obtain a copy of the License at 
     7http://www.mozilla.org/MPL/ 
     8 
     9Software distributed under the License is distributed on an "AS IS" basis, 
     10WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 
     11for the specific language governing rights and limitations under the 
     12License. 
     13 
     14(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net> 
     15 
     16Alternatively, the contents of this file may be used under the terms of 
     17either the GNU General Public License Version 2 or later (the "GPL"), or 
     18the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 
     19in which case the provisions of the GPL or the LGPL are applicable instead 
     20of those above. If you wish to allow use of your version of this file only 
     21under the terms of either the GPL or the LGPL, and not to allow others to 
     22use your version of this file under the terms of the MPL, indicate your 
     23decision by deleting the provisions above and replace them with the notice 
     24and other provisions required by the GPL or the LGPL. If you do not delete 
     25the provisions above, a recipient may use your version of this file under 
     26the terms of any one of the MPL, the GPL or the LGPL. 
     27}}} ***** END LICENSE BLOCK *****/ 
     28/** 
     29 * ==VimperatorPlugin== 
     30 * @name            char-hints-mod.js 
     31 * @description     Character Hints mode 
     32 * ==/VimperatorPlugin== 
     33 * 
     34 * It's based on vimperator-2.0pre(2008/11/28) hints.js 
     35 * 
     36 **/ 
     37(function () //{{{ 
    9538{ 
    96     liberator.statusline.updateInputBuffer(""); 
    97     chh.hintString = ""; 
    98     chh.hintNumber = 0; 
    99     chh.usedTabKey = false; 
    100     chh.hints = []; 
    101     chh.validHints = []; 
    102     chh.canUpdate = false; 
    103     chh.docs = []; 
    104  
    105     if (chh.activeTimeout) 
    106         clearTimeout(chh.activeTimeout); 
    107     chh.activeTimeout = null; 
    108 } 
    109 //}}} 
    110 chh.updateStatusline = function ()//{{{ 
    111 { 
    112     liberator.statusline.updateInputBuffer(("") + 
    113             (chh.hintString ? "\"" + chh.hintString + "\"" : "") + 
    114             (chh.hintNumber > 0 ? " <" + chh.hintNumber + ">" : "")); 
    115 } 
    116 //}}} 
    117 // this function 'click' an element, which also works 
    118 // for javascript links 
    119 chh.hintchars2number = function (hintstr)//{{{ 
    120 { 
    121     // convert into 'normal number then make it decimal-based 
    122  
    123     var converted = ""; 
    124  
    125     // translate users hintchars into a number (chh.conversion) 0 -> 0, 1 -> 1, ... 
    126     for (let i = 0, l = hintstr.length; i < l; i++) 
    127         converted += "" + chh.conversion[chh.hintchars.indexOf(hintstr[i])]; 
    128  
    129     // add one, since hints begin with 0; 
    130  
    131     return parseInt(converted, chh.hintchars.length); // hintchars.length is the base/radix 
    132 } 
    133 //}}} 
    134 chh.number2hintchars = function (nr)//{{{ 
    135 { 
    136     var oldnr = nr; 
    137     var converted = ""; 
    138     var tmp = ""; 
    139  
    140     tmp = nr.toString(chh.hintchars.length); // hintchars.length is the base/radix) 
    141  
    142     // translate numbers into users hintchars 
    143     // tmp might be 2e -> (chh.transval) 2 and 14 -> (chh.hintchars) according hintchars 
    144  
    145     for (let i = 0, l = tmp.length; i < l; i++) 
    146         converted += "" + chh.hintchars[chh.transval[tmp[i]]]; 
    147  
    148     return converted; 
    149 } 
    150 //}}} 
    151 chh.openHint = function (where)//{{{ 
    152 { 
    153     if (chh.validHints.length < 1) 
    154         return false; 
    155  
    156     var x = 1, y = 1; 
    157     var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; 
    158     var elemTagName = elem.localName.toLowerCase(); 
    159     elem.focus(); 
    160  
    161     liberator.buffer.followLink(elem, where); 
    162     return true; 
    163 } 
    164 //}}} 
    165 chh.focusHint = function ()//{{{ 
    166 { 
    167     if (chh.validHints.length < 1) 
    168         return false; 
    169  
    170     var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; 
    171     var doc = window.content.document; 
    172     var elemTagName = elem.localName.toLowerCase(); 
    173     if (elemTagName == "frame" || elemTagName == "iframe") 
    174     { 
    175         elem.contentWindow.focus(); 
    176         return false; 
    177     } 
    178     else 
    179     { 
    180         elem.focus(); 
    181     } 
    182  
    183     var evt = doc.createEvent("MouseEvents"); 
    184     var x = 0; 
    185     var y = 0; 
    186     // for imagemap 
    187     if (elemTagName == "area") 
    188     { 
    189         [x, y] = elem.getAttribute("coords").split(","); 
    190         x = Number(x); 
    191         y = Number(y); 
    192     } 
    193  
    194     evt.initMouseEvent("mouseover", true, true, doc.defaultView, 1, x, y, 0, 0, 0, 0, 0, 0, 0, null); 
    195     elem.dispatchEvent(evt); 
    196 } 
    197 //}}} 
    198 chh.yankHint = function (text)//{{{ 
    199 { 
    200     if (chh.validHints.length < 1) 
    201         return false; 
    202  
    203     var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; 
    204     var loc; 
    205     if (text) 
    206         loc = elem.textContent; 
    207     else 
    208         loc = elem.href; 
    209  
    210     liberator.copyToClipboard(loc); 
    211     liberator.echo("Yanked " + loc, liberator.commandline.FORCE_SINGLELINE); 
    212 } 
    213 //}}} 
    214 chh.saveHint = function (skipPrompt)//{{{ 
    215 { 
    216     if (chh.validHints.length < 1) 
    217         return false; 
    218  
    219     var elem = chh.validHints[chh.hintNumber - 1] || chh.validHints[0]; 
    220  
    221     try 
    222     { 
    223         liberator.buffer.saveLink(elem,skipPrompt); 
    224     } 
    225     catch (e) 
    226     { 
    227         liberator.echoerr(e); 
    228     } 
    229 } 
    230 //}}} 
    231 chh.generate = function (win)//{{{ 
    232 { 
    233     var startDate = Date.now(); 
    234  
    235     if (!win) 
    236         win = window.content; 
    237  
    238     var doc = win.document; 
    239     var height = win.innerHeight; 
    240     var width  = win.innerWidth; 
    241     var scrollX = doc.defaultView.scrollX; 
    242     var scrollY = doc.defaultView.scrollY; 
    243  
    244     var baseNodeAbsolute = doc.createElementNS("http://www.w3.org/1999/xhtml", "span"); 
    245     baseNodeAbsolute.style.backgroundColor = "red"; 
    246     baseNodeAbsolute.style.color = "white"; 
    247     baseNodeAbsolute.style.position = "absolute"; 
    248     baseNodeAbsolute.style.fontSize = "10px"; 
    249     baseNodeAbsolute.style.fontWeight = "bold"; 
    250     baseNodeAbsolute.style.lineHeight = "10px"; 
    251     baseNodeAbsolute.style.padding = "0px 1px 0px 0px"; 
    252     baseNodeAbsolute.style.zIndex = "10000001"; 
    253     baseNodeAbsolute.style.display = "none"; 
    254     baseNodeAbsolute.className = "vimperator-hint"; 
    255  
    256     var elem, tagname, text, span, rect; 
    257     var res = liberator.buffer.evaluateXPath(chh.hinttags, doc, null, true); 
    258     liberator.log("shints: evaluated XPath after: " + (Date.now() - startDate) + "ms"); 
    259  
    260     var fragment = doc.createDocumentFragment(); 
    261     var start = chh.hints.length; 
    262     while ((elem = res.iterateNext()) != null) 
    263     { 
    264         // TODO: for frames, this calculation is wrong 
    265         rect = elem.getBoundingClientRect(); 
    266         if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0) 
    267             continue; 
    268  
    269         rect = elem.getClientRects()[0]; 
    270         if (!rect) 
    271             continue; 
    272  
    273         // TODO: mozilla docs recommend localName instead of tagName 
    274         tagname = elem.tagName.toLowerCase(); 
    275         text = ""; 
    276         span = baseNodeAbsolute.cloneNode(true); 
    277         span.style.left = (rect.left + scrollX) + "px"; 
    278         span.style.top = (rect.top + scrollY) + "px"; 
    279         fragment.appendChild(span); 
    280  
    281         chh.hints.push([elem, text, span, null, elem.style.backgroundColor, elem.style.color]); 
    282     } 
    283  
    284     doc.body.appendChild(fragment); 
    285     chh.docs.push({ doc: doc, start: start, end: chh.hints.length - 1 }); 
    286  
    287     // also generate hints for frames 
    288     for (let i = 0; i < win.frames.length; i++) 
    289         chh.generate(win.frames[i]); 
    290  
    291     liberator.log("shints: generate() completed after: " + (Date.now() - startDate) + "ms"); 
    292     return true; 
    293 } 
    294 //}}} 
    295 // TODO: make it aware of imgspans 
    296 chh.showActiveHint = function (newID, oldID)//{{{ 
    297 { 
    298     var oldElem = chh.validHints[oldID - 1]; 
    299     if (oldElem) 
    300         oldElem.style.backgroundColor = chh.bgcolor; 
    301  
    302     var newElem = chh.validHints[newID - 1]; 
    303     if (newElem) 
    304         newElem.style.backgroundColor = chh.selcolor; 
    305 } 
    306 //}}} 
    307 chh.showHints = function ()//{{{ 
    308 { 
    309     var startDate = Date.now(); 
    310     var win = window.content; 
    311     var height = win.innerHeight; 
    312     var width  = win.innerWidth; 
    313  
    314  
    315     var elem, tagname, text, rect, span, imgspan; 
    316     var hintnum = 1; 
    317     //var findTokens = chh.hintString.split(/ +/); 
    318     var activeHint = chh.hintNumber || 1; 
    319     chh.validHints = []; 
    320  
    321     for (let j = 0; j < chh.docs.length; j++) 
    322     { 
    323         let doc = chh.docs[j].doc; 
    324         let start = chh.docs[j].start; 
    325         let end = chh.docs[j].end; 
    326         let scrollX = doc.defaultView.scrollX; 
    327         let scrollY = doc.defaultView.scrollY; 
    328  
    329 outer: 
    330         for (let i = start; i <= end; i++) 
    331         { 
    332             [elem, , span, imgspan] = chh.hints[i]; 
    333             text = ""; 
    334  
    335             if (elem.firstChild && elem.firstChild.tagName == "IMG") 
    336             { 
    337                 if (!imgspan) 
     39    //////////////////////////////////////////////////////////////////////////////// 
     40    ////////////////////// PRIVATE SECTION ///////////////////////////////////////// 
     41    /////////////////////////////////////////////////////////////////////////////{{{ 
     42 
     43    const ELEM = 0, TEXT = 1, SPAN = 2, IMGSPAN = 3; 
     44 
     45    var myModes = config.browserModes; 
     46 
     47    var hintMode; 
     48    var submode    = ""; // used for extended mode, can be "o", "t", "y", etc. 
     49    var hintString = ""; // the typed string part of the hint is in this string 
     50    var hintNumber = 0;  // only the numerical part of the hint 
     51    var usedTabKey = false; // when we used <Tab> to select an element 
     52    var prevInput = "";    // record previous user input type, "text" || "number" 
     53 
     54    // hints[] = [elem, text, span, imgspan, elem.style.backgroundColor, elem.style.color] 
     55    var pageHints = []; 
     56    var validHints = []; // store the indices of the "hints" array with valid elements 
     57 
     58    var escapeNumbers = false; // escape mode for numbers. true -> treated as hint-text 
     59    var activeTimeout = null;  // needed for hinttimeout > 0 
     60    var canUpdate = false; 
     61 
     62    // keep track of the documents which we generated the hints for 
     63    // docs = { doc: document, start: start_index in hints[], end: end_index in hints[] } 
     64    var docs = []; 
     65 
     66    const Mode = new Struct("prompt", "action", "tags"); 
     67    Mode.defaultValue("tags", function () function () options.hinttags); 
     68    function extended() options.extendedhinttags; 
     69    const hintModes = { 
     70        ";": Mode("Focus hint",                          function (elem) buffer.focusElement(elem),                             extended), 
     71        a: Mode("Save hint with prompt",                 function (elem) buffer.saveLink(elem, false)), 
     72        s: Mode("Save hint",                                     function (elem) buffer.saveLink(elem, true)), 
     73        o: Mode("Follow hint",                                   function (elem) buffer.followLink(elem, liberator.CURRENT_TAB)), 
     74        t: Mode("Follow hint in a new tab",              function (elem) buffer.followLink(elem, liberator.NEW_TAB)), 
     75        b: Mode("Follow hint in a background tab",       function (elem) buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB)), 
     76        v: Mode("View hint source",                              function (elem, loc) buffer.viewSource(loc, false),                    extended), 
     77        V: Mode("View hint source",                              function (elem, loc) buffer.viewSource(loc, true),                     extended), 
     78        w: Mode("Follow hint in a new window",   function (elem) buffer.followLink(elem, liberator.NEW_WINDOW),         extended), 
     79 
     80        "?": Mode("Show information for hint",   function (elem) buffer.showElementInfo(elem),                          extended), 
     81        O: Mode("Open location based on hint",   function (elem, loc) commandline.open(":", "open " + loc, modes.EX)), 
     82        T: Mode("Open new tab based on hint",    function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)), 
     83        W: Mode("Open new window based on hint", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)), 
     84        y: Mode("Yank hint location",                    function (elem, loc) util.copyToClipboard(loc, true)), 
     85        Y: Mode("Yank hint description",                 function (elem) util.copyToClipboard(elem.textContent || "", true),    extended), 
     86    }; 
     87 
     88    // reset all important variables 
     89    function reset() 
     90    { 
     91        statusline.updateInputBuffer(""); 
     92        hintString = ""; 
     93        hintNumber = 0; 
     94        usedTabKey = false; 
     95        prevInput = ""; 
     96        pageHints = []; 
     97        validHints = []; 
     98        canUpdate = false; 
     99        docs = []; 
     100        escapeNumbers = false; 
     101 
     102        if (activeTimeout) 
     103            clearTimeout(activeTimeout); 
     104        activeTimeout = null; 
     105    } 
     106 
     107    function updateStatusline() 
     108    { 
     109        statusline.updateInputBuffer((escapeNumbers ? mappings.getMapLeader() : "") + (hintNumber || "")); 
     110    } 
     111 
     112    function generate(win) 
     113    { 
     114        if (!win) 
     115            win = window.content; 
     116 
     117        var doc = win.document; 
     118        var height = win.innerHeight; 
     119        var width  = win.innerWidth; 
     120        var scrollX = doc.defaultView.scrollX; 
     121        var scrollY = doc.defaultView.scrollY; 
     122 
     123        var baseNodeAbsolute = util.xmlToDom(<span highlight="Hint"/>, doc); 
     124 
     125        var elem, tagname, text, span, rect; 
     126        var res = buffer.evaluateXPath(hintMode.tags(), doc, null, true); 
     127 
     128        var fragment = util.xmlToDom(<div highlight="hints"/>, doc); 
     129        var start = pageHints.length; 
     130        for (let elem in res) 
     131        { 
     132            // TODO: for iframes, this calculation is wrong 
     133            rect = elem.getBoundingClientRect(); 
     134            if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0) 
     135                continue; 
     136 
     137            rect = elem.getClientRects()[0]; 
     138            if (!rect) 
     139                continue; 
     140 
     141            var computedStyle = doc.defaultView.getComputedStyle(elem, null); 
     142            if (computedStyle.getPropertyValue("visibility") == "hidden" || computedStyle.getPropertyValue("display") == "none") 
     143                continue; 
     144 
     145            // TODO: mozilla docs recommend localName instead of tagName 
     146            tagname = elem.tagName.toLowerCase(); 
     147            if (tagname == "input" || tagname == "textarea") 
     148                text = elem.value; 
     149            else if (tagname == "select") 
     150            { 
     151                if (elem.selectedIndex >= 0) 
     152                    text = elem.item(elem.selectedIndex).text; 
     153                else 
     154                    text = ""; 
     155            } 
     156            else 
     157                text = elem.textContent.toLowerCase(); 
     158 
     159            span = baseNodeAbsolute.cloneNode(true); 
     160            span.style.left = (rect.left + scrollX) + "px"; 
     161            span.style.top = (rect.top + scrollY) + "px"; 
     162            fragment.appendChild(span); 
     163 
     164            pageHints.push([elem, text, span, null, elem.style.backgroundColor, elem.style.color]); 
     165        } 
     166 
     167        if (doc.body) 
     168        { 
     169            doc.body.appendChild(fragment); 
     170            docs.push({ doc: doc, start: start, end: pageHints.length - 1 }); 
     171        } 
     172 
     173        // also generate hints for frames 
     174        Array.forEach(win.frames, function (frame) { generate(frame); }); 
     175 
     176        return true; 
     177    } 
     178 
     179    // TODO: make it aware of imgspans 
     180    function showActiveHint(newID, oldID) 
     181    { 
     182        var oldElem = validHints[oldID - 1]; 
     183        if (oldElem) 
     184            setClass(oldElem, false); 
     185 
     186        var newElem = validHints[newID - 1]; 
     187        if (newElem) 
     188            setClass(newElem, true); 
     189    } 
     190 
     191    function setClass(elem, active) 
     192    { 
     193        let prefix = (elem.getAttributeNS(NS.uri, "class") || "") + " "; 
     194        if (active) 
     195            elem.setAttributeNS(NS.uri, "highlight", prefix + "HintActive"); 
     196        else 
     197            elem.setAttributeNS(NS.uri, "highlight", prefix + "HintElem"); 
     198    } 
     199 
     200    function showHints() 
     201    { 
     202 
     203        let elem, tagname, text, rect, span, imgspan; 
     204        let hintnum = 1; 
     205        let validHint = hintMatcher(hintString.toLowerCase()); 
     206        let activeHint = hintNumber || 1; 
     207        validHints = []; 
     208 
     209        for (let [,{ doc: doc, start: start, end: end }] in Iterator(docs)) 
     210        { 
     211            let scrollX = doc.defaultView.scrollX; 
     212            let scrollY = doc.defaultView.scrollY; 
     213 
     214        inner: 
     215            for (let i in (util.interruptableRange(start, end + 1, 500))) 
     216            { 
     217                let hint = pageHints[i]; 
     218                [elem, text, span, imgspan] = hint; 
     219 
     220                // if (!validHint(text)) 
     221                // { 
     222                //     span.style.display = "none"; 
     223                //     if (imgspan) 
     224                //         imgspan.style.display = "none"; 
     225 
     226                //     elem.removeAttributeNS(NS.uri, "highlight"); 
     227                //     continue inner; 
     228                // } 
     229 
     230                if (text == "" && elem.firstChild && elem.firstChild.tagName == "IMG") 
    338231                { 
    339                     rect = elem.firstChild.getBoundingClientRect(); 
    340                     if (!rect) 
     232                    if (!imgspan) 
     233                    { 
     234                        rect = elem.firstChild.getBoundingClientRect(); 
     235                        if (!rect) 
     236                            continue; 
     237 
     238                        imgspan = util.xmlToDom(<span highlight="Hint"/>, doc); 
     239                        imgspan.setAttributeNS(NS.uri, "class", "HintImage"); 
     240                        imgspan.style.left = (rect.left + scrollX) + "px"; 
     241                        imgspan.style.top = (rect.top + scrollY) + "px"; 
     242                        imgspan.style.width = (rect.right - rect.left) + "px"; 
     243                        imgspan.style.height = (rect.bottom - rect.top) + "px"; 
     244                        hint[IMGSPAN] = imgspan; 
     245                        span.parentNode.appendChild(imgspan); 
     246                    } 
     247                    setClass(imgspan, activeHint == hintnum) 
     248                } 
     249 
     250                var chars = num2chars(hintnum++); 
     251                span.setAttribute("number", chars); 
     252                elem.setAttribute("number", chars); 
     253                ++hintnum; 
     254                if (imgspan) 
     255                    imgspan.setAttribute("number", num2chars(hintnum)); 
     256                else 
     257                    setClass(elem, hintString == chars); 
     258                    //setClass(elem, activeHint == chars); 
     259                if(chars.indexOf(hintString)==0) { 
     260                    validHints.push(elem); 
     261                } 
     262            } 
     263        } 
     264 
     265        if (options.usermode) 
     266        { 
     267            let css = []; 
     268            // FIXME: Broken for imgspans. 
     269            for (let [,{ doc: doc }] in Iterator(docs)) 
     270            { 
     271                for (let elem in buffer.evaluateXPath("//*[@liberator:highlight and @number]", doc)) 
     272                { 
     273                    let group = elem.getAttributeNS(NS.uri, "highlight"); 
     274                    css.push(highlight.selector(group) + "[number='" + elem.getAttribute("number") + "'] { " + elem.style.cssText + " }"); 
     275                } 
     276            } 
     277            styles.addSheet("hint-positions", "*", css.join("\n"), true, true); 
     278        } 
     279 
     280        return true; 
     281    } 
     282 
     283    function removeHints(timeout) 
     284    { 
     285        var firstElem = validHints[0] || null; 
     286 
     287        for (let [,{ doc: doc, start: start, end: end }] in Iterator(docs)) 
     288        { 
     289            for (let elem in buffer.evaluateXPath("//*[@liberator:highlight='hints']", doc)) 
     290                elem.parentNode.removeChild(elem); 
     291            for (let i in util.range(start, end + 1)) 
     292            { 
     293                let hint = pageHints[i]; 
     294                if (!timeout || hint[ELEM] != firstElem) 
     295                    hint[ELEM].removeAttributeNS(NS.uri, "highlight"); 
     296            } 
     297 
     298            // animate the disappearance of the first hint 
     299            if (timeout && firstElem) 
     300            { 
     301                // USE THIS FOR MAKING THE SELECTED ELEM RED 
     302                //                firstElem.style.backgroundColor = "red"; 
     303                //                firstElem.style.color = "white"; 
     304                //                setTimeout(function () { 
     305                //                        firstElem.style.backgroundColor = firstElemBgColor; 
     306                //                        firstElem.style.color = firstElemColor; 
     307                //                }, 200); 
     308                // OR USE THIS FOR BLINKING: 
     309                //                var counter = 0; 
     310                //                var id = setInterval(function () { 
     311                //                    firstElem.style.backgroundColor = "red"; 
     312                //                    if (counter % 2 == 0) 
     313                //                        firstElem.style.backgroundColor = "yellow"; 
     314                //                    else 
     315                //                        firstElem.style.backgroundColor = "#88FF00"; 
     316                // 
     317                //                    if (counter++ >= 2) 
     318                //                    { 
     319                //                        firstElem.style.backgroundColor = firstElemBgColor; 
     320                //                        firstElem.style.color = firstElemColor; 
     321                //                        clearTimeout(id); 
     322                //                    } 
     323                //                }, 100); 
     324                setTimeout(function () { firstElem.removeAttributeNS(NS.uri, "highlight") }, timeout); 
     325            } 
     326        } 
     327        styles.removeSheet("hint-positions", null, null, null, true); 
     328 
     329        reset(); 
     330    } 
     331 
     332    function processHints(followFirst) 
     333    { 
     334        if (validHints.length == 0) 
     335        { 
     336            liberator.beep(); 
     337            return false; 
     338        } 
     339 
     340        if (options["followhints"] > 0) 
     341        { 
     342            if (!followFirst) 
     343                return false; // no return hit; don't examine uniqueness 
     344 
     345            // OK. return hit. But there's more than one hint. And 
     346            // there's no tab-selected current link. Do not follow in mode 2 
     347            if ((options["followhints"] == 2) && validHints.length > 1 && !hintNumber) 
     348                return liberator.beep(); 
     349        } 
     350     
     351        if (!followFirst) 
     352        { 
     353            var firstHref = validHints[0].getAttribute("href") || null; 
     354            if (firstHref) 
     355            { 
     356                if (validHints.some(function (e) e.getAttribute("href") != firstHref)) 
     357                    return false; 
     358            } 
     359            else if (validHints.length > 1) 
     360            { 
     361                return false; 
     362            } 
     363        } 
     364 
     365        var timeout = followFirst || events.feedingKeys ? 0 : 500; 
     366        var elems = []; 
     367        for (let [,{ doc: doc }] in Iterator(docs)) 
     368        { 
     369            for (let elem in buffer.evaluateXPath("//*[@number=\""+hintString+"\"]", doc)) 
     370            { 
     371                elems.push(elem); 
     372            } 
     373        } 
     374        var elem = elems[0]; 
     375        removeHints(timeout); 
     376 
     377        if (timeout == 0) 
     378            // force a possible mode change, based on wheter an input field has focus