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
|
{
'use strict';
let listenersMap = new Map();
let backlog = new Set();
let ns = {
debug: true, // DEV_ONLY
get embeddingDocument() {
delete this.embeddingDocument;
return this.embeddingDocument = CSP.isEmbedType(document.contentType);
},
on(eventName, listener) {
let listeners = listenersMap.get(eventName);
if (!listeners) listenersMap.set(eventName, listeners = new Set());
listeners.add(listener);
if (backlog.has(eventName)) this.fire(eventName, listener);
},
detach(eventName, listener) {
let listeners = listenersMap.get(eventName);
if (listeners) listeners.delete(listener);
},
fire(eventName, listener = null) {
if (listener) {
listener({type:eventName, source: this});
return;
}
let listeners = listenersMap.get(eventName);
if (listeners) {
for (let l of listeners) {
this.fire(eventName, l);
}
}
backlog.add(eventName);
},
fetchPolicy() {
let url = document.URL;
if (!UA.isMozilla && url.startsWith("http")) {
(async () => {
this.setup(await Messages.send("fetchPolicy", {url, contextUrl: url}));
})();
return;
}
debug(`Fetching policy from document %s, readyState %s, content %s`,
url, document.readyState, document.documentElement.outerHTML);
let originalState = document.readyState;
let blockedScripts = [];
addEventListener("beforescriptexecute", e => {
// safety net for syncrhonous load on Firefox
if (!this.canScript) {
e.preventDefault();
let script = e.target;
blockedScripts.push(script)
log("Some script managed to be inserted in the DOM while fetching policy, blocking it.\n", script);
}
}, true);
let policy = null;
let setup = policy => {
debug("Fetched %o, readyState %s", policy, document.readyState); // DEV_ONLY
this.setup(policy);
if (this.canScript && blockedScripts.length && originalState === "loading") {
log("Blocked some scripts on %s even though they are actually permitted by policy.", url)
// something went wrong, e.g. with session restore.
for (let s of blockedScripts) {
// reinsert the script
s.replace(s.cloneNode(true));
}
}
}
for (;;) {
try {
policy = browser.runtime.sendSyncMessage(
{id: "fetchPolicy", url, contextUrl: url}, setup);
break;
} catch (e) {
if (!Messages.isMissingEndpoint(e)) {
error(e);
break;
}
error("Background page not ready yet, retrying to fetch policy...")
}
}
},
setup(policy) {
debug("%s, %s, %o", document.URL, document.readyState, policy);
if (!policy) {
policy = {permissions: {capabilities: []}, localFallback: true};
}
this.policy = policy;
if (!policy.permissions || policy.unrestricted) {
this.allows = () => true;
this.capabilities = Object.assign(
new Set(["script"]), { has() { return true; } });
} else {
let perms = policy.permissions;
this.capabilities = new Set(perms.capabilities);
new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
}
this.canScript = this.allows("script");
this.fire("capabilities");
},
policy: null,
allows(cap) {
return this.capabilities && this.capabilities.has(cap);
},
getWindowName() {
return window.name;
}
};
if (this.ns) {
this.ns.merge(ns);
} else {
this.ns = ns;
}
}
|