Skip to content

Commit c993278

Browse files
shakyShaneShane Osbourne
and
Shane Osbourne
authored
fix(duckplayer): fix clicks on shorts (#619)
Co-authored-by: Shane Osbourne <[email protected]>
1 parent 6f2b43e commit c993278

File tree

5 files changed

+84
-45
lines changed

5 files changed

+84
-45
lines changed

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/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
/**

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/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
})

0 commit comments

Comments
 (0)