Skip to content

Commit

Permalink
Trigger refresh of Click to Load placeholders on request block (#1738)
Browse files Browse the repository at this point in the history
Historically, the Click to Load placeholders were only drawn once at
the point page load completed. That had two problems:
1. Sometimes placeholders would take too long to display, if the page
   took a long time to load fully.
2. Sometimes placeholders would not be displayed at all, if the
   blocked content was only created after page load had finished.

To fix that, let's send a message to the content-scope-script to
ensure that placeholders are refreshed each time a Click to Load
request is blocked.

Also tweak the response messages to be inline with the recent
content-scope-script changes to make response messages easier to
identify.

Unfortunately the YouTube Click to Load integration tests need to be
disabled for now, since we had to disable the "hideTrackingElement"
option for now which breaks some of the YouTube SDK integration.
  • Loading branch information
kzar committed Mar 7, 2023
1 parent 8b5d389 commit 73a9baa
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 61 deletions.
3 changes: 2 additions & 1 deletion integration-test/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"spec_dir": "integration-test",
"spec_files": [
"legacy/*.js",
"content-scripts/gpc.js"
"content-scripts/gpc.js",
"!legacy/click-to-load-youtube.js"
]
}
10 changes: 4 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
},
"dependencies": {
"@duckduckgo/autofill": "github:duckduckgo/duckduckgo-autofill#6.3.0",
"@duckduckgo/content-scope-scripts": "github:duckduckgo/content-scope-scripts#4.3.1",
"@duckduckgo/content-scope-scripts": "github:duckduckgo/content-scope-scripts#4.3.3",
"@duckduckgo/ddg2dnr": "github:duckduckgo/ddg2dnr#0.4.1",
"@duckduckgo/jsbloom": "^1.0.2",
"@duckduckgo/privacy-dashboard": "github:duckduckgo/privacy-dashboard#1.4.1",
Expand Down
7 changes: 6 additions & 1 deletion shared/js/background/before-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ const {
} = require('./url-parameters')
const ampProtection = require('./amp-protection')
const {
displayClickToLoadPlaceholders,
getDefaultEnabledClickToLoadRuleActionsForTab
} = require('./dnr-click-to-load')
} = require('./click-to-load')

