Skip to content

Commit 096279a

Browse files
authored
Merge branch 'main' into shane/windows-bridge
2 parents b62081b + 7d31c21 commit 096279a

File tree

20 files changed

+389
-109
lines changed

20 files changed

+389
-109
lines changed

injected/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@types/node": "^22.14.1",
3939
"@typescript-eslint/eslint-plugin": "^8.20.0",
4040
"fast-check": "^4.1.1",
41-
"jasmine": "^5.6.0",
41+
"jasmine": "^5.7.1",
4242
"minimist": "^1.2.8"
4343
}
4444
}

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

special-pages/pages/duckplayer/app/embed-settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export class EmbedSettings {
7171

7272
url.searchParams.set('rel', '0'); // shows related videos from the same channel as the video
7373
url.searchParams.set('modestbranding', '1'); // disables showing the YouTube logo in the video control bar
74+
url.searchParams.set('color', 'white'); // Forces legacy YouTube player UI
7475

7576
if (this.timestamp && this.timestamp.seconds > 0) {
7677
url.searchParams.set('start', String(this.timestamp.seconds)); // if timestamp supplied, start video at specific point

special-pages/pages/duckplayer/integration-tests/duck-player.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import { perPlatform } from 'injected/integration-test/type-helpers.mjs';
55

66
const MOCK_VIDEO_ID = 'VIDEO_ID';
77
const MOCK_VIDEO_TITLE = 'Embedded Video - YouTube';
8-
const youtubeEmbed = (id) => 'https://www.youtube-nocookie.com/embed/' + id + '?iv_load_policy=1&autoplay=1&rel=0&modestbranding=1';
8+
const youtubeEmbed = (id) =>
9+
'https://www.youtube-nocookie.com/embed/' + id + '?iv_load_policy=1&autoplay=1&rel=0&modestbranding=1&color=white';
910
const youtubeEmbedIOS = (id) =>
10-
'https://www.youtube-nocookie.com/embed/' + id + '?iv_load_policy=1&autoplay=1&muted=1&rel=0&modestbranding=1';
11+
'https://www.youtube-nocookie.com/embed/' + id + '?iv_load_policy=1&autoplay=1&muted=1&rel=0&modestbranding=1&color=white';
1112
const html = {
1213
unsupported: `<html><head><title>${MOCK_VIDEO_TITLE}</title></head>
1314
<body>

special-pages/pages/duckplayer/unit-tests/embed-settings.mjs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { EmbedSettings } from '../app/embed-settings.js';
55
describe('creates embed url', () => {
66
it('handles duck scheme', () => {
77
const actual = EmbedSettings.fromHref('duck://player/123')?.toEmbedUrl();
8-
const expected = 'https://www.youtube-nocookie.com/embed/123?iv_load_policy=1&autoplay=1&rel=0&modestbranding=1';
8+
const expected = 'https://www.youtube-nocookie.com/embed/123?iv_load_policy=1&autoplay=1&rel=0&modestbranding=1&color=white';
99
deepEqual(actual, expected);
1010
});
1111
it('handles duck scheme with timestamp', () => {
@@ -15,6 +15,7 @@ describe('creates embed url', () => {
1515
autoplay: '1',
1616
rel: '0',
1717
modestbranding: '1',
18+
color: 'white',
1819
start: '3723',
1920
};
2021
if (!actual) throw new Error('unreachable');
@@ -28,6 +29,7 @@ describe('creates embed url', () => {
2829
autoplay: '1',
2930
rel: '0',
3031
modestbranding: '1',
32+
color: 'white',
3133
start: '3723',
3234
};
3335
if (!actual) throw new Error('unreachable');
@@ -41,6 +43,7 @@ describe('creates embed url', () => {
4143
autoplay: '1',
4244
rel: '0',
4345
modestbranding: '1',
46+
color: 'white',
4447
start: '3723',
4548
};
4649
if (!actual) throw new Error('unreachable');
@@ -54,6 +57,7 @@ describe('creates embed url', () => {
5457
autoplay: '1',
5558
rel: '0',
5659
modestbranding: '1',
60+
color: 'white',
5761
};
5862
if (!actual) throw new Error('unreachable');
5963
const asParams = Object.fromEntries(new URL(actual).searchParams);
@@ -67,6 +71,7 @@ describe('creates embed url', () => {
6771
autoplay: '1',
6872
rel: '0',
6973
modestbranding: '1',
74+
color: 'white',
7075
muted: '1',
7176
};
7277
if (!actual) throw new Error('unreachable');
@@ -80,6 +85,7 @@ describe('creates embed url', () => {
8085
iv_load_policy: '1',
8186
rel: '0',
8287
modestbranding: '1',
88+
color: 'white',
8389
};
8490
if (!actual) throw new Error('unreachable');
8591
const asParams = Object.fromEntries(new URL(actual).searchParams);
@@ -93,6 +99,7 @@ describe('creates embed url', () => {
9399
autoplay: '1',
94100
rel: '0',
95101
modestbranding: '1',
102+
color: 'white',
96103
};
97104
if (!actual) throw new Error('unreachable');
98105
const asParams = Object.fromEntries(new URL(actual).searchParams);

special-pages/pages/new-tab/app/activity/components/Activity.examples.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ export const activityExamples = {
3131
</Activity>
3232
),
3333
},
34+
'activity.noActivity.someTrackers': {
35+
factory: () => (
36+
<Activity expansion={'collapsed'} itemCount={0} trackerCount={56} toggle={noop('toggle')} batched={false}>
37+
<Mock size={0}>
38+
<ActivityBody canBurn={false} visibility={'visible'} />
39+
</Mock>
40+
</Activity>
41+
),
42+
},
3443
};
3544

3645
/**

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,13 @@ export class ActivityPage {
426426
- paragraph: Past 7 days
427427
`);
428428
}
429+
430+
async hasTrackingInfoWithoutButtons() {
431+
const { page } = this;
432+
await expect(page.getByTestId('ActivityHeading')).toMatchAriaSnapshot(`
433+
- img "Privacy Shield"
434+
- heading "56 tracking attempts blocked" [level=2]
435+
- paragraph: Past 7 days
436+
`);
437+
}
429438
}

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,35 @@ test.describe('activity widget', () => {
219219
});
220220

