Rewrite user stylesheet injection for WebEngine
This now works correctly in XML documents. The stylesheet is applied at document creation to reduce flickering, and is updated if the user_stylesheets setting is changed after page load.
This commit is contained in:
parent
0d1e716760
commit
51d48f6b00
@ -37,7 +37,7 @@ from qutebrowser.browser import shared
|
|||||||
from qutebrowser.browser.webengine import spell
|
from qutebrowser.browser.webengine import spell
|
||||||
from qutebrowser.config import config, websettings
|
from qutebrowser.config import config, websettings
|
||||||
from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
|
from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
|
||||||
message, log)
|
message, log, objreg)
|
||||||
|
|
||||||
# The default QWebEngineProfile
|
# The default QWebEngineProfile
|
||||||
default_profile = None
|
default_profile = None
|
||||||
@ -153,33 +153,41 @@ class DictionaryLanguageSetter(DefaultProfileSetter):
|
|||||||
def _init_stylesheet(profile):
|
def _init_stylesheet(profile):
|
||||||
"""Initialize custom stylesheets.
|
"""Initialize custom stylesheets.
|
||||||
|
|
||||||
Mostly inspired by QupZilla:
|
Partially inspired by QupZilla:
|
||||||
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101
|
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101
|
||||||
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/tools/scripts.cpp#L119-L132
|
|
||||||
"""
|
"""
|
||||||
old_script = profile.scripts().findScript('_qute_stylesheet')
|
old_script = profile.scripts().findScript('_qute_stylesheet')
|
||||||
if not old_script.isNull():
|
if not old_script.isNull():
|
||||||
profile.scripts().remove(old_script)
|
profile.scripts().remove(old_script)
|
||||||
|
|
||||||
css = shared.get_user_stylesheet()
|
css = shared.get_user_stylesheet()
|
||||||
source = """
|
source = '\n'.join([
|
||||||
(function() {{
|
'"use strict";',
|
||||||
var css = document.createElement('style');
|
'window._qutebrowser = window._qutebrowser || {};',
|
||||||
css.setAttribute('type', 'text/css');
|
utils.read_file('javascript/stylesheet.js'),
|
||||||
css.appendChild(document.createTextNode('{}'));
|
javascript.assemble('stylesheet', 'set_css', css),
|
||||||
document.getElementsByTagName('head')[0].appendChild(css);
|
])
|
||||||
}})()
|
|
||||||
""".format(javascript.string_escape(css))
|
|
||||||
|
|
||||||
script = QWebEngineScript()
|
script = QWebEngineScript()
|
||||||
script.setName('_qute_stylesheet')
|
script.setName('_qute_stylesheet')
|
||||||
script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
script.setInjectionPoint(QWebEngineScript.DocumentCreation)
|
||||||
script.setWorldId(QWebEngineScript.ApplicationWorld)
|
script.setWorldId(QWebEngineScript.ApplicationWorld)
|
||||||
script.setRunsOnSubFrames(True)
|
script.setRunsOnSubFrames(True)
|
||||||
script.setSourceCode(source)
|
script.setSourceCode(source)
|
||||||
profile.scripts().insert(script)
|
profile.scripts().insert(script)
|
||||||
|
|
||||||
|
|
||||||
|
def _update_stylesheet():
|
||||||
|
"""Update the custom stylesheet in existing tabs."""
|
||||||
|
css = shared.get_user_stylesheet()
|
||||||
|
code = javascript.assemble('stylesheet', 'set_css', css)
|
||||||
|
for win_id in objreg.window_registry:
|
||||||
|
tab_registry = objreg.get('tab-registry', scope='window',
|
||||||
|
window=win_id)
|
||||||
|
for tab in tab_registry.values():
|
||||||
|
tab.run_js_async(code)
|
||||||
|
|
||||||
|
|
||||||
def _set_http_headers(profile):
|
def _set_http_headers(profile):
|
||||||
"""Set the user agent and accept-language for the given profile.
|
"""Set the user agent and accept-language for the given profile.
|
||||||
|
|
||||||
@ -199,6 +207,7 @@ def _update_settings(option):
|
|||||||
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
if option in ['scrolling.bar', 'content.user_stylesheets']:
|
||||||
_init_stylesheet(default_profile)
|
_init_stylesheet(default_profile)
|
||||||
_init_stylesheet(private_profile)
|
_init_stylesheet(private_profile)
|
||||||
|
_update_stylesheet()
|
||||||
elif option in ['content.headers.user_agent',
|
elif option in ['content.headers.user_agent',
|
||||||
'content.headers.accept_language']:
|
'content.headers.accept_language']:
|
||||||
_set_http_headers(default_profile)
|
_set_http_headers(default_profile)
|
||||||
|
@ -533,7 +533,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
def _init_js(self):
|
def _init_js(self):
|
||||||
js_code = '\n'.join([
|
js_code = '\n'.join([
|
||||||
'"use strict";',
|
'"use strict";',
|
||||||
'window._qutebrowser = {};',
|
'window._qutebrowser = window._qutebrowser || {};',
|
||||||
utils.read_file('javascript/scroll.js'),
|
utils.read_file('javascript/scroll.js'),
|
||||||
utils.read_file('javascript/webelem.js'),
|
utils.read_file('javascript/webelem.js'),
|
||||||
])
|
])
|
||||||
|
142
qutebrowser/javascript/stylesheet.js
Normal file
142
qutebrowser/javascript/stylesheet.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2017 Ulrik de Muelenaere <ulrikdem@gmail.com>
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
window._qutebrowser.stylesheet = (function() {
|
||||||
|
if (window._qutebrowser.stylesheet) {
|
||||||
|
return window._qutebrowser.stylesheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcs = {};
|
||||||
|
|
||||||
|
var xhtml_ns = "http://www.w3.org/1999/xhtml";
|
||||||
|
var svg_ns = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
|
var root_elem;
|
||||||
|
var style_elem;
|
||||||
|
var css_content = "";
|
||||||
|
|
||||||
|
var root_observer;
|
||||||
|
var style_observer;
|
||||||
|
var initialized = false;
|
||||||
|
|
||||||
|
// Watch for rewrites of the root element and changes to its children,
|
||||||
|
// then move the stylesheet to the end. Partially inspired by Stylus:
|
||||||
|
// https://github.com/openstyles/stylus/blob/1.1.4.2/content/apply.js#L235-L355
|
||||||
|
function watch_root() {
|
||||||
|
if (root_elem !== document.documentElement) {
|
||||||
|
root_elem = document.documentElement;
|
||||||
|
root_observer.disconnect();
|
||||||
|
root_observer.observe(document, {"childList": true});
|
||||||
|
root_observer.observe(root_elem, {"childList": true});
|
||||||
|
}
|
||||||
|
if (style_elem !== root_elem.lastChild) {
|
||||||
|
root_elem.appendChild(style_elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_style() {
|
||||||
|
var ns = xhtml_ns;
|
||||||
|
if (document.documentElement.namespaceURI === svg_ns) {
|
||||||
|
ns = svg_ns;
|
||||||
|
}
|
||||||
|
style_elem = document.createElementNS(ns, "style");
|
||||||
|
style_elem.textContent = css_content;
|
||||||
|
root_observer = new MutationObserver(watch_root);
|
||||||
|
watch_root();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should only inject the stylesheet if the document already has style
|
||||||
|
// information associated with it. Otherwise we wait until the browser
|
||||||
|
// rewrites it to an XHTML document showing the document tree. As a
|
||||||
|
// starting point for exploring the relevant code in Chromium, see
|
||||||
|
// https://github.com/qt/qtwebengine-chromium/blob/cfe8c60/chromium/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp#L1539-L1540
|
||||||
|
function check_style(node) {
|
||||||
|
var stylesheet = node.nodeType === Node.PROCESSING_INSTRUCTION_NODE &&
|
||||||
|
node.target === "xml-stylesheet" &&
|
||||||
|
node.parentNode === document;
|
||||||
|
var known_ns = node.nodeType === Node.ELEMENT_NODE &&
|
||||||
|
(node.namespaceURI === xhtml_ns ||
|
||||||
|
node.namespaceURI === svg_ns);
|
||||||
|
if (stylesheet || known_ns) {
|
||||||
|
create_style();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_added_style(mutations) {
|
||||||
|
for (var mi = 0; mi < mutations.length; ++mi) {
|
||||||
|
var nodes = mutations[mi].addedNodes;
|
||||||
|
for (var ni = 0; ni < nodes.length; ++ni) {
|
||||||
|
if (check_style(nodes[ni])) {
|
||||||
|
style_observer.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
initialized = true;
|
||||||
|
// Chromium will not rewrite a document inside a frame, so add the
|
||||||
|
// stylesheet even if the document is unstyled.
|
||||||
|
if (window !== window.top) {
|
||||||
|
create_style();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var iter = document.createNodeIterator(document);
|
||||||
|
var node;
|
||||||
|
while ((node = iter.nextNode())) {
|
||||||
|
if (check_style(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
style_observer = new MutationObserver(check_added_style);
|
||||||
|
style_observer.observe(document, {"childList": true, "subtree": true});
|
||||||
|
}
|
||||||
|
|
||||||
|
var doc = document;
|
||||||
|
|
||||||
|
funcs.set_css = function(css) {
|
||||||
|
if (!initialized) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
if (style_elem) {
|
||||||
|
style_elem.textContent = css;
|
||||||
|
// The browser seems to rewrite the document in same-origin frames
|
||||||
|
// without notifying the mutation observer. Ensure that the
|
||||||
|
// stylesheet is in the current document.
|
||||||
|
watch_root();
|
||||||
|
} else {
|
||||||
|
css_content = css;
|
||||||
|
}
|
||||||
|
// Propagate the new CSS to all child frames.
|
||||||
|
// FIXME:qtwebengine This does not work for cross-origin frames.
|
||||||
|
for (var i = 0; i < window.frames.length; ++i) {
|
||||||
|
var frame = window.frames[i];
|
||||||
|
if (frame._qutebrowser && frame._qutebrowser.stylesheet) {
|
||||||
|
frame._qutebrowser.stylesheet.set_css(css);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return funcs;
|
||||||
|
})();
|
@ -16,11 +16,11 @@ Content-Location: http://localhost:(port)/data/downloads/mhtml/simple/simple.htm
|
|||||||
t/html; charset=3DUTF-8">
|
t/html; charset=3DUTF-8">
|
||||||
=20
|
=20
|
||||||
<title>Simple MHTML test</title>
|
<title>Simple MHTML test</title>
|
||||||
<style type=3D"text/css">
|
</head>
|
||||||
html > ::-webkit-scrollbar { width: 0px; height: 0px; }</style></head>
|
|
||||||
<body>
|
<body>
|
||||||
<a href=3D"http://localhost:(port)/">normal link to another page</a>
|
<a href=3D"http://localhost:(port)/">normal link to another page</a>
|
||||||
=20
|
=20
|
||||||
|
|
||||||
</body></html>
|
</body><style>
|
||||||
|
html > ::-webkit-scrollbar { width: 0px; height: 0px; }</style></html>
|
||||||
-----=_qute-UUID
|
-----=_qute-UUID
|
||||||
|
Loading…
Reference in New Issue
Block a user