/** * Copyright 2017-2021 Florian Bruhin (The Compiler) * Copyright 2017 Ulrik de Muelenaere * * 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 . */ "use strict"; window._qutebrowser.stylesheet = (function() { if (window._qutebrowser.stylesheet) { return window._qutebrowser.stylesheet; } const funcs = {}; const xhtml_ns = "http://www.w3.org/1999/xhtml"; const svg_ns = "http://www.w3.org/2000/svg"; let root_elem; let style_elem; let css_content = ""; let root_observer; let 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 (!document.documentElement) { root_observer.observe(document, {"childList": true}); return; } 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() { let ns = xhtml_ns; if (document.documentElement && 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) { const stylesheet = node.nodeType === Node.PROCESSING_INSTRUCTION_NODE && node.target === "xml-stylesheet" && node.parentNode === document; const known_ns = node.nodeType === Node.ELEMENT_NODE && (node.namespaceURI === xhtml_ns || node.namespaceURI === svg_ns); return stylesheet || known_ns; } 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; } 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; } } const style_observer = new MutationObserver((mutations) => { for (const mutation of mutations) { const nodes = mutation.addedNodes; for (let i = 0; i < nodes.length; ++i) { if (check_style(nodes[i])) { create_style(); style_observer.disconnect(); return; } } } }); style_observer.observe(document, {"childList": true, "subtree": true}); } 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 (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; })();