# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import sys
# add this directory to the path
sys.path.append(os.path.dirname(__file__))
from session_store_test_case import SessionStoreTestCase
def inline(title):
return f"data:text/html;charset=utf-8,
{title}"
TEST_WINDOWS = set([
(
inline("Page 1"),
inline("Page 2"),
),
])
class SessionOutcomeTestCase(SessionStoreTestCase):
"""Restart the browser for real, then read back what came of the restore
decision taken during that startup."""
def _quit_and_restore(self, app_args=None):
self.marionette.quit()
saved_args = self.marionette.instance.app_args
try:
if app_args:
self.marionette.instance.app_args = app_args
self.marionette.start_session()
self.marionette.set_context("chrome")
finally:
self.marionette.instance.app_args = saved_args
def _read_outcome(self):
self.wait_for_fog()
events = self.marionette.execute_script(
"""
return Glean.sessionRestore.startupSessionOutcome.testGetValue();
"""
)
self.assertIsNotNone(events, "An outcome should be recorded on startup.")
self.assertEqual(
len(events), 1, "The outcome should be recorded once per startup."
)
return events[0]["extra"]["outcome"]
class TestSessionRestored(SessionOutcomeTestCase):
def setUp(self):
super().setUp(
startup_page=3,
include_private=False,
restore_on_demand=False,
test_windows=TEST_WINDOWS,
)
def test_restoring_a_session_reports_restored(self):
self._quit_and_restore()
self.assertEqual(self._read_outcome(), "restored")
class TestNoWindows(SessionOutcomeTestCase):
def setUp(self):
super().setUp(
startup_page=3,
include_private=False,
restore_on_demand=False,
test_windows=TEST_WINDOWS,
)
def test_a_session_with_only_closed_windows_reports_no_windows(self):
# "no_windows" means there was session state to restore, but it held
# no open windows. Real sessions reach this on macOS: closing the last
# window there leaves the app running, and if the user then quits, the
# saved state has that window only in _closedWindows. Other platforms
# move the last closed window back into windows when quitting with
# none open, so they never save this state.
#
# Closing the last window can't produce it here, since marionette
# needs an open window and other platforms would put it back anyway.
# Instead, build the state from the live session by moving its
# windows into _closedWindows. SessionStore rewrites
# sessionstore.jsonlz4 on shutdown, so write this to a separate file
# and swap it in once the browser has quit.
closed_window_count = self.marionette.execute_async_script(
"""
let [resolve] = arguments;
const { SessionStore } = ChromeUtils.importESModule(
"moz-src:///browser/components/sessionstore/SessionStore.sys.mjs"
);
let state = JSON.parse(SessionStore.getBrowserState());
state._closedWindows = state.windows;
state.windows = [];
IOUtils.writeJSON(
PathUtils.join(PathUtils.profileDir, "session-outcome-fixture.jsonlz4"),
state,
{ compress: true }
).then(() => resolve(state._closedWindows.length));
"""
)
self.marionette.quit()
profile_dir = self.marionette.instance.profile.profile
os.replace(
os.path.join(profile_dir, "session-outcome-fixture.jsonlz4"),
os.path.join(profile_dir, "sessionstore.jsonlz4"),
)
self.marionette.start_session()
self.marionette.set_context("chrome")
self.assertEqual(self._read_outcome(), "no_windows")
self.assertEqual(
len(self.get_closed_windows()),
closed_window_count,
"The closed windows in the saved state should have been restored.",
)
class TestNothing(SessionOutcomeTestCase):
def setUp(self):
super().setUp(
startup_page=1,
include_private=False,
restore_on_demand=False,
test_windows=TEST_WINDOWS,
)
def test_a_deferred_session_carrying_nothing_reports_nothing(self):
# When session restore is not enabled and the previous session has no
# data to bring over (e.g. pinned tabs or saved groups) there is no
# state to restore at all.
self.marionette.set_prefs({
"browser.sessionstore.persist_closed_tabs_between_sessions": False,
})
self._quit_and_restore()
self.assertEqual(self._read_outcome(), "nothing")
class TestDeferredToWindow(SessionOutcomeTestCase):
def setUp(self):
super().setUp(
startup_page=3,
include_private=False,
restore_on_demand=False,
test_windows=TEST_WINDOWS,
)
def test_a_private_first_window_holds_the_session_back(self):
# The decision was to restore, but a private window can't be restored
# into, so the session is held back for an ordinary window later.
self._quit_and_restore(app_args=["-private-window"])
self.assertEqual(self._read_outcome(), "deferred_to_window")