Skip to content

Commit fdd188c

Browse files
authored
ntp: accepting a new message to re-fetch favicons (#1775)
* ntp: accepting a new message to re-fetch favicons * add mock support
1 parent 5309df8 commit fdd188c

File tree

10 files changed

+129
-3
lines changed

10 files changed

+129
-3
lines changed

special-pages/pages/new-tab/app/favorites/components/FavoritesProvider.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { createContext, h } from 'preact';
2-
import { useCallback, useEffect, useReducer, useRef } from 'preact/hooks';
2+
import { useCallback, useContext, useEffect, useReducer, useRef } from 'preact/hooks';
33

44
import { FavoritesService } from '../favorites.service.js';
55
import { useMessaging } from '../../types.js';
66
import { reducer, useConfigSubscription, useDataSubscription, useInitialDataAndConfig } from '../../service.hooks.js';
7+
import { signal, useSignal } from '@preact/signals';
78

89
/**
910
* @typedef {import('../../../types/new-tab.ts').Favorite} Favorite
@@ -54,6 +55,12 @@ export const FavoritesContext = createContext({
5455

5556
export const FavoritesDispatchContext = createContext(/** @type {import("preact/hooks").Dispatch<Events>} */ ({}));
5657

58+
/**
59+
* A simple counter than can be used to invalidate a tree. For example, to force the browser
60+
* to re-fetch icons that previously gave a 404 response
61+
*/
62+
export const FaviconsRefreshedCount = createContext(signal(0));
63+
5764
/**
5865
* @param {object} props
5966
* @param {import("preact").ComponentChild} props.children
@@ -122,9 +129,19 @@ export function FavoritesProvider({ children }) {
122129
[service],
123130
);
124131

132+
const faviconsRefreshedCount = useSignal(0);
133+
useEffect(() => {
134+
if (!service.current) return;
135+
return service.current.onFaviconsRefreshed(() => {
136+
faviconsRefreshedCount.value = faviconsRefreshedCount.value += 1;
137+
});
138+
}, []);
139+
125140
return (
126141
<FavoritesContext.Provider value={{ state, toggle, favoritesDidReOrder, openFavorite, openContextMenu, add, onConfigChanged }}>
127-
<FavoritesDispatchContext.Provider value={dispatch}>{children}</FavoritesDispatchContext.Provider>
142+
<FaviconsRefreshedCount.Provider value={faviconsRefreshedCount}>
143+
<FavoritesDispatchContext.Provider value={dispatch}>{children}</FavoritesDispatchContext.Provider>
144+
</FaviconsRefreshedCount.Provider>
128145
</FavoritesContext.Provider>
129146
);
130147
}
@@ -141,3 +158,7 @@ export function useService() {
141158
}, [ntp]);
142159
return service;
143160
}
161+
162+
export function useFaviconRefreshedCount() {
163+
return useContext(FaviconsRefreshedCount);
164+
}

special-pages/pages/new-tab/app/favorites/components/TileRow.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { h } from 'preact';
44
import { memo } from 'preact/compat';
55
import { FavoritesThemeContext, ROW_CAPACITY } from './Favorites.js';
66
import { useContext } from 'preact/hooks';
7+
import { useFaviconRefreshedCount } from './FavoritesProvider.js';
78

89
/**
910
* @typedef {import('../../../types/new-tab.js').Favorite} Favorite
@@ -21,6 +22,7 @@ export const TileRow = memo(
2122
function TileRow({ topOffset, items, add, visibility }) {
2223
const fillers = ROW_CAPACITY - items.length;
2324
const { theme, animateItems } = useContext(FavoritesThemeContext);
25+
const count = useFaviconRefreshedCount();
2426
return (
2527
<ul className={styles.gridRow} style={{ transform: `translateY(${topOffset}px)` }}>
2628
{items.map((item, index) => {
@@ -31,7 +33,7 @@ export const TileRow = memo(
3133
faviconSrc={item.favicon?.src}
3234
faviconMax={item.favicon?.maxAvailableSize}
3335
title={item.title}
34-
key={item.id + item.favicon?.src + item.favicon?.maxAvailableSize + visibility}
36+
key={item.id + item.favicon?.src + item.favicon?.maxAvailableSize + visibility + count.value}
3537
id={item.id}
3638
index={index}
3739
visibility={visibility}

special-pages/pages/new-tab/app/favorites/favorites.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ There are three representations of a favicon: image source, letters, or fallback
4343
- The widget config
4444
- returns {@link "NewTab Messages".FavoritesConfig}
4545

46+
### `favorites_onRefresh`
47+
- {@link "NewTab Messages".FavoritesOnRefreshSubscription}.
48+
- Used to indicate potential updates, like when a favicon DB is ready
49+
- returns {@link "NewTab Messages".FavoritesRefresh}
50+
4651

4752
## Notifications:
4853

special-pages/pages/new-tab/app/favorites/favorites.service.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ export class FavoritesService {
6868
return this.configService.onData(cb);
6969
}
7070

71+
onFaviconsRefreshed(cb) {
72+
return this.ntp.messaging.subscribe('favorites_onRefresh', (data) => {
73+
if (data.items.some((item) => item.kind === 'favicons')) {
74+
cb();
75+
}
76+
});
77+
}
78+
7179
/**
7280
* Update the in-memory data immediate and persist.
7381
* Any state changes will be broadcast to consumers synchronously

special-pages/pages/new-tab/app/favorites/integration-tests/favorites.page.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,8 @@ export class FavoritesPage {
354354
// Every tile should be shown
355355
await this.waitForNumFavorites(count);
356356
}
357+
358+
async receivesRefresh() {
359+
await this.ntp.mocks.simulateSubscriptionMessage('favorites_onRefresh', { items: [{ kind: 'favicons' }] });
360+
}
357361
}

special-pages/pages/new-tab/app/favorites/integration-tests/favorites.spec.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,25 @@ test.describe('newtab favorites', () => {
122122
await ntp.reducedMotion();
123123
await favorites.hasFallbackIcons(() => ntp.openPage({ favorites: 'fallbacks' }));
124124
});
125+
test('handling missing icons (responding to a "refresh" message)', async ({ page }, workerInfo) => {
126+
const ntp = NewtabPage.create(page, workerInfo);
127+
const favorites = new FavoritesPage(ntp);
128+
await ntp.reducedMotion();
129+
130+
// wait for the first failed request
131+
const firstRequest = page.waitForResponse((res) => {
132+
return res.url().includes('this-does-note-exist');
133+
});
134+
await ntp.openPage({ favorites: 'missing' });
135+
await firstRequest;
136+
137+
// now, we set another wait, assuming this request is re-tried as expected
138+
const secondRequest = page.waitForResponse((res) => res.url().includes('this-does-note-exist'));
139+
140+
// comment out this line to see that the test can fail
141+
await favorites.receivesRefresh();
142+
await secondRequest;
143+
});
125144
test('expansion works with expanded items above', async ({ page }, workerInfo) => {
126145
const ntp = NewtabPage.create(page, workerInfo);
127146
await ntp.reducedMotion();

special-pages/pages/new-tab/app/favorites/mocks/favorites.data.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* "small-icon": {favorites: Favorite[]};
1313
* "fallbacks": {favorites: Favorite[]};
1414
* "titles": {favorites: Favorite[]};
15+
* "missing": {favorites: Favorite[]};
1516
* }}
1617
*/
1718
export const favorites = {
@@ -53,6 +54,18 @@ export const favorites = {
5354
/** @type {Favorite[]} */
5455
favorites: [],
5556
},
57+
missing: {
58+
/** @type {Favorite[]} */
59+
favorites: [
60+
{
61+
id: 'id-missing-1',
62+
etldPlusOne: 'adobe.com',
63+
url: 'https://adobe.com?id=id-many-3',
64+
title: 'Adobe',
65+
favicon: { src: './this-does-note-exist', maxAvailableSize: 16 },
66+
},
67+
],
68+
},
5669
titles: {
5770
/** @type {Favorite[]} */
5871
favorites: [

special-pages/pages/new-tab/app/mock-transport.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,22 @@ export function mockTransport() {
374374
);
375375
return () => controller.abort();
376376
}
377+
case 'favorites_onRefresh': {
378+
if (url.searchParams.get('favoriteRefresh') === 'favicons') {
379+
const timer = setTimeout(() => {
380+
/** @type {import('../types/new-tab').FavoritesRefresh} */
381+
const payload = {
382+
items: [{ kind: 'favicons' }],
383+
};
384+
cb(payload);
385+
}, 1000);
386+
return () => {
387+
clearTimeout(timer);
388+
};
389+
} else {
390+
return () => {};
391+
}
392+
}
377393
}
378394
return () => {};
379395
},
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"title": "Favorites Refresh",
5+
"required": ["items"],
6+
"properties": {
7+
"items": {
8+
"type": "array",
9+
"items": {
10+
"oneOf": [
11+
{
12+
"type": "object",
13+
"required": ["kind"],
14+
"properties": {
15+
"kind": {
16+
"type": "string",
17+
"const": "favicons"
18+
}
19+
}
20+
}
21+
]
22+
}
23+
}
24+
}
25+
}

special-pages/pages/new-tab/types/new-tab.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export interface NewTabMessages {
149149
| CustomizerOnThemeUpdateSubscription
150150
| FavoritesOnConfigUpdateSubscription
151151
| FavoritesOnDataUpdateSubscription
152+
| FavoritesOnRefreshSubscription
152153
| FreemiumPIRBannerOnDataUpdateSubscription
153154
| NextStepsOnConfigUpdateSubscription
154155
| NextStepsOnDataUpdateSubscription
@@ -936,6 +937,18 @@ export interface FavoritesOnDataUpdateSubscription {
936937
subscriptionEvent: "favorites_onDataUpdate";
937938
params: FavoritesData;
938939
}
940+
/**
941+
* Generated from @see "../messages/favorites_onRefresh.subscribe.json"
942+
*/
943+
export interface FavoritesOnRefreshSubscription {
944+
subscriptionEvent: "favorites_onRefresh";
945+
params: FavoritesRefresh;
946+
}
947+
export interface FavoritesRefresh {
948+
items: {
949+
kind: "favicons";
950+
}[];
951+
}
939952
/**
940953
* Generated from @see "../messages/freemiumPIRBanner_onDataUpdate.subscribe.json"
941954
*/

0 commit comments

Comments
 (0)