trisquel-icecat/icecat/extensions/gnu/jsr@javascriptrestrictor/level_cache.js

161 lines
5.7 KiB
JavaScript

/** \file
* \brief Handle domain-specific levels
*
* \author Copyright (C) 2020 Libor Polcak
* \author Copyright (C) 2021 Giorgio Maone
* \author Copyright (C) 2022 Marek Salon
* \author Copyright (C) 2023 Martin Zmitko
*
* \license SPDX-License-Identifier: GPL-3.0-or-later
*/
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
/**
* Returns the a Promise which resolves to the configuration
* for the current level to be used by the content script for injection
* @param {url} string
* @param isPrivate bool specifying incognito mode
*/
function getContentConfiguration(url, frameId, tabId) {
return new Promise(resolve => {
async function resolve_promise() {
await updateUserScripts();
let level = getCurrentLevelJSON(url);
if (level.is_default && frameId !== 0) {
/**
* \bug iframes nested within an iframe with user-specific level do not get this level
*
* Suppose that there is an iframe from domain C nested in an iframe from
* domain B that is iself nested in a visited domain A.
*
* +------------------------------------------------------------+
* | visited domain a.example |
* | |
* | +--------------------------------------------------------+ |
* | | iframe from domain b.example | |
* | | | |
* | | +----------------------------------------------------+ | |
* | | | iframe from domain c.example | | |
* | | | | | |
* | | +----------------------------------------------------+ | |
* | | | |
* | +--------------------------------------------------------+ |
* +------------------------------------------------------------+
*
* Suppose that B has a user-defined specific level settings, and C does
* not have a user-defined specific level settings. The iframe of domain B
* gets the user-defined settings for domain B but the iframe from domain C
* is set with the level of domain A.
*/
level = getCurrentLevelJSON((await TabCache.async(tabId)).url);
}
let {domainHash, incognitoHash} = await Hashes.getFor(url);
resolve({
currentLevel: level,
fpdWrappers: isFpdOn(tabId) ? fp_levels.page_wrappers[fpdSettings.detection] : [],
fpdTrackCallers: fpd_track_callers_tab === tabId,
domainHash,
incognitoHash,
portId: wrappersPortId,
});
}
if (levels_initialised && fp_levels_initialised) {
resolve_promise();
}
else {
levels_updated_callbacks.push(resolve_promise);
}
});
}
/**
* Messaging with content script.
*
* @message The message from content script.
*
* Returns the promise with the message returned to the content script.
*/
function contentScriptLevelSetter(message, {frameId, tab}) {
if (!tab) {
// privileged source, bail out
return;
}
switch (message.message) {
case "get wrapping for URL":
return getContentConfiguration(message.url, frameId, tab.id)
}
}
browser.runtime.onSyncMessage?.addListener(contentScriptLevelSetter);
/**
* Register a dynamic content script to be ran for early configuration and
* injection of the wrapper, hopefully before of the asynchronous
* message listener above
* \see Depends on /nscl/service/DocStartInjection.js, /nscl/service/TabCache.js
*/
DocStartInjection.register(async ({url, frameId, tabId}) => {
let configuration = await getContentConfiguration(url, frameId, tabId);
if (browser.tabs.executeScript) {
// mv2
return `
window.configuration = ${JSON.stringify(configuration)};
if (typeof configureInjection === "function") configureInjection(configuration);
`;
}
return {
callback: "configureInjection",
assign: "configuration",
data: configuration,
};
});
/**
* We need to process both previous and the next URL. See
* https://pagure.io/JShelter/webextension/issue/46 and
* https://pagure.io/JShelter/webextension/issue/58 for more details.
* So far only window.name wrapper needs such information so we did not created any general
* mechanism.
* Beware that this mechanism is triggered even when the user clicks the back button while content
* scripts do not run again.
*/
NavCache.onUrlChanged.addListener(({tabId, frameId, previousUrl, url}) => {
if (getSiteForURL(previousUrl) === getSiteForURL(url)) return;
if (previousUrl === undefined) return; // First page in this window, see https://pagure.io/JShelter/webextension/issue/116#comment-875070
(async () => {
let configuration = await getContentConfiguration(url, frameId, tabId);
if (configuration.currentLevel.windowname) {
if (!browser.tabs.executeScript && browser.scripting) {
browser.scripting.executeScript({
func: () => { window.name = ""; },
injectImmediately: true,
target: {
frameIds: [frameId],
tabId
}
});
return;
}
browser.tabs.executeScript(tabId, {
code: `window.name = "";`,
frameId,
runAt: "document_start",
matchAboutBlank: true,
});
}
})();
});