Skip to content

Commit 76c807d

Browse files
authored
Rethink the Click to Load initialisation logic (#230)
- Avoid messaging the huge placeholder configuration Object to the platform, just to have it sent back again. - Use the already available parentEntity of the current website, instead of asking the platform for it. - Determine if "clicksBeforeSimpleVersion" has been exceeded here, instead of relying on the platform to determine that. - Take care to remove entities from the placeholder configuration Object that are not also enabled in the feature configuration. - Take care to reduce the number of messages sent during initialisation with the happy side-effect of fixing a race condition. - Remove the now unused message handlers and rename the "enableSocialTracker" message to "unblockClickToLoadContent".
1 parent be63500 commit 76c807d

File tree

10 files changed

+623
-386
lines changed

10 files changed

+623
-386
lines changed

Sources/ContentScopeScripts/dist/contentScope.js

+86-51
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,10 @@
19641964
let appID;
19651965

19661966
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.
19671971
const entities = [];
19681972
const entityData = {};
19691973

@@ -3218,7 +3222,7 @@
32183222
if (this.replaceSettings.type === 'loginButton') {
32193223
isLogin = true;
32203224
}
3221-
window.addEventListener('ddg-ctp-enableSocialTracker-complete', () => {
3225+
window.addEventListener('ddg-ctp-unblockClickToLoadContent-complete', () => {
32223226
const parent = replacementElement.parentNode;
32233227

32243228
// If we allow everything when this element is clicked,
@@ -3310,7 +3314,7 @@
33103314
fbElement.addEventListener('error', onError, { once: true });
33113315
}
33123316
}, { once: true });
3313-
enableSocialTracker({ entity: this.entity, action: 'block-ctl-fb', isLogin });
3317+
unblockClickToLoadContent({ entity: this.entity, action: 'block-ctl-fb', isLogin });
33143318
}
33153319
}.bind(this);
33163320
// If this is a login button, show modal if needed
@@ -3323,31 +3327,16 @@
33233327
}
33243328
}
33253329

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();
33483337

33493338
window.addEventListener('ddg-ctp-replace-element', ({ target }) => {
3350-
replaceClickToLoadElements(resp, target);
3339+
replaceClickToLoadElements(target);
33513340
}, { capture: true });
33523341

33533342
// Inform surrogate scripts that CTP is ready
@@ -3506,14 +3495,12 @@
35063495

35073496
/**
35083497
* Replace the blocked CTP elements on the page with placeholders.
3509-
* @param {Object} config
3510-
* The parsed Click to Play configuration.
35113498
* @param {Element} [targetElement]
35123499
* If specified, only this element will be replaced (assuming it matches
35133500
* one of the expected CSS selectors). If omitted, all matching elements
35143501
* in the document will be replaced instead.
35153502
*/
3516-
async function replaceClickToLoadElements (config, targetElement) {
3503+
async function replaceClickToLoadElements (targetElement) {
35173504
for (const entity of Object.keys(config)) {
35183505
for (const widgetData of Object.values(config[entity].elementData)) {
35193506
const selector = widgetData.selectors.join();
@@ -3540,7 +3527,7 @@
35403527
*********************************************************/
35413528

35423529
/**
3543-
* @typedef enableSocialTrackerRequest
3530+
* @typedef unblockClickToLoadContentRequest
35443531
* @property {string} entity
35453532
* The entity to unblock requests for (e.g. "Facebook").
35463533
* @property {bool} [isLogin=false]
@@ -3555,15 +3542,15 @@
35553542
/**
35563543
* Send a message to the background to unblock requests for the given entity for
35573544
* 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.
35603547
*/
3561-
function enableSocialTracker (message) {
3562-
sendMessage('enableSocialTracker', message);
3548+
function unblockClickToLoadContent (message) {
3549+
sendMessage('unblockClickToLoadContent', message);
35633550
}
35643551

35653552
function runLogin (entity) {
3566-
enableSocialTracker({ entity, isLogin: true });
3553+
unblockClickToLoadContent({ entity, isLogin: true });
35673554
originalWindowDispatchEvent(
35683555
createCustomEvent('ddg-ctp-run-login', {
35693556
detail: {
@@ -4292,25 +4279,36 @@
42924279
return { youTubePreview, shadowRoot }
42934280
}
42944281

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()`.
42954285
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
42994303
if (document.readyState === 'complete') {
4300-
initCTL(resp);
4304+
initCTL();
43014305
} else {
43024306
// Content script loaded before page content, so wait for load.
43034307
window.addEventListener('load', (event) => {
4304-
initCTL(resp);
4308+
initCTL();
43054309
});
43064310
}
43074311
},
4308-
getDevMode: function (resp) {
4309-
devMode = resp;
4310-
},
4311-
getYoutubePreviewsEnabled: function (resp) {
4312-
isYoutubePreviewsEnabled = resp;
4313-
},
43144312
setYoutubePreviewsEnabled: function (resp) {
43154313
if (resp?.messageType && typeof resp?.value === 'boolean') {
43164314
originalWindowDispatchEvent(new OriginalCustomEvent(resp.messageType, { detail: resp.value }));
@@ -4321,17 +4319,49 @@
43214319
originalWindowDispatchEvent(new OriginalCustomEvent('ddg-ctp-youTubeVideoDetails', { detail: resp }));
43224320
}
43234321
},
4324-
enableSocialTracker: function (resp) {
4325-
originalWindowDispatchEvent(new OriginalCustomEvent('ddg-ctp-enableSocialTracker-complete', { detail: resp }));
4322+
unblockClickToLoadContent () {
4323+
originalWindowDispatchEvent(new OriginalCustomEvent('ddg-ctp-unblockClickToLoadContent-complete'));
43264324
}
43274325
};
43284326

43294327
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.
43334345

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.
43354365
addEventListener('ddg-ctp', (event) => {
43364366
if (!event.detail) return
43374367
const entity = event.detail.entity;
@@ -4351,6 +4381,11 @@
43514381
}
43524382
}
43534383
});
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');
43544389
}
43554390

43564391
function update$1 (args) {

0 commit comments

Comments
 (0)