Created
January 17, 2013 12:29
-
-
Save caisui/4555618 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // vim: set sw=4 ts=4 fdm=marker et : | |
| //"use strict"; | |
| var INFO = //{{{ | |
| <plugin name="hints-ext" version="0.0.3" | |
| href="http://github.com/caisui/vimperator/blob/master/plugin/hints-ext.js" | |
| summary="Hints Ext" | |
| xmlns="http://vimperator.org/namespaces/liberator"> | |
| <author href="http://d.hatena.ne.jp/caisui">caisui</author> | |
| <license href="http://www.opensource.org/licenses/bsd-license.php">New BSD License</license> | |
| <project name="Vimperator" minVersion="3.0"/> | |
| <item> | |
| <description> | |
| Hint です | |
| </description> | |
| </item> | |
| <item> | |
| <spec>let use_hintchars_ex=1 or 2</spec> | |
| <description> | |
| 1 で、hintchars の 最初の一文字目の出現頻度を同等にします。 | |
| </description> | |
| <description> | |
| 2 で、hintchars の 桁数を同一にします。 | |
| </description> | |
| </item> | |
| <item> | |
| <spec>:let use_hints_ext_hinttags=1</spec> | |
| <description> | |
| <o>hinttags</o> をクエリーセレクタ版で上書きします | |
| </description> | |
| </item> | |
| <item> | |
| <spec>:let use_hints_ext_caret="[a-zA-Z]"</spec> | |
| <description> | |
| caret mode 開始位置を選択 する Hint を 追加 | |
| </description> | |
| </item> | |
| <item> | |
| <spec>:let use_hints_ext_visual="[a-zA-Z]"</spec> | |
| <description> | |
| visual mode 開始位置を選択 する Hint を 追加 | |
| </description> | |
| </item> | |
| <item> | |
| <spec>:let use_hints_ext_extendedhinttags=1</spec> | |
| <description> | |
| <o>extendedhinttags</o> をクエリーセレクタ版で上書きします | |
| </description> | |
| </item> | |
| <item> | |
| <spec>:let use_hints_ext_tranform=1</spec> | |
| <description> | |
| css transform を 考慮して表示します。 | |
| </description> | |
| </item> | |
| <item> | |
| <spec>:js hints.addSimpleMap(<a>key</a>, <a>callback</a>)</spec> | |
| <description> | |
| Hints に 1キーストーロクのmapを割り当てします。 | |
| <p> | |
| <k name="C-f"/>でpage 送り | |
| <code><![CDATA[ | |
| hints.addSimpleMap("<C-f>", function () { | |
| this.hide(); | |
| buffer.scrollPages(1); | |
| this.show(this._submode, "", content.window); | |
| }); | |
| ]]></code> | |
| </p> | |
| <p> | |
| <k name="C-l"/>でhint の 重なりを整理 | |
| <code><![CDATA[ | |
| hints.addSimpleMap("<C-l>", function () {this.relocation(); }); | |
| ]]></code> | |
| </p> | |
| <p> | |
| <k name="C-h"/>で入力済み char を 削除(=<k name="BS"/>の動作をmap) | |
| <code><![CDATA[ | |
| hints.addSimpleMap(["<C-h>"], function () { | |
| this.onEvent({liberatorString: "<BS>"}); | |
| }); | |
| ]]></code> | |
| </p> | |
| </description> | |
| </item> | |
| <item> | |
| <spec>:js hints.addModeEx(<a>mode</a>, <a>prompt</a>, <a>active</a>, <a>generate</a>)</spec> | |
| <description> | |
| <p> | |
| Hint "z" に window を 4分割 する | |
| <code><![CDATA[ | |
| hints.addModeEx("z", "test", function (val) { | |
| alert(val); | |
| }, function (win, screen) { | |
| let w = screen.right - screen.left; | |
| let h = screen.bottom - screen.top; | |
| let l = screen.left; | |
| let t = screen.top; | |
| return [ | |
| {value: 1 , rect: [plugins.hintsExt.Rect(l , t , l + w/2 , t + h/2)], text: "msg 1", showText: true}, | |
| {value: 2 , rect: [plugins.hintsExt.Rect(l + w/2 , t , l + w , t + h/2)], text: "msg 2", showText: true}, | |
| {value: 3 , rect: [plugins.hintsExt.Rect(l , t + h/2 , l + w/2 , t + h )], text: "msg 3", showText: true}, | |
| {value: 4 , rect: [plugins.hintsExt.Rect(l + w/2 , t + h/2 , l + w , t + h )], text: "msg 4", showText: true}, | |
| ]; | |
| }); | |
| ]]></code> | |
| </p> | |
| </description> | |
| </item> | |
| </plugin>; | |
| //}}} | |
| (function () { | |
| if (parseFloat(Application.version) < 4) return; | |
| var original = modules.hints; | |
| this.onUnload = function onUnload() { | |
| delete this.onUnload; | |
| modules.hints = original; | |
| }; | |
| function HintsExt() { | |
| this.init(); | |
| } | |
| function getUtils(win) win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) | |
| if (!highlight.get("HintExtElem")) { | |
| highlight.loadCSS(<![CDATA[ | |
| HintExtElem,,* { | |
| border: 1px solid rgba(128,128,128,.5); | |
| -moz-border-radius: 2px; | |
| background-color: rgba(255,255,128, .1); | |
| -moz-box-sizing: border-box; | |
| } | |
| HintExtActive,,* {background-color: rgba(128,255,128,.3);} | |
| HintExt,,* {font-size: 12px;color:white;background-color:red;font-weight: bold;text-transform: uppercase;-moz-border-radius: 2px; padding: 0 2px;} | |
| HintExtActive>span,,* {background-color: blue;} | |
| HintExtActive,,* {background-color: rgba(128,255,128,.3);} | |
| HintExtActive>*,,* {background-color: blue;} | |
| HintExt::before,,* { /* no style */ } | |
| ]]>.toString()); | |
| styles.addSheet(true, "HintExtStyle", "*", <><![CDATA[ | |
| [liberator|highlight~='HintExtElem'] { | |
| position: absolute!important; | |
| margin: 0!important; | |
| } | |
| [liberator|highlight~='HintExt'] { | |
| position: absolute!important; | |
| z-index:65535!important; | |
| line-height: 100%!important; | |
| white-space: nowrap; | |
| overflow: visible!important; | |
| } | |
| [liberator|highlight~='HintExt']::before { | |
| content: attr(num); | |
| } | |
| ]]></>.toString(), true); | |
| } | |
| HintsExt.prototype = { | |
| init: function (hints) { | |
| this._hintModes = { | |
| __proto__: original._hintModes, | |
| __iterator__: function iterator() { | |
| var seen = {}; | |
| var names = Object.getOwnPropertyNames(this); | |
| for (let [, name] in Iterator(names)) { | |
| if (name === "__iterator__") continue; | |
| yield [name, this[name]]; | |
| seen[name] = 1; | |
| } | |
| for (let name in this.__proto__) { | |
| if (seen[name]) continue; | |
| yield [name, this[name]]; | |
| } | |
| }, | |
| }; | |
| this.simpleMaps = []; | |
| this._reset(); | |
| }, | |
| get previnput() this._prevInput, | |
| _reset: function () { | |
| statusline.updateInputBuffer(""); | |
| this._hintString = ""; | |
| this._hintNumber = 0; | |
| this._usedTabKey = false; | |
| this._prevInput = ""; | |
| this._pageHints = []; | |
| this._validHints = []; | |
| this._canUpdate = false; | |
| this._docs = []; | |
| hints.escNumbers = false; | |
| if (this._activeTimeout) { | |
| clearTimeout(this._activeTimeout); | |
| } | |
| this._activeTimeout = null; | |
| }, | |
| show: function _show(minor, filter, win) { | |
| try { | |
| let time = Date.now(); | |
| const self = this; | |
| this._pageHints = []; | |
| this._hintMode = this._hintModes[minor]; | |
| liberator.assert(this._hintMode); | |
| var isSelector = this._hintMode.hasOwnProperty(minor); | |
| commandline.input((isSelector ? "ex:" : "") + this._hintMode.prompt, null, { | |
| onChange: function(e) { self._hintString != commandline.command && self._onInput(e); }, | |
| onCancel: function () { self._removeHints(); }, | |
| }); | |
| modes.extended = modes.HINTS; | |
| this._oldHintString = void 0; | |
| this._submode = minor; | |
| this._hintString = filter || ""; | |
| this._hintNumber = 0; | |
| this._usedTabKey = false; | |
| this._prevInput = ""; | |
| this._canUpdate = false; | |
| if (!win) win = content.window; | |
| this._generate(win); | |
| // get all keys from the input queue | |
| liberator.threadYield(false); | |
| this._canUpdate = true; | |
| if (liberator.globalVariables.use_hints_ext_tranform) | |
| this.relocation_transform(); | |
| this._showHints(); | |
| if (this._validHints.length == 0) { | |
| liberator.beep(); | |
| modes.reset(); | |
| } | |
| else if (this._validHints.length == 1) | |
| this._processHints(false); | |
| else // Ticket #185 | |
| this._checkUnique(); | |
| liberator.log(<>hints show: {Date.now() - time}ms</>.toString()); | |
| } catch (ex) { | |
| Cu.reportError(ex); | |
| this._reset(); | |
| } | |
| }, | |
| _showHints: function () { | |
| let pageHints = this._pageHints; | |
| let hintString = this._hintString; | |
| this._docs.forEach(function (e) e.root.style.display = "none"); | |
| this._showActiveHint(null, this._hintNumber || 1); | |
| if (this._prevInput != "number") { | |
| let validHints = this._validHints = []; | |
| let test = hints._hintMatcher(hintString); | |
| for (let i = 0, j = pageHints.length; i < j; ++i) { | |
| let item = pageHints[i]; | |
| if (test(item.text)) { | |
| kNum = validHints.length; | |
| validHints[kNum] = item; | |
| //item.hint.firstChild.setAttribute("number", hints._num2chars(kNum + 1)); | |
| var ri, rj = item.rect_list.length; | |
| for (ri = 0; ri < rj; ri++) | |
| item.rect_list[ri].style.display = ""; | |
| item.label.style.display = ""; | |
| } else { | |
| item.chars = ""; | |
| var ri, rj = item.rect_list.length; | |
| for (ri = 0; ri < rj; ri++) | |
| item.rect_list[ri].style.display = "none" | |
| item.label.style.display = "none"; | |
| } | |
| } | |
| for (let i = 0, j = validHints.length; i < j; ++i) { | |
| let item = validHints[i]; | |
| item.chars = item.showText ? hints._num2chars(i + 1, j) + ":" + item.text : hints._num2chars(i + 1, j); | |
| let node = item.label; | |
| node.removeAttribute("num"); | |
| node.textContent = item.chars; | |
| } | |
| } else { | |
| let num = this._hintNumber == 0 ? "" : this._hintNumberStr; | |
| let len = num.length; | |
| let validHints = this._validHints; | |
| for (let i = 0, j = validHints.length; i < j; ++i) { | |
| let item = validHints[i]; | |
| let show = item.chars.substr(0, len) === num; | |
| let style_display = show ? "" : "none"; | |
| item.label.style.display = style_display; | |
| for (var ri = 0, rj = item.rect_list.length; ri < rj; ri++) | |
| item.rect_list[ri].style.display = style_display; | |
| let node = item.label; | |
| if (show) { | |
| node.setAttribute("num", num); | |
| node.textContent = item.chars.substr(len); | |
| } | |
| } | |
| } | |
| this._docs.forEach(function (e) e.root.style.display = ""); | |
| this._showActiveHint(this._hintNumber || 1); | |
| }, | |
| _iterTags: function (win, screen) { | |
| const doc = win.document; | |
| let winUtils = getUtils(win); | |
| let nodeList = Array.slice(winUtils.nodesFromRect( | |
| screen.left, screen.top, 0, screen.right, screen.bottom, 0, true, true | |
| )); | |
| { // sort | |
| let b = [], item; | |
| for (let i = 0, j = nodeList.length, k = 0; i < j; ++i) { | |
| item = nodeList[i]; | |
| if (item.nodeType == Node.ELEMENT_NODE) | |
| b[k++] = item; | |
| } | |
| //xxx: HTMLAreaElement が 含まれないため | |
| let c = Array.slice(doc.getElementsByTagName("area")); | |
| if (c.length > 0) Array.splice.apply(null, [b, b.length, 0].concat(c)); | |
| b.sort(function (a, b) a.compareDocumentPosition(b) & 0x2); | |
| nodeList = b; | |
| } | |
| let selector = this._hintMode.tags(win, screen); | |
| var matcher; | |
| function makeMatcher(array) { | |
| let pos = 0; | |
| return function _matcher(node) { | |
| let index = array.indexOf(node, pos); | |
| return index != -1 ? (pos = index + 1) : false; | |
| }; | |
| } | |
| if (typeof(selector) == "string") { | |
| if (selector[0] == "/") { // xpath | |
| let e, list = []; | |
| let res = util.evaluateXPath(selector, doc, null, true); | |
| while(e = res.iterateNext()) list[list.length] = e; | |
| matcher = makeMatcher(list); | |
| } else { // selector | |
| matcher = function (node) node.mozMatchesSelector(selector); | |
| } | |
| } else if ("length" in selector) { // Array like | |
| if (Array.isArray && !Array.isArray(selector)) | |
| selector = Array.slice(selector); | |
| matcher = makeMatcher(selector); | |
| } else { //generator | |
| let list = []; | |
| for (let e in selector) list[list.length] = e; | |
| matcher = makeMatcher(list); | |
| } | |
| let prev = void 0; | |
| let toString = Object.prototype.toString; | |
| for (let i = 0, j = nodeList.length; i < j; ++i) { | |
| let showText = false; | |
| let text; | |
| let node = nodeList[i]; | |
| if (node.nodeType != Node.ELEMENT_NODE) continue; | |
| if (!matcher(node)) continue; | |
| else if (prev == (prev = node)) continue; | |
| let rects = node.getClientRects(); | |
| if (rects.length === 0) continue; | |
| let objectName = toString.call(node); | |
| if (objectName === "[object HTMLInputElement]" | |
| || objectName === "[object HTMLSelectElement]" | |
| || objectName === "[object HTMLTextAreaElement]") { | |
| [text, showText] = this._getInputHint(node, doc); | |
| //} else if (objectName === "[object HTMLAreaElement]") { | |
| } else { | |
| text = node.textContent.toLowerCase(); | |
| //if (!text.trim()) { | |
| // let img = node.querySelector("img"); | |
| // if (img) { | |
| // text = img.getAttribute("alt"); | |
| // let isInline = win.getComputedStyle(node, null).display.substring(0, 6) === "inline" | |
| // if (isInline) { | |
| // // xxx: Image | |
| // let r = doc.createRange(); | |
| // r.selectNodeContents(node); | |
| // rects = r.getClientRects(); | |
| // r.detach(); | |
| // } | |
| // } | |
| //} | |
| } | |
| if (objectName === "[object HTMLAreaElement]") { | |
| rects = [this._getAreaOffset(node, rects[0])]; | |
| } | |
| yield { | |
| value: node, | |
| rect: rects, | |
| text: text, | |
| showText: showText, | |
| }; | |
| } | |
| }, | |
| _getAreaOffset: function (elem, rect) { | |
| try { | |
| let coords = elem.getAttribute("coords").split(/\s*[;,]\s*/g).map(Number); | |
| let shape = elem.getAttribute("shape").toLowerCase(); | |
| let x1, y1, x2, y2; | |
| if ((shape === "rect" || shape === "rectangle") && coords.length === 4) { | |
| [x1, y1, x2, y2] = coords; | |
| } else if (shape === "circle" && coords.length === 3) { | |
| let [x, y, r] = coords; | |
| [x1, y1, x2, y2] = [x -r, y -r, x + r, y + r]; | |
| } else if ((shape == "poly" || shape == "polygon") && coords.length % 2 == 0) { | |
| let [x, y] = [coords[0], coords[1]]; | |
| [x1, y1, x2, y2] = [x, y, x, y]; | |
| for (let i = 2, j = coords.length; i < j; i += 2) { | |
| [x, y] = [coords[i], coords[i + 1]]; | |
| if (x < x1) x1 = x; | |
| else if (x > x2) x2 = x; | |
| if (y < y1) y1 = y; | |
| else if (y > y2) y2 = y; | |
| } | |
| } else { | |
| return rect; | |
| } | |
| return HintsExt.Rect(rect.left + x1, rect.top + y1, rect.left + x2, rect.top + y2); | |
| } catch (ex) { | |
| return rect; | |
| } | |
| }, | |
| _generate: function _generate(win, screen) { | |
| if (!win) win = config.browser.contentWindow; | |
| const doc = win.document; | |
| if (!screen) | |
| screen = {top: 0, left: 0, bottom: win.innerHeight, right: win.innerWidth}; | |
| function createStack(top, left) { | |
| if (!top) top = 0; | |
| if (!left) left = 0; | |
| var stack = document.createElementNS(XHTML, "div"); | |
| //stack.style.cssText = "position:absolute; top:" + top + "px;left:"+ left + "px;"; | |
| stack.setAttribute("top", top); | |
| stack.setAttribute("left", left); | |
| return stack; | |
| } | |
| var stack; | |
| if (!screen.stack) { | |
| // xxx: iframe | |
| var stack = document.createElement("stack"); | |
| stack.className = "liberator-hint-stack"; | |
| gBrowser.selectedBrowser.parentNode.appendChild(stack); | |
| } else | |
| stack = screen.stack; | |
| if (screen.right <= 0 || screen.bottom <= 0) return; | |
| const hintMode = this._hintMode; | |
| if (hintMode.generate) { | |
| var obj = hintMode.generate(win, screen); | |
| if (obj.toString() === "[object Generator]"); | |
| else if ("length" in obj) | |
| obj = (function _arrayLike(a) {var i; for(i = 0, j = a.length; i < j; i++) yield a[i];})(obj); | |
| } else { | |
| obj = this._iterTags(win, screen); | |
| } | |
| var pageHints = this._pageHints; | |
| var start = pageHints.length; | |
| var baseNode = util.xmlToDom( | |
| <div highlight="HintExtElem"><span highlight="HintExt"/></div>, | |
| doc); | |
| var root = doc.createElementNS(XHTML, "div"); | |
| root.setAttributeNS(NS, "highlight", "hints"); | |
| root.style.cssText = String(<> | |
| z-index: 65535!important; | |
| position: absolute!important; | |
| top: 0!important; | |
| left: 0!important; | |
| text-align: left!important; | |
| margin: 0 !important; | |
| display: none; | |
| overflow: visible; | |
| </>); | |
| root.style.display = "none"; | |
| var appended; | |
| var item, value, rects; | |
| var top, left, width, height, rect, num, e, style; | |
| var ri, rj; | |
| for (item in obj) { | |
| value = item.value; | |
| rects = item.rect; | |
| appended = false; | |
| for (ri = 0, rj = rects.length; ri < rj; ++ri) { | |
| rect = rects[ri]; | |
| top = rect.top > screen.top ? rect.top : screen.top; | |
| left = rect.left > screen.left ? rect.left : screen.left; | |
| width = rect.right - left; | |
| height = rect.bottom - top; | |
| if ((width < 0) || (height < 0) || (top > screen.bottom) || (left > screen.right)) | |
| continue; | |
| style = "top:" + top + "px;left:" + left + "px;width:" + (width) + "px;height:" + (height) + "px;"; | |
| num = pageHints.length; | |
| e = baseNode.cloneNode(true); | |
| e.setAttribute("style", style); | |
| //e.firstChild.setAttribute("number", this._num2chars(num + 1)); | |
| root.appendChild(e); | |
| if (!appended) { | |
| pageHints[num] = { | |
| hint: e, | |
| label: e.firstChild, | |
| elem: value, | |
| text: item.text || "", | |
| showText: item.showText || false, | |
| left: left, | |
| top: top, | |
| rect_list: [e], | |
| }; | |
| appended = true; | |
| } else { | |
| var rect_list = pageHints[num -1].rect_list; | |
| rect_list[rect_list.length] = e; | |
| } | |
| } | |
| } | |
| var body = doc.body || doc.querySelector("body") || doc.documentElement; | |
| //body.appendChild(root); | |
| stack.appendChild(root); | |
| this._docs.push({ doc: doc, start: start, end: pageHints.length - 1, root: root }); | |
| var frames = Array.slice(win.frames); | |
| var aScreen, frame; | |
| for (var i = 0, j = frames.length; i < j; ++i) { | |
| frame = frames[i]; | |
| rect = frame.frameElement.getBoundingClientRect(); | |
| if (rect.top > screen.bottom || rect.left > screen.right) continue; | |
| aScreen = { | |
| top: Math.max(0, - rect.top), | |
| left: Math.max(0, - rect.left), | |
| }; | |
| aScreen.right = Math.min(screen.right, rect.right) - rect.left; | |
| aScreen.bottom = Math.min(screen.bottom, rect.bottom) - rect.top; | |
| aScreen.stack = createStack(rect.top, rect.left); | |
| stack.appendChild(aScreen.stack); | |
| this._generate(frame, aScreen); | |
| } | |
| }, | |
| onEvent: function onEvent(event) { | |
| let key = events.toString(event); | |
| let followFirst = false; | |
| // clear any timeout which might be active after pressing a number | |
| if (this._activeTimeout) { | |
| clearTimeout(this._activeTimeout); | |
| this._activeTimeout = null; | |
| } | |
| switch (key) { | |
| case "<Return>": | |
| followFirst = true; | |
| break; | |
| case "<Tab>": | |
| case "<S-Tab>": | |
| this._usedTabKey = true; | |
| if (this._hintNumber == 0) | |
| this._hintNumber = 1; | |
| let oldId = this._hintNumber; | |
| let length = this._validHints.length; | |
| let [num, dir] = key == "<Tab>" ? [oldId, 1] : [oldId + length - 2, -1]; | |
| for (let i = 0; i < length; ++i) { | |
| let offset = (num + dir * i) % length; | |
| if (!this._validHints[offset].hint.style.display) { | |
| this._hintNumber = offset + 1; | |
| break; | |
| } | |
| } | |
| this._hintNumberStr = this._num2chars(this._hintNumber, this._validHints.length); | |
| this._showActiveHint(this._hintNumber, oldId); | |
| this._updateStatusline(); | |
| return; | |
| case "<BS>": { | |
| const self = this; | |
| if (this._hintNumber > 0 && !this._usedTabKey) { | |
| this._showActiveHint(null, this._hintNumber); | |
| let str = this._hintNumberStr; | |
| str = str.substr(0, str.length - 1); | |
| this._hintNumberStr = str; | |
| this._hintNumber = str ? this._chars2num(str) : 0; | |
| this._showHints(); | |
| if (this._hintNumber == 0) | |
| this._prevInput = "text"; | |
| } | |
| else { | |
| this._usedTabKey = false; | |
| let oldId = this._hintNumber; | |
| let str = this._hintNumberStr; | |
| str = str.substr(0, str.length - 1); | |
| this._hintNumberStr = str; | |
| this._hintNumber = str ? this._chars2num(str) : 0; | |
| this._showActiveHint(this._hintNumber, oldId); | |
| liberator.beep(); | |
| return; | |
| } | |
| } break; | |
| case mappings.getMapLeader(): | |
| hints.escNumbers = !hints.escNumbers; | |
| if (hints.escNumbers && this._usedTabKey) // this._hintNumber not used normally, but someone may wants to toggle | |
| this._hintNumber = 0; // <tab>s ? this._reset. Prevent to show numbers not entered. | |
| this._updateStatusline(); | |
| return; | |
| default: | |
| if (key in this.simpleMaps) { | |
| this.simpleMaps[key].call(this, key); | |
| } else if (this._isHintNumber(key)) { | |
| this._prevInput = "number"; | |
| let oldHintNumber = this._hintNumber; | |
| if (this._hintNumber == 0 || this._usedTabKey) { | |
| this._usedTabKey = false; | |
| this._hintNumber = this._chars2num(key); | |
| this._hintNumberStr = key; | |
| } | |
| else { | |
| this._hintNumberStr += key; | |
| this._hintNumber = this._chars2num(this._hintNumberStr); | |
| } | |
| //this._updateStatusline(); | |
| if (!this._canUpdate) | |
| return; | |
| //if (this._docs.length == 0) { | |
| // this._generate(); | |
| // this._showHints(); | |
| //} | |
| this._showActiveHint(this._hintNumber, oldHintNumber || 1); | |
| this._showHints(); | |
| liberator.assert(this._hintNumber != 0); | |
| this._checkUnique(); | |
| } | |
| } | |
| this._updateStatusline(); | |
| if (this._canUpdate) { | |
| if (this._docs.length == 0 && this._hintString.length > 0) | |
| this._generate(); | |
| //this._showHints(); | |
| this._processHints(followFirst); | |
| } | |
| }, | |
| _removeHints: function (num) { | |
| if (num) { | |
| this.setTimeout(function() this._removeHints(), num); | |
| return; | |
| } | |
| this._showActiveHint(-1, this._hintNumber); | |
| //this._docs.forEach(function (root) { | |
| // let doc = root.doc; | |
| // let result = util.evaluateXPath("//*[@liberator:highlight='hints']", doc, null, true); | |
| // let hints = [], e; | |
| // while (e = result.iterateNext()) | |
| // hints.push(e); | |
| // while (e = hints.pop()) | |
| // e.parentNode.removeChild(e); | |
| //}); | |
| Array.forEach( | |
| gBrowser.mTabBox.querySelectorAll(".liberator-hint-stack"), | |
| function (e) e.parentNode.removeChild(e)); | |
| this._reset(); | |
| }, | |
| _showActiveHint: function (newId, oldId) { | |
| let oldElem = this._validHints[oldId - 1]; | |
| if (oldElem) { | |
| oldElem.rect_list.forEach(function (e) { | |
| e.setAttributeNS(NS.uri, "highlight", "HintExtElem"); | |
| }); | |
| } | |
| let newElem = this._validHints[newId - 1]; | |
| if (newElem) { | |
| newElem.rect_list.forEach(function (e) { | |
| e.setAttributeNS(NS.uri, "highlight", "HintExtElem HintExtActive"); | |
| }); | |
| } | |
| }, | |
| _isHintNumber: function (key) options.hintchars.indexOf(key) >= 0 || (key in this.simpleMaps), | |
| addSimpleMap: function _addSimpleMap(key, callback) { | |
| const map = this.simpleMaps; | |
| Array.concat(key).forEach(function (key) { | |
| map[events.canonicalKeys(key)] = callback; | |
| }); | |
| }, | |
| removeSimpleMap: function (key) delete this.simpleMaps[key], | |
| relocation: function _relocation() { | |
| let time = Date.now(); | |
| function sort(a, b) { | |
| return (a.left - b.left) || (a.top - b.top); | |
| } | |
| const self = this; | |
| const pageHints = self._pageHints; | |
| self._docs.forEach(function (root) { | |
| const doc = root.doc; | |
| const win = doc.defaultView; | |
| const size = parseFloat(win.getComputedStyle(self._pageHints[root.start].label, null).fontSize) + 2; | |
| const lines = []; | |
| for (let i = root.start, j = root.end; i <= j; i++) { | |
| let item = pageHints[i]; | |
| let index = Math.floor(item.top / size + .5); | |
| if (!(index in lines)) lines[index] = []; | |
| lines[index].push(item); | |
| }; | |
| // xxx: debug | |
| if (0) { | |
| let kE = doc.createElement("div"); | |
| kE.style.position = "absolute"; | |
| kE.style.height = size + "px"; | |
| kE.style.padding = "0"; | |
| kE.style.width = win.innerWidth + "px"; | |
| for (let i in util.range(0, lines.length)) { | |
| let top = i * size; | |
| let kE2 = kE.cloneNode(true); | |
| kE2.style.top = top + "px"; | |
| if (i & 1) kE2.style.backgroundColor = "rgba(128,128,128,.3)"; | |
| root.root.appendChild(kE2); | |
| } | |
| } | |
| lines.forEach(function (line, i) { | |
| let top = i * size; | |
| line.sort(sort); | |
| let left = 0; | |
| line.forEach(function (item) { | |
| let elem = item.label; | |
| let w1 = elem.getBoundingClientRect().width; | |
| let w2 = item.left - left; | |
| if (w1 > w2) { | |
| left += w1 + 1; | |
| w1 = w2; | |
| } else left = item.left + 1; | |
| elem.style.left = -w1 + "px"; | |
| elem.style.top = (top - item.top) + "px"; | |
| }); | |
| }); | |
| }); | |
| liberator.log(<>relocation:{Date.now() - time} ms</>.toString()); | |
| }, | |
| relocation_transform: function relocation_transform() { | |
| if (this._pageHints.transform) return; | |
| var tick = Date.now(); | |
| const self = this; | |
| const pageHints = self._pageHints; | |
| if (pageHints[0].elem instanceof Node) aMode = 0; | |
| else if (pageHints[0].elem[0] instanceof Text) aMode = 1; | |
| else { | |
| Cu.reportError("do not support type"); | |
| return; | |
| } | |
| const mode = aMode; | |
| const isTrans = "transform" in CSSStyleDeclaration.prototype; | |
| const _Moz_ = isTrans ? "" : "-moz-"; | |
| const Moz = isTrans ? "" : "Moz"; | |
| const [transform, transformOrigin, transformStyle] = isTrans | |
| ? ["transform", "transformOrigin", "transformStyle"] | |
| : ["MozTransform", "MozTransformOrigin", "MozTransformStyle"] | |
| self._docs.forEach(function (root) { | |
| const doc = root.doc; | |
| const win = doc.defaultView; | |
| const wm = new WeakMap; | |
| const rootElement = root.root; | |
| const body = rootElement.parentNode; | |
| var fragment = doc.createDocumentFragment(); | |
| if (mode === 1) var fragment_label = doc.createDocumentFragment(); | |
| body.removeChild(rootElement); | |
| var offset_list = []; | |
| var range = doc.createRange(); | |
| function getOffsetPosition(node) { | |
| var left = node.offsetLeft; | |
| var top = node.offsetTop; | |
| var parent = node.parentNode; | |
| if (parent) return ({left: left, top: top}); | |
| var res = getOffsetPosition(parent); | |
| res.top += top; | |
| res.left += left; | |
| return res; | |
| } | |
| for (let i = root.start, j = root.end; i <= j; i++) { | |
| var item = pageHints[i]; | |
| var parent = mode === 0 ? item.elem : item.elem[0].parentNode; | |
| var prev = null, base = null, count = 0, node1; | |
| node1 = parent; | |
| while (parent) { | |
| var node = wm.get(parent); | |
| if (node) { | |
| if (!base) base = node; | |
| if (prev) node.appendChild(prev); | |
| break; | |
| } | |
| node = doc.createElement("div"); | |
| var s = win.getComputedStyle(parent, null); | |
| if (s[transform] !== "none") node.setAttribute("trans", true); | |
| node.style.cssText = ""+<> | |
| position: absolute!important; | |
| {_Moz_}transform: {s[transform]}!important; | |
| {_Moz_}transform-origin: {s[transformOrigin]}!important; | |
| {_Moz_}transform-style: {s[transformStyle]}!important; | |
| top: {parent.offsetTop}px!important; | |
| left: {parent.offsetLeft}px!important; | |
| height: {parent.offsetHeight}px!important; | |
| width: {parent.offsetWidth}px!important; | |
| </>; | |
| node.setAttributeNS(NS, "highlight", "hints"); | |
| if (prev) node.appendChild(prev); | |
| else base = node; | |
| wm.set(parent, node); | |
| prev = node; | |
| parent = parent.offsetParent; | |
| } | |
| if (!node.parentNode) | |
| fragment.appendChild(node); | |
| if (!base.mozMatchesSelector("[trans], [trans] *")) continue; | |
| node = item.hint; | |
| range.selectNode(node1); | |
| var rect = range.getBoundingClientRect(); | |
| var label = item.label; | |
| if (mode === 0) { | |
| var rect_list = item.rect_list; | |
| var rects = range.getClientRects(); | |
| for (var ri = 0, rj = rect_list.length; ri < rj; ri++) { | |
| node = rect_list[ri]; | |
| rect = rects[ri]; | |
| node.style.cssText = ""+<> | |
| top: {rect.top - rects[0].top}px; | |
| left: {rect.left - rects[0].left}px; | |
| width: {rect.width}px; | |
| height: {rect.height}px; | |
| </>; | |
| base.appendChild(node); | |
| } | |
| label.style.cssText = ""+<> | |
| top: {item.top}px; | |
| left: {item.left}px; | |
| </>; | |
| rootElement.appendChild(label); | |
| } else if (mode === 1) { | |
| // caret(visible) mode | |
| node.style.cssText = ""+<> | |
| top: {item.top - rect.top}px; | |
| left: {item.left - rect.left}px; | |
| width: {node.style.width}; | |
| height: {node.style.height}; | |
| </>; | |
| base.appendChild(node); | |
| fragment_label.appendChild(label); | |
| item.label = label; | |
| } | |
| } | |
| body.appendChild(rootElement); | |
| body.appendChild(fragment); | |
| if (fragment_label) { | |
| for (let i = root.start, j = root.end; i <= j; i++) { | |
| var item = pageHints[i]; | |
| if (!item.label) continue; | |
| rect = item.hint.getBoundingClientRect(); | |
| item.label.style.cssText = ""+<> | |
| top: {rect.top > 0 ? rect.top : 0}px; | |
| left: {rect.left > 0 ? rect.left: 0}px; | |
| </>; | |
| } | |
| rootElement.appendChild(fragment_label); | |
| } | |
| range.detach(); | |
| }); | |
| liberator.log("transform relocation: " + (Date.now() - tick) + "ms"); | |
| this._pageHints.transform = true; | |
| }, | |
| addModeEx: function (mode, prompt, action, generate) { | |
| let hintMode = Hints.Mode(prompt, action); | |
| hintMode.generate = generate; | |
| this._hintModes[mode] = hintMode; | |
| } | |
| }; | |
| this.Rect = HintsExt.Rect = function (left, top, right, bottom) ({ | |
| left: left, | |
| top: top, | |
| right: right, | |
| bottom: bottom, | |
| }); | |
| let src = Hints.prototype._processHints.toSource() | |
| .replace("this._validHints[activeIndex];", "this._validHints[activeIndex].elem;") | |
| .replace("e.getAttribute", "e.elem.getAttribute") | |
| //.replace("_validHints[0]", "_validHints[0].elem") | |
| .replace("let firstHref = this._validHints[0]", "let firstHref = let(e = this._validHints[0].elem) !e.getAttribute ? null : e") | |
| ; | |
| HintsExt.prototype._processHints = liberator.eval("(function() " + src + ")()"); | |
| ["_num2chars", "_chars2num", "_checkUnique", "_onInput", "_hintMatcher", "_isHintNumber1", "hide", "setTimeout", "addMode", "_updateStatusline", "_getInputHint", "startExtendedHint"] | |
| .forEach(function (a) HintsExt.prototype[a] = Hints.prototype[a]); | |
| ////input[not(@type='hidden')] | //xhtml:input[not(@type='hidden')] | //a | //xhtml:a | //area | //xhtml:area | //iframe | //xhtml:iframe | //textarea | //xhtml:textarea | //button | //xhtml:button | //select | //xhtml:select | //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @role='link'] | |
| let hinttags = HintsExt.prototype.hinttags | |
| = ["input:not([type='hidden'])", "a", "area", "iframe", "textarea", "button", "select"].concat( | |
| ["onclick", "onmouseover", "onmousedown", "onmouseup", "oncommand", "tabindex"].map(function (s) "[" + s + "]"), | |
| ['link' ,'button' ,'checkbox' ,'combobox' ,'listbox' ,'listitem' ,'menuitem' ,'menuitemcheckbox' , | |
| 'menuitemradio' ,'option' ,'radio' ,'scrollbar' ,'slider' ,'spinbutton' ,'tab' ,'textbox' ,'treeitem'].map(function (s) "[role='"+s+"']") | |
| ).join(","); | |
| let h = new HintsExt(); | |
| modules.hints = h; | |
| hints.addModeEx("f", "Focus Frame", function(win) Buffer.focusedWindow = win, function (win, screen) [{rect: [screen], value: win}]); | |
| if (liberator.globalVariables["use_hints_ext_hinttags"]) { | |
| options.hinttags = hinttags; | |
| } | |
| if (liberator.globalVariables["use_hints_ext_extendedhinttags"]) { | |
| options.extendedhinttags = hinttags; | |
| } | |
| { | |
| let c = liberator.globalVariables["use_hints_ext_caret"]; | |
| let v = liberator.globalVariables["use_hints_ext_visual"]; | |
| if (c || v) { | |
| let extra = { | |
| action: function ([elm, line], link) { | |
| let doc = elm.ownerDocument; | |
| let win = doc.defaultView; | |
| let r = doc.createRange(); | |
| let selection = win.getSelection(); | |
| r.setStart(elm, 0); | |
| selection.removeAllRanges(); | |
| selection.addRange(r); | |
| Buffer.focusedWindow = win; | |
| if (line > 0) { | |
| for (; line > 0; line--) | |
| selection.modify("move", "right", "line"); | |
| selection.modify("move", "left", "lineboundary"); | |
| } | |
| options.setPref("accessibility.browsewithcaret", true); | |
| if (this.prompt === "visual") | |
| modes.set(modes.VISUAL, modes.CARET); | |
| }, | |
| generate: function (win, screen) { | |
| let nodes = getUtils(win).nodesFromRect( | |
| screen.left, screen.top, 0, screen.right, screen.bottom, 0, true, true); | |
| nodes = Array.slice(nodes); | |
| { | |
| let a = []; | |
| for (let i = 0, j = nodes.length; i < j; i++) { | |
| let e = nodes[i]; | |
| if (e.nodeType === Node.TEXT_NODE) | |
| a[a.length] = e; | |
| } | |
| a.sort(function (a, b) a.compareDocumentPosition(b) & 0x2); | |
| nodes = a; | |
| } | |
| var q; | |
| var r = win.document.createRange(); | |
| nodes = nodes.filter(function (a) q !== (q = a)); | |
| for (let i = 0, j = nodes.length; i < j; i++) { | |
| let e = nodes[i]; | |
| r.selectNode(e); | |
| let rects = r.getClientRects(); | |
| for (let ri = 0, rj = rects.length; ri < rj; ri++) { | |
| yield { | |
| rect: [rects[ri]], | |
| value: [e, ri], | |
| text: e.data, | |
| }; | |
| } | |
| } | |
| r.detach(); | |
| } | |
| }; | |
| if (c) hints.addModeEx(c, "caret", extra.action, extra.generate); | |
| if (v) hints.addModeEx(v, "visual", extra.action, extra.generate); | |
| } | |
| } | |
| switch (liberator.globalVariables["use_hintchars_ex"]) { | |
| // hintchars-ex.js の 移植 {{{ | |
| case 1: { | |
| let cache = {}; | |
| function getProgression(r, n) { | |
| let table = cache[r] || (cache[r] = {}); | |
| return table[n] || (table[n] = (Math.pow(r, n) - 1) / (r - 1)); | |
| } | |
| function iterProgression(r) { | |
| let cur = 1, pre; | |
| for (let i = 2;; i++) { | |
| pre = cur; | |
| cur = getProgression(r, i); | |
| yield [i, pre, cur]; | |
| } | |
| } | |
| let logBase = {}; | |
| function Log(base, num) { | |
| base = logBase[base] || (logBase[base] = Math.log(base)); | |
| return Math.log(num) / base; | |
| } | |
| hints._chars2num = function(chars) { | |
| const self = this; | |
| let hintchars = options.hintchars; | |
| let base = hintchars.length; | |
| let num = Array.reduce(chars, function(n, c) n * base + hintchars.indexOf(c), 0); | |
| num += getProgression(base, chars.length); | |
| return num; | |
| }; | |
| hints._num2chars = function(num) { | |
| let hintchars = options.hintchars; | |
| let base = hintchars.length; | |
| let digit; | |
| for (let [i, j, k] in iterProgression(base)) | |
| if (num < k) { | |
| num -= j; | |
| digit = i; | |
| break; | |
| } | |
| let chars = ""; | |
| while (num > 0) { | |
| chars = hintchars[num % base] + chars; | |
| num = Math.floor(num / base); | |
| --digit; | |
| } | |
| return Array(digit).join(hintchars[0]) + chars; | |
| }; | |
| hints._checkUnique = function () { | |
| if (this._hintNumber == 0) | |
| return; | |
| liberator.assert(this._hintNumber <= this._validHints.length); | |
| if (this._hintNumber * options["hintchars"].length < this._validHints.length) { | |
| let timeout = options["hinttimeout"]; | |
| if (timeout > 0) | |
| this._activeTimeout = this.setTimeout(function () { this._processHints(true); }, timeout); | |
| } | |
| else // we have a unique hint | |
| this._processHints(true); | |
| }; | |
| } break; //}}} | |
| case 2: { | |
| hints._chars2num = function(chars) { | |
| let hintchars = options.hintchars; | |
| let base = hintchars.length; | |
| var num = 0; | |
| for (let i = 0, j = chars.length; i < j; ++i) { | |
| num = num * base + hintchars.indexOf(chars[i]); | |
| } | |
| return num + 1; | |
| }; | |
| hints._num2chars = function (num, base) { | |
| let chars = options.hintchars; | |
| let len = chars.length; | |
| var s = ""; | |
| num--; | |
| do { | |
| s = chars[num % len] + s; | |
| num = Math.floor(num / len); | |
| } while (num > 0) | |
| if (base) { | |
| let len = chars.length; | |
| let count = 1; | |
| for (let x = len; x < base; count++) x *= len; | |
| for (let i = count - s.length; i > 0; --i) | |
| s = chars[0] + s; | |
| } | |
| return s; | |
| }; | |
| hints._checkUnique = function () { | |
| if (this._hintNumber == 0) | |
| return; | |
| let vlen = this._validHints.length | |
| let num = this._num2chars(vlen).length; | |
| let minNum = (this._hintNumber - 1) * Math.pow(options.hintchars.length, num - this._hintNumberStr.length); | |
| if (vlen <= minNum) { | |
| let oldId = this._hintNumber; | |
| let str = this._hintNumberStr; | |
| str = str.substr(0, str.length - 1); | |
| this._hintNumberStr = str; | |
| this._hintNumber = str ? this._chars2num(str) : 0; | |
| this._showHints(); | |
| if (this._hintNumber == 0) | |
| this._prevInput = "text"; | |
| this._showActiveHint(null, oldId); | |
| liberator.beep(); | |
| } | |
| if (this._hintNumberStr.length < this._num2chars(this._validHints.length).length) { | |
| let timeout = options["hinttimeout"]; | |
| if (timeout > 0) | |
| this._activeTimeout = this.setTimeout(function () { this._processHints(true); }, timeout); | |
| } | |
| else // we have a unique hint | |
| this._processHints(true); | |
| }; | |
| }break; | |
| } | |
| }).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment