viking

webkit based web browser for Enlightenment
Log | Files | Refs | LICENSE

hinting.js (15427B)


      1 /*
      2     (c) 2009 by Leon Winter
      3     (c) 2009, 2010 by Hannes Schueller
      4     (c) 2010 by Hans-Peter Deifel
      5     (c) 2011 by Daniel Carl
      6     see LICENSE file
      7 */
      8 function Hints() {
      9     var config = {
     10         maxAllowedHints: 500,
     11         hintCss: "z-index:100000;font-family:monospace;font-size:10px;"
     12                + "font-weight:bold;color:white;background-color:red;"
     13                + "padding:0px 1px;position:absolute;",
     14         hintClass: "hinting_mode_hint",
     15         hintClassFocus: "hinting_mode_hint_focus",
     16         elemBackground: "#ff0",
     17         elemBackgroundFocus: "#8f0",
     18         elemColor: "#000"
     19     };
     20 
     21     var hintContainer;
     22     var currentFocusNum = 1;
     23     var hints = [];
     24     var mode;
     25 
     26     this.createHints = function(inputText, hintMode)
     27     {
     28         if (hintMode) {
     29             mode = hintMode;
     30         }
     31 
     32         var topwin = window;
     33         var top_height = topwin.innerHeight;
     34         var top_width = topwin.innerWidth;
     35         var xpath_expr;
     36 
     37         var hintCount = 0;
     38         this.clearHints();
     39 
     40         function helper (win, offsetX, offsetY) {
     41             var doc = win.document;
     42 
     43             var win_height = win.height;
     44             var win_width = win.width;
     45 
     46             /* Bounds */
     47             var minX = offsetX < 0 ? -offsetX : 0;
     48             var minY = offsetY < 0 ? -offsetY : 0;
     49             var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
     50             var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
     51 
     52             var scrollX = win.scrollX;
     53             var scrollY = win.scrollY;
     54 
     55             hintContainer = doc.createElement("div");
     56             hintContainer.id = "hint_container";
     57 
     58             xpath_expr = _getXpathXpression(inputText);
     59 
     60             var res = doc.evaluate(xpath_expr, doc,
     61                 function (p) {
     62                     return "http://www.w3.org/1999/xhtml";
     63                 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
     64 
     65             /* generate basic hint element which will be cloned and updated later */
     66             var hintSpan = doc.createElement("span");
     67             hintSpan.setAttribute("class", config.hintClass);
     68             hintSpan.style.cssText = config.hintCss;
     69 
     70             /* due to the different XPath result type, we will need two counter variables */
     71             var rect, elem, text, node, show_text;
     72             for (var i = 0; i < res.snapshotLength; i++)
     73             {
     74                 if (hintCount >= config.maxAllowedHints)
     75                     break;
     76 
     77                 elem = res.snapshotItem(i);
     78                 rect = elem.getBoundingClientRect();
     79                 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
     80                     continue;
     81 
     82                 var style = topwin.getComputedStyle(elem, "");
     83                 if (style.display == "none" || style.visibility != "visible")
     84                     continue;
     85 
     86                 var leftpos = Math.max((rect.left + scrollX), scrollX);
     87                 var toppos = Math.max((rect.top + scrollY), scrollY);
     88 
     89                 /* making this block DOM compliant */
     90                 var hint = hintSpan.cloneNode(false);
     91                 hint.setAttribute("id", "vimprobablehint" + hintCount);
     92                 hint.style.left = leftpos + "px";
     93                 hint.style.top =  toppos + "px";
     94                 text = doc.createTextNode(hintCount + 1);
     95                 hint.appendChild(text);
     96 
     97                 hintContainer.appendChild(hint);
     98                 hintCount++;
     99                 hints.push({
    100                     elem:       elem,
    101                     number:     hintCount,
    102                     span:       hint,
    103                     background: elem.style.background,
    104                     foreground: elem.style.color}
    105                 );
    106 
    107                 /* make the link black to ensure it's readable */
    108                 elem.style.color = config.elemColor;
    109                 elem.style.background = config.elemBackground;
    110             }
    111 
    112             doc.documentElement.appendChild(hintContainer);
    113 
    114             /* recurse into any iframe or frame element */
    115             var frameTags = ["frame","iframe"];
    116             for (var f = 0; f < frameTags.length; ++f) {
    117                 var frames = doc.getElementsByTagName(frameTags[f]);
    118                 for (var i = 0, nframes = frames.length; i < nframes; ++i) {
    119                     elem = frames[i];
    120                     rect = elem.getBoundingClientRect();
    121                     if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
    122                         continue;
    123                     helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
    124                 }
    125             }
    126         }
    127 
    128         helper(topwin, 0, 0);
    129 
    130         this.clearFocus();
    131         this.focusHint(1);
    132         if (hintCount == 1) {
    133             /* just one hinted element - might as well follow it */
    134             return this.fire(1);
    135         }
    136     };
    137 
    138     /* set focus on hint with given number */
    139     this.focusHint = function(n)
    140     {
    141         /* reset previous focused hint */
    142         var hint = _getHintByNumber(currentFocusNum);
    143         if (hint !== null) {
    144             hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass);
    145             hint.elem.style.background = config.elemBackground;
    146         }
    147 
    148         currentFocusNum = n;
    149 
    150         /* mark new hint as focused */
    151         var hint = _getHintByNumber(currentFocusNum);
    152         if (hint !== null) {
    153             hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus);
    154             hint.elem.style.background = config.elemBackgroundFocus;
    155         }
    156     };
    157 
    158     /* set focus to next avaiable hint */
    159     this.focusNextHint = function()
    160     {
    161         var index = _getHintIdByNumber(currentFocusNum);
    162 
    163         if (typeof(hints[index + 1]) != "undefined") {
    164             this.focusHint(hints[index + 1].number);
    165         } else {
    166             this.focusHint(hints[0].number);
    167         }
    168     };
    169 
    170     /* set focus to previous avaiable hint */
    171     this.focusPreviousHint = function()
    172     {
    173         var index = _getHintIdByNumber(currentFocusNum);
    174         if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
    175             this.focusHint(hints[index - 1].number);
    176         } else {
    177             this.focusHint(hints[hints.length - 1].number);
    178         }
    179     };
    180 
    181     /* filters hints matching given number */
    182     this.updateHints = function(n)
    183     {
    184         if (n == 0) {
    185             return this.createHints();
    186         }
    187         /* remove none matching hints */
    188         var remove = [];
    189         for (var i = 0; i < hints.length; ++i) {
    190             var hint = hints[i];
    191             if (0 != hint.number.toString().indexOf(n.toString())) {
    192                 remove.push(hint.number);
    193             }
    194         }
    195 
    196         for (var i = 0; i < remove.length; ++i) {
    197             _removeHint(remove[i]);
    198         }
    199 
    200         if (hints.length === 1) {
    201             return this.fire(hints[0].number);
    202         } else {
    203             return this.focusHint(n);
    204         }
    205     };
    206 
    207     this.clearFocus = function()
    208     {
    209         if (document.activeElement && document.activeElement.blur) {
    210             document.activeElement.blur();
    211         }
    212     };
    213 
    214     /* remove all hints and set previous style to them */
    215     this.clearHints = function()
    216     {
    217         if (hints.length == 0) {
    218             return;
    219         }
    220         for (var i = 0; i < hints.length; ++i) {
    221             var hint = hints[i];
    222             if (typeof(hint.elem) != "undefined") {
    223                 hint.elem.style.background = hint.background;
    224                 hint.elem.style.color = hint.foreground;
    225                 hint.span.parentNode.removeChild(hint.span);
    226             }
    227         }
    228         hints = [];
    229         hintContainer.parentNode.removeChild(hintContainer);
    230         window.onkeyup = null;
    231     };
    232 
    233     /* fires the modeevent on hint with given number */
    234     this.fire = function(n)
    235     {
    236         var doc, result;
    237         if (!n) {
    238             var n = currentFocusNum;
    239         }
    240         var hint = _getHintByNumber(n);
    241         if (typeof(hint.elem) == "undefined")
    242             return "done;";
    243 
    244         var el = hint.elem;
    245         var tag = el.nodeName.toLowerCase();
    246 
    247         this.clearHints();
    248 
    249         if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
    250             el.focus();
    251             if (tag == "input" || tag == "textarea") {
    252                 return "insert;"
    253             }
    254             return "done;";
    255         }
    256 
    257         switch (mode)
    258         {
    259             case "f": case "i": result = _open(el); break;
    260             case "F": case "I": result = _openNewWindow(el); break;
    261             case "s": result = "save;" + _getElemtSource(el); break;
    262             case "y": result = "yank;" + _getElemtSource(el); break;
    263             case "O": result = "colon;" + _getElemtSource(el); break;
    264             default:  result = _getElemtSource(el);
    265         }
    266 
    267         return result;
    268     };
    269 
    270     this.focusInput = function()
    271     {
    272         if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
    273             return;
    274 
    275         /* prefixing html: will result in namespace error */
    276         var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
    277         var r = document.evaluate(hinttags, document,
    278             function(p) {
    279                 return "http://www.w3.org/1999/xhtml";
    280             }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    281         var i;
    282         var j = 0;
    283         var k = 0;
    284         var first = null;
    285         for (i = 0; i < r.snapshotLength; i++) {
    286             var elem = r.snapshotItem(i);
    287             if (k == 0) {
    288                 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
    289                     first = elem;
    290                 } else {
    291                     k--;
    292                 }
    293             }
    294             if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
    295                 elem.focus();
    296                 var tag = elem.nodeName.toLowerCase();
    297                 if (tag == "textarea" || tag == "input") {
    298                     return "insert;";
    299                 }
    300                 break;
    301             }
    302             if (elem == document.activeElement) {
    303                 j = 1;
    304             }
    305             k++;
    306         }
    307         /* no appropriate field found focused - focus the first one */
    308         if (j == 0 && first !== null) {
    309             first.focus();
    310             var tag = elem.nodeName.toLowerCase();
    311             if (tag == "textarea" || tag == "input") {
    312                 return "insert;";
    313             }
    314         }
    315     };
    316 
    317     /* retrieves text content fro given element */
    318     function _getTextFromElement(el)
    319     {
    320         if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
    321             text = el.value;
    322         } else if (el instanceof HTMLSelectElement) {
    323             if (el.selectedIndex >= 0) {
    324                 text = el.item(el.selectedIndex).text;
    325             } else{
    326                 text = "";
    327             }
    328         } else {
    329             text = el.textContent;
    330         }
    331         return text.toLowerCase();;
    332     }
    333 
    334     /* retrieves the hint for given hint number */
    335     function _getHintByNumber(n)
    336     {
    337         var index = _getHintIdByNumber(n);
    338         if (index !== null) {
    339             return hints[index];
    340         }
    341         return null;
    342     }
    343 
    344     /* retrieves the id of hint with given number */
    345     function _getHintIdByNumber(n)
    346     {
    347         for (var i = 0; i < hints.length; ++i) {
    348             var hint = hints[i];
    349             if (hint.number === n) {
    350                 return i;
    351             }
    352         }
    353         return null;
    354     }
    355 
    356     /* removes hint with given number from hints array */
    357     function _removeHint(n)
    358     {
    359         var index = _getHintIdByNumber(n);
    360         if (index === null) {
    361             return;
    362         }
    363         var hint = hints[index];
    364         if (hint.number === n) {
    365             hint.elem.style.background = hint.background;
    366             hint.elem.style.color = hint.foreground;
    367             hint.span.parentNode.removeChild(hint.span);
    368 
    369             /* remove hints from all hints */
    370             hints.splice(index, 1);
    371         }
    372     }
    373 
    374     /* opens given element */
    375     function _open(elem)
    376     {
    377         if (elem.target == "_blank") {
    378             elem.removeAttribute("target");
    379         }
    380         _clickElement(elem);
    381         return "done;";
    382     }
    383 
    384     /* opens given element into new window */
    385     function _openNewWindow(elem)
    386     {
    387         var oldTarget = elem.target;
    388 
    389         /* set target to open in new window */
    390         elem.target = "_blank";
    391         _clickElement(elem);
    392         elem.target = oldTarget;
    393 
    394         return "done;";
    395     }
    396     
    397     /* fire moudedown and click event on given element */
    398     function _clickElement(elem)
    399     {
    400         doc = elem.ownerDocument;
    401         view = elem.contentWindow;
    402 
    403         var evObj = doc.createEvent("MouseEvents");
    404         evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
    405         elem.dispatchEvent(evObj);
    406 
    407         var evObj = doc.createEvent("MouseEvents");
    408         evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
    409         elem.dispatchEvent(evObj);
    410     }
    411 
    412     /* retrieves the url of given element */
    413     function _getElemtSource(elem)
    414     {
    415         var url = elem.href || elem.src;
    416         return url;
    417     }
    418 
    419     /* retrieves the xpath expression according to mode */
    420     function _getXpathXpression(text)
    421     {
    422         var expr;
    423         if (typeof(text) == "undefined") {
    424             text = "";
    425         }
    426         switch (mode) {
    427             case "f":
    428             case "F":
    429                 if (text == "") {
    430                     expr = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a[href] | //area | //textarea | //button | //select";
    431                 } else {
    432                     expr = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + text + "')] | //input[not(@type='hidden') and contains(., '" + text + "')] | //a[@href and contains(., '" + text + "')] | //area[contains(., '" + text + "')] |  //textarea[contains(., '" + text + "')] | //button[contains(@value, '" + text + "')] | //select[contains(., '" + text + "')]";
    433                 }
    434                 break;
    435             case "i":
    436             case "I":
    437                 if (text == "") {
    438                     expr = "//img[@src]";
    439                 } else {
    440                     expr = "//img[@src and contains(., '" + text + "')]";
    441                 }
    442                 break;
    443             default:
    444                 if (text == "") {
    445                     expr = "//*[@role='link' or @href] | //a[href] | //area | //img[not(ancestor::a)]";
    446                 } else {
    447                     expr = "//*[(@role='link' or @href) and contains(., '" + text + "')] | //a[@href and contains(., '" + text + "')] | //area[contains(., '" + text + "')] | //img[not(ancestor::a) and contains(., '" + text + "')]";
    448                 }
    449                 break;
    450         }
    451         return expr;
    452     }
    453 
    454 }
    455 hints = new Hints();