michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { Cc, Ci, Cu } = require("chrome"); michael@0: const gcli = require("gcli/index"); michael@0: michael@0: exports.items = [ michael@0: { michael@0: name: "pagemod", michael@0: description: gcli.lookup("pagemodDesc"), michael@0: }, michael@0: { michael@0: name: "pagemod replace", michael@0: description: gcli.lookup("pagemodReplaceDesc"), michael@0: params: [ michael@0: { michael@0: name: "search", michael@0: type: "string", michael@0: description: gcli.lookup("pagemodReplaceSearchDesc"), michael@0: }, michael@0: { michael@0: name: "replace", michael@0: type: "string", michael@0: description: gcli.lookup("pagemodReplaceReplaceDesc"), michael@0: }, michael@0: { michael@0: name: "ignoreCase", michael@0: type: "boolean", michael@0: description: gcli.lookup("pagemodReplaceIgnoreCaseDesc"), michael@0: }, michael@0: { michael@0: name: "selector", michael@0: type: "string", michael@0: description: gcli.lookup("pagemodReplaceSelectorDesc"), michael@0: defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)", michael@0: }, michael@0: { michael@0: name: "root", michael@0: type: "node", michael@0: description: gcli.lookup("pagemodReplaceRootDesc"), michael@0: defaultValue: null, michael@0: }, michael@0: { michael@0: name: "attrOnly", michael@0: type: "boolean", michael@0: description: gcli.lookup("pagemodReplaceAttrOnlyDesc"), michael@0: }, michael@0: { michael@0: name: "contentOnly", michael@0: type: "boolean", michael@0: description: gcli.lookup("pagemodReplaceContentOnlyDesc"), michael@0: }, michael@0: { michael@0: name: "attributes", michael@0: type: "string", michael@0: description: gcli.lookup("pagemodReplaceAttributesDesc"), michael@0: defaultValue: null, michael@0: }, michael@0: ], michael@0: // Make a given string safe to use in a regular expression. michael@0: escapeRegex: function(aString) { michael@0: return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); michael@0: }, michael@0: exec: function(args, context) { michael@0: let searchTextNodes = !args.attrOnly; michael@0: let searchAttributes = !args.contentOnly; michael@0: let regexOptions = args.ignoreCase ? "ig" : "g"; michael@0: let search = new RegExp(this.escapeRegex(args.search), regexOptions); michael@0: let attributeRegex = null; michael@0: if (args.attributes) { michael@0: attributeRegex = new RegExp(args.attributes, regexOptions); michael@0: } michael@0: michael@0: let root = args.root || context.environment.document; michael@0: let elements = root.querySelectorAll(args.selector); michael@0: elements = Array.prototype.slice.call(elements); michael@0: michael@0: let replacedTextNodes = 0; michael@0: let replacedAttributes = 0; michael@0: michael@0: function replaceAttribute() { michael@0: replacedAttributes++; michael@0: return args.replace; michael@0: } michael@0: function replaceTextNode() { michael@0: replacedTextNodes++; michael@0: return args.replace; michael@0: } michael@0: michael@0: for (let i = 0; i < elements.length; i++) { michael@0: let element = elements[i]; michael@0: if (searchTextNodes) { michael@0: for (let y = 0; y < element.childNodes.length; y++) { michael@0: let node = element.childNodes[y]; michael@0: if (node.nodeType == node.TEXT_NODE) { michael@0: node.textContent = node.textContent.replace(search, replaceTextNode); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (searchAttributes) { michael@0: if (!element.attributes) { michael@0: continue; michael@0: } michael@0: for (let y = 0; y < element.attributes.length; y++) { michael@0: let attr = element.attributes[y]; michael@0: if (!attributeRegex || attributeRegex.test(attr.name)) { michael@0: attr.value = attr.value.replace(search, replaceAttribute); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return gcli.lookupFormat("pagemodReplaceResult", michael@0: [elements.length, replacedTextNodes, michael@0: replacedAttributes]); michael@0: } michael@0: }, michael@0: { michael@0: name: "pagemod remove", michael@0: description: gcli.lookup("pagemodRemoveDesc"), michael@0: }, michael@0: { michael@0: name: "pagemod remove element", michael@0: description: gcli.lookup("pagemodRemoveElementDesc"), michael@0: params: [ michael@0: { michael@0: name: "search", michael@0: type: "string", michael@0: description: gcli.lookup("pagemodRemoveElementSearchDesc"), michael@0: }, michael@0: { michael@0: name: "root", michael@0: type: "node", michael@0: description: gcli.lookup("pagemodRemoveElementRootDesc"), michael@0: defaultValue: null, michael@0: }, michael@0: { michael@0: name: "stripOnly", michael@0: type: "boolean", michael@0: description: gcli.lookup("pagemodRemoveElementStripOnlyDesc"), michael@0: }, michael@0: { michael@0: name: "ifEmptyOnly", michael@0: type: "boolean", michael@0: description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"), michael@0: }, michael@0: ], michael@0: exec: function(args, context) { michael@0: let root = args.root || context.environment.document; michael@0: let elements = Array.prototype.slice.call(root.querySelectorAll(args.search)); michael@0: michael@0: let removed = 0; michael@0: for (let i = 0; i < elements.length; i++) { michael@0: let element = elements[i]; michael@0: let parentNode = element.parentNode; michael@0: if (!parentNode || !element.removeChild) { michael@0: continue; michael@0: } michael@0: if (args.stripOnly) { michael@0: while (element.hasChildNodes()) { michael@0: parentNode.insertBefore(element.childNodes[0], element); michael@0: } michael@0: } michael@0: if (!args.ifEmptyOnly || !element.hasChildNodes()) { michael@0: element.parentNode.removeChild(element); michael@0: removed++; michael@0: } michael@0: } michael@0: michael@0: return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements", michael@0: [elements.length, removed]); michael@0: } michael@0: }, michael@0: { michael@0: name: "pagemod remove attribute", michael@0: description: gcli.lookup("pagemodRemoveAttributeDesc"), michael@0: params: [ michael@0: { michael@0: name: "searchAttributes", michael@0: type: "string", michael@0: description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"), michael@0: }, michael@0: { michael@0: name: "searchElements", michael@0: type: "string", michael@0: description: gcli.lookup("pagemodRemoveAttributeSearchElementsDesc"), michael@0: }, michael@0: { michael@0: name: "root", michael@0: type: "node", michael@0: description: gcli.lookup("pagemodRemoveAttributeRootDesc"), michael@0: defaultValue: null, michael@0: }, michael@0: { michael@0: name: "ignoreCase", michael@0: type: "boolean", michael@0: description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"), michael@0: }, michael@0: ], michael@0: exec: function(args, context) { michael@0: let root = args.root || context.environment.document; michael@0: let regexOptions = args.ignoreCase ? "ig" : "g"; michael@0: let attributeRegex = new RegExp(args.searchAttributes, regexOptions); michael@0: let elements = root.querySelectorAll(args.searchElements); michael@0: elements = Array.prototype.slice.call(elements); michael@0: michael@0: let removed = 0; michael@0: for (let i = 0; i < elements.length; i++) { michael@0: let element = elements[i]; michael@0: if (!element.attributes) { michael@0: continue; michael@0: } michael@0: michael@0: var attrs = Array.prototype.slice.call(element.attributes); michael@0: for (let y = 0; y < attrs.length; y++) { michael@0: let attr = attrs[y]; michael@0: if (attributeRegex.test(attr.name)) { michael@0: element.removeAttribute(attr.name); michael@0: removed++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return gcli.lookupFormat("pagemodRemoveAttributeResult", michael@0: [elements.length, removed]); michael@0: } michael@0: }, michael@0: // This command allows the user to export the page to HTML after DOM changes michael@0: { michael@0: name: "export", michael@0: description: gcli.lookup("exportDesc"), michael@0: }, michael@0: { michael@0: name: "export html", michael@0: description: gcli.lookup("exportHtmlDesc"), michael@0: params: [ michael@0: { michael@0: name: "destination", michael@0: type: { michael@0: name: "selection", michael@0: data: [ "window", "stdout", "clipboard" ] michael@0: }, michael@0: defaultValue: "window" michael@0: } michael@0: ], michael@0: exec: function(args, context) { michael@0: let html = context.environment.document.documentElement.outerHTML; michael@0: if (args.destination === "stdout") { michael@0: return html; michael@0: } michael@0: michael@0: if (args.desination === "clipboard") { michael@0: let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] michael@0: .getService(Ci.nsIClipboardHelper); michael@0: clipboard.copyString(url); michael@0: return ''; michael@0: } michael@0: michael@0: let url = "data:text/plain;charset=utf8," + encodeURIComponent(html); michael@0: context.environment.window.open(url); michael@0: return ''; michael@0: } michael@0: } michael@0: ];