Merge branch 'greasemonkey-quirks'

This commit is contained in:
Florian Bruhin 2019-02-25 09:56:18 +01:00
commit 94542c5f78
4 changed files with 118 additions and 1 deletions

View File

@ -31,7 +31,8 @@ import attr
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
javascript, urlmatch, version, usertypes)
javascript, urlmatch, version, usertypes,
qtutils)
from qutebrowser.api import cmdutils
from qutebrowser.browser import downloads
from qutebrowser.misc import objects
@ -116,6 +117,40 @@ class GreasemonkeyScript:
script.includes = ['*']
return script
def force_document_end(self):
"""Check whether to force @run-at document-end.
This needs to be done on QtWebEngine with Qt 5.12 for known-broken
scripts.
On Qt 5.12, accessing the DOM isn't possible with "@run-at
document-start". It was documented to be impossible before, but seems
to work fine.
However, some scripts do DOM access with "@run-at document-start". Fix
those by forcing them to use document-end instead.
"""
if objects.backend != usertypes.Backend.QtWebEngine:
return False
elif not qtutils.version_check('5.12', compiled=False):
return False
broken_scripts = [
('http://userstyles.org', None),
('https://github.com/ParticleCore', 'Iridium'),
]
return any(self._matches_id(namespace=namespace, name=name)
for namespace, name in broken_scripts)
def _matches_id(self, *, namespace, name):
"""Check if this script matches the given namespace/name.
Both namespace and name can be None in order to match any script.
"""
matches_namespace = namespace is None or self.namespace == namespace
matches_name = name is None or self.name == name
return matches_namespace and matches_name
def code(self):
"""Return the processed JavaScript code of this script.

View File

@ -1038,9 +1038,15 @@ class _WebEngineScripts(QObject):
new_script.setSourceCode(script.code())
new_script.setName("GM-{}".format(script.name))
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
# Override the @run-at value parsed by QWebEngineScript if desired.
if injection_point:
new_script.setInjectionPoint(injection_point)
elif script.force_document_end():
log.greasemonkey.debug("Forcing @run-at document-end for {}"
.format(script.name))
new_script.setInjectionPoint(QWebEngineScript.DocumentReady)
log.greasemonkey.debug('adding script: {}'
.format(new_script.name()))
page_scripts.insert(new_script)

View File

@ -25,8 +25,10 @@ import pytest
QtWebEngineWidgets = pytest.importorskip("PyQt5.QtWebEngineWidgets")
QWebEnginePage = QtWebEngineWidgets.QWebEnginePage
QWebEngineScriptCollection = QtWebEngineWidgets.QWebEngineScriptCollection
QWebEngineScript = QtWebEngineWidgets.QWebEngineScript
from qutebrowser.browser import greasemonkey
from qutebrowser.utils import usertypes
pytestmark = pytest.mark.usefixtures('greasemonkey_manager')
@ -91,3 +93,26 @@ class TestWebengineScripts:
collection = webengine_scripts._widget.page().scripts()
assert collection.toList()[-1].worldId() == worldid
def test_greasemonkey_force_document_end(self, monkeypatch,
webengine_scripts):
"""Make sure document-end is forced when needed."""
monkeypatch.setattr(greasemonkey.objects, 'backend',
usertypes.Backend.QtWebEngine)
monkeypatch.setattr(greasemonkey.qtutils, 'version_check',
lambda version, exact=False, compiled=True:
True)
scripts = [
greasemonkey.GreasemonkeyScript([
('name', 'Iridium'),
('namespace', 'https://github.com/ParticleCore'),
('run-at', 'document-start'),
], None)
]
webengine_scripts._inject_greasemonkey_scripts(scripts)
collection = webengine_scripts._widget.page().scripts()
script = collection.toList()[-1]
assert script.injectionPoint() == QWebEngineScript.DocumentReady

View File

@ -25,6 +25,7 @@ import pytest
import py.path # pylint: disable=no-name-in-module
from PyQt5.QtCore import QUrl
from qutebrowser.utils import usertypes
from qutebrowser.browser import greasemonkey
test_gm_script = r"""
@ -165,6 +166,56 @@ def test_utf8_bom():
assert '// ==UserScript==' in script.code().splitlines()
class TestForceDocumentEnd:
@pytest.fixture
def patch(self, monkeypatch):
def _patch(*, backend, qt_512):
monkeypatch.setattr(greasemonkey.objects, 'backend', backend)
monkeypatch.setattr(greasemonkey.qtutils, 'version_check',
lambda version, exact=False, compiled=True:
qt_512)
return _patch
def _get_script(self, *, namespace, name):
source = textwrap.dedent("""
// ==UserScript==
// @namespace {}
// @name {}
// ==/UserScript==
""".format(namespace, name))
_save_script(source, 'force.user.js')
gm_manager = greasemonkey.GreasemonkeyManager()
scripts = gm_manager.all_scripts()
assert len(scripts) == 1
return scripts[0]
@pytest.mark.parametrize('backend, qt_512', [
(usertypes.Backend.QtWebKit, True),
(usertypes.Backend.QtWebEngine, False),
])
def test_not_applicable(self, patch, backend, qt_512):
"""Test backend/Qt version combinations which don't need a fix."""
patch(backend=backend, qt_512=qt_512)
script = self._get_script(namespace='https://github.com/ParticleCore',
name='Iridium')
assert not script.force_document_end()
@pytest.mark.parametrize('namespace, name, force', [
('http://userstyles.org', 'foobar', True),
('https://github.com/ParticleCore', 'Iridium', True),
('https://github.com/ParticleCore', 'Foo', False),
('https://example.org', 'Iridium', False),
])
def test_matching(self, patch, namespace, name, force):
"""Test matching based on namespace/name."""
patch(backend=usertypes.Backend.QtWebEngine, qt_512=True)
script = self._get_script(namespace=namespace, name=name)
assert script.force_document_end() == force
def test_required_scripts_are_included(download_stub, tmpdir):
test_require_script = textwrap.dedent("""
// ==UserScript==