diff --git a/plugins/aardvark.js b/plugins/aardvark.js new file mode 100755 index 00000000..f8f302b9 --- /dev/null +++ b/plugins/aardvark.js @@ -0,0 +1,1310 @@ +/* +Aardvark is BSD licensed. Use as you wish. Gimme credit if you wanna. + +Copyright (c) 2005-2008, Rob Brown + +This work ‘as-is’ we provide. +No warranty, express or implied. +We’ve done our best, +to debug and test. +Liability for damages denied. + +Permission is granted hereby, +to copy, share, and modify. +Use as is fit, +free or for profit. +On this notice these rights rely. +*/ + +/* + * Copyright © 2011 Kris Maglione + * Original portions available under the terms of the MIT license. + */ + +"use strict"; +var INFO = +["plugin", { name: "aardvark", + version: "0.3", + href: "http://dactyl.sf.net/pentadactyl/plugins#aardvark-plugin", + summary: "Aardvark page editor", + xmlns: "dactyl" }, + ["author", { email: "maglione.k@gmail.com" }, + "Kris Maglione"], + ["author", {}, "Rob Brown"], + ["license", {}, + "Some unnamed BSD variant"], + ["project", { name: "Pentadactyl", "min-version": "1.0" }], + ["p", {}, + "This plugin is a Pentadactyl port of the Aardvark Firefox add-on ", + "and bookmarklet. The code is moderately horrendous, but it ", + "generally works."], + ["item", {}, + ["tags", {}, ":aardvark"], + ["spec", {}, ["ex", {}, ":aardvark"]], + ["description", { short: "true" }, + ["p", {}, "Start Aardvark mode."]]], + + ["item", {}, + ["tags", {}, "A_b"], + ["spec", {}, "b"], + ["description", { short: "true" }, + ["p", {}, "Decolorize element."]]], + + ["item", {}, + ["tags", {}, "A_c"], + ["spec", {}, "c"], + ["description", { short: "true" }, + ["p", {}, "Colorize element."]]], + + ["item", {}, + ["tags", {}, "A_d"], + ["spec", {}, "d"], + ["description", { short: "true" }, + ["p", {}, "Remove width-specifying styles."]]], + + ["item", {}, + ["tags", {}, "A_h"], + ["spec", {}, "h"], + ["description", { short: "true" }, + ["p", {}, "Show a list of available keys."]]], + + ["item", {}, + ["tags", {}, "A_i"], + ["spec", {}, "i"], + ["description", { short: "true" }, + ["p", {}, "Isolate element."]]], + + ["item", {}, + ["tags", {}, "A_j"], + ["spec", {}, "j"], + ["description", { short: "true" }, + ["p", {}, "Convert element to JavaScript source."]]], + + ["item", {}, + ["tags", {}, "A_k"], + ["spec", {}, "k"], + ["description", { short: "true" }, + ["p", {}, "Kill an element using the R.I.P. add-on."]]], + + ["item", {}, + ["tags", {}, "A_n"], + ["spec", {}, "n"], + ["description", { short: "true" }, + ["p", {}, "Select a lower element."]]], + + ["item", {}, + ["tags", {}, "A_p"], + ["spec", {}, "p"], + ["description", { short: "true" }, + ["p", {}, "Paste the last yanked element."]]], + + ["item", {}, + ["tags", {}, "A_r"], + ["spec", {}, "r"], + ["description", { short: "true" }, + ["p", {}, "Remove element."]]], + + ["item", {}, + ["tags", {}, "A_s"], + ["spec", {}, "s"], + ["description", { short: "true" }, + ["p", {}, "Select the given element or the contents of the frontmost display box."]]], + + ["item", {}, + ["tags", {}, "A_t"], + ["spec", {}, "t"], + ["description", { short: "true" }, + ["p", {}, "Thunk the element in a global variable."]]], + + ["item", {}, + ["tags", {}, "A_u"], + ["spec", {}, "u"], + ["description", { short: "true" }, + ["p", {}, "Undo the last operation."]]], + + ["item", {}, + ["tags", {}, "A_v"], + ["spec", {}, "v"], + ["description", { short: "true" }, + ["p", {}, "View element source."]]], + + ["item", {}, + ["tags", {}, "A_w"], + ["spec", {}, "w"], + ["description", { short: "true" }, + ["p", {}, "Select a higher element."]]], + + ["item", {}, + ["tags", {}, "A_x"], + ["spec", {}, "x"], + ["description", { short: "true" }, + ["p", {}, "Show the element's XPath."]]]]; + +highlight.loadCSS(String.raw` + AardvarkDBox;;* { + padding: 0; + border: 1px solid #000; + background-color: #888; + color: black; + font-family: arial; + font-size: 13px; + } + AardvarkDragger;;* { + font-size: 12px; + text-align: left; + color: #000; + padding: 2px; + height: 14px; + margin: 0; + cursor: move; + background-color: #d8d7dc; + } + AardvarkClose;;* { + vertical-align: middle; + width: 17px; + height: 17px; + margin: -2px 4px 0 0; + cursor: pointer; + } + AardvarkSelect;;* { + float: right; + font-size: 11px; + color: #008; + margin: 0; + padding: 0; + cursor: pointer; + } + AardvarkInner;;* { + float: left; + margin: 0; + border: 0; + padding: 4px; + font-size: 13px; + color: #000; + background: #fff; + } + + Aardvark;;* { + border-color: black; + border-width: 1px 2px 2px 1px; + border-style: solid; + font-family: arial; + text-align: left; + color: #000; + font-size: 12px; + position: absolute; + padding: 2px 5px; + } + AardvarkLabel;;*;Aardvark { + border-top-width: 0; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + background-color: #fff0cc; + z-index: 5005; + } + AardvarkKeybox;;*;Aardvark { + background-color: #dfd; + z-index: 100006; + } + AardvarkBorder;;* { + position: absolute; + z-index: 5001; + border-color: #f00; + border-style: solid; + } + + AardvarkIsolated;;* { + float: none; + position: static; + padding: 5px; + margin: 5px; + } + AardvarkIsolatedBody;;* { + width: 100%; + background: none; + background-color: white; + background-image: none; + text-align: left; + } + + AardvarkBW;;* { + color: #000 !important; + background-color: #fff !important; + font-family: arial !important; + font-size: 13px !important; + text-align: left !important; + background-image: none !important; + } + AardvarkBW:-moz-any-link;;* { + text-decoration: underline !important; + } + AardvarkDewidthified;;* { + width: auto !important; + } +`); + +group.styles.add("aardvark", "*", String.raw` + [dactyl|highlight~=AardvarkDBox] div.vsblock { + border: 1px solid #ccc; + border-right: 0; + margin: -1px 0 -1px 1em; + padding: 0; + } + [dactyl|highlight~=AardvarkDBox] div.vsline { + border-right: 0; + margin: 0 0 0 .6em; + text-indent: -.6em; + padding: 0; + } + [dactyl|highlight~=AardvarkDBox] div.vsindent { + border-right: 0; + margin: 0 0 0 1.6em; + text-indent: -.6em; + padding: 0; + } + [dactyl|highlight~=AardvarkDBox] span.tag { + color: #c00; + font-weight:bold; + } + [dactyl|highlight~=AardvarkDBox] span.pname { + color: #080; + font-weight: bold; + } + [dactyl|highlight~=AardvarkDBox] span.pval { + color:#00a; + font-weight: bold; + } + [dactyl|highlight~=AardvarkDBox] span.aname { + color: #050; + font-style: italic; + font-weight: normal; + } + [dactyl|highlight~=AardvarkDBox] span.aval { + color:#007; + font-style: italic; + font-weight: normal; + } + + [dactyl|highlight~=AardvarkDBox] a, + [dactyl|highlight~=AardvarkDBox] a:visited { + color: #007; + text-decoration: underline; + } + [dactyl|highlight~=AardvarkDBox] a:hover { + color: #00f; + } + `); + +let images = { + close: `data:image/gif;base64, + R0lGODlhEQARAMMPANuywOyNo+wnUfEENPN1kfFLbre0u/78/P7S28ZWcIdsdaJIXolVY7IyTpKS + mP///yH5BAEAAA8ALAAAAAARABEAAARw8MkJQAAz5yCGHwKhTcVnglhWDqsnlIIxBV5wEC6CdMnU + FYcgQYAIIjwywIcQbB52HsVDuXRCPQsD1eXEfbJbovP2Ycg64idTGJV4bdB1qeHweYbLlUImAXRO + ZXUaCYANCoIjBgoLCwyHfCMTBpOREQA7 + `, +}; + +for (let k in images) + images[k] = String(images[k].replace(/\s+/g, "")); + +function nodeName(node) { + if (node.nodeName == node.localName.toUpperCase()) + return node.localName; + return node.nodeName; +} + +var AardvarkDBox = Class("AardvarkDBox", { + init: function (aardvark, params) { + if (!("dragger" in params)) + params.dragger = true; + + this.aardvark = aardvark; + + this.bgColor = params.bgColor || ""; + this.position = params.position; + this.id = aardvark.dBoxId++; + + var dims = aardvark.getWindowDimensions(); + dims.width -= 15; + dims.height -= 15; + this.dims = dims; + + let style = "position: absolute; z-index: 100000;" + + "top: " + dims.scrollY + "px;" + + "left: " + dims.scrollX + "px;" + + "max-width: " + (dims.width - 20) + "px;" + + "max-height: " + (dims.height - 20) + "px;" + + let outerDiv = + ["div", { key: "outerContainer", + highlight: "AardvarkDBox", + style: style, + id: "aardvarkdbox-" + aardvark.dBoxId }, + !params.dragger ? [] : + ["div", { command: "drag", key: "dragBar", + highlight: "AardvarkDragger" }, + ["img", { src: images.close, + command: "kill", + highlight: "AardvarkClose", + alt: "close" }], + !params.selectLink ? [] : + ["a", { highlight: "AardvarkSelect", command: "select" }, + "select all"], + params.title || ""], + ["div", { key: "innerContainer", highlight: "AardvarkInner", + style: "overflow: " + (params.hideScrollbar ? "hidden" : "auto") }, + content || ""]]; + + outerDiv = DOM.fromJSON(outerDiv, aardvark.doc, this); + outerDiv.isAardvark = true; + outerDiv.dbox = this; + DOM(outerDiv).mousedown(this.closure.onMouseDown) + .appendTo(aardvark.container); + + if (params.data) + DOM(this.innerContainer).empty().append(params.data); + + aardvark.dBoxId++; + return this; + }, + + onMouseDown: function onMouseDown(event) { + let command = DOM(event.target).attr("command"); + if (command in this && event.button == 0) { + this[command](event); + event.preventDefault(); + } + }, + + kill: function kill() { + if (!this.outerContainer.parentNode) + return false; + DOM(this.outerContainer).remove(); + return true; + }, + + select: function select() { + this.aardvark.highlightText(this.innerContainer); + }, + + drag: function drag() { + let elem = this.outerContainer; + this.aardvark.dragElement = elem; + this.aardvark.dragStartPos = this.aardvark.getPos(elem); + this.aardvark.dragClickX = this.aardvark.mousePosX; + this.aardvark.dragClickY = this.aardvark.mousePosY; + }, + + show: function show() { + var dims = this.dims; + var draggerHeight = 1; + if (this.dragBar) { + draggerHeight = 18; + } + var w = this.innerContainer.offsetWidth; + if (!this.innerContainer.style.width || this.innerContainer.style.width != "") + w += 25; + + if (this.dragBar) + w = Math.max(w, this.dragBar.offsetWidth + 12); + w = Math.min(w, dims.width - 20); + + this.outerContainer.style.width = w + "px"; + this.innerContainer.style.width = w + "px"; + + var diff; + if ((diff = this.innerContainer.offsetWidth - w) > 0) + this.innerContainer.style.width = (w - diff) + "px"; + + var h = Math.min(dims.height - 25, + this.innerContainer.offsetHeight); + + this.outerContainer.style.height = (h + draggerHeight) + "px"; + this.innerContainer.style.height = h + "px"; + if ((diff = this.innerContainer.offsetHeight - h) > 0) + this.innerContainer.style.height = (h - diff) + "px"; + + this.innerContainer.style.backgroundColor = this.bgColor; + + if (this.position) { + var { x, y } = this.position; + } + else { + x = dims.width / 2 - w / 2; + y = dims.height / 2 - h / 2; + } + x += dims.scrollX; + y += dims.scrollY; + + this.aardvark.moveElem(this.outerContainer, x, y); + } +}); + +var Aardvark = Class("Aardvark", { + init: function init(window) { + this.window = window; + this.doc = window.document; + + this.top = DOM(["div", { highlight: "hints" }], this.doc) + .appendTo(this.doc.body); + this.top.style.height; + this.top[0].isAardvark = true; + + this.container = this.top.findAnon("anonid", "hints") || this.container; + + modes.push(modes.AARDVARK, null, this.closure); + + this.undoStack = []; + + this.makeElems(); + this.selectedElem = null; + }, + + get mappingSelf() { return this; }, + + enter: function enter() { + group.events.listen(this.doc, this, "events"); + }, + + leave: function leave(stack) { + group.events.unlisten(this.doc, this, "events"); + if (!stack.push) + this.top.remove(); + }, + + closePanels: function closePanels() { + let panels = this.container.children + .filter((elem) => DOM(elem).highlight.has("AardvarkDBox")); + panels.remove(); + return panels.length; + }, + + rip: function rip(elem) { + if (window.RemoveItPermanently) + RemoveItPermanently.doRipNode(elem); + else { + var dbox = AardvarkDBox(this, { data: this.strings.ripHelp }); + dbox.show(); + } + return true; + }, + + wider: function wider(elem) { + if (elem && elem.parentNode) { + var newElem = this.findValidElement(elem.parentNode); + if (!newElem) + return false; + + if (this.widerStack && + this.widerStack.length > 0 && + this.widerStack[this.widerStack.length - 1] == elem) { + this.widerStack.push(newElem); + } + else { + this.widerStack = [elem, newElem]; + } + this.selectedElem = newElem; + this.showBoxAndLabel(newElem, this.makeElementLabelString(newElem)); + this.didWider = true; + return true; + } + return false; + }, + + narrower: function narrower(elem) { + if (elem) { + if (this.widerStack && + this.widerStack.length > 1 && + this.widerStack[this.widerStack.length - 1] == elem) { + + this.widerStack.pop(); + let newElem = this.widerStack[this.widerStack.length - 1]; + this.selectedElem = newElem; + this.showBoxAndLabel(newElem, this.makeElementLabelString(newElem)); + this.didWider = true; + return true; + } + } + return false; + }, + + select: function select(elem) { + let panels = this.container.children + .filter(elem => elem.dbox); + if (panels.length) + panels[panels.length - 1].dbox.select(); + else if (elem) + this.highlightText(elem); + }, + + viewSource: function viewSource(elem) { + var dbox = AardvarkDBox(this, { + selectLink: true, + title: this.strings.viewHtmlSource, + data: this.getOuterHtmlFormatted(elem, 0) + }); + dbox.show(); + return true; + }, + + colorize: function colorize(elem) { + let val = () => Math.round(Math.random() * 16).toString(16); + + this.undoStack.push({ + styles: { + backgroundColor: elem.style.backgroundColor, + backgroundImage: elem.style.backgroundImage + }, + undo: function undo() { DOM(elem).css(this.styles); } + }); + + DOM(elem).css({ + backgroundColor: "#" + val() + val() + val(), + backgroundImage: "" + }); + return true; + }, + + removeElement: function removeElement(elem) { + if (elem.parentNode != null) { + DOM(elem).hide(); + this.undoStack.push({ + mode: "R", + elem: elem, + undo: function () { DOM(this.elem).show() } + }); + this.clearBox(); + return true; + } + return false; + }, + + paste: function paste(target) { + if (target.parentNode != null) { + if (this.undoStack.length && this.undoStack.slice(-1)[0].mode == "R") { + let src = this.undoStack.pop().elem; + + if (src.localName == "tr" && target.localName != "tr") { + var t = DOM(["table", {}, ["tbody"]], this.doc)[0]; + t.firstChild.appendChild(src); + DOM(src).show(); + src = t; + } + else if (src.localName == "td" && target.localName != "td") + src = DOM(["div"], this.doc).append(DOM(src).contents()).append(src)[0]; + else { + src.parentNode.removeChild(src); + DOM(src).show(); + } + + if (target.localName == "td" && src.localName != "td") + target.insertBefore(src, target.firstChild); + else if (target.localName == "tr" && src.localName != "tr") + target.insertBefore(src, target.firstChild.firstChild); + else + target.parentNode.insertBefore(src, target); + + this.clearBox(); + } + } + return true; + }, + + global: function xpath(target) { + commandline.input("Variable name: ", function (res) { + if (res) { + userContext[res] = target; + DOM(target.ownerDocument.defaultView).unload(function () { + if (userContext[res] == target) + delete userContext[res]; + }); + } + }); + }, + + xpath: function xpath(target) { + AardvarkDBox(this, { + selectLink: true, + title: this.strings.viewXPath, + data: DOM(target).xpath + }).show(); + }, + + isolateElement: function isolateElement(elem) { + let { body } = this.doc; + if (elem.parentNode != null) { + this.clearBox(); + var clone = elem.cloneNode(true); + DOM(clone).highlight.add("AardvarkIsolated"); + + if (clone.localName == "tr" || clone.localName == "td") { + if (clone.localName == "td") + clone = DOM(["tr"], this.doc).append(clone)[0]; + + t = DOM(["table", {}, ["tbody"]], this.doc)[0]; + t.firstChild.appendChild(clone); + clone = t; + } + + var undoData = Array.filter(this.doc.body.childNodes, + e => !e.isAardvark); + DOM(undoData).remove(); + undoData.mode = "I"; + undoData.isolated = DOM(body).highlight.has("AardvarkIsolatedBody"); + undoData.undo = function () { + DOM(body.childNodes).filter(e => !e.isAardvark) + .remove(); + + DOM(body).highlight.toggle("AardvarkIsolatedBody", this.isolated) + .prepend(this); + }; + this.undoStack.push(undoData); + + DOM(this.doc.body).highlight.add("AardvarkIsolatedBody") + .append(clone); + + this.window.scroll(0, 0); + } + return true; + }, + + _undo: function _undo(group) { + return function undo() { + (function rec(node) { + DOM(node).highlight.remove(group) + .children.each(rec); + })(this.elem); + }; + }, + + deWidthify: function deWidthify(node) { + if (node instanceof Element) { + if (node.localName != "img") { + DOM(node).highlight.add("AardvarkDewidthified") + .children.each(this.closure.deWidthify); + + if (arguments.length == 1) { + this.clearBox(); + this.undoStack.push({ elem: node, undo: this._undo("AardvarkDewidthified") }); + } + } + } + return true; + }, + + blackOnWhite: function blackOnWhite(node) { + if (node instanceof Element) { + if (node.localName != "img") { + DOM(node).highlight.add("AardvarkBW") + .children.each(this.closure.blackOnWhite); + + if (arguments.length == 1) + this.undoStack.push({ elem: node, undo: this._undo("AardvarkBW") }); + } + } + return true; + }, + + getOuterHtmlFormatted: function getOuterHtmlFormatted(node, indent) { + var res = []; + switch (node.nodeType) { + case Node.ELEMENT_NODE: + if (node.style.display == "none") + break; + + var isLeaf = node.childNodes.length == 0 + && this.leafElems[node.localName]; + var isTbody = node.localName == "tbody" && node.attributes.length == 0; + if (isTbody) { + for (let node of node.childNodes) + res.push(this.getOuterHtmlFormatted(node, indent)); + } + else { + let inner = ["", "<", + ["span", { class: "tag" }, nodeName(node)]]; + Array.forEach(node.attributes, function (attr) { + if (attr.value != null) { + let value = ["span", { class: "pval" }, attr.value]; + if (attr.localName == "style") + value = template.map(Styles.propertyIter(value), + ({ name, value }) => + [["span", { class: "aname" }, name], + ": ", + ["span", { class: "aval" }, value], + ";"], + " "); + + inner.push(" ", + ["span", { class: "pname" }, nodeName(attr)], + '="', + value, + '"'); + } + }, this) + + if (isLeaf) + res.push(["div", { class: "vsindent" }, + inner, + "/>"]); + else { + inner = [["div", { class: "vsline" }, + inner]]; + + for (let node of node.childNodes) + inner.push(this.getOuterHtmlFormatted(node, indent + 1)); + + res.push(["div", { class: "vsline" }, + ""]); + + if (indent > 0) + res.push(["div", { class: "vsblock" }, inner]); + else + res.push(inner); + } + } + break; + case Node.TEXT_NODE: + var v = DOM.escapeHTML(node.nodeValue).trim(); + if (v) + res.push(["div", { class: "vsindent" }, + v]); + break; + case Node.CDATA_SECTION_NODE: + res.push(["div", { class: "vsindent" }, + ""]); + break; + case Node.ENTITY_REFERENCE_NODE: + res.push(["", "&", nodeName(node)], ["br"]); + break; + case Node.COMMENT_NODE: + res.push(["div", { class: "vsindent" }, + ""]); + break; + } + return res; + }, + + camelCaseProps: { + }, + + domJavascript: function domJavascript(node, indent) { + let FmtString = (str, color) => ["", '"', + ["span", { style: "color:" + (color || "#00b") + "; font-weight: bold" }, + util.escapeString(str, "")], + '"']; + + let self = this; + var indentStr = ""; + for (var c = 0; c < indent; c++) + indentStr += " "; + + switch (node.nodeType) { + case Node.ELEMENT_NODE: + if (node.style.display == "none") + break; + + var isLeaf = node.childNodes.length == 0 + && this.leafElems[node.localName]; + var children = []; + var numChildren = 0; + if (!isLeaf) { + numChildren = node.childNodes.length; + children = template.map(node.childNodes, + node => ["", indentStr, " ", + self.domJavascript(node, indent + 1)], + ["", ",", ["br"]]); + } + + var properties = []; + var styles = []; + Array.forEach(node.attributes, function ({ nodeName, value }) { + if (value != null) { + switch (nodeName) { + case "style": + for (let { name, value } of Styles.propertyIter(value)) + styles.push(["", FmtString(util.camelCase(name), "#060"), ":", + FmtString(value.trim(), "#008")]); + break; + default: + nodeName = this.camelCaseProps[n] || nodeName; + + properties.push(["", FmtString(nodeName, "#080"), ":", + FmtString(value, "#00b")]); + break; + } + } + }, this); + + if (styles.length) { + styles = template.map(styles, util.identity, ", "); + properties.unshift(["", FmtString("style", "080"), ":", + "{", styles, "}"]); + } + + let numProps = properties.length; + properties = template.map(properties, util.identity, ", "); + properties = ["", "{", properties, "}"]; + + let xml = ["", "[", FmtString(nodeName(node), "red")]; + if (numChildren) { + if (numProps) + return ["", xml, ", ", properties, ",\u000a;", children, "]"]; + else + return ["", xml, ",'\u000a", children, "]"]; + } + else if (numProps) + return ["", xml, ", ", properties, "]"]; + else + return ["", xml, "]"]; + break; + case Node.TEXT_NODE: + var n = node.nodeValue; + if (node.nodeValue != "") + n = util.escapeString(n, ""); + + n = n.trim(); + if (n.length > 0) + return ["", '"', ["b", {}, n], '"']; + break; + case Node.CDATA_SECTION_NODE: + case Node.ENTITY_REFERENCE_NODE: + case Node.COMMENT_NODE: + break; + } + return null; + }, + + makeJavascript: function makeJavascript(elem) { + var dbox = AardvarkDBox(this, { + selectLink: true, + title: this.strings.javascriptDomCode, + data: [ + ["pre", { style: "margin:0; width: 97%" }, + this.domJavascript(elem, 0)], + ["br"] + ] + }); + dbox.show(); + return true; + }, + + undo: function undo() { + if (!this.undoStack.length) + return false; + + this.clearBox(); + this.undoStack.pop().undo(); + return true; + }, + + showMenu: function showMenu() { + if (this.helpBox && this.helpBox.kill()) { + delete this.helpBox; + return; + } + + var s = ["table", { style: "margin:5px 10px 0 10px;" }]; + for (let map of mappings.iterate(modes.AARDVARK)) + if (map.name != "") + s.push(["tr", {}, + ["td", { style: "padding: 3px 7px; border: 1px solid black; font-family: courier; font-weight: bold; background-color: #fff" }, + map.name], + ["td", { style: "padding: 3px 7px; font-size: .9em; text-align: left;" }, + map.description] + ]); + + var dbox = AardvarkDBox(this, { + bgColor: "#fff2db", + selectLink: true, + position: { x: 20, y: 20 }, + title: this.strings.aardvarkKeystrokes, + data: s + }); + dbox.show(); + this.helpBox = dbox; + return true; + }, + + highlightText: function highlightText(elem) { + let s = this.window.getSelection(); + s.removeAllRanges(); + s.addRange(RangeFind.nodeRange(elem)); + dactyl.clipboardWrite(DOM.stringify(s), false, "selection"); + }, + + strings: { + aardvarkKeystrokes: 'Aardvark keystrokes', + blackOnWhite: 'black on white', + deWidthify: 'de-widthify', + description: 'Utility for cleaning up a page prior to printing, and for analyzing a page.', + help: 'toggle &help', + javascriptDomCode: 'Javascript DOM code', + ripHelp: ["center", {}, + "If you install the excellent ", + "", ["a", { href: "http://addons.mozilla.org/addon/521/", target: "_blank" }, "R.I.P."], ["br"], + "(Remove It Permanently), the K command will", ["br"], + "permanently remove items from a page."], + undo: 'undo', + viewHtmlSource: 'View HTML source', + viewSource: 'view source', + viewXPath: 'View XPath' + }, + +//------------------------------------------------- +// create the box and tag etc (done once and saved) + makeElems: function makeElems() { + this.borderElems = {}; + + for (let side of ["top", "bottom", "left", "right"]) { + this.borderElems[side] = DOM(["div", { style: "display: none; border-width: 0; border-" + side + "-width: 2px;", + highlight: "AardvarkBorder" }], + this.doc).appendTo(this.container); + } + + this.labelElem = DOM(["div", { style: "display: none;", highlight: "AardvarkLabel" }], + this.doc).appendTo(this.container)[0]; + + this.keyboxElem = DOM(["div", { style: "display: none;", highlight: "AardvarkKeybox" }], + this.doc).appendTo(this.container); + }, + +//------------------------------------------------- +// show the red box around the element, and display +// the string in the little tag + showBoxAndLabel: function showBoxAndLabel(elem, label) { + var pos = this.getPos(elem); + var dims = this.getWindowDimensions(); + var y = pos.y; + + let width = parseFloat(this.borderElems.left.style.borderLeftWidth); + DOM(values(this.borderElems).map(e => e[0])).css({ + left: pos.x - width + "px", + top: pos.y - width + "px", + width: elem.offsetWidth + 2 * width + "px", + height: elem.offsetHeight + 2 * width + "px" + }).show(); + + this.borderElems.left.css({ + width: 0 + }); + this.borderElems.right.css({ + width: 0, + left: (pos.x + elem.offsetWidth) + "px" + }); + this.borderElems.top.css({ + height: 0 + }); + this.borderElems.bottom.css({ + height: 0, + top: (pos.y + elem.offsetHeight) + "px" + }); + + y += elem.offsetHeight + 2; + DOM(this.labelElem).empty().append(label).show(); + + this.labelElem.style.borderTopWidth = ""; + this.labelElem.style.borderTopLeftRadius = ""; + this.labelElem.style.borderTopRightRadius = ""; + if ((y + this.labelElem.offsetHeight) >= dims.scrollY + dims.height) { + this.labelElem.style.cssText += "border-top-width: 1px !important; \ + border-top-left-radius: 6px !important; \ + border-top-right-radius: 6px !important;"; + y = (dims.scrollY + dims.height) - this.labelElem.offsetHeight; + } + else if (this.labelElem.offsetWidth > elem.offsetWidth) { + this.labelElem.style.cssText += "border-top-width: 1px !important; \ + border-top-right-radius: 6px !important;"; + } + this.moveElem(this.labelElem, pos.x + 2, y); + }, + +//------------------------------------------------- +// remove the red box and tag + clearBox: function clearBox() { + this.selectedElem = null; + if (this.borderElems != null) { + for (let elem of values(this.borderElems)) + elem.hide(); + DOM(this.labelElem).hide(); + } + }, + +//------------------------------------------------- + hideKeybox: function hideKeybox() { + this.keyboxElem.hide(); + this.keyboxTimeout = null; + }, + +//------------------------------------------------- + showKeybox: function showKeybox(key){ + if (this.keyboxElem == null) + return; + + this.keyboxElem.empty().append( + template.highlightRegexp(key.aardvarkSpec || key.description, /&(.)/g, + (m, m1) => ["b", { style: "font-size: 2em" }, m1])); + + var dims = this.getWindowDimensions(); + var y = Math.max(0, dims.scrollY + this.mousePosY + 10); + if (y > (dims.scrollY + dims.height) - 30) + y = (dims.scrollY + dims.height) - 60; + + var x = Math.max(0, this.mousePosX + 10); + if (x > (dims.scrollX + dims.width) - 60) + x = (dims.scrollX + dims.width) - 100; + + this.moveElem(this.keyboxElem, x, y); + + this.keyboxElem.show(); + if (this.keyboxTimeout) + this.keyboxTimeout.cancel(); + this.keyboxTimeout = this.timeout(this.hideKeybox, 400); + }, + +//------------------------------------------------- + makeElementLabelString: function makeElementLabelString(elem) { + var s = [["b", { style: "color:#000" }, nodeName(elem)]]; + if (elem.id != "") + s.push(["b", {}, "#"], ["i", {}, elem.id]); + + if (elem.className != "") + s.push(template.map(Array.slice(elem.classList), + clas => [["b", {}, "."], ["i", {}, clas]])); + return s; + }, + + events: { + mouseup: function onMouseUp(evt) { + if (this.dragElement) { + delete this.dragElement; + delete this.dragClickX; + delete this.dragClickY; + delete this.dragStartPos; + } + return false; + }, + + // the following three functions are the main event handlers + //------------------------------------------------- + mousemove: function onMouseMove(evt) { + if (this.mousePosX == evt.clientX && + this.mousePosY == evt.clientY) { + this.moved = false; + return; + } + this.mousePosX = evt.clientX; + this.mousePosY = evt.clientY; + if (this.dragElement) { + this.moveElem(this.dragElement, + (this.mousePosX - this.dragClickX) + this.dragStartPos.x, + (this.mousePosY - this.dragClickY) + this.dragStartPos.y); + this.moved = false; + } + else { + this.moved = true; + } + evt.preventDefault(); + }, + + //------------------------------------------------- + mouseover: function onMouseOver(evt) { + if (!this.moved) + return; + + this.choose(evt.target); + }, + + click: function click(evt) { + this.choose(evt.target); + } + }, + + choose: function choose(elem) { + if (elem == this.labelElem || ~values(this.borderElems).indexOf(elem)) + this.clearBox(); + + if (elem.isAardvark || DOM(elem).ancestors.some(e => e.isAardvark)) + return; + + if (elem == null) { + this.clearBox(); + return; + } + elem = this.findValidElement(elem); + if (elem == null) { + this.clearBox(); + return; + } + this.didWider = false; + if (elem != this.selectedElem) { + this.widerStack = null; + this.selectedElem = elem; + this.showBoxAndLabel(elem, this.makeElementLabelString(elem)); + this.innerItem = null; + this.moved = false; + } + }, + +//------------------------------------------------- +// given an element, walk upwards to find the first +// valid selectable element + findValidElement: function findValidElement(elem) { + for (; elem; elem = elem.parentNode) { + if (Set.has(this.alwaysValidElements, elem.localName)) + break; + + let { display } = DOM(elem).style; + if (Set.has(this.validIfBlockElements, elem.localName) && display == "block") + break; + if (Set.has(this.validIfNotInlineElements, elem.localName) && display != "inline") + break; + } + return elem; + }, + +//----------------------------------------------------- + getPos: function getPos(elem) { + let body = this.doc.body || this.doc.documentElement; + let style = DOM(body).style; + + if (~["absolute", "fixed", "relative"].indexOf(style.position)) { + let rect = body.getClientRects()[0]; + var offsets = [-rect.left, -rect.top]; + } + else + var offsets = [this.doc.defaultView.scrollX, this.doc.defaultView.scrollY]; + + let rect = DOM(elem).rect; + var pos = { + x: rect.left + offsets[0], + y: rect.top + offsets[1] + }; + return pos; + }, + +//----------------------------------------------------- +// move a div (or whatever) to an x y location + moveElem: function moveElem(elem, x, y) { + DOM(elem).css({ left: x + "px", top: y + "px" }); + }, + +//------------------------------------------------- + getWindowDimensions: function getWindowDimensions() { + var out = {}; + out.scrollX = this.window.pageXOffset; + out.scrollY = this.window.pageYOffset; + if (this.doc.compatMode == "BackCompat") { + out.width = this.doc.body.clientWidth; + out.height = this.doc.body.clientHeight; + } + else { + out.width = this.doc.documentElement.clientWidth; + out.height = this.doc.documentElement.clientHeight; + } + return out; + }, + + dBoxId: 0, + + alwaysValidElements: Set(["applet", "blockquote", "div", "form", + "h1", "h2", "h3", "iframe", "img", "object", + "p", "table", "td", "th", "tr"]), + + validIfBlockElements: Set(["a", "span"]), + + validIfNotInlineElements: Set(["code", "li", "ol", "pre", "ul"]), + + leafElems: Set(["area", "base", "basefont", "br", "col", "frame", "hr", + "img", "input", "isindex", "link", "meta", "param"]) +}); + +modes.addMode("AARDVARK", { + char: "A", + description: "Aardvark page editing mode", + bases: [modes.COMMAND], + ownsBuffer: true +}); + +let aardvark = Aardvark.prototype; +aardvark.commands = {}; + +// 0: name (member of aardvark.strings, or literal string) +// 1: function +// 2: needs element +// 3: 'true' for extension only +var commands = [ + ["wider", "wider", true], + ["narrower", "narrower", true], + ["undo", "undo", false], + ["remove", "removeElement", true], + ["kill", "rip", true], + ["isolate", "isolateElement", true], + ["blackOnWhite", "blackOnWhite", true], + ["deWidthify", "deWidthify", true], + ["colorize", "colorize", true], + ["viewSource", "viewSource", true], + ["javascript", "makeJavascript", true], + ["paste", "paste", true], + ["select", "select", false], + ["thunk", "global", true], + ["xpath", "xpath", true], + ["help", "showMenu", false], +]; + +commands.forEach(function([fullname, func, needsElement]) { + if (aardvark.strings[fullname]) + fullname = aardvark.strings[fullname]; + + let name = fullname.replace("&", ""); + if (!/&/.test(fullname)) + fullname = "&" + fullname; + let key = /&(.)/.exec(fullname)[1]; + + aardvark.commands[key] = { + name: name, + fullName: fullname, + func: func, + needsElement: needsElement, + }; + + group.mappings.add([modes.AARDVARK], + [key], + name, + function({ self }) { + if (needsElement) { + if (self.selectedElem && self[func](self.selectedElem) == true) + self.showKeybox(this); + } + else { + if (self[func](self.selectedElem) == true) + self.showKeybox(this); + } + }, { + aardvarkSpec: fullname + }); +}); + +group.mappings.add([modes.AARDVARK], + ["", ""], + "Close open panels or leave Aardvark mode", + function ({ self }) { + if (!self.closePanels()) + modes.remove(modes.AARDVARK); + }); + +group.commands.add(["aardvark"], + "Aardvark page editor", + function (args) { + dactyl.assert(!modes.have(modes.AARDVARK)) + Aardvark(buffer.focusedFrame); + }, + { argCount: 0 }, + true); + +function onUnload() { + modes.removeMode(modes.AARDVARK); +} + diff --git a/plugins/curl.js b/plugins/curl.js new file mode 100755 index 00000000..09b2bdd7 --- /dev/null +++ b/plugins/curl.js @@ -0,0 +1,51 @@ +"use strict"; +var INFO = +["plugin", { name: "curl", + version: "0.3", + href: "http://dactyl.sf.net/pentadactyl/plugins#curl-plugin", + summary: "Curl command-line generator", + xmlns: "dactyl" }, + ["author", { email: "maglione.k@gmail.com" }, + "Kris Maglione"], + ["license", { href: "http://opensource.org/licenses/mit-license.php" }, + "MIT"], + ["project", { name: "Pentadactyl", "min-version": "1.0" }], + ["p", {}, + "This plugin provides a means to generate a ", ["tt", {}, "curl(1)"], " ", + "command-line from the data in a given form."], + ["item", {}, + ["tags", {}, ";C"], + ["strut"], + ["spec", {}, ";C"], + ["description", {}, + ["p", {}, + "Generates a curl command-line from the data in the selected form. ", + "The command includes the data from each form element, along with ", + "the current User-Agent string and the cookies for the current ", + "page."]]]]; + +hints.addMode('C', "Generate curl command for a form", function(elem) { + if (elem.form) + var { url, postData, elements } = DOM(elem).formData; + else + var url = elem.getAttribute("href"); + + if (!url || /^javascript:/.test(url)) + return; + + url = util.newURI(url, null, + elem.ownerDocument.documentURIObject).spec; + + let { shellEscape } = util.closure; + + dactyl.clipboardWrite(["curl"].concat( + [].concat( + [["--form-string", shellEscape(datum)] for (datum of (elements || []))], + postData != null && !elements.length ? [["-d", shellEscape("")]] : [], + [["-H", shellEscape("Cookie: " + elem.ownerDocument.cookie)], + ["-A", shellEscape(navigator.userAgent)], + [shellEscape(url)]] + ).map(function(e) e.join(" ")).join(" \\\n ")).join(" "), true); +}); + +/* vim:se sts=4 sw=4 et: */ diff --git a/plugins/flashblock.js b/plugins/flashblock.js new file mode 100755 index 00000000..9ecb6901 --- /dev/null +++ b/plugins/flashblock.js @@ -0,0 +1,468 @@ +"use strict"; +var INFO = +["plugin", { name: "flashblock", + version: "1.3", + href: "http://dactyl.sf.net/pentadactyl/plugins#flashblock-plugin", + summary: "Flash Blocker", + xmlns: "dactyl" }, + ["author", { email: "maglione.k@gmail.com" }, + "Kris Maglione"], + ["license", { href: "http://opensource.org/licenses/mit-license.php" }, + "MIT"], + ["project", { name: "Pentadactyl", "min-version": "1.0" }], + ["p", {}, + "This plugin provides the same features as the ever popular FlashBlock ", + "Firefox add-on. Place holders are substituted for flash animations and ", + "embedded videos. When clicked, the original embedded content is ", + "restored. Additionally, this plugin provides options to control which ", + "sites can play animations without restrictions and triggers to toggle ", + "the playing of animations on the current page."], + + ["item", {}, + ["tags", {}, "'fb' 'flashblock'"], + ["spec", {}, "'flashblock' 'fb'"], + ["type", {}, "boolean"], + ["default", {}, "true"], + ["description", {}, + ["p", {}, + "Controls the blocking of flash animations. When true, place ", + "holders are substituted for flash animations on untrusted sites."]]], + + ["item", {}, + ["tags", {}, "'fbw' 'fbwhitelist'"], + ["spec", {}, "'fbwhitelist' 'fbw'"], + ["type", {}, "sitelist"], + ["default", {}, ""], + ["description", {}, + ["p", {}, + "Controls which sites may play flash animations without user ", + "intervention. See ", ["ex", {}, ":mk" + config.name.toLowerCase() + "rc"], "."]]], + + ["item", {}, + ["tags", {}, ":flashplay :flp"], + ["strut"], + ["spec", {}, ":flashplay"], + ["description", {}, + ["p", {}, + "Plays any blocked flash animations on the current page."]]], + + ["item", {}, + ["tags", {}, ":flashstop :fls"], + ["strut"], + ["spec", {}, ":flashstop"], + ["description", {}, + ["p", {}, + "Stops any currently playing flash animations on the current ", + "page."]]], + + ["item", {}, + ["tags", {}, ":flashtoggle :flt"], + ["strut"], + ["spec", {}, ":flashtoggle"], + ["description", {}, + ["p", {}, + "Toggles the playing of all animations on the current page. If ", + "any flash animations are currently blocked, all may begin ", + "playing. Otherwise, all animations are stopped."], + + ["example", {}, + ["ex", {}, ":map"], " -silent ", + ["k", { name: "A-p", link: "false" }], + " ", ["ex", {}, ":flashtoggle"], + ["k", { name: "CR" }]]]]]; + +group.options.add(["flashblock", "fb"], + "Enable blocking of flash animations", + "boolean", true, + { setter: reload }); +group.options.add(["fbwhitelist", "fbw"], + "Sites which may run flash animations without prompting", + "sitelist", "", + { + completer: context => completion.visibleHosts(context), + privateData: true, + setter: reload, + validator: () => true, + }); + +["Play", "Stop"].forEach(action => { + group.commands.add(["flash" + action, "fl" + action[0]].map(String.toLowerCase), + action + " all flash animations on the current page", + function () { postMessage(content, "flashblock" + action) }, + { argCount: "0" }, true); +}); + +group.commands.add(["flashtoggle", "flt"], + "Toggle playing of flash animations on the current page", + function () { + if (buffer.allFrames().some(w => DOM("pseudoembed", w.document).length)) + commands.get("flashplay").action(); + else + commands.get("flashstop").action(); + }, + { argCount: "0" }, true); + +group.mappings.add([modes.NORMAL], ["fbwhitelist"], + "Add the current site to the flash whitelist", + function () { whitelist.op("+", whitelist.parse(content.location.hostname)) }); +group.mappings.add([modes.NORMAL], ["fbWhitelist"], + "Toggle the current site in the flash whitelist", + function () { + let host = content.location.hostname; + if (!removeHost(host)) + whitelist.op("+", whitelist.parse(host)); + }); + +var enabled = options.get("flashblock"); +var whitelist = options.get("fbwhitelist"); +function postMessage(content, message) { + buffer.allFrames(content).forEach(f => { f.postMessage(message, "*"); }); +} +function reload(values) { + //for (let [,t] in tabs.browsers) + // t.contentWindow.postMessage("flashblockReload", "*"); + postMessage(gBrowser.mCurrentBrowser.contentWindow, "flashblockReload"); + return values; +} + +function removeHost(host) { + let len = whitelist.value.length; + let uri = util.createURI(host); + whitelist.value = whitelist.value.filter(f => !f(uri)); + return whitelist.value.length != len; +} + +function onUnload() { + group.events.unlisten(null); +} +group.events.listen(window, "flashblockCheckLoad", + function checkLoadFlash(event) { + if(!enabled.value || whitelist.getKey(event.target.documentURIObject)) + event.preventDefault(); + event.stopPropagation(); + }, true, true); + +var data = { + bindings: "dactyl://data/text/xml," + encodeURIComponent('' + + String.raw` + + + + + + ' + + // '' + + // + // + // {new XML(parent.innerHTML)} + // ); + ]]> + + + + + `), + flash: `data:image/png;base64, + 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 + `, + play: `data:image/png;base64, + iVBORw0KGgoAAAANSUhEUgAAACoAAAAqCAYAAADFw8lbAAAABGdBTUEAALGOfPtRkwAAACBjSFJN + AAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAANqUlEQVR4nGL8//8/w1AAAAHE + QqF+RiAWzKnt13j9/oPKp09f5JEl+fh4HoqJSdyZXJd5Gcj9AsRkhwpAADGSGaJivkmlcXcfPcv+ + 9eevAgcHB4MADw8DBxsbig/effrE8P3HD4Yf338wcHCyP1CWk5q6eV73PJAUqRYCBBCpDpU28kle + 9enLVysJEREGaQlhYKgJADEfAw8XGwM7CxPEUKArmRlZGH79+8vw9csXhvefvjN8fPea4eajFwzv + 339gEBTkP3Zuy1xfUhwMEEDEOpTJM664+sbdh03yUmIMygqKDJISIgyC/AIMAoJ8DHxcXAxs7OwI + xUAHg9LUfyD8AQzRL19/MLz/+JXh9ds3DPcePmO4ce8uw7MXzxlM9bRLgSHcy0BEkgAIIGIcKqTi + EPEEGL2cBlpaDArSogzi4uIMEsKCwJDkYmAHOhBkBjsbCwPjP6ADmRgZmIEB+xso9vvXH4Zff34z + /Pz9B8z+DsTfvn5leP7yBcPVmw8YDpw8x8DJyfr9zoEVokB7vuJzBEAAEXKohLRZ8HNFOUkGQz1t + BgUpCQY5aQkGIX4+Bg4OSDRzMDMzMLMxM7CDwhAY58zMjECHMzL8+fsH6MB/YIf++POX4dev30D2 + P4Yv374z/P79m4GN8R/D6et3GHYdOM7w+vWLv8/PbBNjwJMUAAIIn0PFgI58qakiz6APdKSWogSD + jIwMOARBQICbFeJQYEiyMbAy/Pz5A+jI/wxsnDwMoJj8+wfkSKgDf/9l+AGkfwAd+PPbH4bPP78y + /P75h4GR+R/Dg2cvGLbvOcLw4Onjv89ObcXpWIAAYsLhSFZF6xC4Iw00lMGO5OFkY+BlZWKQEORk + YAOmQ34OLgZWVhaGN58/MszbcYjhy18Ghr///zJwcrAysLGxApMDMwMHOxs4WXAC+ZxAteycrAy8 + 3BwMnNycwFjhZFBXVGBwd7BiUFdSYQbFHtBuZmwOAgggrOUoMGcfAOZMuCOlxETBUc0NdCgoBNmZ + 2BiYOBghUfKbkeHhy7cM01ZtZ/j48TNDSqAj0KGSwJAHJglgemViBIYcKK8wgsIZxP/NwAJ0ChvT + X4bff/8y/GdiZtDTUAUni9cf3rMB7T4ELBGs0d0EEEAYIQosvK2AOdXK0tAAHN3YHMnOxc7AzALk + c3IBxdkZfvz8Bda7dOcRhsnLtzM8e/4WGCxMwFAHpl1gDLACQ5ONlZWBi52FgQuonpuTm4GbC6SX + E2ymIDDNG2goMDibGzO8fP3RqqR1qj66uwACCN2hzPuOn1lqoKXNoKykwCAhKoXVkSDAzcHGwMnC + AnYMAyMina87eIZh5tptDE9evWb4BcxQrMBKgAOYTNg5gI7mACYBYEhzAR3PwQ5is4KTBDMrM4Og + kACDiroag762JsOG3YePM0DqDDgACCAUhyaXdrsLCQgqqKrIMsgCy0luPk5wrkYJSWA64+bkAKc/ + djaghVwc4BBDBst2H2eYvmYHw+Onrxi+fv3OwAZMsxyskFBlB8YEKygDsjGBaVag+SxMTOBaTU5E + jMFIVx3kec7Uqn5XZDMBAgjZoYznr9+oVleUZxAREgaXkRwswOhiY4ekSTZGsCM5gKEIciAIsAOr + TiagJSyMmHly6Y6jDAu37WN4/OI1sKj6B44VdmDGYmVnAWcudlZQbADTKjB02YBpGZTR+IABI6cg + xqCpoc5w/NzFKcjmAQQQsg1SQGwlIysBLsxBOZwDGMuMwOob5EhWYEiwAaOYCeh4UN0OwiAAjnSU + SIKAf8Bib/H2owyrdh9jePjkJcPvf//ADmQDp1dgSIKSDTCEWYExwwKsIRiByYOHk5lBVFCIQVNR + DlSjqQKNEYKZBxBAcIeG59Q7S4hJIkKTGVKIg0IT5EhQaLKwc8Ed8heYY/8C0yCwdAe6E4tLoY5d + sO0gw5rdhxlevHzDACy9gMUZ0GGg0ASZB/Q0KwuIzwwMVVBSYmUQBWYwcUlgYAHbElF5bR4wswAC + CO5QYD1sAmpkgHIlrFCHhSYIgKKcBVikcHOyABsaPxg+fv4CLOR/MfwBFub//+Gu3f4CQ3LGxgMM + K7YdZnj46CnDlx/fwCEIwqysjEAa6EgWUAgD+cCGDDuwcSPEA3SshDjDi9dPLWDmAAQQrBxl+vXr + Ry6oFQRqYLAy/YdUi0yQZhsoNEGBxgYMgV9Ax4GKo///gPU5UPg/sPz7C+LgASDHTlyzC1il/mbw + tjUDtxfYubgZWIAh/g9avDP+BZaxLP8ZOIGhwwnMF5KCPAz3H/wwZoAkrP8AAQRzKDMDIye4qQZq + BcGKIxBgBTqODegQJmAIgApoUHX4H8j//xdoCbCK/Afk//uH36Ewx87efIDhH1Cfv4MZg4ykODCd + A0sRYEb8DyregIHzD2guI1Cekx1YqnDzgapgKwZITfUHIIDgIQoi2Nk4gamSEZ42QQDUFgCnQlCb + ABjFwBoSzP4LEgU6FBS0oFAlBoDq/nnbDzEoARs2/AJ84Iz1HxobQGeCg44JVHsBkxso0KAAnPYA + AohgV+Q/sPH7F+hwUCMDGFcQh/1jBDv6HzAk/gNbSH///SXKoSAgJsADzLD8wIwI8vQ/sMf/gTwN + K+KQ/AyuTKAAIIAIOhRk4F+go0DRDjLjH7AJB8rN/8EOBUXpH3C0EgNE+bgZ8kPdGKTFRYE1GzvD + n3//GOBlBoFmMUAAwRwKt+kPUMdPIMnBAI16oI9/Axu+/4C+Z2KChORfIBuUnpiBDWWQI//iyfUw + IABsLRWEejDoqasyiAjwg0MLWxMTZDcI/P39ByYEdhtAAMEc+oeN9T+w9f0ZQyPIzwx/gM5nBOY3 + pv/g3A7S+u8/UAyYZSEOxR+ifMBqtiDMncFcX4tBVFgA2KpihgYg9vL3x8+/DB+B3Rd2pj9rQG4G + iQEEEKwc/c/OylH3/sMXhm/fvwAVghzzHWLUf0ioggr4P0AD/oBoYEEPSrKgqAOFNsMf3CEKangU + hLozWOpqMYgJC4EdCQf/f2Oo//sLWLL8/Mnw9s17BgEBkUswcYAAghf4ChLCh1+8f8/w7Qeo6wBq + jYMM/A+G/xggUQ0s3sEOBnczoeA3MLT/4ChHQfV5pq8Dg4WOJoOYqDAk6SADRtTGzM9/wDIaaP6n + b98YXr15A3YTTA4ggOAOnd5VdeXjuw8MH95/YvgK7N7+BEYtKNOACnOQQ0A0yJq/UEfDACszC9a0 + BqoaU73tGGwNdBnExIXBNREh8B/YrP3y/RfDS2Dn792njyA3nYXJAQQQsu63//7/nAzqw3z+Aaoe + gT4ERjHISUygEGT8D8ntQAxy+L9/EDa0eEUBzMAWVZavE4OXtSmDtIwEuInHyIg9PSKDb79+Mnz6 + 9I3h2p2nDHyc7LOAQvBMAxBAyA79ry4rve7ug6cMb99/Yfj87SuwQwbsRgAdyMICaeGwAh0A7JGB + HQnqG4EwKFf8RwphkKfi3KwZfBwsGSSAxRAXOxsDMeAjsA3w4etvYLPwDcODR48YTAy0pyHLAwQQ + SnwAg/rwj+/vJ9+4/4Dh7cdP4IGDP38gxRKopcMEapKxgBoSjOCQBgXkH2CGAKVFGIhwNGMIdbdl + kBQTAreQiAE/fvxi+AqM8ndAO89dvg40+PvSnursS8hqAAIIPeH8NdLRmnvj2h2Gew9fMLz5CAzZ + 77/BuRvkLCZGUCMZGLXg1g+koP4L7PKyQ0Mt3suWIdTVlkGAh5coB4IAKAOBovzDhx8Ml2/eZbh2 + 6yaDk7FBPQNaFQAQQBgpHOiTi3zsvzPPXr3K8Pj5G4bnrz8Ae5fAYhhYVYJCF5YeQY4FDd2ws7Iz + 8AC7vykBjgzhzlYMwoICDHzAZhqxjvz65Sc4yu89e86w78gJBkVR7vSmqty76GoBAgjXAAS7Q2j2 + BGV5hQx9YCGtKC0N7EPxAh0FKvSZwG1HsEOBbFAmAXkANGTzG1gGcnGwYzMPA4Ci++PPPwxfv/1g + ePD8BcParfsZ3r95vOb4poURDNBCHhkABBCuRPTzwOqpZQ6hWaDGTNp/YBplZAR1nfkZuLlZIZ0z + UPfhPxPD198/Ia18YNywAFs9oFAiBoCqSlB5jebIOGyOBAGAAMKX2j8fWD2tBOhYYDH1P+3P9x/A + RKPEIC0GTKsMnAw/QdUgsEHCBS7EQQkX2F6FOQLqWFhTEQRgPQUY+PD5DzDj3GbYtns/MPrfwhz5 + HZdjAAKImNE8Xt/YnPS/rALdBtqqDDoaCgyaCjLA6lCAgRfYIwCNeBALfv76x/D87UeGm/efMxw6 + dZ7h5JnzDIKcDKWbF0+Zis+RIAAQQMSOjzJnl9Ra3Xv17ZCggDCDirIcg66aMoMqsGurKC3JwM/H + RdCAZy/fMVy+85jh2NlLDKcv3GT49PE5g76ilN3UnuYjDESMjwIEEKkjzrwxGSUeb7/9X8XFxw8e + 7oENRUqJC0C6MBwIR3/4+I3h5buPDI+Atd2tew8ZQJUJsJxmkOLnDFsyo2cHA1LNQwgABBC5Y/i8 + wBA2+Pjl+6GPvyBpj42LF9gpA/Z1mCF8YLpm+P7rB8Ovb58Zvv/4y8DP/odBQpgfFIIXSHEgDAAE + ELkOhQFQEwsUhPzFVQ1iP379PYssycHGbNzb1vAKyHwLxKAcRnyCRgMAAUSpQ+kGAAKIcNtrkACA + AAMACHALg12qSjsAAAAASUVORK5CYII + `, +}; + +var CSS = + /* + * Flash Click to View by Ted Mielczarek (luser_mozilla@perilith.com) + * Original code by Jesse Ruderman (jruderman@hmc.edu) + * taken from http://www.squarefree.com/userstyles/xbl.html + * + * Change XBL binding for tags, click to view flash + */ + String.literal` + + pseudoembed { + display: inline-block; + min-width: 32px !important; + min-height: 32px !important; + border: 1px solid #dfdfdf; + cursor: pointer; + overflow: hidden; + -moz-box-sizing: border-box; + background: url("${data.play}") no-repeat center; + } + pseudoembed:hover { + background-image: url("${data.flash}"); + } + + video, + object[classid*=":D27CDB6E-AE6D-11cf-96B8-444553540000"], + object[codebase*="swflash.cab"], + object[data*=".swf"], + embed[type="application/x-shockwave-flash"], + embed[src*=".swf"], + object[type="application/x-shockwave-flash"], + object[src*=".swf"] { + -moz-binding: url("{bindings}") !important; + } + + /// TODO: Could do better. + /// NoScript is incredibly annoying. The binding can't execute JS on + /// untrusted sites. + video:not([flashblock]), + object[classid*=":D27CDB6E-AE6D-11cf-96B8-444553540000"]:not([flashblock]), + object[codebase*="swflash.cab"]:not([flashblock]), + object[data*=".swf"]:not([flashblock]), + embed[type="application/x-shockwave-flash"]:not([flashblock]), + embed[src*=".swf"]:not([flashblock]), + object[type="application/x-shockwave-flash"]:not([flashblock]), + object[src*=".swf"]:not([flashblock]) { + display: none !important; + } + + /// Java identifiers. + /// TODO: Make this work. + /// applet, + /// object[classid*=":8AD9C840-044E-11D1-B3E9-00805F499D93"], + /// object[classid^="clsid:CAFEEFAC-"], + /// object[classid^="java:"], + /// object[type="application/x-java-applet"], + /// embed[classid*=":8AD9C840-044E-11D1-B3E9-00805F499D93"], + /// embed[classid^="clsid:CAFEEFAC-"], + /// embed[classid^="java:"], + /// embed[type="application/x-java-applet"] + /// { + /// -moz-binding: url("{bindings}") !important; + /// } +`.replace(/\/\/\/.*/gm, ""); + +styles.system.add("flashblock", "*", CSS); +data = null; +CSS = null; + +/* vim:se sts=4 sw=4 et: */ diff --git a/plugins/http-headers.js b/plugins/http-headers.js new file mode 100755 index 00000000..c0c73f1d --- /dev/null +++ b/plugins/http-headers.js @@ -0,0 +1,152 @@ +"use strict"; +isGlobalModule = true; + +var INFO = +["plugin", { name: "http-headers", + version: "0.7", + href: "http://dactyl.sf.net/pentadactyl/plugins#http-headers-plugin", + summary: "HTTP header info", + xmlns: "dactyl" }, + ["author", { email: "maglione.k@gmail.com" }, + "Kris Maglione"], + ["license", { href: "http://opensource.org/licenses/mit-license.php" }, + "MIT"], + ["project", { name: "Pentadactyl", "min-version": "1.0" }], + + ["p", {}, + "Adds request and response headers to the :pageinfo ", + "command, with the keys ", ["em", {}, "h"], " and ", ["em", {}, "H"], " respectively. ", + "See also ", ["o", {}, "pageinfo"], "."], + + ["example", {}, ["ex", {}, ":pageinfo hH"]]]; + +var { Buffer } = require("buffer"); + +var Controller = Class("Controller", XPCOM(Ci.nsIController), { + init: function (command, data) { + this.command = command; + this.update(data); + }, + get wrappedJSObject() { return this; }, + supportsCommand: function (cmd) { return cmd === this.command; }, +}); + +var HttpObserver = Class("HttpObserver", + XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference, Ci.nsIWebProgressListener]), { + + init: function init() { + util.addObserver(this); + }, + + cleanup: function cleanup() { + this.observe.unregister(); + }, + + extractHeaders: function extractHeaders(request, type) { + let major = {}, minor = {}; + request.QueryInterface(Ci.nsIHttpChannelInternal)["get" + type + "Version"](major, minor); + + let headers = [[type.toUpperCase(), "HTTP/" + major.value + "." + minor.value]]; + request["visit" + type + "Headers"]({ + visitHeader: function (header, value) { + headers.push([header, value]); + } + }); + return headers; + }, + + getHeaders: function getHeaders(win, request) { + request.QueryInterface(Ci.nsIChannel); + + let headers = overlay.getData(win.document, "headers", Object); + if ("response" in headers) + return; + + if (win && /^https?$/.test(request.URI.scheme)) { + if (request instanceof Ci.nsIHttpChannel) + request.QueryInterface(Ci.nsIHttpChannel); + else { + request.QueryInterface(Ci.nsIMultiPartChannel); + request.baseChannel.QueryInterface(Ci.nsIHttpChannel); + } + + headers.request = this.extractHeaders(request, "Request"); + headers.request[0][1] = request.requestMethod + " " + + request.URI.path + " " + headers.request[0][1]; + + try { + headers.response = this.extractHeaders(request, "Response"); + headers.response[0][1] += " " + request.responseStatus + " " + + request.responseStatusText; + } + catch (e) {} + + let controller = this.getController(win); + if (controller) + win.controllers.removeController(controller); + win.controllers.appendController(Controller("dactyl-headers", { headers: headers, url: win.document.documentURI })); + } + }, + + observers: { + "http-on-examine-response": util.wrapCallback(function onExamineResponse(request, data) { + request.QueryInterface(Ci.nsIChannel).QueryInterface(Ci.nsIHttpChannel).QueryInterface(Ci.nsIRequest); + + if (request.loadFlags & request.LOAD_DOCUMENT_URI) { + try { + var win = request.notificationCallbacks.getInterface(Ci.nsIDOMWindow); + } + catch (e) { + return; + } + this.getHeaders(win, request); + try { + webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); + } + catch (e) {} + } + }) + }, + + onStateChange: util.wrapCallback(function(webProgress, request, stateFlags, status) { + if ((stateFlags & this.STATE_START) && (stateFlags & this.STATE_IS_DOCUMENT)) + this.getHeaders(webProgress.DOMWindow, request); + else if ((stateFlags & this.STATE_STOP) && (stateFlags & this.STATE_IS_DOCUMENT)) { + this.getHeaders(webProgress.DOMWindow, request); + try { + webProgress.removeProgressListener(this); + } catch (e) {} + } + }), + + getController: function getController(win) { + for (let i of util.range(0, win.controllers.getControllerCount())) { + let controller = win.controllers.getControllerAt(i); + if (controller.supportsCommand("dactyl-headers") && controller.wrappedJSObject instanceof Controller) + return controller.wrappedJSObject; + } + } +}); + +let observer = HttpObserver(); +let onUnload = observer.closure.cleanup; + +function* iterHeaders(buffer, type) { + let win = buffer.focusedFrame; + let store = win.document[overlay.id]; + if (!store || !store.headers) + store = observer.getController(win); + + if (store) + for (let [k, v] of values(store.headers[type] || [])) + yield [k, v]; +} + +iter({ h: "Request", H: "Response" }).forEach(function ([key, name]) { + Buffer.addPageInfoSection(key, name + " Headers", function (verbose) { + if (verbose) + return iterHeaders(this, name.toLowerCase()) + }); +}); + +/* vim:se sts=4 sw=4 et: */ diff --git a/plugins/jscompletion.js b/plugins/jscompletion.js new file mode 100755 index 00000000..fae7f381 --- /dev/null +++ b/plugins/jscompletion.js @@ -0,0 +1,147 @@ +"use strict"; +var INFO = +["plugin", { name: "jscompletion", + version: "1.0.4", + href: "http://dactyl.sf.net/pentadactyl/plugins#jscompletion-plugin", + summary: "JavaScript completion enhancements", + xmlns: "dactyl" }, + ["author", { email: "maglione.k@gmail.com" }, + "Kris Maglione"], + ["license", { href: "http://people.freebsd.org/~phk/" }, + "BEER-WARE"], + ["project", { name: "Pentadactyl", "min-version": "1.0" }], + ["p", {}, + "This plugin provides advanced completion functions for ", + "DOM functions, eval, and some other special functions. ", + "For instance, ", + "", ["ex", {}, ':js content.document.getElementById("', + ["k", { name: "Tab", link: "c_" }], ], " ", + "should provide you with a list of all element IDs ", + "present on the current web page. Many other DOM ", + "methods are provided, along with their namespaced variants."]]; + +function evalXPath(xpath, doc, namespace) { + let res = doc.evaluate(xpath, doc, + function getNamespace(prefix) { + return { + html: "http://www.w3.org/1999/xhtml", + dactyl: NS, + ns: namespace + }[prefix]; + }, + XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + null + ); + return function* () { + for (let i = 0; i < res.snapshotLength; i++) + yield res.snapshotItem(i); + }(); +} + +let NAMESPACES = [ + ["http://purl.org/atom/ns#", "Atom 0.3"], + ["http://www.w3.org/2005/Atom", "Atom 1.0"], + [NS, "Dactyl"], + ["http://www.w3.org/2005/Atom", "RSS"], + ["http://www.w3.org/2000/svg", "SVG"], + ["http://www.mozilla.org/xbl", "XBL"], + ["http://www.w3.org/1999/xhtml", "XHTML 1.0"], + ["http://www.w3.org/2002/06/xhtml2", "XHTML 2.0"], + ["http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "XUL"] +]; + +function addCompleter(names, fn) { + for (let name of util.debrace(names)) + javascript.completers[name] = fn; +} +function* uniq(iter) { + let seen = new RealSet() + for (let val of iter) + if (!seen.add(val)) + yield val; +} + +addCompleter("__lookup{Getter,Setter}__", function (context, func, obj, args) { + if (args.length == 1) + context.completions = + [[k, obj[func](k)] for (k of properties(obj))].concat( + [[k, obj[func](k)] for (k of properties(obj, true))]).filter( + ([k, v]) => v); +}); + +addCompleter("eval", function (context, func, obj, args) { + if (args.length > 1) + return []; + if (!context.cache.js) { + context.cache.js = JavaScript(); + context.cache.context = CompletionContext(""); + } + let ctxt = context.cache.context; + context.keys = { text: "text", description: "description" }; + ctxt.filter = context.filter; + context.cache.js.complete(ctxt); + context.advance(ctxt.offset); + context.completions = ctxt.allItems.items; +}); + +addCompleter("getOwnPropertyDescriptor", function (context, func, obj, args) { + context.anchored = false; + context.keys = { text: util.identity, description: () => "" }; + if (args.length == 2) + return properties(args[0]); +}); + +addCompleter("getElementById", function (context, func, doc, args) { + context.anchored = false; + if (args.length == 1) { + context.keys = { text: e => e.getAttribute("id"), description: util.objectToString }; + context.generate = () => evalXPath("//*[@id]", doc); + } +}); + +function addCompleterNS(names, fn) { + addCompleter(names + "{,NS}", function checkNS(context, func, obj, args) { + context.anchored = false; + context.keys = { text: util.identity, description: () => "" }; + let isNS = /NS$/.test(func); + if (isNS && args.length == 1) + return NAMESPACES; + let prefix = isNS ? "ns:" : ""; + return fn(context, func, obj, args, prefix, isNS && args.shift()); + }); +} + +addCompleterNS("getElementsByClassName", function (context, func, doc, args, prefix, namespace) { + if (args.length == 1) { + let iter = evalXPath("//@" + prefix + "class", doc, namespace); + return array(e.value.split(" ") for (e of iter)).flatten().uniq().array; + } +}); + +addCompleterNS("{getElementsByTagName,createElement}", function (context, func, doc, args, prefix, namespace) { + if (args.length == 1) { + let iter = evalXPath("//" + prefix + "*", doc, namespace); + return uniq(e.localName.toLowerCase() for (e of iter)); + } +}); + +addCompleterNS("getElementsByAttribute", function (context, func, doc, args, prefix, namespace) { + switch (args.length) { + case 1: + let iter = evalXPath("//@" + prefix + "*", doc, namespace); + return uniq(e.name for (e of iter)); + case 2: + iter = evalXPath("//@" + prefix + args[0], doc, namespace); + return uniq(e.value for (e of iter)); + } +}); + +addCompleterNS("{get,set,remove}Attribute", function (context, func, node, args, prefix, namespace) { + context.keys = { text: 0, description: 1 }; + if (args.length == 1) + return [[a.localName, a.value] + for (a of array.iterValues(node.attributes)) + if (!namespace || a.namespaceURI == namespace)]; +}); + +/* vim:se sts=4 sw=4 et: */ diff --git a/plugins/noscript.js b/plugins/noscript.js new file mode 100755 index 00000000..4853a54a --- /dev/null +++ b/plugins/noscript.js @@ -0,0 +1,483 @@ +/* + * Copyright ©2010-2014 Kris Maglione + * Distributable under the terms of the MIT license. + * + * Documentation is at the tail of this file. + */ +"use strict"; + +if (!("noscriptOverlay" in window)) { + if (!userContext.noscriptIgnoreMissing) + dactyl.echoerr("This plugin requires the NoScript add-on."); + throw Finished(); +} + +/* + * this.globalJS ? !this.alwaysBlockUntrustedContent || !this.untrustedSites.matches(s) + * : this.jsPolicySites.matches(s) && !this.untrustedSites.matches(s) && !this.isForbiddenByHttpsStatus(s)); + */ + +function getSites() { + // This logic comes directly from NoScript. To my mind, it's insane. + const ns = services.noscript; + const global = options["script"]; + const groups = { allowed: ns.jsPolicySites, temp: ns.tempSites, untrusted: ns.untrustedSites }; + const show = RealSet(options["noscript-list"]); + const sites = window.noscriptOverlay.getSites(); + + const blockUntrusted = global && ns.alwaysBlockUntrustedContent; + + let res = []; + for (let site of array.iterValues(Array.concat(sites.topSite, sites))) { + let ary = []; + + let untrusted = groups.untrusted.matches(site); + let matchingSite = null; + if (!untrusted) + matchingSite = groups.allowed.matches(site) || blockUntrusted && site; + + let enabled = Boolean(matchingSite); + if (site == sites.topSite && !ns.dom.getDocShellForWindow(content).allowJavascript) + enabled = false; + + let hasPort = /:\d+$/.test(site); + + if (enabled && !global || untrusted) { + if (!enabled || global) + matchingSite = untrusted; + + if (hasPort && ns.ignorePorts) + if (site = groups.allowed.matches(site.replace(/:\d+$/, ""))) + matchingSite = site; + ary.push(matchingSite); + } + else { + if ((!hasPort || ns.ignorePorts) && (show.has("full") || show.has("base"))) { + let domain = !ns.isForbiddenByHttpsStatus(site) && ns.getDomain(site); + if (domain && ns.isJSEnabled(domain) == enabled) { + ary = util.subdomains(domain); + if (!show.has("base") && ary.length > 1) + ary = ary.slice(1); + if (!show.has("full")) + ary = ary.slice(0, 1); + } + } + + if (show.has("address") || ary.length == 0) { + ary.push(site); + + if (hasPort && ns.ignorePorts) { + site = site.replace(/:\d+$/, ""); + if (!groups.allowed.matches(site)) + ary.push(site); + } + } + } + res = res.concat(ary); + } + + let seen = RealSet(); + return res.filter(function (h) { + let res = !seen.has(h); + seen.add(h); + return res; + }); +} +function getObjects() { + let sites = noscriptOverlay.getSites(); + let general = [], specific = []; + for (let group of values(sites.pluginExtras)) + for (let obj of array.iterValues(group)) { + if (!obj.placeholder && (ns.isAllowedObject(obj.url, obj.mime) || obj.tag)) + continue; + specific.push(obj.mime + "@" + obj.url); + general.push("*@" + obj.url); + general.push("*@" + obj.site); + } + sites = buffer.allFrames().map(f => f.location.host); + for (let filter of values(options["noscript-objects"])) { + let host = util.getHost(util.split(filter, /@/, 2)[1]); + if (sites.some(s => s == host)) + specific.push(filter); + } + let seen = RealSet(); + return specific.concat(general).filter(function (site) { + let res = !seen.has(site); + seen.add(site); + return res; + }); +} + +var onUnload = overlay.overlayObject(gBrowser, { + // Extend NoScript's bookmarklet handling hack to the command-line + // Modified from NoScript's own wrapper. + loadURIWithFlags: function loadURIWithFlags(url) { + let args = arguments; + let load = () => loadURIWithFlags.superapply(gBrowser, args); + + if (!commandline.command || !util.isDactyl(Components.stack.caller)) + return load(); + + try { + for (let [cmd, args] of commands.parseCommands(commandline.command)) + var origURL = args.literalArg; + + let isJS = url => /^(?:data|javascript):/i.test(url); + let allowJS = prefs.get("noscript.allowURLBarJS", true); + + if (isJS(origURL) && allowJS) { + if (services.noscript.executeJSURL(origURL, load)) + return; + } + else if (url != origURL && isJS(url)) { + if(services.noscript.handleBookmark(url, load)) + return; + } + } + catch (e) { + util.reportError(e); + } + return load(); + } +}); + +highlight.loadCSS(String.raw` + NoScriptAllowed color: green; + NoScriptBlocked color: #444; font-style: italic; + NoScriptTemp color: blue; + NoScriptUntrusted color: #c00; font-style: italic; +`); + +let groupProto = {}; +["temp", "jsPolicy", "untrusted"].forEach(function (group) { + memoize(groupProto, group, + function () { + return services.noscript[group + "Sites"].matches(this.site); + }); +}); +let groupDesc = { + NoScriptTemp: "Temporarily allowed", + NoScriptAllowed: "Allowed permanently", + NoScriptUntrusted: "Untrusted", + NoScriptBlocked: "Blocked" +}; + +function splitContext(context, list) { + for (let [name, title, filter] of values(list)) { + let ctxt = context.split(name); + ctxt.title = [title]; + ctxt.filters.push(filter); + } +} + +completion.noscriptObjects = function (context) { + let whitelist = options.get("noscript-objects").set; + context = context.fork(); + context.compare = CompletionContext.Sort.unsorted; + context.generate = getObjects; + context.keys = { + text: util.identity, + description: key => whitelist.has(key) ? "Allowed" : "Forbidden" + }; + splitContext(context, getObjects, [ + ["forbidden", "Forbidden objects", item => !whitelist.has(item.item)], + ["allowed", "Allowed objects", item => whitelist.has(item.item)]]); +}; +completion.noscriptSites = function (context) { + context.compare = CompletionContext.Sort.unsorted; + context.generate = getSites; + context.keys = { + text: util.identity, + description: site => groupDesc[this.highlight] + + (this.groups.untrusted && this.highlight != "NoScriptUntrusted" ? " (untrusted)" : ""), + + highlight: function (site) { + return this.groups.temp ? "NoScriptTemp" : + this.groups.jsPolicy ? "NoScriptAllowed" : + this.groups.untrusted ? "NoScriptUntrusted" : + "NoScriptBlocked"; + }, + groups: site => ({ site: site, __proto__: groupProto }) + }; + splitContext(context, [ + ["normal", "Active sites", item => item.groups.jsPolicy || !item.groups.untrusted], + ["untrusted", "Untrusted sites", item => !item.groups.jsPolicy && item.groups.untrusted]]); + context.maxItems = 100; +} + +services.add("noscript", "@maone.net/noscript-service;1"); + +var PrefBase = "noscript."; +var Pref = Struct("text", "pref", "description"); +let prefs = { + forbid: [ + ["bookmarklet", "forbidBookmarklets", "Forbid bookmarklets"], + ["collapse", "collapseObject", "Collapse forbidden objects"], + ["flash", "forbidFlash", "Block Adobe® Flash® animations"], + ["fonts", "forbidFonts", "Forbid remote font loading"], + ["frame", "forbidFrames", "Block foreign elements"], + ["iframe", "forbidIFrames", "Block foreign