From 948fa033c7339d6648af2141e88283105ffbaa31 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 7 Sep 2016 11:20:32 +0200 Subject: [PATCH] Implement :insert-text for QtWebEngine --- qutebrowser/browser/commands.py | 30 ++++++++----------- qutebrowser/browser/webelem.py | 4 +++ .../browser/webengine/webengineelem.py | 7 +++++ qutebrowser/browser/webkit/webkitelem.py | 11 +++++++ qutebrowser/javascript/webelem.js | 7 +++++ tests/end2end/features/yankpaste.feature | 7 +---- tests/end2end/test_insert_mode.py | 5 +++- 7 files changed, 47 insertions(+), 24 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6a0232cfb..283827f91 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1525,32 +1525,28 @@ class CommandDispatcher: self.insert_text(utils.get_clipboard()) @cmdutils.register(instance='command-dispatcher', maxsplit=0, - scope='window', backend=usertypes.Backend.QtWebKit) + scope='window') def insert_text(self, text): """Insert text at cursor position. Args: text: The text to insert. """ - # FIXME:qtwebengine have a proper API for this tab = self._current_widget() if not tab.has_js(): raise cmdexc.CommandError("This command needs javascript enabled.") - page = tab._widget.page() # pylint: disable=protected-access - try: - elem = webkitelem.focus_elem(page.currentFrame()) - except webkitelem.IsNullError: - raise cmdexc.CommandError("No element focused!") - if not elem.is_editable(strict=True): - raise cmdexc.CommandError("Focused element is not editable!") - log.misc.debug("Inserting text into element {}".format( - elem.debug_text())) - elem.run_js_async(""" - var text = '{}'; - var event = document.createEvent('TextEvent'); - event.initTextEvent('textInput', true, true, null, text); - this.dispatchEvent(event); - """.format(javascript.string_escape(text))) + + def _insert_text_cb(elem): + if elem is None: + message.error(self._win_id, "No element focused!") + return + try: + elem.insert_text(text) + except webelem.Error as e: + message.error(self._win_id, str(e)) + return + + tab.elements.find_focused(_insert_text_cb) @cmdutils.register(instance='command-dispatcher', scope='window', hide=True) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 008d6b280..e9e164b0d 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -158,6 +158,10 @@ class AbstractWebElement(collections.abc.MutableMapping): # FIXME:qtwebengine what to do about use_js with WebEngine? raise NotImplementedError + def insert_text(self, text): + """Insert the given text into the element.""" + raise NotImplementedError + def run_js_async(self, code, callback=None): """Run the given JS snippet async on the element.""" # FIXME:qtwebengine get rid of this? diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 9610d3647..96a201f04 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -108,6 +108,13 @@ class WebEngineElement(webelem.AbstractWebElement): js_code = javascript.assemble('webelem', 'set_text', self._id, text) self._tab.run_js_async(js_code) + def insert_text(self, text): + if not self.is_editable(strict=True): + raise webelem.Error("Element is not editable!") + log.misc.debug("Inserting text into element {!r}".format(self)) + js_code = javascript.assemble('webelem', 'insert_text', self._id, text) + self._tab.run_js_async(js_code) + def run_js_async(self, code, callback=None): """Run the given JS snippet async on the element.""" # FIXME:qtwebengine get rid of this? diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 67c394e48..ff24cbad2 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -135,6 +135,17 @@ class WebKitElement(webelem.AbstractWebElement): text = javascript.string_escape(text) self._elem.evaluateJavaScript("this.value='{}'".format(text)) + def insert_text(self, text): + if not self.is_editable(strict=True): + raise webelem.Error("Element is not editable!") + log.misc.debug("Inserting text into element {!r}".format(self)) + self.run_js_async(""" + var text = '{}'; + var event = document.createEvent('TextEvent'); + event.initTextEvent('textInput', true, true, null, text); + this.dispatchEvent(event); + """.format(javascript.string_escape(text))) + def run_js_async(self, code, callback=None): """Run the given JS snippet async on the element.""" self._check_vanished() diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 6de878c74..bf3d3b8ba 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -132,6 +132,13 @@ window._qutebrowser.webelem = (function() { elements[id].value = text; }; + funcs.insert_text = function(id, text) { + var elem = elements[id]; + var event = document.createEvent("TextEvent"); + event.initTextEvent("textInput", true, true, null, text); + elem.dispatchEvent(event); + }; + funcs.element_at_pos = function(x, y) { // FIXME:qtwebengine // If the element at the specified point belongs to another document diff --git a/tests/end2end/features/yankpaste.feature b/tests/end2end/features/yankpaste.feature index 987d5b5fd..d4057f14d 100644 --- a/tests/end2end/features/yankpaste.feature +++ b/tests/end2end/features/yankpaste.feature @@ -229,7 +229,6 @@ Feature: Yanking and pasting. #### :insert-text - @qtwebengine_todo: :insert-text is not implemented yet Scenario: Inserting text into an empty text field When I open data/paste_primary.html And I run :click-element id qute-textarea @@ -238,7 +237,6 @@ Feature: Yanking and pasting. # Compare Then the text field should contain "Hello world" - @qtwebengine_todo: :insert-text is not implemented yet Scenario: Inserting text into a text field at specific position When I open data/paste_primary.html And I set the text field to "one two three four" @@ -252,7 +250,6 @@ Feature: Yanking and pasting. # Compare Then the text field should contain "onHello worlde two three four" - @qtwebengine_todo: :insert-text is not implemented yet Scenario: Inserting text into a text field with undo When I open data/paste_primary.html And I run :click-element id qute-textarea @@ -265,18 +262,16 @@ Feature: Yanking and pasting. # Compare Then the text field should contain "This text should stay" - @qtwebengine_todo: :insert-text is not implemented yet Scenario: Inserting text without a focused field When I open data/paste_primary.html And I run :enter-mode insert And I run :insert-text test Then the error "No element focused!" should be shown - @qtwebengine_todo: :insert-text is not implemented yet Scenario: Inserting text with a read-only field When I open data/paste_primary.html And I run :click-element id qute-textarea-noedit And I wait for "Clicked non-editable element!" in the log And I run :enter-mode insert And I run :insert-text test - Then the error "Focused element is not editable!" should be shown + Then the error "Element is not editable!" should be shown diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index a3edd651e..86fd22ba5 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -49,8 +49,11 @@ def test_insert_mode(file_name, elem_id, source, input_text, auto_insert, quteproc.press_keys(input_text) elif source == 'clipboard': if request.config.webengine: - pytest.xfail(reason="QtWebEngine TODO: :insert-text is not " + pytest.xfail(reason="QtWebEngine TODO: caret mode is not " "implemented") + # Note we actually run the keypress tests with QtWebEngine, as for + # some reason it selects all the text when clicking the field the + # second time. quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(input_text)) quteproc.send_cmd(':insert-text {clipboard}')