|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const { Cc, Ci, Cu } = require("chrome"); |
|
8 const gcli = require("gcli/index"); |
|
9 |
|
10 exports.items = [ |
|
11 { |
|
12 name: "pagemod", |
|
13 description: gcli.lookup("pagemodDesc"), |
|
14 }, |
|
15 { |
|
16 name: "pagemod replace", |
|
17 description: gcli.lookup("pagemodReplaceDesc"), |
|
18 params: [ |
|
19 { |
|
20 name: "search", |
|
21 type: "string", |
|
22 description: gcli.lookup("pagemodReplaceSearchDesc"), |
|
23 }, |
|
24 { |
|
25 name: "replace", |
|
26 type: "string", |
|
27 description: gcli.lookup("pagemodReplaceReplaceDesc"), |
|
28 }, |
|
29 { |
|
30 name: "ignoreCase", |
|
31 type: "boolean", |
|
32 description: gcli.lookup("pagemodReplaceIgnoreCaseDesc"), |
|
33 }, |
|
34 { |
|
35 name: "selector", |
|
36 type: "string", |
|
37 description: gcli.lookup("pagemodReplaceSelectorDesc"), |
|
38 defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)", |
|
39 }, |
|
40 { |
|
41 name: "root", |
|
42 type: "node", |
|
43 description: gcli.lookup("pagemodReplaceRootDesc"), |
|
44 defaultValue: null, |
|
45 }, |
|
46 { |
|
47 name: "attrOnly", |
|
48 type: "boolean", |
|
49 description: gcli.lookup("pagemodReplaceAttrOnlyDesc"), |
|
50 }, |
|
51 { |
|
52 name: "contentOnly", |
|
53 type: "boolean", |
|
54 description: gcli.lookup("pagemodReplaceContentOnlyDesc"), |
|
55 }, |
|
56 { |
|
57 name: "attributes", |
|
58 type: "string", |
|
59 description: gcli.lookup("pagemodReplaceAttributesDesc"), |
|
60 defaultValue: null, |
|
61 }, |
|
62 ], |
|
63 // Make a given string safe to use in a regular expression. |
|
64 escapeRegex: function(aString) { |
|
65 return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); |
|
66 }, |
|
67 exec: function(args, context) { |
|
68 let searchTextNodes = !args.attrOnly; |
|
69 let searchAttributes = !args.contentOnly; |
|
70 let regexOptions = args.ignoreCase ? "ig" : "g"; |
|
71 let search = new RegExp(this.escapeRegex(args.search), regexOptions); |
|
72 let attributeRegex = null; |
|
73 if (args.attributes) { |
|
74 attributeRegex = new RegExp(args.attributes, regexOptions); |
|
75 } |
|
76 |
|
77 let root = args.root || context.environment.document; |
|
78 let elements = root.querySelectorAll(args.selector); |
|
79 elements = Array.prototype.slice.call(elements); |
|
80 |
|
81 let replacedTextNodes = 0; |
|
82 let replacedAttributes = 0; |
|
83 |
|
84 function replaceAttribute() { |
|
85 replacedAttributes++; |
|
86 return args.replace; |
|
87 } |
|
88 function replaceTextNode() { |
|
89 replacedTextNodes++; |
|
90 return args.replace; |
|
91 } |
|
92 |
|
93 for (let i = 0; i < elements.length; i++) { |
|
94 let element = elements[i]; |
|
95 if (searchTextNodes) { |
|
96 for (let y = 0; y < element.childNodes.length; y++) { |
|
97 let node = element.childNodes[y]; |
|
98 if (node.nodeType == node.TEXT_NODE) { |
|
99 node.textContent = node.textContent.replace(search, replaceTextNode); |
|
100 } |
|
101 } |
|
102 } |
|
103 |
|
104 if (searchAttributes) { |
|
105 if (!element.attributes) { |
|
106 continue; |
|
107 } |
|
108 for (let y = 0; y < element.attributes.length; y++) { |
|
109 let attr = element.attributes[y]; |
|
110 if (!attributeRegex || attributeRegex.test(attr.name)) { |
|
111 attr.value = attr.value.replace(search, replaceAttribute); |
|
112 } |
|
113 } |
|
114 } |
|
115 } |
|
116 |
|
117 return gcli.lookupFormat("pagemodReplaceResult", |
|
118 [elements.length, replacedTextNodes, |
|
119 replacedAttributes]); |
|
120 } |
|
121 }, |
|
122 { |
|
123 name: "pagemod remove", |
|
124 description: gcli.lookup("pagemodRemoveDesc"), |
|
125 }, |
|
126 { |
|
127 name: "pagemod remove element", |
|
128 description: gcli.lookup("pagemodRemoveElementDesc"), |
|
129 params: [ |
|
130 { |
|
131 name: "search", |
|
132 type: "string", |
|
133 description: gcli.lookup("pagemodRemoveElementSearchDesc"), |
|
134 }, |
|
135 { |
|
136 name: "root", |
|
137 type: "node", |
|
138 description: gcli.lookup("pagemodRemoveElementRootDesc"), |
|
139 defaultValue: null, |
|
140 }, |
|
141 { |
|
142 name: "stripOnly", |
|
143 type: "boolean", |
|
144 description: gcli.lookup("pagemodRemoveElementStripOnlyDesc"), |
|
145 }, |
|
146 { |
|
147 name: "ifEmptyOnly", |
|
148 type: "boolean", |
|
149 description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"), |
|
150 }, |
|
151 ], |
|
152 exec: function(args, context) { |
|
153 let root = args.root || context.environment.document; |
|
154 let elements = Array.prototype.slice.call(root.querySelectorAll(args.search)); |
|
155 |
|
156 let removed = 0; |
|
157 for (let i = 0; i < elements.length; i++) { |
|
158 let element = elements[i]; |
|
159 let parentNode = element.parentNode; |
|
160 if (!parentNode || !element.removeChild) { |
|
161 continue; |
|
162 } |
|
163 if (args.stripOnly) { |
|
164 while (element.hasChildNodes()) { |
|
165 parentNode.insertBefore(element.childNodes[0], element); |
|
166 } |
|
167 } |
|
168 if (!args.ifEmptyOnly || !element.hasChildNodes()) { |
|
169 element.parentNode.removeChild(element); |
|
170 removed++; |
|
171 } |
|
172 } |
|
173 |
|
174 return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements", |
|
175 [elements.length, removed]); |
|
176 } |
|
177 }, |
|
178 { |
|
179 name: "pagemod remove attribute", |
|
180 description: gcli.lookup("pagemodRemoveAttributeDesc"), |
|
181 params: [ |
|
182 { |
|
183 name: "searchAttributes", |
|
184 type: "string", |
|
185 description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"), |
|
186 }, |
|
187 { |
|
188 name: "searchElements", |
|
189 type: "string", |
|
190 description: gcli.lookup("pagemodRemoveAttributeSearchElementsDesc"), |
|
191 }, |
|
192 { |
|
193 name: "root", |
|
194 type: "node", |
|
195 description: gcli.lookup("pagemodRemoveAttributeRootDesc"), |
|
196 defaultValue: null, |
|
197 }, |
|
198 { |
|
199 name: "ignoreCase", |
|
200 type: "boolean", |
|
201 description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"), |
|
202 }, |
|
203 ], |
|
204 exec: function(args, context) { |
|
205 let root = args.root || context.environment.document; |
|
206 let regexOptions = args.ignoreCase ? "ig" : "g"; |
|
207 let attributeRegex = new RegExp(args.searchAttributes, regexOptions); |
|
208 let elements = root.querySelectorAll(args.searchElements); |
|
209 elements = Array.prototype.slice.call(elements); |
|
210 |
|
211 let removed = 0; |
|
212 for (let i = 0; i < elements.length; i++) { |
|
213 let element = elements[i]; |
|
214 if (!element.attributes) { |
|
215 continue; |
|
216 } |
|
217 |
|
218 var attrs = Array.prototype.slice.call(element.attributes); |
|
219 for (let y = 0; y < attrs.length; y++) { |
|
220 let attr = attrs[y]; |
|
221 if (attributeRegex.test(attr.name)) { |
|
222 element.removeAttribute(attr.name); |
|
223 removed++; |
|
224 } |
|
225 } |
|
226 } |
|
227 |
|
228 return gcli.lookupFormat("pagemodRemoveAttributeResult", |
|
229 [elements.length, removed]); |
|
230 } |
|
231 }, |
|
232 // This command allows the user to export the page to HTML after DOM changes |
|
233 { |
|
234 name: "export", |
|
235 description: gcli.lookup("exportDesc"), |
|
236 }, |
|
237 { |
|
238 name: "export html", |
|
239 description: gcli.lookup("exportHtmlDesc"), |
|
240 params: [ |
|
241 { |
|
242 name: "destination", |
|
243 type: { |
|
244 name: "selection", |
|
245 data: [ "window", "stdout", "clipboard" ] |
|
246 }, |
|
247 defaultValue: "window" |
|
248 } |
|
249 ], |
|
250 exec: function(args, context) { |
|
251 let html = context.environment.document.documentElement.outerHTML; |
|
252 if (args.destination === "stdout") { |
|
253 return html; |
|
254 } |
|
255 |
|
256 if (args.desination === "clipboard") { |
|
257 let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"] |
|
258 .getService(Ci.nsIClipboardHelper); |
|
259 clipboard.copyString(url); |
|
260 return ''; |
|
261 } |
|
262 |
|
263 let url = "data:text/plain;charset=utf8," + encodeURIComponent(html); |
|
264 context.environment.window.open(url); |
|
265 return ''; |
|
266 } |
|
267 } |
|
268 ]; |