diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index d06bc9cac..e1f0b57db 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -29,7 +29,7 @@ import glob import attr from PyQt5.QtCore import pyqtSignal, QObject -from qutebrowser.utils import log, standarddir +from qutebrowser.utils import log, standarddir, jinja from qutebrowser.commands import cmdutils @@ -41,115 +41,6 @@ def _scripts_dir(): class GreasemonkeyScript: """Container class for userscripts, parses metadata blocks.""" - GM_BOOTSTRAP_TEMPLATE = r"""var _qute_script_id = "__gm_{scriptName}"; - -function GM_log(text) {{ - console.log(text); -}} - -GM_info = (function() {{ - return {{ - 'script': {scriptInfo}, - 'scriptMetaStr': {scriptMeta}, - 'scriptWillUpdate': false, - 'version': '0.0.1', - 'scriptHandler': 'Tampermonkey' //so scripts don't expect exportFunction - }}; -}}()); - -function GM_setValue(key, value) {{ - if (localStorage !== null && - typeof key === "string" && - (typeof value === "string" || - typeof value === "number" || - typeof value == "boolean")) {{ - localStorage.setItem(_qute_script_id + key, value); - }} -}} - -function GM_getValue(key, default_) {{ - if (localStorage !== null && typeof key === "string") {{ - return localStorage.getItem(_qute_script_id + key) || default_; - }} -}} - -function GM_deleteValue(key) {{ - if (localStorage !== null && typeof key === "string") {{ - localStorage.removeItem(_qute_script_id + key); - }} -}} - -function GM_listValues() {{ - var i; - var keys = []; - for (i = 0; i < localStorage.length; ++i) {{ - if (localStorage.key(i).startsWith(_qute_script_id)) {{ - keys.push(localStorage.key(i)); - }} - }} - return keys; -}} - -function GM_openInTab(url) {{ - window.open(url); -}} - - -// Almost verbatim copy from Eric -function GM_xmlhttpRequest(/* object */ details) {{ - details.method = details.method.toUpperCase() || "GET"; - - if(!details.url) {{ - throw("GM_xmlhttpRequest requires an URL."); - }} - - // build XMLHttpRequest object - var oXhr = new XMLHttpRequest; - // run it - if("onreadystatechange" in details) - oXhr.onreadystatechange = function() {{ - details.onreadystatechange(oXhr) - }}; - if("onload" in details) - oXhr.onload = function() {{ details.onload(oXhr) }}; - if("onerror" in details) - oXhr.onerror = function() {{ details.onerror(oXhr) }}; - - oXhr.open(details.method, details.url, true); - - if("headers" in details) - for(var header in details.headers) - oXhr.setRequestHeader(header, details.headers[header]); - - if("data" in details) - oXhr.send(details.data); - else - oXhr.send(); -}} - -function GM_addStyle(/* String */ styles) {{ - var head = document.getElementsByTagName("head")[0]; - if (head === undefined) {{ - document.onreadystatechange = function() {{ - if (document.readyState == "interactive") {{ - var oStyle = document.createElement("style"); - oStyle.setAttribute("type", "text/css"); - oStyle.appendChild(document.createTextNode(styles)); - document.getElementsByTagName("head")[0].appendChild(oStyle); - }} - }} - }} - else {{ - var oStyle = document.createElement("style"); - oStyle.setAttribute("type", "text/css"); - oStyle.appendChild(document.createTextNode(styles)); - head.appendChild(oStyle); - }} -}} - -unsafeWindow = window; -""" - def __init__(self, properties, code): self._code = code self.includes = [] @@ -200,12 +91,12 @@ unsafeWindow = window; browser's debugger/inspector will not match up to the line numbers in the source script directly. """ - gm_bootstrap = self.GM_BOOTSTRAP_TEMPLATE.format( - scriptName=self.name, - scriptInfo=self._meta_json(), - scriptMeta=self.script_meta) - return '\n'.join( - ["(function(){", gm_bootstrap, self._code, "})();"]) + return jinja.js_environment.get_template( + 'greasemonkey_wrapper.js').render( + scriptName=self.name, + scriptInfo=self._meta_json(), + scriptMeta=self.script_meta, + scriptSource=self._code) def _meta_json(self): return json.dumps({ diff --git a/qutebrowser/javascript/.eslintignore b/qutebrowser/javascript/.eslintignore index ca4d3c667..036a72cfe 100644 --- a/qutebrowser/javascript/.eslintignore +++ b/qutebrowser/javascript/.eslintignore @@ -1,2 +1,4 @@ # Upstream Mozilla's code pac_utils.js +# Actually a jinja template so eslint chokes on the {{}} syntax. +greasemonkey_wrapper.js diff --git a/qutebrowser/javascript/greasemonkey_wrapper.js b/qutebrowser/javascript/greasemonkey_wrapper.js new file mode 100644 index 000000000..b49dc2c02 --- /dev/null +++ b/qutebrowser/javascript/greasemonkey_wrapper.js @@ -0,0 +1,118 @@ +(function () { + var _qute_script_id = "__gm_{{ scriptName }}"; + + function GM_log(text) { + console.log(text); + } + + var GM_info = (function () { + return { + 'script': {{ scriptInfo }}, + 'scriptMetaStr': {{ scriptMeta }}, + 'scriptWillUpdate': false, + 'version': '0.0.1', + 'scriptHandler': 'Tampermonkey' // so scripts don't expect exportFunction + }; + }()); + + function GM_setValue(key, value) { + if (localStorage !== null && + typeof key === "string" && + (typeof value === "string" || + typeof value === "number" || + typeof value === "boolean")) { + localStorage.setItem(_qute_script_id + key, value); + } + } + + function GM_getValue(key, default_) { + if (localStorage !== null && typeof key === "string") { + return localStorage.getItem(_qute_script_id + key) || default_; + } + } + + function GM_deleteValue(key) { + if (localStorage !== null && typeof key === "string") { + localStorage.removeItem(_qute_script_id + key); + } + } + + function GM_listValues() { + var i, keys = []; + for (i = 0; i < localStorage.length; i = i + 1) { + if (localStorage.key(i).startsWith(_qute_script_id)) { + keys.push(localStorage.key(i)); + } + } + return keys; + } + + function GM_openInTab(url) { + window.open(url); + } + + + // Almost verbatim copy from Eric + function GM_xmlhttpRequest(/* object */ details) { + details.method = details.method.toUpperCase() || "GET"; + + if (!details.url) { + throw ("GM_xmlhttpRequest requires an URL."); + } + + // build XMLHttpRequest object + var oXhr = new XMLHttpRequest(); + // run it + if ("onreadystatechange" in details) { + oXhr.onreadystatechange = function () { + details.onreadystatechange(oXhr); + }; + } + if ("onload" in details) { + oXhr.onload = function () { details.onload(oXhr) }; + } + if ("onerror" in details) { + oXhr.onerror = function () { details.onerror(oXhr) }; + } + + oXhr.open(details.method, details.url, true); + + if ("headers" in details) { + for (var header in details.headers) { + oXhr.setRequestHeader(header, details.headers[header]); + } + } + + if ("data" in details) { + oXhr.send(details.data); + } else { + oXhr.send(); + } + } + + function GM_addStyle(/* String */ styles) { + var head = document.getElementsByTagName("head")[0]; + if (head === undefined) { + document.onreadystatechange = function () { + if (document.readyState == "interactive") { + var oStyle = document.createElement("style"); + oStyle.setAttribute("type", "text/css"); + oStyle.appendChild(document.createTextNode(styles)); + document.getElementsByTagName("head")[0].appendChild(oStyle); + } + } + } + else { + var oStyle = document.createElement("style"); + oStyle.setAttribute("type", "text/css"); + oStyle.appendChild(document.createTextNode(styles)); + head.appendChild(oStyle); + } + } + + unsafeWindow = window; + + //====== The actual user script source ======// +{{ scriptSource }} + //====== End User Script ======// +})(); diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index e7b536b60..b6f53645b 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -136,3 +136,4 @@ def render(template, **kwargs): environment = Environment() +js_environment = jinja2.Environment(loader=Loader('javascript'))