1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
/** \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,
});
}
})();
});
|