From 54ca9b34e529e7d0fc84c77c16cff21ce302381e Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 25 Jun 2018 14:28:28 +1200 Subject: [PATCH] greasemonkey: enable running in isolated js worlds QtWebEngine (via chromium) has the ability to run injected scripts in isolated "worlds". What is isolated is just the javascript environment, so variables and functions defined by the page and the script won't clobber each other, or be able to interact (including variables saved to the global `window` object). The DOM is still accessible from "isolated" scripts. This is NOT a security measure. You cannot put untrusted scripts in one of these isolated worlds and expect it to not be able to do whatever page js can do, it is just for namespacing convenience. See https://stackoverflow.com/questions/9515704/insert-code-into-the-page-context-using-a-content-script for some examples of how to inject scripts into the page scope using DOM elements. Now you can specify the world ID in a `@qute-js-world` directive like: ``` // ==UserScript== // @name Do thing // @match *://some.site/* // @qute-js-world 1234 // ==/UserScript== document.body.innerHTML = "overwritten" ``` The QtWebEngine docs say worldid is a `quint32` so you can put whatever number (positive, whole, real) you want there. I have chosen to allow the `qutebrowser.utils.usertypes` enum as aliases for IDs that are predefined in `qutebrowser.browser.webengine.webenginetab._JS_WORLD_MAP`. So you can pass `main`, `application`, `user` or `jseval` in there too. `main` (0) is the default one and is the only one in which JS disabled when `content.javascript.enabled` is set to `false`. All others are still enabled. I'm not sure whether using any of those already-named worlds makes sense, apart from `main`. We could stop people from using them I suppose. Another option is to allow people to pass in `*` as a value to have scripts put into their own little worlds, probably backed by a counter in the GreaseMonkeyManager class. Chrome docs: https://developer.chrome.com/extensions/content_scripts#execution-environment Webengine docs: https://doc.qt.io/qt-5/qwebenginescript.html#details --- qutebrowser/browser/greasemonkey.py | 3 +++ qutebrowser/browser/webengine/webenginetab.py | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index db8246bab..cff45f3ac 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -58,6 +58,7 @@ class GreasemonkeyScript: self.run_at = None self.script_meta = None self.runs_on_sub_frames = True + self.jsworld = "main" for name, value in properties: if name == 'name': self.name = value @@ -77,6 +78,8 @@ class GreasemonkeyScript: self.runs_on_sub_frames = False elif name == 'require': self.requires.append(value) + elif name == 'qute-js-world': + self.jsworld = value HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n' PROPS_REGEX = r'// @(?P[^\s]+)\s*(?P.*)' diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 185021bfa..501735fa5 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -900,7 +900,18 @@ class _WebEngineScripts(QObject): # @run-at (and @include/@exclude/@match) is parsed by # QWebEngineScript. new_script = QWebEngineScript() - new_script.setWorldId(QWebEngineScript.MainWorld) + try: + world = int(script.jsworld) + except ValueError: + try: + world = _JS_WORLD_MAP[usertypes.JsWorld[ + script.jsworld.lower()]] + except KeyError: + log.greasemonkey.error( + "script {} has invalid value for '@qute-js-world'" + ": {}".format(script.name, script.jsworld)) + continue + new_script.setWorldId(world) new_script.setSourceCode(script.code()) new_script.setName("GM-{}".format(script.name)) new_script.setRunsOnSubFrames(script.runs_on_sub_frames)