diff --git a/package-lock.json b/package-lock.json index 63564ff6..79cdd174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "contain-facebook", - "version": "2.0.3", + "version": "2.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -6828,9 +6828,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, "pump": { "version": "3.0.0", diff --git a/package.json b/package.json index b837acfa..4a5c8d56 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "fs-extra": "^8.1.0", "mocha": "^5.0.5", "nyc": "^14.1.1", + "psl": "^1.7.0", "sinon": "^7.3.2", "sinon-chai": "^3.0.0", "webextensions-geckodriver": "^0.6.1", diff --git a/src/background.js b/src/background.js index 3903db53..739b0867 100644 --- a/src/background.js +++ b/src/background.js @@ -1,3 +1,5 @@ +/* global psl */ + const FACEBOOK_CONTAINER_DETAILS = { name: "Facebook", color: "toolbar", @@ -266,6 +268,27 @@ async function maybeReopenTab (url, tab, request) { return {cancel: true}; } +const rootDomainCache = {}; + +function getRootDomain(url) { + if (url in rootDomainCache) { + // After storing 128 entries, it will delete the oldest each time. + const returnValue = rootDomainCache[url]; + if (Object.keys(rootDomainCache).length > 128) { + delete rootDomainCache[(Object.keys(rootDomainCache)[0])]; + } + return returnValue; + } + + const urlObject = new URL(url); + if (urlObject.hostname === "") { return false; } + const parsedUrl = psl.parse(urlObject.hostname); + + rootDomainCache[url] = parsedUrl.domain; + return parsedUrl.domain; + +} + function isFacebookURL (url) { const parsedUrl = new URL(url); for (let facebookHostRE of facebookHostREs) { @@ -276,23 +299,13 @@ function isFacebookURL (url) { return false; } -// TODO: Consider users if accounts.spotify.com already in FBC -async function supportSiteSubdomainCheck (url) { - if (url === "accounts.spotify.com") { - await addDomainToFacebookContainer("https://www.spotify.com"); - await addDomainToFacebookContainer("https://open.spotify.com"); - } - return; -} - // TODO: refactor parsedUrl "up" so new URL doesn't have to be called so much // TODO: refactor fbcStorage "up" so browser.storage.local.get doesn't have to be called so much async function addDomainToFacebookContainer (url) { - const parsedUrl = new URL(url); const fbcStorage = await browser.storage.local.get(); - fbcStorage.domainsAddedToFacebookContainer.push(parsedUrl.host); + const rootDomain = getRootDomain(url); + fbcStorage.domainsAddedToFacebookContainer.push(rootDomain); await browser.storage.local.set({"domainsAddedToFacebookContainer": fbcStorage.domainsAddedToFacebookContainer}); - await supportSiteSubdomainCheck(parsedUrl.host); } async function removeDomainFromFacebookContainer (domain) { @@ -302,11 +315,10 @@ async function removeDomainFromFacebookContainer (domain) { await browser.storage.local.set({"domainsAddedToFacebookContainer": fbcStorage.domainsAddedToFacebookContainer}); } -// TODO: Add PSL Subdomain Check against current list async function isAddedToFacebookContainer (url) { - const parsedUrl = new URL(url); const fbcStorage = await browser.storage.local.get(); - if (fbcStorage.domainsAddedToFacebookContainer.includes(parsedUrl.host)) { + const rootDomain = getRootDomain(url); + if (fbcStorage.domainsAddedToFacebookContainer.includes(rootDomain)) { return true; } return false; @@ -462,6 +474,13 @@ async function containFacebook (request) { delete tabsWaitingToLoad[request.tabId]; } + // Listen to requests and open Facebook into its Container, + // open other sites into the default tab context + if (request.tabId === -1) { + // Request doesn't belong to a tab + return; + } + const tab = await browser.tabs.get(request.tabId); updateBrowserActionIcon(tab); @@ -470,12 +489,6 @@ async function containFacebook (request) { if (urlSearchParm.has("fbclid")) { return {redirectUrl: stripFbclid(request.url)}; } - // Listen to requests and open Facebook into its Container, - // open other sites into the default tab context - if (request.tabId === -1) { - // Request doesn't belong to a tab - return; - } return maybeReopenTab(request.url, tab, request); } @@ -570,15 +583,24 @@ function setupWindowsAndTabsListeners() { setupWebRequestListeners(); setupWindowsAndTabsListeners(); - browser.runtime.onMessage.addListener( (message, {url}) => { - if (message === "what-sites-are-added") { + async function messageHandler(request, sender) { + switch (request.message) { + case "what-sites-are-added": return browser.storage.local.get().then(fbcStorage => fbcStorage.domainsAddedToFacebookContainer); - } else if (message.removeDomain) { - removeDomainFromFacebookContainer(message.removeDomain).then( results => results ); - } else { - addDomainToFacebookContainer(url).then( results => results); + case "remove-domain-from-list": + removeDomainFromFacebookContainer(request.removeDomain).then( results => results ); + break; + case "add-domain-to-list": + addDomainToFacebookContainer(sender.url).then( results => results); + break; + case "get-root-domain": + return getRootDomain(request.url); + default: + throw new Error("Unexpected message!"); } - }); + } + + browser.runtime.onMessage.addListener(messageHandler); maybeReopenAlreadyOpenTabs(); diff --git a/src/content_script.js b/src/content_script.js index d18e3a91..2dd94cde 100755 --- a/src/content_script.js +++ b/src/content_script.js @@ -210,7 +210,9 @@ function addFacebookBadge (target, badgeClassUId, socialAction) { htmlBadgeFragmentPromptButtonAllow.addEventListener("click", (e) => { e.preventDefault(); allowClickSwitch = true; - browser.runtime.sendMessage("add-to-facebook-container"); + browser.runtime.sendMessage({ + message: "add-domain-to-list" + }); target.click(); }); @@ -607,10 +609,24 @@ function contentScriptInit(resetSwitch, msg) { } } +async function getRootDomainFromBackground(url) { + // Send request to background to parse URL via PSL + const backgroundResp = await browser.runtime.sendMessage({ + message: "get-root-domain", + url + }); + + return backgroundResp; +} + async function CheckIfURLShouldBeBlocked() { - const siteList = await browser.runtime.sendMessage("what-sites-are-added"); + const siteList = await browser.runtime.sendMessage({ + message: "what-sites-are-added" + }); + + const site = await getRootDomainFromBackground(window.location.href); - if (siteList.includes(window.location.host)) { + if (siteList.includes(site)) { checkForTrackers = false; } else { contentScriptInit(false); diff --git a/src/manifest.json b/src/manifest.json index e82bb4c3..129dbde5 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -55,7 +55,10 @@ ], "background": { - "scripts": ["background.js"] + "scripts": [ + "psl.min.js", + "background.js" + ] }, "content_scripts": [ diff --git a/src/panel.css b/src/panel.css index c2a7ddb6..dcf872fe 100644 --- a/src/panel.css +++ b/src/panel.css @@ -181,7 +181,7 @@ a:hover { } .highlight-on-hover.remove-site-from-container.disabled-button::after, -.highlight-on-hover.remove-site-from-container.disabled-button span +.highlight-on-hover.remove-site-from-container.disabled-button span, .highlight-on-hover.add-site-to-container.disabled-button::after, .highlight-on-hover.add-site-to-container.disabled-button span { opacity: 0.4; diff --git a/src/panel.js b/src/panel.js index 9355ffbc..ff57a182 100644 --- a/src/panel.js +++ b/src/panel.js @@ -117,25 +117,33 @@ const addSpan = (wrapper, stringId) => { setClassAndAppend(wrapper, span); }; -const getActiveHostname = async() => { +const getActiveRootDomainFromBackground = async() => { + // Get active page URL const tabsQueryResult = await browser.tabs.query({currentWindow: true, active: true}); const currentActiveTab = tabsQueryResult[0]; - const currentActiveURL = new URL(currentActiveTab.url); - const thisHostname = currentActiveURL.hostname; - return thisHostname; + + // Send request to background to parse URL via PSL + const backgroundResp = await browser.runtime.sendMessage({ + message: "get-root-domain", + url: currentActiveTab.url + }); + + return backgroundResp; }; const isSiteInContainer = async(panelId) => { - if (panelId === "on-facebook") { // Site is on default FBC domain. Show the "remove site" button, in a disabled state. return true; } - const addedSitesList = await browser.runtime.sendMessage("what-sites-are-added"); - const activeTabHostname = await getActiveHostname(); + const addedSitesList = await browser.runtime.sendMessage({ + message: "what-sites-are-added" + }); + + const activeRootDomain = await getActiveRootDomainFromBackground(); - if (addedSitesList.includes(activeTabHostname)) { + if (addedSitesList.includes(activeRootDomain)) { return true; } }; @@ -219,7 +227,11 @@ const addDeleteSiteListeners = () => { document.querySelectorAll(".site-added").forEach(btn => { btn.addEventListener("click", async (e) => { // TODO: refactor to remove the domain straight from browser.storage.local? - await browser.runtime.sendMessage({removeDomain: e.dataset.domain}); + + await browser.runtime.sendMessage({ + message: "remove-domain-from-list", + removeDomain: e.dataset.domain + }); }); }); }; @@ -520,7 +532,9 @@ const buildAllowedSitesPanel = async(panelId) => { addLightSubhead(listsWrapper, "sites-included"); makeSiteList(listsWrapper, defaultAllowedSites); - const siteList = await browser.runtime.sendMessage("what-sites-are-added"); + const siteList = await browser.runtime.sendMessage({ + message: "what-sites-are-added" + }); const sitesAllowedSubhead = addLightSubhead(listsWrapper, "sites-allowed"); sitesAllowedSubhead.classList.add("sites-allowed"); makeSiteList(listsWrapper, siteList, true, true); // (...sitesAllowed=true, addX=true) @@ -542,14 +556,20 @@ const buildAllowedSitesPanel = async(panelId) => { }; const removeSiteFromContainer = async () => { - const activeHostname = await getActiveHostname(); - buildRemoveSitePanel(activeHostname); + const activeRootDomain = await getActiveRootDomainFromBackground(); + + await browser.runtime.sendMessage({ + message: "remove-domain-from-list", + removeDomain: activeRootDomain + }); + browser.tabs.reload(); + window.close(); }; -const addSiteToContainer = async (activeHostname) => { - if (!activeHostname) { throw new Error("Missing site name. Cannot add site."); } +const addSiteToContainer = async () => { + const activeRootDomain = await getActiveRootDomainFromBackground(); const fbcStorage = await browser.storage.local.get(); - fbcStorage.domainsAddedToFacebookContainer.push(activeHostname); + fbcStorage.domainsAddedToFacebookContainer.push(activeRootDomain); await browser.storage.local.set({"domainsAddedToFacebookContainer": fbcStorage.domainsAddedToFacebookContainer}); browser.tabs.reload(); window.close(); @@ -557,7 +577,7 @@ const addSiteToContainer = async (activeHostname) => { const buildAddSitePanel = async (siteName) => { if (!siteName) { - siteName = await getActiveHostname(); + siteName = await getActiveRootDomainFromBackground(); } const panelId = "add-site"; @@ -604,7 +624,10 @@ const buildRemoveSitePanel = (siteName) => { blueRemoveButton.classList.add("uiMessage", "remove-btn"); blueRemoveButton.id = "remove"; blueRemoveButton.addEventListener("click", async() => { - await browser.runtime.sendMessage( {removeDomain: siteName} ); + await browser.runtime.sendMessage({ + message: "remove-domain-from-list", + removeDomain: siteName + }); browser.tabs.reload(); window.close(); }); diff --git a/src/psl.min.js b/src/psl.min.js new file mode 100644 index 00000000..3abdce6a --- /dev/null +++ b/src/psl.min.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).psl=a();}}(function(){return function s(m,t,u){function r(o,a){if(!t[o]){if(!m[o]){var i="function"==typeof require&&require;if(!a&&i)return i(o,!0);if(p)return p(o,!0);var e=new Error("Cannot find module '"+o+"'");throw e.code="MODULE_NOT_FOUND",e;}var n=t[o]={exports:{}};m[o][0].call(n.exports,function(a){return r(m[o][1][a]||a);},n,n.exports,s,m,t,u);}return t[o].exports;}for(var p="function"==typeof require&&require,a=0;a= 0x80 (not a basic code point)","invalid-input":"Invalid input"},c=b-y,x=Math.floor,q=String.fromCharCode;function A(a){throw new RangeError(k[a]);}function l(a,o){for(var i=a.length,e=[];i--;)e[i]=o(a[i]);return e;}function g(a,o){var i=a.split("@"),e="";return 1>>10&1023|55296),a=56320|1023&a),o+=q(a);}).join("");}function L(a,o){return a+22+75*(a<26)-((0!=o)<<5);}function I(a,o,i){var e=0;for(a=i?x(a/t):a>>1,a+=x(a/o);c*f>>1x((d-g)/m))&&A("overflow"),g+=u*m,!(u<(r=t<=j?y:j+f<=t?f:t-j));t+=b)m>x(d/(p=b-r))&&A("overflow"),m*=p;j=I(g-s,o=c.length+1,0==s),x(g/o)>d-h&&A("overflow"),h+=x(g/o),g%=o,c.splice(g++,0,h);}return _(c);}function j(a){var o,i,e,n,s,m,t,u,r,p,k,c,l,g,h,j=[];for(c=(a=O(a)).length,o=w,s=v,m=i=0;mx((d-i)/(l=e+1))&&A("overflow"),i+=(t-o)*l,o=t,m=0;md&&A("overflow"),k==o){for(u=i,r=b;!(u<(p=r<=s?y:s+f<=r?f:r-s));r+=b)h=u-p,g=b-p,j.push(q(L(p+h%g,0))),u=x(h/g);j.push(q(L(u,0))),s=I(i,l,e==n),i=0,++e;}++i,++o;}return j.join("");}if(n={version:"1.4.1",ucs2:{decode:O,encode:_},decode:h,encode:j,toASCII:function(a){return g(a,function(a){return r.test(a)?"xn--"+j(a):a;});},toUnicode:function(a){return g(a,function(a){return u.test(a)?h(a.slice(4).toLowerCase()):a;});}},0,o&&i)if(T.exports==o)i.exports=n;else for(s in n)n.hasOwnProperty(s)&&(o[s]=n[s]);else a.punycode=n;}(this);}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{});},{}]},{},[2])(2);}); diff --git a/test/features/add-domain-to-fbc.test.js b/test/features/add-domain-to-fbc.test.js index b7317b33..e8d59de1 100644 --- a/test/features/add-domain-to-fbc.test.js +++ b/test/features/add-domain-to-fbc.test.js @@ -6,16 +6,18 @@ describe("Add domain to Facebook Container", () => { background = webExtension.background; }); - describe("runtime message add-to-facebook-container", () => { + describe("runtime message add-domain-to-list", () => { beforeEach(async () => { - await background.browser.runtime.onMessage.addListener.yield("add-to-facebook-container", { + await background.browser.runtime.onMessage.addListener.yield({ + message: "add-domain-to-list" + }, { url: "https://example.com" }); }); describe("runtime message what-sites-are-added", () => { it("should return the added sites", async () => { - const [promise] = await background.browser.runtime.onMessage.addListener.yield("what-sites-are-added", {}); + const [promise] = await background.browser.runtime.onMessage.addListener.yield({message: "what-sites-are-added"}); const sites = await promise; expect(sites.includes("example.com")).to.be.true; }); @@ -25,14 +27,15 @@ describe("Add domain to Facebook Container", () => { describe("runtime message removeDomain", () => { it("should have removed the domain", async () => { await background.browser.runtime.onMessage.addListener.yield({ + message: "remove-domain-from-list", removeDomain: "example.com" - }, {}); + }); - const [promise] = await background.browser.runtime.onMessage.addListener.yield("what-sites-are-added", {}); + const [promise] = await background.browser.runtime.onMessage.addListener.yield({message: "what-sites-are-added"}); const sites = await promise; expect(sites.includes("example.com")).to.be.false; }); }); }); -}); \ No newline at end of file +});