Skip to content

Display Click to Load placeholders on demand #287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions inject/chrome.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,25 @@ function init () {
}
})

window.addEventListener('sendMessageProxy' + messageSecret, (m) => {
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
const messageType = m.detail.messageType
window.addEventListener('sendMessageProxy' + messageSecret, event => {
event.stopImmediatePropagation()

if (!(event instanceof CustomEvent) || !event?.detail) {
return console.warn('no details in sendMessage proxy', event)
}

const messageType = event.detail?.messageType
if (!allowedMessages.includes(messageType)) {
return console.warn('Ignoring invalid sendMessage messageType', messageType)
}
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
chrome.runtime.sendMessage(m && m.detail, response => {
const msg = { func: messageType, response }
const stringifiedArgs = JSON.stringify({ detail: msg })

chrome.runtime.sendMessage(event.detail, response => {
const message = {
messageType: 'response',
responseMessageType: messageType,
response
}
const stringifiedArgs = JSON.stringify(message)
const callRandomUpdateFunction = `
window.${reusableMethodName}('${reusableSecret}', ${stringifiedArgs});
`
Expand Down
24 changes: 17 additions & 7 deletions inject/mozilla.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,26 @@ function init () {
}
})

window.addEventListener('sendMessageProxy' + messageSecret, (m) => {
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
const messageType = m.detail.messageType
window.addEventListener('sendMessageProxy' + messageSecret, event => {
event.stopImmediatePropagation()

if (!(event instanceof CustomEvent) || !event?.detail) {
return console.warn('no details in sendMessage proxy', event)
}

const messageType = event.detail?.messageType
if (!allowedMessages.includes(messageType)) {
return console.warn('Ignoring invalid sendMessage messageType', messageType)
}
// @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f
chrome.runtime.sendMessage(m && m.detail, response => {
const msg = { func: messageType, response }
contentScopeFeatures.update({ detail: msg })

chrome.runtime.sendMessage(event.detail, response => {
const message = {
messageType: 'response',
responseMessageType: messageType,
response
}

contentScopeFeatures.update(message)
})
})
}
Expand Down
80 changes: 45 additions & 35 deletions src/features/click-to-play.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ let sharedStrings = null
const entities = []
const entityData = {}

let readyResolver
const ready = new Promise(resolve => { readyResolver = resolve })

/*********************************************************
* Widget Replacement logic
*********************************************************/
Expand Down Expand Up @@ -364,22 +367,6 @@ class DuckWidget {
}
}

/**
* Initialise the Click to Load feature, once the necessary details have been
* returned by the platform.
* @returns {Promise}
*/
async function initCTL () {
await replaceClickToLoadElements()

window.addEventListener('ddg-ctp-replace-element', ({ target }) => {
replaceClickToLoadElements(target)
}, { capture: true })

// Inform surrogate scripts that CTP is ready
originalWindowDispatchEvent(createCustomEvent('ddg-ctp-ready'))
}

function replaceTrackingElement (widget, trackingElement, placeholderElement, hideTrackingElement = false, currentPlaceholder = null) {
widget.dispatchEvent(trackingElement, 'ddg-ctp-tracking-element')

Expand Down Expand Up @@ -492,11 +479,14 @@ async function replaceYouTubeCTL (trackingElement, widget, togglePlaceholder = f
}

// Show YouTube Preview for embedded video
// TODO: Fix the hideTrackingElement option and reenable, or remove it. It's
// disabled for YouTube videos so far since it caused multiple
// placeholders to be displayed on the page.
if (isYoutubePreviewsEnabled === true) {
const { youTubePreview, shadowRoot } = await createYouTubePreview(trackingElement, widget)
const currentPlaceholder = togglePlaceholder ? document.getElementById(`yt-ctl-dialog-${widget.widgetID}`) : null
replaceTrackingElement(
widget, trackingElement, youTubePreview, /* hideTrackingElement= */ true, currentPlaceholder
widget, trackingElement, youTubePreview, /* hideTrackingElement= */ false, currentPlaceholder
)
showExtraUnblockIfShortPlaceholder(shadowRoot, youTubePreview)

Expand All @@ -506,7 +496,7 @@ async function replaceYouTubeCTL (trackingElement, widget, togglePlaceholder = f
const { blockingDialog, shadowRoot } = await createYouTubeBlockingDialog(trackingElement, widget)
const currentPlaceholder = togglePlaceholder ? document.getElementById(`yt-ctl-preview-${widget.widgetID}`) : null
replaceTrackingElement(
widget, trackingElement, blockingDialog, /* hideTrackingElement= */ true, currentPlaceholder
widget, trackingElement, blockingDialog, /* hideTrackingElement= */ false, currentPlaceholder
)
showExtraUnblockIfShortPlaceholder(shadowRoot, blockingDialog)
}
Expand Down Expand Up @@ -537,6 +527,8 @@ function showExtraUnblockIfShortPlaceholder (shadowRoot, placeholder) {
* in the document will be replaced instead.
*/
async function replaceClickToLoadElements (targetElement) {
await ready

for (const entity of Object.keys(config)) {
for (const widgetData of Object.values(config[entity].elementData)) {
const selector = widgetData.selectors.join()
Expand Down Expand Up @@ -1312,7 +1304,7 @@ async function createYouTubePreview (originalElement, widget) {
// Convention is that each function should be named the same as the sendMessage
// method we are calling into eg. calling `sendMessage('getClickToLoadState')`
// will result in a response routed to `updateHandlers.getClickToLoadState()`.
const updateHandlers = {
const messageResponseHandlers = {
getClickToLoadState (response) {
devMode = response.devMode
isYoutubePreviewsEnabled = response.youtubePreviewsEnabled
Expand All @@ -1322,14 +1314,15 @@ const updateHandlers = {
// first.

// Start Click to Load
if (document.readyState === 'complete') {
initCTL()
} else {
// Content script loaded before page content, so wait for load.
window.addEventListener('load', (event) => {
initCTL()
})
}
window.addEventListener('ddg-ctp-replace-element', ({ target }) => {
replaceClickToLoadElements(target)
}, { capture: true })

// Inform surrogate scripts that CTP is ready
originalWindowDispatchEvent(createCustomEvent('ddg-ctp-ready'))

// Mark the feature as ready, to allow placeholder replacements.
readyResolver()
},
setYoutubePreviewsEnabled: function (resp) {
if (resp?.messageType && typeof resp?.value === 'boolean') {
Expand All @@ -1346,6 +1339,8 @@ const updateHandlers = {
}
}

const knownMessageResponseType = Object.prototype.hasOwnProperty.bind(messageResponseHandlers)

export function init (args) {
const websiteOwner = args?.site?.parentEntity
const settings = args?.featureSettings?.clickToPlay || {}
Expand Down Expand Up @@ -1411,17 +1406,32 @@ export function init (args) {
})

// Request the current state of Click to Load from the platform.
// Note: When the response is received, initCTL() is then called by the
// response handler to finish starting up the feature.
// Note: When the response is received, the response handler finishes
// starting up the feature.
sendMessage('getClickToLoadState')
}

export function update (args) {
const detail = args && args.detail
if (!(detail && detail.func)) { return }
export function update (message) {
// TODO: Once all Click to Load messages include the feature property, drop
// messages that don't include the feature property too.
if (message?.feature && message?.feature !== 'clickToLoad') return

const fn = updateHandlers[detail.func]
if (typeof fn !== 'function') { return }
const messageType = message?.messageType
if (!messageType) return

fn(detail.response)
// Message responses.
if (messageType === 'response') {
const messageResponseType = message?.responseMessageType
if (messageResponseType && knownMessageResponseType(messageResponseType)) {
return messageResponseHandlers[messageResponseType](message.response)
}
}

// Other known update messages.
if (messageType === 'displayClickToLoadPlaceholders') {
// TODO: Pass `message.options.ruleAction` through, that way only
// content corresponding to the entity for that ruleAction need to
// be replaced with a placeholder.
return replaceClickToLoadElements()
}
}