Bug 1633913 - Annotate ActivityStream actions that occur during startup, and have the cached about:home document ignore them. r=Mardak

Differential Revision: https://phabricator.services.mozilla.com/D80998
This commit is contained in:
Mike Conley 2020-07-07 23:07:00 +00:00
Родитель 9b0685fe4c
Коммит 26e47e4baf
18 изменённых файлов: 220 добавлений и 46 удалений

Просмотреть файл

@ -5071,6 +5071,7 @@ var AboutHomeStartupCache = {
_enabled: false,
_initted: false,
_hasWrittenThisSession: false,
_finalized: false,
init() {
if (this._initted) {
@ -5193,6 +5194,7 @@ var AboutHomeStartupCache = {
this._appender = null;
this._cacheDeferred = null;
this._finalized = false;
},
_aboutHomeURI: null,
@ -5237,6 +5239,7 @@ var AboutHomeStartupCache = {
if (this._cacheTask.isArmed) {
this.log.trace("Finalizing cache task on shutdown");
this._finalized = true;
await this._cacheTask.finalize();
}
},
@ -5659,6 +5662,12 @@ var AboutHomeStartupCache = {
if (!this._initted || !this._enabled) {
return;
}
if (this._finalized) {
this.log.trace("Ignoring preloaded newtab update after finalization.");
return;
}
this.log.trace("Preloaded about:newtab was updated.");
this._cacheTask.disarm();

Просмотреть файл

@ -62,6 +62,15 @@ export const rehydrationMiddleware = ({ getState }) => {
getState.didRequestInitialState = false;
return next => action => {
if (getState.didRehydrate || window.__FROM_STARTUP_CACHE__) {
// Startup messages can be safely ignored by the about:home document
// stored in the startup cache.
if (
window.__FROM_STARTUP_CACHE__ &&
action.meta &&
action.meta.isStartup
) {
return null;
}
return next(action);
}

Просмотреть файл

@ -2807,6 +2807,12 @@ const rehydrationMiddleware = ({
getState.didRequestInitialState = false;
return next => action => {
if (getState.didRehydrate || window.__FROM_STARTUP_CACHE__) {
// Startup messages can be safely ignored by the about:home document
// stored in the startup cache.
if (window.__FROM_STARTUP_CACHE__ && action.meta && action.meta.isStartup) {
return null;
}
return next(action);
}

Просмотреть файл

@ -991,6 +991,9 @@ class _ASRouter {
ac.BroadcastToContent({
type: at.AS_ROUTER_INITIALIZED,
data: ASRouterPreferences.specialConditions,
meta: {
isStartup: true,
},
})
);

Просмотреть файл

@ -735,6 +735,9 @@ this.ActivityStream = class ActivityStream {
data: {
locale: this.locale,
},
meta: {
isStartup: true,
},
}),
{ type: at.UNINIT }
);

Просмотреть файл

@ -214,12 +214,15 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
return this._providerSwitcher;
}
setupPrefs() {
setupPrefs(isStartup = false) {
// Send the initial state of the pref on our reducer
this.store.dispatch(
ac.BroadcastToContent({
type: at.DISCOVERY_STREAM_CONFIG_SETUP,
data: this.config,
meta: {
isStartup,
},
})
);
this.store.dispatch(
@ -230,6 +233,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
PREF_COLLECTION_DISMISSIBLE
],
},
meta: {
isStartup,
},
})
);
}
@ -378,7 +384,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
return layout;
}
updatePlacements(sendUpdate, layout) {
updatePlacements(sendUpdate, layout, isStartup = false) {
const placements = [];
const placementsMap = {};
for (const row of layout.filter(r => r.components && r.components.length)) {
@ -396,6 +402,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
sendUpdate({
type: at.DISCOVERY_STREAM_SPOCS_PLACEMENTS,
data: { placements },
meta: {
isStartup,
},
});
}
}
@ -421,6 +430,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
sendUpdate({
type: at.DISCOVERY_STREAM_LAYOUT_UPDATE,
data: layoutResp,
meta: {
isStartup,
},
});
if (layoutResp.spocs) {
@ -439,8 +451,11 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
url,
spocs_per_domain: layoutResp.spocs.spocs_per_domain,
},
meta: {
isStartup,
},
});
this.updatePlacements(sendUpdate, layoutResp.layout);
this.updatePlacements(sendUpdate, layoutResp.layout, isStartup);
}
}
}
@ -454,7 +469,11 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
* the scope for isStartup and the promises object.
* Combines feed results and promises for each component with a feed.
*/
buildFeedPromise({ newFeedsPromises, newFeeds }, isStartup, sendUpdate) {
buildFeedPromise(
{ newFeedsPromises, newFeeds },
isStartup = false,
sendUpdate
) {
return component => {
const { url } = component.feed;
@ -476,6 +495,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
feed: newFeeds[url],
url,
},
meta: {
isStartup,
},
});
// We grab affinities off the first feed for the moment.
@ -560,7 +582,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
.reduce(this.reduceFeedComponents(isStartup, sendUpdate), initialData);
}
async loadComponentFeeds(sendUpdate, isStartup) {
async loadComponentFeeds(sendUpdate, isStartup = false) {
const { DiscoveryStream } = this.store.getState();
if (!DiscoveryStream || !DiscoveryStream.layout) {
@ -587,6 +609,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
await this.cache.set("feeds", newFeeds);
sendUpdate({
type: at.DISCOVERY_STREAM_FEEDS_UPDATE,
meta: {
isStartup,
},
});
}
@ -827,6 +852,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
lastUpdated: spocsState.lastUpdated,
spocs: spocsState.spocs,
},
meta: {
isStartup,
},
});
}
@ -854,7 +882,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
* We can call this on startup because it's generally fast.
* It reports to devtools the last time the data in the cache was updated.
*/
async loadAffinityScoresCache() {
async loadAffinityScoresCache(isStartup = false) {
const cachedData = (await this.cache.get()) || {};
const { affinities } = cachedData;
if (this.personalized && affinities && affinities.scores) {
@ -874,6 +902,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
data: {
lastUpdated: this.domainAffinitiesLastUpdated,
},
meta: {
isStartup,
},
})
);
}
@ -1212,7 +1243,9 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
* @param {RefreshAll} options
*/
async refreshAll(options = {}) {
const affinityCacheLoadPromise = this.loadAffinityScoresCache();
const affinityCacheLoadPromise = this.loadAffinityScoresCache(
options.isStartup
);
const spocsPersonalized = this.store.getState().Prefs.values[
PREF_SPOCS_PERSONALIZED
@ -1699,7 +1732,7 @@ this.DiscoveryStreamFeed = class DiscoveryStreamFeed {
case at.INIT:
// During the initialization of Firefox:
// 1. Set-up listeners and initialize the redux state for config;
this.setupPrefs();
this.setupPrefs(true /* isStartup */);
// 2. If config.enabled is true, start loading data.
if (this.config.enabled) {
await this.enable();

Просмотреть файл

@ -94,8 +94,8 @@ this.HighlightsFeed = class HighlightsFeed {
}
postInit() {
SectionsManager.enableSection(SECTION_ID);
this.fetchHighlights({ broadcast: true });
SectionsManager.enableSection(SECTION_ID, true /* isStartup */);
this.fetchHighlights({ broadcast: true, isStartup: true });
this.downloadsManager.init(this.store);
}
@ -240,7 +240,7 @@ this.HighlightsFeed = class HighlightsFeed {
// If we already have the image for the card, use that immediately. Else
// asynchronously fetch the image. NEVER fetch a screenshot for downloads
if (!page.image && page.type !== "download") {
this.fetchImage(page);
this.fetchImage(page, options.isStartup);
}
// Adjust the type for 'history' items that are also 'bookmarked' when we
@ -285,7 +285,8 @@ this.HighlightsFeed = class HighlightsFeed {
SectionsManager.updateSection(
SECTION_ID,
{ rows: highlights },
shouldBroadcast
shouldBroadcast,
options.isStartup
);
}
@ -293,7 +294,7 @@ this.HighlightsFeed = class HighlightsFeed {
* Fetch an image for a given highlight and update the card with it. If no
* image is available then fallback to fetching a screenshot.
*/
fetchImage(page) {
fetchImage(page, isStartup = false) {
// Request a screenshot if we don't already have one pending
const { preview_image_url: imageUrl, url } = page;
return Screenshots.maybeCacheScreenshot(
@ -301,7 +302,13 @@ this.HighlightsFeed = class HighlightsFeed {
imageUrl || url,
"image",
image => {
SectionsManager.updateSectionCard(SECTION_ID, url, { image }, true);
SectionsManager.updateSectionCard(
SECTION_ID,
url,
{ image },
true,
isStartup
);
}
);
}
@ -315,7 +322,10 @@ this.HighlightsFeed = class HighlightsFeed {
break;
case at.SYSTEM_TICK:
case at.TOP_SITES_UPDATED:
this.fetchHighlights({ broadcast: false });
this.fetchHighlights({
broadcast: false,
isStartup: !!action.meta?.isStartup,
});
break;
case at.PREF_CHANGED:
// Update existing pages when the user changes what should be shown

Просмотреть файл

@ -128,7 +128,13 @@ this.PrefsFeed = class PrefsFeed {
// Set the initial state of all prefs in redux
this.store.dispatch(
ac.BroadcastToContent({ type: at.PREFS_INITIAL_VALUES, data: values })
ac.BroadcastToContent({
type: at.PREFS_INITIAL_VALUES,
data: values,
meta: {
isStartup: true,
},
})
);
}

Просмотреть файл

@ -106,7 +106,7 @@ this.RecommendationProviderSwitcher = class RecommendationProviderSwitcher {
/**
* Sets affinityProvider state to the correct version.
*/
setVersion() {
setVersion(isStartup = false) {
const version = this.store.getState().Prefs.values[
PREF_PERSONALIZATION_VERSION
];
@ -129,6 +129,9 @@ this.RecommendationProviderSwitcher = class RecommendationProviderSwitcher {
data: {
version,
},
meta: {
isStartup,
},
})
);
}
@ -178,7 +181,7 @@ this.RecommendationProviderSwitcher = class RecommendationProviderSwitcher {
onAction(action) {
switch (action.type) {
case at.INIT:
this.setVersion();
this.setVersion(true /* isStartup */);
break;
case at.DISCOVERY_STREAM_CONFIG_CHANGE:
this.teardown();

Просмотреть файл

@ -261,8 +261,8 @@ const SectionsManager = {
this.emit(this.REMOVE_SECTION, id);
this.sections.delete(id);
},
enableSection(id) {
this.updateSection(id, { enabled: true }, true);
enableSection(id, isStartup = false) {
this.updateSection(id, { enabled: true }, true, isStartup);
this.emit(this.ENABLE_SECTION, id);
},
disableSection(id) {
@ -278,14 +278,20 @@ const SectionsManager = {
this.updateSection(id, section, true)
);
},
updateSection(id, options, shouldBroadcast) {
updateSection(id, options, shouldBroadcast, isStartup = false) {
this.updateLinkMenuOptions(options, id);
if (this.sections.has(id)) {
const optionsWithDedupe = Object.assign({}, options, {
dedupeConfigurations: this._dedupeConfiguration,
});
this.sections.set(id, Object.assign(this.sections.get(id), options));
this.emit(this.UPDATE_SECTION, id, optionsWithDedupe, shouldBroadcast);
this.emit(
this.UPDATE_SECTION,
id,
optionsWithDedupe,
shouldBroadcast,
isStartup
);
}
},
@ -387,14 +393,22 @@ const SectionsManager = {
* @param url The url of the card to update
* @param options The options to update for the card
* @param shouldBroadcast Whether or not to broadcast the update
* @param isStartup If this update is during startup.
*/
updateSectionCard(id, url, options, shouldBroadcast) {
updateSectionCard(id, url, options, shouldBroadcast, isStartup = false) {
if (this.sections.has(id)) {
const card = this.sections.get(id).rows.find(elem => elem.url === url);
if (card) {
Object.assign(card, options);
}
this.emit(this.UPDATE_SECTION_CARD, id, url, options, shouldBroadcast);
this.emit(
this.UPDATE_SECTION_CARD,
id,
url,
options,
shouldBroadcast,
isStartup
);
}
},
removeSectionCard(sectionId, url) {
@ -456,7 +470,12 @@ class SectionsFeed {
);
// Catch any sections that have already been added
SectionsManager.sections.forEach((section, id) =>
this.onAddSection(SectionsManager.ADD_SECTION, id, section)
this.onAddSection(
SectionsManager.ADD_SECTION,
id,
section,
true /* isStartup */
)
);
}
@ -472,12 +491,15 @@ class SectionsFeed {
);
}
onAddSection(event, id, options) {
onAddSection(event, id, options, isStartup = false) {
if (options) {
this.store.dispatch(
ac.BroadcastToContent({
type: at.SECTION_REGISTER,
data: Object.assign({ id }, options),
meta: {
isStartup,
},
})
);
@ -498,11 +520,20 @@ class SectionsFeed {
);
}
onUpdateSection(event, id, options, shouldBroadcast = false) {
onUpdateSection(
event,
id,
options,
shouldBroadcast = false,
isStartup = false
) {
if (options) {
const action = {
type: at.SECTION_UPDATE,
data: Object.assign(options, { id }),
meta: {
isStartup,
},
};
this.store.dispatch(
shouldBroadcast
@ -512,11 +543,21 @@ class SectionsFeed {
}
}
onUpdateSectionCard(event, id, url, options, shouldBroadcast = false) {
onUpdateSectionCard(
event,
id,
url,
options,
shouldBroadcast = false,
isStartup = false
) {
if (options) {
const action = {
type: at.SECTION_UPDATE_CARD,
data: { id, url, options },
meta: {
isStartup,
},
};
this.store.dispatch(
shouldBroadcast

Просмотреть файл

@ -129,7 +129,7 @@ this.TopSitesFeed = class TopSitesFeed {
this.store.getState().Prefs.values[DEFAULT_SITES_PREF]
);
this._storage = this.store.dbStorage.getDbTable("sectionPrefs");
this.refresh({ broadcast: true });
this.refresh({ broadcast: true, isStartup: true });
Services.obs.addObserver(this, "browser-search-engine-modified");
for (let [pref] of SEARCH_TILE_OVERRIDE_PREFS) {
Services.prefs.addObserver(pref, this);
@ -293,7 +293,7 @@ this.TopSitesFeed = class TopSitesFeed {
return false;
}
async getLinksWithDefaults() {
async getLinksWithDefaults(isStartup = false) {
const numItems =
this.store.getState().Prefs.values[ROWS_PREF] *
TOP_SITES_MAX_SITES_PER_ROW;
@ -437,11 +437,11 @@ this.TopSitesFeed = class TopSitesFeed {
if (link) {
// If there is a custom screenshot this is the only image we display
if (link.customScreenshotURL) {
this._fetchScreenshot(link, link.customScreenshotURL);
this._fetchScreenshot(link, link.customScreenshotURL, isStartup);
} else if (link.searchTopSite && !link.isDefault) {
this._attachTippyTopIconForSearchShortcut(link, link.label);
} else {
this._fetchIcon(link);
this._fetchIcon(link, isStartup);
}
// Remove internal properties that might be updated after dispatch
@ -496,13 +496,16 @@ this.TopSitesFeed = class TopSitesFeed {
/**
* Refresh the top sites data for content.
* @param {bool} options.broadcast Should the update be broadcasted.
* @param {bool} options.isStartup Being called while TopSitesFeed is initting.
*/
async refresh(options = {}) {
if (!this._tippyTopProvider.initialized) {
await this._tippyTopProvider.init();
}
const links = await this.getLinksWithDefaults();
const links = await this.getLinksWithDefaults({
isStartup: options.isStartup,
});
const newAction = { type: at.TOP_SITES_UPDATED, data: { links } };
let storedPrefs;
try {
@ -513,6 +516,12 @@ this.TopSitesFeed = class TopSitesFeed {
}
newAction.data.pref = getDefaultOptions(storedPrefs);
if (options.isStartup) {
newAction.meta = {
isStartup: true,
};
}
if (options.broadcast) {
// Broadcast an update to all open content pages
this.store.dispatch(ac.BroadcastToContent(newAction));
@ -522,7 +531,7 @@ this.TopSitesFeed = class TopSitesFeed {
}
}
async updateCustomSearchShortcuts() {
async updateCustomSearchShortcuts(isStartup = false) {
if (!this.store.getState().Prefs.values[SEARCH_SHORTCUTS_EXPERIMENT]) {
return;
}
@ -550,6 +559,9 @@ this.TopSitesFeed = class TopSitesFeed {
ac.BroadcastToContent({
type: at.UPDATE_SEARCH_SHORTCUTS,
data: { searchShortcuts },
meta: {
isStartup,
},
})
);
}
@ -572,7 +584,7 @@ this.TopSitesFeed = class TopSitesFeed {
/**
* Get an image for the link preferring tippy top, rich favicon, screenshots.
*/
async _fetchIcon(link) {
async _fetchIcon(link, isStartup = false) {
// Nothing to do if we already have a rich icon from the page
if (link.favicon && link.faviconSize >= MIN_FAVICON_SIZE) {
return;
@ -588,15 +600,16 @@ this.TopSitesFeed = class TopSitesFeed {
this._requestRichIcon(link.url);
// Also request a screenshot if we don't have one yet
await this._fetchScreenshot(link, link.url);
await this._fetchScreenshot(link, link.url, isStartup);
}
/**
* Fetch, cache and broadcast a screenshot for a specific topsite.
* @param link cached topsite object
* @param url where to fetch the image from
* @param isStartup Whether the screenshot is fetched while initting TopSitesFeed.
*/
async _fetchScreenshot(link, url) {
async _fetchScreenshot(link, url, isStartup = false) {
// We shouldn't bother caching screenshots if they won't be shown.
if (
link.screenshot ||
@ -613,6 +626,9 @@ this.TopSitesFeed = class TopSitesFeed {
ac.BroadcastToContent({
data: { screenshot, url: link.url },
type: at.SCREENSHOT_UPDATED,
meta: {
isStartup,
},
})
)
);
@ -845,7 +861,7 @@ this.TopSitesFeed = class TopSitesFeed {
switch (action.type) {
case at.INIT:
this.init();
this.updateCustomSearchShortcuts();
this.updateCustomSearchShortcuts(true /* isStartup */);
break;
case at.SYSTEM_TICK:
this.refresh({ broadcast: false });

Просмотреть файл

@ -81,7 +81,7 @@ this.TopStoriesFeed = class TopStoriesFeed {
}
async onInit() {
SectionsManager.enableSection(SECTION_ID);
SectionsManager.enableSection(SECTION_ID, true /* isStartup */);
if (this.discoveryStreamEnabled) {
return;
}

Просмотреть файл

@ -388,6 +388,9 @@ describe("ASRouter", () => {
ac.BroadcastToContent({
type: "AS_ROUTER_INITIALIZED",
data: ASRouterPreferences.specialConditions,
meta: {
isStartup: true,
},
})
);
});

Просмотреть файл

@ -139,6 +139,17 @@ describe("initStore", () => {
dispatch(action);
assert.calledWith(next, action);
});
it("should not let startup actions go through for the preloaded about:home document", () => {
globals.set("__FROM_STARTUP_CACHE__", true);
const next = sinon.spy();
const dispatch = rehydrationMiddleware(store)(next);
const action = ac.BroadcastToContent(
{ type: "FOO", meta: { isStartup: true } },
123
);
dispatch(action);
assert.notCalled(next);
});
});
describe("queueEarlyMessageMiddleware", () => {
it("should allow all local actions to go through", () => {

Просмотреть файл

@ -458,6 +458,7 @@ describe("DiscoveryStreamFeed", () => {
assert.calledWith(feed.store.dispatch, {
type: "DISCOVERY_STREAM_SPOCS_PLACEMENTS",
data: { placements: [{ name: "first" }, { name: "second" }] },
meta: { isStartup: false },
});
});
it("should fire update placements from loadLayout", async () => {
@ -646,9 +647,11 @@ describe("DiscoveryStreamFeed", () => {
assert.calledWith(feed.store.dispatch.firstCall, {
type: at.DISCOVERY_STREAM_FEED_UPDATE,
data: { feed: { data: { status: "failed" } }, url: "foo.com" },
meta: { isStartup: false },
});
assert.calledWith(feed.store.dispatch.secondCall, {
type: at.DISCOVERY_STREAM_FEEDS_UPDATE,
meta: { isStartup: false },
});
});

Просмотреть файл

@ -601,7 +601,8 @@ describe("Highlights Feed", () => {
sectionsManagerStub.updateSection,
SECTION_ID,
{ rows: [] },
true
true,
undefined
);
});
it("should broadcast if options.broadcast is true", async () => {
@ -614,7 +615,8 @@ describe("Highlights Feed", () => {
sectionsManagerStub.updateSection,
SECTION_ID,
{ rows: [] },
true
true,
undefined
);
});
it("should not broadcast if options.broadcast is false and initialized is true", async () => {
@ -627,7 +629,8 @@ describe("Highlights Feed", () => {
sectionsManagerStub.updateSection,
SECTION_ID,
{ rows: [] },
false
false,
undefined
);
});
});
@ -725,7 +728,10 @@ describe("Highlights Feed", () => {
feed.onAction({ type: at.SYSTEM_TICK });
assert.calledOnce(feed.fetchHighlights);
assert.calledWithExactly(feed.fetchHighlights, { broadcast: false });
assert.calledWithExactly(feed.fetchHighlights, {
broadcast: false,
isStartup: false,
});
});
it("should fetch highlights on PREF_CHANGED for include prefs", async () => {
feed.fetchHighlights = sinon.spy();
@ -794,7 +800,10 @@ describe("Highlights Feed", () => {
feed.onAction({ type: at.TOP_SITES_UPDATED });
assert.calledOnce(feed.fetchHighlights);
assert.calledWithExactly(feed.fetchHighlights, { broadcast: false });
assert.calledWithExactly(feed.fetchHighlights, {
broadcast: false,
isStartup: false,
});
});
it("should call fetchHighlights when deleting or archiving from Pocket", async () => {
feed.fetchHighlights = sinon.spy();

Просмотреть файл

@ -90,6 +90,7 @@ describe("RecommendationProviderSwitcher", () => {
ac.BroadcastToContent({
type: at.DISCOVERY_STREAM_PERSONALIZATION_VERSION,
data: { version: 1 },
meta: { isStartup: false },
})
);
assert.equal(feed.affinityProviderV2, null);
@ -109,6 +110,7 @@ describe("RecommendationProviderSwitcher", () => {
ac.BroadcastToContent({
type: at.DISCOVERY_STREAM_PERSONALIZATION_VERSION,
data: { version: 2 },
meta: { isStartup: false },
})
);
assert.deepEqual(feed.affinityProviderV2.modelKeys, ["1", "2", "3", "4"]);

Просмотреть файл

@ -598,7 +598,10 @@ describe("Top Sites Feed", () => {
await feed.init();
assert.calledOnce(feed.refresh);
assert.calledWithExactly(feed.refresh, { broadcast: true });
assert.calledWithExactly(feed.refresh, {
broadcast: true,
isStartup: true,
});
});
it("should initialise the storage", async () => {
await feed.init();
@ -1622,7 +1625,11 @@ describe("Top Sites Feed", () => {
},
],
},
meta: { from: "ActivityStream:Main", to: "ActivityStream:Content" },
meta: {
from: "ActivityStream:Main",
to: "ActivityStream:Content",
isStartup: false,
},
type: "UPDATE_SEARCH_SHORTCUTS",
});
});