From 941b92e60d4f636e16ab0d3bd0814073b27a0642 Mon Sep 17 00:00:00 2001 From: Brendan Taylor Date: Sun, 6 Mar 2011 02:40:35 -0700 Subject: frames-enabled follow.js --- examples/data/scripts/follow.js | 291 +++++++++++++++++++++------------------- 1 file changed, 153 insertions(+), 138 deletions(-) (limited to 'examples/data/scripts/follow.js') diff --git a/examples/data/scripts/follow.js b/examples/data/scripts/follow.js index 2976834..1551f1c 100644 --- a/examples/data/scripts/follow.js +++ b/examples/data/scripts/follow.js @@ -12,81 +12,101 @@ */ uzbl.follow = function() { - // Export - charset = arguments[0] - keypress = arguments[1] - newwindow = arguments[2] + charset = arguments[0]; + newwindow = arguments[2]; // Some shortcuts and globals uzblid = 'uzbl_link_hint'; uzbldivid = uzblid + '_div_container'; - doc = document; - win = window; - links = doc.links; - forms = doc.forms; - - // Make onclick-links "clickable" - try { - HTMLElement.prototype.click = function() { - if (typeof this.onclick == 'function') { - this.onclick({ type: 'click' }); - } - } - } catch(e) {} + var keypress = arguments[1]; return arguments.callee.followLinks(keypress); } +uzbl.follow.isFrame = function(el) { + return (el.tagName == "FRAME" || el.tagName == "IFRAME"); +} + +// find the document that the given element belongs to +uzbl.follow.getDocument = function(el) { + if (this.isFrame(el)) + return el.contentDocument; + + var doc = el; + while (doc.parentNode !== null) + doc = doc.parentNode; + return doc; +} + +// find all documents in the display, searching frames recursively +uzbl.follow.documents = function() { + return this.windows().map(function(w) { return w.document; }).filter(function(d) { return d != null; }); +} + +// find all windows in the display, searching for frames recursively +uzbl.follow.windows = function(w) { +  w = (typeof w == 'undefined') ? window.top : w; + + var wins = [w]; + var frames = w.frames; + for(var i = 0; i < frames.length; i++) + wins = wins.concat(uzbl.follow.windows(frames[i])); + return wins; +} + +// search all frames for elements matching the given CSS selector +uzbl.follow.query = function(selector) { + var res = []; + this.documents().forEach(function (doc) { + var set = doc.body.querySelectorAll(selector); + // convert the NodeList to an Array + set = Array.prototype.slice.call(set); + res = res.concat(set); + }); + return res; +} + // Calculate element position to draw the hint -// Pretty accurate but fails in some very fancy cases uzbl.follow.elementPosition = function(el) { - var up = el.offsetTop; - var left = el.offsetLeft; - var width = el.offsetWidth; - var height = el.offsetHeight; - while (el.offsetParent) { - el = el.offsetParent; - up += el.offsetTop; - left += el.offsetLeft; - } - return [up, left, width, height]; -} + var rect = el.getBoundingClientRect(); -// Calculate if an element is visible -uzbl.follow.isVisible = function(el) { - if (el == doc) return true; - if (!el) return false; - if (!el.parentNode) return false; + var left, up; - if (el.style) { - if (el.style.display == 'none') return false; - if (el.style.visibility == 'hidden') return false; + if (uzbl.follow.isFrame(el)) { + left = document.defaultView.scrollX; + up = document.defaultView.scrollY; + } else { + left = Math.max((rect.left + document.defaultView.scrollX), document.defaultView.scrollX); + up = Math.max((rect.top + document.defaultView.scrollY), document.defaultView.scrollY); } - return this.isVisible(el.parentNode); + + return [up, left, rect.width, rect.height]; } // Calculate if an element is on the viewport. uzbl.follow.elementInViewport = function(el) { - offset = this.elementPosition(el); - var up = offset[0]; - var left = offset[1]; - var width = offset[2]; + offset = uzbl.follow.elementPosition(el); + var up = offset[0]; + var left = offset[1]; + var width = offset[2]; var height = offset[3]; - return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset; + return up < window.pageYOffset + window.innerHeight && + left < window.pageXOffset + window.innerWidth && + (up + height) > window.pageYOffset && + (left + width) > window.pageXOffset; } // Removes all hints/leftovers that might be generated // by this script. -uzbl.follow.removeAllHints = function() { +uzbl.follow.removeAllHints = function(doc) { var elements = doc.getElementById(uzbldivid); if (elements) elements.parentNode.removeChild(elements); } // Generate a hint for an element with the given label // Here you can play around with the style of the hints! -uzbl.follow.generateHint = function(el, label) { - var pos = this.elementPosition(el); +uzbl.follow.generateHint = function(doc, el, label, top, left) { var hint = doc.createElement('div'); hint.setAttribute('name', uzblid); hint.innerText = label; @@ -105,10 +125,10 @@ uzbl.follow.generateHint = function(el, label) { hint.style.padding = '1px'; hint.style.position = 'absolute'; hint.style.zIndex = '1000'; - hint.style.left = pos[1] + 'px'; - hint.style.top = pos[0] + 'px'; hint.style.textDecoration = 'none'; hint.style.webkitTransform = 'translate(-5px,-5px)'; + hint.style.top = top + 'px'; + hint.style.left = left + 'px'; return hint; } @@ -117,79 +137,53 @@ uzbl.follow.generateHint = function(el, label) { // or pass the focus, on links we try to perform a click, // but at least set the href of the link. (needs some improvements) uzbl.follow.clickElem = function(item) { - this.removeAllHints(); - if (item) { - if (newwindow && item.tagName == 'A') { - window.open(item.href); - return "XXXRESET_MODEXXX" - } - else { - var name = item.tagName; - if (name == 'A') { - item.click(); - window.location = item.href; - return "XXXRESET_MODEXXX"; - } else if (name == 'INPUT') { - var type = item.getAttribute('type').toUpperCase(); - if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { - item.focus(); - item.select(); - return "XXXEMIT_FORM_ACTIVEXXX"; - } else { - item.click(); - return "XXXRESET_MODEXXX"; - } - } else if (name == 'TEXTAREA' || name == 'SELECT') { - item.focus(); - item.select(); - return "XXXEMIT_FORM_ACTIVEXXX"; - } else { - item.click(); - window.location = item.href; - return "XXXRESET_MODEXXX"; - } - } - } -} + if(!item) return; + var name = item.tagName; -// Returns a list of all links (in this version -// just the elements itself, but in other versions, we -// add the label here. -uzbl.follow.addLinks = function() { - res = [[], []]; - for (var l = 0; l < links.length; l++) { - var li = links[l]; - if (this.isVisible(li) && this.elementInViewport(li)) res[0].push(li); - } - return res; -} - -// Same as above, just for the form elements -uzbl.follow.addFormElems = function() { - res = [[], []]; - for (var f = 0; f < forms.length; f++) { - for (var e = 0; e < forms[f].elements.length; e++) { - var el = forms[f].elements[e]; - if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && this.isVisible(el) && this.elementInViewport(el)) res[0].push(el); + if (name == 'INPUT') { + var type = item.getAttribute('type').toUpperCase(); + if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') { + item.focus(); + item.select(); + return "XXXEMIT_FORM_ACTIVEXXX"; } + // otherwise fall through to a simulated mouseclick. + } else if (name == 'TEXTAREA' || name == 'SELECT') { + item.focus(); + item.select(); + return "XXXEMIT_FORM_ACTIVEXXX"; } - return res; + + // simulate a mouseclick to activate the element + var mouseEvent = document.createEvent("MouseEvent"); + mouseEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + item.dispatchEvent(mouseEvent); + return "XXXRESET_MODEXXX"; } -// Draw all hints for all elements passed. "len" is for -// the number of chars we should use to avoid collisions +// Draw all hints for all elements passed. uzbl.follow.reDrawHints = function(elems, chars) { - this.removeAllHints(); - var hintdiv = doc.createElement('div'); - hintdiv.setAttribute('id', uzbldivid); - for (var i = 0; i < elems[0].length; i++) { - if (elems[0][i]) { - var label = elems[1][i].substring(chars); - var h = this.generateHint(elems[0][i], label); - hintdiv.appendChild(h); - } - } - if (document.body) document.body.appendChild(hintdiv); + var elements = elems.map(function(pair) { return pair[0] }); + var labels = elems.map(function(pair) { return pair[1].substring(chars) }); + // we have to calculate element positions before we modify the DOM + // otherwise the elementPosition call slows way down. + var positions = elements.map(uzbl.follow.elementPosition); + + this.documents().forEach(function(doc) { + uzbl.follow.removeAllHints(doc); + if (!doc.body) return; + doc.hintdiv = doc.createElement('div'); + doc.hintdiv.setAttribute('id', uzbldivid); + doc.body.appendChild(doc.hintdiv); + }); + + elements.forEach(function(el, i) { + var label = labels[i]; + var pos = positions[i]; + var doc = uzbl.follow.getDocument(el); + var h = uzbl.follow.generateHint(doc, el, label, pos[0], pos[1]); + doc.hintdiv.appendChild(h); + }); } // pass: number of keys @@ -221,8 +215,7 @@ uzbl.follow.intToLabel = function(n) { // returns: number uzbl.follow.labelToInt = function(label) { var n = 0; - var i; - for(i = 0; i < label.length; ++i) { + for(var i = 0; i < label.length; ++i) { n *= charset.length; n += charset.indexOf(label[i]); } @@ -231,32 +224,54 @@ uzbl.follow.labelToInt = function(label) { // Put it all together uzbl.follow.followLinks = function(follow) { - //if(follow.charAt(0) == 'l') { - // follow = follow.substr(1); - // charset = 'thsnlrcgfdbmwvz-/'; - //} var s = follow.split(''); var linknr = this.labelToInt(follow); - if (document.body) document.body.setAttribute('onkeyup', 'keyPressHandler(event)'); - var linkelems = this.addLinks(); - var formelems = this.addFormElems(); - var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])]; - var len = this.labelLength(elems[0].length); - var oldDiv = doc.getElementById(uzbldivid); - var leftover = [[], []]; - if (s.length == len && linknr < elems[0].length && linknr >= 0) - return this.clickElem(elems[0][linknr]); - - for (var j = 0; j < elems[0].length; j++) { + + var followable = 'a, area, textarea, select, input:not([type=hidden]), button'; + var uri = 'a, area, frame, iframe'; + //var focusable = 'a, area, textarea, select, input:not([type=hidden]), button, frame, iframe, applet, object'; + //var desc = '*[title], img[alt], applet[alt], area[alt], input[alt]'; + //var image = 'img, input[type=image]'; + + if(newwindow) + var res = this.query(uri); + else + var res = this.query(followable); + + var elems = res.filter(uzbl.follow.elementInViewport); + var len = this.labelLength(elems.length); + + if (s.length == len && linknr < elems.length && linknr >= 0) { + // an element has been selected! + var el = elems[linknr]; + + // clear all of our hints + this.documents().forEach(uzbl.follow.removeAllHints); + + if (newwindow) { + // we're opening a new window using the URL attached to this element + var uri = el.src || el.href; + if(uri.match(/javascript:/)) return; + window.open(uri); + return "XXXRESET_MODEXXX" + } + + // we're just going to click the element + return this.clickElem(el); + } + + var leftover = []; + for (var j = 0; j < elems.length; j++) { var b = true; var label = this.intToLabel(j); var n = label.length; - for (n; n < len; n++) label = charset.charAt(0) + label; - for (var k = 0; k < s.length; k++) b = b && label.charAt(k) == s[k]; - if (b) { - leftover[0].push(elems[0][j]); - leftover[1].push(label); - } + for (n; n < len; n++) + label = charset.charAt(0) + label; + for (var k = 0; k < s.length; k++) + b = b && label.charAt(k) == s[k]; + if (b) + leftover.push([elems[j], label]); } + this.reDrawHints(leftover, s.length); } -- cgit v1.2.3