# 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 marionette_driver import Wait
from session_store_test_case import SessionStoreTestCase
def wait_for_fog(marionette):
# Glean's blocking test APIs (testGetValue) park the main thread forever if
# Glean is still pre-init, and FOG is initialized from a startup idle task,
# so it can lag the point where the browser reports itself started up.
Wait(marionette, timeout=60).until(
lambda _: marionette.execute_script(
"""
return Services.fog.initialized;
"""
),
message="FOG should be initialized before reading Glean metrics.",
)
def inline(title):
return f"data:text/html;charset=utf-8,
{title}"
def as_bool(value):
# Glean has reported boolean event extras as strings in the past, so accept
# either rather than depending on which.
return value if isinstance(value, bool) else value == "true"
TEST_WINDOWS = set([
(
inline("Page 1"),
inline("Page 2"),
),
])
class SessionAvailabilityTestCase(SessionStoreTestCase):
"""Restart the browser for real, then read back the telemetry that startup
recorded about what it found to restore from."""
def _quit_and_restore(self):
self.marionette.quit()
self.marionette.start_session()
self.marionette.set_context("chrome")
def _read_availability_telemetry(self):
"""Read the session_restore.startup_session_availability event that the
startup we just completed recorded, and return its extra keys."""
wait_for_fog(self.marionette)
events = self.marionette.execute_script(
"""
return Glean.sessionRestore.startupSessionAvailability.testGetValue();
"""
)
self.assertIsNotNone(
events, "Session availability should be recorded on startup."
)
self.assertEqual(
len(events), 1, "Session availability should be recorded once per startup."
)
return events[0]["extra"]
class TestSessionAvailableAndWanted(SessionAvailabilityTestCase):
def setUp(self):
# browser.startup.page has to be enforced through setUp: it is
# re-applied on every restart, so setting it later doesn't survive.
super().setUp(
startup_page=3,
include_private=False,
restore_on_demand=False,
test_windows=TEST_WINDOWS,
)
def test_previous_session_is_available(self):
self._quit_and_restore()
telemetry = self._read_availability_telemetry()
self.assertEqual(
telemetry["origin"],
"clean",
"A clean shutdown leaves a session in the clean file.",
)
self.assertEqual(telemetry["format"], "jsonlz4")
self.assertEqual(telemetry["clean"], "present", "The clean file was found.")
self.assertTrue(
as_bool(telemetry["startup_page_is_resume"]),
"The user asked for the previous session to be restored.",
)
class TestOneOffResume(SessionAvailabilityTestCase):
def setUp(self):
super().setUp(
startup_page=1,
include_private=False,
restore_on_demand=False,
test_windows=TEST_WINDOWS,
)
def test_one_off_resume_is_recorded_before_it_is_cleared(self):
# SessionStore clears resume_session_once once it considers the session
# as good as resumed, so this only passes while the value reported is
# the one snapshotted ahead of that.
# A browser update will one-time override the "don't restore my
# previous session" behavior via this pref
self.marionette.set_prefs({
"browser.sessionstore.resume_session_once": True,
})
self._quit_and_restore()
telemetry = self._read_availability_telemetry()
self.assertTrue(
as_bool(telemetry["resume_session_once"]),
"A pending one-off resume is recorded before SessionStore clears it.",
)
self.assertFalse(
as_bool(telemetry["startup_page_is_resume"]),
"The standing preference is reported separately from the one-off.",
)
class TestResumeArmedForOsRestart(SessionAvailabilityTestCase):
def setUp(self):
super().setUp(
startup_page=1,
include_private=False,
restore_on_demand=False,
test_windows=TEST_WINDOWS,
)
def test_one_off_resume_armed_for_an_os_restart_is_recorded(self):
# Quitting for an OS restart arms both of these prefs. Starting up
# again without -os-restarted then has SessionStartup.init() clear
# resume_session_once before the session file read completes, so the
# value reported has to come from the snapshot init() takes.
self.marionette.set_prefs({
"browser.sessionstore.resume_session_once": True,
"browser.sessionstore.resuming_after_os_restart": True,
})
self._quit_and_restore()
telemetry = self._read_availability_telemetry()
self.assertTrue(
as_bool(telemetry["resuming_after_os_restart"]),
"A resume armed for an OS restart is reported.",
)
self.assertFalse(
as_bool(telemetry["restarted_by_os"]),
"The OS did not in fact restart us, which is what clears the pref.",
)
self.assertTrue(
as_bool(telemetry["resume_session_once"]),
"The pending one-off resume is recorded as it was found at startup.",
)
def test_one_off_resume_is_recorded_when_the_os_did_restart_us(self):
# The counterpart of the test above: started with -os-restarted, so
# SessionStartup.init() leaves resume_session_once alone and the
# session really is resumed.
self.marionette.set_prefs({
"browser.sessionstore.resume_session_once": True,
"browser.sessionstore.resuming_after_os_restart": True,
})
self.marionette.quit()
saved_args = self.marionette.instance.app_args
try:
self.marionette.instance.app_args = ["-os-restarted"]
self.marionette.start_session()
self.marionette.set_context("chrome")
finally:
self.marionette.instance.app_args = saved_args
telemetry = self._read_availability_telemetry()
self.assertTrue(
as_bool(telemetry["restarted_by_os"]),
"The OS started the browser, so the armed resume stands.",
)
self.assertTrue(
as_bool(telemetry["resuming_after_os_restart"]),
"A resume armed for an OS restart is reported.",
)
self.assertTrue(
as_bool(telemetry["resume_session_once"]),
"The pending one-off resume is recorded as it was found at startup.",
)