Merge remote-tracking branch 'origin/pr/3793'
This commit is contained in:
commit
7fdeeb25b7
@ -30,6 +30,7 @@ markers =
|
||||
qtbug60673: Tests which are broken if the conversion from orange selection to real selection is flaky
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need an unicode locale to work
|
||||
qtwebkit6021_skip: Tests which would fail on WebKit version 602.1
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
|
@ -31,9 +31,10 @@ import attr
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
|
||||
javascript, urlmatch)
|
||||
javascript, urlmatch, version, usertypes)
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
def _scripts_dir():
|
||||
@ -108,13 +109,18 @@ class GreasemonkeyScript:
|
||||
browser's debugger/inspector will not match up to the line
|
||||
numbers in the source script directly.
|
||||
"""
|
||||
# Don't use Proxy on this webkit version, the support isn't there.
|
||||
use_proxy = not (
|
||||
objects.backend == usertypes.Backend.QtWebKit and
|
||||
version.qWebKitVersion() == '602.1')
|
||||
template = jinja.js_environment.get_template('greasemonkey_wrapper.js')
|
||||
return template.render(
|
||||
scriptName=javascript.string_escape(
|
||||
"/".join([self.namespace or '', self.name])),
|
||||
scriptInfo=self._meta_json(),
|
||||
scriptMeta=javascript.string_escape(self.script_meta),
|
||||
scriptSource=self._code)
|
||||
scriptSource=self._code,
|
||||
use_proxy=use_proxy)
|
||||
|
||||
def _meta_json(self):
|
||||
return json.dumps({
|
||||
|
@ -145,9 +145,59 @@
|
||||
}
|
||||
};
|
||||
|
||||
const unsafeWindow = window;
|
||||
{% if use_proxy %}
|
||||
/*
|
||||
* Try to give userscripts an environment that they expect. Which
|
||||
* seems to be that the global window object should look the same as
|
||||
* the page's one and that if a script writes to an attribute of
|
||||
* window it should be able to access that variable in the global
|
||||
* scope.
|
||||
* Use a Proxy to stop scripts from actually changing the global
|
||||
* window (that's what unsafeWindow is for).
|
||||
* Use the "with" statement to make the proxy provide what looks
|
||||
* like global scope.
|
||||
*
|
||||
* There are other Proxy functions that we may need to override.
|
||||
* set, get and has are definitely required.
|
||||
*/
|
||||
const unsafeWindow = window;
|
||||
const qute_gm_window_shadow = {}; // stores local changes to window
|
||||
const qute_gm_windowProxyHandler = {
|
||||
get: function(target, prop) {
|
||||
if (prop in qute_gm_window_shadow)
|
||||
return qute_gm_window_shadow[prop];
|
||||
if (prop in target) {
|
||||
if (typeof target[prop] === 'function' && typeof target[prop].prototype == 'undefined')
|
||||
// Getting TypeError: Illegal Execution when callers try to execute
|
||||
// eg addEventListener from here because they were returned
|
||||
// unbound
|
||||
return target[prop].bind(target);
|
||||
return target[prop];
|
||||
}
|
||||
},
|
||||
set: function(target, prop, val) {
|
||||
return qute_gm_window_shadow[prop] = val;
|
||||
},
|
||||
has: function(target, key) {
|
||||
return key in qute_gm_window_shadow || key in target;
|
||||
}
|
||||
};
|
||||
const qute_gm_window_proxy = new Proxy(
|
||||
unsafeWindow, qute_gm_windowProxyHandler);
|
||||
|
||||
// ====== The actual user script source ====== //
|
||||
with (qute_gm_window_proxy) {
|
||||
// We can't return `this` or `qute_gm_window_proxy` from
|
||||
// `qute_gm_window_proxy.get('window')` because the Proxy implementation
|
||||
// does typechecking on read-only things. So we have to shadow `window`
|
||||
// more conventionally here.
|
||||
const window = qute_gm_window_proxy;
|
||||
// ====== The actual user script source ====== //
|
||||
{{ scriptSource }}
|
||||
// ====== End User Script ====== //
|
||||
// ====== End User Script ====== //
|
||||
};
|
||||
{% else %}
|
||||
// ====== The actual user script source ====== //
|
||||
{{ scriptSource }}
|
||||
// ====== End User Script ====== //
|
||||
{% endif %}
|
||||
})();
|
||||
|
@ -36,7 +36,7 @@ from helpers import logfail
|
||||
from helpers.logfail import fail_on_logging
|
||||
from helpers.messagemock import message_mock
|
||||
from helpers.fixtures import * # noqa: F403
|
||||
from qutebrowser.utils import qtutils, standarddir, usertypes, utils
|
||||
from qutebrowser.utils import qtutils, standarddir, usertypes, utils, version
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
import qutebrowser.app # To register commands
|
||||
@ -77,6 +77,10 @@ def _apply_platform_markers(config, item):
|
||||
"https://bugreports.qt.io/browse/QTBUG-60673"),
|
||||
('unicode_locale', sys.getfilesystemencoding() == 'ascii',
|
||||
"Skipped because of ASCII locale"),
|
||||
('qtwebkit6021_skip',
|
||||
version.qWebKitVersion and
|
||||
version.qWebKitVersion() == '602.1',
|
||||
"Broken on WebKit 602.1")
|
||||
]
|
||||
|
||||
for searched_marker, condition, default_reason in markers:
|
||||
@ -219,8 +223,10 @@ def check_display(request):
|
||||
@pytest.fixture(autouse=True)
|
||||
def set_backend(monkeypatch, request):
|
||||
"""Make sure the backend global is set."""
|
||||
backend = (usertypes.Backend.QtWebEngine if request.config.webengine
|
||||
else usertypes.Backend.QtWebKit)
|
||||
if not request.config.webengine and version.qWebKitVersion:
|
||||
backend = usertypes.Backend.QtWebKit
|
||||
else:
|
||||
backend = usertypes.Backend.QtWebEngine
|
||||
monkeypatch.setattr(objects, 'backend', backend)
|
||||
|
||||
|
||||
|
@ -43,10 +43,10 @@ import helpers.stubs as stubsmod
|
||||
import helpers.utils
|
||||
from qutebrowser.config import (config, configdata, configtypes, configexc,
|
||||
configfiles)
|
||||
from qutebrowser.utils import objreg, standarddir, utils
|
||||
from qutebrowser.utils import objreg, standarddir, utils, usertypes
|
||||
from qutebrowser.browser import greasemonkey
|
||||
from qutebrowser.browser.webkit import cookies
|
||||
from qutebrowser.misc import savemanager, sql
|
||||
from qutebrowser.misc import savemanager, sql, objects
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
@ -360,9 +360,10 @@ def qnam(qapp):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webengineview(qtbot):
|
||||
def webengineview(qtbot, monkeypatch):
|
||||
"""Get a QWebEngineView if QtWebEngine is available."""
|
||||
QtWebEngineWidgets = pytest.importorskip('PyQt5.QtWebEngineWidgets')
|
||||
monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine)
|
||||
view = QtWebEngineWidgets.QWebEngineView()
|
||||
qtbot.add_widget(view)
|
||||
return view
|
||||
@ -379,9 +380,10 @@ def webpage(qnam):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webview(qtbot, webpage):
|
||||
def webview(qtbot, webpage, monkeypatch):
|
||||
"""Get a new QWebView object."""
|
||||
QtWebKitWidgets = pytest.importorskip('PyQt5.QtWebKitWidgets')
|
||||
monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebKit)
|
||||
|
||||
view = QtWebKitWidgets.QWebView()
|
||||
qtbot.add_widget(view)
|
||||
|
@ -158,3 +158,70 @@ def test_required_scripts_are_included(download_stub, tmpdir):
|
||||
# Additionally check that the base script is still being parsed correctly
|
||||
assert "Script is running." in scripts[0].code()
|
||||
assert scripts[0].excludes
|
||||
|
||||
|
||||
class TestWindowIsolation:
|
||||
"""Check that greasemonkey scripts get a shadowed global scope."""
|
||||
|
||||
@pytest.fixture
|
||||
def setup(self):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
class SetupData:
|
||||
pass
|
||||
ret = SetupData()
|
||||
|
||||
# Change something in the global scope
|
||||
ret.setup_script = "window.$ = 'global'"
|
||||
|
||||
# Greasemonkey script to report back on its scope.
|
||||
test_script = greasemonkey.GreasemonkeyScript.parse(
|
||||
textwrap.dedent("""
|
||||
// ==UserScript==
|
||||
// @name scopetest
|
||||
// ==/UserScript==
|
||||
// Check the thing the page set is set to the expected type
|
||||
result.push(window.$);
|
||||
result.push($);
|
||||
// Now overwrite it
|
||||
window.$ = 'shadowed';
|
||||
// And check everything is how the script would expect it to be
|
||||
// after just writing to the "global" scope
|
||||
result.push(window.$);
|
||||
result.push($);
|
||||
""")
|
||||
)
|
||||
|
||||
# The compiled source of that scripts with some additional setup
|
||||
# bookending it.
|
||||
ret.test_script = "\n".join([
|
||||
"var result = [];",
|
||||
test_script.code(),
|
||||
"""
|
||||
// Now check that the actual global scope has
|
||||
// not been overwritten
|
||||
result.push(window.$);
|
||||
result.push($);
|
||||
// And return our findings
|
||||
result;"""
|
||||
])
|
||||
|
||||
# What we expect the script to report back.
|
||||
ret.expected = [
|
||||
"global", "global",
|
||||
"shadowed", "shadowed",
|
||||
"global", "global"]
|
||||
return ret
|
||||
|
||||
def test_webengine(self, callback_checker, webengineview, setup):
|
||||
page = webengineview.page()
|
||||
page.runJavaScript(setup.setup_script)
|
||||
page.runJavaScript(setup.test_script, callback_checker.callback)
|
||||
callback_checker.check(setup.expected)
|
||||
|
||||
# The JSCore in 602.1 doesn't fully support Proxy.
|
||||
@pytest.mark.qtwebkit6021_skip
|
||||
def test_webkit(self, webview, setup):
|
||||
elem = webview.page().mainFrame().documentElement()
|
||||
elem.evaluateJavaScript(setup.setup_script)
|
||||
result = elem.evaluateJavaScript(setup.test_script)
|
||||
assert result == setup.expected
|
||||
|
@ -26,7 +26,7 @@ import pytest
|
||||
@pytest.mark.parametrize('js_enabled, expected', [(True, 2.0), (False, None)])
|
||||
def test_simple_js_webkit(webview, js_enabled, expected):
|
||||
"""With QtWebKit, evaluateJavaScript works when JS is on."""
|
||||
# If we get there (because of the webengineview fixture) we can be certain
|
||||
# If we get there (because of the webview fixture) we can be certain
|
||||
# QtWebKit is available
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
webview.settings().setAttribute(QWebSettings.JavascriptEnabled, js_enabled)
|
||||
@ -37,7 +37,7 @@ def test_simple_js_webkit(webview, js_enabled, expected):
|
||||
@pytest.mark.parametrize('js_enabled, expected', [(True, 2.0), (False, 2.0)])
|
||||
def test_element_js_webkit(webview, js_enabled, expected):
|
||||
"""With QtWebKit, evaluateJavaScript on an element works with JS off."""
|
||||
# If we get there (because of the webengineview fixture) we can be certain
|
||||
# If we get there (because of the webview fixture) we can be certain
|
||||
# QtWebKit is available
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
webview.settings().setAttribute(QWebSettings.JavascriptEnabled, js_enabled)
|
||||
|
Loading…
Reference in New Issue
Block a user