def set_node_style(self, property_name, property_value): """ Set value of the style property of the element """ return self.tab.evaljs(u"{element}.style[{property}] = {value}".format( element=self.element_js, property=escape_js(property_name), value=escape_js(property_value) ))
def init_storage(self): frame = self.parent().web_page.mainFrame() eval_expr = u"eval({})".format( escape_js(""" (function() {{ var storage = window[{storage_name}]; storage.events = {{}}; storage.callMethod = function(methodName) {{ return function(eventId) {{ var eventsStorage = window[{storage_name}].events; eventsStorage[eventId][methodName].call(eventsStorage[eventId]); }}; }} storage.preventDefault.connect(storage.callMethod('preventDefault')) storage.stopImmediatePropagation.connect(storage.callMethod('stopImmediatePropagation')) storage.stopPropagation.connect(storage.callMethod('stopPropagation')) storage.add = function(event) {{ var id = storage.getId() storage.events[id] = event; return id; }} }})() """.format(storage_name=escape_js(self.name)))) frame.evaluateJavaScript(eval_expr)
def remove_event(self, event_id): js_func = """ delete window[{storage_name}].events[{event_id}] """.format( storage_name=escape_js(self.name), event_id=escape_js(event_id), ) escape_and_evaljs(self.parent().web_page.mainFrame(), js_func)
def get_event_property(self, event_id, property_name): js_func = """ window[{storage_name}].events[{event_id}][{property_name}] """.format(storage_name=escape_js(self.name), event_id=escape_js(event_id), property_name=escape_js(property_name)) result = escape_and_evaljs(self.parent().web_page.mainFrame(), js_func) return result.get('result', None)
def set_event_handler(self, event_name, handler): """ Set on-event type event listeners to the element """ handler_id = self.event_handlers_storage.add(handler) func = u"window[{storage_name}][{func_id}]".format( storage_name=escape_js(self.event_handlers_storage.name), func_id=escape_js(handler_id), ) self.tab.evaljs(u"{element}['on' + {event_name}] = {func}".format( element=self.element_js, event_name=escape_js(event_name), func=func)) return handler_id
def remove_event_handler(self, event_name, handler_id): """ Remove event listeners from the element for the specified event and handler. """ func = u"window[{storage_name}][{func_id}]".format( storage_name=escape_js(self.event_handlers_storage.name), func_id=escape_js(handler_id), ) self.tab.evaljs(u"{element}.removeEventListener({event_name}, {func})".format( element=self.element_js, event_name=escape_js(event_name), func=func )) self.event_handlers_storage.remove(handler_id)
def unset_event_handler(self, event_name, handler_id): """ Remove on-event type event listeners from the element """ self.tab.evaljs(u"{element}['on' + {event_name}] = null".format( element=self.element_js, event_name=escape_js(event_name), )) self.event_handlers_storage.remove(handler_id)
def set_event_handler(self, event_name, handler): """ Set on-event type event listeners to the element """ handler_id = self.event_handlers_storage.add(handler) func = u"window[{storage_name}][{func_id}]".format( storage_name=escape_js(self.event_handlers_storage.name), func_id=escape_js(handler_id), ) self.tab.evaljs(u"{element}['on' + {event_name}] = {func}".format( element=self.element_js, event_name=escape_js(event_name), func=func )) return handler_id
def __call__(self, *args): expr = "eval('('+{func_text}+')')({args})".format( func_text=self.source, args=escape_js(*args) ) wrapper_script = get_process_errors_js(expr) res = self.tab.evaljs(wrapper_script) if not isinstance(res, dict): raise ScriptError({ 'type': ScriptError.UNKNOWN_ERROR, 'js_error_message': res, 'message': "unknown error during JS function call: " "{!r}; {!r}".format(res, wrapper_script) }) if res.get("error", False): err_message = res.get('errorMessage') err_type = res.get('errorType', '<custom JS error>') err_repr = res.get('errorRepr', '<unknown JS error>') if err_message is None: err_message = err_repr raise ScriptError({ 'type': ScriptError.JS_ERROR, 'js_error_type': err_type, 'js_error_message': err_message, 'js_error': err_repr, 'message': "error during JS function call: " "{!r}".format(err_repr) }) return res.get("result")
def add_event_handler(self, event_name, handler, options=None): """ Add event listeners to the element for the specified event """ handler_id = self.event_handlers_storage.add(handler) func = u"window[{storage_name}][{func_id}]".format( storage_name=escape_js(self.event_handlers_storage.name), func_id=escape_js(handler_id), ) self.tab.evaljs(u"{element}.addEventListener({event_name}, {func}, {options})".format( element=self.element_js, event_name=escape_js(event_name), func=func, options=escape_js(options) )) return handler_id
def fill(self, values): """ Fill the values of the element """ return self.tab.evaljs(u"({fill_form_values_func})({element}, {values}, {set_field_value})".format( fill_form_values_func=FILL_FORM_VALUES_JS, element=self.element_js, values=escape_js(values), set_field_value=SET_FIELD_VALUE_JS ))
def select_all(self, selector): """ Select DOM elements and return a list of instances of `HTMLElement` :param selector valid CSS selector :return list of elements """ js_query = u"document.querySelectorAll({})".format(escape_js(selector)) return self.evaljs(js_query)
def add(self, func): func_id = get_id() event_wrapper = u"window[{storage_name}].add(event)".format( storage_name=escape_js(self.events_storage.name), ) js_func = u"window[{storage_name}][{func_id}] = " \ u"function(event) {{ window[{storage_name}].run({func_id}, {event}, event) }}"\ .format( storage_name=escape_js(self.name), func_id=escape_js(func_id), event=event_wrapper ) escape_and_evaljs(self.parent().web_page.mainFrame(), js_func) self.storage[func_id] = func return func_id
def __init__(self, splash, source): """ :param splash.browser_tab.BrowserTab tab: BrowserTab object :param str source: function source code """ self.lua = splash.lua self.tab = splash.tab self.source = escape_js(source) self.exceptions = splash.exceptions
def form_values(self, values='auto'): """ Return all values of the element if it is a form""" self.assert_node_type('form') return self.tab.evaljs(u"({form_values_func})({element}, {values}, {field_value_func})".format( form_values_func=FORM_VALUES_JS, field_value_func=FIELD_VALUE_JS, values=escape_js(values), element=self.element_js ))
def node_method(self, method_name): """ Return function which calls the specified method of the element """ method_name = escape_js(method_name) @empty_strings_as_none def call(*args): return self.tab.evaljs(u"{element}[{method}]({args})".format( element=self.element_js, method=method_name, args=escape_js_args(*args) )) return call
def select(self, selector): """ Select DOM element and return an instance of `HTMLElement` :param selector valid CSS selector :return element """ js_query = u"document.querySelector({})".format(escape_js(selector)) result = self.evaljs(js_query) if result == "": return None return result
def evaljs(self, js_source, handle_errors=True, result_protection=True): """ Run JS code in page context and return the result. If JavaScript exception or an syntax error happens and `handle_errors` is True then Python JsError exception is raised. When `result_protection` is True (default) protection against badly written or malicious scripts is activated. Disable it when the script result is known to be good, i.e. it only contains objects/arrays/primitives without circular references. """ frame = self.web_page.mainFrame() eval_expr = u"eval({})".format(escape_js(js_source)) if result_protection: eval_expr = get_sanitized_result_js(eval_expr) if not handle_errors: return qt2py(frame.evaluateJavaScript(eval_expr)) res = frame.evaluateJavaScript(get_process_errors_js(eval_expr)) if not isinstance(res, dict): raise JsError({ 'type': ScriptError.UNKNOWN_ERROR, 'js_error_message': res, 'message': "unknown JS error: {!r}".format(res) }) if res.get("error", False): err_message = res.get('errorMessage') err_type = res.get('errorType', '<custom JS error>') err_repr = res.get('errorRepr', '<unknown JS error>') if err_message is None: err_message = err_repr raise JsError({ 'type': ScriptError.JS_ERROR, 'js_error_type': err_type, 'js_error_message': err_message, 'js_error': err_repr, 'message': "JS error: {!r}".format(err_repr) }) return res.get("result", None)
def evaljs(self, js_source, handle_errors=True, result_protection=True, dom_elements=True): """ Run JS code in page context and return the result. If JavaScript exception or an syntax error happens and `handle_errors` is True then Python JsError exception is raised. When `result_protection` is True (default) protection against badly written or malicious scripts is activated. Disable it when the script result is known to be good, i.e. it only contains objects/arrays/primitives without circular references. When `dom_elements` is True (default) top-level DOM elements will be saved in JS field of window object under `self._elements_storage.name` key. The result of evaluation will be object with `type` property and `id` property. In JS the original DOM element can accessed through ``window[self._elements_storage.name][id]``. """ frame = self.web_page.mainFrame() eval_expr = u"eval({})".format(escape_js(js_source)) if dom_elements: self._init_js_objects_storage() eval_expr = store_dom_elements(eval_expr, self._elements_storage.name) if result_protection: eval_expr = get_sanitized_result_js(eval_expr) if handle_errors: res = frame.evaluateJavaScript(get_process_errors_js(eval_expr)) if not isinstance(res, dict): raise JsError({ 'type': ScriptError.UNKNOWN_ERROR, 'js_error_message': res, 'message': "unknown JS error: {!r}".format(res) }) if res.get("error", False): err_message = res.get('errorMessage') err_type = res.get('errorType', '<custom JS error>') err_repr = res.get('errorRepr', '<unknown JS error>') if err_message is None: err_message = err_repr raise JsError({ 'type': ScriptError.JS_ERROR, 'js_error_type': err_type, 'js_error_message': err_message, 'js_error': err_repr, 'message': "JS error: {!r}".format(err_repr) }) result = res.get("result", None) else: result = qt2py(frame.evaluateJavaScript(eval_expr)) return self._process_js_result(result, allow_dom=dom_elements)
def node_property(self, property_name): """ Return value of the specified property of the element """ return self.tab.evaljs(u"{element}[{property}]".format( element=self.element_js, property=escape_js(property_name) ))
def escape_js_args(*args): return ','.join([ arg.element_js if isinstance(arg, HTMLElement) else escape_js(arg) for arg in args ])
def get_node_style(self, property_name): """ Get value of the style property of the element """ return self.tab.evaljs(u"{element}.style[{property}]".format( element=self.element_js, property=escape_js(property_name), ))
def escape_and_evaljs(frame, js_func): eval_expr = u"eval({})".format(escape_js(js_func)) return frame.evaluateJavaScript(get_process_errors_js(eval_expr))
def call(*args): return self.tab.evaljs(u"{element}[{method}]({args})".format( element=self.element_js, method=escape_js(method_name), args=escape_js_args(*args)))
def wait_for_resume(self, js_source, callback, errback, timeout): """ Run some Javascript asynchronously. The JavaScript must contain a method called `main()` that accepts one argument. The first argument will be an object with `resume()` and `error()` methods. The code _must_ call one of these functions before the timeout or else it will be canceled. """ frame = self.web_page.mainFrame() callback_proxy = OneShotCallbackProxy(self, callback, errback, timeout) self._callback_proxies_to_cancel.add(callback_proxy) frame.addToJavaScriptWindowObject(callback_proxy.name, callback_proxy) wrapped = u""" (function () { try { eval(%(script_text)s); } catch (err) { var main = function (splash) { throw err; } } (function () { var sanitize = %(sanitize_func)s; var _result = {}; var _splash = window["%(callback_name)s"]; var splash = { 'error': function (message) { _splash.error(message, false); }, 'resume': function (value) { _result['value'] = value; try { _splash.resume(sanitize(_result)); } catch (err) { _splash.error(err, true); } }, 'set': function (key, value) { _result[key] = value; } }; delete window["%(callback_name)s"]; try { if (typeof main === 'undefined') { throw "wait_for_resume(): no main() function defined"; } main(splash); } catch (err) { _splash.error(err, true); } })(); })();undefined """ % dict( sanitize_func=SANITIZE_FUNC_JS, script_text=escape_js(js_source), callback_name=callback_proxy.name ) def cancel_callback(): callback_proxy.cancel(reason='javascript window object cleared') self.logger.log("wait_for_resume wrapped script:\n%s" % wrapped, min_level=3) frame.javaScriptWindowObjectCleared.connect(cancel_callback) frame.evaluateJavaScript(wrapped)
def wait_for_resume(self, js_source, callback, errback, timeout): """ Run some Javascript asynchronously. The JavaScript must contain a method called `main()` that accepts one argument. The first argument will be an object with `resume()` and `error()` methods. The code _must_ call one of these functions before the timeout or else it will be canceled. """ frame = self.web_page.mainFrame() callback_proxy = OneShotCallbackProxy(self, callback, errback, timeout) self._callback_proxies_to_cancel.add(callback_proxy) frame.addToJavaScriptWindowObject(callback_proxy.name, callback_proxy) wrapped = u""" (function () { try { eval(%(script_text)s); } catch (err) { var main = function (splash) { throw err; } } (function () { var sanitize = %(sanitize_func)s; var _result = {}; var _splash = window["%(callback_name)s"]; var splash = { 'error': function (message) { _splash.error(message, false); }, 'resume': function (value) { _result['value'] = value; try { _splash.resume(sanitize(_result)); } catch (err) { _splash.error(err, true); } }, 'set': function (key, value) { _result[key] = value; } }; delete window["%(callback_name)s"]; try { if (typeof main === 'undefined') { throw "wait_for_resume(): no main() function defined"; } main(splash); } catch (err) { _splash.error(err, true); } })(); })();undefined """ % dict(sanitize_func=SANITIZE_FUNC_JS, script_text=escape_js(js_source), callback_name=callback_proxy.name) def cancel_callback(): callback_proxy.cancel(reason='javascript window object cleared') self.logger.log("wait_for_resume wrapped script:\n%s" % wrapped, min_level=3) frame.javaScriptWindowObjectCleared.connect(cancel_callback) frame.evaluateJavaScript(wrapped)
def call(*args): return self.tab.evaljs(u"{element}[{method}]({args})".format( element=self.element_js, method=escape_js(method_name), args=escape_js_args(*args) ))