Bug 1968779 - Wait for builtin-newtab.js to signal that it has finished onStartup before allowing ActivityStream to be instantiated. r=pdahiya

Differential Revision: https://phabricator.services.mozilla.com/D251397
This commit is contained in:
Mike Conley 2025-05-28 19:17:35 +00:00 committed by mconley@mozilla.com
parent 4a3b6f3796
commit 0dd415b292
3 changed files with 70 additions and 62 deletions

View file

@ -464,10 +464,16 @@ class BaseAboutNewTabRedirector {
export class AboutNewTabRedirectorParent extends BaseAboutNewTabRedirector {
#addonInitialized = false;
#suspendedChannels = [];
#addonInitializedPromise = null;
#addonInitializedResolver = null;
constructor() {
super();
let { promise, resolve } = Promise.withResolvers();
this.#addonInitializedPromise = promise;
this.#addonInitializedResolver = resolve;
ChromeUtils.registerWindowActor("AboutNewTab", {
parent: {
esModuleURI: "resource:///actors/AboutNewTabParent.sys.mjs",
@ -499,18 +505,30 @@ export class AboutNewTabRedirectorParent extends BaseAboutNewTabRedirector {
* Waits for the AddonManager to be fully initialized, and for the built-in
* addon to be ready. Once that's done, it tterates any suspended channels and
* resumes them, now that the built-in addon has been set up.
*
* @returns {Promise<undefined>}
* Resolves when the built-in addon has initialized and all suspended
* channels are resumed.
*/
builtInAddonInitialized() {
notifyBuiltInAddonInitialized() {
this.#addonInitialized = true;
for (let suspendedChannel of this.#suspendedChannels) {
suspendedChannel.resume();
}
this.#suspendedChannels = [];
this.#addonInitializedResolver();
}
/**
* Returns a Promise that reoslves when the newtab built-in addon has notified
* that it has finished initializing. If this is somehow checked when
* BROWSER_NEWTAB_AS_ADDON is not true, then this always resolves.
*
* @type {Promise<undefined>}
*/
get promiseBuiltInAddonInitialized() {
if (!AppConstants.BROWSER_NEWTAB_AS_ADDON) {
return Promise.resolve();
}
return this.#addonInitializedPromise;
}
newChannel(uri, loadInfo) {

View file

@ -48,45 +48,51 @@ this.builtin_newtab = class extends ExtensionAPI {
return;
}
const { rootURI } = this.extension;
try {
const { rootURI } = this.extension;
resProto.setSubstitutionWithFlags(
ResourceSubstitution,
rootURI,
Ci.nsISubstitutingProtocolHandler.ALLOW_CONTENT_ACCESS
);
let aomStartup = Cc[
"@mozilla.org/addons/addon-manager-startup;1"
].getService(Ci.amIAddonManagerStartup);
const manifestURI = Services.io.newURI(
"manifest.json",
null,
this.extension.rootURI
);
this.#chromeHandle = aomStartup.registerChrome(manifestURI, [
["content", "newtab", "data/content", "contentaccessible=yes"],
]);
let redirector = Cc[
"@mozilla.org/network/protocol/about;1?what=newtab"
].getService(Ci.nsIAboutModule).wrappedJSObject;
if (this.extension.rootURI.spec.endsWith("newtab@mozilla.org.xpi!/")) {
// We must be a train-hopped XPI running in this app. This means we
// may have Fluent files or Glean pings/metrics to register dynamically.
const newtabFileSource = new L10nFileSource(
"newtab",
"app",
SUPPORTED_LOCALES,
`resource://newtab/locales/{locale}/`
resProto.setSubstitutionWithFlags(
ResourceSubstitution,
rootURI,
Ci.nsISubstitutingProtocolHandler.ALLOW_CONTENT_ACCESS
);
L10nRegistry.getInstance().registerSources([newtabFileSource]);
// Dynamically register any Glean pings/metrics here.
this.registerMetricsFromJson();
let aomStartup = Cc[
"@mozilla.org/addons/addon-manager-startup;1"
].getService(Ci.amIAddonManagerStartup);
const manifestURI = Services.io.newURI(
"manifest.json",
null,
this.extension.rootURI
);
this.#chromeHandle = aomStartup.registerChrome(manifestURI, [
["content", "newtab", "data/content", "contentaccessible=yes"],
]);
let redirector = Cc[
"@mozilla.org/network/protocol/about;1?what=newtab"
].getService(Ci.nsIAboutModule).wrappedJSObject;
if (this.extension.rootURI.spec.endsWith("newtab@mozilla.org.xpi!/")) {
// We must be a train-hopped XPI running in this app. This means we
// may have Fluent files or Glean pings/metrics to register dynamically.
const newtabFileSource = new L10nFileSource(
"newtab",
"app",
SUPPORTED_LOCALES,
`resource://newtab/locales/{locale}/`
);
L10nRegistry.getInstance().registerSources([newtabFileSource]);
// Dynamically register any Glean pings/metrics here.
this.registerMetricsFromJson();
}
redirector.notifyBuiltInAddonInitialized();
Glean.newtab.addonReadySuccess.set(true);
} catch(e) {
Glean.newtab.addonReadySuccess.set(false);
throw e;
}
redirector.builtInAddonInitialized();
}
onShutdown() {

View file

@ -10,7 +10,6 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ActivityStream: "resource://newtab/lib/ActivityStream.sys.mjs",
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
});
@ -160,28 +159,13 @@ export const AboutNewTab = {
}
if (AppConstants.BROWSER_NEWTAB_AS_ADDON) {
let addonPolicy = WebExtensionPolicy.getByID(BUILTIN_ADDON_ID);
if (!addonPolicy) {
// If this is the first time that the build flag was set to true, we
// might not yet have refreshed the addon database cache yet, in which
// case the addonPolicy will be null. In that case, we'll wait for the
// database to be ready before proceeding.
//
// We don't always just wait for the databaseReady Promise to resolve
// in order to avoid regressing newtab render times by needlessly
// going back to the event loop.
await lazy.AddonManagerPrivate.databaseReady;
addonPolicy = WebExtensionPolicy.getByID(BUILTIN_ADDON_ID);
}
// Wait until the built-in addon has reported that it has finished
// initializing.
let redirector = Cc[
"@mozilla.org/network/protocol/about;1?what=newtab"
].getService(Ci.nsIAboutModule).wrappedJSObject;
if (!addonPolicy) {
// Something's gone very wrong here, and we should collect telemetry
// about it.
Glean.newtab.addonReadySuccess.set(false);
} else {
await addonPolicy.readyPromise;
Glean.newtab.addonReadySuccess.set(true);
}
await redirector.promiseBuiltInAddonInitialized;
} else {
// We may have had the built-in addon installed in the past. Since the
// flag is false, let's go ahead and remove it. We don't need to await on