Permalink
Browse files

Add qutebrowser.utils.javascript

webelem.javascript_escape got renamed to javascript.string_escape, and a
new javascript.assemble got added to make it easier to call a function
inside a .js file.
  • Loading branch information...
The-Compiler committed Aug 4, 2016
1 parent 10dd313 commit 08b70f0f4cfb102f41b1105b1b06681d30362c59
@@ -44,7 +44,7 @@
from qutebrowser.browser.webkit import webelem, downloads, mhtml
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
- objreg, utils, typing)
+ objreg, utils, typing, javascript)
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import instances, sortfilter
@@ -1485,7 +1485,7 @@ def paste_primary(self):
var event = document.createEvent('TextEvent');
event.initTextEvent('textInput', true, true, null, sel);
this.dispatchEvent(event);
- """.format(webelem.javascript_escape(sel)))
+ """.format(javascript.string_escape(sel)))
def _search_cb(self, found, *, tab, old_scroll_pos, options, text, prev):
"""Callback called from search/search_next/search_prev.
@@ -23,8 +23,7 @@
from PyQt5.QtCore import QUrl
-from qutebrowser.browser.webkit import webelem
-from qutebrowser.utils import utils
+from qutebrowser.utils import utils, javascript
class PDFJSNotFound(Exception):
@@ -65,7 +64,7 @@ def _generate_pdfjs_script(url):
return (
'PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
'PDFView.open("{url}");\n'
- ).format(url=webelem.javascript_escape(url.toString(QUrl.FullyEncoded)))
+ ).format(url=javascript.string_escape(url.toString(QUrl.FullyEncoded)))
def fix_urls(asset):
@@ -31,7 +31,7 @@
from qutebrowser.browser import browsertab
from qutebrowser.browser.webengine import webview
-from qutebrowser.utils import usertypes, qtutils, log, utils
+from qutebrowser.utils import usertypes, qtutils, log, javascript
class WebEnginePrinting(browsertab.AbstractPrinting):
@@ -220,10 +220,7 @@ def update_scroll_pos(jsret):
self._pos_px = QPoint(jsret['px']['x'], jsret['px']['y'])
self.perc_changed.emit(*self._pos_perc)
- js_code = """
- {scroll_js}
- scroll_pos();
- """.format(scroll_js=utils.read_file('javascript/scroll.js'))
+ js_code = javascript.assemble('scroll', 'scroll_pos')
self._tab.run_js_async(js_code, update_scroll_pos)
def pos_px(self):
@@ -233,12 +230,7 @@ def pos_perc(self):
return self._pos_perc
def to_perc(self, x=None, y=None):
- js_code = """
- {scroll_js}
- scroll_to_perc({x}, {y});
- """.format(scroll_js=utils.read_file('javascript/scroll.js'),
- x='undefined' if x is None else x,
- y='undefined' if y is None else y)
+ js_code = javascript.assemble('scroll', 'scroll_to_perc', x, y)
self._tab.run_js_async(js_code)
def to_point(self, point):
@@ -249,10 +241,7 @@ def delta(self, x=0, y=0):
self._tab.run_js_async("window.scrollBy({x}, {y});".format(x=x, y=y))
def delta_page(self, x=0, y=0):
- js_code = """
- {scroll_js}
- scroll_delta_page({x}, {y});
- """.format(scroll_js=utils.read_file('javascript/scroll.js'), x=x, y=y)
+ js_code = javascript.assemble('scroll', 'scroll_delta_page', x, y)
self._tab.run_js_async(js_code)
def up(self, count=1):
@@ -33,7 +33,7 @@
from PyQt5.QtWebKit import QWebElement
from qutebrowser.config import config
-from qutebrowser.utils import log, usertypes, utils
+from qutebrowser.utils import log, usertypes, utils, javascript
Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext',
@@ -202,7 +202,7 @@ def set_text(self, text, *, use_js=False):
else:
log.misc.debug("Filling element {} via javascript.".format(
self.debug_text()))
- text = javascript_escape(text)
+ text = javascript.string_escape(text)
self._elem.evaluateJavaScript("this.value='{}'".format(text))
def set_inner_xml(self, xml):
@@ -501,34 +501,6 @@ def is_visible(self, mainframe):
return all([visible_on_screen, visible_in_frame])
-
-
-def javascript_escape(text):
- """Escape values special to javascript in strings.
-
- With this we should be able to use something like:
- elem.evaluateJavaScript("this.value='{}'".format(javascript_escape(...)))
- And all values should work.
- """
- # This is a list of tuples because order matters, and using OrderedDict
- # makes no sense because we don't actually need dict-like properties.
- replacements = (
- ('\\', r'\\'), # First escape all literal \ signs as \\.
- ("'", r"\'"), # Then escape ' and " as \' and \".
- ('"', r'\"'), # (note it won't hurt when we escape the wrong one).
- ('\n', r'\n'), # We also need to escape newlines for some reason.
- ('\r', r'\r'),
- ('\x00', r'\x00'),
- ('\ufeff', r'\ufeff'),
- # http://stackoverflow.com/questions/2965293/
- ('\u2028', r'\u2028'),
- ('\u2029', r'\u2029'),
- )
- for orig, repl in replacements:
- text = text.replace(orig, repl)
- return text
-
-
def get_child_frames(startframe):
"""Get all children recursively of a given QWebFrame.
@@ -0,0 +1,72 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""Utilities related to javascript interaction."""
+
+
+from qutebrowser.utils import utils
+
+
+def string_escape(text):
+ """Escape values special to javascript in strings.
+
+ With this we should be able to use something like:
+ elem.evaluateJavaScript("this.value='{}'".format(string_escape(...)))
+ And all values should work.
+ """
+ # This is a list of tuples because order matters, and using OrderedDict
+ # makes no sense because we don't actually need dict-like properties.
+ replacements = (
+ ('\\', r'\\'), # First escape all literal \ signs as \\.
+ ("'", r"\'"), # Then escape ' and " as \' and \".
+ ('"', r'\"'), # (note it won't hurt when we escape the wrong one).
+ ('\n', r'\n'), # We also need to escape newlines for some reason.
+ ('\r', r'\r'),
+ ('\x00', r'\x00'),
+ ('\ufeff', r'\ufeff'),
+ # http://stackoverflow.com/questions/2965293/
+ ('\u2028', r'\u2028'),
+ ('\u2029', r'\u2029'),
+ )
+ for orig, repl in replacements:
+ text = text.replace(orig, repl)
+ return text
+
+
+def _convert_js_arg(arg):
+ """Convert the given argument so it's the equivalent in JS."""
+ if arg is None:
+ return 'undefined'
+ elif isinstance(arg, str):
+ return string_escape(arg)
+ elif isinstance(arg, int):
+ return str(arg)
+ else:
+ raise TypeError("Don't know how to handle {!r} of type {}!".format(
+ arg, type(arg).__name__))
+
+
+def assemble(name, function, *args):
+ """Assemble a javascript file and a function call."""
+ code = "{code}\n{function}({args});".format(
+ code=utils.read_file('javascript/{}.js'.format(name)),
+ function=function,
+ args=', '.join(_convert_js_arg(arg) for arg in args),
+ )
+ return code
@@ -145,6 +145,9 @@
'qutebrowser/utils/error.py'),
('tests/unit/utils/test_typing.py',
'qutebrowser/utils/typing.py'),
+ ('tests/unit/utils/test_javascript.py',
+ 'qutebrowser/utils/javascript.py'),
+
('tests/unit/completion/test_models.py',
'qutebrowser/completion/models/base.py'),
@@ -36,8 +36,7 @@
from PyQt5.QtCore import pyqtSignal, QUrl
from qutebrowser.misc import ipc
-from qutebrowser.utils import log, utils
-from qutebrowser.browser.webkit import webelem
+from qutebrowser.utils import log, utils, javascript
from helpers import utils as testutils
from end2end.fixtures import testprocess
@@ -527,7 +526,7 @@ def click_element(self, text):
'else if (_es.snapshotLength > 1) {{ console.log("qute:ambiguous '
'elems") }} '
'else {{ console.log("qute:okay"); _es.snapshotItem(0).click() }}'
- ).format(text=webelem.javascript_escape(_xpath_escape(text)))
+ ).format(text=javascript.string_escape(_xpath_escape(text)))
self.send_cmd(':jseval ' + script, escape=False)
message = self.wait_for_js('qute:*').message
if message.endswith('qute:no elems'):
@@ -562,8 +561,8 @@ def compare_session(self, expected):
def _xpath_escape(text):
"""Escape a string to be used in an XPath expression.
- The resulting string should still be escaped with javascript_escape, to
- prevent javascript from interpreting the quotes.
+ The resulting string should still be escaped with javascript.string_escape,
+ to prevent javascript from interpreting the quotes.
This function is needed because XPath does not provide any character
escaping mechanisms, so to get the string
@@ -26,7 +26,7 @@
# Note that we got double protection, once because we use QUrl.FullyEncoded and
-# because we use qutebrowser.browser.webelem.javascript_escape. Characters
+# because we use qutebrowser.utils.javascript.string_escape. Characters
# like " are already replaced by QUrl.
@pytest.mark.parametrize('url, expected', [
('http://foo.bar', "http://foo.bar"),
@@ -23,12 +23,8 @@
import collections.abc
import operator
import itertools
-import binascii
-import os.path
-import hypothesis
-import hypothesis.strategies
-from PyQt5.QtCore import PYQT_VERSION, QRect, QPoint
+from PyQt5.QtCore import QRect, QPoint
from PyQt5.QtWebKit import QWebElement
import pytest
@@ -818,98 +814,6 @@ def test_zoomed(self, stubs, config_stub, js_rect, zoom_text_only,
assert rect == QRect(20, 20, 8, 8)
-class TestJavascriptEscape:
-
- TESTS = {
- 'foo\\bar': r'foo\\bar',
- 'foo\nbar': r'foo\nbar',
- 'foo\rbar': r'foo\rbar',
- "foo'bar": r"foo\'bar",
- 'foo"bar': r'foo\"bar',
- 'one\\two\rthree\nfour\'five"six': r'one\\two\rthree\nfour\'five\"six',
- '\x00': r'\x00',
- 'hellö': 'hellö',
- '': '',
- '\x80Ā': '\x80Ā',
- '𐀀\x00𐀀\x00': r'𐀀\x00𐀀\x00',
- '𐀀\ufeff': r'𐀀\ufeff',
- '\ufeff': r'\ufeff',
- # http://stackoverflow.com/questions/2965293/
- '\u2028': r'\u2028',
- '\u2029': r'\u2029',
- }
-
- # Once there was this warning here:
- # load glyph failed err=6 face=0x2680ba0, glyph=1912
- # http://qutebrowser.org:8010/builders/debian-jessie/builds/765/steps/unittests/
- # Should that be ignored?
-
- @pytest.mark.parametrize('before, after', sorted(TESTS.items()), ids=repr)
- def test_fake_escape(self, before, after):
- """Test javascript escaping with some expected outcomes."""
- assert webelem.javascript_escape(before) == after
-
- def _test_escape(self, text, qtbot, webframe):
- """Helper function for test_real_escape*."""
- try:
- self._test_escape_simple(text, webframe)
- except AssertionError:
- # Try another method if the simple method failed.
- #
- # See _test_escape_hexlified documentation on why this is
- # necessary.
- self._test_escape_hexlified(text, qtbot, webframe)
-
- def _test_escape_hexlified(self, text, qtbot, webframe):
- """Test conversion by hexlifying in javascript.
-
- Since the conversion of QStrings to Python strings is broken in some
- older PyQt versions in some corner cases, we load an HTML file which
- generates an MD5 of the escaped text and use that for comparisons.
- """
- escaped = webelem.javascript_escape(text)
- path = os.path.join(os.path.dirname(__file__),
- 'test_webelem_jsescape.html')
- with open(path, encoding='utf-8') as f:
- html_source = f.read().replace('%INPUT%', escaped)
-
- with qtbot.waitSignal(webframe.loadFinished) as blocker:
- webframe.setHtml(html_source)
- assert blocker.args == [True]
-
- result = webframe.evaluateJavaScript('window.qute_test_result')
- assert result is not None
- assert '|' in result
- result_md5, result_text = result.split('|', maxsplit=1)
- text_md5 = binascii.hexlify(text.encode('utf-8')).decode('ascii')
- assert result_md5 == text_md5, result_text
-
- def _test_escape_simple(self, text, webframe):
- """Test conversion by using evaluateJavaScript."""
- escaped = webelem.javascript_escape(text)
- result = webframe.evaluateJavaScript('"{}";'.format(escaped))
- assert result == text
-
- @pytest.mark.parametrize('text', sorted(TESTS), ids=repr)
- def test_real_escape(self, webframe, qtbot, text):
- """Test javascript escaping with a real QWebPage."""
- self._test_escape(text, qtbot, webframe)
-
- @pytest.mark.qt_log_ignore('^OpenType support missing for script')
- @hypothesis.given(hypothesis.strategies.text())
- def test_real_escape_hypothesis(self, webframe, qtbot, text):
- """Test javascript escaping with a real QWebPage and hypothesis."""
- # We can't simply use self._test_escape because of this:
- # https://github.com/pytest-dev/pytest-qt/issues/69
-
- # self._test_escape(text, qtbot, webframe)
- try:
- self._test_escape_simple(text, webframe)
- except AssertionError:
- if PYQT_VERSION >= 0x050300:
- self._test_escape_hexlified(text, qtbot, webframe)
-
-
class TestGetChildFrames:
"""Check get_child_frames."""
Oops, something went wrong.

0 comments on commit 08b70f0

Please sign in to comment.