Merge remote-tracking branch 'origin/pr/4133'

This commit is contained in:
Florian Bruhin 2018-10-05 22:56:53 +02:00
commit c225e724ac
7 changed files with 193 additions and 12 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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

View 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

View File

@ -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')