commit b224e97d13d9a4965f963f53e5823a4c019aa445 parent 0b1cf66f3b35bccf28116a94bdb4b9fab38ee60c Author: Andrea Marchesini <amarchesini@mozilla.com> Date: Tue, 28 Oct 2025 09:02:06 +0000 Bug 1995964 - Make IPPService.updateState a sync operation, r=ip-protection-reviewers,fchasen Differential Revision: https://phabricator.services.mozilla.com/D269741 Diffstat:
16 files changed, 399 insertions(+), 366 deletions(-)
diff --git a/browser/components/ipprotection/IPPEnrollAndEntitleManager.sys.mjs b/browser/components/ipprotection/IPPEnrollAndEntitleManager.sys.mjs @@ -0,0 +1,239 @@ +/* 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/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + IPPStartupCache: "resource:///modules/ipprotection/IPPStartupCache.sys.mjs", + IPProtectionService: + "resource:///modules/ipprotection/IPProtectionService.sys.mjs", + IPPSignInWatcher: "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs", +}); + +const LOG_PREF = "browser.ipProtection.log"; + +ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { + return console.createInstance({ + prefix: "IPPEnrollAndEntitleManager", + maxLogLevel: Services.prefs.getBoolPref(LOG_PREF, false) ? "Debug" : "Warn", + }); +}); + +/** + * This class manages the enrolling and entitlement. + */ +class IPPEnrollAndEntitleManagerSingleton extends EventTarget { + #runningPromise = null; + + #isEnrolled = false; + #entitlement = null; + + constructor() { + super(); + + this.handleEvent = this.#handleEvent.bind(this); + } + + init() { + // We will use data from the cache until we are fully functional. Then we + // will recompute the state in `initOnStartupCompleted`. + this.#entitlement = lazy.IPPStartupCache.entitlement; + this.#isEnrolled = !!this.#entitlement; + + lazy.IPPSignInWatcher.addEventListener( + "IPPSignInWatcher:StateChanged", + this.handleEvent + ); + } + + initOnStartupCompleted() { + if (!lazy.IPPSignInWatcher.isSignedIn) { + return; + } + + try { + // This bit must be async because we want to trigger the updateState at + // the end of the rest of the initialization. + lazy.IPProtectionService.guardian + .isLinkedToGuardian(/* only cache: */ true) + .then( + async isEnrolled => { + this.#isEnrolled = isEnrolled; + + if (isEnrolled) { + const data = + await IPPEnrollAndEntitleManagerSingleton.#maybeEntitle(); + this.#setEntitlement(data.entitlement); + } else { + this.#setEntitlement(null); + } + }, + () => { + // In case we were using cached values, it's time to reset them. + this.#isEnrolled = false; + this.#setEntitlement(null); + } + ); + } catch (_) { + // In case we were using cached values, it's time to reset them. + this.#isEnrolled = false; + this.#setEntitlement(null); + } + } + + uninit() { + lazy.IPPSignInWatcher.removeEventListener( + "IPPSignInWatcher:StateChanged", + this.handleEvent + ); + + this.#entitlement = null; + this.#isEnrolled = false; + } + + #handleEvent(_event) { + if (!lazy.IPPSignInWatcher.isSignedIn) { + this.#isEnrolled = false; + this.#setEntitlement(null); + return; + } + + this.maybeEnrollAndEntitle(); + } + + maybeEnrollAndEntitle() { + if (this.#runningPromise) { + return this.#runningPromise; + } + + if (this.#entitlement && this.#isEnrolled) { + return Promise.resolve({ isEnrolledAndEntitled: true }); + } + + const enrollAndEntitle = async () => { + if (!this.#isEnrolled) { + const data = await IPPEnrollAndEntitleManagerSingleton.#maybeEnroll(); + if (!data.isEnrolled) { + return { isEnrolledAndEntitled: false, error: data.error }; + } + + this.#isEnrolled = true; + } + + const data = await IPPEnrollAndEntitleManagerSingleton.#maybeEntitle(); + if (!data.entitlement) { + return { isEnrolledAndEntitled: false, error: data.error }; + } + + this.#setEntitlement(data.entitlement); + return { isEnrolledAndEntitled: true }; + }; + + this.#runningPromise = enrollAndEntitle().finally(() => { + this.#runningPromise = null; + }); + + return this.#runningPromise; + } + + // This method is static because we don't want to change the internal state + // of the singleton. + static async #maybeEnroll() { + let isEnrolled = false; + try { + isEnrolled = await lazy.IPProtectionService.guardian.isLinkedToGuardian( + /* only cache: */ false + ); + } catch (error) { + return { isEnrolled, error: error?.message }; + } + + if (isEnrolled) { + return { isEnrolled }; + } + + try { + const enrollment = await lazy.IPProtectionService.guardian.enroll(); + isEnrolled = !!enrollment?.ok; + + lazy.logConsole.debug( + "Guardian:", + isEnrolled ? "Enrolled" : "Enrollment Failed" + ); + + return { isEnrolled, error: enrollment?.error }; + } catch (error) { + return { isEnrolled, error: error?.message }; + } + } + + // This method is static because we don't want to change the internal state + // of the singleton. + static async #maybeEntitle() { + let { status, entitlement, error } = + await lazy.IPProtectionService.guardian.fetchUserInfo(); + lazy.logConsole.debug("Entitlement:", { status, entitlement, error }); + + // If we see an error during the READY state, let's trigger an error state. + if (error || !entitlement || status != 200) { + return { entitlement: null, error: error || `Status: ${status}` }; + } + + return { entitlement }; + } + + #setEntitlement(entitlement) { + this.#entitlement = entitlement; + lazy.IPPStartupCache.storeEntitlement(this.#entitlement); + + lazy.IPProtectionService.updateState(); + + this.dispatchEvent( + new CustomEvent("IPPEnrollAndEntitleManager:StateChanged", { + bubbles: true, + composed: true, + }) + ); + } + + get isEnrolledAndEntitled() { + return this.#isEnrolled && !!this.#entitlement; + } + + /** + * Checks if a user has upgraded. + * + * @returns {boolean} + */ + get hasUpgraded() { + return this.#entitlement?.subscribed; + } + + /** + * Checks if the entitlement exists and it contains a UUID + */ + get hasEntitlementUid() { + return !!this.#entitlement?.uid; + } + + /** + * Checks if we have the entitlement + */ + get hasEntitlement() { + return !!this.#entitlement; + } + + async refetchEntitlement() { + this.#setEntitlement(null); + await this.maybeEnrollAndEntitle(); + } + + resetEntitlement() { + this.#setEntitlement(null); + } +} + +const IPPEnrollAndEntitleManager = new IPPEnrollAndEntitleManagerSingleton(); + +export { IPPEnrollAndEntitleManager }; diff --git a/browser/components/ipprotection/IPPEnrollHelper.sys.mjs b/browser/components/ipprotection/IPPEnrollHelper.sys.mjs @@ -1,139 +0,0 @@ -/* 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/. */ - -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - IPProtectionService: - "resource:///modules/ipprotection/IPProtectionService.sys.mjs", - IPPSignInWatcher: "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs", -}); - -const LOG_PREF = "browser.ipProtection.log"; - -ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { - return console.createInstance({ - prefix: "IPPEnrollHelper", - maxLogLevel: Services.prefs.getBoolPref(LOG_PREF, false) ? "Debug" : "Warn", - }); -}); - -/** - * A class that manages the enrolling state. - */ -class IPPEnrollHelperSingleton { - #isEnrolled = false; - #enrolling = null; - - constructor() { - this.handleEvent = this.#handleEvent.bind(this); - } - - init() { - lazy.IPPSignInWatcher.addEventListener( - "IPPSignInWatcher:StateChanged", - this.handleEvent - ); - } - - initOnStartupCompleted() { - if (!lazy.IPPSignInWatcher.isSignedIn) { - return; - } - - try { - // This bit must be async because we want to trigger the updateState at - // the end of the rest of the initialization. - lazy.IPProtectionService.guardian - .isLinkedToGuardian(/* only cache: */ true) - .then( - isEnrolled => this.#setEnrolled(isEnrolled), - () => { - // We don't want to report errors during the initialization - } - ); - } catch (_) { - // If we are not enrolled. It's not a big deal. - } - } - - uninit() { - lazy.IPPSignInWatcher.removeEventListener( - "IPPSignInWatcher:StateChanged", - this.handleEvent - ); - } - - #handleEvent(_event) { - if (!lazy.IPPSignInWatcher.isSignedIn) { - this.#setEnrolled(false); - return; - } - - this.maybeEnroll(); - } - - async maybeEnroll() { - if (this.#isEnrolled) { - return Promise.resolve({ isEnrolled: true }); - } - - try { - this.#setEnrolled( - await lazy.IPProtectionService.guardian.isLinkedToGuardian( - /* only cache: */ false - ) - ); - } catch (error) { - return Promise.resolve({ isEnrolled: false, error: error?.message }); - } - - if (this.#isEnrolled) { - return Promise.resolve({ isEnrolled: true }); - } - - if (this.#enrolling) { - return this.#enrolling; - } - - this.#enrolling = lazy.IPProtectionService.guardian - .enroll() - .then(enrollment => { - let isEnrolled = enrollment?.ok; - - lazy.logConsole.debug( - "Guardian:", - isEnrolled ? "Enrolled" : "Enrollment Failed" - ); - - this.#setEnrolled(isEnrolled); - return { isEnrolled, error: enrollment?.error }; - }) - .catch(error => { - return { isEnrolled: false, error: error?.message }; - }) - .finally(() => { - this.#enrolling = null; - }); - - return this.#enrolling; - } - - get isEnrolled() { - return this.#isEnrolled; - } - - #setEnrolled(isEnrolled) { - if (this.#isEnrolled === isEnrolled) { - return; - } - - this.#isEnrolled = isEnrolled; - lazy.IPProtectionService.updateState(); - } -} - -const IPPEnrollHelper = new IPPEnrollHelperSingleton(); - -export { IPPEnrollHelper }; diff --git a/browser/components/ipprotection/IPProtectionHelpers.sys.mjs b/browser/components/ipprotection/IPProtectionHelpers.sys.mjs @@ -24,7 +24,7 @@ ChromeUtils.defineESModuleGetters(lazy, { }); import { IPPAutoStartHelpers } from "resource:///modules/ipprotection/IPPAutoStart.sys.mjs"; -import { IPPEnrollHelper } from "resource:///modules/ipprotection/IPPEnrollHelper.sys.mjs"; +import { IPPEnrollAndEntitleManager } from "resource:///modules/ipprotection/IPPEnrollAndEntitleManager.sys.mjs"; import { IPPNimbusHelper } from "resource:///modules/ipprotection/IPPNimbusHelper.sys.mjs"; import { IPProtectionServerlist } from "resource:///modules/ipprotection/IPProtectionServerlist.sys.mjs"; import { IPPSignInWatcher } from "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs"; @@ -75,7 +75,7 @@ class UIHelper { /** * This simple class resets the account data when needed */ -class AccountResetHelper { +class ProxyResetHelper { constructor() { this.handleEvent = this.#handleEvent.bind(this); } @@ -97,15 +97,19 @@ class AccountResetHelper { } #handleEvent(_event) { - // Reset stored account information and stop the proxy, - // if the account is no longer available. + if (!lazy.IPProtectionService.proxyManager) { + return; + } + if ( - (lazy.IPProtectionService.hasEntitlement && - lazy.IPProtectionService.state === - lazy.IPProtectionStates.UNAVAILABLE) || + lazy.IPProtectionService.state === lazy.IPProtectionStates.UNAVAILABLE || lazy.IPProtectionService.state === lazy.IPProtectionStates.UNAUTHENTICATED ) { - lazy.IPProtectionService.resetAccount(); + if (lazy.IPProtectionService.proxyManager.active) { + lazy.IPProtectionService.proxyManager.stop(false); + } + + lazy.IPProtectionService.proxyManager.reset(); } } } @@ -122,7 +126,10 @@ class VPNAddonHelper { initOnStartupCompleted() { this.addonVPNListener = { onInstallEnded(_install, addon) { - if (addon.id === VPN_ADDON_ID && lazy.IPProtectionService.hasUpgraded) { + if ( + addon.id === VPN_ADDON_ID && + IPPEnrollAndEntitleManager.hasUpgraded + ) { // Place the widget in the customization palette. lazy.CustomizableUI.removeWidgetFromArea( lazy.IPProtectionWidget.WIDGET_ID @@ -150,10 +157,10 @@ class VPNAddonHelper { const IPPHelpers = [ IPPStartupCache, IPPSignInWatcher, - IPPEnrollHelper, IPProtectionServerlist, + IPPEnrollAndEntitleManager, new UIHelper(), - new AccountResetHelper(), + new ProxyResetHelper(), new VPNAddonHelper(), ...IPPAutoStartHelpers, IPPNimbusHelper, diff --git a/browser/components/ipprotection/IPProtectionPanel.sys.mjs b/browser/components/ipprotection/IPProtectionPanel.sys.mjs @@ -7,6 +7,8 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { CustomizableUI: "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", + IPPEnrollAndEntitleManager: + "resource:///modules/ipprotection/IPPEnrollAndEntitleManager.sys.mjs", IPProtectionService: "resource:///modules/ipprotection/IPProtectionService.sys.mjs", IPProtectionStates: @@ -110,8 +112,7 @@ export class IPProtectionPanel { constructor(window, variant = "") { this.handleEvent = this.#handleEvent.bind(this); - let { activatedAt: protectionEnabledSince, hasUpgraded } = - lazy.IPProtectionService; + let { activatedAt: protectionEnabledSince } = lazy.IPProtectionService; this.state = { isSignedOut: !lazy.IPPSignInWatcher.isSignedIn, @@ -123,7 +124,7 @@ export class IPProtectionPanel { }, error: "", variant, - hasUpgraded, + hasUpgraded: lazy.IPPEnrollAndEntitleManager.hasUpgraded, }; if (window) { @@ -200,7 +201,7 @@ export class IPProtectionPanel { */ showing(panelView) { if (this.initiatedUpgrade) { - lazy.IPProtectionService.refetchEntitlement(); + lazy.IPPEnrollAndEntitleManager.refetchEntitlement(); this.initiatedUpgrade = false; } @@ -326,9 +327,17 @@ export class IPProtectionPanel { "IPProtectionService:StateChanged", this.handleEvent ); + lazy.IPPEnrollAndEntitleManager.addEventListener( + "IPPEnrollAndEntitleManager:StateChanged", + this.handleEvent + ); } #removeProxyListeners() { + lazy.IPPEnrollAndEntitleManager.removeEventListener( + "IPPEnrollAndEntitleManager:StateChanged", + this.handleEvent + ); lazy.IPProtectionService.removeEventListener( "IPProtectionService:StateChanged", this.handleEvent @@ -354,12 +363,12 @@ export class IPProtectionPanel { this.close(); } else if (event.type == "IPProtection:SignIn") { this.startLoginFlow(); - } else if (event.type == "IPProtectionService:StateChanged") { - let { - state, - activatedAt: protectionEnabledSince, - hasUpgraded, - } = lazy.IPProtectionService; + } else if ( + event.type == "IPProtectionService:StateChanged" || + event.type === "IPPEnrollAndEntitleManager:StateChanged" + ) { + let { state, activatedAt: protectionEnabledSince } = + lazy.IPProtectionService; let hasError = state === lazy.IPProtectionStates.ERROR && lazy.IPProtectionService.errors.includes(ERRORS.GENERIC); @@ -368,7 +377,7 @@ export class IPProtectionPanel { isSignedOut: !lazy.IPPSignInWatcher.isSignedIn, isProtectionEnabled: !!protectionEnabledSince, protectionEnabledSince, - hasUpgraded, + hasUpgraded: lazy.IPPEnrollAndEntitleManager.hasUpgraded, error: hasError ? ERRORS.GENERIC : "", }); } diff --git a/browser/components/ipprotection/IPProtectionService.sys.mjs b/browser/components/ipprotection/IPProtectionService.sys.mjs @@ -8,7 +8,8 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { GuardianClient: "resource:///modules/ipprotection/GuardianClient.sys.mjs", - IPPEnrollHelper: "resource:///modules/ipprotection/IPPEnrollHelper.sys.mjs", + IPPEnrollAndEntitleManager: + "resource:///modules/ipprotection/IPPEnrollAndEntitleManager.sys.mjs", IPPHelpers: "resource:///modules/ipprotection/IPProtectionHelpers.sys.mjs", IPPNimbusHelper: "resource:///modules/ipprotection/IPPNimbusHelper.sys.mjs", IPPProxyManager: "resource:///modules/ipprotection/IPPProxyManager.sys.mjs", @@ -74,16 +75,11 @@ export const IPProtectionStates = Object.freeze({ class IPProtectionServiceSingleton extends EventTarget { #state = IPProtectionStates.UNINITIALIZED; - // Prevents multiple `#updateState()` executions at once. - #updating = false; - errors = []; guardian = null; proxyManager = null; - #entitlement = null; - #helpers = null; /** @@ -97,24 +93,6 @@ class IPProtectionServiceSingleton extends EventTarget { } /** - * Checks if a user has upgraded. - * - * @returns {boolean} - */ - get hasUpgraded() { - return this.#entitlement?.subscribed; - } - - /** - * Checks if the service has an entitlement object - * - * @returns {boolean} - */ - get hasEntitlement() { - return !!this.#entitlement; - } - - /** * Checks if the proxy is active and was activated. * * @returns {Date} @@ -160,11 +138,10 @@ class IPProtectionServiceSingleton extends EventTarget { } this.proxyManager = new lazy.IPPProxyManager(this.guardian); - this.#entitlement = lazy.IPPStartupCache.entitlement; this.#helpers.forEach(helper => helper.init()); - await this.#updateState(); + this.#updateState(); if (lazy.IPPStartupCache.isStartupCompleted) { this.initOnStartupCompleted(); @@ -184,7 +161,6 @@ class IPProtectionServiceSingleton extends EventTarget { } this.proxyManager?.destroy(); - this.#entitlement = null; this.errors = []; this.#helpers.forEach(helper => helper.uninit()); @@ -207,16 +183,16 @@ class IPProtectionServiceSingleton extends EventTarget { async start(userAction = true) { await lazy.IPProtectionServerlist.maybeFetchList(); - // Wait for enrollment to finish. - const enrollData = await lazy.IPPEnrollHelper.maybeEnroll(); - if (!enrollData || !enrollData.isEnrolled) { - this.setErrorState(enrollData.error || ERRORS.GENERIC); + const enrollAndEntitleData = + await lazy.IPPEnrollAndEntitleManager.maybeEnrollAndEntitle(); + if (!enrollAndEntitleData || !enrollAndEntitleData.isEnrolledAndEntitled) { + this.setErrorState(enrollAndEntitleData.error || ERRORS.GENERIC); return; } // Retry getting state if the previous attempt failed. if (this.#state === IPProtectionStates.ERROR) { - await this.#updateState(); + this.#updateState(); } if (this.#state !== IPProtectionStates.READY) { @@ -285,60 +261,6 @@ class IPProtectionServiceSingleton extends EventTarget { } } - /** - * Reset the statuses that are set based on a FxA account. - */ - resetAccount() { - this.#entitlement = null; - lazy.IPPStartupCache.storeEntitlement(null); - - if (this.proxyManager?.active) { - this.stop(false); - } - this.proxyManager.reset(); - } - - /** - * Clear the current entitlement and requests a state update to dispatch - * the current hasUpgraded status. - * - * @returns {Promise<void>} - */ - async refetchEntitlement() { - let prevState = this.#state; - this.#entitlement = null; - lazy.IPPStartupCache.storeEntitlement(null); - - await this.#updateState(); - // hasUpgraded might not change the state. - if (prevState === this.#state) { - this.#stateChanged(this.#state, prevState); - } - } - - /** - * Gets the entitlement information for the user. - */ - async #getEntitlement() { - if (this.#entitlement) { - return this.#entitlement; - } - - let { status, entitlement, error } = await this.guardian.fetchUserInfo(); - lazy.logConsole.debug("Entitlement:", { status, entitlement, error }); - - if (error || !entitlement || status != 200) { - this.#setErrorState(error || `Status: ${status}`); - return null; - } - - // Entitlement is set until the user changes or it is cleared to check subscription status. - this.#entitlement = entitlement; - lazy.IPPStartupCache.storeEntitlement(entitlement); - - return entitlement; - } - async startLoginFlow(browser) { return lazy.SpecialMessageActions.fxaSignInFlow(SIGNIN_DATA, browser); } @@ -348,16 +270,8 @@ class IPProtectionServiceSingleton extends EventTarget { * * Updates will be queued if another update is in progress. */ - async #updateState() { - // Wait for any current updates to finish. - await this.#updating; - - // Start a new update - this.#updating = this.#checkState(); - let newState = await this.#updating; - this.#updating = false; - - this.#setState(newState); + #updateState() { + this.#setState(this.#checkState()); } /** @@ -365,7 +279,7 @@ class IPProtectionServiceSingleton extends EventTarget { * * @returns {Promise<IPProtectionStates>} */ - async #checkState() { + #checkState() { // The IPP feature is disabled. if (!this.featureEnabled) { return IPProtectionStates.UNINITIALIZED; @@ -391,23 +305,8 @@ class IPProtectionServiceSingleton extends EventTarget { return IPProtectionStates.ACTIVE; } - // The proxy can be started if the current entitlement is valid. - if (this.#entitlement?.uid) { - return IPProtectionStates.READY; - } - - if (!lazy.IPPEnrollHelper.isEnrolled) { - return !eligible - ? IPProtectionStates.UNAVAILABLE - : IPProtectionStates.READY; - } - - // The following are remote authentication checks and should be avoided - // whenever possible. - - // Check if the current account can get an entitlement. - let entitled = await this.#getEntitlement(); - if (!entitled && !eligible) { + // Check if the current account is enrolled and has an entitlement. + if (!lazy.IPPEnrollAndEntitleManager.isEnrolledAndEntitled && !eligible) { return IPProtectionStates.UNAVAILABLE; } diff --git a/browser/components/ipprotection/docs/Components.rst b/browser/components/ipprotection/docs/Components.rst @@ -35,7 +35,7 @@ A diagram of all the main components is the following: IPPNimbusHelper["Nimbus Eligibility Helper"] IPPAutoStart["Auto-Start Helper"] IPPEarlyStartupFilter["Early Startup Filter Helper"] - IPPEnrollHelper["Enrollment Helper"] + IPPEnrollAndEntitleManager["Enroll & Entitle Manager"] end %% Proxy stack @@ -137,7 +137,7 @@ IPPNimbusHelper Monitors the Nimbus feature (``NimbusFeatures.ipProtection``) and triggers a state recomputation on updates. -IPPEnrollHelper +IPPEnrollAndEntitleManager Orchestrates the user enrollment flow with Guardian and updates the service when enrollment status changes. diff --git a/browser/components/ipprotection/docs/StateMachine.rst b/browser/components/ipprotection/docs/StateMachine.rst @@ -34,5 +34,4 @@ Events and integration points - ``IPProtectionService:StateChanged`` is dispatched on state changes with ``detail.state`` and ``detail.prevState``. - Helpers can call ``IPProtectionService.updateState()`` to request a recomputation. -- Public actions: ``start(userAction)``, ``stop(userAction)``, - ``resetAccount()``, and ``startLoginFlow(browser)``. +- Public actions: ``start(userAction)``, ``stop(userAction)``, and ``startLoginFlow(browser)``. diff --git a/browser/components/ipprotection/moz.build b/browser/components/ipprotection/moz.build @@ -13,7 +13,7 @@ EXTRA_JS_MODULES.ipprotection += [ "GuardianClient.sys.mjs", "IPPAutoStart.sys.mjs", "IPPChannelFilter.sys.mjs", - "IPPEnrollHelper.sys.mjs", + "IPPEnrollAndEntitleManager.sys.mjs", "IPPExceptionsManager.sys.mjs", "IPPNetworkErrorObserver.sys.mjs", "IPPNimbusHelper.sys.mjs", diff --git a/browser/components/ipprotection/tests/browser/browser_IPProtectionService.js b/browser/components/ipprotection/tests/browser/browser_IPProtectionService.js @@ -66,14 +66,14 @@ add_task(async function test_IPProtectionService_updateEligibility() { add_task(async function test_IPProtectionService_updateEnrollment() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); await SpecialPowers.pushPrefEnv({ set: [["browser.ipProtection.enabled", true]], }); - // hasEnrolled / isEnrolled is async so wait for widget. + // isEnrolledAndEntitled is async so wait for widget. await waitForWidgetAdded(); let button = document.getElementById(IPProtectionWidget.WIDGET_ID); @@ -91,7 +91,7 @@ add_task(async function test_IPProtectionService_updateEnrollment() { */ add_task(async function test_IPProtectionService_enroll() { setupService({ - isEnrolled: false, + isEnrolledAndEntitled: false, canEnroll: true, }); @@ -103,7 +103,7 @@ add_task(async function test_IPProtectionService_enroll() { isSignedIn: true, }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); Assert.equal( IPProtectionService.state, IPProtectionStates.READY, @@ -111,7 +111,7 @@ add_task(async function test_IPProtectionService_enroll() { ); setupService({ - isEnrolled: true, + isEnrolledAndEntitled: true, }); await openPanel(); await IPProtectionService.enrolling; @@ -133,7 +133,7 @@ add_task(async function test_IPProtectionService_enroll() { add_task( async function test_IPProtectionService_enroll_when_enrolled_in_experiment() { setupService({ - isEnrolled: false, + isEnrolledAndEntitled: false, isSignedIn: true, canEnroll: true, }); @@ -146,7 +146,7 @@ add_task( await waitForWidgetAdded(); setupService({ - isEnrolled: true, + isEnrolledAndEntitled: true, }); let content = await openPanel(); @@ -175,7 +175,7 @@ add_task( add_task( async function test_IPProtectionService_updateEntitlement_in_experiment() { setupService({ - isEnrolled: false, + isEnrolledAndEntitled: false, isSignedIn: true, canEnroll: true, }); @@ -188,7 +188,7 @@ add_task( await waitForWidgetAdded(); setupService({ - isEnrolled: true, + isEnrolledAndEntitled: true, }); await openPanel(); await IPProtectionService.enrolling; @@ -210,7 +210,7 @@ add_task( add_task(async function test_IPProtectionService_updateEntitlement() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); await SpecialPowers.pushPrefEnv({ @@ -232,7 +232,7 @@ add_task(async function test_IPProtectionService_updateEntitlement() { add_task(async function test_ipprotection_ready() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); const sandbox = sinon.createSandbox(); @@ -268,7 +268,7 @@ add_task(async function test_IPProtectionService_pass_errors() { let cleanupAlpha = await setupExperiment({ enabled: true, variant: "alpha" }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); let content = await openPanel(); @@ -322,17 +322,17 @@ add_task(async function test_IPProtectionService_pass_errors() { add_task(async function test_IPProtectionService_retry_errors() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, canEnroll: true, }); let cleanupAlpha = await setupExperiment({ enabled: true, variant: "alpha" }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); let content = await openPanel(); // Mock a failure - IPProtectionService.resetAccount(); + IPPEnrollAndEntitleManager.resetEntitlement(); await IPProtectionService.setErrorState(); let startedEventPromise = BrowserTestUtils.waitForEvent( @@ -368,7 +368,7 @@ add_task(async function test_IPProtectionService_stop_on_signout() { }); let cleanupAlpha = await setupExperiment({ enabled: true, variant: "alpha" }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); let content = await openPanel(); @@ -407,8 +407,8 @@ add_task(async function test_IPProtectionService_stop_on_signout() { setupService({ isSignedIn: false, }); - let signedOut = IPProtectionService.updateState(); - await Promise.all([signedOut, vpnOffPromise]); + IPProtectionService.updateState(); + await vpnOffPromise; Assert.notStrictEqual( IPProtectionService.state, @@ -446,7 +446,7 @@ add_task(async function test_IPProtectionService_reload() { let cleanupAlpha = await setupExperiment({ enabled: true, variant: "alpha" }); setupService({ - isEnrolled: true, + isEnrolledAndEntitled: true, }); let content = await openPanel(); @@ -507,7 +507,7 @@ add_task(async function test_IPProtectionService_addon() { ); setupService({ - isEnrolled: true, + isEnrolledAndEntitled: true, isSignedIn: true, entitlement: { status: 200, @@ -519,7 +519,7 @@ add_task(async function test_IPProtectionService_addon() { }, }, }); - await IPProtectionService.refetchEntitlement(); + await IPPEnrollAndEntitleManager.refetchEntitlement(); const extension = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", @@ -554,7 +554,7 @@ add_task(async function test_IPProtectionService_addon() { await extension.unload(); cleanupService(); // hasUpgraded=false - await IPProtectionService.updateState(); + await IPPEnrollAndEntitleManager.refetchEntitlement(); const extension2 = ExtensionTestUtils.loadExtension({ useAddonManager: "permanent", diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_content.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_content.js @@ -203,7 +203,7 @@ add_task(async function test_ipprotection_events_on_toggle() { // Reset service state. cleanupService(); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); let button = document.getElementById(lazy.IPProtectionWidget.WIDGET_ID); let panelView = PanelMultiView.getViewNode( @@ -220,9 +220,9 @@ add_task(async function test_ipprotection_events_on_toggle() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); - await IPProtectionService.updateState(); + await IPPEnrollAndEntitleManager.refetchEntitlement(); Assert.ok( BrowserTestUtils.isVisible(content), diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_telemetry.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_telemetry.js @@ -49,9 +49,9 @@ add_task(async function user_toggle_on_and_off() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); await content.updateComplete; let toggle = content.connectionToggleEl; @@ -134,7 +134,7 @@ add_task(async function toggle_off_on_shutdown() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); IPProtectionService.updateState(); await content.updateComplete; diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_toolbar.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_toolbar.js @@ -73,9 +73,9 @@ add_task(async function toolbar_icon_status() { let content = panelView.querySelector(IPProtectionPanel.CONTENT_TAGNAME); setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); await putServerInRemoteSettings(); content.requestUpdate(); await content.updateComplete; @@ -125,9 +125,9 @@ add_task(async function toolbar_icon_status() { add_task(async function toolbar_icon_status_new_window() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); let content = await openPanel(); @@ -204,7 +204,7 @@ add_task(async function customize_toolbar_remove_widget() { add_task(async function toolbar_placement_customized() { setupService({ isSignedIn: true, - isEnrolled: true, + isEnrolledAndEntitled: true, }); let start = CustomizableUI.getPlacementOfWidget(IPProtectionWidget.WIDGET_ID); diff --git a/browser/components/ipprotection/tests/browser/head.js b/browser/components/ipprotection/tests/browser/head.js @@ -17,8 +17,8 @@ const { IPPSignInWatcher } = ChromeUtils.importESModule( "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs" ); -const { IPPEnrollHelper } = ChromeUtils.importESModule( - "resource:///modules/ipprotection/IPPEnrollHelper.sys.mjs" +const { IPPEnrollAndEntitleManager } = ChromeUtils.importESModule( + "resource:///modules/ipprotection/IPPEnrollAndEntitleManager.sys.mjs" ); const { HttpServer, HTTP_403 } = ChromeUtils.importESModule( @@ -223,7 +223,7 @@ let DEFAULT_EXPERIMENT = { let DEFAULT_SERVICE_STATUS = { isSignedIn: false, - isEnrolled: false, + isEnrolledAndEntitled: false, canEnroll: true, entitlement: { status: 200, @@ -243,7 +243,7 @@ let DEFAULT_SERVICE_STATUS = { /* exported DEFAULT_SERVICE_STATUS */ let STUBS = { - isEnrolled: undefined, + isEnrolledAndEntitled: undefined, enroll: undefined, fetchUserInfo: undefined, fetchProxyPass: undefined, @@ -275,7 +275,10 @@ add_setup(async function setupVPN() { function setupStubs(stubs = STUBS) { stubs.isSignedIn = setupSandbox.stub(IPPSignInWatcher, "isSignedIn"); - stubs.isEnrolled = setupSandbox.stub(IPPEnrollHelper, "isEnrolled"); + stubs.isEnrolledAndEntitled = setupSandbox.stub( + IPPEnrollAndEntitleManager, + "isEnrolledAndEntitled" + ); stubs.enroll = setupSandbox.stub(IPProtectionService.guardian, "enroll"); stubs.fetchUserInfo = setupSandbox.stub( IPProtectionService.guardian, @@ -291,7 +294,7 @@ function setupStubs(stubs = STUBS) { function setupService( { isSignedIn, - isEnrolled, + isEnrolledAndEntitled, canEnroll, entitlement, proxyPass, @@ -302,8 +305,8 @@ function setupService( stubs.isSignedIn.get(() => isSignedIn); } - if (typeof isEnrolled != "undefined") { - stubs.isEnrolled.get(() => isEnrolled); + if (typeof isEnrolledAndEntitled != "undefined") { + stubs.isEnrolledAndEntitled.get(() => isEnrolledAndEntitled); } if (typeof canEnroll != "undefined") { diff --git a/browser/components/ipprotection/tests/xpcshell/test_IPProtectionPanel.js b/browser/components/ipprotection/tests/xpcshell/test_IPProtectionPanel.js @@ -12,8 +12,8 @@ const { IPProtectionService, IPProtectionStates } = ChromeUtils.importESModule( const { IPPSignInWatcher } = ChromeUtils.importESModule( "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs" ); -const { IPPEnrollHelper } = ChromeUtils.importESModule( - "resource:///modules/ipprotection/IPPEnrollHelper.sys.mjs" +const { IPPEnrollAndEntitleManager } = ChromeUtils.importESModule( + "resource:///modules/ipprotection/IPPEnrollAndEntitleManager.sys.mjs" ); /** @@ -135,7 +135,9 @@ add_task(async function test_updateState() { add_task(async function test_IPProtectionPanel_signedIn() { let sandbox = sinon.createSandbox(); sandbox.stub(IPPSignInWatcher, "isSignedIn").get(() => true); - sandbox.stub(IPPEnrollHelper, "isEnrolled").get(() => true); + sandbox + .stub(IPPEnrollAndEntitleManager, "isEnrolledAndEntitled") + .get(() => true); sandbox .stub(IPProtectionService.guardian, "isLinkedToGuardian") .resolves(true); @@ -159,7 +161,7 @@ add_task(async function test_IPProtectionPanel_signedIn() { "IPProtectionService:StateChanged", () => IPProtectionService.state === IPProtectionStates.READY ); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); await signedInEventPromise; @@ -196,7 +198,7 @@ add_task(async function test_IPProtectionPanel_signedOut() { "IPProtectionService:StateChanged", () => IPProtectionService.state === IPProtectionStates.UNAVAILABLE ); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); await signedOutEventPromise; @@ -226,7 +228,9 @@ add_task(async function test_IPProtectionPanel_started_stopped() { let sandbox = sinon.createSandbox(); sandbox.stub(IPPSignInWatcher, "isSignedIn").get(() => true); - sandbox.stub(IPPEnrollHelper, "isEnrolled").get(() => true); + sandbox + .stub(IPPEnrollAndEntitleManager, "isEnrolledAndEntitled") + .get(() => true); sandbox .stub(IPProtectionService.guardian, "isLinkedToGuardian") .resolves(true); @@ -248,7 +252,7 @@ add_task(async function test_IPProtectionPanel_started_stopped() { }, }); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); let startedEventPromise = waitForEvent( IPProtectionService, diff --git a/browser/components/ipprotection/tests/xpcshell/test_IPProtectionService.js b/browser/components/ipprotection/tests/xpcshell/test_IPProtectionService.js @@ -15,8 +15,8 @@ const { IPProtectionService, IPProtectionStates } = ChromeUtils.importESModule( const { IPPSignInWatcher } = ChromeUtils.importESModule( "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs" ); -const { IPPEnrollHelper } = ChromeUtils.importESModule( - "resource:///modules/ipprotection/IPPEnrollHelper.sys.mjs" +const { IPPEnrollAndEntitleManager } = ChromeUtils.importESModule( + "resource:///modules/ipprotection/IPPEnrollAndEntitleManager.sys.mjs" ); do_get_profile(); @@ -127,14 +127,15 @@ add_task(async function test_IPProtectionService_stop() { let sandbox = sinon.createSandbox(); setupStubs(sandbox); - IPProtectionService.init(); - - await waitForEvent( + const waitForReady = waitForEvent( IPProtectionService, "IPProtectionService:StateChanged", () => IPProtectionService.state === IPProtectionStates.READY ); + IPProtectionService.init(); + await waitForReady; + await IPProtectionService.start(); let stoppedEventPromise = waitForEvent( @@ -168,7 +169,9 @@ add_task(async function test_IPProtectionService_stop() { */ add_task(async function test_IPProtectionService_updateState_signedIn() { let sandbox = sinon.createSandbox(); - sandbox.stub(IPPEnrollHelper, "isEnrolled").get(() => true); + sandbox + .stub(IPPEnrollAndEntitleManager, "isEnrolledAndEntitled") + .get(() => true); await IPProtectionService.init(); @@ -180,7 +183,7 @@ add_task(async function test_IPProtectionService_updateState_signedIn() { () => IPProtectionService.state === IPProtectionStates.READY ); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); await signedInEventPromise; @@ -207,7 +210,7 @@ add_task(async function test_IPProtectionService_updateState_signedOut() { () => IPProtectionService.state === IPProtectionStates.UNAVAILABLE ); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); await signedOutEventPromise; @@ -221,22 +224,22 @@ add_task(async function test_IPProtectionService_updateState_signedOut() { }); /** - * Tests that refetchEntitlement returns true if a linked VPN is found - * and sends an event. + * Tests that refetchEntitlement works as expected if a linked VPN is found and sends an event. */ add_task( async function test_IPProtectionService_refetchEntitlement_has_vpn_linked() { const sandbox = sinon.createSandbox(); setupStubs(sandbox); - IPProtectionService.init(); - - await waitForEvent( + const waitForReady = waitForEvent( IPProtectionService, "IPProtectionService:StateChanged", () => IPProtectionService.state === IPProtectionStates.READY ); + IPProtectionService.init(); + await waitForReady; + IPProtectionService.guardian.fetchUserInfo.resolves({ status: 200, error: null, @@ -248,16 +251,19 @@ add_task( }); let hasUpgradedEventPromise = waitForEvent( - IPProtectionService, - "IPProtectionService:StateChanged", - () => IPProtectionService.hasUpgraded + IPPEnrollAndEntitleManager, + "IPPEnrollAndEntitleManager:StateChanged", + () => IPPEnrollAndEntitleManager.hasUpgraded ); - await IPProtectionService.refetchEntitlement(); + await IPPEnrollAndEntitleManager.refetchEntitlement(); await hasUpgradedEventPromise; - Assert.ok(IPProtectionService.hasUpgraded, "hasUpgraded should be true"); + Assert.ok( + IPPEnrollAndEntitleManager.hasUpgraded, + "hasUpgraded should be true" + ); IPProtectionService.uninit(); sandbox.restore(); @@ -265,7 +271,7 @@ add_task( ); /** - * Tests that refetchEntitlement returns false if no linked VPN is found and + * Tests that refetchEntlement returns errors if no linked VPN is found and * sends an event. */ add_task( @@ -282,15 +288,18 @@ add_task( }); let hasUpgradedEventPromise = waitForEvent( - IPProtectionService, - "IPProtectionService:StateChanged" + IPPEnrollAndEntitleManager, + "IPPEnrollAndEntitleManager:StateChanged" ); - await IPProtectionService.refetchEntitlement(); + await IPPEnrollAndEntitleManager.refetchEntitlement(); await hasUpgradedEventPromise; - Assert.ok(!IPProtectionService.hasUpgraded, "hasUpgraded should be false"); + Assert.ok( + !IPPEnrollAndEntitleManager.hasUpgraded, + "hasUpgraded should be false" + ); IPProtectionService.uninit(); sandbox.restore(); @@ -298,8 +307,8 @@ add_task( ); /** - * Tests that refetchEntitlement returns false when signed out and sends - * an event. + * Tests that signing off generates a reset of the entitlement and the sending + * of an event. */ add_task(async function test_IPProtectionService_hasUpgraded_signed_out() { let sandbox = sinon.createSandbox(); @@ -313,12 +322,12 @@ add_task(async function test_IPProtectionService_hasUpgraded_signed_out() { IPProtectionService, "IPProtectionService:StateChanged" ); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); await signedOutEventPromise; Assert.ok( - !IPProtectionService.hasUpgraded, + !IPPEnrollAndEntitleManager.hasUpgraded, "hasUpgraded should be false in after signing out" ); diff --git a/browser/components/ipprotection/tests/xpcshell/test_IPProtectionStates.js b/browser/components/ipprotection/tests/xpcshell/test_IPProtectionStates.js @@ -12,8 +12,8 @@ const { IPPNimbusHelper } = ChromeUtils.importESModule( const { IPPSignInWatcher } = ChromeUtils.importESModule( "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs" ); -const { IPPEnrollHelper } = ChromeUtils.importESModule( - "resource:///modules/ipprotection/IPPEnrollHelper.sys.mjs" +const { IPPEnrollAndEntitleManager } = ChromeUtils.importESModule( + "resource:///modules/ipprotection/IPPEnrollAndEntitleManager.sys.mjs" ); do_get_profile(); @@ -74,7 +74,7 @@ add_task(async function test_IPProtectionStates_uninitialized() { sandbox.stub(IPPNimbusHelper, "isEligible").get(() => true); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); Assert.notStrictEqual( IPProtectionService.state, @@ -96,6 +96,7 @@ add_task(async function test_IPProtectionStates_unauthenticated() { .stub(IPProtectionService.guardian, "isLinkedToGuardian") .resolves(false); sandbox.stub(IPProtectionService.guardian, "enroll").resolves({ ok: true }); + sandbox.stub(IPPNimbusHelper, "isEligible").get(() => false); await IPProtectionService.init(); @@ -107,7 +108,7 @@ add_task(async function test_IPProtectionStates_unauthenticated() { sandbox.stub(IPPNimbusHelper, "isEligible").get(() => true); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); Assert.equal( IPProtectionService.state, @@ -117,7 +118,7 @@ add_task(async function test_IPProtectionStates_unauthenticated() { sandbox.stub(IPPSignInWatcher, "isSignedIn").get(() => false); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); Assert.equal( IPProtectionService.state, @@ -156,8 +157,8 @@ add_task(async function test_IPProtectionStates_enrolling() { IPProtectionService.guardian.isLinkedToGuardian.resolves(true); - const enrollData = await IPPEnrollHelper.maybeEnroll(); - Assert.ok(enrollData.isEnrolled, "Fully enrolled"); + const enrollData = await IPPEnrollAndEntitleManager.maybeEnrollAndEntitle(); + Assert.ok(enrollData.isEnrolledAndEntitled, "Fully enrolled and entitled"); Assert.equal( IPProtectionService.state, @@ -194,7 +195,7 @@ add_task(async function test_IPProtectionStates_ready() { sandbox.stub(IPPSignInWatcher, "isSignedIn").get(() => false); - await IPProtectionService.updateState(); + IPProtectionService.updateState(); Assert.notStrictEqual( IPProtectionService.state, @@ -229,14 +230,16 @@ add_task(async function test_IPProtectionStates_active() { }, }); - IPProtectionService.init(); - - await waitForEvent( + const waitForReady = waitForEvent( IPProtectionService, "IPProtectionService:StateChanged", () => IPProtectionService.state === IPProtectionStates.READY ); + IPProtectionService.init(); + + await waitForReady; + Assert.equal( IPProtectionService.state, IPProtectionStates.READY,