Source code

Revision control

Copy as Markdown

Other Tools

/* 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, {
AboutWelcomeDefaults:
AboutWelcomeTelemetry:
SpecialMessageActions:
});
ChromeUtils.defineLazyGetter(lazy, "log", () => {
const { Logger } = ChromeUtils.importESModule(
);
return new Logger("AboutWelcomeParent");
});
ChromeUtils.defineLazyGetter(
lazy,
"Telemetry",
() => new lazy.AboutWelcomeTelemetry()
);
const DID_SEE_ABOUT_WELCOME_PREF = "trailhead.firstrun.didSeeAboutWelcome";
const AWTerminate = {
WINDOW_CLOSED: "welcome-window-closed",
TAB_CLOSED: "welcome-tab-closed",
APP_SHUT_DOWN: "app-shut-down",
ADDRESS_BAR_NAVIGATED: "address-bar-navigated",
};
const LIGHT_WEIGHT_THEMES = {
AUTOMATIC: "default-theme@mozilla.org",
DARK: "firefox-compact-dark@mozilla.org",
LIGHT: "firefox-compact-light@mozilla.org",
ALPENGLOW: "firefox-alpenglow@mozilla.org",
};
class AboutWelcomeObserver {
constructor() {
Services.obs.addObserver(this, "quit-application");
this.win = Services.focus.activeWindow;
if (!this.win) {
return;
}
this.terminateReason = AWTerminate.ADDRESS_BAR_NAVIGATED;
this.onWindowClose = () => {
this.terminateReason = AWTerminate.WINDOW_CLOSED;
};
this.onTabClose = () => {
this.terminateReason = AWTerminate.TAB_CLOSED;
};
this.win.addEventListener("TabClose", this.onTabClose, { once: true });
this.win.addEventListener("unload", this.onWindowClose, { once: true });
}
observe(aSubject, aTopic) {
switch (aTopic) {
case "quit-application":
this.terminateReason = AWTerminate.APP_SHUT_DOWN;
break;
}
}
// Added for testing
get AWTerminate() {
return AWTerminate;
}
stop() {
lazy.log.debug(`Terminate reason is ${this.terminateReason}`);
// Clear the entrypoint pref
Services.prefs.clearUserPref("browser.aboutwelcome.entrypoint");
Services.obs.removeObserver(this, "quit-application");
if (!this.win) {
return;
}
this.win.removeEventListener("TabClose", this.onTabClose);
this.win.removeEventListener("unload", this.onWindowClose);
this.win = null;
}
}
export class AboutWelcomeParent extends JSWindowActorParent {
constructor() {
super();
this.startAboutWelcomeObserver();
}
startAboutWelcomeObserver() {
this.AboutWelcomeObserver = new AboutWelcomeObserver();
}
// Static methods that calls into ShellService to check
// if Firefox is pinned or already default
static doesAppNeedPin() {
return lazy.ShellService.doesAppNeedPin();
}
static isDefaultBrowser() {
return lazy.ShellService.isDefaultBrowser();
}
didDestroy() {
if (this.AboutWelcomeObserver) {
this.AboutWelcomeObserver.stop();
}
this.RegionHomeObserver?.stop();
lazy.Telemetry.sendTelemetry({
event: "SESSION_END",
event_context: {
reason: this.AboutWelcomeObserver.terminateReason,
page: "about:welcome",
},
message_id: this.AWMessageId,
});
}
/**
* Handle messages from AboutWelcomeChild.sys.mjs
*
* @param {string} type
* @param {any=} data
* @param {Browser} the xul:browser rendering the page
*/
async onContentMessage(type, data, browser) {
lazy.log.debug(`Received content event: ${type}`);
switch (type) {
case "AWPage:SET_WELCOME_MESSAGE_SEEN":
this.AWMessageId = data;
try {
Services.prefs.setBoolPref(DID_SEE_ABOUT_WELCOME_PREF, true);
} catch (e) {
lazy.log.debug(`Fails to set ${DID_SEE_ABOUT_WELCOME_PREF}.`);
}
break;
case "AWPage:SPECIAL_ACTION":
return lazy.SpecialMessageActions.handleAction(data, browser);
case "AWPage:FXA_METRICS_FLOW_URI":
return lazy.FxAccounts.config.promiseMetricsFlowURI("aboutwelcome");
case "AWPage:TELEMETRY_EVENT":
lazy.Telemetry.sendTelemetry(data);
break;
case "AWPage:GET_ATTRIBUTION_DATA":
let attributionData =
await lazy.AboutWelcomeDefaults.getAttributionContent();
return attributionData;
case "AWPage:ENSURE_ADDON_INSTALLED":
return new Promise(resolve => {
let listener = {
onInstallEnded(install, addon) {
if (addon.id === data) {
lazy.AddonManager.removeInstallListener(listener);
resolve("complete");
}
},
onInstallCancelled() {
lazy.AddonManager.removeInstallListener(listener);
resolve("install cancelled");
},
onInstallFailed() {
lazy.AddonManager.removeInstallListener(listener);
resolve("install failed");
},
};
lazy.AddonManager.addInstallListener(listener);
});
case "AWPage:GET_ADDON_DETAILS":
let addonDetails =
await lazy.AboutWelcomeDefaults.getAddonFromRepository(data);
return {
label: addonDetails.name,
icon: addonDetails.iconURL,
type: addonDetails.type,
screenshots: addonDetails.screenshots,
url: addonDetails.url,
};
case "AWPage:SELECT_THEME":
await lazy.BuiltInThemes.ensureBuiltInThemes();
return lazy.AddonManager.getAddonByID(LIGHT_WEIGHT_THEMES[data]).then(
addon => addon.enable()
);
case "AWPage:GET_SELECTED_THEME":
let themes = await lazy.AddonManager.getAddonsByTypes(["theme"]);
let activeTheme = themes.find(addon => addon.isActive);
// Store the current theme ID so user can restore their previous theme.
if (activeTheme?.id) {
LIGHT_WEIGHT_THEMES.AUTOMATIC = activeTheme.id;
}
// convert this to the short form name that the front end code
// expects
let themeShortName = Object.keys(LIGHT_WEIGHT_THEMES).find(
key => LIGHT_WEIGHT_THEMES[key] === activeTheme?.id
);
return themeShortName?.toLowerCase();
case "AWPage:DOES_APP_NEED_PIN":
return AboutWelcomeParent.doesAppNeedPin();
case "AWPage:NEED_DEFAULT":
// Only need to set default if we're supposed to check and not default.
return (
Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser") &&
!AboutWelcomeParent.isDefaultBrowser()
);
case "AWPage:WAIT_FOR_MIGRATION_CLOSE":
// Support multiples types of migration: 1) content modal 2) old
// migration modal 3) standalone content modal
return new Promise(resolve => {
const topics = [
"MigrationWizard:Closed",
"MigrationWizard:Destroyed",
];
const observer = () => {
topics.forEach(t => Services.obs.removeObserver(observer, t));
resolve();
};
topics.forEach(t => Services.obs.addObserver(observer, t));
});
case "AWPage:GET_APP_AND_SYSTEM_LOCALE_INFO":
return lazy.LangPackMatcher.getAppAndSystemLocaleInfo();
case "AWPage:EVALUATE_SCREEN_TARGETING":
return lazy.AWScreenUtils.evaluateTargetingAndRemoveScreens(data);
case "AWPage:ADD_SCREEN_IMPRESSION":
return lazy.AWScreenUtils.addScreenImpression(data);
case "AWPage:NEGOTIATE_LANGPACK":
return lazy.LangPackMatcher.negotiateLangPackForLanguageMismatch(data);
case "AWPage:ENSURE_LANG_PACK_INSTALLED":
return lazy.LangPackMatcher.ensureLangPackInstalled(data);
case "AWPage:SET_REQUESTED_LOCALES":
return lazy.LangPackMatcher.setRequestedAppLocales(data);
case "AWPage:SEND_TO_DEVICE_EMAILS_SUPPORTED": {
return lazy.BrowserUtils.sendToDeviceEmailsSupported();
}
default:
lazy.log.debug(`Unexpected event ${type} was not handled.`);
}
return undefined;
}
/**
* @param {{name: string, data?: any}} message
* @override
*/
receiveMessage(message) {
const { name, data } = message;
let browser;
if (this.manager.rootFrameLoader) {
browser = this.manager.rootFrameLoader.ownerElement;
return this.onContentMessage(name, data, browser);
}
lazy.log.warn(`Not handling ${name} because the browser doesn't exist.`);
return null;
}
}
export class AboutWelcomeShoppingParent extends AboutWelcomeParent {
/**
* Handle messages from AboutWelcomeChild.sys.mjs
*
* @param {string} type
* @param {any=} data
* @param {Browser} the xul:browser rendering the page
*/
onContentMessage(type, data, browser) {
// Only handle the messages that are relevant to the shopping page.
switch (type) {
case "AWPage:SPECIAL_ACTION":
case "AWPage:TELEMETRY_EVENT":
case "AWPage:EVALUATE_SCREEN_TARGETING":
case "AWPage:ADD_SCREEN_IMPRESSION":
return super.onContentMessage(type, data, browser);
}
return undefined;
}
// Override unnecessary methods
startAboutWelcomeObserver() {}
didDestroy() {}
}