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 { export class AboutNewTabRedirectorParent extends BaseAboutNewTabRedirector {
#addonInitialized = false; #addonInitialized = false;
#suspendedChannels = []; #suspendedChannels = [];
#addonInitializedPromise = null;
#addonInitializedResolver = null;
constructor() { constructor() {
super(); super();
let { promise, resolve } = Promise.withResolvers();
this.#addonInitializedPromise = promise;
this.#addonInitializedResolver = resolve;
ChromeUtils.registerWindowActor("AboutNewTab", { ChromeUtils.registerWindowActor("AboutNewTab", {
parent: { parent: {
esModuleURI: "resource:///actors/AboutNewTabParent.sys.mjs", 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 * 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 * 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. * 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; this.#addonInitialized = true;
for (let suspendedChannel of this.#suspendedChannels) { for (let suspendedChannel of this.#suspendedChannels) {
suspendedChannel.resume(); suspendedChannel.resume();
} }
this.#suspendedChannels = []; 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) { newChannel(uri, loadInfo) {

View file

@ -48,45 +48,51 @@ this.builtin_newtab = class extends ExtensionAPI {
return; return;
} }
const { rootURI } = this.extension; try {
const { rootURI } = this.extension;
resProto.setSubstitutionWithFlags( resProto.setSubstitutionWithFlags(
ResourceSubstitution, ResourceSubstitution,
rootURI, rootURI,
Ci.nsISubstitutingProtocolHandler.ALLOW_CONTENT_ACCESS 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}/`
); );
L10nRegistry.getInstance().registerSources([newtabFileSource]);
// Dynamically register any Glean pings/metrics here. let aomStartup = Cc[
this.registerMetricsFromJson(); "@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() { onShutdown() {

View file

@ -10,7 +10,6 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
ActivityStream: "resource://newtab/lib/ActivityStream.sys.mjs", ActivityStream: "resource://newtab/lib/ActivityStream.sys.mjs",
AddonManager: "resource://gre/modules/AddonManager.sys.mjs", AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs", ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
}); });
@ -160,28 +159,13 @@ export const AboutNewTab = {
} }
if (AppConstants.BROWSER_NEWTAB_AS_ADDON) { if (AppConstants.BROWSER_NEWTAB_AS_ADDON) {
let addonPolicy = WebExtensionPolicy.getByID(BUILTIN_ADDON_ID); // Wait until the built-in addon has reported that it has finished
if (!addonPolicy) { // initializing.
// If this is the first time that the build flag was set to true, we let redirector = Cc[
// might not yet have refreshed the addon database cache yet, in which "@mozilla.org/network/protocol/about;1?what=newtab"
// case the addonPolicy will be null. In that case, we'll wait for the ].getService(Ci.nsIAboutModule).wrappedJSObject;
// 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);
}
if (!addonPolicy) { await redirector.promiseBuiltInAddonInitialized;
// 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);
}
} else { } else {
// We may have had the built-in addon installed in the past. Since the // 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 // flag is false, let's go ahead and remove it. We don't need to await on