import os

from marionette_driver.errors import (
    JavascriptException,
    NoAlertPresentException,
    ScriptTimeoutException,
)
from marionette_driver.marionette import Alert
from marionette_driver.wait import Wait

from marionette_harness import MarionetteTestCase


class TestExecuteAsyncContent(MarionetteTestCase):
    def setUp(self):
        super(TestExecuteAsyncContent, self).setUp()
        self.marionette.timeout.script = 1

    def tearDown(self):
        if self.alert_present():
            alert = self.marionette.switch_to_alert()
            alert.dismiss()
            self.wait_for_alert_closed()

    def alert_present(self):
        try:
            Alert(self.marionette).text
            return True
        except NoAlertPresentException:
            return False

    def wait_for_alert_closed(self, timeout=None):
        Wait(self.marionette, timeout=timeout).until(lambda _: not self.alert_present())

    def test_execute_async_simple(self):
        self.assertEqual(
            1, self.marionette.execute_async_script("arguments[arguments.length-1](1);")
        )

    def test_execute_async_ours(self):
        self.assertEqual(1, self.marionette.execute_async_script("arguments[0](1);"))

    def test_script_timeout_error(self):
        with self.assertRaisesRegex(ScriptTimeoutException, "Timed out after 100 ms"):
            self.marionette.execute_async_script("var x = 1;", script_timeout=100)

    def test_script_timeout_reset_after_timeout_error(self):
        script_timeout = self.marionette.timeout.script
        with self.assertRaises(ScriptTimeoutException):
            self.marionette.execute_async_script("var x = 1;", script_timeout=100)
        self.assertEqual(self.marionette.timeout.script, script_timeout)

    def test_script_timeout_no_timeout_error(self):
        self.assertTrue(
            self.marionette.execute_async_script(
                """
            var callback = arguments[arguments.length - 1];
            setTimeout(function() { callback(true); }, 500);
            """,
                script_timeout=1000,
            )
        )

    def test_no_timeout(self):
        self.marionette.timeout.script = 10
        self.assertTrue(
            self.marionette.execute_async_script(
                """
            var callback = arguments[arguments.length - 1];
            setTimeout(function() { callback(true); }, 500);
            """
            )
        )

    def test_execute_async_unload(self):
        self.marionette.timeout.script = 5
        unload = """
                window.location.href = "about:blank";
                 """
        self.assertRaises(
            JavascriptException, self.marionette.execute_async_script, unload
        )

    def test_check_window(self):
        self.assertTrue(
            self.marionette.execute_async_script(
                "arguments[0](window != null && window != undefined);"
            )
        )

    def test_same_context(self):
        var1 = "testing"
        self.assertEqual(
            self.marionette.execute_script(
                """
            this.testvar = '{}';
            return this.testvar;
            """.format(
                    var1
                )
            ),
            var1,
        )
        self.assertEqual(
            self.marionette.execute_async_script(
                "arguments[0](this.testvar);", new_sandbox=False
            ),
            var1,
        )

    def test_execute_no_return(self):
        self.assertEqual(self.marionette.execute_async_script("arguments[0]()"), None)

    def test_execute_js_exception(self):
        try:
            self.marionette.execute_async_script(
                """
                let a = 1;
                foo(bar);
                """
            )
            self.fail()
        except JavascriptException as e:
            self.assertIsNotNone(e.stacktrace)
            self.assertIn(
                os.path.relpath(__file__.replace(".pyc", ".py")), e.stacktrace
            )

    def test_execute_async_js_exception(self):
        try:
            self.marionette.execute_async_script(
                """
                let [resolve] = arguments;
                resolve(foo());
            """
            )
            self.fail()
        except JavascriptException as e:
            self.assertIsNotNone(e.stacktrace)
            self.assertIn(
                os.path.relpath(__file__.replace(".pyc", ".py")), e.stacktrace
            )

    def test_script_finished(self):
        self.assertTrue(
            self.marionette.execute_async_script(
                """
            arguments[0](true);
            """
            )
        )

    def test_execute_permission(self):
        self.assertRaises(
            JavascriptException,
            self.marionette.execute_async_script,
            """
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
                              .getService(Components.interfaces.nsIPrefBranch);
arguments[0](4);
""",
        )

    def test_sandbox_reuse(self):
        # Sandboxes between `execute_script()` invocations are shared.
        self.marionette.execute_async_script(
            "this.foobar = [23, 42];" "arguments[0]();"
        )
        self.assertEqual(
            self.marionette.execute_async_script(
                "arguments[0](this.foobar);", new_sandbox=False
            ),
            [23, 42],
        )

    def test_sandbox_refresh_arguments(self):
        self.marionette.execute_async_script(
            "this.foobar = [arguments[0], arguments[1]];"
            "let resolve = "
            "arguments[arguments.length - 1];"
            "resolve();",
            script_args=[23, 42],
        )
        self.assertEqual(
            self.marionette.execute_async_script(
                "arguments[0](this.foobar);", new_sandbox=False
            ),
            [23, 42],
        )

    # Functions defined in higher privilege scopes, such as the privileged
    # JSWindowActor child runs in, cannot be accessed from
    # content.  This tests that it is possible to introspect the objects on
    # `arguments` without getting permission defined errors.  This is made
    # possible because the last argument is always the callback/complete
    # function.
    #
    # See bug 1290966.
    def test_introspection_of_arguments(self):
        self.marionette.execute_async_script(
            "arguments[0].cheese; __webDriverCallback();", script_args=[], sandbox=None
        )

    def test_return_value_on_alert(self):
        res = self.marionette.execute_async_script("alert()")
        self.assertIsNone(res)


class TestExecuteAsyncChrome(TestExecuteAsyncContent):
    def setUp(self):
        super(TestExecuteAsyncChrome, self).setUp()
        self.marionette.set_context("chrome")

    def test_execute_async_unload(self):
        pass

    def test_execute_permission(self):
        self.assertEqual(
            5,
            self.marionette.execute_async_script(
                """
            var c = Components.classes;
            arguments[0](5);
            """
            ),
        )

    def test_execute_async_js_exception(self):
        # Javascript exceptions are not propagated in chrome code
        self.marionette.timeout.script = 0.2
        with self.assertRaises(ScriptTimeoutException):
            self.marionette.execute_async_script(
                """
                var callback = arguments[arguments.length - 1];
                setTimeout(function() { callback(foo()); }, 50);
                """
            )

    def test_return_value_on_alert(self):
        pass
