mirror of
https://github.com/lexmount/moli.git
synced 2026-09-23 16:01:25 +00:00
fix(indexeddb): use intrinsic templates for factory creation
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::context_bootstrap::exposed_interfaces::build_intrinsic_interface_instance;
|
||||
use crate::util::{get_private_object, get_private_value, set_private_value};
|
||||
use crate::web_api_interfaces;
|
||||
use moli_webapi_declare::WebApiObject;
|
||||
@@ -14,7 +15,7 @@ const INDEXED_DB_READWRITE_TRANSACTION_QUEUE_FIELD: &str =
|
||||
"moli.IndexedDb.runtime.readwriteTransactionQueue";
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::IDBFactory, require_prototype)]
|
||||
#[webapi(interface = web_api_interfaces::IDBFactory)]
|
||||
struct IndexedDbFactoryRuntimeDeclaration {
|
||||
#[webapi(slot = INDEXED_DB_EVENT_LISTENERS_SLOT, init = "null_object")]
|
||||
event_listeners: (),
|
||||
@@ -306,15 +307,12 @@ pub(in crate::context_bootstrap::indexed_db) fn indexed_db_factory_storage_scope
|
||||
fn build_indexed_db_factory_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
// The factory is script-visible and must receive IDBFactory.prototype;
|
||||
// only the private owner state around it is null-prototype.
|
||||
let factory = IndexedDbFactoryRuntimeDeclaration {
|
||||
event_listeners: (),
|
||||
}
|
||||
.bind(scope)
|
||||
.ok()?;
|
||||
let factory_proto = global_constructor_prototype(scope, "IDBFactory")?;
|
||||
let _ = factory.set_prototype(scope, factory_proto.into());
|
||||
// Materialize the realm's intrinsic before constructing the factory, without
|
||||
// reading an author-replaced public IDBFactory binding on first use.
|
||||
let factory = build_intrinsic_interface_instance(scope, "IDBFactory").ok()?;
|
||||
IndexedDbFactoryRuntimeDeclaration::default()
|
||||
.initialize(scope, factory)
|
||||
.ok()?;
|
||||
let listeners = new_null_prototype_object(scope);
|
||||
set_indexed_db_slot_value(
|
||||
scope,
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
use super::*;
|
||||
use moli_url::origin_ascii_serialization;
|
||||
|
||||
#[test]
|
||||
fn indexed_db_first_use_ignores_public_constructor_overrides() {
|
||||
let fixture = include_str!("../../../tests/fixtures/indexeddb-first-use.js");
|
||||
for mode in ["number", "function", "getter", "delete"] {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://indexeddb-first-use.test/");
|
||||
assert_eq!(
|
||||
vm.lazy_constructor_materialization_count_for_test("IDBFactory")
|
||||
.expect("initial IDBFactory materialization count"),
|
||||
0,
|
||||
"{mode}: IDBFactory must remain lazy before the probe"
|
||||
);
|
||||
assert_eq!(
|
||||
vm.eval(&format!("({fixture})({mode:?})"))
|
||||
.unwrap_or_else(|error| panic!("{mode}: first IndexedDB access failed: {error}")),
|
||||
"ok"
|
||||
);
|
||||
assert_eq!(
|
||||
vm.lazy_constructor_materialization_count_for_test("IDBFactory")
|
||||
.expect("final IDBFactory materialization count"),
|
||||
1,
|
||||
"{mode}: first IndexedDB access must materialize the intrinsic exactly once"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexed_db_runtime_state_is_created_on_first_use_without_window_slots() {
|
||||
let mut vm = new_storage_page_task_executor_test_vm("https://indexeddb-lazy-runtime.test/");
|
||||
|
||||
@@ -1,4 +1,44 @@
|
||||
use super::*;
|
||||
use crate::worker::WorkerGlobalKind;
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_indexed_db_first_use_ignores_public_constructor_overrides() {
|
||||
ensure_v8();
|
||||
let script_url = url::Url::parse("https://indexeddb-first-use.test/worker.js").unwrap();
|
||||
let fixture = include_str!("../../../../tests/fixtures/indexeddb-first-use.js");
|
||||
for kind in [
|
||||
WorkerGlobalKind::Dedicated {
|
||||
name: String::new(),
|
||||
},
|
||||
WorkerGlobalKind::Shared {
|
||||
name: String::new(),
|
||||
storage_key: moli_storage_key::MoliStorageKey::first_party_from_url(&script_url, None),
|
||||
},
|
||||
WorkerGlobalKind::Service {
|
||||
registration_id: ServiceWorkerRegistrationId::from_u64_for_test(1),
|
||||
version_id: ServiceWorkerVersionId::from_u64_for_test(1),
|
||||
scope_url: url::Url::parse("https://indexeddb-first-use.test/").unwrap(),
|
||||
},
|
||||
] {
|
||||
for mode in ["number", "function", "getter", "delete"] {
|
||||
let (bootstrap_tx, mut bootstrap_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let handle = spawn_test_worker_with_options(
|
||||
WorkerSpawnOptions::new(format!("({fixture})({mode:?})"), script_url.to_string())
|
||||
.with_global_kind(kind.clone())
|
||||
.with_bootstrap_completion_sender(bootstrap_tx),
|
||||
);
|
||||
let bootstrap = timeout(TIMEOUT, bootstrap_rx.recv()).await;
|
||||
handle.terminate_and_join();
|
||||
bootstrap
|
||||
.expect("timed out waiting for IndexedDB first-use checks")
|
||||
.expect("worker bootstrap channel closed")
|
||||
.result
|
||||
.unwrap_or_else(|error| {
|
||||
panic!("{kind:?}, {mode}: first IndexedDB access failed: {error:?}")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn lazy_diagnostics(
|
||||
handle: &WorkerHandle,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
(mode => {
|
||||
const check = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
let constructorReads = 0;
|
||||
const replacement = mode === 'function' ? function Fake() {} : 123;
|
||||
const poison = () => {
|
||||
constructorReads++;
|
||||
throw new Error('public IDBFactory getter must not run');
|
||||
};
|
||||
// Do not read IDBFactory, its descriptor, or indexedDB before the override:
|
||||
// doing so could materialize the intrinsic and hide a first-use regression.
|
||||
if (mode === 'getter') {
|
||||
Object.defineProperty(globalThis, 'IDBFactory', {
|
||||
get: poison,
|
||||
configurable: false
|
||||
});
|
||||
} else if (mode === 'delete') {
|
||||
check(delete globalThis.IDBFactory, 'constructor deletion');
|
||||
} else {
|
||||
globalThis.IDBFactory = replacement;
|
||||
}
|
||||
|
||||
const factory = globalThis.indexedDB;
|
||||
check(typeof factory === 'object' && factory !== null, 'factory exists');
|
||||
const prototype = Object.getPrototypeOf(factory);
|
||||
const intrinsic = prototype.constructor;
|
||||
check(intrinsic !== replacement && intrinsic.name === 'IDBFactory', 'intrinsic constructor');
|
||||
check(prototype === intrinsic.prototype, 'intrinsic prototype');
|
||||
check(factory instanceof intrinsic, 'factory instance');
|
||||
check(Object.prototype.toString.call(factory) === '[object IDBFactory]', 'factory tag');
|
||||
check(factory.cmp(1, 2) === -1 && factory.cmp(2, 2) === 0, 'native cmp works');
|
||||
for (const name of ['open', 'deleteDatabase', 'databases']) {
|
||||
check(typeof factory[name] === 'function', name + ' method');
|
||||
}
|
||||
check(globalThis.indexedDB === factory, 'SameObject');
|
||||
check(constructorReads === 0, 'no public constructor reads');
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'IDBFactory');
|
||||
if (mode === 'getter') {
|
||||
check(descriptor.get === poison && !descriptor.configurable, 'getter preserved');
|
||||
} else if (mode === 'delete') {
|
||||
check(descriptor === undefined, 'deleted constructor stays absent');
|
||||
} else {
|
||||
check(descriptor.value === replacement, 'replacement preserved');
|
||||
}
|
||||
return 'ok';
|
||||
})
|
||||
Reference in New Issue
Block a user