Skip to content

Commit 6ec1ebb

Browse files
shakyShanegithub-actions[bot]
authored andcommitted
Release build 4.24.0 [ci release]
1 parent e4afd8f commit 6ec1ebb

File tree

21 files changed

+319
-775
lines changed

21 files changed

+319
-775
lines changed

Sources/ContentScopeScripts/dist/contentScope.js

Lines changed: 2 additions & 3 deletions
Large diffs are not rendered by default.

Sources/ContentScopeScripts/dist/contentScopeIsolated.js

Lines changed: 21 additions & 46 deletions
Large diffs are not rendered by default.

build/android/contentScope.js

Lines changed: 2 additions & 3 deletions
Large diffs are not rendered by default.

build/chrome-mv3/inject.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -787,8 +787,7 @@
787787
windows: [
788788
...baseFeatures,
789789
'windowsPermissionUsage',
790-
'duckPlayer',
791-
'harmfulApis'
790+
'duckPlayer'
792791
],
793792
firefox: [
794793
...baseFeatures,

build/chrome/inject.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/contentScope.js

Lines changed: 45 additions & 48 deletions
Large diffs are not rendered by default.

build/firefox/inject.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,8 +817,7 @@
817817
windows: [
818818
...baseFeatures,
819819
'windowsPermissionUsage',
820-
'duckPlayer',
821-
'harmfulApis'
820+
'duckPlayer'
822821
],
823822
firefox: [
824823
...baseFeatures,

build/integration/contentScope.js

Lines changed: 45 additions & 48 deletions
Large diffs are not rendered by default.

build/tracker-lookup.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

build/windows/contentScope.js

Lines changed: 46 additions & 571 deletions
Large diffs are not rendered by default.

integration-test/playwright/duckplayer.e2e.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,24 @@ test.describe('e2e: Duck Player Thumbnail Overlays on YouTube.com', () => {
1919
await overlays.hoverShort()
2020
await overlays.overlaysDontShow()
2121
})
22+
test('control (without our script): clicking on a short loads correctly', async ({ page }, workerInfo) => {
23+
// @ts-expect-error - TS doesn't know about the "use.e2e" property
24+
workerInfo.skip(!workerInfo.project.use?.e2e)
25+
const overlays = DuckplayerOverlays.create(page, workerInfo)
26+
await overlays.gotoYoutubeHomepage()
27+
await page.waitForTimeout(2000)
28+
await overlays.clicksFirstShortsThumbnail()
29+
await overlays.showsShortsPage()
30+
})
31+
test('e2e: when enabled, clicking shorts has no impact', async ({ page }, workerInfo) => {
32+
// @ts-expect-error - TS doesn't know about the "use.e2e" property
33+
workerInfo.skip(!workerInfo.project.use?.e2e)
34+
const overlays = DuckplayerOverlays.create(page, workerInfo)
35+
await overlays.overlaysEnabled({ json: 'overlays-live' })
36+
await overlays.userSettingIs('enabled')
37+
await overlays.gotoYoutubeHomepage()
38+
await page.waitForTimeout(2000)
39+
await overlays.clicksFirstShortsThumbnail()
40+
await overlays.showsShortsPage()
41+
})
2242
})

