2 Copyright (c) 2009 Leon Winter
3 Copyright (c) 2009-2011 Hannes Schueller
4 Copyright (c) 2009-2010 Matto Fransen
5 Copyright (c) 2010-2011 Hans-Peter Deifel
6 Copyright (c) 2010-2011 Thomas Adam
7 Copyright (c) 2011 Albert Kim
8 Copyright (c) 2011 Daniel Carl
10 Permission is hereby granted, free of charge, to any person obtaining a copy
11 of this software and associated documentation files (the "Software"), to deal
12 in the Software without restriction, including without limitation the rights
13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the Software is
15 furnished to do so, subject to the following conditions:
17 The above copyright notice and this permission notice shall be included in
18 all copies or substantial portions of the Software.
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31 hintCss: "z-index:100000;font-family:monospace;font-size:10px;"
32 + "font-weight:bold;color:white;background-color:red;"
33 + "padding:0px 1px;position:absolute;",
34 hintClass: "hinting_mode_hint",
35 hintClassFocus: "hinting_mode_hint_focus",
36 elemBackground: "#ff0",
37 elemBackgroundFocus: "#8f0",
42 var currentFocusNum = 1;
46 this.createHints = function(inputText, hintMode)
53 var top_height = topwin.innerHeight;
54 var top_width = topwin.innerWidth;
60 function helper (win, offsetX, offsetY) {
61 var doc = win.document;
63 var win_height = win.height;
64 var win_width = win.width;
67 var minX = offsetX < 0 ? -offsetX : 0;
68 var minY = offsetY < 0 ? -offsetY : 0;
69 var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
70 var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
72 var scrollX = win.scrollX;
73 var scrollY = win.scrollY;
75 hintContainer = doc.createElement("div");
76 hintContainer.id = "hint_container";
78 if (typeof(inputText) == "undefined" || inputText == "") {
79 xpath_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";
81 xpath_expr = _caseInsensitiveExpr("//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(translate(., '$u', '$l'), '$l')] | //input[not(@type='hidden') and contains(translate(., '$u', '$l'), '$l')] | //a[@href and contains(translate(., '$u', '$l'), '$l')] | //area[contains(translate(., '$u', '$l'), '$l')] | //textarea[contains(translate(., '$u', '$l'), '$l')] | //button[contains(translate(@value, '$u', '$l'), '$l')] | //select[contains(translate(., '$u', '$l'), '$l')]", inputText);
84 var res = doc.evaluate(xpath_expr, doc,
86 return "http://www.w3.org/1999/xhtml";
87 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
89 /* generate basic hint element which will be cloned and updated later */
90 var hintSpan = doc.createElement("span");
91 hintSpan.setAttribute("class", config.hintClass);
92 hintSpan.style.cssText = config.hintCss;
94 /* due to the different XPath result type, we will need two counter variables */
95 var rect, elem, text, node, show_text;
96 for (var i = 0; i < res.snapshotLength; i++)
98 if (hintCount >= config.maxAllowedHints)
101 elem = res.snapshotItem(i);
102 rect = elem.getBoundingClientRect();
103 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
106 var style = topwin.getComputedStyle(elem, "");
107 if (style.display == "none" || style.visibility != "visible")
110 var leftpos = Math.max((rect.left + scrollX), scrollX);
111 var toppos = Math.max((rect.top + scrollY), scrollY);
113 /* check if we already created a hint for this URL */
114 var hintNumber = hintCount;
115 if (elem.nodeName.toLowerCase() == "a") {
116 for (var j = 0; j < hints.length; j++) {
118 if (h.elem.nodeName.toLowerCase() != "a")
120 if (h.elem.href.toLowerCase() == elem.href.toLowerCase()){
121 hintNumber = h.number - 1;
127 /* making this block DOM compliant */
128 var hint = hintSpan.cloneNode(false);
129 hint.setAttribute("id", "vimprobablehint" + hintNumber);
130 hint.style.left = leftpos + "px";
131 hint.style.top = toppos + "px";
132 text = doc.createTextNode(hintNumber + 1);
133 hint.appendChild(text);
135 hintContainer.appendChild(hint);
136 if (hintNumber == hintCount)
139 hintNumber = -2; /* do not follow dupes */
143 number: hintNumber+1,
145 background: elem.style.background,
146 foreground: elem.style.color}
149 /* make the link black to ensure it's readable */
150 elem.style.color = config.elemColor;
151 elem.style.background = config.elemBackground;
154 doc.documentElement.appendChild(hintContainer);
156 /* recurse into any iframe or frame element */
157 var frameTags = ["frame","iframe"];
158 for (var f = 0; f < frameTags.length; ++f) {
159 var frames = doc.getElementsByTagName(frameTags[f]);
160 for (var i = 0, nframes = frames.length; i < nframes; ++i) {
162 rect = elem.getBoundingClientRect();
163 if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
165 helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
170 helper(topwin, 0, 0);
174 if (hintCount == 1) {
175 /* just one hinted element - might as well follow it */
180 /* set focus on hint with given number */
181 this.focusHint = function(n)
183 /* reset previous focused hint */
184 var hint = _getHintByNumber(currentFocusNum);
186 hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass);
187 hint.elem.style.background = config.elemBackground;
192 /* mark new hint as focused */
193 var hint = _getHintByNumber(currentFocusNum);
195 hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus);
196 hint.elem.style.background = config.elemBackgroundFocus;
200 /* set focus to next avaiable hint */
201 this.focusNextHint = function()
203 var index = _getHintIdByNumber(currentFocusNum);
205 if (typeof(hints[index + 1]) != "undefined") {
206 this.focusHint(hints[index + 1].number);
208 this.focusHint(hints[0].number);
212 /* set focus to previous avaiable hint */
213 this.focusPreviousHint = function()
215 var index = _getHintIdByNumber(currentFocusNum);
216 if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
217 this.focusHint(hints[index - 1].number);
219 this.focusHint(hints[hints.length - 1].number);
223 /* filters hints matching given number */
224 this.updateHints = function(n)
227 return this.createHints();
229 /* remove none matching hints */
231 for (var i = 0; i < hints.length; ++i) {
233 if (0 != hint.number.toString().indexOf(n.toString())) {
234 remove.push(hint.number);
238 for (var i = 0; i < remove.length; ++i) {
239 _removeHint(remove[i]);
242 if (hints.length === 1) {
243 return this.fire(hints[0].number);
245 return this.focusHint(n);
249 this.clearFocus = function()
251 if (document.activeElement && document.activeElement.blur) {
252 document.activeElement.blur();
256 /* remove all hints and set previous style to them */
257 this.clearHints = function()
259 if (hints.length == 0) {
262 for (var i = 0; i < hints.length; ++i) {
264 if (typeof(hint.elem) != "undefined") {
265 hint.elem.style.background = hint.background;
266 hint.elem.style.color = hint.foreground;
267 hint.span.parentNode.removeChild(hint.span);
271 hintContainer.parentNode.removeChild(hintContainer);
272 window.onkeyup = null;
275 /* fires the modeevent on hint with given number */
276 this.fire = function(n)
280 var n = currentFocusNum;
282 var hint = _getHintByNumber(n);
283 if (typeof(hint.elem) == "undefined")
287 var tag = el.nodeName.toLowerCase();
291 if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
293 if (tag == "input" || tag == "textarea") {
301 case "f": result = _open(el); break;
302 case "F": result = _openNewWindow(el); break;
303 default: result = _getElemtSource(el);
309 this.focusInput = function()
311 if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
314 /* prefixing html: will result in namespace error */
315 var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
316 var r = document.evaluate(hinttags, document,
318 return "http://www.w3.org/1999/xhtml";
319 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
324 for (i = 0; i < r.snapshotLength; i++) {
325 var elem = r.snapshotItem(i);
327 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
333 if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
335 var tag = elem.nodeName.toLowerCase();
336 if (tag == "textarea" || tag == "input") {
341 if (elem == document.activeElement) {
346 /* no appropriate field found focused - focus the first one */
347 if (j == 0 && first !== null) {
349 var tag = elem.nodeName.toLowerCase();
350 if (tag == "textarea" || tag == "input") {
356 /* retrieves text content fro given element */
357 function _getTextFromElement(el)
359 if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
361 } else if (el instanceof HTMLSelectElement) {
362 if (el.selectedIndex >= 0) {
363 text = el.item(el.selectedIndex).text;
368 text = el.textContent;
370 return text.toLowerCase();;
373 /* retrieves the hint for given hint number */
374 function _getHintByNumber(n)
376 var index = _getHintIdByNumber(n);
377 if (index !== null) {
383 /* retrieves the id of hint with given number */
384 function _getHintIdByNumber(n)
386 for (var i = 0; i < hints.length; ++i) {
388 if (hint.number === n) {
395 /* removes hint with given number from hints array */
396 function _removeHint(n)
398 var index = _getHintIdByNumber(n);
399 if (index === null) {
402 var hint = hints[index];
403 if (hint.number === n) {
404 hint.elem.style.background = hint.background;
405 hint.elem.style.color = hint.foreground;
406 hint.span.parentNode.removeChild(hint.span);
408 /* remove hints from all hints */
409 hints.splice(index, 1);
413 /* opens given element */
416 if (elem.target == "_blank") {
417 elem.removeAttribute("target");
423 /* opens given element into new window */
424 function _openNewWindow(elem)
426 var oldTarget = elem.target;
428 /* set target to open in new window */
429 elem.target = "_blank";
431 elem.target = oldTarget;
436 /* fire moudedown and click event on given element */
437 function _clickElement(elem)
439 doc = elem.ownerDocument;
440 view = elem.contentWindow;
442 var evObj = doc.createEvent("MouseEvents");
443 evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
444 elem.dispatchEvent(evObj);
446 var evObj = doc.createEvent("MouseEvents");
447 evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
448 elem.dispatchEvent(evObj);
451 /* retrieves the url of given element */
452 function _getElemtSource(elem)
454 var url = elem.href || elem.src;
458 /* returns a case-insensitive version of the XPath expression */
459 function _caseInsensitiveExpr(xpath, searchString)
461 return xpath.split("$u").join(searchString.toUpperCase())
462 .split("$l").join(searchString.toLowerCase());