tor-browser

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

test_quit_restart.py (21391B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import sys
      6 import unittest
      7 from urllib.parse import quote
      8 
      9 import mozinfo
     10 
     11 from marionette_driver import errors
     12 from marionette_driver.by import By
     13 from marionette_harness import MarionetteTestCase
     14 
     15 
     16 def inline(doc):
     17    return "data:text/html;charset=utf-8,{}".format(quote(doc))
     18 
     19 
     20 class TestServerQuitApplication(MarionetteTestCase):
     21    def tearDown(self):
     22        if self.marionette.session is None:
     23            self.marionette.start_session()
     24 
     25    def quit(self, flags=None, safe_mode=False):
     26        body = {}
     27        if flags is not None:
     28            body["flags"] = list(
     29                flags,
     30            )
     31        if safe_mode:
     32            body["safeMode"] = safe_mode
     33 
     34        resp = self.marionette._send_message("Marionette:Quit", body)
     35        self.marionette.session_id = None
     36        self.marionette.session = None
     37        self.marionette.process_id = None
     38        self.marionette.profile = None
     39        self.marionette.window = None
     40 
     41        self.assertIn("cause", resp)
     42 
     43        self.marionette.client.close()
     44        self.marionette.instance.runner.wait()
     45 
     46        return resp["cause"]
     47 
     48    def test_types(self):
     49        for typ in [42, True, "foo", []]:
     50            print("testing type {}".format(type(typ)))
     51            with self.assertRaises(errors.InvalidArgumentException):
     52                self.marionette._send_message("Marionette:Quit", typ)
     53 
     54        with self.assertRaises(errors.InvalidArgumentException):
     55            self.quit("foo")
     56 
     57    def test_undefined_default(self):
     58        cause = self.quit()
     59        self.assertEqual("shutdown", cause)
     60 
     61    def test_empty_default(self):
     62        cause = self.quit(())
     63        self.assertEqual("shutdown", cause)
     64 
     65    def test_incompatible_quit_flags(self):
     66        with self.assertRaises(errors.InvalidArgumentException):
     67            self.quit(("eAttemptQuit", "eForceQuit"))
     68 
     69    def test_attempt_quit(self):
     70        cause = self.quit(("eAttemptQuit",))
     71        self.assertEqual("shutdown", cause)
     72 
     73    def test_force_quit(self):
     74        cause = self.quit(("eForceQuit",))
     75        self.assertEqual("shutdown", cause)
     76 
     77    def test_safe_mode_requires_restart(self):
     78        with self.assertRaises(errors.InvalidArgumentException):
     79            self.quit(("eAttemptQuit",), True)
     80 
     81    @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS")
     82    def test_silent_quit_missing_windowless_capability(self):
     83        with self.assertRaises(errors.UnsupportedOperationException):
     84            self.quit(("eSilently",))
     85 
     86 
     87 class TestQuitRestart(MarionetteTestCase):
     88    def setUp(self):
     89        MarionetteTestCase.setUp(self)
     90 
     91        self.pid = self.marionette.process_id
     92        self.profile = self.marionette.profile
     93        self.session_id = self.marionette.session_id
     94 
     95        # Use a preference to check that the restart was successful. If its
     96        # value has not been forced, a restart will cause a reset of it.
     97        self.assertNotEqual(
     98            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
     99        )
    100        self.marionette.set_pref("startup.homepage_welcome_url", "about:about")
    101 
    102    def tearDown(self):
    103        # Ensure to restart a session if none exist for clean-up
    104        if self.marionette.session is None:
    105            self.marionette.start_session()
    106 
    107        self.marionette.clear_pref("startup.homepage_welcome_url")
    108 
    109        MarionetteTestCase.tearDown(self)
    110 
    111    @property
    112    def is_safe_mode(self):
    113        with self.marionette.using_context("chrome"):
    114            return self.marionette.execute_script(
    115                """
    116              return Services.appinfo.inSafeMode;
    117            """
    118            )
    119 
    120    def shutdown(self, restart=False):
    121        self.marionette.set_context("chrome")
    122        self.marionette.execute_script(
    123            """
    124            let flags = Ci.nsIAppStartup.eAttemptQuit;
    125            if (arguments[0]) {
    126              flags |= Ci.nsIAppStartup.eRestart;
    127            }
    128            Services.startup.quit(flags);
    129        """,
    130            script_args=(restart,),
    131        )
    132 
    133    def test_force_restart(self):
    134        self.marionette.restart(in_app=False)
    135        self.assertEqual(self.marionette.profile, self.profile)
    136        self.assertNotEqual(self.marionette.session_id, self.session_id)
    137 
    138        # A forced restart will cause a new process id
    139        self.assertNotEqual(self.marionette.process_id, self.pid)
    140        self.assertNotEqual(
    141            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    142        )
    143 
    144    def test_force_clean_restart(self):
    145        self.marionette.restart(in_app=False, clean=True)
    146        self.assertNotEqual(self.marionette.profile, self.profile)
    147        self.assertNotEqual(self.marionette.session_id, self.session_id)
    148        # A forced restart will cause a new process id
    149        self.assertNotEqual(self.marionette.process_id, self.pid)
    150        self.assertNotEqual(
    151            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    152        )
    153 
    154    def test_force_quit(self):
    155        self.marionette.quit(in_app=False)
    156 
    157        self.assertEqual(self.marionette.session, None)
    158        with self.assertRaisesRegex(
    159            errors.InvalidSessionIdException, "Please start a session"
    160        ):
    161            self.marionette.get_url()
    162 
    163    def test_force_clean_quit(self):
    164        self.marionette.quit(in_app=False, clean=True)
    165 
    166        self.assertEqual(self.marionette.session, None)
    167        with self.assertRaisesRegex(
    168            errors.InvalidSessionIdException, "Please start a session"
    169        ):
    170            self.marionette.get_url()
    171 
    172        self.marionette.start_session()
    173        self.assertNotEqual(self.marionette.profile, self.profile)
    174        self.assertNotEqual(self.marionette.session_id, self.session_id)
    175        self.assertNotEqual(
    176            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    177        )
    178 
    179    def test_quit_no_in_app_and_clean(self):
    180        # Test that in_app and clean cannot be used in combination
    181        with self.assertRaisesRegex(
    182            ValueError, "cannot be triggered with the clean flag set"
    183        ):
    184            self.marionette.quit(in_app=True, clean=True)
    185 
    186    def test_restart_no_in_app_and_clean(self):
    187        # Test that in_app and clean cannot be used in combination
    188        with self.assertRaisesRegex(
    189            ValueError, "cannot be triggered with the clean flag set"
    190        ):
    191            self.marionette.restart(in_app=True, clean=True)
    192 
    193    def test_restart_preserves_requested_capabilities(self):
    194        self.marionette.delete_session()
    195        self.marionette.start_session(capabilities={"test:fooBar": True})
    196 
    197        self.marionette.restart(in_app=False)
    198        self.assertEqual(self.marionette.session.get("test:fooBar"), True)
    199 
    200    def test_restart_safe_mode(self):
    201        try:
    202            self.assertFalse(self.is_safe_mode, "Safe Mode is unexpectedly enabled")
    203            self.marionette.restart(safe_mode=True)
    204            self.assertTrue(self.is_safe_mode, "Safe Mode is not enabled")
    205        finally:
    206            self.marionette.quit(in_app=False, clean=True)
    207 
    208    def test_restart_safe_mode_requires_in_app(self):
    209        self.assertFalse(self.is_safe_mode, "Safe Mode is unexpectedly enabled")
    210 
    211        with self.assertRaisesRegex(ValueError, "in_app restart is required"):
    212            self.marionette.restart(in_app=False, safe_mode=True)
    213 
    214        self.assertFalse(self.is_safe_mode, "Safe Mode is unexpectedly enabled")
    215        self.marionette.quit(in_app=False, clean=True)
    216 
    217    def test_in_app_restart(self):
    218        details = self.marionette.restart()
    219        self.assertTrue(details["in_app"], "Expected in_app restart")
    220        self.assertFalse(details["forced"], "Expected non-forced shutdown")
    221 
    222        self.assertEqual(self.marionette.profile, self.profile)
    223        self.assertNotEqual(self.marionette.session_id, self.session_id)
    224 
    225        self.assertNotEqual(self.marionette.process_id, self.pid)
    226 
    227        self.assertNotEqual(
    228            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    229        )
    230 
    231    def test_in_app_restart_component_prevents_shutdown(self):
    232        with self.marionette.using_context("chrome"):
    233            self.marionette.execute_script(
    234                """
    235                Services.obs.addObserver(subject => {
    236                  let cancelQuit = subject.QueryInterface(Ci.nsISupportsPRBool);
    237                  cancelQuit.data = true;
    238                }, "quit-application-requested");
    239                """
    240            )
    241 
    242        details = self.marionette.restart()
    243        self.assertTrue(details["in_app"], "Expected in_app restart")
    244        self.assertTrue(details["forced"], "Expected forced shutdown")
    245 
    246        self.assertEqual(self.marionette.profile, self.profile)
    247        self.assertNotEqual(self.marionette.session_id, self.session_id)
    248 
    249        self.assertNotEqual(self.marionette.process_id, self.pid)
    250 
    251        self.assertNotEqual(
    252            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    253        )
    254 
    255    def test_in_app_restart_with_callback(self):
    256        details = self.marionette.restart(callback=lambda: self.shutdown(restart=True))
    257        self.assertTrue(details["in_app"], "Expected in_app restart")
    258 
    259        self.assertEqual(self.marionette.profile, self.profile)
    260        self.assertNotEqual(self.marionette.session_id, self.session_id)
    261 
    262        self.assertNotEqual(self.marionette.process_id, self.pid)
    263 
    264        self.assertNotEqual(
    265            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    266        )
    267 
    268    def test_in_app_restart_with_non_callable_callback(self):
    269        with self.assertRaisesRegex(ValueError, "is not callable"):
    270            self.marionette.restart(callback=4)
    271 
    272        self.assertEqual(self.marionette.instance.runner.returncode, None)
    273        self.assertEqual(self.marionette.is_shutting_down, False)
    274 
    275    @unittest.skipIf(sys.platform.startswith("win"), "Bug 1493796")
    276    def test_in_app_restart_with_callback_but_process_quits_instead(self):
    277        try:
    278            timeout_shutdown = self.marionette.shutdown_timeout
    279            timeout_startup = self.marionette.startup_timeout
    280            self.marionette.shutdown_timeout = 5
    281            self.marionette.startup_timeout = 0
    282 
    283            with self.assertRaisesRegex(
    284                IOError, "Process unexpectedly quit without restarting"
    285            ):
    286                self.marionette.restart(callback=lambda: self.shutdown(restart=False))
    287        finally:
    288            self.marionette.shutdown_timeout = timeout_shutdown
    289            self.marionette.startup_timeout = timeout_startup
    290 
    291    @unittest.skipIf(sys.platform.startswith("win"), "Bug 1493796")
    292    def test_in_app_restart_with_callback_missing_shutdown(self):
    293        try:
    294            timeout_shutdown = self.marionette.shutdown_timeout
    295            timeout_startup = self.marionette.startup_timeout
    296            self.marionette.shutdown_timeout = 5
    297            self.marionette.startup_timeout = 0
    298 
    299            with self.assertRaisesRegex(
    300                IOError, "the connection to Marionette server is lost"
    301            ):
    302                self.marionette.restart(in_app=True, callback=lambda: False)
    303        finally:
    304            self.marionette.shutdown_timeout = timeout_shutdown
    305            self.marionette.startup_timeout = timeout_startup
    306 
    307    def test_in_app_restart_preserves_requested_capabilities(self):
    308        self.marionette.delete_session()
    309        self.marionette.start_session(capabilities={"test:fooBar": True})
    310 
    311        details = self.marionette.restart()
    312        self.assertTrue(details["in_app"], "Expected in_app restart")
    313        self.assertEqual(self.marionette.session.get("test:fooBar"), True)
    314 
    315    @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS")
    316    def test_in_app_silent_restart_fails_without_windowless_flag_on_mac_os(self):
    317        self.marionette.delete_session()
    318        self.marionette.start_session()
    319 
    320        with self.assertRaises(errors.UnsupportedOperationException):
    321            self.marionette.restart(silent=True)
    322 
    323    @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS")
    324    def test_in_app_silent_restart_windowless_flag_on_mac_os(self):
    325        self.marionette.delete_session()
    326        self.marionette.start_session(capabilities={"moz:windowless": True})
    327 
    328        self.marionette.restart(silent=True)
    329        self.assertTrue(self.marionette.session_capabilities["moz:windowless"])
    330 
    331        self.marionette.restart()
    332        self.assertTrue(self.marionette.session_capabilities["moz:windowless"])
    333 
    334        self.marionette.delete_session()
    335 
    336    @unittest.skipUnless(sys.platform.startswith("darwin"), "Only supported on MacOS")
    337    def test_in_app_silent_restart_requires_in_app(self):
    338        self.marionette.delete_session()
    339        self.marionette.start_session(capabilities={"moz:windowless": True})
    340 
    341        with self.assertRaisesRegex(ValueError, "in_app restart is required"):
    342            self.marionette.restart(in_app=False, silent=True)
    343 
    344        self.marionette.delete_session()
    345 
    346    @unittest.skipIf(
    347        sys.platform.startswith("darwin"), "Not supported on other platforms than MacOS"
    348    )
    349    def test_in_app_silent_restart_windowless_flag_unsupported_platforms(self):
    350        self.marionette.delete_session()
    351 
    352        with self.assertRaises(errors.SessionNotCreatedException):
    353            self.marionette.start_session(capabilities={"moz:windowless": True})
    354 
    355    def test_in_app_quit(self):
    356        details = self.marionette.quit()
    357        self.assertTrue(details["in_app"], "Expected in_app shutdown")
    358        self.assertFalse(details["forced"], "Expected non-forced shutdown")
    359        self.assertEqual(self.marionette.instance.runner.returncode, 0)
    360 
    361        self.assertEqual(self.marionette.session, None)
    362        with self.assertRaisesRegex(
    363            errors.InvalidSessionIdException, "Please start a session"
    364        ):
    365            self.marionette.get_url()
    366 
    367        self.marionette.start_session()
    368        self.assertEqual(self.marionette.profile, self.profile)
    369        self.assertNotEqual(self.marionette.session_id, self.session_id)
    370        self.assertNotEqual(
    371            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    372        )
    373 
    374    def test_in_app_quit_forced_because_component_prevents_shutdown(self):
    375        with self.marionette.using_context("chrome"):
    376            self.marionette.execute_script(
    377                """
    378                Services.obs.addObserver(subject => {
    379                    let cancelQuit = subject.QueryInterface(Ci.nsISupportsPRBool);
    380                    cancelQuit.data = true;
    381                }, "quit-application-requested");
    382                """
    383            )
    384 
    385        details = self.marionette.quit()
    386        self.assertTrue(details["in_app"], "Expected in_app shutdown")
    387        self.assertTrue(details["forced"], "Expected forced shutdown")
    388        self.assertEqual(self.marionette.instance.runner.returncode, 0)
    389 
    390        self.assertEqual(self.marionette.session, None)
    391        with self.assertRaisesRegex(
    392            errors.InvalidSessionIdException, "Please start a session"
    393        ):
    394            self.marionette.get_url()
    395 
    396        self.marionette.start_session()
    397        self.assertEqual(self.marionette.profile, self.profile)
    398        self.assertNotEqual(self.marionette.session_id, self.session_id)
    399        self.assertNotEqual(
    400            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    401        )
    402 
    403    def test_in_app_quit_with_callback(self):
    404        details = self.marionette.quit(callback=self.shutdown)
    405        self.assertTrue(details["in_app"], "Expected in_app shutdown")
    406        self.assertFalse(details["forced"], "Expected non-forced shutdown")
    407 
    408        self.assertEqual(self.marionette.instance.runner.returncode, 0)
    409        self.assertEqual(self.marionette.is_shutting_down, False)
    410 
    411        self.assertEqual(self.marionette.session, None)
    412        with self.assertRaisesRegex(
    413            errors.InvalidSessionIdException, "Please start a session"
    414        ):
    415            self.marionette.get_url()
    416 
    417        self.marionette.start_session()
    418        self.assertEqual(self.marionette.profile, self.profile)
    419        self.assertNotEqual(self.marionette.session_id, self.session_id)
    420        self.assertNotEqual(
    421            self.marionette.get_pref("startup.homepage_welcome_url"), "about:about"
    422        )
    423 
    424    def test_in_app_quit_with_non_callable_callback(self):
    425        with self.assertRaisesRegex(ValueError, "is not callable"):
    426            self.marionette.quit(callback=4)
    427        self.assertEqual(self.marionette.instance.runner.returncode, None)
    428        self.assertEqual(self.marionette.is_shutting_down, False)
    429 
    430    def test_in_app_quit_forced_because_callback_does_not_shutdown(self):
    431        try:
    432            timeout = self.marionette.shutdown_timeout
    433            self.marionette.shutdown_timeout = 5
    434 
    435            with self.assertRaisesRegex(IOError, "Process still running"):
    436                self.marionette.quit(in_app=True, callback=lambda: False)
    437 
    438            self.assertNotEqual(self.marionette.instance.runner.returncode, None)
    439            self.assertEqual(self.marionette.is_shutting_down, False)
    440        finally:
    441            self.marionette.shutdown_timeout = timeout
    442 
    443        self.marionette.start_session()
    444 
    445    def test_in_app_quit_with_callback_that_raises_an_exception(self):
    446        def errorneous_callback():
    447            raise Exception("foo")
    448 
    449        with self.assertRaisesRegex(Exception, "foo"):
    450            self.marionette.quit(in_app=True, callback=errorneous_callback)
    451        self.assertEqual(self.marionette.instance.runner.returncode, None)
    452        self.assertEqual(self.marionette.is_shutting_down, False)
    453 
    454        self.assertIsNotNone(self.marionette.session)
    455        self.marionette.current_window_handle
    456 
    457    def test_in_app_quit_with_dismissed_beforeunload_prompt(self):
    458        self.marionette.navigate(
    459            inline(
    460                """
    461          <input type="text">
    462          <script>
    463            window.addEventListener("beforeunload", function (event) {
    464              event.preventDefault();
    465            });
    466          </script>
    467        """
    468            )
    469        )
    470 
    471        self.marionette.find_element(By.TAG_NAME, "input").send_keys("foo")
    472        self.marionette.quit()
    473        self.assertNotEqual(self.marionette.instance.runner.returncode, None)
    474        self.marionette.start_session()
    475 
    476    def test_reset_context_after_quit_by_set_context(self):
    477        # Check that we are in content context which is used by default in
    478        # Marionette
    479        self.assertNotIn(
    480            "chrome://",
    481            self.marionette.get_url(),
    482            "Context does not default to content",
    483        )
    484 
    485        self.marionette.set_context("chrome")
    486        self.marionette.quit()
    487        self.assertEqual(self.marionette.session, None)
    488        self.marionette.start_session()
    489        self.assertNotIn(
    490            "chrome://",
    491            self.marionette.get_url(),
    492            "Not in content context after quit with using_context",
    493        )
    494 
    495    def test_reset_context_after_quit_by_using_context(self):
    496        # Check that we are in content context which is used by default in
    497        # Marionette
    498        self.assertNotIn(
    499            "chrome://",
    500            self.marionette.get_url(),
    501            "Context does not default to content",
    502        )
    503 
    504        with self.marionette.using_context("chrome"):
    505            self.marionette.quit()
    506            self.assertEqual(self.marionette.session, None)
    507            self.marionette.start_session()
    508            self.assertNotIn(
    509                "chrome://",
    510                self.marionette.get_url(),
    511                "Not in content context after quit with using_context",
    512            )
    513 
    514    def test_keep_context_after_restart_by_set_context(self):
    515        # Check that we are in content context which is used by default in
    516        # Marionette
    517        self.assertNotIn(
    518            "chrome://", self.marionette.get_url(), "Context doesn't default to content"
    519        )
    520 
    521        # restart while we are in chrome context
    522        self.marionette.set_context("chrome")
    523        self.marionette.restart()
    524 
    525        self.assertNotEqual(self.marionette.process_id, self.pid)
    526 
    527        self.assertIn(
    528            "chrome://",
    529            self.marionette.get_url(),
    530            "Not in chrome context after a restart with set_context",
    531        )
    532 
    533    def test_keep_context_after_restart_by_using_context(self):
    534        # Check that we are in content context which is used by default in
    535        # Marionette
    536        self.assertNotIn(
    537            "chrome://",
    538            self.marionette.get_url(),
    539            "Context does not default to content",
    540        )
    541 
    542        # restart while we are in chrome context
    543        with self.marionette.using_context("chrome"):
    544            self.marionette.restart()
    545 
    546            self.assertNotEqual(self.marionette.process_id, self.pid)
    547 
    548            self.assertIn(
    549                "chrome://",
    550                self.marionette.get_url(),
    551                "Not in chrome context after a restart with using_context",
    552            )