integration-test/playwright/duckplayer.spec.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ test.describe('Duck Player Thumbnail Overlays on YouTube.com', () => {
2828
await overlays.hoverShort()
2929
await overlays.overlaysDontShow()
3030
})
31+
/**
32+
* https://app.asana.com/0/1201048563534612/1204993915251837/f
33+
*/
34+
test('Clicks are not intercepted on shorts when "enabled"', async ({ page }, workerInfo) => {
35+
const overlays = DuckplayerOverlays.create(page, workerInfo)
36+
await overlays.overlaysEnabled()
37+
await overlays.userSettingIs('enabled')
38+
await overlays.gotoThumbsPage()
39+
const navigation = overlays.requestWillFail()
40+
await overlays.clicksFirstShortsThumbnail()
41+
const url = await navigation
42+
await overlays.opensShort(url)
43+
})
3144
test('Overlays don\'t show on thumbnails when disabled', async ({ page }, workerInfo) => {
3245
const overlays = DuckplayerOverlays.create(page, workerInfo)
3346

integration-test/playwright/harmful-apis.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { readFileSync } from 'fs'
33
import { mockWindowsMessaging, wrapWindowsScripts } from '@duckduckgo/messaging/lib/test-utils.mjs'
44
import { perPlatform } from './type-helpers.mjs'
55

6-
test('Harmful APIs protections', async ({ page }, testInfo) => {
6+
test.skip('Harmful APIs protections', async ({ page }, testInfo) => {
77
const protection = HarmfulApisSpec.create(page, testInfo)
88
await protection.enabled()
99
const results = await protection.runTests();

integration-test/playwright/page-objects/duckplayer-overlays.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,22 @@ export class DuckplayerOverlays {
6060
await this.page.getByRole('button', { name: 'Reject the use of cookies and other data for the purposes described' }).click()
6161
}
6262

63+
async clicksFirstShortsThumbnail () {
64+
await this.page.locator('[href*="/shorts"] img').first().click({ force: true })
65+
}
66+
67+
async showsShortsPage () {
68+
await this.page.waitForURL(/^https:\/\/www.youtube.com\/shorts/, { timeout: 5000 })
69+
}
70+
71+
/**
72+
* @param {string} requestUrl
73+
*/
74+
opensShort (requestUrl) {
75+
const url = new URL(requestUrl)
76+
expect(url.pathname).toBe('/shorts/1')
77+
}
78+
6379
/**
6480
* @param {object} [params]
6581
* @param {"default" | "incremental-dom"} [params.variant]
@@ -348,6 +364,22 @@ export class DuckplayerOverlays {
348364
? 'contentScopeScriptsIsolated'
349365
: 'contentScopeScripts'
350366
}
367+
368+
/**
369+
* @return {Promise<string>}
370+
*/
371+
requestWillFail () {
372+
return new Promise((resolve, reject) => {
373+
// on windows it will be a failed request
374+
const timer = setTimeout(() => {
375+
reject(new Error('timed out'))
376+
}, 5000)
377+
this.page.on('framenavigated', (req) => {
378+
clearTimeout(timer)
379+
resolve(req.url())
380+
})
381+
})
382+
}
351383
}
352384

353385
/**

integration-test/playwright/windows-permissions.spec.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,42 @@ export class WindowsPermissionsSpec {
3131
}
3232

3333
async enabled () {
34+
await this.installPolyfills()
3435
const config = JSON.parse(readFileSync(this.config, 'utf8'))
3536
await this.setup({ config })
3637
await this.page.goto(this.htmlPage)
3738
}
3839

40+
/**
41+
* In CI, the global objects such as USB might not be installed on the
42+
* version of chromium running there.
43+
*/
44+
async installPolyfills () {
45+
await this.page.addInitScript(() => {
46+
// @ts-expect-error - testing
47+
if (typeof Bluetooth === 'undefined') {
48+
globalThis.Bluetooth = {}
49+
globalThis.Bluetooth.prototype = { requestDevice: async () => { /* noop */ } }
50+
}
51+
// @ts-expect-error - testing
52+
if (typeof USB === 'undefined') {
53+
globalThis.USB = {}
54+
globalThis.USB.prototype = { requestDevice: async () => { /* noop */ } }
55+
}
56+
57+
// @ts-expect-error - testing
58+
if (typeof Serial === 'undefined') {
59+
globalThis.Serial = {}
60+
globalThis.Serial.prototype = { requestPort: async () => { /* noop */ } }
61+
}
62+
// @ts-expect-error - testing
63+
if (typeof HID === 'undefined') {
64+
globalThis.HID = {}
65+
globalThis.HID.prototype = { requestDevice: async () => { /* noop */ } }
66+
}
67+
})
68+
}
69+
3970
/**
4071
* @param {object} params
4172
* @param {Record<string, any>} params.config

integration-test/test-pages/harmful-apis/config/apis.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"unprotectedTemporary": [],
33
"features": {
4+
"windowsPermissionUsage": {
5+
"state": "disabled"
6+
},
47
"harmfulApis": {
58
"state": "enabled",
69
"settings": {

integration-test/test-pages/permissions/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,19 @@
1414
<script>
1515
test('Disabled Windows Permissions', async () => {
1616
const registerProtocolHandler = await captureError(() => Navigator.prototype.registerProtocolHandler())
17+
const requestDevice = await captureError(() => Bluetooth.prototype.requestDevice())
18+
const usbDevice = await captureError(() => USB.prototype.requestDevice())
19+
const serialPort = await captureError(() => Serial.prototype.requestPort())
20+
const hidDevice = await captureError(() => HID.prototype.requestDevice())
21+
const midi = await captureError(() => Navigator.prototype.requestMIDIAccess())
1722

1823
return [
24+
{ name: 'Bluetooth.prototype.requestDevice()', result: requestDevice.toString(), expected: 'Error: Permission denied' },
25+
{ name: 'USB.prototype.requestDevice', result: usbDevice.toString(), expected: 'Error: Permission denied' },
26+
{ name: 'Serial.prototype.requestPort', result: serialPort.toString(), expected: 'Error: Permission denied' },
27+
{ name: 'HID.prototype.requestDevice', result: hidDevice.toString(), expected: 'Error: Permission denied' },
1928
{ name: 'Protocol handler: Navigator.prototype.registerProtocolHandler', result: registerProtocolHandler.toString(), expected: 'Error: Permission denied' },
29+
{ name: 'MIDI: Navigator.prototype.requestMIDIAccess', result: midi.toString(), expected: 'Error: Permission denied' },
2030
];
2131
})
2232

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"playwright-headed": "playwright test --headed",
4343
"preplaywright": "npm run build-windows && npm run build-apple",
4444
"preplaywright-headed": "npm run build-windows && npm run build-apple",
45-
"playwright-e2e": "E2E=true playwright test --project duckplayer-e2e --headed"
45+
"playwright-e2e": "E2E=true playwright test --project duckplayer-e2e"
4646
},
4747
"type": "module",
4848
"workspaces": [

src/features.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ export const platformSupport = {
4040
windows: [
4141
...baseFeatures,
4242
'windowsPermissionUsage',
43-
'duckPlayer',
44-
'harmfulApis'
43+
'duckPlayer'
4544
],
4645
firefox: [
4746
...baseFeatures,

src/features/duckplayer/overlays.js

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export async function initOverlays (environment, comms) {
109109

110110
/**
111111
* Bind hover events and make sure hovering the video will correctly show the hover
112-
* overlay and mousouting will hide it.
112+
* overlay and mouse-out will hide it.
113113
*/
114114
bindEvents: (video) => {
115115
if (video) {
@@ -119,7 +119,7 @@ export async function initOverlays (environment, comms) {
119119
// this can occur with 'shorts'
120120
if (video.href !== before) {
121121
if (!VideoThumbnail.isSingleVideoURL(video.href)) {
122-
return console.log('bailing because the video has changed')
122+
return
123123
}
124124
}
125125
IconOverlay.moveHoverOverlayToVideoElement(video)
@@ -280,50 +280,25 @@ export async function initOverlays (environment, comms) {
280280
// bail if it's not a valid element
281281
if (!isValidVideoLinkOrPreview(element)) return
282282

283-
// handle mouseover + click events
284-
const handler = {
285-
handleEvent (event) {
286-
switch (event.type) {
287-
case 'mouseover': {
288-
/**
289-
* Store the element's link value on hover - this occurs just in time
290-
* before the youtube overlay take sover the event space
291-
*/
292-
const href = element instanceof HTMLAnchorElement
293-
? VideoParams.fromHref(element.href)?.toPrivatePlayerUrl()
294-
: null
295-
if (href) {
296-
OpenInDuckPlayer.lastMouseOver = href
297-
}
298-
break
299-
}
300-
case 'click': {
301-
/**
302-
* On click, the receiver might be the preview element - if
303-
* it is, we want to use the last hovered `a` tag instead
304-
*/
305-
event.preventDefault()
306-
event.stopPropagation()
307-
308-
const link = event.target.closest('a')
309-
const fromClosest = VideoParams.fromHref(link?.href)?.toPrivatePlayerUrl()
310-
311-
if (fromClosest) {
312-
comms.openDuckPlayer({ href: fromClosest })
313-
} else if (OpenInDuckPlayer.lastMouseOver) {
314-
comms.openDuckPlayer({ href: OpenInDuckPlayer.lastMouseOver })
315-
} else {
316-
// could not navigate, doing nothing
317-
}
318-
319-
break
320-
}
321-
}
283+
function handler (event) {
284+
/**
285+
* Opening in Duck Player, preventing normal navigation
286+
*/
287+
function openInDuckPlayer (href) {
288+
event.preventDefault()
289+
event.stopPropagation()
290+
comms.openDuckPlayer({ href })
291+
}
292+
293+
// select either closest `a` or defer to element.href
294+
const targetValue = event.target.closest('a')?.href || /** @type {HTMLAnchorElement} */(element).href
295+
const validPrivatePlayerUrl = VideoParams.fromHref(targetValue)?.toPrivatePlayerUrl()
296+
297+
if (validPrivatePlayerUrl) {
298+
return openInDuckPlayer(validPrivatePlayerUrl)
322299
}
323300
}
324301

325-
// register both handlers
326-
element.addEventListener('mouseover', handler, true)
327302
element.addEventListener('click', handler, true)
328303

329304
// store the handler for removal later (eg: if settings change)
@@ -333,7 +308,6 @@ export async function initOverlays (environment, comms) {
333308

334309
disable: () => {
335310
OpenInDuckPlayer.clickBoundElements.forEach((handler, element) => {
336-
element.removeEventListener('mouseover', handler, true)
337311
element.removeEventListener('click', handler, true)
338312
OpenInDuckPlayer.clickBoundElements.delete(element)
339313
})

src/features/windows-permission-usage.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* global Geolocation */
1+
/* global Bluetooth, Geolocation, HID, Serial, USB */
22
import { DDGProxy, DDGReflect } from '../utils'
33
import { defineProperty } from '../wrapper-utils'
44
import ContentFeature from '../content-feature'
@@ -381,7 +381,16 @@ export default class WindowsPermissionUsage extends ContentFeature {
381381

382382
// these permissions cannot be disabled using WebView2 or DevTools protocol
383383
const permissionsToDisable = [
384-
{ name: 'Protocol handler', prototype: () => Navigator.prototype, method: 'registerProtocolHandler', isPromise: false }
384+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
385+
{ name: 'Bluetooth', prototype: () => Bluetooth.prototype, method: 'requestDevice', isPromise: true },
386+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
387+
{ name: 'USB', prototype: () => USB.prototype, method: 'requestDevice', isPromise: true },
388+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
389+
{ name: 'Serial', prototype: () => Serial.prototype, method: 'requestPort', isPromise: true },
390+
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
391+
{ name: 'HID', prototype: () => HID.prototype, method: 'requestDevice', isPromise: true },
392+
{ name: 'Protocol handler', prototype: () => Navigator.prototype, method: 'registerProtocolHandler', isPromise: false },
393+
{ name: 'MIDI', prototype: () => Navigator.prototype, method: 'requestMIDIAccess', isPromise: true }
385394
]
386395
for (const { name, prototype, method, isPromise } of permissionsToDisable) {
387396
try {
@@ -399,5 +408,18 @@ export default class WindowsPermissionUsage extends ContentFeature {
399408
console.info(`Could not disable access to ${name} because of error`, error)
400409
}
401410
}
411+
412+
// these permissions can be disabled using DevTools protocol but it's not reliable and can throw exception sometimes
413+
const permissionsToDelete = [
414+
{ name: 'Idle detection', permission: 'IdleDetector' },
415+
{ name: 'NFC', permission: 'NDEFReader' },
416+
{ name: 'Orientation', permission: 'ondeviceorientation' },
417+
{ name: 'Motion', permission: 'ondevicemotion' }
418+
]
419+
for (const { permission } of permissionsToDelete) {
420+
if (permission in window) {
421+
Reflect.deleteProperty(window, permission)
422+
}
423+
}
402424
}
403425
}

0 commit comments

Comments
 (0)