fix(js): isolate bootstrap declarations
This commit is contained in:
1 parent
23a8a3111b
commit
6a55b63fa6
2 files changed
+65
-8
No files matched your search
Vendored
+9
@@ -1,4 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
(function () {
|
||||||
|
|
||||||
// Pre-declare all internal globals as non-enumerable so they are invisible
|
// Pre-declare all internal globals as non-enumerable so they are invisible
|
||||||
// to Object.keys(window) / for-in enumeration. Must run before any var
|
// to Object.keys(window) / for-in enumeration. Must run before any var
|
||||||
@@ -175,6 +176,12 @@ let _fpSeed = 0;
|
|||||||
// when SPAs dynamically insert multiple <script module> tags at once.
|
// when SPAs dynamically insert multiple <script module> tags at once.
|
||||||
let __dynScriptQueue = [];
|
let __dynScriptQueue = [];
|
||||||
let __dynScriptBusy = false;
|
let __dynScriptBusy = false;
|
||||||
|
Object.defineProperty(globalThis, '__obscura_hasPendingDynamicScripts', {
|
||||||
|
value: function() { return __dynScriptBusy || __dynScriptQueue.length > 0; },
|
||||||
|
writable: false,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: false,
|
||||||
|
});
|
||||||
function _decodeDataScriptUrl(url) {
|
function _decodeDataScriptUrl(url) {
|
||||||
const comma = url.indexOf(',');
|
const comma = url.indexOf(',');
|
||||||
if (!url.startsWith('data:') || comma < 5) {
|
if (!url.startsWith('data:') || comma < 5) {
|
||||||
@@ -8959,3 +8966,5 @@ if (typeof Response !== 'undefined' && Response.prototype && !Response.prototype
|
|||||||
if (typeof val === 'function') { walk(val); }
|
if (typeof val === 'function') { walk(val); }
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -860,15 +860,14 @@ impl ObscuraJsRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the serialized dynamic-script queue is still fetching or
|
/// Whether the serialized dynamic-script queue is still fetching or
|
||||||
/// evaluating a script. The queue variables are global lexicals rather
|
/// evaluating a script. The queue stays private to the bootstrap closure;
|
||||||
/// than window properties, so page code cannot overwrite this state.
|
/// Rust reads it through a hidden status function so page declarations
|
||||||
|
/// cannot collide with or overwrite the queue itself.
|
||||||
pub fn has_pending_dynamic_scripts(&mut self) -> bool {
|
pub fn has_pending_dynamic_scripts(&mut self) -> bool {
|
||||||
self.evaluate(
|
self.evaluate("globalThis.__obscura_hasPendingDynamicScripts?.() === true")
|
||||||
"typeof __dynScriptBusy !== 'undefined' && (__dynScriptBusy || __dynScriptQueue.length > 0)",
|
.ok()
|
||||||
)
|
.and_then(|value| value.as_bool())
|
||||||
.ok()
|
.unwrap_or(false)
|
||||||
.and_then(|value| value.as_bool())
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arm a hard wall-clock backstop on synchronous V8 work. A page stuck in a
|
/// Arm a hard wall-clock backstop on synchronous V8 work. A page stuck in a
|
||||||
@@ -2500,6 +2499,55 @@ mod tests {
|
|||||||
assert_eq!(result, serde_json::json!(["A", "B"]));
|
assert_eq!(result, serde_json::json!(["A", "B"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn page_var_declarations_do_not_collide_with_dom_interfaces() {
|
||||||
|
let mut rt = setup_runtime("<html><body></body></html>");
|
||||||
|
|
||||||
|
rt.execute_script(
|
||||||
|
"legacy-node-guard",
|
||||||
|
"if (!window.Node) { var Node = {}; } globalThis.__legacyNodeRan = true;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
rt.evaluate("globalThis.__legacyNodeRan").unwrap(),
|
||||||
|
serde_json::json!(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
rt.execute_script(
|
||||||
|
"page-element",
|
||||||
|
"var Element = function PageElement() {}; globalThis.__createdTag = document.createElement('div').tagName;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
rt.evaluate("globalThis.__createdTag").unwrap(),
|
||||||
|
serde_json::json!("DIV")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dynamic_script_status_bridge_is_hidden_and_idle() {
|
||||||
|
let mut rt = setup_runtime("<html><body></body></html>");
|
||||||
|
assert!(!rt.has_pending_dynamic_scripts());
|
||||||
|
assert_eq!(
|
||||||
|
rt.evaluate("typeof __dynScriptBusy").unwrap(),
|
||||||
|
serde_json::json!("undefined")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
rt.evaluate(
|
||||||
|
"Object.getOwnPropertyNames(globalThis).includes('__obscura_hasPendingDynamicScripts')"
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
serde_json::json!(false)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
rt.evaluate(
|
||||||
|
"Reflect.ownKeys(globalThis).includes('__obscura_hasPendingDynamicScripts')"
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
serde_json::json!(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Regression test for #147: a TypeError in one script must not poison
|
/// Regression test for #147: a TypeError in one script must not poison
|
||||||
/// the runtime so that subsequent scripts (or DOM queries) collapse to
|
/// the runtime so that subsequent scripts (or DOM queries) collapse to
|
||||||
/// empty. The reporter saw `--dump text` return 1 byte after offside.js
|
/// empty. The reporter saw `--dump text` return 1 byte after offside.js
|
||||||
|
|||||||
Reference in new issue
Block a user