diff --git a/packages/special-pages/index.mjs b/packages/special-pages/index.mjs index 068befc7c..8f4e90ad2 100644 --- a/packages/special-pages/index.mjs +++ b/packages/special-pages/index.mjs @@ -36,7 +36,7 @@ export const support = { 'apple': ['copy', 'inline-html'], }, /** @type {Partial>} */ - sslerrorpage: { + specialerrorpage: { 'integration': ['copy', 'build-js'], 'apple': ['copy', 'build-js', 'inline-html'], }, diff --git a/packages/special-pages/messages/sslerrorpage/leaveSite.notify.json b/packages/special-pages/messages/specialerrorpage/leaveSite.notify.json similarity index 100% rename from packages/special-pages/messages/sslerrorpage/leaveSite.notify.json rename to packages/special-pages/messages/specialerrorpage/leaveSite.notify.json diff --git a/packages/special-pages/messages/sslerrorpage/visitSite.notify.json b/packages/special-pages/messages/specialerrorpage/visitSite.notify.json similarity index 100% rename from packages/special-pages/messages/sslerrorpage/visitSite.notify.json rename to packages/special-pages/messages/specialerrorpage/visitSite.notify.json diff --git a/packages/special-pages/pages/sslerrorpage/.gitignore b/packages/special-pages/pages/specialerrorpage/.gitignore similarity index 100% rename from packages/special-pages/pages/sslerrorpage/.gitignore rename to packages/special-pages/pages/specialerrorpage/.gitignore diff --git a/packages/special-pages/pages/sslerrorpage/readme.md b/packages/special-pages/pages/specialerrorpage/readme.md similarity index 100% rename from packages/special-pages/pages/sslerrorpage/readme.md rename to packages/special-pages/pages/specialerrorpage/readme.md diff --git a/packages/special-pages/pages/sslerrorpage/src/img/Shield-Alert-96x96.data.svg b/packages/special-pages/pages/specialerrorpage/src/img/Shield-Alert-96x96.data.svg similarity index 100% rename from packages/special-pages/pages/sslerrorpage/src/img/Shield-Alert-96x96.data.svg rename to packages/special-pages/pages/specialerrorpage/src/img/Shield-Alert-96x96.data.svg diff --git a/packages/special-pages/pages/sslerrorpage/src/img/Shield-Alert-96x96.svg b/packages/special-pages/pages/specialerrorpage/src/img/Shield-Alert-96x96.svg similarity index 100% rename from packages/special-pages/pages/sslerrorpage/src/img/Shield-Alert-96x96.svg rename to packages/special-pages/pages/specialerrorpage/src/img/Shield-Alert-96x96.svg diff --git a/packages/special-pages/pages/sslerrorpage/src/index.html b/packages/special-pages/pages/specialerrorpage/src/index.html similarity index 100% rename from packages/special-pages/pages/sslerrorpage/src/index.html rename to packages/special-pages/pages/specialerrorpage/src/index.html diff --git a/packages/special-pages/pages/sslerrorpage/src/js/index.js b/packages/special-pages/pages/specialerrorpage/src/js/index.js similarity index 88% rename from packages/special-pages/pages/sslerrorpage/src/js/index.js rename to packages/special-pages/pages/specialerrorpage/src/js/index.js index 0beb5ca38..c195605f0 100644 --- a/packages/special-pages/pages/sslerrorpage/src/js/index.js +++ b/packages/special-pages/pages/specialerrorpage/src/js/index.js @@ -1,18 +1,18 @@ /** - * @module SSLError Page + * @module SpecialError Page * @category Special Pages * * @description * - * [[include:packages/special-pages/pages/sslerrorpage/readme.md]] + * [[include:packages/special-pages/pages/specialerrorpage/readme.md]] */ import { execTemplate } from './template.js' -import { defaultLoadData } from './defaults.js' +import { loadData } from './loadData.js' import { createTypedMessages } from '@duckduckgo/messaging' import { createSpecialPageMessaging } from '../../../../shared/create-special-page-messaging' -export class SslerrorpagePage { +export class SpecialerrorpagePage { /** * @param {import("@duckduckgo/messaging").Messaging} messaging */ @@ -35,10 +35,10 @@ export class SslerrorpagePage { const messaging = createSpecialPageMessaging({ env: import.meta.env, injectName: import.meta.injectName, - pageName: 'sslErrorPage' + pageName: 'specialErrorPage' }) -const page = new SslerrorpagePage(messaging) +const page = new SpecialerrorpagePage(messaging) window.addEventListener('DOMContentLoaded', () => { loadHTML() bindEvents(page) @@ -61,7 +61,7 @@ function loadHTML () { if (!parsed.strings) { console.warn('missing `strings` from the incoming json data') } - const mergedStrings = { ...defaultLoadData.strings, ...parsed.strings } + const mergedStrings = { ...loadData.ssl.strings, ...parsed.strings } container.innerHTML = execTemplate(mergedStrings).toString() document.body.appendChild(container) } @@ -84,7 +84,7 @@ function domElements () { } /** - * @param {SslerrorpagePage} page + * @param {SpecialerrorpagePage} page */ function bindEvents (page) { const dom = domElements() diff --git a/packages/special-pages/pages/specialerrorpage/src/js/loadData.js b/packages/special-pages/pages/specialerrorpage/src/js/loadData.js new file mode 100644 index 000000000..814b7491c --- /dev/null +++ b/packages/special-pages/pages/specialerrorpage/src/js/loadData.js @@ -0,0 +1,26 @@ +export const loadData = { + ssl: { + strings: { + header: 'Warning: This site may be insecure', + body: 'The certificate for this site is invalid. You might be connecting to a server that is pretending to be example.com which could put your confidential information at risk', + advancedInfoHeader: 'DuckDuckGo warns you when a website has an invalid certificate.', + advancedButton: 'Advanced...', + leaveSiteButton: 'Leave This Site', + specificMessage: 'The security certificate for bad.example.com is not trusted by your computer\'s operating system', + advancedInfoBody: 'It’s possible that the website is misconfigured or that an attacker has compromised your connection.', + visitSiteBody: 'Accept Risk and Visit Site' + } + }, + phishing: { + strings: { + header: 'Warning: This site puts your personal information at risk', + body: 'This website may be impersonating a legitimate site in order to trick you into providing personal information, such as passwords or credit card numbers. Learn more', + advancedInfoHeader: 'DuckDuckGo warns you when a website has been flagged as malicious.', + advancedButton: 'Advanced...', + leaveSiteButton: 'Leave This Site', + specificMessage: '', + advancedInfoBody: 'Warnings are shown for websites that have been reported to be deceptive. Deceptive websites try to trick you into believing they are legitimate websites you trust. If you understand the risks involved, you can continue anyway.

See our Phishing and Malware Protection help page for more information.', + visitSiteBody: 'Accept Risk and Visit Site' + } + } +} diff --git a/packages/special-pages/pages/sslerrorpage/src/js/template.js b/packages/special-pages/pages/specialerrorpage/src/js/template.js similarity index 95% rename from packages/special-pages/pages/sslerrorpage/src/js/template.js rename to packages/special-pages/pages/specialerrorpage/src/js/template.js index 1a354277e..21e7aaadb 100644 --- a/packages/special-pages/pages/sslerrorpage/src/js/template.js +++ b/packages/special-pages/pages/specialerrorpage/src/js/template.js @@ -17,7 +17,7 @@ export function execTemplate (strings) {

${strings.advancedInfoHeader}

-

${trustedUnsafeEscaped(strings.specificMessage)} ${strings.advancedInfoBody}

+

${trustedUnsafeEscaped(strings.specificMessage)} ${trustedUnsafeEscaped(strings.advancedInfoBody)}

diff --git a/packages/special-pages/pages/sslerrorpage/src/style.css b/packages/special-pages/pages/specialerrorpage/src/style.css similarity index 75% rename from packages/special-pages/pages/sslerrorpage/src/style.css rename to packages/special-pages/pages/specialerrorpage/src/style.css index 23e995731..738616dac 100644 --- a/packages/special-pages/pages/sslerrorpage/src/style.css +++ b/packages/special-pages/pages/specialerrorpage/src/style.css @@ -1,37 +1,40 @@ :root { /* Light theme colors */ - --background-color: #eee; + --background-color: #eee; --text-color: rgba(0, 0, 0, 0.84); - --border-color: rgba(0, 0, 0, 0.10); - --warning-container-bg: #FFF; + --link-color: #000; + --border-color: rgba(0, 0, 0, 0.1); + --warning-container-bg: #fff; --advanced-info-bg: rgba(0, 0, 0, 0.03); - --button-bg: #FFF; - --button-active-bg:#e0e0e0; - --button-text: #000000; - --leave-site-btn-bg: linear-gradient(180deg, #4266D8 0%, #224CD2 100%); + --button-bg: #fff; + --button-active-bg: #e0e0e0; + --button-text: #000; + --leave-site-btn-bg: linear-gradient(180deg, #4266d8 0%, #224cd2 100%); --leave-site-btn-border-color: rgba(40, 145, 255, 0.05); - --leave-site-btn-shadow-color: rgba(40, 145, 255, 0.10); - --accept-risk-color: black; + --leave-site-btn-shadow-color: rgba(40, 145, 255, 0.1); + --accept-risk-color: #000; /* Dark theme colors */ - --background-color-dark: #333333; - --text-color-dark: #FFFFFFD6; + --background-color-dark: #333; + --text-color-dark: rgba(255, 255, 255, 0.84); + --link-color-dark: #ccc; --border-color-dark: rgba(255, 255, 255, 0.3); --warning-container-bg-dark: #222222; - --advanced-info-bg-dark: #2F2F2F; - --button-bg-dark: #333333; - --button-active-bg-dark:#757575; - --button-text-dark: #FFFFFF; - --leave-site-btn-bg-dark: linear-gradient(180deg, #4266D8 0%, #224CD2 100%); + --advanced-info-bg-dark: #2f2f2f; + --button-bg-dark: #333; + --button-active-bg-dark: #757575; + --button-text-dark: #fff; + --leave-site-btn-bg-dark: linear-gradient(180deg, #4266d8 0%, #224cd2 100%); --leave-site-btn-border-color-dark: rgba(40, 145, 255, 0.2); --leave-site-btn-shadow-color-dark: rgba(40, 145, 255, 0.3); - --accept-risk-color-dark: #CCCCCC; + --accept-risk-color-dark: #ccc; --max-height-for-query: 320px; } /* Base styles */ -html, body { +html, +body { height: 100%; margin: 0; background-color: var(--background-color); @@ -65,14 +68,14 @@ body { .full-container { @media (max-height: 320px) { top: 40px; - transform: translateX(-50%) + transform: translateX(-50%); } } -.full-container[data-state=open] { +.full-container[data-state="open"] { @media (max-height: 460px) { top: 40px; - transform: translateX(-50%) + transform: translateX(-50%); } } @@ -122,7 +125,7 @@ body { opacity: 1; } -[data-state=closed] .advanced-info { +[data-state="closed"] .advanced-info { opacity: 0; height: 0; padding-block: 0; @@ -154,10 +157,11 @@ body { border: 0.5px solid var(--border-color); background: var(--button-bg); color: var(--button-text); - box-shadow: 0px 1px 1px 0px var(--border-color), 0px 0px 1px 0px var(--border-color); + box-shadow: 0px 1px 1px 0px var(--border-color), + 0px 0px 1px 0px var(--border-color); } -[data-state=open] .button.advanced { +[data-state="open"] .button.advanced { display: none; } @@ -169,7 +173,8 @@ body { color: white; border: 0.5px solid var(--leave-site-btn-border-color); background: var(--leave-site-btn-bg); - box-shadow: 0px 1px 1px 0px var(--leave-site-btn-shadow-color), 0px 0px 1px 0px var(--leave-site-btn-border-color); + box-shadow: 0px 1px 1px 0px var(--leave-site-btn-shadow-color), + 0px 0px 1px 0px var(--leave-site-btn-border-color); } .leave-this-site:active { @@ -185,15 +190,21 @@ body { padding: 0; } +.advanced-info a, +.warning-text a { + color: var(--link-color); +} + @media (prefers-color-scheme: dark) { :root { --background-color: var(--background-color-dark); --text-color: var(--text-color-dark); + --link-color: var(--link-color-dark); --border-color: var(--border-color-dark); --warning-container-bg: var(--warning-container-bg-dark); --advanced-info-bg: var(--advanced-info-bg-dark); --button-bg: var(--button-bg-dark); - --button-active-bg:var(--button-active-bg-dark); + --button-active-bg: var(--button-active-bg-dark); --button-text: var(--button-text-dark); --leave-site-btn-bg: var(--leave-site-btn-bg-dark); --leave-site-btn-border-color: var(--leave-site-btn-border-color-dark); diff --git a/packages/special-pages/pages/sslerrorpage/src/js/defaults.js b/packages/special-pages/pages/sslerrorpage/src/js/defaults.js deleted file mode 100644 index 772a18ef0..000000000 --- a/packages/special-pages/pages/sslerrorpage/src/js/defaults.js +++ /dev/null @@ -1,12 +0,0 @@ -export const defaultLoadData = { - strings: { - header: 'Warning: This site may be insecure', - body: 'The certificate for this site is invalid. You might be connecting to a server that is pretending to be example.com which could put your confidential information at risk', - advancedInfoHeader: 'DuckDuckGo warns you when a website has an invalid certificate.', - advancedButton: 'Advanced...', - leaveSiteButton: 'Leave This Site', - specificMessage: 'The security certificate for bad.example.com is not trusted by your computer\'s operating system', - advancedInfoBody: 'It’s possible that the website is misconfigured or that an attacker has compromised your connection.', - visitSiteBody: 'Accept Risk and Visit Site' - } -} diff --git a/packages/special-pages/playwright.config.js b/packages/special-pages/playwright.config.js index 7ffa4ef69..e02f62981 100644 --- a/packages/special-pages/playwright.config.js +++ b/packages/special-pages/playwright.config.js @@ -19,6 +19,7 @@ export default defineConfig({ testMatch: [ 'duckplayer.spec.js', 'onboarding.spec.js', + 'specialerror.spec.js', 'sslerror.spec.js', 'release-notes.spec.js' ], diff --git a/packages/special-pages/tests/page-objects/sslerror.js b/packages/special-pages/tests/page-objects/specialerror.js similarity index 73% rename from packages/special-pages/tests/page-objects/sslerror.js rename to packages/special-pages/tests/page-objects/specialerror.js index 6783e7db4..e3e203989 100644 --- a/packages/special-pages/tests/page-objects/sslerror.js +++ b/packages/special-pages/tests/page-objects/specialerror.js @@ -1,15 +1,16 @@ import { Mocks } from './mocks.js' +import { expect } from '@playwright/test' import { perPlatform } from '../../../../integration-test/playwright/type-helpers.mjs' import { join } from 'node:path' import { readFileSync } from 'node:fs' -import { defaultLoadData } from '../../pages/sslerrorpage/src/js/defaults' +import { loadData } from '../../pages/specialerrorpage/src/js/loadData' /** * @typedef {import('../../../../integration-test/playwright/type-helpers.mjs').Build} Build * @typedef {import('../../../../integration-test/playwright/type-helpers.mjs').PlatformInfo} PlatformInfo */ -export class SSLErrorPage { +export class SpecialErrorPage { /** * @param {import("@playwright/test").Page} page * @param {Build} build @@ -30,8 +31,9 @@ export class SSLErrorPage { /** * Opens a page with optional parameters. * This method ensures that mocks are installed and routes are set up before navigating to the page. + * @param {'ssl'|'phishing'} [errorType] */ - async openPage () { + async openPage (errorType = 'ssl') { await this.mocks.install() await this.page.route('/**', (route, req) => { const url = new URL(req.url()) @@ -40,7 +42,7 @@ export class SSLErrorPage { if (filepath === '/') { filepath = 'index.html' const html = readFileSync(join(this.basePath, filepath), 'utf8') - const next = html.replace('$LOAD_TIME_DATA$', JSON.stringify(defaultLoadData)) + const next = html.replace('$LOAD_TIME_DATA$', JSON.stringify(loadData[errorType])) // Strings File return route.fulfill({ body: next, status: 200, @@ -62,7 +64,7 @@ export class SSLErrorPage { */ get basePath () { return this.build.switch({ - apple: () => '../../Sources/ContentScopeScripts/dist/pages/sslerrorpage' + apple: () => '../../Sources/ContentScopeScripts/dist/pages/specialerrorpage' }) } @@ -73,7 +75,7 @@ export class SSLErrorPage { static create (page, testInfo) { // Read the configuration object to determine which platform we're testing against const { platformInfo, build } = perPlatform(testInfo.project.use) - return new SSLErrorPage(page, build, platformInfo) + return new SpecialErrorPage(page, build, platformInfo) } async darkMode () { @@ -88,8 +90,34 @@ export class SSLErrorPage { async visitsSite () { const { page } = this await page.pause() - await page.getByRole('button', { name: 'Advanced...' }).click() + this.showsAdvancedInfo() await page.getByRole('button', { name: 'Accept Risk and Visit Site' }).click() await this.mocks.waitForCallCount({ method: 'visitSite', count: 1 }) } + + /** + * Clicks on advanced link to show expanded info + */ + async showsAdvancedInfo () { + const { page } = this + await page.getByRole('button', { name: 'Advanced...' }).click() + } + + /** + * Clicks on link and expects it to open a URL in a new window + * + * @param {string} linkName + * @param {string} newPageURL + */ + async opensNewPage (linkName, newPageURL) { + const { page } = this + const newPagePromise = page.waitForEvent('popup') + + await page.pause() + await expect(page.getByRole('link', { name: linkName })).toBeVisible() + await page.getByRole('link', { name: linkName }).click() + + const newPage = await newPagePromise + await expect(newPage).toHaveURL(newPageURL) + } } diff --git a/packages/special-pages/tests/specialerror.spec.js b/packages/special-pages/tests/specialerror.spec.js new file mode 100644 index 000000000..3b66249c4 --- /dev/null +++ b/packages/special-pages/tests/specialerror.spec.js @@ -0,0 +1,22 @@ +import { test } from '@playwright/test' +import { SpecialErrorPage } from './page-objects/specialerror' + +test.describe('specialerror', () => { + test('leaves site', async ({ page }, workerInfo) => { + const special = SpecialErrorPage.create(page, workerInfo) + await special.openPage('ssl') + await special.leavesSite() + }) + test('visits site', async ({ page }, workerInfo) => { + const special = SpecialErrorPage.create(page, workerInfo) + await special.openPage('ssl') + await special.visitsSite() + }) + test('opens phishing help page in a new window', async ({ page }, workerInfo) => { + const special = SpecialErrorPage.create(page, workerInfo) + await special.openPage('phishing') + await special.opensNewPage('Learn more', 'https://duckduckgo.com/duckduckgo-help-pages/') + await special.showsAdvancedInfo() + await special.opensNewPage('Phishing and Malware Protection help page', 'https://duckduckgo.com/duckduckgo-help-pages/') + }) +}) diff --git a/packages/special-pages/tests/sslerror.spec.js b/packages/special-pages/tests/sslerror.spec.js deleted file mode 100644 index 31fb3f202..000000000 --- a/packages/special-pages/tests/sslerror.spec.js +++ /dev/null @@ -1,15 +0,0 @@ -import { test } from '@playwright/test' -import { SSLErrorPage } from './page-objects/sslerror' - -test.describe('sslerror', () => { - test('leaves site', async ({ page }, workerInfo) => { - const ssl = SSLErrorPage.create(page, workerInfo) - await ssl.openPage() - await ssl.leavesSite() - }) - test('visits site', async ({ page }, workerInfo) => { - const ssl = SSLErrorPage.create(page, workerInfo) - await ssl.openPage() - await ssl.visitsSite() - }) -}) diff --git a/packages/special-pages/types/sslerrorpage.ts b/packages/special-pages/types/specialerrorpage.ts similarity index 52% rename from packages/special-pages/types/sslerrorpage.ts rename to packages/special-pages/types/specialerrorpage.ts index e9181caaa..2284077d8 100644 --- a/packages/special-pages/types/sslerrorpage.ts +++ b/packages/special-pages/types/specialerrorpage.ts @@ -1,5 +1,5 @@ /** - * @module Sslerrorpage Messages + * @module Specialerrorpage Messages * @description * * These types are auto-generated from schema files. @@ -8,26 +8,26 @@ */ /** - * Requests, Notifications and Subscriptions from the Sslerrorpage feature + * Requests, Notifications and Subscriptions from the Specialerrorpage feature */ -export interface SslerrorpageMessages { +export interface SpecialerrorpageMessages { notifications: LeaveSiteNotification | VisitSiteNotification; } /** - * Generated from @see "../messages/sslerrorpage/leaveSite.notify.json" + * Generated from @see "../messages/specialerrorpage/leaveSite.notify.json" */ export interface LeaveSiteNotification { method: "leaveSite"; } /** - * Generated from @see "../messages/sslerrorpage/visitSite.notify.json" + * Generated from @see "../messages/specialerrorpage/visitSite.notify.json" */ export interface VisitSiteNotification { method: "visitSite"; } -declare module "../pages/sslerrorpage/src/js/index.js" { - export interface SslerrorpagePage { - notify: import("@duckduckgo/messaging/lib/shared-types").MessagingBase['notify'] +declare module "../pages/specialerrorpage/src/js/index.js" { + export interface SpecialerrorpagePage { + notify: import("@duckduckgo/messaging/lib/shared-types").MessagingBase['notify'] } } \ No newline at end of file