/ src / js / background.js
background.js
  1  'use strict';
  2  
  3  /* global importScripts, chrome */
  4  
  5  importScripts('common.js', 'sites.js', 'dnr_manager.js');
  6  
  7  // chrome.browserAction changed to chrome.action in MV3
  8  const actionApi = chrome.action || chrome.browserAction;
  9  
 10  const restrictions = {
 11    'adweek.com': /^((?!\.adweek\.com\/(.+\/)?(amp|agencyspy|tvnewser|tvspy)\/).)*$/,
 12    'barrons.com': /.+\.barrons\.com\/(amp\/)?article(s)?\/.+/,
 13    'economist.com': /.+economist\.com\/.+\/\d{1,4}\/\d{1,2}\/\d{2}\/.+/,
 14    'seekingalpha.com': /.+seekingalpha\.com\/article\/.+/,
 15    'techinasia.com': /\.techinasia\.com\/.+/,
 16    'ft.com': /.+\.ft.com\/content\//
 17  };
 18  
 19  // Don't remove cookies before page load
 20  const allowCookies = [
 21    'brisbanetimes.com.au',
 22    'canberratimes.com.au',
 23    'cen.acs.org',
 24    'demorgen.be',
 25    'denverpost.com',
 26    'examiner.com.au',
 27    'gelocal.it',
 28    'grubstreet.com',
 29    'harpers.org',
 30    'hbr.org',
 31    'humo.be',
 32    'lesechos.fr',
 33    'lrb.co.uk',
 34    'medium.com',
 35    'mercurynews.com',
 36    'newstatesman.com',
 37    'nrc.nl',
 38    'nymag.com',
 39    'ocregister.com',
 40    'parool.nl',
 41    'qz.com',
 42    'scientificamerican.com',
 43    'seattletimes.com',
 44    'seekingalpha.com',
 45    'sofrep.com',
 46    'spectator.co.uk',
 47    'speld.nl',
 48    'techinasia.com',
 49    'telegraaf.nl',
 50    'the-american-interest.com',
 51    'theadvocate.com.au',
 52    'theage.com.au',
 53    'theatlantic.com',
 54    'theaustralian.com.au',
 55    'thecut.com',
 56    'thediplomat.com',
 57    'themercury.com.au',
 58    'towardsdatascience.com',
 59    'trouw.nl',
 60    'vn.nl',
 61    'volkskrant.nl',
 62    'vulture.com',
 63    'nzz.ch',
 64    'thehindu.com',
 65    'financialpost.com',
 66    'haaretz.co.il',
 67    'haaretz.com',
 68    'themarker.com',
 69    'sueddeutsche.de',
 70    'gelocal.it',
 71    'elmundo.es',
 72    'time.com',
 73    'zeit.de',
 74    'expansion.com',
 75    'dailytelegraph.com.au',
 76    'washingtonpost.com'
 77  ];
 78  
 79  // Removes cookies after page load
 80  const removeCookies = [
 81    'bloomberg.com',
 82    'bloombergquint.com',
 83    'brisbanetimes.com.au',
 84    'canberratimes.com.au',
 85    'cen.acs.org',
 86    'demorgen.be',
 87    'denverpost.com',
 88    'examiner.com.au',
 89    'globes.co.il',
 90    'grubstreet.com',
 91    'harpers.org',
 92    'hbr.org',
 93    'humo.be',
 94    'lesechos.fr',
 95    'mercurynews.com',
 96    'newstatesman.com',
 97    'nrc.nl',
 98    'nymag.com',
 99    'ocregister.com',
100    'qz.com',
101    'scientificamerican.com',
102    'seattletimes.com',
103    'sofrep.com',
104    'spectator.co.uk',
105    'speld.nl',
106    'telegraaf.nl',
107    'theadvocate.com.au',
108    'theage.com.au',
109    'theatlantic.com',
110    'thecut.com',
111    'thediplomat.com',
112    'towardsdatascience.com',
113    'vn.nl',
114    'vulture.com',
115    'wsj.com',
116    'medium.com',
117    'washingtonpost.com',
118    'japantimes.co.jp'
119  ];
120  
121  // Contains remove cookie sites above plus any custom sites
122  let _removeCookies = removeCookies;
123  
124  // select specific cookie(s) to hold from removeCookies domains
125  const removeCookiesSelectHold = {
126    'qz.com': ['gdpr'],
127    'wsj.com': ['wsjregion'],
128    'seattletimes.com': ['st_newsletter_splash_seen']
129  };
130  
131  // select only specific cookie(s) to drop from removeCookies domains
132  const removeCookiesSelectDrop = {
133    'ambito.com': ['TDNotesRead'],
134    'demorgen.be': ['TID_ID'],
135    'fd.nl': ['socialread'],
136    'humo.be': ['TID_ID'],
137    'nrc.nl': ['counter'],
138    'speld.nl': ['speld-paywall']
139  };
140  
141  // Override User-Agent with Googlebot
142  const useGoogleBotSites = [
143    'adelaidenow.com.au',
144    'barrons.com',
145    'couriermail.com.au',
146    'fd.nl',
147    'genomeweb.com',
148    'heraldsun.com.au',
149    'lavoixdunord.fr',
150    'ntnews.com.au',
151    'quora.com',
152    'seekingalpha.com',
153    'telegraph.co.uk',
154    'theaustralian.com.au',
155    'themercury.com.au',
156    'thenational.scot',
157    'wsj.com',
158    'kansascity.com',
159    'republic.ru',
160    'nzz.ch',
161    'df.cl',
162    'ft.com',
163    'wired.com',
164    'zeit.de'
165  ];
166  
167  // Override User-Agent with Bingbot
168  const useBingBot = [];
169  
170  const useGoogleAdsBotMobile = [
171    'haaretz.co.il',
172    'haaretz.com',
173    'themarker.com'
174  ];
175  
176  // Contains google bot sites above plus any custom sites
177  let _useGoogleBotSites = useGoogleBotSites;
178  
179  function setDefaultOptions () {
180    extensionApi.storage.sync.set({
181      sites: defaultSites
182    }, function () {
183      extensionApi.runtime.openOptionsPage();
184    });
185  }
186  
187  // Block external scripts
188  const blockedRegexes = {
189    'adweek.com': /.+\.lightboxcdn\.com\/.+/,
190    'afr.com': /afr\.com\/assets\/vendorsReactRedux_client.+\.js/,
191    'businessinsider.com': /(.+\.tinypass\.com\/.+|cdn\.onesignal\.com\/sdks\/.+\.js)/,
192    'chicagotribune.com': /.+:\/\/.+\.tribdss\.com\//,
193    'economist.com': /(.+\.tinypass\.com\/.+|economist\.com\/engassets\/_next\/static\/chunks\/framework.+\.js)/,
194    'editorialedomani.it': /(js\.pelcro\.com\/.+|editorialedomani.it\/pelcro\.js)/,
195    'foreignpolicy.com': /(cdn\.cxense\.com\/|\.tinypass\.com\/)/,
196    'fortune.com': /.+\.tinypass\.com\/.+/,
197    'haaretz.co.il': /haaretz\.co\.il\/htz\/js\/inter\.js/,
198    'haaretz.com': /haaretz\.com\/hdc\/web\/js\/minified\/header-scripts-int.js.+/,
199    'inquirer.com': /.+\.tinypass\.com\/.+/,
200    'lastampa.it': /.+\.repstatic\.it\/minify\/sites\/lastampa\/.+\/config\.cache\.php\?name=social_js/,
201    'lrb.co.uk': /.+\.tinypass\.com\/.+/,
202    'medscape.com': /.+\.medscapestatic\.com\/.*medscape-library\.js/,
203    'interest.co.nz': /(.+\.presspatron\.com.+|.+interest\.co\.nz.+pp-ablock-banner\.js)/,
204    'repubblica.it': /scripts\.repubblica\.it\/pw\/pw\.js.+/,
205    'spectator.co.uk': /.+\.tinypass\.com\/.+/,
206    'spectator.com.au': /.+\.tinypass\.com\/.+/,
207    'telegraph.co.uk': /.+telegraph\.co\.uk.+martech.+/,
208    'thecourier.com.au': /.+cdn-au\.piano\.io\/api\/tinypass.+\.js/,
209    'thenation.com': /thenation\.com\/.+\/paywall-script\.php/,
210    'thenational.scot': /(.+\.tinypass\.com\/.+|.+thenational\.scot.+omniture\.js|.+thenational\.scot.+responsive-sync.+)/,
211    'thewrap.com': /thewrap\.com\/.+\/wallkit\.js/,
212    'wsj.com': /cdn\.ampproject\.org\/v\d\/amp-access-.+\.js/,
213    'historyextra.com': /.+\.evolok\.net\/.+\/authorize\/.+/,
214    'barrons.com': /cdn\.ampproject\.org\/v\d\/amp-access-.+\.js/,
215    'irishtimes.com': /cdn\.ampproject\.org\/v\d\/amp-access-.+\.js/,
216    'elmercurio.com': /(merreader\.emol\.cl\/assets\/js\/merPramV2.js|staticmer\.emol\.cl\/js\/inversiones\/PramModal.+\.js)/,
217    'sloanreview.mit.edu': /(.+\.tinypass\.com\/.+|.+\.netdna-ssl\.com\/wp-content\/themes\/smr\/assets\/js\/libs\/welcome-ad\.js)/,
218    'latercera.com': /.+\.cxense\.com\/+/,
219    'lesechos.fr': /.+\.tinypass\.com\/.+/,
220    'thehindu.com': /ajax\.cloudflare\.com\/cdn-cgi\/scripts\/.+\/cloudflare-static\/rocket-loader\.min\.js/,
221    'technologyreview.com': /.+\.blueconic\.net\/.+/,
222    'spectator.us': /(cdn\.cxense\.com\/.+|\.tinypass\.com\/.+)/,
223    'gelocal.it': /(\.repstatic\.it\/minify\/sites\/gelocal\/.+\/config\.cache(_\d)?\.php|cdn\.ampproject\.org\/v\d\/amp-(access|ad)-.+\.js)/,
224    'elmundo.es': /cdn\.ampproject\.org\/v\d\/amp-(access|ad|consent)-.+\.js/,
225    'time.com': /\/time\.com\/dist\/meter-wall-client-js\..+\.js/,
226    'thestar.com': /\.com\/api\/overlaydata/,
227    'elpais.com': /(\.epimg\.net\/js\/.+\/(noticia|user)\.min\.js|\/elpais\.com\/arc\/subs\/p\.min\.js|cdn\.ampproject\.org\/v\d\/amp-(access|(sticky-)?ad|consent)-.+\.js)/,
228    'expansion.com': /cdn\.ampproject\.org\/v\d\/amp-(access|ad|consent)-.+\.js/,
229    'chicagobusiness.com': /(\.tinypass\.com\/|\.chicagobusiness\.com\/.+\/js\/js_.+\.js)/,
230    'dailytelegraph.com.au': /cdn\.ampproject\.org\/v\d\/amp-(access|ad|consent)-.+\.js/,
231    'theglobeandmail.com': /(\.theglobeandmail\.com\/pf\/dist\/engine\/react\.js|smartwall\.theglobeandmail\.com\/)/,
232    'nytimes.com': /(meter-svc\.nytimes\.com\/meter\.js|mwcm\.nyt\.com\/.+\.js|cooking\.nytimes\.com\/api\/.+\/access)/,
233    'latimes.com': /(metering\.platform\.latimes\.com\/|cdn\.ampproject\.org\/v\d\/amp-(access|subscriptions)-.+\.js)/,
234    'theathletic.com': /cdn\.ampproject\.org\/v\d\/amp-(access|subscriptions)-.+\.js/,
235    'japantimes.co.jp': /cdn\.cxense\.com\//,
236    'scmp.com': /(\.tinypass\.com\/|cdn\.ampproject\.org\/v\d\/amp-access-.+\.js)/,
237    'ilmessaggero.it': /(utils\.cedsdigital\.it\/js\/PaywallMeter\.js)/,
238    'washingtonpost.com': /\.washingtonpost\.com\/tetro\/metering\/evaluate/
239  };
240  
241  const userAgentDesktop = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
242  const userAgentMobile = 'Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible ; Googlebot/2.1 ; +http://www.google.com/bot.html)';
243  const userAgentDesktopBingBot = 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)';
244  const userAgentMobileBingBot = 'Chrome/80.0.3987.92 Mobile Safari/537.36 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)';
245  const userAgentGoogleAdsBotMobile = 'Mozilla/5.0 (Linux; Android 5.0; SM-G920A) AppleWebKit (KHTML, like Gecko) Chrome Mobile Safari (compatible; AdsBot-Google-Mobile; +http://www.google.com/mobile/adsbot.html)';
246  
247  let enabledSites = [];
248  
249  // Get the enabled sites
250  extensionApi.storage.sync.get({
251    sites: {},
252    customSites: []
253  }, function (items) {
254    enabledSites = Object.values(items.sites).concat(items.customSites);
255  
256    // Remove cookies for custom sites
257    _removeCookies = removeCookies.concat(items.customSites);
258  
259    // Update DNR rules
260    dnrManager.updateRules(enabledSites, items.customSites, _useGoogleBotSites, useBingBot, useGoogleAdsBotMobile, blockedRegexes, allowCookies);
261  
262  
263    // GA removed in MV3
264  
265  });
266  
267  // Listen for changes to options
268  extensionApi.storage.onChanged.addListener(function (changes, namespace) {
269    if (changes.sites && changes.sites.newValue) {
270      const sites = changes.sites.newValue;
271      enabledSites = Object.values(sites);
272      
273      // Refresh DNR rules when settings change
274      extensionApi.storage.sync.get(['customSites'], function(items) {
275          dnrManager.updateRules(enabledSites, items.customSites || [], _useGoogleBotSites, useBingBot, useGoogleAdsBotMobile, blockedRegexes, allowCookies);
276      });
277    }
278  });
279  
280  // Set and show default options on install
281  extensionApi.runtime.onInstalled.addListener(function (details) {
282    if (details.reason === 'install') {
283      setDefaultOptions();
284    } else if (details.reason === 'update') {
285      // User updated extension
286    }
287  });
288  
289  extensionApi.tabs.onUpdated.addListener(function (tabId, info, tab) {
290    updateBadge(tab);
291    if (info.status === 'loading' && tab.url && isSiteEnabled({ url: tab.url })) {
292      chrome.scripting.executeScript({
293        target: { tabId: tabId },
294        files: ['src/js/contentScript.js'],
295        injectImmediately: true
296      }).catch(err => {
297          // Silently fail if tab is closed or restricted
298      });
299    }
300  });
301  extensionApi.tabs.onActivated.addListener(function (activeInfo) {
302    extensionApi.tabs.get(activeInfo.tabId, updateBadge);
303  });
304  
305  function updateBadge (activeTab) {
306    if (extensionApi.runtime.lastError || !activeTab) { return; }
307    const badgeText = getBadgeText(activeTab.url);
308    actionApi.setBadgeBackgroundColor({ color: 'blue' });
309    actionApi.setBadgeText({ text: badgeText });
310  }
311  
312  function getBadgeText (currentUrl) {
313    return currentUrl && isSiteEnabled({ url: currentUrl }) ? 'ON' : '';
314  }
315  
316  // Redirects are now handled via DNR in dnr_manager.js
317  
318  
319  // Script blocking is now handled via DNR in dnr_manager.js
320  
321  
322  
323  
324  // remove cookies after page load
325  extensionApi.webRequest.onCompleted.addListener(function (details) {
326    let domainToRemove;
327    for (const domain of _removeCookies) {
328      if (enabledSites.includes(domain) && matchUrlDomain(domain, details.url)) {
329        domainToRemove = domain;
330        break;
331      }
332    }
333    if (domainToRemove) {
334      extensionApi.cookies.getAll({ domain: domainToRemove }, function (cookies) {
335        for (const ck of cookies) {
336          const cookie = {
337            url: (ck.secure ? 'https://' : 'http://') + ck.domain + ck.path,
338            name: ck.name,
339            storeId: ck.storeId
340          };
341          // .firstPartyDomain = undefined on Chrome (doesn't support it)
342          if (ck.firstPartyDomain !== undefined) {
343            cookie.firstPartyDomain = ck.firstPartyDomain;
344          }
345          const cookieDomain = ck.domain;
346          const rcDomain = cookieDomain.replace(/^(\.?www\.|\.)/, '');
347          // hold specific cookie(s) from removeCookies domains
348          if ((rcDomain in removeCookiesSelectHold) && removeCookiesSelectHold[rcDomain].includes(ck.name)) {
349            continue; // don't remove specific cookie
350          }
351          // drop only specific cookie(s) from removeCookies domains
352          if ((rcDomain in removeCookiesSelectDrop) && !(removeCookiesSelectDrop[rcDomain].includes(ck.name))) {
353            continue; // only remove specific cookie
354          }
355          extensionApi.cookies.remove(cookie);
356        }
357      });
358    }
359  }, {
360    urls: ['<all_urls>']
361  });
362  
363  // Google Analytics removed in MV3
364  
365  function isSiteEnabled (details) {
366    const enabledSite = matchUrlDomain(enabledSites, details.url);
367    if (enabledSite in restrictions) {
368      return restrictions[enabledSite].test(details.url);
369    }
370    return !!enabledSite;
371  }
372  
373  function matchUrlDomain (domains, url) {
374    return matchDomain(domains, urlHost(url));
375  }
376  
377  function matchDomain (domains, hostname) {
378    let matchedDomain = false;
379    if (!hostname) { hostname = window.location.hostname; }
380    if (typeof domains === 'string') { domains = [domains]; }
381    domains.some(domain => (hostname === domain || hostname.endsWith('.' + domain)) && (matchedDomain = domain));
382    return matchedDomain;
383  }
384  
385  function urlHost (url) {
386    if (url && url.startsWith('http')) {
387      try {
388        return new URL(url).hostname;
389      } catch (e) {
390        console.log(`url not valid: ${url} error: ${e}`);
391      }
392    }
393    return url;
394  }