2024-07-24 13:57:40 +00:00

680 lines
17 KiB
JavaScript

import { blockUrls, commonJSHandlers, commons, rules } from "./rules.js";
// Vars
let initialized = false;
let cachedRules = {};
let tabList = {};
const xmlTabs = {};
let lastDeclarativeNetRuleId = 1;
let settings = { statusIndicators: true, whitelistedDomains: {} };
const isManifestV3 = chrome.runtime.getManifest().manifest_version == 3;
// Badges
function setBadge(tabId, text) {
const chromeAction = chrome?.browserAction ?? chrome?.action;
if (!chromeAction || !settings.statusIndicators) return;
chromeAction.setBadgeText({ text: text || "", tabId: tabId });
if (chromeAction.setBadgeBackgroundColor)
chromeAction.setBadgeBackgroundColor({
color: "#646464",
tabId: tabId,
});
}
function setSuccessBadge(tabId) {
setBadge(tabId, "✅");
}
function setDisabledBadge(tabId) {
setBadge(tabId, "⛔");
}
// Common functions
function getHostname(url, cleanup) {
try {
if (url.indexOf("http") != 0) {
throw true;
}
const a = new URL(url);
return typeof cleanup == "undefined"
? a.hostname
: a.hostname.replace(/^w{2,3}\d*\./i, "");
} catch (error) {
return false;
}
}
// Whitelisting
function updateSettings() {
return new Promise((resolve) => {
lastDeclarativeNetRuleId = 1;
chrome.storage.local.get(
{ settings: { whitelistedDomains: {}, statusIndicators: true } },
async ({ settings: storedSettings }) => {
settings = storedSettings;
if (isManifestV3) {
updateWhitelistRules();
}
resolve();
}
);
});
}
async function updateWhitelistRules() {
if (!isManifestV3) {
console.warn("Called unsupported function");
return;
}
const previousRules = (
chrome.declarativeNetRequest.getDynamicRules()
).map((v) => {
return v.id;
});
const addRules = Object.entries(settings.whitelistedDomains)
.filter((element) => element[1])
.map((v) => {
return {
id: lastDeclarativeNetRuleId++,
priority: 1,
action: { type: "allow" },
condition: {
urlFilter: "*",
resourceTypes: ["script", "stylesheet", "xmlhttprequest", "image"],
initiatorDomains: [v[0]],
},
};
});
chrome.declarativeNetRequest.updateDynamicRules({
addRules,
removeRuleIds: previousRules,
});
}
function isWhitelisted(tab) {
if (typeof settings.whitelistedDomains[tab.hostname] != "undefined") {
return true;
}
for (const i in tab.host_levels) {
if (typeof settings.whitelistedDomains[tab.host_levels[i]] != "undefined") {
return true;
}
}
return false;
}
function getWhitelistedDomain(tab) {
if (typeof settings.whitelistedDomains[tab.hostname] != "undefined") {
return tab.hostname;
}
for (const i in tab.host_levels) {
if (typeof settings.whitelistedDomains[tab.host_levels[i]] != "undefined") {
return tab.host_levels[i];
}
}
return false;
}
async function toggleWhitelist(tab) {
if (tab.url.indexOf("http") != 0 || !tabList[tab.id]) {
return;
}
if (tabList[tab.id].whitelisted) {
// const hostname = getWhitelistedDomain(tabList[tab.id]);
delete settings.whitelistedDomains[tabList[tab.id].hostname];
} else {
settings.whitelistedDomains[tabList[tab.id].hostname] = true;
}
chrome.storage.local.set({ settings }, function () {
for (const i in tabList) {
if (tabList[i].hostname == tabList[tab.id].hostname) {
tabList[i].whitelisted = !tabList[tab.id].whitelisted;
}
}
});
if (isManifestV3) {
updateWhitelistRules();
}
}
// Maintain tab list
function getPreparedTab(tab) {
tab.hostname = false;
tab.whitelisted = false;
tab.host_levels = [];
if (tab.url) {
tab.hostname = getHostname(tab.url, true);
if (tab.hostname) {
const parts = tab.hostname.split(".");
for (let i = parts.length; i >= 2; i--) {
tab.host_levels.push(parts.slice(-1 * i).join("."));
}
tab.whitelisted = isWhitelisted(tab);
}
}
return tab;
}
function onCreatedListener(tab) {
tabList[tab.id] = getPreparedTab(tab);
}
function onUpdatedListener(tabId, changeInfo, tab) {
if (changeInfo.status) {
tabList[tab.id] = getPreparedTab(tab);
}
}
function onRemovedListener(tabId) {
if (tabList[tabId]) {
delete tabList[tabId];
}
}
async function recreateTabList(magic) {
tabList = {};
let results;
if (isManifestV3) {
results = chrome.tabs.query({});
} else {
results = new Promise((resolve, reject) => {
chrome.tabs.query({}, (result) => {
if (chrome.runtime.lastError) reject(chrome.runtime.lastError);
resolve(result);
});
});
}
results.forEach(onCreatedListener);
if (magic) {
for (const i in tabList) {
if (tabList.hasOwnProperty(i)) {
doTheMagic(tabList[i].id);
}
}
}
}
chrome.tabs.onCreated.addListener(onCreatedListener);
chrome.tabs.onUpdated.addListener(onUpdatedListener);
chrome.tabs.onRemoved.addListener(onRemovedListener);
// chrome.runtime.onStartup.addListener(async () => initialize(true));
chrome.runtime.onInstalled.addListener(
async () => initialize(false, true)
);
// URL blocking
function blockUrlCallback(d) {
// Cached request: find the appropriate tab
// TODO: parse rules.json for this function.
if (d.tabId == -1 && d.initiator) {
// const hostname = getHostname(d.initiator, true);
for (const tabId in tabList) {
if (tabList[tabId].hostname == getHostname(d.initiator, true)) {
d.tabId = parseInt(tabId);
break;
}
}
}
if (tabList[d.tabId]?.whitelisted ?? false) {
setDisabledBadge(d.tabId);
return { cancel: false };
}
if (tabList[d.tabId] && d.url) {
const cleanURL = d.url.split("?")[0];
// To shorten the checklist, many filters are grouped by keywords
for (const group in blockUrls.common_groups) {
if (d.url.indexOf(group) > -1) {
const groupFilters = blockUrls.common_groups[group];
for (const i in groupFilters) {
if (
(groupFilters[i].q && d.url.indexOf(groupFilters[i].r) > -1) ||
(!groupFilters[i].q && cleanURL.indexOf(groupFilters[i].r) > -1)
) {
// Check for exceptions
if (groupFilters[i].e && tabList[d.tabId].host_levels.length > 0) {
for (const level in tabList[d.tabId].host_levels) {
for (const exception in groupFilters[i].e) {
if (
groupFilters[i].e[exception] ==
tabList[d.tabId].host_levels[level]
) {
return { cancel: false };
}
}
}
}
setSuccessBadge(d.tabId);
return { cancel: true };
}
}
}
}
// Check ungrouped filters
const groupFilters = blockUrls.common;
for (const i in groupFilters) {
if (
(groupFilters[i].q && d.url.indexOf(groupFilters[i].r) > -1) ||
(!groupFilters[i].q && cleanURL.indexOf(groupFilters[i].r) > -1)
) {
// Check for exceptions
if (groupFilters[i].e && tabList[d.tabId].host_levels.length > 0) {
for (const level in tabList[d.tabId].host_levels) {
for (const exception in groupFilters[i].e) {
if (
groupFilters[i].e[exception] ==
tabList[d.tabId].host_levels[level]
) {
return { cancel: false };
}
}
}
}
setSuccessBadge(d.tabId);
return { cancel: true };
}
}
// Site specific filters
if (d.tabId > -1 && tabList[d.tabId].host_levels.length > 0) {
for (const level in tabList[d.tabId].host_levels) {
if (blockUrls.specific[tabList[d.tabId].host_levels[level]]) {
const rules = blockUrls.specific[tabList[d.tabId].host_levels[level]];
for (const i in rules) {
if (d.url.indexOf(rules[i]) > -1) {
setSuccessBadge(d.tabId);
return { cancel: true };
}
}
}
}
}
}
return { cancel: false };
}
if (!isManifestV3) {
chrome.webRequest.onBeforeRequest.addListener(
blockUrlCallback,
{
urls: ["http://*/*", "https://*/*"],
types: ["script", "stylesheet", "xmlhttprequest"],
},
["blocking"]
);
chrome.webRequest.onHeadersReceived.addListener(
function (d) {
if (tabList[d.tabId]) {
d.responseHeaders.forEach(function (h) {
if (h.name == "Content-Type" || h.name == "content-type") {
xmlTabs[d.tabId] = h.value.indexOf("/xml") > -1;
}
});
}
return { cancel: false };
},
{ urls: ["http://*/*", "https://*/*"], types: ["main_frame"] },
["blocking", "responseHeaders"]
);
}
// Reporting
function reportWebsite(info, tab, anon, issueType, notes, callback) {
if (tab.url.indexOf("http") != 0 || !tabList[tab.id]) {
return;
}
const hostname = getHostname(tab.url);
if (hostname.length == 0) {
return;
}
if (tabList[tab.id].whitelisted) {
return chrome.notifications.create("report", {
type: "basic",
title: chrome.i18n.getMessage("reportSkippedTitle", hostname),
message: chrome.i18n.getMessage("reportSkippedMessage"),
iconUrl: "icons/48.png",
});
}
if (!anon) {
chrome.tabs.create({
url: `https://github.com/OhMyGuus/I-Dont-Care-About-Cookies/issues/new?assignees=OhMyGuus&labels=Website+request&template=website_request.yml&title=%5BREQ%5D%3A+${encodeURIComponent(
hostname
)}&url=${encodeURIComponent(hostname)}&version=${encodeURIComponent(
chrome.runtime.getManifest().version
)}&browser=${encodeURIComponent(getBrowserAndVersion())}`,
});
} else {
fetch("https://api.istilldontcareaboutcookies.com/api/report", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
issueType,
notes,
url: tab.url,
browser: getBrowserAndVersion(),
extensionVersion: chrome.runtime.getManifest().version,
}),
})
.then((response) => response.json())
.then((response) => {
if (
response &&
!response.error &&
!response.errors &&
response.responseURL
) {
chrome.tabs.create({
url: response.responseURL,
});
callback({ error: false });
} else {
callback({ error: true });
}
})
.catch(() => {
callback({ error: true });
});
}
}
function getBrowserAndVersion() {
const useragent = navigator.userAgent;
if (useragent.includes("Firefox")) {
return useragent.match(/Firefox\/([0-9]+[\S]+)/)[0].replace("/", " ");
} else if (useragent.includes("Chrome")) {
if (navigator.userAgentData.brands.length > 2) {
const { brand, version } = navigator.userAgentData.brands[1];
return brand + " " + version;
}
}
return "Other";
}
// Adding custom CSS/JS
function activateDomain(hostname, tabId, frameId) {
if (!cachedRules[hostname]) {
cachedRules[hostname] = rules[hostname] || {};
}
if (!cachedRules[hostname]) {
return false;
}
const cachedRule = cachedRules[hostname];
let status = false;
// cached_rule.s = Custom css for webpage
// cached_rule.c = Common css for webpage
// cached_rule.j = Common js for webpage
if (typeof cachedRule.s != "undefined") {
insertCSS({ tabId, frameId: frameId || 0, css: cachedRule.s });
status = true;
}
if (typeof cachedRule.c != "undefined") {
insertCSS({ tabId, frameId: frameId || 0, css: commons[cachedRule.c] });
status = true;
}
if (typeof cachedRule.j != "undefined") {
executeScript({
tabId,
frameId,
file: `/data/js/${commonJSHandlers[cachedRule.j]}.js`,
});
status = true;
}
if (status) {
setSuccessBadge(tabId);
}
return status;
}
function doTheMagic(tabId, frameId, anotherTry) {
if (!tabList[tabId] || tabList[tabId].url.indexOf("http") != 0) {
return;
}
if (tabList[tabId].whitelisted) {
setDisabledBadge(tabId);
return;
}
// Common CSS rules
insertCSS(
{ tabId, frameId: frameId || 0, file: "/data/css/common.css" },
function () {
// A failure? Retry.
if (chrome.runtime.lastError) {
console.log(chrome.runtime.lastError);
const currentTry = anotherTry || 1;
if (currentTry == 10) {
return;
}
if (currentTry > 5) {
setTimeout(() => doTheMagic(tabId, frameId || 0, currentTry + 1));
} else {
doTheMagic(tabId, frameId || 0, currentTry + 1);
}
return;
}
// Common social embeds
executeScript({ tabId, frameId, file: "/data/js/embedsHandler.js" });
if (activateDomain(tabList[tabId].hostname, tabId, frameId || 0)) {
return;
}
for (const level in tabList[tabId].host_levels) {
if (
activateDomain(tabList[tabId].host_levels[level], tabId, frameId || 0)
) {
return true;
}
}
// Common JS rules when custom rules don't exist
executeScript({
tabId,
frameId,
file: "/data/js/0_defaultClickHandler.js",
});
}
);
}
chrome.webNavigation.onCommitted.addListener(async (tab) => {
if (tab.frameId > 0) {
return;
}
if (!initialized) {
initialize();
}
tabList[tab.tabId] = getPreparedTab(tab);
doTheMagic(tab.tabId);
});
chrome.webNavigation.onCompleted.addListener(async function (tab) {
if (!initialized) {
initialize();
}
if (tab.frameId > 0 && tab.url != "about:blank") {
doTheMagic(tab.tabId, tab.frameId);
}
});
// Toolbar menu
chrome.runtime.onMessage.addListener((request, info, sendResponse) => {
initialize().then(() => {
let responseSend = false;
if (typeof request == "object") {
if (request.tabId && tabList[request.tabId]) {
if (request.command == "get_active_tab") {
const response = { tab: tabList[request.tabId] };
if (response.tab.whitelisted) {
response.tab.hostname = getWhitelistedDomain(
tabList[request.tabId]
);
}
sendResponse(response);
responseSend = true;
} else if (request.command == "toggle_extension") {
toggleWhitelist(tabList[request.tabId]);
} else if (request.command == "report_website") {
reportWebsite(
info,
tabList[request.tabId],
request.anon,
request.issueType,
request.notes,
sendResponse
);
responseSend = true;
} else if (request.command == "refresh_page") {
executeScript({
tabId: request.tabId,
func: () => {
window.location.reload();
},
});
}
} else {
if (request.command == "open_options_page") {
chrome.tabs.create({
url: chrome.runtime.getURL("/data/options.html"),
});
}
}
} else if (request == "update_settings") {
updateSettings();
}
if (!responseSend) {
sendResponse();
}
});
return true;
});
function insertCSS(injection, callback) {
const { tabId, css, file, frameId } = injection;
if (isManifestV3) {
chrome.scripting.insertCSS(
{
target: { tabId: tabId, frameIds: [frameId || 0] },
css: css,
files: file ? [file] : undefined,
origin: "USER",
},
callback
);
} else {
chrome.tabs.insertCSS(
tabId,
{
file,
code: css,
frameId: frameId || 0,
runAt: xmlTabs[tabId] ? "document_idle" : "document_start",
cssOrigin: "user",
},
callback
);
}
}
function executeScript(injection, callback) {
const { tabId, func, file, frameId } = injection;
if (isManifestV3) {
// manifest v3
chrome.scripting.executeScript(
{
target: { tabId, frameIds: [frameId || 0] },
files: file ? [file] : undefined,
func,
},
callback
);
} else {
// manifest v2
chrome.tabs.executeScript(
tabId,
{
file,
frameId: frameId || 0,
code: func == undefined ? undefined : "(" + func.toString() + ")();",
runAt: xmlTabs[tabId] ? "document_idle" : "document_end",
},
callback
);
}
}
async function loadCachedRules() {
// TODO: Load cached rules for V3 to improve speed (Requires testing to see if this actually is faster for v3)
cachedRules = {};
}
async function initialize(checkInitialized, magic) {
if (checkInitialized && initialized) {
return;
}
loadCachedRules();
updateSettings();
recreateTabList(magic);
initialized = true;
}
initialize();