This commit is contained in:
Florian Bruhin 2017-12-06 11:50:59 +01:00
parent dd63508be7
commit 2633dcc0d5
8 changed files with 51 additions and 52 deletions

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Load, parse and make avalaible greasemonkey scripts.""" """Load, parse and make available Greasemonkey scripts."""
import re import re
import os import os
@ -39,6 +39,7 @@ def _scripts_dir():
class GreasemonkeyScript: class GreasemonkeyScript:
"""Container class for userscripts, parses metadata blocks.""" """Container class for userscripts, parses metadata blocks."""
def __init__(self, properties, code): def __init__(self, properties, code):
@ -72,15 +73,15 @@ class GreasemonkeyScript:
@classmethod @classmethod
def parse(cls, source): def parse(cls, source):
"""GreaseMonkeyScript factory. """GreasemonkeyScript factory.
Takes a userscript source and returns a GreaseMonkeyScript. Takes a userscript source and returns a GreasemonkeyScript.
Parses the greasemonkey metadata block, if present, to fill out Parses the Greasemonkey metadata block, if present, to fill out
attributes. attributes.
""" """
matches = re.split(cls.HEADER_REGEX, source, maxsplit=2) matches = re.split(cls.HEADER_REGEX, source, maxsplit=2)
try: try:
_, props, _code = matches _head, props, _code = matches
except ValueError: except ValueError:
props = "" props = ""
script = cls(re.findall(cls.PROPS_REGEX, props), source) script = cls(re.findall(cls.PROPS_REGEX, props), source)
@ -90,9 +91,9 @@ class GreasemonkeyScript:
return script return script
def code(self): def code(self):
"""Return the processed javascript code of this script. """Return the processed JavaScript code of this script.
Adorns the source code with GM_* methods for greasemonkey Adorns the source code with GM_* methods for Greasemonkey
compatibility and wraps it in an IFFE to hide it within a compatibility and wraps it in an IFFE to hide it within a
lexical scope. Note that this means line numbers in your lexical scope. Note that this means line numbers in your
browser's debugger/inspector will not match up to the line browser's debugger/inspector will not match up to the line
@ -118,6 +119,7 @@ class GreasemonkeyScript:
@attr.s @attr.s
class MatchingScripts(object): class MatchingScripts(object):
"""All userscripts registered to run on a particular url.""" """All userscripts registered to run on a particular url."""
url = attr.ib() url = attr.ib()
@ -128,11 +130,11 @@ class MatchingScripts(object):
class GreasemonkeyManager(QObject): class GreasemonkeyManager(QObject):
"""Manager of userscripts and a greasemonkey compatible environment. """Manager of userscripts and a Greasemonkey compatible environment.
Signals: Signals:
scripts_reloaded: Emitted when scripts are reloaded from disk. scripts_reloaded: Emitted when scripts are reloaded from disk.
Any any cached or already-injected scripts should be Any cached or already-injected scripts should be
considered obselete. considered obselete.
""" """
@ -151,8 +153,8 @@ class GreasemonkeyManager(QObject):
def load_scripts(self): def load_scripts(self):
"""Re-read Greasemonkey scripts from disk. """Re-read Greasemonkey scripts from disk.
The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's The scripts are read from a 'greasemonkey' subdirectory in
data directory (see `:version`). qutebrowser's data directory (see `:version`).
""" """
self._run_start = [] self._run_start = []
self._run_end = [] self._run_end = []

View File

@ -245,10 +245,10 @@ def _init_profiles():
def inject_userscripts(): def inject_userscripts():
"""Register user javascript files with the global profiles.""" """Register user JavaScript files with the global profiles."""
# The greasemonkey metadata block support in qtwebengine only starts at 5.8 # The Greasemonkey metadata block support in QtWebEngine only starts at
# Otherwise have to handle injecting the scripts into the page at very # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in response
# early load, probs same place in view as the enableJS check. # to urlChanged.
if not qtutils.version_check('5.8'): if not qtutils.version_check('5.8'):
return return
@ -256,10 +256,7 @@ def inject_userscripts():
# just get replaced by new gm scripts like if we were injecting them # just get replaced by new gm scripts like if we were injecting them
# ourselves so we need to remove all gm scripts, while not removing # ourselves so we need to remove all gm scripts, while not removing
# any other stuff that might have been added. Like the one for # any other stuff that might have been added. Like the one for
# stylsheets. # stylesheets.
# Could either use a different world for gm scripts, check for gm metadata
# values (would mean no non-gm userscripts), or check the code for
# _qute_script_id
for profile in [default_profile, private_profile]: for profile in [default_profile, private_profile]:
scripts = profile.scripts() scripts = profile.scripts()
for script in scripts.toList(): for script in scripts.toList():

View File

@ -308,7 +308,7 @@ class WebEnginePage(QWebEnginePage):
def _inject_userjs(self, url): def _inject_userjs(self, url):
"""Inject userscripts registered for `url` into the current page.""" """Inject userscripts registered for `url` into the current page."""
if qtutils.version_check('5.8'): if qtutils.version_check('5.8'):
# Handled in webenginetab with the builtin greasemonkey # Handled in webenginetab with the builtin Greasemonkey
# support. # support.
return return

View File

@ -95,7 +95,7 @@ class BrowserPage(QWebPage):
"""Connect userjs related signals to `frame`. """Connect userjs related signals to `frame`.
Connect the signals used as triggers for injecting user Connect the signals used as triggers for injecting user
javascripts into the passed QWebFrame. JavaScripts into the passed QWebFrame.
""" """
log.greasemonkey.debug("Connecting to frame {} ({})" log.greasemonkey.debug("Connecting to frame {} ({})"
.format(frame, frame.url().toDisplayString())) .format(frame, frame.url().toDisplayString()))
@ -299,7 +299,7 @@ class BrowserPage(QWebPage):
self.error_occurred = False self.error_occurred = False
def _inject_userjs(self, frame): def _inject_userjs(self, frame):
"""Inject user javascripts into the page. """Inject user JavaScripts into the page.
Args: Args:
frame: The QWebFrame to inject the user scripts into. frame: The QWebFrame to inject the user scripts into.

View File

@ -10,7 +10,8 @@
'scriptMetaStr': {{ scriptMeta | tojson }}, 'scriptMetaStr': {{ scriptMeta | tojson }},
'scriptWillUpdate': false, 'scriptWillUpdate': false,
'version': "0.0.1", 'version': "0.0.1",
'scriptHandler': 'Tampermonkey' // so scripts don't expect exportFunction // so scripts don't expect exportFunction
'scriptHandler': 'Tampermonkey',
}; };
function checkKey(key, funcName) { function checkKey(key, funcName) {
@ -40,7 +41,7 @@
} }
function GM_listValues() { function GM_listValues() {
let keys = []; const keys = [];
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
if (localStorage.key(i).startsWith(_qute_script_id)) { if (localStorage.key(i).startsWith(_qute_script_id)) {
keys.push(localStorage.key(i).slice(_qute_script_id.length)); keys.push(localStorage.key(i).slice(_qute_script_id.length));
@ -59,11 +60,11 @@
details.method = details.method ? details.method.toUpperCase() : "GET"; details.method = details.method ? details.method.toUpperCase() : "GET";
if (!details.url) { if (!details.url) {
throw ("GM_xmlhttpRequest requires an URL."); throw new Error("GM_xmlhttpRequest requires an URL.");
} }
// build XMLHttpRequest object // build XMLHttpRequest object
let oXhr = new XMLHttpRequest(); const oXhr = new XMLHttpRequest();
// run it // run it
if ("onreadystatechange" in details) { if ("onreadystatechange" in details) {
oXhr.onreadystatechange = function() { oXhr.onreadystatechange = function() {
@ -71,16 +72,16 @@
}; };
} }
if ("onload" in details) { if ("onload" in details) {
oXhr.onload = function () { details.onload(oXhr) }; oXhr.onload = function() { details.onload(oXhr); };
} }
if ("onerror" in details) { if ("onerror" in details) {
oXhr.onerror = function () { details.onerror(oXhr) }; oXhr.onerror = function () { details.onerror(oXhr); };
} }
oXhr.open(details.method, details.url, true); oXhr.open(details.method, details.url, true);
if ("headers" in details) { if ("headers" in details) {
for (let header in details.headers) { for (const header in details.headers) {
oXhr.setRequestHeader(header, details.headers[header]); oXhr.setRequestHeader(header, details.headers[header]);
} }
} }
@ -93,19 +94,18 @@
} }
function GM_addStyle(/* String */ styles) { function GM_addStyle(/* String */ styles) {
let oStyle = document.createElement("style"); const oStyle = document.createElement("style");
oStyle.setAttribute("type", "text/css"); oStyle.setAttribute("type", "text/css");
oStyle.appendChild(document.createTextNode(styles)); oStyle.appendChild(document.createTextNode(styles));
let head = document.getElementsByTagName("head")[0]; const head = document.getElementsByTagName("head")[0];
if (head === undefined) { if (head === undefined) {
document.onreadystatechange = function() { document.onreadystatechange = function() {
if (document.readyState == "interactive") { if (document.readyState === "interactive") {
document.getElementsByTagName("head")[0].appendChild(oStyle); document.getElementsByTagName("head")[0].appendChild(oStyle);
} }
} };
} } else {
else {
head.appendChild(oStyle); head.appendChild(oStyle);
} }
} }

View File

@ -124,8 +124,8 @@ Feature: Javascript stuff
And I run :tab-next And I run :tab-next
Then the window sizes should be the same Then the window sizes should be the same
Scenario: Have a greasemonkey script run at page start Scenario: Have a GreaseMonkey script run at page start
When I have a greasemonkey file saved for document-start with noframes unset When I have a GreaseMonkey file saved for document-start with noframes unset
And I run :greasemonkey-reload And I run :greasemonkey-reload
And I open data/hints/iframe.html And I open data/hints/iframe.html
# This second reload is required in webengine < 5.8 for scripts # This second reload is required in webengine < 5.8 for scripts
@ -133,15 +133,15 @@ Feature: Javascript stuff
And I run :reload And I run :reload
Then the javascript message "Script is running on /data/hints/iframe.html" should be logged Then the javascript message "Script is running on /data/hints/iframe.html" should be logged
Scenario: Have a greasemonkey script running on frames Scenario: Have a GreaseMonkey script running on frames
When I have a greasemonkey file saved for document-end with noframes unset When I have a GreaseMonkey file saved for document-end with noframes unset
And I run :greasemonkey-reload And I run :greasemonkey-reload
And I open data/hints/iframe.html And I open data/hints/iframe.html
Then the javascript message "Script is running on /data/hints/html/wrapped.html" should be logged Then the javascript message "Script is running on /data/hints/html/wrapped.html" should be logged
@flaky @flaky
Scenario: Have a greasemonkey script running on noframes Scenario: Have a GreaseMonkey script running on noframes
When I have a greasemonkey file saved for document-end with noframes set When I have a GreaseMonkey file saved for document-end with noframes set
And I run :greasemonkey-reload And I run :greasemonkey-reload
And I open data/hints/iframe.html And I open data/hints/iframe.html
Then the javascript message "Script is running on /data/hints/html/wrapped.html" should not be logged Then the javascript message "Script is running on /data/hints/html/wrapped.html" should not be logged

View File

@ -35,7 +35,7 @@ def check_window_sizes(quteproc):
test_gm_script = r""" test_gm_script = r"""
// ==UserScript== // ==UserScript==
// @name Qutebrowser test userscript // @name qutebrowser test userscript
// @namespace invalid.org // @namespace invalid.org
// @include http://localhost:*/data/hints/iframe.html // @include http://localhost:*/data/hints/iframe.html
// @include http://localhost:*/data/hints/html/wrapped.html // @include http://localhost:*/data/hints/html/wrapped.html
@ -47,7 +47,7 @@ console.log("Script is running on " + window.location.pathname);
""" """
@bdd.when(bdd.parsers.parse("I have a greasemonkey file saved for {stage} " @bdd.when(bdd.parsers.parse("I have a GreaseMonkey file saved for {stage} "
"with noframes {frameset}")) "with noframes {frameset}"))
def create_greasemonkey_file(quteproc, stage, frameset): def create_greasemonkey_file(quteproc, stage, frameset):
script_path = os.path.join(quteproc.basedir, 'data', 'greasemonkey') script_path = os.path.join(quteproc.basedir, 'data', 'greasemonkey')

View File

@ -28,7 +28,7 @@ from qutebrowser.browser import greasemonkey
test_gm_script = """ test_gm_script = """
// ==UserScript== // ==UserScript==
// @name Qutebrowser test userscript // @name qutebrowser test userscript
// @namespace invalid.org // @namespace invalid.org
// @include http://localhost:*/data/title.html // @include http://localhost:*/data/title.html
// @match http://trolol* // @match http://trolol*
@ -58,7 +58,7 @@ def test_all():
gm_manager = greasemonkey.GreasemonkeyManager() gm_manager = greasemonkey.GreasemonkeyManager()
assert (gm_manager.all_scripts()[0].name == assert (gm_manager.all_scripts()[0].name ==
"Qutebrowser test userscript") "qutebrowser test userscript")
@pytest.mark.parametrize("url, expected_matches", [ @pytest.mark.parametrize("url, expected_matches", [
@ -70,7 +70,7 @@ def test_all():
('https://badhost.xxx/', 0), ('https://badhost.xxx/', 0),
]) ])
def test_get_scripts_by_url(url, expected_matches): def test_get_scripts_by_url(url, expected_matches):
"""Check greasemonkey include/exclude rules work.""" """Check Greasemonkey include/exclude rules work."""
save_script(test_gm_script, 'test.user.js') save_script(test_gm_script, 'test.user.js')
gm_manager = greasemonkey.GreasemonkeyManager() gm_manager = greasemonkey.GreasemonkeyManager()