Merge remote-tracking branch 'origin/pr/4133'
This commit is contained in:
commit
c225e724ac
@ -2109,7 +2109,10 @@ class CommandDispatcher:
|
|||||||
raise cmdexc.CommandError(str(e))
|
raise cmdexc.CommandError(str(e))
|
||||||
|
|
||||||
widget = self._current_widget()
|
widget = self._current_widget()
|
||||||
widget.run_js_async(js_code, callback=jseval_cb, world=world)
|
try:
|
||||||
|
widget.run_js_async(js_code, callback=jseval_cb, world=world)
|
||||||
|
except browsertab.WebTabError as e:
|
||||||
|
raise cmdexc.CommandError(str(e))
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def fake_key(self, keystring, global_=False):
|
def fake_key(self, keystring, global_=False):
|
||||||
|
@ -46,7 +46,8 @@ 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, # noqa: C901 pragma: no mccabe
|
||||||
|
filename=None):
|
||||||
self._code = code
|
self._code = code
|
||||||
self.includes = []
|
self.includes = []
|
||||||
self.matches = []
|
self.matches = []
|
||||||
@ -81,11 +82,19 @@ class GreasemonkeyScript:
|
|||||||
elif name == 'qute-js-world':
|
elif name == 'qute-js-world':
|
||||||
self.jsworld = value
|
self.jsworld = value
|
||||||
|
|
||||||
|
if not self.name:
|
||||||
|
if filename:
|
||||||
|
self.name = filename
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"@name key required or pass filename to init."
|
||||||
|
)
|
||||||
|
|
||||||
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||||
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, source):
|
def parse(cls, source, filename=None):
|
||||||
"""GreasemonkeyScript factory.
|
"""GreasemonkeyScript factory.
|
||||||
|
|
||||||
Takes a userscript source and returns a GreasemonkeyScript.
|
Takes a userscript source and returns a GreasemonkeyScript.
|
||||||
@ -97,7 +106,11 @@ class GreasemonkeyScript:
|
|||||||
_head, 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,
|
||||||
|
filename=filename
|
||||||
|
)
|
||||||
script.script_meta = props
|
script.script_meta = props
|
||||||
if not script.includes and not script.matches:
|
if not script.includes and not script.matches:
|
||||||
script.includes = ['*']
|
script.includes = ['*']
|
||||||
@ -121,7 +134,7 @@ class GreasemonkeyScript:
|
|||||||
scriptName=javascript.string_escape(
|
scriptName=javascript.string_escape(
|
||||||
"/".join([self.namespace or '', self.name])),
|
"/".join([self.namespace or '', self.name])),
|
||||||
scriptInfo=self._meta_json(),
|
scriptInfo=self._meta_json(),
|
||||||
scriptMeta=javascript.string_escape(self.script_meta),
|
scriptMeta=javascript.string_escape(self.script_meta or ''),
|
||||||
scriptSource=self._code,
|
scriptSource=self._code,
|
||||||
use_proxy=use_proxy)
|
use_proxy=use_proxy)
|
||||||
|
|
||||||
@ -235,7 +248,8 @@ class GreasemonkeyManager(QObject):
|
|||||||
continue
|
continue
|
||||||
script_path = os.path.join(scripts_dir, script_filename)
|
script_path = os.path.join(scripts_dir, script_filename)
|
||||||
with open(script_path, encoding='utf-8-sig') as script_file:
|
with open(script_path, encoding='utf-8-sig') as script_file:
|
||||||
script = GreasemonkeyScript.parse(script_file.read())
|
script = GreasemonkeyScript.parse(script_file.read(),
|
||||||
|
script_filename)
|
||||||
if not script.name:
|
if not script.name:
|
||||||
script.name = script_filename
|
script.name = script_filename
|
||||||
self.add_script(script, force)
|
self.add_script(script, force)
|
||||||
|
@ -950,6 +950,15 @@ class _WebEngineScripts(QObject):
|
|||||||
scripts = self._greasemonkey.all_scripts()
|
scripts = self._greasemonkey.all_scripts()
|
||||||
self._inject_greasemonkey_scripts(scripts)
|
self._inject_greasemonkey_scripts(scripts)
|
||||||
|
|
||||||
|
def _remove_all_greasemonkey_scripts(self):
|
||||||
|
page_scripts = self._widget.page().scripts()
|
||||||
|
for script in page_scripts.toList():
|
||||||
|
if script.name().startswith("GM-"):
|
||||||
|
log.greasemonkey.debug('Removing script: {}'
|
||||||
|
.format(script.name()))
|
||||||
|
removed = page_scripts.remove(script)
|
||||||
|
assert removed, script.name()
|
||||||
|
|
||||||
def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
|
def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
|
||||||
remove_first=True):
|
remove_first=True):
|
||||||
"""Register user JavaScript files with the current tab.
|
"""Register user JavaScript files with the current tab.
|
||||||
@ -973,12 +982,7 @@ class _WebEngineScripts(QObject):
|
|||||||
# have been added elsewhere, like the one for stylesheets.
|
# have been added elsewhere, like the one for stylesheets.
|
||||||
page_scripts = self._widget.page().scripts()
|
page_scripts = self._widget.page().scripts()
|
||||||
if remove_first:
|
if remove_first:
|
||||||
for script in page_scripts.toList():
|
self._remove_all_greasemonkey_scripts()
|
||||||
if script.name().startswith("GM-"):
|
|
||||||
log.greasemonkey.debug('Removing script: {}'
|
|
||||||
.format(script.name()))
|
|
||||||
removed = page_scripts.remove(script)
|
|
||||||
assert removed, script.name()
|
|
||||||
|
|
||||||
if not scripts:
|
if not scripts:
|
||||||
return
|
return
|
||||||
@ -987,6 +991,15 @@ class _WebEngineScripts(QObject):
|
|||||||
new_script = QWebEngineScript()
|
new_script = QWebEngineScript()
|
||||||
try:
|
try:
|
||||||
world = int(script.jsworld)
|
world = int(script.jsworld)
|
||||||
|
if not 0 <= world <= qtutils.MAX_WORLD_ID:
|
||||||
|
log.greasemonkey.error(
|
||||||
|
"script {} has invalid value for '@qute-js-world'"
|
||||||
|
": {}, should be between 0 and {}"
|
||||||
|
.format(
|
||||||
|
script.name,
|
||||||
|
script.jsworld,
|
||||||
|
qtutils.MAX_WORLD_ID))
|
||||||
|
continue
|
||||||
except ValueError:
|
except ValueError:
|
||||||
try:
|
try:
|
||||||
world = _JS_WORLD_MAP[usertypes.JsWorld[
|
world = _JS_WORLD_MAP[usertypes.JsWorld[
|
||||||
@ -1104,6 +1117,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
world_id = QWebEngineScript.ApplicationWorld
|
world_id = QWebEngineScript.ApplicationWorld
|
||||||
elif isinstance(world, int):
|
elif isinstance(world, int):
|
||||||
world_id = world
|
world_id = world
|
||||||
|
if not 0 <= world_id <= qtutils.MAX_WORLD_ID:
|
||||||
|
raise browsertab.WebTabError(
|
||||||
|
"World ID should be between 0 and {}"
|
||||||
|
.format(qtutils.MAX_WORLD_ID))
|
||||||
else:
|
else:
|
||||||
world_id = _JS_WORLD_MAP[world]
|
world_id = _JS_WORLD_MAP[world]
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ Module attributes:
|
|||||||
value.
|
value.
|
||||||
MINVALS: A dictionary of C/Qt types (as string) mapped to their minimum
|
MINVALS: A dictionary of C/Qt types (as string) mapped to their minimum
|
||||||
value.
|
value.
|
||||||
|
MAX_WORLD_ID: The highest world ID allowed in this version of QtWebEngine.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -98,6 +99,10 @@ def version_check(version, exact=False, compiled=True):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-69904
|
||||||
|
MAX_WORLD_ID = 256 if version_check('5.11.2') else 11
|
||||||
|
|
||||||
|
|
||||||
def is_new_qtwebkit():
|
def is_new_qtwebkit():
|
||||||
"""Check if the given version is a new QtWebKit."""
|
"""Check if the given version is a new QtWebKit."""
|
||||||
assert qWebKitVersion is not None
|
assert qWebKitVersion is not None
|
||||||
|
@ -118,6 +118,26 @@ Feature: Various utility commands.
|
|||||||
Then the javascript message "Hello from the page!" should be logged
|
Then the javascript message "Hello from the page!" should be logged
|
||||||
And "No output or error" should be logged
|
And "No output or error" should be logged
|
||||||
|
|
||||||
|
@qtwebkit_skip @qt>=5.11.2
|
||||||
|
Scenario: :jseval using too high of a world id in Qt versions bigger than 5.11.2
|
||||||
|
When I run :jseval --world=257 console.log("Hello from JS!");
|
||||||
|
Then the error "World ID should be between 0 and 256" should be shown
|
||||||
|
|
||||||
|
@qtwebkit_skip @qt<5.11.2
|
||||||
|
Scenario: :jseval using too high of a world id in Qt versions smaller than 5.11.2
|
||||||
|
When I run :jseval --world=12 console.log("Hello from JS!");
|
||||||
|
Then the error "World ID should be between 0 and 11" should be shown
|
||||||
|
|
||||||
|
@qtwebkit_skip @qt>=5.11.2
|
||||||
|
Scenario: :jseval using a negative world id in Qt versions bigger than 5.11.2
|
||||||
|
When I run :jseval --world=-1 console.log("Hello from JS!");
|
||||||
|
Then the error "World ID should be between 0 and 256" should be shown
|
||||||
|
|
||||||
|
@qtwebkit_skip @qt<5.11.2
|
||||||
|
Scenario: :jseval using a negative world id in Qt versions smaller than 5.11.2
|
||||||
|
When I run :jseval --world=-1 console.log("Hello from JS!");
|
||||||
|
Then the error "World ID should be between 0 and 11" should be shown
|
||||||
|
|
||||||
Scenario: :jseval --file using a file that exists as js-code
|
Scenario: :jseval --file using a file that exists as js-code
|
||||||
When I run :jseval --file (testdata)/misc/jseval_file.js
|
When I run :jseval --file (testdata)/misc/jseval_file.js
|
||||||
Then the javascript message "Hello from JS!" should be logged
|
Then the javascript message "Hello from JS!" should be logged
|
||||||
|
107
tests/unit/browser/webengine/test_webenginetab.py
Normal file
107
tests/unit/browser/webengine/test_webenginetab.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||||
|
#
|
||||||
|
# This file is part of qutebrowser.
|
||||||
|
#
|
||||||
|
# qutebrowser is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# qutebrowser is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""Test webenginetab."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
|
import pytest
|
||||||
|
QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets")
|
||||||
|
QWebEnginePage = QtWebEngineWidgets.QWebEnginePage
|
||||||
|
QWebEngineScriptCollection = QtWebEngineWidgets.QWebEngineScriptCollection
|
||||||
|
|
||||||
|
from qutebrowser.browser.webengine import webenginetab
|
||||||
|
from qutebrowser.browser import greasemonkey
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.usefixtures('greasemonkey_manager')
|
||||||
|
|
||||||
|
|
||||||
|
class TestWebengineScripts:
|
||||||
|
"""Test the _WebEngineScripts utility class."""
|
||||||
|
|
||||||
|
class FakeWidget(QObject):
|
||||||
|
"""Fake widget for _WebengineScripts to use."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.scripts = []
|
||||||
|
self.page = mock.create_autospec(QWebEnginePage)
|
||||||
|
self.scripts_mock = mock.create_autospec(
|
||||||
|
QWebEngineScriptCollection
|
||||||
|
)
|
||||||
|
self.scripts_mock.toList.return_value = self.scripts
|
||||||
|
self.page.return_value.scripts.return_value = self.scripts_mock
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mocked_scripts(self, fake_web_tab):
|
||||||
|
scripts = webenginetab._WebEngineScripts(fake_web_tab)
|
||||||
|
scripts._widget = self.FakeWidget()
|
||||||
|
return scripts
|
||||||
|
|
||||||
|
def test_greasemonkey_undefined_world(self, mocked_scripts, caplog):
|
||||||
|
"""Make sure scripts with non-existent worlds are rejected."""
|
||||||
|
scripts = [
|
||||||
|
greasemonkey.GreasemonkeyScript(
|
||||||
|
[('qute-js-world', 'Mars'), ('name', 'test')], None)
|
||||||
|
]
|
||||||
|
|
||||||
|
with caplog.at_level(logging.ERROR, 'greasemonkey'):
|
||||||
|
mocked_scripts._inject_greasemonkey_scripts(scripts)
|
||||||
|
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
msg = caplog.records[0].message
|
||||||
|
assert "has invalid value for '@qute-js-world': Mars" in msg
|
||||||
|
mocked_scripts._widget.scripts_mock.insert.assert_not_called()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("worldid", [-1, 257])
|
||||||
|
def test_greasemonkey_out_of_range_world(self, worldid,
|
||||||
|
mocked_scripts, caplog):
|
||||||
|
"""Make sure scripts with out-of-range worlds are rejected."""
|
||||||
|
scripts = [
|
||||||
|
greasemonkey.GreasemonkeyScript(
|
||||||
|
[('qute-js-world', worldid), ('name', 'test')], None)
|
||||||
|
]
|
||||||
|
|
||||||
|
with caplog.at_level(logging.ERROR, 'greasemonkey'):
|
||||||
|
mocked_scripts._inject_greasemonkey_scripts(scripts)
|
||||||
|
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
msg = caplog.records[0].message
|
||||||
|
assert "has invalid value for '@qute-js-world': " in msg
|
||||||
|
assert "should be between 0 and" in msg
|
||||||
|
mocked_scripts._widget.scripts_mock.insert.assert_not_called()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("worldid", [0, 10])
|
||||||
|
def test_greasemonkey_good_worlds_are_passed(self, worldid,
|
||||||
|
mocked_scripts, caplog):
|
||||||
|
"""Make sure scripts with valid worlds have it set."""
|
||||||
|
scripts = [
|
||||||
|
greasemonkey.GreasemonkeyScript(
|
||||||
|
[('name', 'foo'), ('qute-js-world', worldid)], None
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
with caplog.at_level(logging.ERROR, 'greasemonkey'):
|
||||||
|
mocked_scripts._inject_greasemonkey_scripts(scripts)
|
||||||
|
|
||||||
|
calls = mocked_scripts._widget.scripts_mock.insert.call_args_list
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0][0][0].worldId() == worldid
|
@ -113,6 +113,21 @@ def test_no_metadata(caplog):
|
|||||||
assert len(scripts.end) == 1
|
assert len(scripts.end) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_name():
|
||||||
|
"""Ensure that GreaseMonkeyScripts must have a name."""
|
||||||
|
msg = "@name key required or pass filename to init."
|
||||||
|
with pytest.raises(ValueError, match=msg):
|
||||||
|
greasemonkey.GreasemonkeyScript([("something", "else")], "")
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_name_with_fallback():
|
||||||
|
"""Ensure that script's name can fallback to the provided filename."""
|
||||||
|
script = greasemonkey.GreasemonkeyScript(
|
||||||
|
[("something", "else")], "", filename=r"C:\COM1")
|
||||||
|
assert script
|
||||||
|
assert script.name == r"C:\COM1"
|
||||||
|
|
||||||
|
|
||||||
def test_bad_scheme(caplog):
|
def test_bad_scheme(caplog):
|
||||||
"""qute:// isn't in the list of allowed schemes."""
|
"""qute:// isn't in the list of allowed schemes."""
|
||||||
_save_script("var nothing = true;\n", 'nothing.user.js')
|
_save_script("var nothing = true;\n", 'nothing.user.js')
|
||||||
|
Loading…
Reference in New Issue
Block a user