Repository: qutebrowser/qutebrowser

Issue:
userscript:_qute_stylesheet: Uncaught TypeError: Cannot read property 'appendChild' of undefined
I am seeing the following error when accessing https://travis-ci.com/private_org/private_repo/pull_requests:

> 14:07:36 ERROR: [userscript:_qute_stylesheet:6] Uncaught TypeError: Cannot read property 'appendChild' of undefined

Before there was a warning already:

> 14:07:32 WARNING: [https://cdn.travis-ci.com/assets/vendor-284b882d99e4c1b0d34e3a66b48188d1.js:21] Error

```
% qutebrowser --version
qutebrowser v0.10.1
Git commit: 
Backend: QtWebEngine

CPython: 3.6.1
Qt: 5.9.0
PyQt: 5.8.2

sip: 4.19.2
colorama: 0.3.9
pypeg2: 2.15
jinja2: 2.9.6
pygments: 2.2.0
yaml: 3.12
cssutils: no
typing: yes
PyQt5.QtWebEngineWidgets: yes
pdf.js: 1.6.210 (/usr/share/pdf.js/build/pdf.js)
Webkit: 538.1
SSL: OpenSSL 1.0.2l  25 May 2017

Style: QFusionStyle
Platform: Linux-4.11.5-1-ARCH-x86_64-with-arch, 64bit
Frozen: False
Imported from /usr/lib/python3.6/site-packages/qutebrowser
Qt library executable path: /usr/lib/qt/libexec, data path: /usr/share/qt
OS Version: 

--- /etc/lsb-release ---
LSB_VERSION=1.4
DISTRIB_ID=Arch
DISTRIB_RELEASE=rolling
DISTRIB_DESCRIPTION="Arch Linux"

--- /etc/os-release ---
NAME="Arch Linux"
PRETTY_NAME="Arch Linux"
ID=arch
ID_LIKE=archlinux

Paths:
config: /home/daniel/.local/share/qutebrowser-work/config
data: /home/daniel/.local/share/qutebrowser-work/data
system_data: /usr/share/qutebrowser
cache: /home/daniel/.local/share/qutebrowser-work/cache
download: /home/daniel/.local/share/qutebrowser-work/download
runtime: /home/daniel/.local/share/qutebrowser-work/runtime
```

I assume it comes from https://github.com/qutebrowser/qutebrowser/blob/2ffb10badf723522d82256154716c8cd344dec4a/qutebrowser/browser/webengine/webenginesettings.py#L145-L152 somehow?!

(the `cache` dir does not exist currently (it is a symlink pointing to a non-existing dir), in case that matters)
Make user-stylesheet work better with QtWebEngine
After [QTBUG-51192](https://bugreports.qt.io/browse/QTBUG-51192) was abandoned, I guess it's time to see if we can fix user stylesheets in some different way.

Looking at [Stylus](https://github.com/openstyles/stylus) (Stylish-fork which is open source), it seems to do some pretty [complex stuff](https://github.com/openstyles/stylus/blob/master/apply.js) to avoid flickering, but it doesn't actually need much of the Chromium WebExtension API, so it might be possible to do the same in qutebrowser.


Discussion:
I can reproduce it - looks like `document.getElementsByTagName('head')[0]` returns `undefined`. The HTML (both the thing coming from the server and the replaced one from Travis' JS) does have a `<head>` tag though...
Cool.
Does it only happen with travis-ci.com? (Have not seen it on .org.)
I just noticed the [Acid3 test](http://acid3.acidtests.org/) also fails on 99/100 and shows the same errors. When disabling the user stylesheet script:

```diff
diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py
index 76c2aa4ec..a75568587 100644
--- a/qutebrowser/browser/webengine/webenginesettings.py
+++ b/qutebrowser/browser/webengine/webenginesettings.py
@@ -157,7 +157,7 @@ def _init_stylesheet(profile):
     script.setWorldId(QWebEngineScript.ApplicationWorld)
     script.setRunsOnSubFrames(True)
     script.setSourceCode(source)
-    profile.scripts().insert(script)
+    # profile.scripts().insert(script)
 
 
 def _set_user_agent(profile):
```

it gets to 100/100.
Flickering becomes more annoying on slow connections.

Resolving patch:
diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py
--- a/qutebrowser/browser/webengine/webenginesettings.py
+++ b/qutebrowser/browser/webengine/webenginesettings.py
@@ -29,6 +29,7 @@
 
 import os
 
+import sip
 from PyQt5.QtGui import QFont
 from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
                                       QWebEngineScript)
@@ -37,7 +38,7 @@
 from qutebrowser.browser.webengine import spell
 from qutebrowser.config import config, websettings
 from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
-                               message, log)
+                               message, log, objreg)
 
 # The default QWebEngineProfile
 default_profile = None
@@ -153,33 +154,44 @@ def _set(self, value, settings=None):
 def _init_stylesheet(profile):
     """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/tools/scripts.cpp#L119-L132
     """
     old_script = profile.scripts().findScript('_qute_stylesheet')
     if not old_script.isNull():
         profile.scripts().remove(old_script)
 
     css = shared.get_user_stylesheet()
-    source = """
-        (function() {{
-            var css = document.createElement('style');
-            css.setAttribute('type', 'text/css');
-            css.appendChild(document.createTextNode('{}'));
-            document.getElementsByTagName('head')[0].appendChild(css);
-        }})()
-    """.format(javascript.string_escape(css))
+    source = '\n'.join([
+        '"use strict";',
+        'window._qutebrowser = window._qutebrowser || {};',
+        utils.read_file('javascript/stylesheet.js'),
+        javascript.assemble('stylesheet', 'set_css', css),
+    ])
 
     script = QWebEngineScript()
     script.setName('_qute_stylesheet')
-    script.setInjectionPoint(QWebEngineScript.DocumentReady)
+    script.setInjectionPoint(QWebEngineScript.DocumentCreation)
     script.setWorldId(QWebEngineScript.ApplicationWorld)
     script.setRunsOnSubFrames(True)
     script.setSourceCode(source)
     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, window in objreg.window_registry.items():
+        # We could be in the middle of destroying a window here
+        if sip.isdeleted(window):
+            continue
+        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):
     """Set the user agent and accept-language for the given profile.
 
@@ -199,6 +211,7 @@ def _update_settings(option):
     if option in ['scrolling.bar', 'content.user_stylesheets']:
         _init_stylesheet(default_profile)
         _init_stylesheet(private_profile)
+        _update_stylesheet()
     elif option in ['content.headers.user_agent',
                     'content.headers.accept_language']:
         _set_http_headers(default_profile)
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -533,7 +533,7 @@ def __init__(self, *, win_id, mode_manager, private, parent=None):
     def _init_js(self):
         js_code = '\n'.join([
             '"use strict";',
-            'window._qutebrowser = {};',
+            'window._qutebrowser = window._qutebrowser || {};',
             utils.read_file('javascript/scroll.js'),
             utils.read_file('javascript/webelem.js'),
         ])


Test patch:
diff --git a/tests/end2end/data/downloads/mhtml/simple/simple-webengine.mht b/tests/end2end/data/downloads/mhtml/simple/simple-webengine.mht
--- a/tests/end2end/data/downloads/mhtml/simple/simple-webengine.mht
+++ b/tests/end2end/data/downloads/mhtml/simple/simple-webengine.mht
@@ -16,11 +16,11 @@ Content-Location: http://localhost:(port)/data/downloads/mhtml/simple/simple.htm
 t/html; charset=3DUTF-8">
        =20
         <title>Simple MHTML test</title>
-    <style type=3D"text/css">
-html > ::-webkit-scrollbar { width: 0px; height: 0px; }</style></head>
+    </head>
     <body>
         <a href=3D"http://localhost:(port)/">normal link to another page</a>
    =20
 
-</body></html>
+</body><style>
+html > ::-webkit-scrollbar { width: 0px; height: 0px; }</style></html>
 -----=_qute-UUID