1from __future__ import absolute_import
2
3import os
4import urllib
5
6from marionette_driver import By, errors
7from marionette_driver.marionette import Alert, HTMLElement
8from marionette_driver.wait import Wait
9
10from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin
11
12
13def inline(doc):
14    return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc))
15
16
17elements = inline("<p>foo</p> <p>bar</p>")
18
19globals = set([
20              "atob",
21              "Audio",
22              "btoa",
23              "document",
24              "navigator",
25              "URL",
26              "window",
27              ])
28
29
30class TestExecuteContent(MarionetteTestCase):
31
32    def alert_present(self):
33        try:
34            Alert(self.marionette).text
35            return True
36        except errors.NoAlertPresentException:
37            return False
38
39    def wait_for_alert_closed(self, timeout=None):
40        Wait(self.marionette, timeout=timeout).until(
41            lambda _: not self.alert_present())
42
43    def tearDown(self):
44        if self.alert_present():
45            alert = self.marionette.switch_to_alert()
46            alert.dismiss()
47            self.wait_for_alert_closed()
48
49    def assert_is_defined(self, property, sandbox="default"):
50        self.assertTrue(self.marionette.execute_script(
51            "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox),
52            "property {} is undefined".format(property))
53
54    def assert_is_web_element(self, element):
55        self.assertIsInstance(element, HTMLElement)
56
57    def test_return_number(self):
58        self.assertEqual(1, self.marionette.execute_script("return 1"))
59        self.assertEqual(1.5, self.marionette.execute_script("return 1.5"))
60
61    def test_return_boolean(self):
62        self.assertTrue(self.marionette.execute_script("return true"))
63
64    def test_return_string(self):
65        self.assertEqual("foo", self.marionette.execute_script("return 'foo'"))
66
67    def test_return_array(self):
68        self.assertEqual(
69            [1, 2], self.marionette.execute_script("return [1, 2]"))
70        self.assertEqual(
71            [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]"))
72        self.assertEqual(
73            [True, False], self.marionette.execute_script("return [true, false]"))
74        self.assertEqual(
75            ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']"))
76        self.assertEqual(
77            [1, 1.5, True, "foo"], self.marionette.execute_script("return [1, 1.5, true, 'foo']"))
78        self.assertEqual(
79            [1, [2]], self.marionette.execute_script("return [1, [2]]"))
80
81    def test_return_object(self):
82        self.assertEqual(
83            {"foo": 1}, self.marionette.execute_script("return {foo: 1}"))
84        self.assertEqual(
85            {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}"))
86        self.assertEqual(
87            {"foo": True}, self.marionette.execute_script("return {foo: true}"))
88        self.assertEqual(
89            {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}"))
90        self.assertEqual(
91            {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}"))
92        self.assertEqual(
93            {"foo": {"bar": [1, 2]}},
94            self.marionette.execute_script("return {foo: {bar: [1, 2]}}"))
95
96    def test_no_return_value(self):
97        self.assertIsNone(self.marionette.execute_script("true"))
98
99    def test_argument_null(self):
100        self.assertIsNone(self.marionette.execute_script(
101            "return arguments[0]",
102            script_args=(None,),
103            sandbox="default"))
104        self.assertIsNone(self.marionette.execute_script(
105            "return arguments[0]",
106            script_args=(None,),
107            sandbox="system"))
108        self.assertIsNone(self.marionette.execute_script(
109            "return arguments[0]",
110            script_args=(None,),
111            sandbox=None))
112
113    def test_argument_number(self):
114        self.assertEqual(
115            1, self.marionette.execute_script("return arguments[0]", (1,)))
116        self.assertEqual(
117            1.5, self.marionette.execute_script("return arguments[0]", (1.5,)))
118
119    def test_argument_boolean(self):
120        self.assertTrue(self.marionette.execute_script("return arguments[0]", (True,)))
121
122    def test_argument_string(self):
123        self.assertEqual(
124            "foo", self.marionette.execute_script("return arguments[0]", ("foo",)))
125
126    def test_argument_array(self):
127        self.assertEqual(
128            [1, 2], self.marionette.execute_script("return arguments[0]", ([1, 2],)))
129
130    def test_argument_object(self):
131        self.assertEqual({"foo": 1}, self.marionette.execute_script(
132            "return arguments[0]", ({"foo": 1},)))
133
134    def test_default_sandbox_globals(self):
135        for property in globals:
136            self.assert_is_defined(property, sandbox="default")
137
138        self.assert_is_defined("Components")
139        self.assert_is_defined("window.wrappedJSObject")
140
141    def test_system_globals(self):
142        for property in globals:
143            self.assert_is_defined(property, sandbox="system")
144
145        self.assert_is_defined("Components", sandbox="system")
146        self.assert_is_defined("window.wrappedJSObject", sandbox="system")
147
148    def test_mutable_sandbox_globals(self):
149        for property in globals:
150            self.assert_is_defined(property, sandbox=None)
151
152        # Components is there, but will be removed soon
153        self.assert_is_defined("Components", sandbox=None)
154        # wrappedJSObject is always there in sandboxes
155        self.assert_is_defined("window.wrappedJSObject", sandbox=None)
156
157    def test_exception(self):
158        self.assertRaises(errors.JavascriptException,
159                          self.marionette.execute_script, "return foo")
160
161    def test_stacktrace(self):
162        with self.assertRaises(errors.JavascriptException) as cm:
163            self.marionette.execute_script("return b")
164
165        # by default execute_script pass the name of the python file
166        self.assertIn(os.path.basename(__file__.replace(".pyc", ".py")),
167                      cm.exception.stacktrace)
168        self.assertIn("b is not defined", cm.exception.message)
169        self.assertIn("return b", cm.exception.stacktrace)
170
171    def test_permission(self):
172        for sandbox in ["default", None]:
173            with self.assertRaises(errors.JavascriptException):
174               self.marionette.execute_script(
175                    "Components.classes['@mozilla.org/preferences-service;1']")
176
177    def test_return_web_element(self):
178        self.marionette.navigate(elements)
179        expected = self.marionette.find_element(By.TAG_NAME, "p")
180        actual = self.marionette.execute_script(
181            "return document.querySelector('p')")
182        self.assertEqual(expected, actual)
183
184    def test_return_web_element_array(self):
185        self.marionette.navigate(elements)
186        expected = self.marionette.find_elements(By.TAG_NAME, "p")
187        actual = self.marionette.execute_script("""
188            let els = document.querySelectorAll('p')
189            return [els[0], els[1]]""")
190        self.assertEqual(expected, actual)
191
192    # Bug 938228 identifies a problem with unmarshaling NodeList
193    # objects from the DOM.  document.querySelectorAll returns this
194    # construct.
195    def test_return_web_element_nodelist(self):
196        self.marionette.navigate(elements)
197        expected = self.marionette.find_elements(By.TAG_NAME, "p")
198        actual = self.marionette.execute_script(
199            "return document.querySelectorAll('p')")
200        self.assertEqual(expected, actual)
201
202    def test_sandbox_reuse(self):
203        # Sandboxes between `execute_script()` invocations are shared.
204        self.marionette.execute_script("this.foobar = [23, 42];")
205        self.assertEqual(self.marionette.execute_script(
206            "return this.foobar;", new_sandbox=False), [23, 42])
207
208    def test_sandbox_refresh_arguments(self):
209        self.marionette.execute_script(
210            "this.foobar = [arguments[0], arguments[1]]", [23, 42])
211        self.assertEqual(self.marionette.execute_script(
212            "return this.foobar", new_sandbox=False), [23, 42])
213
214    def test_mutable_sandbox_wrappedjsobject(self):
215        self.assert_is_defined("window.wrappedJSObject")
216        with self.assertRaises(errors.JavascriptException):
217            self.marionette.execute_script("window.wrappedJSObject.foo = 1", sandbox=None)
218
219    def test_default_sandbox_wrappedjsobject(self):
220        self.assert_is_defined("window.wrappedJSObject", sandbox="default")
221
222        try:
223            self.marionette.execute_script(
224                "window.wrappedJSObject.foo = 4", sandbox="default")
225            self.assertEqual(self.marionette.execute_script(
226                "return window.wrappedJSObject.foo", sandbox="default"), 4)
227        finally:
228            self.marionette.execute_script(
229                "delete window.wrappedJSObject.foo", sandbox="default")
230
231    def test_system_sandbox_wrappedjsobject(self):
232        self.assert_is_defined("window.wrappedJSObject", sandbox="system")
233
234        self.marionette.execute_script(
235            "window.wrappedJSObject.foo = 4", sandbox="system")
236        self.assertEqual(self.marionette.execute_script(
237            "return window.wrappedJSObject.foo", sandbox="system"), 4)
238
239    def test_system_dead_object(self):
240        self.assert_is_defined("window.wrappedJSObject", sandbox="system")
241
242        self.marionette.execute_script(
243            "window.wrappedJSObject.foo = function() { return 'yo' }",
244            sandbox="system")
245        self.marionette.execute_script(
246            "dump(window.wrappedJSObject.foo)", sandbox="system")
247
248        self.marionette.execute_script(
249            "window.wrappedJSObject.foo = function() { return 'yolo' }",
250            sandbox="system")
251        typ = self.marionette.execute_script(
252            "return typeof window.wrappedJSObject.foo", sandbox="system")
253        self.assertEqual("function", typ)
254        obj = self.marionette.execute_script(
255            "return window.wrappedJSObject.foo.toString()", sandbox="system")
256        self.assertIn("yolo", obj)
257
258    def test_lasting_side_effects(self):
259        def send(script):
260            return self.marionette._send_message(
261                "executeScript", {"script": script}, key="value")
262
263        send("window.foo = 1")
264        foo = send("return window.foo")
265        self.assertEqual(1, foo)
266
267        for property in globals:
268            exists = send("return typeof {} != 'undefined'".format(property))
269            self.assertTrue(exists, "property {} is undefined".format(property))
270
271        self.assertTrue(send("return typeof Components.utils == 'undefined'"))
272        self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'"))
273
274    def test_no_callback(self):
275        self.assertTrue(self.marionette.execute_script(
276            "return typeof arguments[0] == 'undefined'"))
277
278    @skip_if_mobile("Intermittent on Android - bug 1334035")
279    def test_window_set_timeout_is_not_cancelled(self):
280        def content_timeout_triggered(mn):
281            return mn.execute_script("return window.n", sandbox=None) > 0
282
283        # subsequent call to execute_script after this
284        # should not cancel the setTimeout event
285        self.marionette.navigate(inline("""
286            <script>
287            window.n = 0;
288            setTimeout(() => ++window.n, 4000);
289            </script>"""))
290
291        # as debug builds are inherently slow,
292        # we need to assert the event did not already fire
293        self.assertEqual(0, self.marionette.execute_script(
294            "return window.n", sandbox=None),
295            "setTimeout already fired")
296
297        # if event was cancelled, this will time out
298        Wait(self.marionette, timeout=8).until(
299            content_timeout_triggered,
300            message="Scheduled setTimeout event was cancelled by call to execute_script")
301
302    def test_access_chrome_objects_in_event_listeners(self):
303        # sandbox.window.addEventListener/removeEventListener
304        # is used by Marionette for installing the unloadHandler which
305        # is used to return an error when a document is unloaded during
306        # script execution.
307        #
308        # Certain web frameworks, notably Angular, override
309        # window.addEventListener/removeEventListener and introspects
310        # objects passed to them.  If these objects originates from chrome
311        # without having been cloned, a permission denied error is thrown
312        # as part of the security precautions put in place by the sandbox.
313
314        # addEventListener is called when script is injected
315        self.marionette.navigate(inline("""
316            <script>
317            window.addEventListener = (event, listener) => listener.toString();
318            </script>
319            """))
320        self.marionette.execute_script("", sandbox=None)
321
322        # removeEventListener is called when sandbox is unloaded
323        self.marionette.navigate(inline("""
324            <script>
325            window.removeEventListener = (event, listener) => listener.toString();
326            </script>
327            """))
328        self.marionette.execute_script("", sandbox=None)
329
330    def test_access_global_objects_from_chrome(self):
331        # test inspection of arguments
332        self.marionette.execute_script("__webDriverArguments.toString()")
333
334    def test_toJSON(self):
335        foo = self.marionette.execute_script("""
336            return {
337              toJSON () {
338                return "foo";
339              }
340            }""",
341            sandbox=None)
342        self.assertEqual("foo", foo)
343
344    def test_unsafe_toJSON(self):
345        el = self.marionette.execute_script("""
346            return {
347              toJSON () {
348                return document.documentElement;
349              }
350            }""",
351            sandbox=None)
352        self.assert_is_web_element(el)
353
354    @skip_if_mobile("Modal dialogs not supported in Fennec")
355    def test_return_value_on_alert(self):
356        res = self.marionette._send_message("executeScript", {"script": "alert()"})
357        self.assertIn("value", res)
358        self.assertIsNone(res["value"])
359
360
361class TestExecuteChrome(WindowManagerMixin, TestExecuteContent):
362
363    def setUp(self):
364        super(TestExecuteChrome, self).setUp()
365
366        self.marionette.set_context("chrome")
367
368    def tearDown(self):
369        super(TestExecuteChrome, self).tearDown()
370
371    def test_permission(self):
372        self.marionette.execute_script(
373            "Components.classes['@mozilla.org/preferences-service;1']")
374
375    @skip_if_mobile("New windows not supported in Fennec")
376    def test_unmarshal_element_collection(self):
377
378        def open_window_with_js():
379            self.marionette.execute_script(
380                "window.open('chrome://marionette/content/test.xul', 'xul', 'chrome');")
381
382        try:
383            win = self.open_window(trigger=open_window_with_js)
384            self.marionette.switch_to_window(win)
385
386            expected = self.marionette.find_elements(By.TAG_NAME, "textbox")
387            actual = self.marionette.execute_script(
388                "return document.querySelectorAll('textbox')")
389            self.assertEqual(expected, actual)
390
391        finally:
392            self.close_all_windows()
393
394    def test_async_script_timeout(self):
395        with self.assertRaises(errors.ScriptTimeoutException):
396            self.marionette.execute_async_script("""
397                var cb = arguments[arguments.length - 1];
398                setTimeout(function() { cb() }, 2500);
399                """, script_timeout=100)
400
401    def test_lasting_side_effects(self):
402        pass
403
404    def test_return_web_element(self):
405        pass
406
407    def test_return_web_element_array(self):
408        pass
409
410    def test_return_web_element_nodelist(self):
411        pass
412
413    def test_window_set_timeout_is_not_cancelled(self):
414        pass
415
416    def test_mutable_sandbox_wrappedjsobject(self):
417        pass
418
419    def test_default_sandbox_wrappedjsobject(self):
420        pass
421
422    def test_system_sandbox_wrappedjsobject(self):
423        pass
424
425    def test_access_chrome_objects_in_event_listeners(self):
426        pass
427
428    def test_return_value_on_alert(self):
429        pass
430
431
432class TestElementCollections(MarionetteTestCase):
433
434    def assertSequenceIsInstance(self, seq, typ):
435        for item in seq:
436            self.assertIsInstance(item, typ)
437
438    def test_array(self):
439        self.marionette.navigate(inline("<p>foo <p>bar"))
440        els = self.marionette.execute_script("return Array.from(document.querySelectorAll('p'))")
441        self.assertIsInstance(els, list)
442        self.assertEqual(2, len(els))
443        self.assertSequenceIsInstance(els, HTMLElement)
444
445    def test_html_all_collection(self):
446        self.marionette.navigate(inline("<p>foo <p>bar"))
447        els = self.marionette.execute_script("return document.all")
448        self.assertIsInstance(els, list)
449        # <html>, <head>, <body>, <p>, <p>
450        self.assertEqual(5, len(els))
451        self.assertSequenceIsInstance(els, HTMLElement)
452
453    def test_html_collection(self):
454        self.marionette.navigate(inline("<p>foo <p>bar"))
455        els = self.marionette.execute_script("return document.getElementsByTagName('p')")
456        self.assertIsInstance(els, list)
457        self.assertEqual(2, len(els))
458        self.assertSequenceIsInstance(els, HTMLElement)
459
460    def test_html_form_controls_collection(self):
461        self.marionette.navigate(inline("<form><input><input></form>"))
462        els = self.marionette.execute_script("return document.forms[0].elements")
463        self.assertIsInstance(els, list)
464        self.assertEqual(2, len(els))
465        self.assertSequenceIsInstance(els, HTMLElement)
466
467    def test_html_options_collection(self):
468        self.marionette.navigate(inline("<select><option><option></select>"))
469        els = self.marionette.execute_script("return document.querySelector('select').options")
470        self.assertIsInstance(els, list)
471        self.assertEqual(2, len(els))
472        self.assertSequenceIsInstance(els, HTMLElement)
473
474    def test_node_list(self):
475        self.marionette.navigate(inline("<p>foo <p>bar"))
476        els = self.marionette.execute_script("return document.querySelectorAll('p')")
477        self.assertIsInstance(els, list)
478        self.assertEqual(2, len(els))
479        self.assertSequenceIsInstance(els, HTMLElement)
480