|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2019 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 __future__ import unicode_literals |
|
16 |
|
17 from PyQt5.QtCore import QUrlQuery, QUrl |
|
18 |
|
19 from .WebBrowserTools import readAllFileContents |
|
20 |
|
21 |
|
22 def setupWebChannel(worldId): |
|
23 """ |
|
24 Function generating a script to setup the web channel. |
|
25 |
|
26 @param worldId world ID for which to setup the channel |
|
27 @type int |
|
28 @return script to setup the web channel |
|
29 @rtype str |
|
30 """ |
|
31 source = """ |
|
32 // ==UserScript== |
|
33 {0} |
|
34 // ==/UserScript== |
|
35 |
|
36 (function() {{ |
|
37 {1} |
|
38 |
|
39 function registerExternal(e) {{ |
|
40 window.external = e; |
|
41 if (window.external) {{ |
|
42 var event = document.createEvent('Event'); |
|
43 event.initEvent('_eric_external_created', true, true); |
|
44 window._eric_external = true; |
|
45 document.dispatchEvent(event); |
|
46 }} |
|
47 }} |
|
48 |
|
49 if (self !== top) {{ |
|
50 if (top._eric_external) |
|
51 registerExternal(top.external); |
|
52 else |
|
53 top.document.addEventListener( |
|
54 '_eric_external_created', function() {{ |
|
55 registerExternal(top.external); |
|
56 }}); |
|
57 return; |
|
58 }} |
|
59 |
|
60 function registerWebChannel() {{ |
|
61 try {{ |
|
62 new QWebChannel(qt.webChannelTransport, function(channel) {{ |
|
63 var external = channel.objects.eric_object; |
|
64 external.extra = {{}}; |
|
65 for (var key in channel.objects) {{ |
|
66 if (key != 'eric_object' && key.startsWith('eric_')) {{ |
|
67 external.extra[key.substr(5)] = channel.objects[key]; |
|
68 }} |
|
69 }} |
|
70 registerExternal(external); |
|
71 }}); |
|
72 }} catch (e) {{ |
|
73 setTimeout(registerWebChannel, 100); |
|
74 }} |
|
75 }} |
|
76 registerWebChannel(); |
|
77 |
|
78 }})()""" |
|
79 |
|
80 from WebBrowser.WebBrowserPage import WebBrowserPage |
|
81 if worldId == WebBrowserPage.SafeJsWorld: |
|
82 match = "// @exclude eric:*" |
|
83 else: |
|
84 match = "// @include eric:*" |
|
85 return source.format( |
|
86 match, readAllFileContents(":/javascript/qwebchannel.js")) |
|
87 |
|
88 |
|
89 def setupWindowObject(): |
|
90 """ |
|
91 Function generating a script to setup window.object add-ons. |
|
92 |
|
93 @return generated script |
|
94 @rtype str |
|
95 """ |
|
96 source = """ |
|
97 (function() { |
|
98 var external = {}; |
|
99 external.AddSearchProvider = function(url) { |
|
100 window.location = 'eric:AddSearchProvider?url=' + url; |
|
101 }; |
|
102 external.IsSearchProviderInstalled = function(url) { |
|
103 console.warn('NOT IMPLEMENTED: IsSearchProviderInstalled()'); |
|
104 return false; |
|
105 }; |
|
106 window.external = external; |
|
107 window.print = function() { |
|
108 window.location = 'eric:PrintPage'; |
|
109 }; |
|
110 })()""" |
|
111 |
|
112 return source |
|
113 |
|
114 |
|
115 def setStyleSheet(css): |
|
116 """ |
|
117 Function generating a script to set a user style sheet. |
|
118 |
|
119 @param css style sheet to be applied |
|
120 @type str |
|
121 @return script to set a user style sheet |
|
122 @rtype str |
|
123 """ |
|
124 source = """ |
|
125 (function() {{ |
|
126 var css = document.createElement('style'); |
|
127 css.setAttribute('type', 'text/css'); |
|
128 css.appendChild(document.createTextNode('{0}')); |
|
129 document.getElementsByTagName('head')[0].appendChild(css); |
|
130 }})()""" |
|
131 |
|
132 style = css.replace("'", "\\'").replace("\n", "\\n") |
|
133 return source.format(style) |
|
134 |
|
135 |
|
136 def getFormData(pos): |
|
137 """ |
|
138 Function generating a script to extract data for a form element. |
|
139 |
|
140 @param pos position to extract data at |
|
141 @type QPoint |
|
142 @return script to extract form data |
|
143 @rtype str |
|
144 """ |
|
145 source = """ |
|
146 (function() {{ |
|
147 var e = document.elementFromPoint({0}, {1}); |
|
148 if (!e || e.tagName.toLowerCase() != 'input') |
|
149 return; |
|
150 var fe = e.parentElement; |
|
151 while (fe) {{ |
|
152 if (fe.tagName.toLowerCase() != 'form') |
|
153 break; |
|
154 fe = fe.parentElement; |
|
155 }} |
|
156 if (!fe) |
|
157 return; |
|
158 var res = {{ |
|
159 method: fe.method.toLowerCase(), |
|
160 action: fe.action, |
|
161 inputName: e.name, |
|
162 inputs: [], |
|
163 }}; |
|
164 for (var i = 0; i < fe.length; ++i) {{ |
|
165 var input = fe.elements[i]; |
|
166 res.inputs.push([input.name, input.value]); |
|
167 }} |
|
168 return res; |
|
169 }})()""" |
|
170 return source.format(pos.x(), pos.y()) |
|
171 |
|
172 |
|
173 def getAllImages(): |
|
174 """ |
|
175 Function generating a script to extract all image tags of a web page. |
|
176 |
|
177 @return script to extract image tags |
|
178 @rtype str |
|
179 """ |
|
180 source = """ |
|
181 (function() { |
|
182 var out = []; |
|
183 var imgs = document.getElementsByTagName('img'); |
|
184 for (var i = 0; i < imgs.length; ++i) { |
|
185 var e = imgs[i]; |
|
186 out.push({ |
|
187 src: e.src, |
|
188 alt: e.alt |
|
189 }); |
|
190 } |
|
191 return out; |
|
192 })()""" |
|
193 return source |
|
194 |
|
195 |
|
196 def getAllMetaAttributes(): |
|
197 """ |
|
198 Function generating a script to extract all meta attributes of a web page. |
|
199 |
|
200 @return script to extract meta attributes |
|
201 @rtype str |
|
202 """ |
|
203 source = """ |
|
204 (function() { |
|
205 var out = []; |
|
206 var meta = document.getElementsByTagName('meta'); |
|
207 for (var i = 0; i < meta.length; ++i) { |
|
208 var e = meta[i]; |
|
209 out.push({ |
|
210 name: e.getAttribute('name'), |
|
211 content: e.getAttribute('content'), |
|
212 httpequiv: e.getAttribute('http-equiv'), |
|
213 charset: e.getAttribute('charset') |
|
214 }); |
|
215 } |
|
216 return out; |
|
217 })()""" |
|
218 return source |
|
219 |
|
220 |
|
221 def getOpenSearchLinks(): |
|
222 """ |
|
223 Function generating a script to extract all open search links. |
|
224 |
|
225 @return script to extract all open serach links |
|
226 @rtype str |
|
227 """ |
|
228 source = """ |
|
229 (function() { |
|
230 var out = []; |
|
231 var links = document.getElementsByTagName('link'); |
|
232 for (var i = 0; i < links.length; ++i) { |
|
233 var e = links[i]; |
|
234 if (e.type == 'application/opensearchdescription+xml') { |
|
235 out.push({ |
|
236 url: e.getAttribute('href'), |
|
237 title: e.getAttribute('title') |
|
238 }); |
|
239 } |
|
240 } |
|
241 return out; |
|
242 })()""" |
|
243 return source |
|
244 |
|
245 |
|
246 def sendPostData(url, data): |
|
247 """ |
|
248 Function generating a script to send Post data. |
|
249 |
|
250 @param url URL to send the data to |
|
251 @type QUrl |
|
252 @param data data to be sent |
|
253 @type QByteArray |
|
254 @return script to send Post data |
|
255 @rtype str |
|
256 """ |
|
257 source = """ |
|
258 (function() {{ |
|
259 var form = document.createElement('form'); |
|
260 form.setAttribute('method', 'POST'); |
|
261 form.setAttribute('action', '{0}'); |
|
262 var val; |
|
263 {1} |
|
264 form.submit(); |
|
265 }})()""" |
|
266 |
|
267 valueSource = """ |
|
268 val = document.createElement('input'); |
|
269 val.setAttribute('type', 'hidden'); |
|
270 val.setAttribute('name', '{0}'); |
|
271 val.setAttribute('value', '{1}'); |
|
272 form.appendChild(val);""" |
|
273 |
|
274 values = "" |
|
275 query = QUrlQuery(data) |
|
276 for name, value in query.queryItems(QUrl.FullyDecoded): |
|
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 |