|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module containing function to generate JavaScript code. |
|
8 """ |
|
9 |
|
10 # |
|
11 # This code was ported from QupZilla. |
|
12 # Copyright (C) David Rosca <nowrep@gmail.com> |
|
13 # |
|
14 |
|
15 from PyQt5.QtCore import QUrlQuery, QUrl |
|
16 |
|
17 from .WebBrowserTools import getJavascript |
|
18 |
|
19 |
|
20 def setupWebChannel(worldId): |
|
21 """ |
|
22 Function generating a script to setup the web channel. |
|
23 |
|
24 @param worldId world ID for which to setup the channel |
|
25 @type int |
|
26 @return script to setup the web channel |
|
27 @rtype str |
|
28 """ |
|
29 source = """ |
|
30 // ==UserScript== |
|
31 {0} |
|
32 // ==/UserScript== |
|
33 |
|
34 (function() {{ |
|
35 {1} |
|
36 |
|
37 function registerExternal(e) {{ |
|
38 window.external = e; |
|
39 if (window.external) {{ |
|
40 var event = document.createEvent('Event'); |
|
41 event.initEvent('_eric_external_created', true, true); |
|
42 window._eric_external = true; |
|
43 document.dispatchEvent(event); |
|
44 }} |
|
45 }} |
|
46 |
|
47 if (self !== top) {{ |
|
48 if (top._eric_external) |
|
49 registerExternal(top.external); |
|
50 else |
|
51 top.document.addEventListener( |
|
52 '_eric_external_created', function() {{ |
|
53 registerExternal(top.external); |
|
54 }}); |
|
55 return; |
|
56 }} |
|
57 |
|
58 function registerWebChannel() {{ |
|
59 try {{ |
|
60 new QWebChannel(qt.webChannelTransport, function(channel) {{ |
|
61 var external = channel.objects.eric_object; |
|
62 external.extra = {{}}; |
|
63 for (var key in channel.objects) {{ |
|
64 if (key != 'eric_object' && key.startsWith('eric_')) {{ |
|
65 external.extra[key.substr(5)] = channel.objects[key]; |
|
66 }} |
|
67 }} |
|
68 registerExternal(external); |
|
69 }}); |
|
70 }} catch (e) {{ |
|
71 setTimeout(registerWebChannel, 100); |
|
72 }} |
|
73 }} |
|
74 registerWebChannel(); |
|
75 |
|
76 }})()""" |
|
77 |
|
78 from WebBrowser.WebBrowserPage import WebBrowserPage |
|
79 match = ( |
|
80 "// @exclude eric:*" |
|
81 if worldId == WebBrowserPage.SafeJsWorld else |
|
82 "// @include eric:*" |
|
83 ) |
|
84 return source.format(match, getJavascript("qwebchannel.js")) |
|
85 |
|
86 |
|
87 def setupWindowObject(): |
|
88 """ |
|
89 Function generating a script to setup window.object add-ons. |
|
90 |
|
91 @return generated script |
|
92 @rtype str |
|
93 """ |
|
94 source = """ |
|
95 (function() { |
|
96 var external = {}; |
|
97 external.AddSearchProvider = function(url) { |
|
98 window.location = 'eric:AddSearchProvider?url=' + url; |
|
99 }; |
|
100 external.IsSearchProviderInstalled = function(url) { |
|
101 console.warn('NOT IMPLEMENTED: IsSearchProviderInstalled()'); |
|
102 return false; |
|
103 }; |
|
104 window.external = external; |
|
105 window.print = function() { |
|
106 window.location = 'eric:PrintPage'; |
|
107 }; |
|
108 })()""" |
|
109 |
|
110 return source |
|
111 |
|
112 |
|
113 def setStyleSheet(css): |
|
114 """ |
|
115 Function generating a script to set a user style sheet. |
|
116 |
|
117 @param css style sheet to be applied |
|
118 @type str |
|
119 @return script to set a user style sheet |
|
120 @rtype str |
|
121 """ |
|
122 source = """ |
|
123 (function() {{ |
|
124 var css = document.createElement('style'); |
|
125 css.setAttribute('type', 'text/css'); |
|
126 css.appendChild(document.createTextNode('{0}')); |
|
127 document.getElementsByTagName('head')[0].appendChild(css); |
|
128 }})()""" |
|
129 |
|
130 style = css.replace("'", "\\'").replace("\n", "\\n") |
|
131 return source.format(style) |
|
132 |
|
133 |
|
134 def getFormData(pos): |
|
135 """ |
|
136 Function generating a script to extract data for a form element. |
|
137 |
|
138 @param pos position to extract data at |
|
139 @type QPoint |
|
140 @return script to extract form data |
|
141 @rtype str |
|
142 """ |
|
143 source = """ |
|
144 (function() {{ |
|
145 var e = document.elementFromPoint({0}, {1}); |
|
146 if (!e || e.tagName.toLowerCase() != 'input') |
|
147 return; |
|
148 var fe = e.parentElement; |
|
149 while (fe) {{ |
|
150 if (fe.tagName.toLowerCase() != 'form') |
|
151 break; |
|
152 fe = fe.parentElement; |
|
153 }} |
|
154 if (!fe) |
|
155 return; |
|
156 var res = {{ |
|
157 method: fe.method.toLowerCase(), |
|
158 action: fe.action, |
|
159 inputName: e.name, |
|
160 inputs: [], |
|
161 }}; |
|
162 for (var i = 0; i < fe.length; ++i) {{ |
|
163 var input = fe.elements[i]; |
|
164 res.inputs.push([input.name, input.value]); |
|
165 }} |
|
166 return res; |
|
167 }})()""" |
|
168 return source.format(pos.x(), pos.y()) |
|
169 |
|
170 |
|
171 def getAllImages(): |
|
172 """ |
|
173 Function generating a script to extract all image tags of a web page. |
|
174 |
|
175 @return script to extract image tags |
|
176 @rtype str |
|
177 """ |
|
178 source = """ |
|
179 (function() { |
|
180 var out = []; |
|
181 var imgs = document.getElementsByTagName('img'); |
|
182 for (var i = 0; i < imgs.length; ++i) { |
|
183 var e = imgs[i]; |
|
184 out.push({ |
|
185 src: e.src, |
|
186 alt: e.alt |
|
187 }); |
|
188 } |
|
189 return out; |
|
190 })()""" |
|
191 return source |
|
192 |
|
193 |
|
194 def getAllMetaAttributes(): |
|
195 """ |
|
196 Function generating a script to extract all meta attributes of a web page. |
|
197 |
|
198 @return script to extract meta attributes |
|
199 @rtype str |
|
200 """ |
|
201 source = """ |
|
202 (function() { |
|
203 var out = []; |
|
204 var meta = document.getElementsByTagName('meta'); |
|
205 for (var i = 0; i < meta.length; ++i) { |
|
206 var e = meta[i]; |
|
207 out.push({ |
|
208 name: e.getAttribute('name'), |
|
209 content: e.getAttribute('content'), |
|
210 httpequiv: e.getAttribute('http-equiv'), |
|
211 charset: e.getAttribute('charset') |
|
212 }); |
|
213 } |
|
214 return out; |
|
215 })()""" |
|
216 return source |
|
217 |
|
218 |
|
219 def getOpenSearchLinks(): |
|
220 """ |
|
221 Function generating a script to extract all open search links. |
|
222 |
|
223 @return script to extract all open serach links |
|
224 @rtype str |
|
225 """ |
|
226 source = """ |
|
227 (function() { |
|
228 var out = []; |
|
229 var links = document.getElementsByTagName('link'); |
|
230 for (var i = 0; i < links.length; ++i) { |
|
231 var e = links[i]; |
|
232 if (e.type == 'application/opensearchdescription+xml') { |
|
233 out.push({ |
|
234 url: e.getAttribute('href'), |
|
235 title: e.getAttribute('title') |
|
236 }); |
|
237 } |
|
238 } |
|
239 return out; |
|
240 })()""" |
|
241 return source |
|
242 |
|
243 |
|
244 def sendPostData(url, data): |
|
245 """ |
|
246 Function generating a script to send Post data. |
|
247 |
|
248 @param url URL to send the data to |
|
249 @type QUrl |
|
250 @param data data to be sent |
|
251 @type QByteArray |
|
252 @return script to send Post data |
|
253 @rtype str |
|
254 """ |
|
255 source = """ |
|
256 (function() {{ |
|
257 var form = document.createElement('form'); |
|
258 form.setAttribute('method', 'POST'); |
|
259 form.setAttribute('action', '{0}'); |
|
260 var val; |
|
261 {1} |
|
262 form.submit(); |
|
263 }})()""" |
|
264 |
|
265 valueSource = """ |
|
266 val = document.createElement('input'); |
|
267 val.setAttribute('type', 'hidden'); |
|
268 val.setAttribute('name', '{0}'); |
|
269 val.setAttribute('value', '{1}'); |
|
270 form.appendChild(val);""" |
|
271 |
|
272 values = "" |
|
273 query = QUrlQuery(data) |
|
274 for name, value in query.queryItems( |
|
275 QUrl.ComponentFormattingOption.FullyDecoded |
|
276 ): |
|
277 value = value.replace("'", "\\'") |
|
278 name = name.replace("'", "\\'") |
|
279 values += valueSource.format(name, value) |
|
280 |
|
281 return source.format(url.toString(), values) |
|
282 |
|
283 |
|
284 def setupFormObserver(): |
|
285 """ |
|
286 Function generating a script to monitor a web form for user entries. |
|
287 |
|
288 @return script to monitor a web page |
|
289 @rtype str |
|
290 """ |
|
291 source = """ |
|
292 (function() { |
|
293 function findUsername(inputs) { |
|
294 var usernameNames = ['user', 'name', 'login']; |
|
295 for (var i = 0; i < usernameNames.length; ++i) { |
|
296 for (var j = 0; j < inputs.length; ++j) |
|
297 if (inputs[j].type == 'text' && |
|
298 inputs[j].value.length && |
|
299 inputs[j].name.indexOf(usernameNames[i]) != -1) |
|
300 return inputs[j].value; |
|
301 } |
|
302 for (var i = 0; i < inputs.length; ++i) |
|
303 if (inputs[i].type == 'text' && inputs[i].value.length) |
|
304 return inputs[i].value; |
|
305 for (var i = 0; i < inputs.length; ++i) |
|
306 if (inputs[i].type == 'email' && inputs[i].value.length) |
|
307 return inputs[i].value; |
|
308 return ''; |
|
309 } |
|
310 |
|
311 function registerForm(form) { |
|
312 form.addEventListener('submit', function() { |
|
313 var form = this; |
|
314 var data = ''; |
|
315 var password = ''; |
|
316 var inputs = form.getElementsByTagName('input'); |
|
317 for (var i = 0; i < inputs.length; ++i) { |
|
318 var input = inputs[i]; |
|
319 var type = input.type.toLowerCase(); |
|
320 if (type != 'text' && type != 'password' && |
|
321 type != 'email') |
|
322 continue; |
|
323 if (!password && type == 'password') |
|
324 password = input.value; |
|
325 data += encodeURIComponent(input.name); |
|
326 data += '='; |
|
327 data += encodeURIComponent(input.value); |
|
328 data += '&'; |
|
329 } |
|
330 if (!password) |
|
331 return; |
|
332 data = data.substring(0, data.length - 1); |
|
333 var url = window.location.href; |
|
334 var username = findUsername(inputs); |
|
335 external.passwordManager.formSubmitted( |
|
336 url, username, password, data); |
|
337 }, true); |
|
338 } |
|
339 |
|
340 for (var i = 0; i < document.forms.length; ++i) |
|
341 registerForm(document.forms[i]); |
|
342 |
|
343 var observer = new MutationObserver(function(mutations) { |
|
344 for (var mutation of mutations) |
|
345 for (var node of mutation.addedNodes) |
|
346 if (node.tagName && node.tagName.toLowerCase() == 'form') |
|
347 registerForm(node); |
|
348 }); |
|
349 observer.observe(document.documentElement, { |
|
350 childList: true, subtree: true |
|
351 }); |
|
352 |
|
353 })()""" |
|
354 return source |
|
355 |
|
356 |
|
357 def completeFormData(data): |
|
358 """ |
|
359 Function generating a script to fill in form data. |
|
360 |
|
361 @param data data to be filled into the form |
|
362 @type QByteArray |
|
363 @return script to fill a form |
|
364 @rtype str |
|
365 """ |
|
366 source = """ |
|
367 (function() {{ |
|
368 var data = '{0}'.split('&'); |
|
369 var inputs = document.getElementsByTagName('input'); |
|
370 |
|
371 for (var i = 0; i < data.length; ++i) {{ |
|
372 var pair = data[i].split('='); |
|
373 if (pair.length != 2) |
|
374 continue; |
|
375 var key = decodeURIComponent(pair[0]); |
|
376 var val = decodeURIComponent(pair[1]); |
|
377 for (var j = 0; j < inputs.length; ++j) {{ |
|
378 var input = inputs[j]; |
|
379 var type = input.type.toLowerCase(); |
|
380 if (type != 'text' && type != 'password' && |
|
381 type != 'email') |
|
382 continue; |
|
383 if (input.name == key) {{ |
|
384 input.value = val; |
|
385 input.dispatchEvent(new Event('change')); |
|
386 }} |
|
387 }} |
|
388 }} |
|
389 |
|
390 }})()""" |
|
391 |
|
392 data = bytes(data).decode("utf-8") |
|
393 data = data.replace("'", "\\'") |
|
394 return source.format(data) |
|
395 |
|
396 |
|
397 def setCss(css): |
|
398 """ |
|
399 Function generating a script to set a given CSS style sheet. |
|
400 |
|
401 @param css style sheet |
|
402 @type str |
|
403 @return script to set the style sheet |
|
404 @rtype str |
|
405 """ |
|
406 source = """ |
|
407 (function() {{ |
|
408 var css = document.createElement('style'); |
|
409 css.setAttribute('type', 'text/css'); |
|
410 css.appendChild(document.createTextNode('{0}')); |
|
411 document.getElementsByTagName('head')[0].appendChild(css); |
|
412 }})()""" |
|
413 style = css.replace("'", "\\'").replace("\n", "\\n") |
|
414 return source.format(style) |
|
415 |
|
416 |
|
417 def scrollToAnchor(anchor): |
|
418 """ |
|
419 Function generating script to scroll to a given anchor. |
|
420 |
|
421 @param anchor name of the anchor to scroll to |
|
422 @type str |
|
423 @return script to set the style sheet |
|
424 @rtype str |
|
425 """ |
|
426 source = """ |
|
427 (function() {{ |
|
428 var e = document.getElementById("{0}") |
|
429 if (!e) {{ |
|
430 var els = document.querySelectorAll("[name='{0}']"); |
|
431 if (els.length) |
|
432 e = els[0] |
|
433 }} |
|
434 if (e) |
|
435 e.scrollIntoView() |
|
436 }})()""" |
|
437 return source.format(anchor) |
|
438 |
|
439 ########################################################################### |
|
440 ## scripts below are specific for eric |
|
441 ########################################################################### |
|
442 |
|
443 |
|
444 def getFeedLinks(): |
|
445 """ |
|
446 Function generating a script to extract all RSS and Atom feed links. |
|
447 |
|
448 @return script to extract all RSS and Atom feed links |
|
449 @rtype str |
|
450 """ |
|
451 source = """ |
|
452 (function() { |
|
453 var out = []; |
|
454 var links = document.getElementsByTagName('link'); |
|
455 for (var i = 0; i < links.length; ++i) { |
|
456 var e = links[i]; |
|
457 if ((e.rel == 'alternate') && |
|
458 ((e.type == 'application/atom+xml') || |
|
459 (e.type == 'application/rss+xml') |
|
460 ) |
|
461 ) { |
|
462 out.push({ |
|
463 url: e.getAttribute('href'), |
|
464 title: e.getAttribute('title') |
|
465 }); |
|
466 } |
|
467 } |
|
468 return out; |
|
469 })()""" |
|
470 return source |