From 27fdf4903a5bffec6af39032c07bd8f22f255ca6 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 29 May 2015 18:36:39 +0200 Subject: [PATCH 1/9] Implement :jseval (Issue #334) TODO: - Tests - Doesn't show errors --- qutebrowser/misc/utilcmds.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index ea6fdf1cc..1c7e40d99 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -111,6 +111,19 @@ def message_warning(win_id, text): message.warning(win_id, text) +@cmdutils.register(maxsplit=0, no_cmd_split=True) +def jseval(s): + """Evaluate a JavaScript string. + + Args: + s: The string to evaluate. + """ + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + message_info(tabbed_browser.widget(0) + .page().mainFrame().evaluateJavaScript(s)) + + @cmdutils.register(debug=True) def debug_crash(typ: {'type': ('exception', 'segfault')}='exception'): """Crash for debugging purposes. From 85eea17b183751a89ddc8bb4bb82a3a0ccdf5ba5 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Wed, 3 Jun 2015 22:31:15 +0200 Subject: [PATCH 2/9] Try to get the error ... not sure about this ... source is undefined when you type stuff in the console, I *think* this is the only scenario? But maybe not? --- qutebrowser/browser/commands.py | 20 ++++++++++++++++++++ qutebrowser/browser/webpage.py | 11 +++++++++++ qutebrowser/misc/utilcmds.py | 13 ------------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 0c01260d7..bc2159843 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1510,3 +1510,23 @@ class CommandDispatcher: view = self._current_widget() for _ in range(count): view.triggerPageAction(member) + + @cmdutils.register(instance='command-dispatcher', scope='window', + maxsplit=0, no_cmd_split=True) + def jseval(self, js): + """Evaluate a JavaScript string. + + Args: + s: The string to evaluate. + """ + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + out = tabbed_browser.widget(0).page().mainFrame().evaluateJavaScript( + 'window.__qute_jseval__ = true;\n' + js) + + if out is not None: + message.info(self._win_id, out) + elif tabbed_browser.widget(0).page().jseval_error: + message.error(self._win_id, + tabbed_browser.widget(0).page().jseval_error) + tabbed_browser.widget(0).page().jseval_error = None diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 005a6e300..9e49032df 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -63,6 +63,7 @@ class BrowserPage(QWebPage): def __init__(self, win_id, tab_id, parent=None): super().__init__(parent) self._win_id = win_id + self.jseval_error = None self._is_shutting_down = False self._extension_handlers = { QWebPage.ErrorPageExtension: self._handle_errorpage, @@ -497,6 +498,16 @@ class BrowserPage(QWebPage): def javaScriptConsoleMessage(self, msg, line, source): """Override javaScriptConsoleMessage to use debug log.""" + + jseval = self.mainFrame().evaluateJavaScript('window.__qute_jseval__') + if jseval: + self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = undefined;') + if source == 'undefined' and jseval: + self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = false;') + print('jseval errror ->', jseval) + self.jseval_error = 'Error on line {}: {}'.format(line, msg) + print('other js error ->', msg, line, source) + if config.get('general', 'log-javascript-console'): log.js.debug("[{}:{}] {}".format(source, line, msg)) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 1c7e40d99..ea6fdf1cc 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -111,19 +111,6 @@ def message_warning(win_id, text): message.warning(win_id, text) -@cmdutils.register(maxsplit=0, no_cmd_split=True) -def jseval(s): - """Evaluate a JavaScript string. - - Args: - s: The string to evaluate. - """ - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='last-focused') - message_info(tabbed_browser.widget(0) - .page().mainFrame().evaluateJavaScript(s)) - - @cmdutils.register(debug=True) def debug_crash(typ: {'type': ('exception', 'segfault')}='exception'): """Crash for debugging purposes. From 94178c558abf0d5da0bdd6a5ec81348022d72a23 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 20:07:47 +0200 Subject: [PATCH 3/9] Well, getting the error doesn't work... --- doc/help/commands.asciidoc | 14 ++++++++++++++ qutebrowser/browser/commands.py | 28 ++++++++++++++++++---------- qutebrowser/browser/webpage.py | 11 ----------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index c33bccaaf..287620867 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -20,6 +20,7 @@ |<>|Start hinting. |<>|Open main startpage in current tab. |<>|Toggle the web inspector. +|<>|Evaluate a JavaScript string. |<>|Execute a command after some time. |<>|Open typical prev/next links or navigate using the URL path. |<>|Open a URL in the current/[count]th tab. @@ -241,6 +242,19 @@ Open main startpage in current tab. === inspector Toggle the web inspector. +[[jseval]] +=== jseval +Syntax: +:jseval 'js_code'+ + +Evaluate a JavaScript string. + +==== positional arguments +* +'js_code'+: The string to evaluate. + +==== note +* This command does not split arguments after the last argument and handles quotes literally. +* With this command, +;;+ is interpreted literally instead of splitting off a second command. + [[later]] === later Syntax: +:later 'ms' 'command'+ diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index bc2159843..aa8430127 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1513,20 +1513,28 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_cmd_split=True) - def jseval(self, js): + def jseval(self, js_code): """Evaluate a JavaScript string. Args: - s: The string to evaluate. + js_code: The string to evaluate. """ tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') - out = tabbed_browser.widget(0).page().mainFrame().evaluateJavaScript( - 'window.__qute_jseval__ = true;\n' + js) + frame = tabbed_browser.widget(0).page().mainFrame() + out = frame.evaluateJavaScript(js_code) - if out is not None: - message.info(self._win_id, out) - elif tabbed_browser.widget(0).page().jseval_error: - message.error(self._win_id, - tabbed_browser.widget(0).page().jseval_error) - tabbed_browser.widget(0).page().jseval_error = None + if out is None: + # Getting the actual error (if any) seems to be difficult. The + # error does end up in BrowserPage.javaScriptConsoleMessage(), but + # distinguishing between :jseval errors and errors from the webpage + # is not trivial... + message.info(self._win_id, 'No output or error') + else: + # The output can be a string, number, dict, array, etc. But *don't* + # output too much data, as this will make qutebrowser hang + out = str(out) + if len(out) > 5000: + message.info(self._win_id, out[:5000] + ' [...trimmed...]') + else: + message.info(self._win_id, out) diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 9e49032df..005a6e300 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -63,7 +63,6 @@ class BrowserPage(QWebPage): def __init__(self, win_id, tab_id, parent=None): super().__init__(parent) self._win_id = win_id - self.jseval_error = None self._is_shutting_down = False self._extension_handlers = { QWebPage.ErrorPageExtension: self._handle_errorpage, @@ -498,16 +497,6 @@ class BrowserPage(QWebPage): def javaScriptConsoleMessage(self, msg, line, source): """Override javaScriptConsoleMessage to use debug log.""" - - jseval = self.mainFrame().evaluateJavaScript('window.__qute_jseval__') - if jseval: - self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = undefined;') - if source == 'undefined' and jseval: - self.mainFrame().evaluateJavaScript('window.__qute_jseval__ = false;') - print('jseval errror ->', jseval) - self.jseval_error = 'Error on line {}: {}'.format(line, msg) - print('other js error ->', msg, line, source) - if config.get('general', 'log-javascript-console'): log.js.debug("[{}:{}] {}".format(source, line, msg)) From b0880df695867bc60a8ac9edd79bd8fee671a393 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 23:24:47 +0200 Subject: [PATCH 4/9] Execute in the current tab, and not the first one --- qutebrowser/browser/commands.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index aa8430127..774cbaaa9 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1519,10 +1519,8 @@ class CommandDispatcher: Args: js_code: The string to evaluate. """ - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='last-focused') - frame = tabbed_browser.widget(0).page().mainFrame() - out = frame.evaluateJavaScript(js_code) + out = self._current_widget().page().mainFrame().evaluateJavaScript( + js_code) if out is None: # Getting the actual error (if any) seems to be difficult. The From 8ecc3a3bb0b1fd769c22cef9ab042665b328ab97 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jun 2015 11:22:37 +0200 Subject: [PATCH 5/9] Fix lint. --- qutebrowser/browser/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 9ff65e7bc..9ba2b3cc2 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1572,8 +1572,8 @@ class CommandDispatcher: Args: js_code: The string to evaluate. """ - out = self._current_widget().page().mainFrame().evaluateJavaScript( - js_code) + frame = self._current_widget().page().mainFrame() + out = frame.evaluateJavaScript(js_code) if out is None: # Getting the actual error (if any) seems to be difficult. The From efcea65596f5a2a01db00d77d27916e55414b48e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jun 2015 11:24:04 +0200 Subject: [PATCH 6/9] Add --quiet argument to :jseval. --- doc/help/commands.asciidoc | 7 +++++-- qutebrowser/browser/commands.py | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 9e387f42e..fe71e8eb0 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -244,12 +244,15 @@ Toggle the web inspector. [[jseval]] === jseval -Syntax: +:jseval 'js_code'+ +Syntax: +:jseval [*--quiet*] 'js-code'+ Evaluate a JavaScript string. ==== positional arguments -* +'js_code'+: The string to evaluate. +* +'js-code'+: The string to evaluate. + +==== optional arguments +* +*-q*+, +*--quiet*+: Don't show resulting JS object. ==== note * This command does not split arguments after the last argument and handles quotes literally. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 9ba2b3cc2..af8815a78 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1566,15 +1566,19 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_cmd_split=True) - def jseval(self, js_code): + def jseval(self, js_code, quiet=False): """Evaluate a JavaScript string. Args: js_code: The string to evaluate. + quiet: Don't show resulting JS object. """ frame = self._current_widget().page().mainFrame() out = frame.evaluateJavaScript(js_code) + if quiet: + return + if out is None: # Getting the actual error (if any) seems to be difficult. The # error does end up in BrowserPage.javaScriptConsoleMessage(), but From 6f690c442ee06b44cbd218b8375d5493c76fc966 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jun 2015 11:24:21 +0200 Subject: [PATCH 7/9] Regenerate authors. --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index e1706d8e7..0cb373219 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -138,8 +138,8 @@ Contributors, sorted by the number of commits in descending order: * Raphael Pierzina * Joel Torstensson * Claude -* Lamar Pavel * Martin Tournoij +* Lamar Pavel * Austin Anderson * Artur Shaik * Antoni Boucher From 8369c74f74dc46606003e54a3bf1c582d06a451d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jun 2015 11:24:57 +0200 Subject: [PATCH 8/9] Update changelog. --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index a41732c2c..8db54d7bf 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -41,6 +41,7 @@ Added - New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one. - New (hidden) command `:clear-keychain` to clear a partially entered keychain (bound to `` by default, in addition to clearing search). - Many new color settings (foreground setting for every background setting). +- New command `:jseval` to run a javascript snippet on the current page. Changed ~~~~~~~ From 167faafff2d5845851a834b5fa5b89feead748d9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jun 2015 11:30:46 +0200 Subject: [PATCH 9/9] Fix command parsing for arguments containing _. --- qutebrowser/commands/command.py | 22 +++++++++++++--------- scripts/src2asciidoc.py | 10 +++++++++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index 4bda15f8a..8da3bc714 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -29,6 +29,11 @@ from qutebrowser.utils import log, utils, message, docutils, objreg, usertypes from qutebrowser.utils import debug as debug_utils +def arg_name(name): + """Get the name an argument should have based on its Python name.""" + return name.rstrip('_').replace('_', '-') + + class Command: """Base skeleton for a command. @@ -288,7 +293,7 @@ class Command: A list of args. """ args = [] - name = param.name.rstrip('_').replace('_', '-') + name = arg_name(param.name) shortname = annotation_info.flag or name[0] if len(shortname) != 1: raise ValueError("Flag '{}' of parameter {} (command {}) must be " @@ -304,7 +309,7 @@ class Command: if typ is not bool: self.flags_with_args += [short_flag, long_flag] else: - args.append(name) + args.append(param.name) if not annotation_info.hide: self.pos_args.append((param.name, name)) return args @@ -408,17 +413,16 @@ class Command: raise TypeError("{}: invalid parameter type {} for argument " "{!r}!".format(self.name, param.kind, param.name)) - def _get_param_name_and_value(self, param): - """Get the converted name and value for an inspect.Parameter.""" - name = param.name.rstrip('_') - value = getattr(self.namespace, name) + def _get_param_value(self, param): + """Get the converted value for an inspect.Parameter.""" + value = getattr(self.namespace, param.name) if param.name in self._type_conv: # We convert enum types after getting the values from # argparse, because argparse's choices argument is # processed after type conversation, which is not what we # want. value = self._type_conv[param.name](value) - return name, value + return value def _get_call_args(self, win_id): """Get arguments for a function call. @@ -452,14 +456,14 @@ class Command: # Special case for win_id parameter. self._get_win_id_arg(win_id, param, args, kwargs) continue - name, value = self._get_param_name_and_value(param) + value = self._get_param_value(param) if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: args.append(value) elif param.kind == inspect.Parameter.VAR_POSITIONAL: if value is not None: args += value elif param.kind == inspect.Parameter.KEYWORD_ONLY: - kwargs[name] = value + kwargs[param.name] = value else: raise TypeError("{}: Invalid parameter type {} for argument " "'{}'!".format( diff --git a/scripts/src2asciidoc.py b/scripts/src2asciidoc.py index 31d82f6e8..dddbc3a4f 100755 --- a/scripts/src2asciidoc.py +++ b/scripts/src2asciidoc.py @@ -37,7 +37,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) import qutebrowser.app from scripts import asciidoc2html, utils from qutebrowser import qutebrowser -from qutebrowser.commands import cmdutils +from qutebrowser.commands import cmdutils, command from qutebrowser.config import configdata from qutebrowser.utils import docutils @@ -54,6 +54,14 @@ class UsageFormatter(argparse.HelpFormatter): """Override _format_usage to not add the 'usage:' prefix.""" return super()._format_usage(usage, actions, groups, '') + def _get_default_metavar_for_optional(self, action): + """Do name transforming when getting metavar.""" + return command.arg_name(action.dest.upper()) + + def _get_default_metavar_for_positional(self, action): + """Do name transforming when getting metavar.""" + return command.arg_name(action.dest) + def _metavar_formatter(self, action, default_metavar): """Override _metavar_formatter to add asciidoc markup to metavars.