tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_execute_script.py (19954B)


      1 import os
      2 import sys
      3 from urllib.parse import quote
      4 
      5 from marionette_driver import By, errors
      6 from marionette_driver.marionette import Alert, WebElement
      7 from marionette_driver.wait import Wait
      8 from marionette_harness import MarionetteTestCase, WindowManagerMixin
      9 
     10 # add this directory to the path
     11 sys.path.append(os.path.dirname(__file__))
     12 
     13 from chrome_handler_mixin import ChromeHandlerMixin
     14 
     15 
     16 def inline(doc):
     17    return "data:text/html;charset=utf-8,{}".format(quote(doc))
     18 
     19 
     20 elements = inline("<p>foo</p> <p>bar</p>")
     21 
     22 shadow_dom = """
     23    <style>
     24        custom-checkbox-element {
     25            display:block; width:20px; height:20px;
     26        }
     27    </style>
     28    <custom-checkbox-element></custom-checkbox-element>
     29    <script>
     30        customElements.define('custom-checkbox-element',
     31            class extends HTMLElement {
     32                constructor() {
     33                        super();
     34                        this.attachShadow({mode: '%s'}).innerHTML = `
     35                            <div><input type="checkbox"/></div>
     36                        `;
     37                    }
     38            });
     39    </script>"""
     40 
     41 
     42 globals = set(
     43    [
     44        "atob",
     45        "Audio",
     46        "btoa",
     47        "document",
     48        "navigator",
     49        "URL",
     50        "window",
     51    ]
     52 )
     53 
     54 
     55 class TestExecuteContent(MarionetteTestCase):
     56    def alert_present(self):
     57        try:
     58            Alert(self.marionette).text
     59            return True
     60        except errors.NoAlertPresentException:
     61            return False
     62 
     63    def wait_for_alert_closed(self, timeout=None):
     64        Wait(self.marionette, timeout=timeout).until(lambda _: not self.alert_present())
     65 
     66    def tearDown(self):
     67        if self.alert_present():
     68            alert = self.marionette.switch_to_alert()
     69            alert.dismiss()
     70            self.wait_for_alert_closed()
     71 
     72    def assert_is_defined(self, property, sandbox="default"):
     73        self.assertTrue(
     74            self.marionette.execute_script(
     75                "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox
     76            ),
     77            "property {} is undefined".format(property),
     78        )
     79 
     80    def assert_is_web_element(self, element):
     81        self.assertIsInstance(element, WebElement)
     82 
     83    def test_return_number(self):
     84        self.assertEqual(1, self.marionette.execute_script("return 1"))
     85        self.assertEqual(1.5, self.marionette.execute_script("return 1.5"))
     86 
     87    def test_return_boolean(self):
     88        self.assertTrue(self.marionette.execute_script("return true"))
     89 
     90    def test_return_string(self):
     91        self.assertEqual("foo", self.marionette.execute_script("return 'foo'"))
     92 
     93    def test_return_array(self):
     94        self.assertEqual([1, 2], self.marionette.execute_script("return [1, 2]"))
     95        self.assertEqual(
     96            [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]")
     97        )
     98        self.assertEqual(
     99            [True, False], self.marionette.execute_script("return [true, false]")
    100        )
    101        self.assertEqual(
    102            ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']")
    103        )
    104        self.assertEqual(
    105            [1, 1.5, True, "foo"],
    106            self.marionette.execute_script("return [1, 1.5, true, 'foo']"),
    107        )
    108        self.assertEqual([1, [2]], self.marionette.execute_script("return [1, [2]]"))
    109 
    110    def test_return_object(self):
    111        self.assertEqual({"foo": 1}, self.marionette.execute_script("return {foo: 1}"))
    112        self.assertEqual(
    113            {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}")
    114        )
    115        self.assertEqual(
    116            {"foo": True}, self.marionette.execute_script("return {foo: true}")
    117        )
    118        self.assertEqual(
    119            {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}")
    120        )
    121        self.assertEqual(
    122            {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}")
    123        )
    124        self.assertEqual(
    125            {"foo": {"bar": [1, 2]}},
    126            self.marionette.execute_script("return {foo: {bar: [1, 2]}}"),
    127        )
    128 
    129    def test_no_return_value(self):
    130        self.assertIsNone(self.marionette.execute_script("true"))
    131 
    132    def test_argument_null(self):
    133        self.assertIsNone(
    134            self.marionette.execute_script(
    135                "return arguments[0]", script_args=(None,), sandbox="default"
    136            )
    137        )
    138        self.assertIsNone(
    139            self.marionette.execute_script(
    140                "return arguments[0]", script_args=(None,), sandbox="system"
    141            )
    142        )
    143        self.assertIsNone(
    144            self.marionette.execute_script(
    145                "return arguments[0]", script_args=(None,), sandbox=None
    146            )
    147        )
    148 
    149    def test_argument_number(self):
    150        self.assertEqual(1, self.marionette.execute_script("return arguments[0]", (1,)))
    151        self.assertEqual(
    152            1.5, self.marionette.execute_script("return arguments[0]", (1.5,))
    153        )
    154 
    155    def test_argument_boolean(self):
    156        self.assertTrue(self.marionette.execute_script("return arguments[0]", (True,)))
    157 
    158    def test_argument_string(self):
    159        self.assertEqual(
    160            "foo", self.marionette.execute_script("return arguments[0]", ("foo",))
    161        )
    162 
    163    def test_argument_array(self):
    164        self.assertEqual(
    165            [1, 2], self.marionette.execute_script("return arguments[0]", ([1, 2],))
    166        )
    167 
    168    def test_argument_object(self):
    169        self.assertEqual(
    170            {"foo": 1},
    171            self.marionette.execute_script("return arguments[0]", ({"foo": 1},)),
    172        )
    173 
    174    def test_argument_shadow_root(self):
    175        self.marionette.navigate(inline(shadow_dom % "open"))
    176        elem = self.marionette.find_element(By.TAG_NAME, "custom-checkbox-element")
    177        shadow_root = elem.shadow_root
    178        nodeType = self.marionette.execute_script(
    179            "return arguments[0].nodeType", script_args=(shadow_root,)
    180        )
    181        self.assertEqual(nodeType, 11)
    182 
    183    def test_argument_web_element(self):
    184        self.marionette.navigate(elements)
    185        elem = self.marionette.find_element(By.TAG_NAME, "p")
    186        nodeType = self.marionette.execute_script(
    187            "return arguments[0].nodeType", script_args=(elem,)
    188        )
    189        self.assertEqual(nodeType, 1)
    190 
    191    def test_default_sandbox_globals(self):
    192        for property in globals:
    193            self.assert_is_defined(property, sandbox="default")
    194 
    195        self.assert_is_defined("Components")
    196        self.assert_is_defined("window.wrappedJSObject")
    197 
    198    def test_system_globals(self):
    199        for property in globals:
    200            self.assert_is_defined(property, sandbox="system")
    201 
    202        self.assert_is_defined("Components", sandbox="system")
    203        self.assert_is_defined("window.wrappedJSObject", sandbox="system")
    204 
    205    def test_mutable_sandbox_globals(self):
    206        for property in globals:
    207            self.assert_is_defined(property, sandbox=None)
    208 
    209        # Components is there, but will be removed soon
    210        self.assert_is_defined("Components", sandbox=None)
    211        # wrappedJSObject is always there in sandboxes
    212        self.assert_is_defined("window.wrappedJSObject", sandbox=None)
    213 
    214    def test_exception(self):
    215        self.assertRaises(
    216            errors.JavascriptException, self.marionette.execute_script, "return foo"
    217        )
    218 
    219    def test_stacktrace(self):
    220        with self.assertRaises(errors.JavascriptException) as cm:
    221            self.marionette.execute_script("return b")
    222 
    223        # by default execute_script pass the name of the python file
    224        self.assertIn(
    225            os.path.relpath(__file__.replace(".pyc", ".py")), cm.exception.stacktrace
    226        )
    227        self.assertIn("b is not defined", str(cm.exception))
    228 
    229    def test_syntaxerror_stack(self):
    230        with self.assertRaises(errors.JavascriptException) as cm:
    231            self.marionette.execute_script("notAFunc() {")
    232 
    233        # by default execute_script pass the name of the python file
    234        self.assertIn(
    235            os.path.relpath(__file__.replace(".pyc", ".py")), cm.exception.stacktrace
    236        )
    237        self.assertIn("SyntaxError: unexpected token: '{'", str(cm.exception))
    238 
    239    def test_permission(self):
    240        for sandbox in ["default", None]:
    241            with self.assertRaises(errors.JavascriptException):
    242                self.marionette.execute_script(
    243                    "Components.classes['@mozilla.org/preferences-service;1']"
    244                )
    245 
    246    def test_return_web_element(self):
    247        self.marionette.navigate(elements)
    248        expected = self.marionette.find_element(By.TAG_NAME, "p")
    249        actual = self.marionette.execute_script("return document.querySelector('p')")
    250        self.assertEqual(expected, actual)
    251 
    252    def test_return_web_element_array(self):
    253        self.marionette.navigate(elements)
    254        expected = self.marionette.find_elements(By.TAG_NAME, "p")
    255        actual = self.marionette.execute_script(
    256            """
    257            let els = document.querySelectorAll('p')
    258            return [els[0], els[1]]"""
    259        )
    260        self.assertEqual(expected, actual)
    261 
    262    def test_return_web_element_nested_array(self):
    263        self.marionette.navigate(elements)
    264        expected = self.marionette.find_elements(By.TAG_NAME, "p")
    265        actual = self.marionette.execute_script(
    266            """
    267            let els = document.querySelectorAll('p')
    268            return { els: [els[0], els[1]] }"""
    269        )
    270        self.assertEqual(expected, actual["els"])
    271 
    272    def test_return_web_element_nested_dict(self):
    273        self.marionette.navigate(elements)
    274        expected = self.marionette.find_element(By.TAG_NAME, "p")
    275        actual = self.marionette.execute_script(
    276            """
    277            let el = document.querySelector('p')
    278            return { path: { to: { el } } }"""
    279        )
    280        self.assertEqual(expected, actual["path"]["to"]["el"])
    281 
    282    # Bug 938228 identifies a problem with unmarshaling NodeList
    283    # objects from the DOM.  document.querySelectorAll returns this
    284    # construct.
    285    def test_return_web_element_nodelist(self):
    286        self.marionette.navigate(elements)
    287        expected = self.marionette.find_elements(By.TAG_NAME, "p")
    288        actual = self.marionette.execute_script("return document.querySelectorAll('p')")
    289        self.assertEqual(expected, actual)
    290 
    291    def test_sandbox_reuse(self):
    292        # Sandboxes between `execute_script()` invocations are shared.
    293        self.marionette.execute_script("this.foobar = [23, 42];")
    294        self.assertEqual(
    295            self.marionette.execute_script("return this.foobar;", new_sandbox=False),
    296            [23, 42],
    297        )
    298 
    299    def test_sandbox_refresh_arguments(self):
    300        self.marionette.execute_script(
    301            "this.foobar = [arguments[0], arguments[1]]", [23, 42]
    302        )
    303        self.assertEqual(
    304            self.marionette.execute_script("return this.foobar", new_sandbox=False),
    305            [23, 42],
    306        )
    307 
    308    def test_mutable_sandbox_wrappedjsobject(self):
    309        self.assert_is_defined("window.wrappedJSObject")
    310        with self.assertRaises(errors.JavascriptException):
    311            self.marionette.execute_script(
    312                "window.wrappedJSObject.foo = 1", sandbox=None
    313            )
    314 
    315    def test_default_sandbox_wrappedjsobject(self):
    316        self.assert_is_defined("window.wrappedJSObject", sandbox="default")
    317 
    318        try:
    319            self.marionette.execute_script(
    320                "window.wrappedJSObject.foo = 4", sandbox="default"
    321            )
    322            self.assertEqual(
    323                self.marionette.execute_script(
    324                    "return window.wrappedJSObject.foo", sandbox="default"
    325                ),
    326                4,
    327            )
    328        finally:
    329            self.marionette.execute_script(
    330                "delete window.wrappedJSObject.foo", sandbox="default"
    331            )
    332 
    333    def test_system_sandbox_wrappedjsobject(self):
    334        self.assert_is_defined("window.wrappedJSObject", sandbox="system")
    335 
    336        self.marionette.execute_script(
    337            "window.wrappedJSObject.foo = 4", sandbox="system"
    338        )
    339        self.assertEqual(
    340            self.marionette.execute_script(
    341                "return window.wrappedJSObject.foo", sandbox="system"
    342            ),
    343            4,
    344        )
    345 
    346    def test_system_dead_object(self):
    347        self.assert_is_defined("window.wrappedJSObject", sandbox="system")
    348 
    349        self.marionette.execute_script(
    350            "window.wrappedJSObject.foo = function() { return 'yo' }", sandbox="system"
    351        )
    352        self.marionette.execute_script(
    353            "dump(window.wrappedJSObject.foo)", sandbox="system"
    354        )
    355 
    356        self.marionette.execute_script(
    357            "window.wrappedJSObject.foo = function() { return 'yolo' }",
    358            sandbox="system",
    359        )
    360        typ = self.marionette.execute_script(
    361            "return typeof window.wrappedJSObject.foo", sandbox="system"
    362        )
    363        self.assertEqual("function", typ)
    364        obj = self.marionette.execute_script(
    365            "return window.wrappedJSObject.foo.toString()", sandbox="system"
    366        )
    367        self.assertIn("yolo", obj)
    368 
    369    def test_lasting_side_effects(self):
    370        def send(script):
    371            return self.marionette._send_message(
    372                "WebDriver:ExecuteScript", {"script": script}, key="value"
    373            )
    374 
    375        send("window.foo = 1")
    376        foo = send("return window.foo")
    377        self.assertEqual(1, foo)
    378 
    379        for property in globals:
    380            exists = send("return typeof {} != 'undefined'".format(property))
    381            self.assertTrue(exists, "property {} is undefined".format(property))
    382 
    383        self.assertTrue(
    384            send(
    385                """
    386            return (typeof Components == 'undefined') ||
    387                (typeof Components.utils == 'undefined')
    388        """
    389            )
    390        )
    391        self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'"))
    392 
    393    def test_no_callback(self):
    394        self.assertTrue(
    395            self.marionette.execute_script("return typeof arguments[0] == 'undefined'")
    396        )
    397 
    398    def test_window_set_timeout_is_not_cancelled(self):
    399        def content_timeout_triggered(mn):
    400            return mn.execute_script("return window.n", sandbox=None) > 0
    401 
    402        # subsequent call to execute_script after this
    403        # should not cancel the setTimeout event
    404        self.marionette.navigate(
    405            inline(
    406                """
    407            <script>
    408            window.n = 0;
    409            setTimeout(() => ++window.n, 4000);
    410            </script>"""
    411            )
    412        )
    413 
    414        # as debug builds are inherently slow,
    415        # we need to assert the event did not already fire
    416        self.assertEqual(
    417            0,
    418            self.marionette.execute_script("return window.n", sandbox=None),
    419            "setTimeout already fired",
    420        )
    421 
    422        # if event was cancelled, this will time out
    423        Wait(self.marionette, timeout=8).until(
    424            content_timeout_triggered,
    425            message="Scheduled setTimeout event was cancelled by call to execute_script",
    426        )
    427 
    428    def test_access_chrome_objects_in_event_listeners(self):
    429        # sandbox.window.addEventListener/removeEventListener
    430        # is used by Marionette for installing the unloadHandler which
    431        # is used to return an error when a document is unloaded during
    432        # script execution.
    433        #
    434        # Certain web frameworks, notably Angular, override
    435        # window.addEventListener/removeEventListener and introspects
    436        # objects passed to them.  If these objects originates from chrome
    437        # without having been cloned, a permission denied error is thrown
    438        # as part of the security precautions put in place by the sandbox.
    439 
    440        # addEventListener is called when script is injected
    441        self.marionette.navigate(
    442            inline(
    443                """
    444            <script>
    445            window.addEventListener = (event, listener) => listener.toString();
    446            </script>
    447            """
    448            )
    449        )
    450        self.marionette.execute_script("", sandbox=None)
    451 
    452        # removeEventListener is called when sandbox is unloaded
    453        self.marionette.navigate(
    454            inline(
    455                """
    456            <script>
    457            window.removeEventListener = (event, listener) => listener.toString();
    458            </script>
    459            """
    460            )
    461        )
    462        self.marionette.execute_script("", sandbox=None)
    463 
    464    def test_access_global_objects_from_chrome(self):
    465        # test inspection of arguments
    466        self.marionette.execute_script("__webDriverArguments.toString()")
    467 
    468    def test_toJSON(self):
    469        foo = self.marionette.execute_script(
    470            """
    471            return {
    472              toJSON () {
    473                return "foo";
    474              }
    475            }
    476        """,
    477            sandbox=None,
    478        )
    479        self.assertEqual("foo", foo)
    480 
    481    def test_unsafe_toJSON(self):
    482        el = self.marionette.execute_script(
    483            """
    484            return {
    485              toJSON () {
    486                return document.documentElement;
    487              }
    488            }
    489        """,
    490            sandbox=None,
    491        )
    492        self.assert_is_web_element(el)
    493        self.assertEqual(el, self.marionette.find_element(By.CSS_SELECTOR, ":root"))
    494 
    495    def test_comment_in_last_line(self):
    496        self.marionette.execute_script(" // comment ")
    497 
    498    def test_return_value_on_alert(self):
    499        res = self.marionette.execute_script("alert()")
    500        self.assertIsNone(res)
    501 
    502 
    503 class TestExecuteChrome(ChromeHandlerMixin, WindowManagerMixin, TestExecuteContent):
    504    def setUp(self):
    505        super(TestExecuteChrome, self).setUp()
    506 
    507        self.marionette.set_context("chrome")
    508        win = self.open_chrome_window(self.chrome_base_url + "test.xhtml")
    509        self.marionette.switch_to_window(win)
    510 
    511    def tearDown(self):
    512        self.close_all_windows()
    513 
    514        super(TestExecuteChrome, self).tearDown()
    515 
    516    def test_permission(self):
    517        self.marionette.execute_script(
    518            "Components.classes['@mozilla.org/preferences-service;1']"
    519        )
    520 
    521    def test_unmarshal_element_collection(self):
    522        expected = self.marionette.find_elements(By.TAG_NAME, "input")
    523        actual = self.marionette.execute_script(
    524            "return document.querySelectorAll('input')"
    525        )
    526        self.assertTrue(len(expected) > 0)
    527        self.assertEqual(expected, actual)
    528 
    529    def test_argument_shadow_root(self):
    530        pass
    531 
    532    def test_argument_web_element(self):
    533        elem = self.marionette.find_element(By.TAG_NAME, "input")
    534        nodeType = self.marionette.execute_script(
    535            "return arguments[0].nodeType", script_args=(elem,)
    536        )
    537        self.assertEqual(nodeType, 1)
    538 
    539    def test_async_script_timeout(self):
    540        with self.assertRaises(errors.ScriptTimeoutException):
    541            self.marionette.execute_async_script(
    542                """
    543                var cb = arguments[arguments.length - 1];
    544                setTimeout(function() { cb() }, 2500);
    545                """,
    546                script_timeout=100,
    547            )
    548 
    549    def test_lasting_side_effects(self):
    550        pass
    551 
    552    def test_return_web_element(self):
    553        pass
    554 
    555    def test_return_web_element_array(self):
    556        pass
    557 
    558    def test_return_web_element_nested_array(self):
    559        pass
    560 
    561    def test_return_web_element_nested_dict(self):
    562        pass
    563 
    564    def test_return_web_element_nodelist(self):
    565        pass
    566 
    567    def test_window_set_timeout_is_not_cancelled(self):
    568        pass
    569 
    570    def test_mutable_sandbox_wrappedjsobject(self):
    571        pass
    572 
    573    def test_default_sandbox_wrappedjsobject(self):
    574        pass
    575 
    576    def test_system_sandbox_wrappedjsobject(self):
    577        pass
    578 
    579    def test_access_chrome_objects_in_event_listeners(self):
    580        pass
    581 
    582    def test_return_value_on_alert(self):
    583        pass
    584 
    585    def test_syntaxerror_stack(self):
    586        pass