function buildResponse (url, requestData, tab, isMainFrame) {
if (url.toLowerCase() !== requestData.url.toLowerCase()) {
Expand Down Expand Up @@ -255,6 +256,10 @@ function blockHandleResponse (thisTab, requestData) {
const serviceWorkerInitiated = requestData.tabId === -1

if (tracker) {
if (tracker?.matchedRule?.action?.startsWith('block-ctl-')) {
displayClickToLoadPlaceholders(thisTab, tracker.matchedRule.action)
}

// temp allowlisted trackers to fix site breakage
if (thisTab.site.isFeatureEnabled('trackerAllowlist')) {
const allowListed = trackerAllowlist(thisTab.site.url, requestData.url)
Expand Down
69 changes: 69 additions & 0 deletions shared/js/background/click-to-load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import tdsStorage from './storage/tds'
import { sendTabMessage } from './utils'

/**
* Find the enabled Click to Load rule actions for the given tab.
* Note: Take care to ensure wait for the extension configuration to be ready
* first.
* @param {import('./classes/tab')} tab
* @return {string[]}
*/
export function getDefaultEnabledClickToLoadRuleActionsForTab (tab) {
// Click to Load feature isn't supported or is disabled for the tab.
if (!tab?.site?.isFeatureEnabled('clickToPlay')) {
return []
}

const clickToLoadSettings =
tdsStorage?.config?.features?.clickToPlay?.settings

// Click to Load configuration isn't ready yet.
if (!clickToLoadSettings) {
console.warn('Click to Load configuration not ready yet, skipped.')
return []
}

const enabledRuleActions = []
const { parentEntity } = tab.site

for (let [entity, { ruleActions, state }] of Object.entries(clickToLoadSettings)) {
// No rule actions, or entity is disabled.
if (!ruleActions || ruleActions.length === 0 || state !== 'enabled') {
continue
}

// TODO: Remove this workaround once the content-scope-scripts and
// privacy-configuration repositories have been updated.
if (entity === 'Facebook') entity = 'Facebook, Inc.'

// Enabled Click to Load entity is third-party for this tab, note its
// rule actions.
if (parentEntity !== entity) {
enabledRuleActions.push(...ruleActions)
}
}

return enabledRuleActions
}

/**
* Triggers a refresh of the Click to Load placeholders for the given tab.
* @param {import('./classes/tab')} tab
* @param {string} [ruleAction]
* If provided, only placeholders associated with the entity containing that
* rule action will be refreshed. By default, all placeholders will be
* refreshed.
*/
export async function displayClickToLoadPlaceholders (tab, ruleAction) {
const message = {
type: 'update',
feature: 'clickToLoad',
messageType: 'displayClickToLoadPlaceholders',
options: { }
}
if (typeof ruleAction === 'string') {
message.options.ruleAction = ruleAction
}

await sendTabMessage(tab.id, message)
}
46 changes: 1 addition & 45 deletions shared/js/background/dnr-click-to-load.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getDefaultEnabledClickToLoadRuleActionsForTab } from './click-to-load'
import { getNextSessionRuleId } from './dnr-session-rule-id'
import settings from './settings'
import tdsStorage from './storage/tds'
Expand Down Expand Up @@ -68,51 +69,6 @@ async function generateDnrAllowingRules (tab, ruleAction) {
return allowingRules
}

/**
* Find the enabled Click to Load rule actions for the given tab.
* Note: Take care to ensure wait for the extension configuration to be ready
* first.
* @param {import('./classes/tab')} tab
* @return {string[]}
*/
export function getDefaultEnabledClickToLoadRuleActionsForTab (tab) {
// Click to Load feature isn't supported or is disabled for the tab.
if (!tab?.site?.isFeatureEnabled('clickToPlay')) {
return []
}

const clickToLoadSettings =
tdsStorage?.config?.features?.clickToPlay?.settings

// Click to Load configuration isn't ready yet.
if (!clickToLoadSettings) {
console.warn('Click to Load configuration not ready yet, skipped.')
return []
}

const enabledRuleActions = []
const { parentEntity } = tab.site

for (let [entity, { ruleActions, state }] of Object.entries(clickToLoadSettings)) {
// No rule actions, or entity is disabled.
if (!ruleActions || ruleActions.length === 0 || state !== 'enabled') {
continue
}

// TODO: Remove this workaround once the content-scope-scripts and
// privacy-configuration repositories have been updated.
if (entity === 'Facebook') entity = 'Facebook, Inc.'

// Enabled Click to Load entity is third-party for this tab, note its
// rule actions.
if (parentEntity !== entity) {
enabledRuleActions.push(...ruleActions)
}
}

return enabledRuleActions
}

/**
* Ensure the correct declarativeNetRequest allowing session rules are added so
* that the default Click to Load rule actions are enabled/disabled for the tab.
Expand Down
27 changes: 20 additions & 7 deletions shared/js/content-scripts/content-scope-messaging.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,34 @@ function getSecret () {
async function init () {
const secret = await getSecret()

// Content-scope-script messaging proxy, to allow the Click to Load content
// script to send messages to the extension's background and receive a
// response.
// Note: This event listener is only for Chrome MV3 builds of the extension,
// the equivalent Chrome MV2 event listener lives in
// content-scope-scripts/inject/chrome.js.
window.addEventListener('sendMessageProxy' + secret, event => {
// MV3 message proxy for click to load
event.stopImmediatePropagation()
const detail = event && event.detail
if (!detail) {

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

const messageType = event.detail?.messageType
if (!allowedMessages.includes(messageType)) {
return console.warn('Ignoring invalid sendMessage messageType', messageType)
}
chrome.runtime.sendMessage(detail, response => {
const msg = { type: 'update', detail: { func: detail.messageType, response } }

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

window.dispatchEvent(new CustomEvent(secret, {
detail: msg
detail: message
}))
})
})
Expand Down

0 comments on commit 73a9baa

Please sign in to comment.