221221
await widget.hasRows(5);
222-
await batching.removesItem(0);
222+
await batching.itemRemovedViaPatch(0);
223223
await widget.hasRows(4);
224224
});
225+
test('patching removes last item and maintains tracker count', async ({ page }, workerInfo) => {
226+
test.info().annotations.push({
227+
type: 'link',
228+
description: 'https://app.asana.com/1/137249556945/project/1207414201589134/task/1210188026604205?focus=true',
229+
});
230+
test.info().annotations.push({
231+
type: 'info',
232+
description: `
233+
This test simulates removing the last item in the list. When that occurs, the tracker count
234+
should remain if it was greater than zero. The bug here was that we'd reset back to 'no tracking activity'.
235+
`,
236+
});
237+
const ntp = NewtabPage.create(page, workerInfo);
238+
await ntp.reducedMotion();
239+
240+
const widget = new ActivityPage(page, ntp);
241+
const batching = new BatchingPage(page, ntp, widget);
242+
243+
await ntp.openPage({
244+
additional: { feed: 'activity', 'activity.api': 'batched', platform: 'windows', activity: 'singleWithTrackers' },
245+
});
246+
247+
await widget.hasRows(1);
248+
await batching.removesItem({ index: 0, nextTrackerCount: 56 }); // from activity/mocks/activity.mocks.js
249+
await widget.hasTrackingInfoWithoutButtons();
250+
});
225251
test('items are fetched to replace patched removals', async ({ page }, workerInfo) => {
226252
const ntp = NewtabPage.create(page, workerInfo);
227253
await ntp.reducedMotion();

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,28 @@ export class BatchingPage {
106106
*
107107
* @param {number} index - The index of the item to remove from the sample data array.
108108
*/
109-
async removesItem(index) {
109+
async itemRemovedViaPatch(index) {
110110
const data = generateSampleData(this.ap.entries);
111111
data.splice(index, 1);
112112
const update = toPatch(data);
113113
await this.ntp.mocks.simulateSubscriptionMessage(sub('activity_onDataPatch'), update);
114114
}
115115

116+
/**
117+
* Simulates removing all items
118+
* @param {object} params
119+
* @param {number} params.index - The index of the item to remove from the sample data array.
120+
* @param {number} params.nextTrackerCount
121+
*/
122+
async removesItem({ index, nextTrackerCount }) {
123+
const { page } = this;
124+
await page.locator('button[data-action="remove"]').nth(index).click();
125+
126+
const update = toPatch([]);
127+
update.totalTrackersBlocked = nextTrackerCount;
128+
await this.ntp.mocks.simulateSubscriptionMessage(sub('activity_onDataPatch'), update);
129+
}
130+
116131
async fillsHoleWhenItemRemoved() {
117132
if (this.ap.entries !== 6) throw new Error('this scenario expects 6 initial items');
118133

@@ -131,7 +146,7 @@ export class BatchingPage {
131146
});
132147

133148
// now remove the first item via subscription
134-
await this.removesItem(0);
149+
await this.itemRemovedViaPatch(0);
135150

136151
// now there must have been 2 calls
137152
const [, second] = await this.ntp.mocks.waitForCallCount({ method: 'activity_getDataForUrls', count: 2 });

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ export function activityMockTransport() {
3636
/** @type {import('../../../types/new-tab.ts').NewTabMessages['notifications']} */
3737
const msg = /** @type {any} */ (_msg);
3838
switch (msg.method) {
39+
case 'activity_removeItem': {
40+
// grab the tracker count of the current dataset before we alter it
41+
const oldCount = dataset.activity.reduce((acc, item) => acc + item.trackingStatus.totalCount, 0);
42+
43+
// now filter the items
44+
dataset.activity = dataset.activity.filter((x) => x.url !== msg.params.url);
45+
46+
// create the patch dataset, and use the original tracker count
47+
const patchParams = toPatch(dataset.activity);
48+
patchParams.totalTrackersBlocked = oldCount;
49+
50+
// simulate the native side pushing the fresh data back into the page.
51+
setTimeout(() => {
52+
const cb = subs.get('activity_onDataPatch');
53+
cb(patchParams);
54+
}, 0);
55+
break;
56+
}
3957
default: {
4058
console.warn('unhandled notification', msg);
4159
}
@@ -53,6 +71,9 @@ export function activityMockTransport() {
5371
if (sub === 'activity_onDataUpdate') {
5472
subs.set('activity_onDataUpdate', cb);
5573
}
74+
if (sub === 'activity_onDataPatch') {
75+
subs.set('activity_onDataPatch', cb);
76+
}
5677
if (sub === 'activity_onDataUpdate' && url.searchParams.has('flood')) {
5778
let count = 0;
5879
const int = setInterval(() => {

special-pages/pages/new-tab/app/activity/mocks/activity.mocks.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ export const activityMocks = {
4040
},
4141
],
4242
},
43+
singleWithTrackers: {
44+
activity: [
45+
{
46+
favicon: { src: 'selco-icon.png' },
47+
url: 'https://example.com',
48+
title: 'example.com',
49+
etldPlusOne: 'example.com',
50+
favorite: false,
51+
trackersFound: true,
52+
trackingStatus: {
53+
trackerCompanies: [{ displayName: 'Google' }, { displayName: 'Facebook' }, { displayName: 'Amazon' }],
54+
totalCount: 56,
55+
},
56+
history: [],
57+
},
58+
],
59+
},
4360
few: {
4461
activity: [
4562
{

special-pages/pages/new-tab/app/privacy-stats/components/ActivityHeading.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export function ActivityHeading({ expansion, canExpand, itemCount, trackerCount,
2424
const [formatter] = useState(() => new Intl.NumberFormat());
2525
const adBlocking = useAdBlocking();
2626

27-
const none = itemCount === 0;
28-
const someItems = itemCount > 0;
27+
const none = itemCount === 0 && trackerCount === 0;
28+
const some = itemCount > 0 || trackerCount > 0;
2929
const trackerCountFormatted = formatter.format(trackerCount);
3030

3131
let allTimeString;
@@ -46,7 +46,7 @@ export function ActivityHeading({ expansion, canExpand, itemCount, trackerCount,
4646
<img src={adBlocking ? './icons/shield-green.svg' : './icons/shield.svg'} alt="Privacy Shield" />
4747
</span>
4848
{none && <h2 className={styles.title}>{t('activity_noRecent_title')}</h2>}
49-
{someItems && (
49+
{some && (
5050
<h2 className={styles.title}>
5151
<Trans str={allTimeString} values={{ count: trackerCountFormatted }} />
5252
</h2>
@@ -64,12 +64,12 @@ export function ActivityHeading({ expansion, canExpand, itemCount, trackerCount,
6464
/>
6565
</span>
6666
)}
67-
{itemCount === 0 && (
67+
{none && (
6868
<p className={cn(styles.subtitle, { [styles.indented]: !adBlocking })}>
6969
{adBlocking ? t('activity_noRecentAdsAndTrackers_subtitle') : t('activity_noRecent_subtitle')}
7070
</p>
7171
)}
72-
{itemCount > 0 && (
72+
{some && (
7373
<p className={cn(styles.subtitle, styles.indented, { [styles.uppercase]: !adBlocking })}>
7474
{t('stats_feedCountBlockedPeriod')}
7575
</p>

special-pages/pages/onboarding/app/components/v3/ComparisonTable.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { h } from 'preact';
22
import cn from 'classnames';
33
import { comparisonTableData, tableIconPrefix } from './data-comparison-table';
44
import { useTypedTranslation } from '../../types';
5+
import { useGlobalState } from '../../global';
56

67
import styles from './ComparisonTable.module.css';
78

@@ -69,7 +70,11 @@ export function ComparisonTableRow({ icon, title, statuses }) {
6970

7071
export function ComparisonTable() {
7172
const { t } = useTypedTranslation();
72-
const tableData = comparisonTableData(t);
73+
const state = useGlobalState();
74+
75+
const systemSettingsStep = /** @type {import('../../types').SystemSettingsStep|undefined} */ (state.stepDefinitions.systemSettings);
76+
const adBlockingEnabled = systemSettingsStep?.rows?.some((row) => row === 'ad-blocking' || row === 'youtube-ad-blocking') ?? false;
77+
const tableData = comparisonTableData(t, adBlockingEnabled);
7378

7479
return (
7580
<table className={styles.table}>

special-pages/pages/onboarding/app/components/v3/data-comparison-table.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ export const tableIconPrefix = 'assets/img/steps/v3/';
2020
*
2121
* Safari was removed from the latest comparison table layout. Keeping it the data just in case it comes back.
2222
*
23-
* @type {(t: ReturnType<typeof import('../../types')['useTypedTranslation']>['t']) => FeatureSupportData[]} */
24-
export const comparisonTableData = (t) => [
23+
* @type {(t: ReturnType<typeof import('../../types')['useTypedTranslation']>['t'], adBlockingEnabled?: boolean) => FeatureSupportData[]} */
24+
export const comparisonTableData = (t, adBlockingEnabled = false) => [
2525
{
2626
icon: 'search.svg',
2727
title: t('comparison_searchPrivately'),
@@ -69,7 +69,7 @@ export const comparisonTableData = (t) => [
6969
},
7070
{
7171
icon: 'video-player.svg',
72-
title: t('comparison_privateYoutube'),
72+
title: adBlockingEnabled ? t('comparison_youtubeAdFree') : t('comparison_privateYoutube'),
7373
statuses: {
7474
chrome: SupportStatus.NOT_SUPPORTED,
7575
safari: SupportStatus.NOT_SUPPORTED,

0 commit comments

Comments
 (0)