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