Skip to content
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

GH-1566 - Smart blocking should check for Click2Play #388

Merged
merged 9 commits into from Jun 11, 2019
Next

GH-1566 adjust smart blocking to handle click 2 play

  • Loading branch information
christophertino committed May 30, 2019
commit 7aadb55d839a4a64e353edf24afa6caed4f77fda
@@ -11,7 +11,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0
*/

import throttle from 'lodash.throttle';
import _ from 'underscore';
import React from 'react';
import ClassNames from 'classnames';
import {
@@ -178,7 +178,7 @@ class DonutGraph extends React.Component {
* @param {Array} categories list of categories detected on the site
* @param {Object} options options for the graph
*/
bakeDonut = throttle(this._bakeDonut.bind(this), 600, { leading: true, trailing: true }) // matches panelData#updatePanelUI throttling
bakeDonut = _.throttle(this._bakeDonut.bind(this), 600, { leading: true, trailing: true }) // matches panelData#updatePanelUI throttling

_bakeDonut(categories, options) {
const {
@@ -11,9 +11,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0
*/

import _ from 'underscore';
import React from 'react';
import * as D3 from 'd3';
import isEqual from 'lodash.isequal';

/**
* Generates an animated graph displaying locally stored stats
@@ -30,7 +30,7 @@ class StatsGraph extends React.Component {
* Lifecycle event
*/
componentDidUpdate(prevProps) {
if (!isEqual(prevProps, this.props)) {
if (!_.isEqual(prevProps, this.props)) {
this.generateGraph();
}
}
@@ -49,7 +49,9 @@
<p id="text"><%= data.click2play_text %></p>
<% } %>
<img id="ghostery-blocked" src="<%= data.ghostery_blocked_src %>" title="<%= data.ghostery_blocked_title %>">
<a id="action-once" href="#" onclick="return false"><img src="<%= data.allow_once_src %>" title="<%= data.allow_once_title %>"></a>
<% if (data.smartBlocked === false) { %>
<a id="action-once" href="#" onclick="return false"><img src="<%= data.allow_once_src %>" title="<%= data.allow_once_title %>"></a>
<% } %>
<% if (data.blacklisted === false) { %>
<a id="action-always" href="#" onclick="return false"><img src="<%= data.allow_always_src %>" title="<%= data.allow_always_title %>"></a>
<% } %>

This file was deleted.

@@ -52,8 +52,6 @@
"foundation-sites": "^6.4.4-rc1",
"history": "^4.7.2",
"json-api-normalizer": "^0.4.10",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"moment": "^2.19.1",
"prop-types": "^15.6.2",
"query-string": "^6.1.0",
@@ -71,6 +69,7 @@
"spanan": "^2.0.0",
"ua-parser-js": "^0.7.17",
"underscore": "^1.8.3",
"underscore-template-loader": "^1.0.0",
"url-search-params": "^0.10.2",
"whatwg-fetch": "^3.0.0"
},
@@ -96,7 +95,6 @@
"eslint-plugin-react": "^7.6.1",
"fs-extra": "^4.0.3",
"glob": "^7.1.2",
"html-loader": "^0.5.1",
"jest": "^23.6.0",
"jsdoc": "^3.5.5",
"jsonfile": "^4.0.0",
@@ -35,7 +35,6 @@ import { isBug } from '../utils/matcher';
import * as utils from '../utils/utils';

const IS_EDGE = (globals.BROWSER_INFO.name === 'edge');
const RequestsMap = new Map();
/**
* This class is a collection of handlers for
* webNavigation, webRequest and tabs events.
@@ -66,11 +65,10 @@ class EventHandlers {
onBeforeNavigate(details) {
const { tabId, frameId, url } = details;

// frameId === 0 indicates the navigation event ocurred in the content window, not a subframe
// frameId === 0 indicates the navigation event occurred in the content window, not a subframe
if (frameId === 0) {
log(`❤ ❤ ❤ Tab ${tabId} navigating to ${url} ❤ ❤ ❤`);

RequestsMap.clear();
this._clearTabData(tabId);
this._resetNotifications();
// TODO understand why this does not work when placed in the 'reload' branch in onCommitted
@@ -105,7 +103,7 @@ class EventHandlers {
tabId, frameId, transitionType, transitionQualifiers
} = details;

// frameId === 0 indicates the navigation event ocurred in the content window, not a subframe
// frameId === 0 indicates the navigation event occurred in the content window, not a subframe
if (frameId === 0) {
// update reload info before creating/clearing tab info
if (transitionType === 'reload' && !transitionQualifiers.includes('forward_back')) {
@@ -285,8 +283,6 @@ class EventHandlers {
return;
}

RequestsMap.clear();

// Code below executes for top level frame only
log(`foundBugs: ${foundBugs.getAppsCount(details.tabId)}, tab_id: ${details.tabId}`);

@@ -296,11 +292,11 @@ class EventHandlers {
log('onNavigationCompleted injectScript error', err);
});
}
// The problem is that requests may continue well after onNavigationCompleted
// This breaks allow once for C2P, as it clears too early
// Requests may continue well after onNavigationCompleted, which breaks
// C2P's "allow once" feature because the allowOnceList is cleared too early
setTimeout(() => {
this._eventReset(details.tabId);
}, 2000);
}, 5000); // match THRESHHOLD value in PolicySmartBlock._requestWasSlow()
}

/**
@@ -427,6 +423,9 @@ class EventHandlers {
};
}

const smartBlocked = !block ? this.policySmartBlock.shouldBlock(app_id, cat_id, tab_id, page_url, details.type, details.timeStamp) : false;
const smartUnblocked = block ? this.policySmartBlock.shouldUnblock(app_id, cat_id, tab_id, page_url, details.type) : false;

// process the tracker asynchronously
// very important to block request processing as little as necessary
setTimeout(() => {
@@ -436,20 +435,16 @@ class EventHandlers {
type: details.type,
url: details.url,
block,
smartBlocked,
tab_id,
from_frame: details.parentFrameId !== -1
});
}, 1);

if (block) {
if (this.policySmartBlock.shouldUnblock(app_id, cat_id, tab_id, page_url, details.type)) {
return { cancel: false };
}
if ((block && !smartUnblocked) || smartBlocked) {
return this._blockHelper(details, tab_id, app_id, bug_id, request_id, fromRedirect);
}
if (this.policySmartBlock.shouldBlock(app_id, cat_id, tab_id, page_url, details.type, details.timeStamp)) {
return this._blockHelper(details, tab_id, app_id, bug_id, request_id);
}

return { cancel: false };
}

@@ -600,13 +595,14 @@ class EventHandlers {
*/
_processBug(details) {
const {
bug_id, app_id, type, url, block, tab_id
bug_id, app_id, type, url, block, smartBlocked, tab_id
} = details;
const tab = tabInfo.getTabInfo(tab_id);
const allowedOnce = c2pDb.allowedOnce(details.tab_id, app_id);

let num_apps_old;

log((block ? 'Blocked' : 'Found'), type, url);
log((block || smartBlocked ? 'Blocked' : 'Found'), type, url);
log(`^^^ Pattern ID ${bug_id} on tab ID ${tab_id}`);

if (conf.show_alert) {
@@ -620,13 +616,13 @@ class EventHandlers {
// throttled in PanelData
panelData.updatePanelUI();

if (block && (conf.enable_click2play || conf.enable_click2playSocial)) {
if ((block || smartBlocked) && (conf.enable_click2play || conf.enable_click2playSocial) && !allowedOnce) {
buildC2P(details, app_id);
}

// Note: tab.purplebox handles a race condition where this function is sometimes called before onNavigation()
if (conf.show_alert && tab && !tab.prefetched && tab.purplebox) {
if (foundBugs.getAppsCount(details.tab_id) > num_apps_old || c2pDb.allowedOnce(details.tab_id, app_id)) {
if (foundBugs.getAppsCount(details.tab_id) > num_apps_old || allowedOnce) {
this.purplebox.updateBox(details.tab_id, app_id);
}
}
@@ -15,7 +15,6 @@
*/

import _ from 'underscore';
import throttle from 'lodash.throttle';
import button from './BrowserButton';
import conf from './Conf';
import foundBugs from './FoundBugs';
@@ -212,7 +211,7 @@ class PanelData {
* Invoked in EventHandlers#onBeforeRequest when a new bug has been found
* Sends updated data to the panel and blocking and/or summary components
*/
updatePanelUI = throttle(this._updatePanelUI.bind(this), 600, { leading: true, trailing: true }); // matches donut redraw throttling
updatePanelUI = _.throttle(this._updatePanelUI.bind(this), 600, { leading: true, trailing: true }); // matches donut redraw throttling
_updatePanelUI() {
if (!this._panelPort || !this._activeTab) { return; }

@@ -27,6 +27,7 @@ import globals from './Globals';
*/
export const BLOCK_REASON_BLOCK_PAUSED = 'BLOCK_REASON_BLOCK_PAUSED';
export const BLOCK_REASON_GLOBAL_BLOCKED = 'BLOCK_REASON_GLOBAL_BLOCKED';
export const BLOCK_REASON_GLOBAL_UNBLOCKED = 'BLOCK_REASON_GLOBAL_UNBLOCKED';
export const BLOCK_REASON_WHITELISTED = 'BLOCK_REASON_WHITELISTED';
export const BLOCK_REASON_BLACKLISTED = 'BLOCK_REASON_BLACKLISTED';
export const BLOCK_REASON_SS_UNBLOCKED = 'BLOCK_REASON_SS_UNBLOCKED';
@@ -124,33 +125,30 @@ class Policy {
return { block: false, reason: BLOCK_REASON_BLOCK_PAUSED };
}

const allowedOnce = c2pDb.allowedOnce(tab_id, app_id);
if (conf.selected_app_ids.hasOwnProperty(app_id)) {
if (conf.toggle_individual_trackers && conf.site_specific_unblocks.hasOwnProperty(tab_host) && conf.site_specific_unblocks[tab_host].includes(+app_id)) {
if (this.blacklisted(tab_url)) {
const allowedOnce = c2pDb.allowedOnce(tab_id, app_id);
return { block: !allowedOnce, reason: allowedOnce ? BLOCK_REASON_C2P_ALLOWED_ONCE : BLOCK_REASON_BLACKLISTED };
}
return { block: false, reason: BLOCK_REASON_SS_UNBLOCKED };
}
if (this.whitelisted(tab_url)) {
return { block: false, reason: BLOCK_REASON_WHITELISTED };
}
const allowedOnce = c2pDb.allowedOnce(tab_id, app_id);
return { block: !allowedOnce, reason: allowedOnce ? BLOCK_REASON_C2P_ALLOWED_ONCE : BLOCK_REASON_GLOBAL_BLOCKED };
}
// We get here when app_id is not selected for blocking
// We get here when app_id is not selected for global blocking
if (conf.toggle_individual_trackers && conf.site_specific_blocks.hasOwnProperty(tab_host) && conf.site_specific_blocks[tab_host].includes(+app_id)) {
if (this.whitelisted(tab_url)) {
return { block: false, reason: BLOCK_REASON_WHITELISTED };
}
const allowedOnce = c2pDb.allowedOnce(tab_id, app_id);
return { block: !allowedOnce, reason: allowedOnce ? BLOCK_REASON_C2P_ALLOWED_ONCE : BLOCK_REASON_SS_BLOCKED };
}
if (this.blacklisted(tab_url)) {
const allowedOnce = c2pDb.allowedOnce(tab_id, app_id);
return { block: !allowedOnce, reason: allowedOnce ? BLOCK_REASON_C2P_ALLOWED_ONCE : BLOCK_REASON_BLACKLISTED };
}
return { block: false, reason: BLOCK_REASON_GLOBAL_BLOCKED };
return { block: false, reason: allowedOnce ? BLOCK_REASON_C2P_ALLOWED_ONCE : BLOCK_REASON_GLOBAL_UNBLOCKED };
}
}

@@ -18,6 +18,7 @@ import tabInfo from './TabInfo';
import compDb from './CompatibilityDb';
import globals from './Globals';
import Policy from './Policy';
import c2pDb from './Click2PlayDb';
import { log } from '../utils/common';
/**
* Class for handling Smart Blocking site policy.
@@ -86,7 +87,7 @@ class PolicySmartBlock {

let reason;

// block if it's been more than 5 seconds since page load started
// Block all trackers that load after 5 seconds from when page load started
if (this._requestWasSlow(tabId, appId, requestTimestamp)) {
reason = 'slow';

@@ -98,14 +99,14 @@ class PolicySmartBlock {
reason = 'allowedType'; // allow if tracker is in breaking type
} else if (this._pageWasReloaded(tabId, appId)) {
reason = 'pageReloaded'; // allow if page has been reloaded recently
} else if (c2pDb.allowedOnce(tabId, appId)) {
reason = 'c2pAllowOnce'; // allow if the user has selected "allow once" in Click2Play
}
}

// We are only blocking slow trackers that do not cause page breakage
const result = (reason === 'slow');
if (result) {
// We don't want record in tabInfo reasons other than 'slow'
// Smart blocking should not claim that it unblocks trackers which were unblocked
// for other reasons before shouldBlock was called for them.
log('Smart Blocking blocked appId', appId, 'for reason:', reason);
tabInfo.setTabSmartBlockAppInfo(tabId, appId, 'slow', true);
}
@@ -258,7 +259,7 @@ class PolicySmartBlock {
}

/**
* Check if request loaded after a threshhold time since page load.
* Check if request loaded after a threshold time since page load.
* @param {string} tabId tab id
* @param {string} appId tracker id
* @param {number} requestTimestamp timestamp of the request
@@ -22,20 +22,28 @@ import Policy from '../classes/Policy';
import tabInfo from '../classes/TabInfo';
import { log } from './common';
import { sendMessage, processUrl, injectScript } from './utils';
import c2p_tpl from '../../app/templates/click2play';
import c2p_tpl from '../../app/templates/click2play.html';
import c2p_images from '../../app/data-images/click2play';

const policy = new Policy();

/**
* Builds Click2Play templates for a given tab_id.
*
* Restricted Sites: Only show the "allow once" option, since blacklisting overrides
* site-specific tracker allow settings.
*
* Smart Blocking: If the app was blocked by Smart Blocking, we only show the "allow always" option because
* the tracker was most likely a lazy-loaded widget (comment, video) and C2P will reset the allowOnceList
* before the widget has a chance to load.
*
* @memberOf BackgroundUtils
*
* @param {Object} details request parameters
* @param {number} app_id tracker id
*/
export function buildC2P(details, app_id) {
const { tab_id } = details;
const { tab_id, smartBlocked } = details;
let c2pApp = c2pDb.db.apps && c2pDb.db.apps[app_id];

if (!c2pApp) {
@@ -59,6 +67,7 @@ export function buildC2P(details, app_id) {
c2pApp.forEach((c2pAppDef) => {
const tplData = {
blacklisted, // if the site is blacklisted, don't show allow_always button
smartBlocked, // if the app has been Smart Blocked, don't show allow_once (see comment block)
button: !!c2pAppDef.button,
ghostery_blocked_src: c2p_images.ghosty_blocked,
allow_always_src: c2p_images.allow_unblock,
@@ -81,7 +90,7 @@ export function buildC2P(details, app_id) {
}
}

c2pHtml.push(c2p_tpl(tplData));
c2pHtml.push(c2p_tpl({ data: tplData }));
});

if (app_id === 2575) { // Hubspot forms. Adjust selector.
@@ -181,7 +190,7 @@ function _getHubspotFormSelector(url) {
// Hubspot url has a fixed format
// https://forms.hubspot.com/embed/v3/form/532040/95b5de3a-6d4a-4729-bebf-07c41268d773?callback=hs_reqwest_0&hutk=941df50e9277ee76755310cd78647a08
// The following three parameters are privacy-safe:
// 532040 - parner id
// 532040 - partner id
// 95b5de3a-6d4a-4729-bebf-07c41268d773 - form id on the page
// hs_reqwest_0 - function which will be called on the client after the request
//
@@ -11,7 +11,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

// depenencies
// dependencies
const glob = require('glob');
const path = require('path');
const webpack = require('webpack');
@@ -158,7 +158,7 @@ const config = {
{
test: /\.(html)$/,
use: {
loader: 'html-loader'
loader: 'underscore-template-loader'
}
},{
test: /\.(jsx|js)?/,
ProTip! Use n and p to navigate between commits in a pull request.