From fec9a028f35447c4053d015fa4723fd687de96d0 Mon Sep 17 00:00:00 2001 From: Andreas Tolfsen Date: Wed, 7 Jun 2017 12:46:14 +0100 Subject: [PATCH] Bug 1370850 - Serialise undefined script evaluation return value to null; r=maja_zf When a call through the content frame proxy is interrupted by the dialogueObserver, the synchronous promise that is meant to wait for a response from the frame script is resolved immediately with an undefined return value. When an undefined value is assigned to the response body, it gets dropped during JSON serialisation. To ensure the "value" field expected from the Execute Script and Execute Async Script commands is populated, we need to assign a null value to resp.body.value. We can treat undefined as null by calling evaluate.toJSON again on the return value from the proxied frame script call. This effectively means we serialise it twice, since it first needs to be serialised to cross the IPC border, though the second computation only looks at primitives and no known web element store is required. It would be nicer if the content frame script itself would be able to return early with null by installing a user prompt notification event, but this is not possible because the tabmodal dialogue that appears blocks script execution. This means we need to rely on the dialogueObserver in testing/marionette/proxy.js to take care of the dialogue for us. MozReview-Commit-ID: D14TA2TYYXI --HG-- extra : rebase_source : 5fc17a1f0786e2fc9a2d054ef7c1f8b0e3336906 --- testing/marionette/driver.js | 10 ++++--- .../tests/unit/test_execute_script.py | 27 ++++++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/testing/marionette/driver.js b/testing/marionette/driver.js index 3afd98dae880..d323ce5586f8 100644 --- a/testing/marionette/driver.js +++ b/testing/marionette/driver.js @@ -871,11 +871,13 @@ GeckoDriver.prototype.execute_ = function (script, args, timeout, opts = {}) { case Context.CONTENT: // evaluate in content with lasting side-effects if (!opts.sandboxName) { - return this.listener.execute(script, args, timeout, opts); + return this.listener.execute(script, args, timeout, opts) + .then(evaluate.toJSON); // evaluate in content with sandbox } else { - return this.listener.executeInSandbox(script, args, timeout, opts); + return this.listener.executeInSandbox(script, args, timeout, opts) + .then(evaluate.toJSON); } case Context.CHROME: @@ -887,8 +889,8 @@ GeckoDriver.prototype.execute_ = function (script, args, timeout, opts = {}) { opts.timeout = timeout; let wargs = evaluate.fromJSON(args, this.curBrowser.seenEls, sb.window); - let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts); - return evaluatePromise.then(res => evaluate.toJSON(res, this.curBrowser.seenEls)); + return evaluate.sandbox(sb, script, wargs, opts) + .then(res => evaluate.toJSON(res, this.curBrowser.seenEls)); } }; diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py index 74674a798fbb..406f2be81ac6 100644 --- a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py @@ -6,7 +6,7 @@ import os import urllib from marionette_driver import By, errors -from marionette_driver.marionette import HTMLElement +from marionette_driver.marionette import Alert, HTMLElement from marionette_driver.wait import Wait from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin @@ -31,6 +31,23 @@ globals = set([ class TestExecuteContent(MarionetteTestCase): + def alert_present(self): + try: + Alert(self.marionette).text + return True + except errors.NoAlertPresentException: + return False + + def wait_for_alert_closed(self, timeout=None): + Wait(self.marionette, timeout=timeout).until( + lambda _: not self.alert_present()) + + def tearDown(self): + if self.alert_present(): + alert = self.marionette.switch_to_alert() + alert.dismiss() + self.wait_for_alert_closed() + def assert_is_defined(self, property, sandbox="default"): self.assertTrue(self.marionette.execute_script( "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox), @@ -340,6 +357,11 @@ class TestExecuteContent(MarionetteTestCase): sandbox=None) self.assert_is_web_element(el) + def test_return_value_on_alert(self): + res = self.marionette._send_message("executeScript", {"script": "alert()"}) + self.assertIn("value", res) + self.assertIsNone(res["value"]) + class TestExecuteChrome(WindowManagerMixin, TestExecuteContent): @@ -408,6 +430,9 @@ class TestExecuteChrome(WindowManagerMixin, TestExecuteContent): def test_access_chrome_objects_in_event_listeners(self): pass + def test_return_value_on_alert(self): + pass + class TestElementCollections(MarionetteTestCase):