Skip to content

Commit 59eda23

Browse files
committed
XML-compatible soft reload.
1 parent 2e0ee1a commit 59eda23

File tree

1 file changed

+91
-35
lines changed

1 file changed

+91
-35
lines changed

src/content/syncFetchPolicy.js

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,22 @@
1313
{id: "fetchPolicy", url, contextUrl: url},
1414
callback);
1515
};
16+
debug("Initial readyState and body", document.readyState, document.body);
1617

17-
if (UA.isMozilla && document.readyState !== "complete") {
18+
if (UA.isMozilla) {
1819
// Mozilla has already parsed the <head> element, we must take extra steps...
1920

2021
let softReloading = true;
2122
debug("Early parsing: preemptively suppressing events and script execution.");
2223

23-
{
24+
try {
25+
26+
if (document.body && document.body.onload) {
27+
// special treatment for body[onload], which could not be suppressed otherwise
28+
document.body._onload = document.body.getAttribute("onload");
29+
document.body.removeAttribute("onload");
30+
document.body.onload = null;
31+
}
2432

2533
// List updated by build.sh from https://hg.mozilla.org/mozilla-central/raw-file/tip/xpcom/ds/StaticAtoms.py
2634
// whenever html5_events/html5_events.pl retrieves something new.
@@ -44,55 +52,103 @@
4452
for (let et of eventTypes) document.addEventListener(et, eventSuppressor, true);
4553

4654
ns.on("capabilities", () => {
55+
if (document.body && document.body._onload) {
56+
document.body.setAttribute("onload", document.body._onload);
57+
}
58+
4759
let {readyState} = document;
4860
debug("Readystate: %s, canScript: ", readyState, ns.canScript);
49-
if (ns.canScript) {
50-
let softReload = e => {
51-
try {
52-
debug("Soft reload", e);
53-
removeEventListener("DOMContentLoaded", softReload, true);
54-
let document = window.wrappedJSObject.document;
55-
let html = document.documentElement.outerHTML;
56-
document.open();
57-
softReloading = false;
58-
document.write(html);
59-
document.close();
60-
} catch(e) {
61-
error(e);
61+
if (!ns.canScript) {
62+
for (let node of document.querySelectorAll("*")) {
63+
let evAttrs = [...node.attributes].filter(a => a.name.toLowerCase().startsWith("on"));
64+
for (let a of evAttrs) {
65+
debug("Reparsing event attribute", a, node);
66+
node.removeAttributeNode(a);
67+
node.setAttributeNodeNS(a);
6268
}
6369
}
64-
if (readyState === "loading") {
65-
debug("Deferring softReload to DOMContentLoaded...");
66-
addEventListener("DOMContentLoaded", softReload, true);
67-
} else {
68-
softReload();
69-
}
70-
} else {
71-
try {
72-
for (let node of document.querySelectorAll("*")) {
73-
let evAttrs = [...node.attributes].filter(a => a.name.toLowerCase().startsWith("on"));
74-
for (let a of evAttrs) {
75-
debug("Reparsing event attribute after CSP", a, node);
76-
node.removeAttributeNode(a);
77-
node.setAttributeNodeNS(a);
70+
softReloading = false;
71+
return;
72+
}
73+
74+
let softReload = ev => {
75+
let html = document.documentElement.outerHTML;
76+
try {
77+
debug("Soft reload", ev, html);
78+
softReloading = false;
79+
try {
80+
let doc = window.wrappedJSObject.document;
81+
removeEventListener("DOMContentLoaded", softReload, true);
82+
doc.open();
83+
doc.write(html);
84+
doc.close();
85+
debug("Written", html)
86+
} catch (e) {
87+
debug("Can't use document.write(), XML document?");
88+
try {
89+
Promise.all([...document.querySelectorAll("script")].map(s => {
90+
let clone = document.createElement("script");
91+
for (let a of s.attributes) {
92+
clone.setAttribute(a.name, a.value);
93+
}
94+
clone.textContent = s.textContent;
95+
let doneEvents = ["afterscriptexecute", "load", "error"];
96+
return new Promise(resolve => {
97+
let listener = ev => {
98+
if (ev.target !== clone) return;
99+
debug("Resolving on ", ev.type, ev.target);
100+
resolve(ev.target);
101+
for (let et of doneEvents) removeEventListener(et, listener, true);
102+
};
103+
for (let et of doneEvents) {
104+
addEventListener(et, listener, true);
105+
}
106+
s.replaceWith(clone);
107+
debug("Replaced", clone);
108+
});
109+
})).then(r => {
110+
debug("All scripts done", r);
111+
document.dispatchEvent(new Event("readystatechange"));
112+
document.dispatchEvent(new Event("DOMContentLoaded", {
113+
bubbles: true,
114+
cancelable: true
115+
}));
116+
if (document.readyState === "complete") {
117+
window.dispatchEvent(new Event("load"));
118+
}
119+
});
120+
} catch (e) {
121+
error(e);
78122
}
79123
}
80-
softReloading = false;
81-
} catch (e) {
124+
} catch(e) {
82125
error(e);
83126
}
127+
};
128+
129+
if (readyState === "loading") {
130+
debug("Deferring softReload to DOMContentLoaded...");
131+
addEventListener("DOMContentLoaded", softReload, true);
132+
} else {
133+
softReload();
84134
}
135+
85136
});
137+
} catch (e) {
138+
error(e);
86139
}
87140

88-
addEventListener("beforescriptexecute", e => {
141+
let scriptSuppressor = e => {
89142
if (!e.isTrusted) return;
90-
debug(e.type, e.target);
143+
debug(e.type, e.target, softReloading); // DEV_ONLY
91144
if (softReloading) {
92145
e.preventDefault();
93-
debug("Blocked early script", s);
146+
debug("Blocked early script", e.target);
147+
} else {
148+
removeEventListener(e.type, scriptSuppressor);
94149
}
95-
}, true);
150+
};
151+
addEventListener("beforescriptexecute", scriptSuppressor, true);
96152
}
97153

98154
let setup = policy => {

0 commit comments

Comments
 (0)