From e295e8054c876f160cfc0cd91bbb375a7f0baaab Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sat, 8 Sep 2018 19:29:15 -0700 Subject: [PATCH 1/3] Add support for triggering change handlers when using :open-editor --- qutebrowser/browser/commands.py | 2 ++ qutebrowser/browser/webelem.py | 4 ++++ qutebrowser/browser/webengine/webengineelem.py | 3 +++ qutebrowser/browser/webkit/webkitelem.py | 8 ++++++++ qutebrowser/javascript/webelem.js | 5 +++++ 5 files changed, 22 insertions(+) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index d5e1797ac..a931eb66e 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1652,6 +1652,8 @@ class CommandDispatcher: """ try: elem.set_value(text) + # Kick off js handlers to trick them into thinking there was input. + elem.dispatch_event("input") except webelem.OrphanedError: message.error('Edited element vanished') ed.backup() diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 1d719738b..621ec91e2 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -139,6 +139,10 @@ class AbstractWebElement(collections.abc.MutableMapping): """Set the element value.""" raise NotImplementedError + def dispatch_event(self, event): + """Set the element value.""" + raise NotImplementedError + def insert_text(self, text): """Insert the given text into the element.""" raise NotImplementedError diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index aefa51f5a..44176cf73 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -135,6 +135,9 @@ class WebEngineElement(webelem.AbstractWebElement): def set_value(self, value): self._js_call('set_value', value) + def dispatch_event(self, event): + self._js_call('dispatch_event', event) + def caret_position(self): """Get the text caret position for the current element. diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 7ec979d09..81cb24780 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -128,6 +128,14 @@ class WebKitElement(webelem.AbstractWebElement): value = javascript.string_escape(value) self._elem.evaluateJavaScript("this.value='{}'".format(value)) + def dispatch_event(self, event): + self._check_vanished() + if self._tab.is_deleted(): + raise webelem.OrphanedError("Tab containing element vanished") + log.webelem.debug("Firing event on {!r} via javascript.".format(self)) + self._elem.evaluateJavaScript("this.dispatchEvent(new Event('{}'))" + .format(event)) + def caret_position(self): """Get the text caret position for the current element.""" self._check_vanished() diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index ad18899f6..5b1a0679b 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -362,6 +362,11 @@ window._qutebrowser.webelem = (function() { document.execCommand("insertText", false, text); }; + funcs.dispatch_event = (id, event) => { + const elem = elements[id]; + elem.dispatchEvent(new Event(event)); + }; + funcs.set_attribute = (id, name, value) => { elements[id].setAttribute(name, value); }; From 1da2bdb1e50478c2d2fe557a46400f7120ddfa3e Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 19 Sep 2018 23:51:15 -0700 Subject: [PATCH 2/3] Add bubbles, cancelable, and composed options to dispatch_event --- qutebrowser/browser/commands.py | 2 +- qutebrowser/browser/webelem.py | 12 ++++++++++-- qutebrowser/browser/webengine/webengineelem.py | 6 ++++-- qutebrowser/browser/webkit/webkitelem.py | 13 ++++++++----- qutebrowser/javascript/.eslintrc.yaml | 1 + qutebrowser/javascript/caret.js | 2 +- qutebrowser/javascript/webelem.js | 9 +++++++-- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index a931eb66e..aa8ce3502 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1653,7 +1653,7 @@ class CommandDispatcher: try: elem.set_value(text) # Kick off js handlers to trick them into thinking there was input. - elem.dispatch_event("input") + elem.dispatch_event("input", bubbles=True) except webelem.OrphanedError: message.error('Edited element vanished') ed.backup() diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 621ec91e2..5ff59653b 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -139,8 +139,16 @@ class AbstractWebElement(collections.abc.MutableMapping): """Set the element value.""" raise NotImplementedError - def dispatch_event(self, event): - """Set the element value.""" + def dispatch_event(self, event, bubbles=False, + cancelable=False, composed=False): + """Dispatch an event to the element. + + Args: + bubbles: Whether this event should bubble. + cancelable: Whether this event can be cancelled. + composed: Whether the event will trigger listeners outside of a + shadow root + """ raise NotImplementedError def insert_text(self, text): diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 44176cf73..ad23c9337 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -135,8 +135,10 @@ class WebEngineElement(webelem.AbstractWebElement): def set_value(self, value): self._js_call('set_value', value) - def dispatch_event(self, event): - self._js_call('dispatch_event', event) + + def dispatch_event(self, event, bubbles=False, + cancelable=False, composed=False): + self._js_call('dispatch_event', event, bubbles, cancelable, composed) def caret_position(self): """Get the text caret position for the current element. diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 81cb24780..151cdb1dc 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -128,13 +128,16 @@ class WebKitElement(webelem.AbstractWebElement): value = javascript.string_escape(value) self._elem.evaluateJavaScript("this.value='{}'".format(value)) - def dispatch_event(self, event): + def dispatch_event(self, event, bubbles=False, + cancelable=False, composed=False): self._check_vanished() - if self._tab.is_deleted(): - raise webelem.OrphanedError("Tab containing element vanished") log.webelem.debug("Firing event on {!r} via javascript.".format(self)) - self._elem.evaluateJavaScript("this.dispatchEvent(new Event('{}'))" - .format(event)) + event = javascript.string_escape(event) + self._elem.evaluateJavaScript( + "this.dispatchEvent(new Event('{}', " + "{{'bubbles': {}, 'cancelable': {}, 'composed': {}}}))" + .format(event, str(bubbles).lower(), str(cancelable).lower(), + str(composed).lower())) def caret_position(self): """Get the text caret position for the current element.""" diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index 9e99b0aa5..cb1bb1fcb 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -59,3 +59,4 @@ rules: multiline-ternary: ["error", "always-multiline"] max-lines-per-function: "off" require-unicode-regexp: "off" + max-params: "off" diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js index 3f858f3fb..a6da3aa71 100644 --- a/qutebrowser/javascript/caret.js +++ b/qutebrowser/javascript/caret.js @@ -1,5 +1,5 @@ /* eslint-disable max-len, max-statements, complexity, -max-params, default-case, valid-jsdoc */ +default-case, valid-jsdoc */ // Copyright 2014 The Chromium Authors. All rights reserved. // diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 5b1a0679b..7c7e9ad09 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -362,9 +362,14 @@ window._qutebrowser.webelem = (function() { document.execCommand("insertText", false, text); }; - funcs.dispatch_event = (id, event) => { + + funcs.dispatch_event = (id, event, bubbles = false, + cancelable = false, composed = false) => { const elem = elements[id]; - elem.dispatchEvent(new Event(event)); + elem.dispatchEvent( + new Event(event, {"bubbles": bubbles, + "cancelable": cancelable, + "composed": composed})); }; funcs.set_attribute = (id, name, value) => { From f0568ece57a7feb496c54610d3a69d05cf8fd360 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 20 Sep 2018 18:24:22 -0700 Subject: [PATCH 3/3] Make convert_js_arg public and use it for dispatch_element in webkit --- qutebrowser/browser/webengine/webengineelem.py | 1 - qutebrowser/browser/webkit/webkitelem.py | 9 +++++---- qutebrowser/javascript/webelem.js | 1 - qutebrowser/utils/javascript.py | 4 ++-- tests/unit/utils/test_javascript.py | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index ad23c9337..4ef20da18 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -135,7 +135,6 @@ class WebEngineElement(webelem.AbstractWebElement): def set_value(self, value): self._js_call('set_value', value) - def dispatch_event(self, event, bubbles=False, cancelable=False, composed=False): self._js_call('dispatch_event', event, bubbles, cancelable, composed) diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 151cdb1dc..b9f2c55c2 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -132,12 +132,13 @@ class WebKitElement(webelem.AbstractWebElement): cancelable=False, composed=False): self._check_vanished() log.webelem.debug("Firing event on {!r} via javascript.".format(self)) - event = javascript.string_escape(event) self._elem.evaluateJavaScript( - "this.dispatchEvent(new Event('{}', " + "this.dispatchEvent(new Event({}, " "{{'bubbles': {}, 'cancelable': {}, 'composed': {}}}))" - .format(event, str(bubbles).lower(), str(cancelable).lower(), - str(composed).lower())) + .format(javascript.convert_js_arg(event), + javascript.convert_js_arg(bubbles), + javascript.convert_js_arg(cancelable), + javascript.convert_js_arg(composed))) def caret_position(self): """Get the text caret position for the current element.""" diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 7c7e9ad09..4b9a8e8cd 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -362,7 +362,6 @@ window._qutebrowser.webelem = (function() { document.execCommand("insertText", false, text); }; - funcs.dispatch_event = (id, event, bubbles = false, cancelable = false, composed = false) => { const elem = elements[id]; diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index 93df8e70f..0ee9b6274 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -49,7 +49,7 @@ def string_escape(text): return text -def _convert_js_arg(arg): +def convert_js_arg(arg): """Convert the given argument so it's the equivalent in JS.""" if arg is None: return 'undefined' @@ -66,7 +66,7 @@ def _convert_js_arg(arg): def assemble(module, function, *args): """Assemble a javascript file and a function call.""" - js_args = ', '.join(_convert_js_arg(arg) for arg in args) + js_args = ', '.join(convert_js_arg(arg) for arg in args) if module == 'window': parts = ['window', function] else: diff --git a/tests/unit/utils/test_javascript.py b/tests/unit/utils/test_javascript.py index 29e090fd0..9d227a386 100644 --- a/tests/unit/utils/test_javascript.py +++ b/tests/unit/utils/test_javascript.py @@ -88,9 +88,9 @@ class TestStringEscape: def test_convert_js_arg(arg, expected): if expected is TypeError: with pytest.raises(TypeError): - javascript._convert_js_arg(arg) + javascript.convert_js_arg(arg) else: - assert javascript._convert_js_arg(arg) == expected + assert javascript.convert_js_arg(arg) == expected @pytest.mark.parametrize('base, expected_base', [