refactor the mutationobserver method into futures and intervals
This commit is contained in:
parent
6313a74423
commit
e89292d651
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,3 +42,4 @@ TODO
|
|||||||
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
||||||
/misc/file_version_info.txt
|
/misc/file_version_info.txt
|
||||||
/doc/extapi/_build
|
/doc/extapi/_build
|
||||||
|
tags
|
||||||
|
@ -976,7 +976,7 @@ class _WebEngineScripts(QObject):
|
|||||||
js_code = javascript.wrap_global(
|
js_code = javascript.wrap_global(
|
||||||
'stylesheet',
|
'stylesheet',
|
||||||
utils.read_file('javascript/stylesheet.js'),
|
utils.read_file('javascript/stylesheet.js'),
|
||||||
javascript.assemble('stylesheet', 'set_css', css),
|
javascript.assemble('stylesheet', 'set_new_page_css', css),
|
||||||
)
|
)
|
||||||
self._inject_early_js('stylesheet', js_code, subframes=True)
|
self._inject_early_js('stylesheet', js_code, subframes=True)
|
||||||
|
|
||||||
|
@ -31,42 +31,98 @@ window._qutebrowser.stylesheet = (function() {
|
|||||||
const svg_ns = "http://www.w3.org/2000/svg";
|
const svg_ns = "http://www.w3.org/2000/svg";
|
||||||
|
|
||||||
let root_elem;
|
let root_elem;
|
||||||
|
let root_key;
|
||||||
let style_elem;
|
let style_elem;
|
||||||
let css_content = "";
|
let css_content = "";
|
||||||
|
|
||||||
let root_observer;
|
function is_stylesheet_applied(css) {
|
||||||
let initialized = false;
|
return style_elem && style_elem.textContent === css;
|
||||||
|
}
|
||||||
|
|
||||||
// Watch for rewrites of the root element and changes to its children,
|
function ensure_stylesheet_loaded() {
|
||||||
// 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 (!document.documentElement) {
|
if (!document.documentElement) {
|
||||||
root_observer.observe(document, {"childList": true});
|
throw new Error(
|
||||||
return;
|
"ensure_stylesheet_loaded called before DOM was available"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root_elem !== document.documentElement) {
|
if (style_elem) {
|
||||||
root_elem = document.documentElement;
|
style_elem.textContent = css_content;
|
||||||
root_observer.disconnect();
|
} else {
|
||||||
root_observer.observe(document, {"childList": true});
|
style_elem = create_style(css_content);
|
||||||
root_observer.observe(root_elem, {"childList": true});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style_elem !== root_elem.lastChild) {
|
if (style_elem !== root_elem.lastChild) {
|
||||||
root_elem.appendChild(style_elem);
|
root_elem.appendChild(style_elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensure_root_present() {
|
||||||
|
let waiting_interval;
|
||||||
|
function is_root_present() {
|
||||||
|
return document && document.documentElement;
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (is_root_present()) {
|
||||||
|
root_elem = document.documentElement;
|
||||||
|
resolve(document.documentElement);
|
||||||
|
} else {
|
||||||
|
waiting_interval = setInterval(() => {
|
||||||
|
if (!is_root_present()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearInterval(waiting_interval);
|
||||||
|
root_elem = document.documentElement;
|
||||||
|
resolve(document.documentElement);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function wait_for_new_root() {
|
||||||
|
let waiting_interval;
|
||||||
|
function is_new_root() {
|
||||||
|
return (
|
||||||
|
document.documentElement &&
|
||||||
|
document.documentElement.getAttribute("__qb_key") !==
|
||||||
|
root_key &&
|
||||||
|
check_style(document.documentElement)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function setup_new_root(new_root_elem) {
|
||||||
|
root_elem = new_root_elem;
|
||||||
|
root_key = new Date().getTime();
|
||||||
|
root_elem.setAttribute("__qb_key", root_key);
|
||||||
|
// style_elem would refer to a node in the old page's dom
|
||||||
|
style_elem = null;
|
||||||
|
return root_elem;
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (is_new_root()) {
|
||||||
|
resolve(setup_new_root(document.documentElement));
|
||||||
|
} else {
|
||||||
|
waiting_interval = setInterval(() => {
|
||||||
|
if (!is_new_root()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearInterval(waiting_interval);
|
||||||
|
resolve(setup_new_root(document.documentElement));
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function create_style() {
|
function create_style() {
|
||||||
let ns = xhtml_ns;
|
let ns = xhtml_ns;
|
||||||
if (document.documentElement &&
|
if (
|
||||||
document.documentElement.namespaceURI === svg_ns) {
|
document.documentElement &&
|
||||||
|
document.documentElement.namespaceURI === svg_ns
|
||||||
|
) {
|
||||||
ns = svg_ns;
|
ns = svg_ns;
|
||||||
}
|
}
|
||||||
style_elem = document.createElementNS(ns, "style");
|
style_elem = document.createElementNS(ns, "style");
|
||||||
style_elem.textContent = css_content;
|
style_elem.textContent = css_content;
|
||||||
root_observer = new MutationObserver(watch_root);
|
return style_elem;
|
||||||
watch_root();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should only inject the stylesheet if the document already has style
|
// We should only inject the stylesheet if the document already has style
|
||||||
@ -75,74 +131,44 @@ window._qutebrowser.stylesheet = (function() {
|
|||||||
// starting point for exploring the relevant code in Chromium, see
|
// 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
|
// https://github.com/qt/qtwebengine-chromium/blob/cfe8c60/chromium/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp#L1539-L1540
|
||||||
function check_style(node) {
|
function check_style(node) {
|
||||||
const stylesheet = node.nodeType === Node.PROCESSING_INSTRUCTION_NODE &&
|
const stylesheet =
|
||||||
node.target === "xml-stylesheet" &&
|
node.nodeType === Node.PROCESSING_INSTRUCTION_NODE &&
|
||||||
node.parentNode === document;
|
node.target === "xml-stylesheet" &&
|
||||||
const known_ns = node.nodeType === Node.ELEMENT_NODE &&
|
node.parentNode === document;
|
||||||
(node.namespaceURI === xhtml_ns ||
|
const known_ns =
|
||||||
node.namespaceURI === svg_ns);
|
node.nodeType === Node.ELEMENT_NODE &&
|
||||||
|
(node.namespaceURI === xhtml_ns || node.namespaceURI === svg_ns);
|
||||||
return stylesheet || known_ns;
|
return stylesheet || known_ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function set_css(css) {
|
||||||
initialized = true;
|
ensure_root_present().then(() => {
|
||||||
// Chromium will not rewrite a document inside a frame, so add the
|
if (is_stylesheet_applied(css)) {
|
||||||
// stylesheet even if the document is unstyled.
|
|
||||||
if (window !== window.top) {
|
|
||||||
create_style();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const iter = document.createNodeIterator(document,
|
|
||||||
NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_ELEMENT);
|
|
||||||
let node;
|
|
||||||
while ((node = iter.nextNode())) {
|
|
||||||
if (check_style(node)) {
|
|
||||||
create_style();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
css_content = css;
|
||||||
const style_observer = new MutationObserver((mutations) => {
|
|
||||||
for (const mutation of mutations) {
|
ensure_stylesheet_loaded();
|
||||||
const nodes = mutation.addedNodes;
|
// Propagate the new CSS to all child frames.
|
||||||
for (let i = 0; i < nodes.length; ++i) {
|
// FIXME:qtwebengine This does not work for cross-origin frames.
|
||||||
if (check_style(nodes[i])) {
|
for (let i = 0; i < window.frames.length; ++i) {
|
||||||
create_style();
|
const frame = window.frames[i];
|
||||||
style_observer.disconnect();
|
if (frame._qutebrowser && frame._qutebrowser.stylesheet) {
|
||||||
return;
|
frame._qutebrowser.stylesheet.set_css(css);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
style_observer.observe(document, {"childList": true, "subtree": true});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
funcs.set_css = function(css) {
|
function set_new_page_css(css) {
|
||||||
if (!initialized) {
|
wait_for_new_root().then(() => {
|
||||||
init();
|
set_css(css);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (css_content === css) {
|
// exports
|
||||||
return;
|
funcs.set_css = set_css;
|
||||||
}
|
funcs.set_new_page_css = set_new_page_css;
|
||||||
|
|
||||||
css_content = css;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
// Propagate the new CSS to all child frames.
|
|
||||||
// FIXME:qtwebengine This does not work for cross-origin frames.
|
|
||||||
for (let i = 0; i < window.frames.length; ++i) {
|
|
||||||
const frame = window.frames[i];
|
|
||||||
if (frame._qutebrowser && frame._qutebrowser.stylesheet) {
|
|
||||||
frame._qutebrowser.stylesheet.set_css(css);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return funcs;
|
return funcs;
|
||||||
})();
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user