greasemonkey: run scripts on subframes on webkit

Use the `QWebPage.frameCreated` signal to get notifications of subframes
and connect the javascript injection triggering signals on those frames
too.

I had to add a `url = url() or requestedUrl()` bit in there because the
inject_userjs method was getting called to early or something when
frame.url() wasn't set or was set to the previous page so we were
passing the wrong url to greasemonkey.scripts_for().

I ran into a bizarre (I maybe it is completely obvious and I just don't
see it) issue where the signals attached to the main frame that were
connected to a partial function with the main frame as an argument were
not getting emitted, or at least those partial functions were not being
called. I worked around it by using None to mean defaulting to the main
frame in a couple of places.
This commit is contained in:
Jimmy 2017-10-24 21:19:22 +13:00
parent 4c3461038d
commit 9aeb5775c1
2 changed files with 49 additions and 15 deletions

View File

@ -50,7 +50,6 @@ class GreasemonkeyScript:
self.namespace = None self.namespace = None
self.run_at = None self.run_at = None
self.script_meta = None self.script_meta = None
# Running on subframes is only supported on the qtwebengine backend.
self.runs_on_sub_frames = True self.runs_on_sub_frames = True
for name, value in properties: for name, value in properties:
if name == 'name': if name == 'name':

View File

@ -86,12 +86,33 @@ class BrowserPage(QWebPage):
self.on_save_frame_state_requested) self.on_save_frame_state_requested)
self.restoreFrameStateRequested.connect( self.restoreFrameStateRequested.connect(
self.on_restore_frame_state_requested) self.on_restore_frame_state_requested)
self.mainFrame().javaScriptWindowObjectCleared.connect( self.connect_userjs_signals(None)
functools.partial(self.inject_userjs, load='start')) self.frameCreated.connect(self.connect_userjs_signals)
self.mainFrame().initialLayoutCompleted.connect(
functools.partial(self.inject_userjs, load='end')) @pyqtSlot('QWebFrame*')
self.mainFrame().loadFinished.connect( def connect_userjs_signals(self, frame_arg):
functools.partial(self.inject_userjs, load='idle')) """
Connect the signals used as triggers for injecting user
javascripts into `frame_arg`.
"""
# If we pass whatever self.mainFrame() or self.currentFrame() returns
# at init time into the partial functions which the signals
# below call then the signals don't seem to be called at all for
# the main frame of the first tab. I have no idea why I am
# seeing this behavior. Replace the None in the call to this
# function in __init__ with self.mainFrame() and try for
# yourself.
if frame_arg:
frame = frame_arg
else:
frame = self.mainFrame()
frame.javaScriptWindowObjectCleared.connect(
functools.partial(self.inject_userjs, frame_arg, load='start'))
frame.initialLayoutCompleted.connect(
functools.partial(self.inject_userjs, frame_arg, load='end'))
frame.loadFinished.connect(
functools.partial(self.inject_userjs, frame_arg, load='idle'))
def javaScriptPrompt(self, frame, js_msg, default): def javaScriptPrompt(self, frame, js_msg, default):
"""Override javaScriptPrompt to use qutebrowser prompts.""" """Override javaScriptPrompt to use qutebrowser prompts."""
@ -290,16 +311,24 @@ class BrowserPage(QWebPage):
self.error_occurred = False self.error_occurred = False
@pyqtSlot() @pyqtSlot()
def inject_userjs(self, load): def inject_userjs(self, frame, load):
"""Inject user javascripts into the page. """Inject user javascripts into the page.
param: The page load stage to inject the corresponding scripts Args:
for. Support values are "start", "end" and "idle", frame: The QWebFrame to inject the user scripts into, or
corresponding to the allowed values of the `@run-at` None for the main frame.
directive in the greasemonkey metadata spec. load: The page load stage to inject the corresponding
scripts for. Support values are "start", "end" and
"idle", corresponding to the allowed values of the
`@run-at` directive in the greasemonkey metadata spec.
""" """
if not frame:
frame = self.mainFrame()
url = frame.url()
if url.isEmpty():
url = frame.requestedUrl()
greasemonkey = objreg.get('greasemonkey') greasemonkey = objreg.get('greasemonkey')
url = self.currentFrame().url()
scripts = greasemonkey.scripts_for(url) scripts = greasemonkey.scripts_for(url)
if load == "start": if load == "start":
@ -309,9 +338,15 @@ class BrowserPage(QWebPage):
elif load == "idle": elif load == "idle":
toload = scripts.idle toload = scripts.idle
if url.isEmpty():
# This happens during normal usage like with view source but may
# also indicate a bug.
log.greasemonkey.debug("Not running scripts for frame with no "
"url: {}".format(frame))
for script in toload: for script in toload:
log.webview.debug('Running GM script: {}'.format(script.name)) if frame is self.mainFrame() or script.runs_on_sub_frames:
self.currentFrame().evaluateJavaScript(script.code()) log.webview.debug('Running GM script: {}'.format(script.name))
frame.evaluateJavaScript(script.code())
@pyqtSlot('QWebFrame*', 'QWebPage::Feature') @pyqtSlot('QWebFrame*', 'QWebPage::Feature')
def _on_feature_permission_requested(self, frame, feature): def _on_feature_permission_requested(self, frame, feature):