Skip to content

Commit 3689594

Browse files
authored
Windows support for Malicious website protection (#1423)
1 parent 8a043e3 commit 3689594

28 files changed

+256
-80
lines changed

special-pages/index.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export const support = {
5050
'special-error': {
5151
integration: ['copy', 'build-js'],
5252
apple: ['copy', 'build-js', 'inline-html'],
53+
windows: ['copy', 'build-js', 'inline-html'],
5354
},
5455
/** @type {Partial<Record<ImportMeta['injectName'], string[]>>} */
5556
'new-tab': {

special-pages/pages/special-error/app/components/AdvancedInfo.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@ export function AdvancedInfo() {
6363

6464
return (
6565
<div className={styles.wrapper}>
66-
<div className={styles.container} onAnimationEnd={trigger}>
67-
<AdvancedInfoHeading />
66+
<div className={styles.animationContainer} onAnimationEnd={trigger}>
67+
<div className={styles.container}>
68+
<AdvancedInfoHeading />
6869

69-
<AdvancedInfoContent />
70+
<AdvancedInfoContent />
7071

71-
<VisitSiteLink elemRef={ref} />
72+
<VisitSiteLink elemRef={ref} />
73+
</div>
7274
</div>
7375
</div>
7476
);

special-pages/pages/special-error/app/components/AdvancedInfo.module.css

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
.animationContainer {
2+
animation-duration: 300ms;
3+
animation-fill-mode: forwards;
4+
animation-name: appear;
5+
}
6+
17
.container {
28
align-items: flex-start;
39
display: flex;
410
flex-flow: column;
511
gap: var(--sp-4);
612
max-width: var(--ios-content-max-width);
13+
padding: var(--sp-6) var(--sp-10);
714
width: 100%;
8-
9-
animation-duration: 300ms;
10-
animation-fill-mode: forwards;
11-
animation-name: appear;
1215
}
1316

1417
.heading {
@@ -38,25 +41,15 @@
3841

3942
@keyframes appear {
4043
0% {
41-
padding: 0 var(--sp-10);
4244
max-height: 0;
4345
}
4446
100% {
45-
padding: var(--sp-6) var(--sp-10);
4647
max-height: calc(400 * var(--px-in-rem));
4748
}
4849
}
4950

5051
/* Platform-specific styles */
5152

52-
/* macOS */
53-
[data-platform-name="macos"] {
54-
& .container {
55-
background: var(--advanced-info-bg);
56-
box-shadow: inset 0 1px 0 0 var(--border-color);
57-
}
58-
}
59-
6053
/* iOS */
6154
[data-platform-name="ios"] {
6255
& .wrapper {
@@ -90,3 +83,19 @@
9083
line-height: calc(21 * var(--px-in-rem));
9184
}
9285
}
86+
87+
/* macOS */
88+
[data-platform-name="macos"] {
89+
& .container {
90+
background: var(--advanced-info-bg);
91+
box-shadow: inset 0 1px 0 0 var(--border-color);
92+
}
93+
}
94+
95+
/* windows */
96+
[data-platform-name="windows"] {
97+
& .container {
98+
background: var(--advanced-info-bg);
99+
box-shadow: inset 0 1px 0 0 var(--border-color);
100+
}
101+
}

special-pages/pages/special-error/app/components/App.module.css

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ html,
22
body {
33
height: 100%;
44
margin: 0;
5+
6+
--theme-font-family: system, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
57
}
68

79
.main {
@@ -36,6 +38,13 @@ body {
3638

3739
/* Platform-specific styles */
3840

41+
/* iOS */
42+
[data-platform-name="ios"] {
43+
& .container {
44+
align-items: center;
45+
}
46+
}
47+
3948
/* macOS */
4049
[data-platform-name="macos"] {
4150
& .container {
@@ -47,9 +56,13 @@ body {
4756
}
4857
}
4958

50-
/* iOS */
51-
[data-platform-name="ios"] {
59+
/* Windows */
60+
[data-platform-name="windows"] {
5261
& .container {
53-
align-items: center;
62+
background: var(--container-bg);
63+
border-radius: var(--sp-4);
64+
border: 1px solid var(--border-color);
65+
min-width: calc(400 * var(--px-in-rem));
66+
width: calc(504 * var(--px-in-rem));
5467
}
55-
}
68+
}

special-pages/pages/special-error/app/components/Components.jsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import { AdvancedInfoButton, LeaveSiteButton, Warning, WarningContent, WarningHe
1717
* @typedef {SSLExpiredCertificate|SSLInvalidCertificate|SSLSelfSignedCertificate|SSLWrongHost} SSLError
1818
*/
1919

20-
/** @type {Record<Extract<import("../../types/special-error.js").InitialSetupResponse['platform']['name'], "macos"|"ios">, string>} */
20+
/** @type {Record<Extract<import("../../types/special-error.js").InitialSetupResponse['platform']['name'], "ios"|"macos"|"windows">, string>} */
2121
const platforms = {
22-
macos: 'macOS',
2322
ios: 'iOS',
23+
macos: 'macOS',
24+
windows: 'Windows',
2425
};
2526

2627
/**
@@ -58,8 +59,8 @@ export function Components() {
5859
};
5960

6061
return (
61-
<div data-theme={isDarkMode ? 'dark' : 'light'}>
62-
<div className={styles.selector}>
62+
<div>
63+
<nav className={styles.selector}>
6364
<fieldset>
6465
<label for="platform-select">Platform:</label>
6566
<select id="platform-select" onChange={(e) => handlePlatformChange(e.currentTarget?.value)}>
@@ -84,8 +85,8 @@ export function Components() {
8485
})}
8586
</select>
8687
</fieldset>
87-
</div>
88-
<main class={styles.main} data-platform-name={platformName}>
88+
</nav>
89+
<main class={styles.main} data-platform-name={platformName} data-theme={isDarkMode ? 'dark' : 'light'}>
8990
<h1>Special Error Components</h1>
9091

9192
<section>

special-pages/pages/special-error/app/components/Warning.jsx

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import classNames from 'classnames';
33
import { useTypedTranslation } from '../types';
44
import { useMessaging } from '../providers/MessagingProvider';
55
import { useErrorData } from '../providers/SpecialErrorProvider';
6-
import { usePlatformName } from '../providers/SettingsProvider';
6+
import { usePlatformName, useIsMobile } from '../providers/SettingsProvider';
77
import { useWarningHeading, useWarningContent } from '../hooks/ErrorStrings';
88
import { Text } from '../../../../shared/components/Text/Text';
99
import { Button } from '../../../../shared/components/Button/Button';
@@ -16,15 +16,12 @@ import styles from './Warning.module.css';
1616
*/
1717
export function AdvancedInfoButton({ onClick }) {
1818
const { t } = useTypedTranslation();
19-
const platformName = usePlatformName();
19+
const isMobile = useIsMobile();
20+
const buttonVariant = isMobile ? 'ghost' : 'standard';
2021

2122
return (
22-
<Button
23-
variant={platformName === 'macos' ? 'standard' : 'ghost'}
24-
className={classNames(styles.button, styles.advanced)}
25-
onClick={onClick}
26-
>
27-
{platformName === 'ios' ? t('advancedButton') : t('advancedEllipsisButton')}
23+
<Button variant={buttonVariant} className={classNames(styles.button, styles.advanced)} onClick={onClick}>
24+
{isMobile ? t('advancedButton') : t('advancedEllipsisButton')}
2825
</Button>
2926
);
3027
}
@@ -34,12 +31,22 @@ export function LeaveSiteButton() {
3431
const { messaging } = useMessaging();
3532
const platformName = usePlatformName();
3633

34+
/** @type {import('../../../../shared/components/Button/Button').ButtonProps['variant']} */
35+
let buttonVariant;
36+
switch (platformName) {
37+
case 'ios':
38+
case 'android':
39+
buttonVariant = 'primary';
40+
break;
41+
case 'windows':
42+
buttonVariant = 'accentBrand';
43+
break;
44+
default:
45+
buttonVariant = 'accent';
46+
}
47+
3748
return (
38-
<Button
39-
variant={platformName === 'macos' ? 'accent' : 'primary'}
40-
className={classNames(styles.button, styles.leaveSite)}
41-
onClick={() => messaging?.leaveSite()}
42-
>
49+
<Button variant={buttonVariant} className={classNames(styles.button, styles.leaveSite)} onClick={() => messaging?.leaveSite()}>
4350
{t('leaveSiteButton')}
4451
</Button>
4552
);
@@ -49,16 +56,26 @@ export function WarningHeading() {
4956
const { kind } = useErrorData();
5057
const heading = useWarningHeading();
5158
const platformName = usePlatformName();
59+
const isMobile = useIsMobile();
60+
61+
/** @type {'title-2'|'title-2-emphasis'|'custom-title-1'} */
62+
let textVariant;
63+
switch (platformName) {
64+
case 'ios':
65+
case 'android':
66+
textVariant = 'title-2';
67+
break;
68+
case 'windows':
69+
textVariant = 'custom-title-1';
70+
break;
71+
default:
72+
textVariant = 'title-2-emphasis';
73+
}
5274

5375
return (
5476
<header className={classNames(styles.heading, styles[kind])}>
5577
<i className={styles.icon} aria-hidden="true" />
56-
<Text
57-
as="h1"
58-
variant={platformName === 'macos' ? 'title-2-emphasis' : 'title-2'}
59-
strictSpacing={platformName !== 'macos'}
60-
className={styles.title}
61-
>
78+
<Text as="h1" variant={textVariant} strictSpacing={isMobile} className={styles.title}>
6279
{heading}
6380
</Text>
6481
</header>

special-pages/pages/special-error/app/components/Warning.module.css

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
}
7272
}
7373

74-
7574
/* iOS */
7675
[data-platform-name="ios"] {
7776
& .container {
@@ -140,4 +139,53 @@
140139
white-space: normal;
141140
}
142141
}
142+
}
143+
144+
/* Windows */
145+
[data-platform-name="windows"] {
146+
--border-radius-sm: 6px;
147+
148+
& .icon {
149+
align-self: flex-start;
150+
flex: 0 0 var(--sp-12);
151+
height: var(--sp-12);
152+
width: var(--sp-12);
153+
}
154+
155+
& .heading {
156+
gap: var(--sp-4);
157+
}
158+
159+
& .ssl.heading {
160+
height: var(--sp-8);
161+
}
162+
163+
& .ssl .icon {
164+
background-image: url(../../../../shared/assets/img/icons/Shield-Alert-96.svg);
165+
margin-left: calc(-1 * var(--sp-2));
166+
margin-top: calc(-1 * var(--sp-2));
167+
}
168+
169+
& .phishing .icon, & .malware .icon {
170+
background-image: url(../../../../shared/assets/img/icons/Malware-Site-96.svg);
171+
margin-left: calc(-1 * var(--sp-2));
172+
margin-right: calc(-1 * var(--sp-1));
173+
}
174+
175+
& .buttonContainer {
176+
flex-flow: row-reverse;
177+
gap: var(--sp-4);
178+
justify-content: flex-end;
179+
}
180+
181+
& .button {
182+
flex: 0 0 calc(50% - var(--sp-2));
183+
184+
/* TODO: Move to shared? */
185+
font-family: var(--theme-font-family);
186+
font-size: calc(14 * var(--px-in-rem));
187+
font-weight: 400;
188+
line-height: normal;
189+
}
190+
143191
}

special-pages/pages/special-error/app/providers/SettingsProvider.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ export function SettingsProvider({ settings, children }) {
1717
export function usePlatformName() {
1818
return useContext(SettingsContext).settings.platform?.name;
1919
}
20+
21+
export function useIsMobile() {
22+
const platformName = useContext(SettingsContext).settings.platform?.name;
23+
return platformName === 'android' || platformName === 'ios';
24+
}

special-pages/pages/special-error/app/styles/variables.css

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,4 @@
3030
}
3131
}
3232

33-
[data-platform-name="macos"], [data-platform-name="ios"] {
34-
--theme-font: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
35-
}
36-
37-
3833

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* global process */
2+
import { expect, test } from '@playwright/test';
3+
import { SpecialErrorPage } from './special-error';
4+
5+
const maxDiffPixels = 20;
6+
7+
test.describe('screenshots @screenshots', () => {
8+
test.skip(process.env.CI === 'true');
9+
10+
test('SSL expired cert error', async ({ page }, workerInfo) => {
11+
const special = SpecialErrorPage.create(page, workerInfo);
12+
await special.openPage({ errorId: 'ssl.expired' });
13+
await expect(page).toHaveScreenshot('ssl-expired-cert.png', { maxDiffPixels });
14+
});
15+
16+
test('SSL expired cert error with reduced motion', async ({ page }, workerInfo) => {
17+
const special = SpecialErrorPage.create(page, workerInfo);
18+
await special.reducedMotion();
19+
await special.openPage({ errorId: 'ssl.expired' });
20+
await special.showsAdvancedInfo();
21+
await expect(page).toHaveScreenshot('ssl-expired-cert-reduced-motion.png', { maxDiffPixels });
22+
});
23+
24+
test('Phishing warning with advanced info', async ({ page }, workerInfo) => {
25+
const special = SpecialErrorPage.create(page, workerInfo);
26+
await special.openPage({ errorId: 'phishing' });
27+
await special.showsAdvancedInfo();
28+
await expect(page).toHaveScreenshot('phishing-warning-advanced.png', { maxDiffPixels });
29+
});
30+
31+
test('Malware warning in Russian', async ({ page }, workerInfo) => {
32+
const special = SpecialErrorPage.create(page, workerInfo);
33+
await special.openPage({ errorId: 'malware', locale: 'ru' });
34+
await expect(page).toHaveScreenshot('malware-warning-ru.png', { maxDiffPixels });
35+
});
36+
});

0 commit comments

Comments
 (0)