|
1964 | 1964 | let appID;
|
1965 | 1965 |
|
1966 | 1966 | const titleID = 'DuckDuckGoPrivacyEssentialsCTLElementTitle';
|
| 1967 | + |
| 1968 | + // TODO: Remove these redundant data structures and refactor the related code. |
| 1969 | + // There should be no need to have the entity configuration stored in two |
| 1970 | + // places. |
1967 | 1971 | const entities = [];
|
1968 | 1972 | const entityData = {};
|
1969 | 1973 |
|
|
3218 | 3222 | if (this.replaceSettings.type === 'loginButton') {
|
3219 | 3223 | isLogin = true;
|
3220 | 3224 | }
|
3221 |
| - window.addEventListener('ddg-ctp-enableSocialTracker-complete', () => { |
| 3225 | + window.addEventListener('ddg-ctp-unblockClickToLoadContent-complete', () => { |
3222 | 3226 | const parent = replacementElement.parentNode;
|
3223 | 3227 |
|
3224 | 3228 | // If we allow everything when this element is clicked,
|
|
3310 | 3314 | fbElement.addEventListener('error', onError, { once: true });
|
3311 | 3315 | }
|
3312 | 3316 | }, { once: true });
|
3313 |
| - enableSocialTracker({ entity: this.entity, action: 'block-ctl-fb', isLogin }); |
| 3317 | + unblockClickToLoadContent({ entity: this.entity, action: 'block-ctl-fb', isLogin }); |
3314 | 3318 | }
|
3315 | 3319 | }.bind(this);
|
3316 | 3320 | // If this is a login button, show modal if needed
|
|
3323 | 3327 | }
|
3324 | 3328 | }
|
3325 | 3329 |
|
3326 |
| - async function initCTL (resp) { |
3327 |
| - for (const entity of Object.keys(resp)) { |
3328 |
| - entities.push(entity); |
3329 |
| - const { informationalModal, simpleVersion } = resp[entity]; |
3330 |
| - const shouldShowLoginModal = !!informationalModal; |
3331 |
| - |
3332 |
| - const currentEntityData = { |
3333 |
| - shouldShowLoginModal, |
3334 |
| - simpleVersion |
3335 |
| - }; |
3336 |
| - |
3337 |
| - if (shouldShowLoginModal) { |
3338 |
| - currentEntityData.modalIcon = informationalModal.icon; |
3339 |
| - currentEntityData.modalTitle = informationalModal.messageTitle; |
3340 |
| - currentEntityData.modalText = informationalModal.messageBody; |
3341 |
| - currentEntityData.modalAcceptText = informationalModal.confirmButtonText; |
3342 |
| - currentEntityData.modalRejectText = informationalModal.rejectButtonText; |
3343 |
| - } |
3344 |
| - |
3345 |
| - entityData[entity] = currentEntityData; |
3346 |
| - } |
3347 |
| - await replaceClickToLoadElements(resp); |
| 3330 | + /** |
| 3331 | + * Initialise the Click to Load feature, once the necessary details have been |
| 3332 | + * returned by the platform. |
| 3333 | + * @returns {Promise} |
| 3334 | + */ |
| 3335 | + async function initCTL () { |
| 3336 | + await replaceClickToLoadElements(); |
3348 | 3337 |
|
3349 | 3338 | window.addEventListener('ddg-ctp-replace-element', ({ target }) => {
|
3350 |
| - replaceClickToLoadElements(resp, target); |
| 3339 | + replaceClickToLoadElements(target); |
3351 | 3340 | }, { capture: true });
|
3352 | 3341 |
|
3353 | 3342 | // Inform surrogate scripts that CTP is ready
|
|
3506 | 3495 |
|
3507 | 3496 | /**
|
3508 | 3497 | * Replace the blocked CTP elements on the page with placeholders.
|
3509 |
| - * @param {Object} config |
3510 |
| - * The parsed Click to Play configuration. |
3511 | 3498 | * @param {Element} [targetElement]
|
3512 | 3499 | * If specified, only this element will be replaced (assuming it matches
|
3513 | 3500 | * one of the expected CSS selectors). If omitted, all matching elements
|
3514 | 3501 | * in the document will be replaced instead.
|
3515 | 3502 | */
|
3516 |
| - async function replaceClickToLoadElements (config, targetElement) { |
| 3503 | + async function replaceClickToLoadElements (targetElement) { |
3517 | 3504 | for (const entity of Object.keys(config)) {
|
3518 | 3505 | for (const widgetData of Object.values(config[entity].elementData)) {
|
3519 | 3506 | const selector = widgetData.selectors.join();
|
|
3540 | 3527 | *********************************************************/
|
3541 | 3528 |
|
3542 | 3529 | /**
|
3543 |
| - * @typedef enableSocialTrackerRequest |
| 3530 | + * @typedef unblockClickToLoadContentRequest |
3544 | 3531 | * @property {string} entity
|
3545 | 3532 | * The entity to unblock requests for (e.g. "Facebook").
|
3546 | 3533 | * @property {bool} [isLogin=false]
|
|
3555 | 3542 | /**
|
3556 | 3543 | * Send a message to the background to unblock requests for the given entity for
|
3557 | 3544 | * the page.
|
3558 |
| - * @param {enableSocialTrackerRequest} message |
3559 |
| - * @see {@event ddg-ctp-enableSocialTracker-complete} for the response handler. |
| 3545 | + * @param {unblockClickToLoadContentRequest} message |
| 3546 | + * @see {@event ddg-ctp-unblockClickToLoadContent-complete} for the response handler. |
3560 | 3547 | */
|
3561 |
| - function enableSocialTracker (message) { |
3562 |
| - sendMessage('enableSocialTracker', message); |
| 3548 | + function unblockClickToLoadContent (message) { |
| 3549 | + sendMessage('unblockClickToLoadContent', message); |
3563 | 3550 | }
|
3564 | 3551 |
|
3565 | 3552 | function runLogin (entity) {
|
3566 |
| - enableSocialTracker({ entity, isLogin: true }); |
| 3553 | + unblockClickToLoadContent({ entity, isLogin: true }); |
3567 | 3554 | originalWindowDispatchEvent(
|
3568 | 3555 | createCustomEvent('ddg-ctp-run-login', {
|
3569 | 3556 | detail: {
|
|
4292 | 4279 | return { youTubePreview, shadowRoot }
|
4293 | 4280 | }
|
4294 | 4281 |
|
| 4282 | + // Convention is that each function should be named the same as the sendMessage |
| 4283 | + // method we are calling into eg. calling `sendMessage('getClickToLoadState')` |
| 4284 | + // will result in a response routed to `updateHandlers.getClickToLoadState()`. |
4295 | 4285 | const updateHandlers = {
|
4296 |
| - // Convention is that each function should be named the same as the sendMessage method we are calling into |
4297 |
| - // eg. calling sendMessage('initClickToLoad') will result in a response routed to 'updateHandlers.initClickToLoad()' |
4298 |
| - initClickToLoad: function (resp) { |
| 4286 | + getClickToLoadState (response) { |
| 4287 | + devMode = response.devMode; |
| 4288 | + isYoutubePreviewsEnabled = response.youtubePreviewsEnabled; |
| 4289 | + const { clickToLoadClicks } = response; |
| 4290 | + |
| 4291 | + for (const [entity, clickCount] of Object.entries(clickToLoadClicks)) { |
| 4292 | + if (entityData[entity]) { |
| 4293 | + entityData[entity].simpleVersion = |
| 4294 | + clickCount >= entityData[entity].maxClicks; |
| 4295 | + } |
| 4296 | + } |
| 4297 | + |
| 4298 | + // TODO: Move the below init logic to the exported init() function, |
| 4299 | + // somehow waiting for this response handler to have been called |
| 4300 | + // first. |
| 4301 | + |
| 4302 | + // Start Click to Load |
4299 | 4303 | if (document.readyState === 'complete') {
|
4300 |
| - initCTL(resp); |
| 4304 | + initCTL(); |
4301 | 4305 | } else {
|
4302 | 4306 | // Content script loaded before page content, so wait for load.
|
4303 | 4307 | window.addEventListener('load', (event) => {
|
4304 |
| - initCTL(resp); |
| 4308 | + initCTL(); |
4305 | 4309 | });
|
4306 | 4310 | }
|
4307 | 4311 | },
|
4308 |
| - getDevMode: function (resp) { |
4309 |
| - devMode = resp; |
4310 |
| - }, |
4311 |
| - getYoutubePreviewsEnabled: function (resp) { |
4312 |
| - isYoutubePreviewsEnabled = resp; |
4313 |
| - }, |
4314 | 4312 | setYoutubePreviewsEnabled: function (resp) {
|
4315 | 4313 | if (resp?.messageType && typeof resp?.value === 'boolean') {
|
4316 | 4314 | originalWindowDispatchEvent(new OriginalCustomEvent(resp.messageType, { detail: resp.value }));
|
|
4321 | 4319 | originalWindowDispatchEvent(new OriginalCustomEvent('ddg-ctp-youTubeVideoDetails', { detail: resp }));
|
4322 | 4320 | }
|
4323 | 4321 | },
|
4324 |
| - enableSocialTracker: function (resp) { |
4325 |
| - originalWindowDispatchEvent(new OriginalCustomEvent('ddg-ctp-enableSocialTracker-complete', { detail: resp })); |
| 4322 | + unblockClickToLoadContent () { |
| 4323 | + originalWindowDispatchEvent(new OriginalCustomEvent('ddg-ctp-unblockClickToLoadContent-complete')); |
4326 | 4324 | }
|
4327 | 4325 | };
|
4328 | 4326 |
|
4329 | 4327 | function init$e (args) {
|
4330 |
| - sendMessage('getDevMode'); |
4331 |
| - sendMessage('getYoutubePreviewsEnabled'); |
4332 |
| - sendMessage('initClickToLoad', config); |
| 4328 | + const websiteOwner = args?.site?.parentEntity; |
| 4329 | + const settings = args?.featureSettings?.clickToPlay || {}; |
| 4330 | + |
| 4331 | + for (const entity of Object.keys(config)) { |
| 4332 | + // Strip config entities that are first-party, or aren't enabled in the |
| 4333 | + // extension's clickToPlay settings. |
| 4334 | + // Note: To support legacy configurations consider `undefined` state as |
| 4335 | + // "enabled". |
| 4336 | + if ((websiteOwner && entity === websiteOwner) || |
| 4337 | + !settings[entity] || |
| 4338 | + settings[entity].state === 'disabled') { |
| 4339 | + delete config[entity]; |
| 4340 | + continue |
| 4341 | + } |
| 4342 | + |
| 4343 | + // Populate the entities and entityData data structures. |
| 4344 | + // TODO: Remove them and this logic, they seem unnecessary. |
4333 | 4345 |
|
4334 |
| - // Listen for events from surrogates |
| 4346 | + entities.push(entity); |
| 4347 | + |
| 4348 | + const shouldShowLoginModal = !!config[entity].informationalModal; |
| 4349 | + const maxClicks = config[entity].clicksBeforeSimpleVersion || 3; |
| 4350 | + const currentEntityData = { maxClicks, shouldShowLoginModal }; |
| 4351 | + |
| 4352 | + if (shouldShowLoginModal) { |
| 4353 | + const { informationalModal } = config[entity]; |
| 4354 | + currentEntityData.modalIcon = informationalModal.icon; |
| 4355 | + currentEntityData.modalTitle = informationalModal.messageTitle; |
| 4356 | + currentEntityData.modalText = informationalModal.messageBody; |
| 4357 | + currentEntityData.modalAcceptText = informationalModal.confirmButtonText; |
| 4358 | + currentEntityData.modalRejectText = informationalModal.rejectButtonText; |
| 4359 | + } |
| 4360 | + |
| 4361 | + entityData[entity] = currentEntityData; |
| 4362 | + } |
| 4363 | + |
| 4364 | + // Listen for events from "surrogate" scripts. |
4335 | 4365 | addEventListener('ddg-ctp', (event) => {
|
4336 | 4366 | if (!event.detail) return
|
4337 | 4367 | const entity = event.detail.entity;
|
|
4351 | 4381 | }
|
4352 | 4382 | }
|
4353 | 4383 | });
|
| 4384 | + |
| 4385 | + // Request the current state of Click to Load from the platform. |
| 4386 | + // Note: When the response is received, initCTL() is then called by the |
| 4387 | + // response handler to finish starting up the feature. |
| 4388 | + sendMessage('getClickToLoadState'); |
4354 | 4389 | }
|
4355 | 4390 |
|
4356 | 4391 | function update$1 (args) {
|
|
0 commit comments