diff --git a/moli-core/tests/web_apis/fixtures/indexeddb-database-names.js b/moli-core/tests/web_apis/fixtures/indexeddb-database-names.js new file mode 100644 index 0000000000..234a701b68 --- /dev/null +++ b/moli-core/tests/web_apis/fixtures/indexeddb-database-names.js @@ -0,0 +1,182 @@ +globalThis.databaseNameChecks = []; +async function databaseNamesProbe(prefix = 'database-names') { + const checks = databaseNameChecks; + const check = (label, pass, actual = '') => checks.push({label, pass, actual: String(actual)}); + const units = name => Array.from({length: name.length}, (_, i) => name.charCodeAt(i)).join(','); + const result = request => new Promise((resolve, reject) => { + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + const complete = tx => new Promise((resolve, reject) => { + tx.oncomplete = resolve; + tx.onabort = () => reject(tx.error || new Error('unexpected abort')); + }); + const exercise = async (label, Realm, MethodRealm) => { + const here = (name, pass, actual) => check(label + ': ' + name, pass, actual); + const factory = Realm.indexedDB; + const open = (...args) => MethodRealm.IDBFactory.prototype.open.apply(factory, args); + const remove = (...args) => MethodRealm.IDBFactory.prototype.deleteDatabase.apply(factory, args); + const list = () => MethodRealm.IDBFactory.prototype.databases.call(factory); + const getName = Object.getOwnPropertyDescriptor(MethodRealm.IDBDatabase.prototype, 'name').get; + const stem = prefix + '-' + label + '-'; + const names = ['', '\0', '\ud800', '\udfff', '\ufffd', '\ud801', + '\udfff\ud800', '\ufffd\ufffd', '💾', 'd800', '\ud800\0'].map(s => stem + s); + const retained = []; + for (let i = 0; i < names.length; ++i) { + const conversion = []; + const name = {[Symbol.toPrimitive](hint) { conversion.push(hint); return names[i]; }}; + const version = {valueOf() { conversion.push('version'); return 1; }}; + const request = open(name, version); + here('conversion order ' + i, conversion.join() === 'string,version', conversion); + let oldVersion, upgradeName; + request.onupgradeneeded = event => { + oldVersion = event.oldVersion; + upgradeName = getName.call(request.result); + request.result.createObjectStore('records'); + }; + const db = await result(request); + retained.push(db); + here('upgrade name ' + i, upgradeName === names[i], upgradeName === undefined ? '' : units(upgradeName)); + here('independent creation ' + i, oldVersion === 0, oldVersion); + here('open name ' + i, db.name === names[i], units(db.name)); + const tx = db.transaction('records', 'readwrite'); + const done = complete(tx); + tx.objectStore('records').put(i, 'same-key'); + await done; + db.close(); + here('closed name ' + i, getName.call(db) === names[i], units(getName.call(db))); + } + let listed = (await list()).filter(db => db.name.startsWith(stem)); + here('listing keeps every database', listed.length === names.length, listed.length); + for (let i = 0; i < names.length; ++i) { + here('listing units ' + i, listed.some(db => db.name === names[i] && db.version === 1)); + const db = await result(open(new Realm.String(names[i]))); + const tx = db.transaction('records'); + const done = complete(tx); + const read = tx.objectStore('records').get('same-key'); + await done; + here('independent records ' + i, read.result === i, read.result); + db.close(); + } + + const order = []; + const marker = new Error('name conversion'); + let caught; + try { + open({toString() { order.push('name'); throw marker; }}, {valueOf() { order.push('version'); return 1; }}); + } catch (error) { caught = error; } + here('name exception preserves identity and prevents version conversion', caught === marker && order.join() === 'name', order); + for (const [method, callback] of [['open', open], ['deleteDatabase', remove]]) { + let error; + try { callback(Symbol('name')); } catch (caught) { error = caught; } + here(method + ' rejects Symbol in callee realm', error !== undefined && Object.getPrototypeOf(error) === MethodRealm.TypeError.prototype, error?.name); + } + + const upgrading = open(names[2], 2); + let upgradeOld; + upgrading.onupgradeneeded = event => { + upgradeOld = event.oldVersion; + upgrading.transaction.objectStore('records').put('upgraded', 'same-key'); + }; + const upgraded = await result(upgrading); + here('upgrade uses exact database', upgradeOld === 1 && upgraded.name === names[2], upgradeOld); + upgraded.close(); + const aborted = open(names[3], 3); + aborted.onupgradeneeded = () => { + aborted.transaction.objectStore('records').put('aborted', 'same-key'); + aborted.transaction.abort(); + }; + let abortError; + try { (await result(aborted)).close(); } catch (error) { abortError = error; } + here('aborted upgrade reports AbortError', abortError?.name === 'AbortError', abortError?.name); + listed = (await list()).filter(db => db.name.startsWith(stem)); + for (let i = 0; i < names.length; ++i) { + here('versions remain independent ' + i, listed.some(db => db.name === names[i] && db.version === (i === 2 ? 2 : 1))); + } + const rollback = await result(open(names[3])); + const readTx = rollback.transaction('records'); + const readDone = complete(readTx); + const rolledBack = readTx.objectStore('records').get('same-key'); + await readDone; + here('aborted data remains independent', rolledBack.result === 3, rolledBack.result); + rollback.close(); + + // Keep a different surrogate name open. A mistaken identity match still + // completes via onblocked so every engine reports the same assertions. + const held = await result(open(names[3])); + let notifications = 0; + held.onversionchange = () => { ++notifications; }; + const deleting = remove({toString() { return names[2]; }}); + let blocked = false; + deleting.onblocked = () => { blocked = true; held.close(); }; + await result(deleting); + held.close(); + here('delete does not notify another database', notifications === 0, notifications); + here('delete is not blocked by another database', !blocked, blocked); + listed = (await list()).filter(db => db.name.startsWith(stem)); + here('delete removes only the exact database', !listed.some(db => db.name === names[2]) + && listed.length === names.length - 1, listed.length); + here('retained connection name survives deletion', getName.call(retained[2]) === names[2], units(getName.call(retained[2]))); + for (const name of names) await result(remove(name)); + here('cleanup removes all test databases', !(await list()).some(db => db.name.startsWith(stem))); + }; + + await exercise('own', globalThis, globalThis); + if (typeof document !== 'undefined') { + const frame = document.createElement('iframe'); + const loaded = new Promise(resolve => { frame.onload = resolve; }); + frame.src = 'about:blank'; + document.body.appendChild(frame); + await loaded; + try { + await exercise('foreign-method', globalThis, frame.contentWindow); + await exercise('foreign-factory', frame.contentWindow, globalThis); + } finally { frame.remove(); } + + const firstName = prefix + '-shared-\ud800'; + const secondName = prefix + '-shared-\udfff'; + for (const name of [firstName, secondName]) { + const request = indexedDB.open(name, 1); + request.onupgradeneeded = () => request.result.createObjectStore('records'); + (await result(request)).close(); + } + const held = await result(indexedDB.open(firstName)); + let notices = 0; + held.onversionchange = () => { ++notices; held.close(); }; + const source = `onmessage = async event => { + const request = indexedDB.open(event.data, 2); + let oldVersion; + request.onupgradeneeded = event => { oldVersion = event.oldVersion; }; + request.onerror = () => postMessage({error: request.error.name}); + request.onsuccess = () => { + const db = request.result; + const name = db.name; + db.close(); + postMessage({name, oldVersion}); + }; + };`; + const url = URL.createObjectURL(new Blob([source], {type: 'text/javascript'})); + const worker = new Worker(url); + try { + const reply = new Promise((resolve, reject) => { + worker.onmessage = event => resolve(event.data); + worker.onerror = event => reject(new Error(event.message)); + }); + worker.postMessage(secondName); + const observed = await reply; + check('worker upgrade preserves database name', observed.name === secondName && observed.oldVersion === 1, JSON.stringify(observed)); + check('worker upgrade does not notify a different Window connection', notices === 0, notices); + held.close(); + const listed = await indexedDB.databases(); + check('Window sees independent versions after worker upgrade', listed.some(db => db.name === firstName && db.version === 1) + && listed.some(db => db.name === secondName && db.version === 2)); + } finally { + held.close(); + worker.terminate(); + URL.revokeObjectURL(url); + await result(indexedDB.deleteDatabase(firstName)); + await result(indexedDB.deleteDatabase(secondName)); + } + } + return {state: checks.every(check => check.pass) ? 'pass' : 'fail', checks}; +} diff --git a/moli-core/tests/web_apis/indexed_db_transaction.rs b/moli-core/tests/web_apis/indexed_db_transaction.rs index 9581ee84c1..c7f8ce6cb7 100644 --- a/moli-core/tests/web_apis/indexed_db_transaction.rs +++ b/moli-core/tests/web_apis/indexed_db_transaction.rs @@ -21,6 +21,28 @@ async fn indexed_db_key_ranges_protect_bounds_and_validate_receivers() -> Result Ok(()) } +#[tokio::test(flavor = "multi_thread")] +async fn indexed_db_database_names_preserve_utf16_identity_across_realms_and_queues() -> Result<()> +{ + let server = FixtureServer::spawn().await?; + let browser = Browser::new(AppConfig::default())?; + let fixture = include_str!("fixtures/indexeddb-database-names.js"); + for target in ["window", "child", "worker"] { + let source = format!( + "{fixture}\ndatabaseNamesProbe('database-names-{target}').then(finish, error => finish({{state: 'error', error: String(error), checks: databaseNameChecks}}));" + ); + let result = run_probe(&browser, &server, target, &source).await?; + assert_eq!(result["state"], "pass", "{target}: {result}"); + assert_eq!( + result["checks"].as_array().unwrap().len(), + if target == "worker" { 100 } else { 303 }, + "{target}: {result}" + ); + } + server.shutdown().await; + Ok(()) +} + #[tokio::test(flavor = "multi_thread")] async fn indexed_db_database_attributes_preserve_connection_state_across_upgrade_and_close() -> Result<()> { diff --git a/moli-indexeddb/src/connection_queue.rs b/moli-indexeddb/src/connection_queue.rs index d2adb953e2..0dc7a3abec 100644 --- a/moli-indexeddb/src/connection_queue.rs +++ b/moli-indexeddb/src/connection_queue.rs @@ -1,6 +1,7 @@ //! FIFO coordination for IDBFactory open/delete algorithms. Queue ownership //! spans event loops; callbacks only wake the accepting loop and carry no JS values. +use crate::IndexedDbName; use parking_lot::Mutex; use std::{ collections::{BTreeMap, VecDeque}, @@ -17,7 +18,7 @@ pub struct ConnectionRequestQueues { #[derive(Default)] struct QueueState { next_id: u64, - queues: BTreeMap<(String, String), VecDeque>, + queues: BTreeMap<(String, IndexedDbName), VecDeque>, } struct QueuedRequest { @@ -30,7 +31,7 @@ struct QueuedRequest { #[derive(Clone)] pub struct ConnectionRequestHandle { queues: Weak, - key: (String, String), + key: (String, IndexedDbName), id: u64, } @@ -52,10 +53,10 @@ impl ConnectionRequestQueues { pub fn enqueue( self: &Arc, storage_key: &str, - name: &str, + name: impl Into, wake: ConnectionRequestWake, ) -> ConnectionRequestLease { - let key = (storage_key.to_owned(), name.to_owned()); + let key = (storage_key.to_owned(), name.into()); let mut state = self.state.lock(); state.next_id = state .next_id diff --git a/moli-indexeddb/src/manager.rs b/moli-indexeddb/src/manager.rs index e560331cba..1cb917e238 100644 --- a/moli-indexeddb/src/manager.rs +++ b/moli-indexeddb/src/manager.rs @@ -135,12 +135,17 @@ impl IndexedDbManager { }) } - pub fn delete_database(&mut self, origin: &str, name: &str) -> Result<(), IndexedDbError> { + pub fn delete_database( + &mut self, + origin: &str, + name: impl Into, + ) -> Result<(), IndexedDbError> { + let name = &name.into(); self.ensure_origin_loaded(origin)?; if self .databases .values() - .any(|db| db.origin == origin && db.name == name) + .any(|db| db.origin == origin && &db.name == name) { return Err(IndexedDbError::InvalidState(format!( "database `{name}` for origin `{origin}` is still open" @@ -269,7 +274,7 @@ impl IndexedDbManager { pub(crate) fn committed_origin_usage_except_database( &self, origin: &str, - db_name: &str, + db_name: &IndexedDbName, ) -> u64 { self.origins .get(origin) @@ -278,7 +283,7 @@ impl IndexedDbManager { state .databases .iter() - .filter(|(name, _)| name.as_str() != db_name) + .filter(|(name, _)| *name != db_name) .map(|(name, database)| database_usage_bytes(name, database)), ) }) @@ -330,8 +335,9 @@ impl IndexedDbManager { pub fn database_version( &mut self, origin: &str, - name: &str, + name: impl Into, ) -> Result, IndexedDbError> { + let name = &name.into(); self.ensure_origin_loaded(origin)?; Ok(self .origins @@ -1126,7 +1132,7 @@ impl IndexedDbManager { Ok(()) } - fn allocate_database_handle(&mut self, origin: String, name: String) -> DatabaseHandle { + fn allocate_database_handle(&mut self, origin: String, name: IndexedDbName) -> DatabaseHandle { let handle = DatabaseHandle::from_raw(self.next_database_handle.fetch_add(1, Ordering::Relaxed)); self.databases.insert( @@ -1144,7 +1150,7 @@ impl IndexedDbManager { &mut self, database: DatabaseHandle, origin: &str, - db_name: &str, + db_name: &IndexedDbName, working_copy: DatabaseData, ) -> TransactionHandle { let stores = working_copy.stores.keys().cloned().collect::>(); @@ -1181,7 +1187,7 @@ impl IndexedDbManager { pub(crate) fn database_data( &self, origin: &str, - name: &str, + name: &IndexedDbName, ) -> Result<&DatabaseData, IndexedDbError> { self.origins .get(origin) diff --git a/moli-indexeddb/src/persistence.rs b/moli-indexeddb/src/persistence.rs index 4bae8ef4e2..a09b521ab8 100644 --- a/moli-indexeddb/src/persistence.rs +++ b/moli-indexeddb/src/persistence.rs @@ -24,6 +24,17 @@ struct PersistentOrigin { #[serde(default)] origin: Option, databases: BTreeMap, + // Keep ordinary Unicode names in the legacy JSON map. JSON strings + // cannot represent names containing unpaired UTF-16 surrogates. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + databases_utf16: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct PersistentNamedDatabase { + name: Vec, + #[serde(flatten)] + database: PersistentDatabase, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -350,7 +361,17 @@ pub(crate) fn origin_path(storage_root: &Path, origin: &str) -> PathBuf { fn origin_state_from_persistent(persistent: PersistentOrigin) -> OriginState { let mut databases = BTreeMap::new(); - for (name, database) in persistent.databases { + for (name, database) in persistent + .databases + .into_iter() + .map(|(name, database)| (IndexedDbName::from(name), database)) + .chain( + persistent + .databases_utf16 + .into_iter() + .map(|named| (IndexedDbName::from_utf16(named.name), named.database)), + ) + { let mut stores = BTreeMap::new(); for (store_name, store) in database .stores @@ -417,6 +438,7 @@ fn origin_state_from_persistent(persistent: PersistentOrigin) -> OriginState { fn persistent_origin_from_state(origin: &str, state: &OriginState) -> PersistentOrigin { let mut databases = BTreeMap::new(); + let mut databases_utf16 = Vec::new(); for (name, database) in &state.databases { let mut stores = BTreeMap::new(); let mut stores_utf16 = Vec::new(); @@ -465,17 +487,24 @@ fn persistent_origin_from_state(origin: &str, state: &OriginState) -> Persistent }), } } - databases.insert( - name.clone(), - PersistentDatabase { - version: database.version, - stores, - stores_utf16, - }, - ); + let database = PersistentDatabase { + version: database.version, + stores, + stores_utf16, + }; + match String::from_utf16(name.as_utf16()) { + Ok(name) => { + databases.insert(name, database); + } + Err(_) => databases_utf16.push(PersistentNamedDatabase { + name: name.as_utf16().to_vec(), + database, + }), + } } PersistentOrigin { origin: Some(origin.to_owned()), databases, + databases_utf16, } } diff --git a/moli-indexeddb/src/state.rs b/moli-indexeddb/src/state.rs index 3fe2a823dd..fcb9d67b80 100644 --- a/moli-indexeddb/src/state.rs +++ b/moli-indexeddb/src/state.rs @@ -10,7 +10,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq)] pub(crate) struct OriginState { - pub(crate) databases: BTreeMap, + pub(crate) databases: BTreeMap, } #[derive(Debug, Clone, PartialEq)] @@ -38,7 +38,7 @@ pub(crate) struct IndexData { #[derive(Debug, Clone)] pub(crate) struct DatabaseHandleState { pub(crate) origin: String, - pub(crate) name: String, + pub(crate) name: IndexedDbName, pub(crate) closed: bool, } @@ -46,7 +46,7 @@ pub(crate) struct DatabaseHandleState { pub(crate) struct TransactionState { pub(crate) database: DatabaseHandle, pub(crate) origin: String, - pub(crate) db_name: String, + pub(crate) db_name: IndexedDbName, pub(crate) mode: TransactionMode, pub(crate) stores: BTreeSet, pub(crate) state: TransactionLifecycle, diff --git a/moli-indexeddb/src/tests.rs b/moli-indexeddb/src/tests.rs index fc24d69139..b59bb39f9d 100644 --- a/moli-indexeddb/src/tests.rs +++ b/moli-indexeddb/src/tests.rs @@ -18,6 +18,7 @@ struct TestDir { path: PathBuf, } +mod database_names; mod durability; mod index_rename; mod record_revision; @@ -271,7 +272,7 @@ fn seed_database_record(manager: &mut IndexedDbManager, origin: &str, name: &str let opened = manager .open(OpenOptions { origin: origin.to_owned(), - name: name.to_owned(), + name: name.into(), version: None, }) .expect("open should succeed"); @@ -309,7 +310,7 @@ fn open_new_database_creates_upgrade_transaction() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -331,7 +332,7 @@ fn in_memory_manager_does_not_persist_across_reopen() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: Some(1), }) .expect("open should succeed"); @@ -358,7 +359,7 @@ fn in_memory_manager_does_not_persist_across_reopen() { let reopened = IndexedDbManager::new_in_memory() .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("reopen should succeed"); @@ -406,7 +407,7 @@ fn upgrade_transaction_can_create_store_and_persist() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: Some(3), }) .expect("open should succeed"); @@ -435,7 +436,7 @@ fn upgrade_transaction_can_create_store_and_persist() { .expect("manager should be recreated") .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("reopen should succeed"); @@ -452,7 +453,7 @@ fn upgrade_transaction_can_persist_index_metadata() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: Some(1), }) .expect("open should succeed"); @@ -507,7 +508,7 @@ fn upgrade_transaction_can_persist_index_metadata() { let reopened = reopened_manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("reopen should succeed"); @@ -526,7 +527,7 @@ fn index_key_path_may_be_empty_string() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: Some(1), }) .expect("open should succeed"); @@ -560,7 +561,7 @@ fn readwrite_transaction_can_store_and_reload_records() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -595,7 +596,7 @@ fn readwrite_transaction_can_store_and_reload_records() { let reopened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("reopen should succeed"); @@ -640,7 +641,7 @@ fn external_blob_file_and_file_system_handle_objects_persist_with_their_record() let opened = manager .open(OpenOptions { origin: origin.to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -679,7 +680,7 @@ fn external_blob_file_and_file_system_handle_objects_persist_with_their_record() let reopened = manager .open(OpenOptions { origin: origin.to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("database should reopen"); @@ -705,7 +706,7 @@ fn write_quota_rejection_rolls_back_working_copy() { let opened = manager .open(OpenOptions { origin: origin.to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -778,7 +779,7 @@ fn external_blob_bytes_participate_in_quota_and_rollback() { let opened = manager .open(OpenOptions { origin: origin.to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -837,7 +838,7 @@ fn transaction_commit_rechecks_aggregate_quota_without_publishing_working_copy() let opened = manager .open(OpenOptions { origin: origin.to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -911,7 +912,7 @@ fn origin_usage_tracks_committed_metadata_and_record_bytes() { let opened = manager .open(OpenOptions { origin: origin.to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1157,7 +1158,7 @@ fn readwrite_transaction_can_list_keys() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1202,7 +1203,7 @@ fn mixed_keys_sort_in_indexeddb_order() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1250,7 +1251,7 @@ fn aborted_transaction_does_not_persist_changes() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1297,7 +1298,7 @@ fn delete_database_removes_persisted_state() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1321,7 +1322,7 @@ fn delete_database_removes_persisted_state() { .expect("manager should reopen") .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1343,7 +1344,7 @@ fn databases_lists_committed_name_version_snapshot() { let beta = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "beta".to_owned(), + name: "beta".into(), version: Some(2), }) .expect("beta open should succeed"); @@ -1353,7 +1354,7 @@ fn databases_lists_committed_name_version_snapshot() { let alpha = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "alpha".to_owned(), + name: "alpha".into(), version: None, }) .expect("alpha open should succeed"); @@ -1368,11 +1369,11 @@ fn databases_lists_committed_name_version_snapshot() { infos, vec![ DatabaseNameAndVersion { - name: "alpha".to_owned(), + name: "alpha".into(), version: 1, }, DatabaseNameAndVersion { - name: "beta".to_owned(), + name: "beta".into(), version: 2, }, ] @@ -1399,7 +1400,7 @@ fn clear_origin_removes_persisted_state_and_keeps_other_origins() { let first = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("first open should succeed"); @@ -1416,7 +1417,7 @@ fn clear_origin_removes_persisted_state_and_keeps_other_origins() { let second = manager .open(OpenOptions { origin: "https://other.example".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("second open should succeed"); @@ -1438,7 +1439,7 @@ fn clear_origin_removes_persisted_state_and_keeps_other_origins() { let cleared = reopened .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("cleared origin open should succeed"); @@ -1453,7 +1454,7 @@ fn clear_origin_removes_persisted_state_and_keeps_other_origins() { let kept = reopened .open(OpenOptions { origin: "https://other.example".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("other origin open should succeed"); @@ -1468,7 +1469,7 @@ fn readonly_transaction_rejects_writes() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1504,7 +1505,7 @@ fn transaction_scope_is_enforced_per_object_store() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1543,7 +1544,7 @@ fn delete_database_rejects_open_connections() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1571,7 +1572,7 @@ fn aborted_upgrade_transaction_does_not_publish_schema() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: Some(2), }) .expect("open should succeed"); @@ -1592,7 +1593,7 @@ fn aborted_upgrade_transaction_does_not_publish_schema() { .expect("manager should reopen") .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("reopen should succeed"); @@ -1614,7 +1615,7 @@ fn upgrade_can_delete_object_store_before_commit() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); @@ -1653,7 +1654,7 @@ fn distinct_origins_persist_to_distinct_storage_files() { let first = manager .open(OpenOptions { origin: "https://a:b".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("first open should succeed"); @@ -1673,7 +1674,7 @@ fn distinct_origins_persist_to_distinct_storage_files() { let second = manager .open(OpenOptions { origin: "https://a/b".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("second open should succeed"); @@ -1694,14 +1695,14 @@ fn distinct_origins_persist_to_distinct_storage_files() { let first = reopened .open(OpenOptions { origin: "https://a:b".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("first reopen should succeed"); let second = reopened .open(OpenOptions { origin: "https://a/b".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("second reopen should succeed"); @@ -1730,7 +1731,7 @@ fn failed_open_does_not_block_later_delete_database() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: Some(2), }) .expect("open should succeed"); @@ -1750,7 +1751,7 @@ fn failed_open_does_not_block_later_delete_database() { let error = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: Some(1), }) .expect_err("lower version open should fail"); @@ -1768,7 +1769,7 @@ fn generated_key_preview_and_failed_quota_write_leave_generator_unchanged() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "key-preview".to_owned(), + name: "key-preview".into(), version: None, }) .unwrap(); @@ -1841,7 +1842,7 @@ fn auto_increment_rejects_exhausted_generator_range() { let opened = manager .open(OpenOptions { origin: "https://example.com".to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); diff --git a/moli-indexeddb/src/tests/database_names.rs b/moli-indexeddb/src/tests/database_names.rs new file mode 100644 index 0000000000..863eecc658 --- /dev/null +++ b/moli-indexeddb/src/tests/database_names.rs @@ -0,0 +1,232 @@ +use super::*; +use std::sync::Arc; + +const ORIGIN: &str = "https://database-names.test"; + +fn open(manager: &mut IndexedDbManager, name: &IndexedDbName, version: u64) -> OpenResult { + manager + .open(OpenOptions { + origin: ORIGIN.into(), + name: name.clone(), + version: Some(version), + }) + .unwrap() +} + +#[test] +fn database_names_preserve_utf16_records_versions_and_legacy_persistence() { + let dir = TestDir::new(); + let legacy = IndexedDbName::from("legacy-\0-数据库-💾"); + let mut manager = IndexedDbManager::new(&dir.path).unwrap(); + let db = open(&mut manager, &legacy, 1); + manager + .commit_transaction(db.upgrade_transaction.unwrap()) + .unwrap(); + manager.close_database(db.database).unwrap(); + drop(manager); + let path = origin_path(&dir.path, ORIGIN); + let persisted: serde_json::Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap(); + assert!(persisted.get("databases_utf16").is_none()); + assert_eq!(persisted["databases"]["legacy-\0-数据库-💾"]["version"], 1); + + let names = [ + vec![], + vec![0], + vec![0xd800], + vec![0xd801], + vec![0xdfff], + vec![0xfffd], + vec![0xdfff, 0xd800], + vec![0xd83d, 0xdcbe], + vec![0xe000], + ] + .map(IndexedDbName::from_utf16); + let mut manager = IndexedDbManager::new(&dir.path).unwrap(); + let db = open(&mut manager, &legacy, 1); + assert_eq!(db.disposition, OpenDisposition::Existing); + manager.close_database(db.database).unwrap(); + for (i, name) in names.iter().enumerate() { + let db = open(&mut manager, name, 1); + assert_eq!( + db.disposition, + OpenDisposition::UpgradeNeeded { + old_version: 0, + new_version: 1 + } + ); + let tx = db.upgrade_transaction.unwrap(); + manager + .create_object_store(tx, "s", ObjectStoreOptions::default()) + .unwrap(); + manager + .put(tx, "s", Some(Key::from(1)), vec![i as u8]) + .unwrap(); + manager.commit_transaction(tx).unwrap(); + manager.close_database(db.database).unwrap(); + } + drop(manager); + let persisted: serde_json::Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap(); + assert_eq!(persisted["databases"].as_object().unwrap().len(), 6); + assert_eq!(persisted["databases_utf16"].as_array().unwrap().len(), 4); + + let mut manager = IndexedDbManager::new(&dir.path).unwrap(); + let listed = manager + .databases(ORIGIN) + .unwrap() + .into_iter() + .map(|db| db.name) + .collect::>(); + assert_eq!(listed, names.iter().cloned().chain([legacy]).collect()); + for (i, name) in names.iter().enumerate() { + let db = open(&mut manager, name, 1); + assert_eq!(db.disposition, OpenDisposition::Existing); + assert_eq!(manager.database_info(db.database).unwrap().name, *name); + let tx = manager + .begin_transaction(db.database, &["s".into()], TransactionMode::ReadOnly) + .unwrap(); + assert_eq!( + manager.get(tx, "s", &Key::from(1)).unwrap(), + RequestOutcome::Value(Some(vec![i as u8].into())) + ); + manager.commit_transaction(tx).unwrap(); + manager.close_database(db.database).unwrap(); + } + + let db = open(&mut manager, &names[2], 2); + let tx = db.upgrade_transaction.unwrap(); + manager.put(tx, "s", Some(Key::from(1)), vec![99]).unwrap(); + manager.abort_transaction(tx).unwrap(); + let tx = manager + .begin_transaction(db.database, &["s".into()], TransactionMode::ReadOnly) + .unwrap(); + assert_eq!( + manager.get(tx, "s", &Key::from(1)).unwrap(), + RequestOutcome::Value(Some(vec![2].into())) + ); + manager.commit_transaction(tx).unwrap(); + manager.close_database(db.database).unwrap(); + assert_eq!( + manager.database_version(ORIGIN, &names[2]).unwrap(), + Some(1) + ); + let db = open(&mut manager, &names[3], 2); + manager + .commit_transaction(db.upgrade_transaction.unwrap()) + .unwrap(); + manager.close_database(db.database).unwrap(); + // A connection to another surrogate name must not block deleting this one. + let held = open(&mut manager, &names[4], 1); + manager.delete_database(ORIGIN, &names[2]).unwrap(); + assert!(matches!( + manager.delete_database(ORIGIN, &names[4]), + Err(IndexedDbError::InvalidState(_)) + )); + manager.close_database(held.database).unwrap(); + drop(manager); + let mut manager = IndexedDbManager::new(&dir.path).unwrap(); + for (i, name) in names.iter().enumerate() { + assert_eq!( + manager.database_version(ORIGIN, name).unwrap(), + match i { + 2 => None, + 3 => Some(2), + _ => Some(1), + } + ); + } +} + +#[test] +fn connection_and_transaction_queues_use_exact_database_code_units() { + let mut manager = IndexedDbManager::new_in_memory(); + let names = [0xd800, 0xdfff, 0xfffd].map(|unit| IndexedDbName::from_utf16(vec![unit])); + let queues = manager.connection_request_queues(); + let leases = names + .each_ref() + .map(|name| queues.enqueue(ORIGIN, name, Arc::new(|| {}))); + let same = queues.enqueue(ORIGIN, &names[0], Arc::new(|| {})); + assert!(leases.iter().all(|lease| lease.handle().is_head())); + assert!(!same.handle().is_head()); + let databases = names.each_ref().map(|name| { + let db = open(&mut manager, name, 1); + let tx = db.upgrade_transaction.unwrap(); + manager + .create_object_store(tx, "s", ObjectStoreOptions::default()) + .unwrap(); + manager.commit_transaction(tx).unwrap(); + db.database + }); + let transactions = databases.map(|db| { + manager + .queue_transaction_start( + db, + &["s".into()], + TransactionMode::ReadWrite, + Arc::new(|| {}), + ) + .unwrap() + }); + let same_tx = manager + .queue_transaction_start( + databases[0], + &["s".into()], + TransactionMode::ReadWrite, + Arc::new(|| {}), + ) + .unwrap(); + assert!(transactions.iter().all(|lease| lease.handle().is_ready())); + assert!(!same_tx.handle().is_ready()); + for (i, lease) in transactions.into_iter().enumerate() { + let tx = manager.start_queued_transaction(lease.handle()).unwrap(); + manager + .put(tx, "s", Some(Key::from(1)), vec![i as u8]) + .unwrap(); + manager.commit_transaction(tx).unwrap(); + } + assert!(same_tx.handle().is_ready()); + let tx = manager.start_queued_transaction(same_tx.handle()).unwrap(); + assert_eq!( + manager.get(tx, "s", &Key::from(1)).unwrap(), + RequestOutcome::Value(Some(vec![0].into())) + ); + manager.abort_transaction(tx).unwrap(); + drop(leases); + assert!(same.handle().is_head()); +} + +#[test] +fn database_name_quota_accounting_preserves_other_databases_on_rollback() { + let mut manager = IndexedDbManager::new_in_memory(); + let names = [0xd800, 0xdfff, 0xfffd].map(|unit| IndexedDbName::from_utf16(vec![unit])); + for name in &names { + let db = open(&mut manager, name, 1); + manager + .commit_transaction(db.upgrade_transaction.unwrap()) + .unwrap(); + manager.close_database(db.database).unwrap(); + } + assert_eq!(manager.origin_usage_bytes(ORIGIN).unwrap(), 33); + let db = open(&mut manager, &names[0], 2); + let tx = db.upgrade_transaction.unwrap(); + manager + .create_object_store(tx, "s", ObjectStoreOptions::default()) + .unwrap(); + assert!(matches!( + manager.commit_transaction_with_quota( + tx, + IndexedDbQuotaCheck { + quota: 33, + non_indexed_db_usage: 0 + } + ), + Err(IndexedDbError::QuotaExceeded { .. }) + )); + assert_eq!(manager.origin_usage_bytes(ORIGIN).unwrap(), 33); + assert!( + manager + .databases(ORIGIN) + .unwrap() + .iter() + .all(|db| db.version == 1) + ); +} diff --git a/moli-indexeddb/src/transaction_queue.rs b/moli-indexeddb/src/transaction_queue.rs index 543c166557..484ad83a4e 100644 --- a/moli-indexeddb/src/transaction_queue.rs +++ b/moli-indexeddb/src/transaction_queue.rs @@ -20,7 +20,7 @@ pub(crate) struct TransactionRequestQueues { #[derive(Default)] struct QueueState { next_id: u64, - queues: BTreeMap<(String, String), VecDeque>, + queues: BTreeMap<(String, IndexedDbName), VecDeque>, } struct TransactionScope { @@ -46,7 +46,7 @@ struct QueuedRequest { #[derive(Clone)] pub struct TransactionRequestHandle { queues: Weak, - key: (String, String), + key: (String, IndexedDbName), id: u64, database: DatabaseHandle, scope: Arc, @@ -71,7 +71,7 @@ impl Drop for TransactionRequestLease { impl TransactionRequestQueues { pub(crate) fn enqueue( self: &Arc, - key: (String, String), + key: (String, IndexedDbName), database: DatabaseHandle, stores: BTreeSet, mode: TransactionMode, diff --git a/moli-indexeddb/src/types.rs b/moli-indexeddb/src/types.rs index da3c008db8..d2ba2ee3ca 100644 --- a/moli-indexeddb/src/types.rs +++ b/moli-indexeddb/src/types.rs @@ -54,7 +54,7 @@ pub enum OpenDisposition { #[derive(Debug, Clone, PartialEq, Eq)] pub struct OpenOptions { pub origin: String, - pub name: String, + pub name: IndexedDbName, pub version: Option, } @@ -67,14 +67,14 @@ pub struct OpenResult { #[derive(Debug, Clone, PartialEq, Eq)] pub struct DatabaseInfo { - pub name: String, + pub name: IndexedDbName, pub version: u64, pub object_store_names: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct DatabaseNameAndVersion { - pub name: String, + pub name: IndexedDbName, pub version: u64, } diff --git a/moli-indexeddb/src/usage.rs b/moli-indexeddb/src/usage.rs index 239102c2fc..9ebd0fceea 100644 --- a/moli-indexeddb/src/usage.rs +++ b/moli-indexeddb/src/usage.rs @@ -16,15 +16,15 @@ pub(crate) fn origin_usage_bytes(state: &OriginState) -> u64 { ) } -pub(crate) fn database_usage_bytes(name: &str, database: &DatabaseData) -> u64 { +pub(crate) fn database_usage_bytes(name: &IndexedDbName, database: &DatabaseData) -> u64 { database_usage_bytes_for_stores(name, database.stores.iter()) } pub(crate) fn database_usage_bytes_for_stores<'a>( - name: &str, + name: &IndexedDbName, stores: impl Iterator, ) -> u64 { - string_usage_bytes(name) + name.usage_bytes() .saturating_add(U64_STORAGE_BYTES) .saturating_add(sum_usage( stores.map(|(name, store)| object_store_usage_bytes(name, store)), diff --git a/moli-protocol/src/domains/storage/tests.rs b/moli-protocol/src/domains/storage/tests.rs index 1844421b59..07493ab4ad 100644 --- a/moli-protocol/src/domains/storage/tests.rs +++ b/moli-protocol/src/domains/storage/tests.rs @@ -151,7 +151,7 @@ fn seed_indexed_db_usage(manager: &moli_core::storage::SharedIndexedDbManager, o let opened = manager .open(moli_core::storage::IndexedDbOpenOptions { origin: origin.to_owned(), - name: "site-data".to_owned(), + name: "site-data".into(), version: Some(1), }) .expect("IndexedDB seed open should succeed"); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/database.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/database.rs index 99145faf65..e85c2e908e 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/database.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/database.rs @@ -7,12 +7,12 @@ use moli_webapi_declare::WebApiObject; #[derive(WebApiObject)] #[webapi(interface = web_api_interfaces::IDBDatabase, require_prototype)] -struct IdbDatabaseObjectDeclaration { +struct IdbDatabaseObjectDeclaration<'scope> { #[webapi(slot = INDEXED_DB_EVENT_LISTENERS_SLOT, init = "null_object")] event_listeners: (), #[webapi(slot = DATABASE_NAME_SLOT)] - name: String, + name: v8::Local<'scope, v8::String>, #[webapi(slot = DATABASE_VERSION_SLOT)] version: f64, } @@ -26,7 +26,8 @@ pub(in crate::context_bootstrap::indexed_db) fn create_database_object<'s>( ) -> Option> { let storage_key = storage_scope.storage_key().to_owned(); let database_key = database_registry_key(&storage_key, &info.name); - let database = IdbDatabaseObjectDeclaration::new(info.name.clone(), info.version as f64) + let name = idb_name_to_v8(scope, &info.name); + let database = IdbDatabaseObjectDeclaration::new(name, info.version as f64) .bind(scope) .ok()?; register_indexed_db_wrapper_with_owner( diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/registry/connections.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/registry/connections.rs index 5570cac867..887dc5be1f 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/registry/connections.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/registry/connections.rs @@ -9,9 +9,17 @@ use crate::context_bootstrap::indexed_db::{ pub(in crate::context_bootstrap::indexed_db) fn database_registry_key( origin: &str, - name: &str, + name: &IndexedDbName, ) -> String { - format!("{origin}\u{0}{name}") + // Connection notifications carry opaque strings. Encode code units rather + // than formatting a DOMString lossily; four hex digits per unit also keep + // names containing NUL distinct from the storage-key separator. + use std::fmt::Write; + let mut key = format!("{origin}\u{0}"); + for unit in name.as_utf16() { + write!(key, "{unit:04x}").expect("database registry key"); + } + key } pub(in crate::context_bootstrap::indexed_db) fn has_open_database_connections_for_key( diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/database.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/database.rs index 5bb2c8f468..d69ef4201a 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/database.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/database.rs @@ -9,7 +9,7 @@ use super::{ dom_exception_value, enqueue_blocked_delete_task, enqueue_blocked_open_task, enqueue_drain_blocked_open_requests_task, enqueue_indexed_db_task, enqueue_ready_transaction_starts, enqueue_transaction_abort_task, - enqueue_transaction_commit_task, indexed_db_databases_settle_task_payload, + enqueue_transaction_commit_task, idb_name_to_v8, indexed_db_databases_settle_task_payload, indexed_db_factory_storage_scope, indexed_db_runtime_factory, indexed_db_typed_execution_owner, indexed_db_typed_wrapper_is, object_bool_property, object_property_as_object, object_store_info_from_database_metadata, origin_allows_indexed_db, @@ -19,8 +19,7 @@ use super::{ storage_scope_for_window_execution_context, store_request_error, sync_transaction_object_store_names_from_database, throw_type_error, transaction_handle_from_value, unregister_open_database_connection, - unregister_regular_transaction, v8_string, validate_storage_bucket_scope, - with_indexed_db_manager, + unregister_regular_transaction, validate_storage_bucket_scope, with_indexed_db_manager, }; use moli_indexeddb::IndexedDbName; diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/databases.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/databases.rs index 77fa3ebeb0..0dbdb0195c 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/databases.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/databases.rs @@ -70,9 +70,8 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_factory_databases_callback<' let array = v8::Array::new(scope, infos.len() as i32); for (index, info) in infos.into_iter().enumerate() { let object = ObjectLiteralDeclaration::bind(scope); - if let Some(name) = v8_string(scope, &info.name) { - object.set_string_property(scope, "name", name.into()); - } + let name = idb_name_to_v8(scope, &info.name); + object.set_string_property(scope, "name", name.into()); let version = v8::Number::new(scope, info.version as f64); object.set_string_property(scope, "version", version.into()); let _ = array.set_index(scope, index as u32, object.into_value()); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/delete_database.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/delete_database.rs index ebb6e6160b..2b7543e6a1 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/delete_database.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/delete_database.rs @@ -5,8 +5,8 @@ use crate::webidl; #[derive(webidl::WebIdlArgs)] #[webidl(prefix = "IDBFactory.deleteDatabase")] struct IdbFactoryDeleteDatabaseArgs { - #[webidl(required)] - name: String, + #[webidl(required, with = parse_database_name_arg)] + name: IndexedDbName, } pub(in crate::context_bootstrap::indexed_db) fn idb_factory_delete_database_callback<'s>( @@ -60,3 +60,16 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_factory_delete_database_call enqueue_blocked_delete_task(scope, request, &origin, &name); rv.set(request.into()); } + +fn parse_database_name_arg<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: &v8::FunctionCallbackArguments<'s>, + index: i32, +) -> Result { + webidl::convert::( + scope, + args.get(index), + webidl::Context::argument("IDBFactory.deleteDatabase", (index + 1) as usize), + ) + .map(|name| IndexedDbName::from_utf16(name.0)) +} diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/open.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/open.rs index d69b5cd2c9..08aaa2660e 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/open.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/factory/open.rs @@ -5,8 +5,8 @@ use crate::webidl; #[derive(webidl::WebIdlArgs)] #[webidl(prefix = "IDBFactory.open")] struct IdbFactoryOpenArgs { - #[webidl(required)] - name: String, + #[webidl(required, with = parse_database_name_arg)] + name: IndexedDbName, #[webidl(converter = "enforce_range_unsigned_long_long")] version: Option, } @@ -71,3 +71,16 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_factory_open_callback<'s>( enqueue_blocked_open_task(scope, request, &origin, &name, version); rv.set(request.into()); } + +fn parse_database_name_arg<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: &v8::FunctionCallbackArguments<'s>, + index: i32, +) -> Result { + webidl::convert::( + scope, + args.get(index), + webidl::Context::argument("IDBFactory.open", (index + 1) as usize), + ) + .map(|name| IndexedDbName::from_utf16(name.0)) +} diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/state.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/state.rs index ac551a8a13..a8aa020d12 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/state.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/state.rs @@ -130,7 +130,7 @@ mod tests { let opened = manager .open(OpenOptions { origin: origin.to_owned(), - name: "app".to_owned(), + name: "app".into(), version: None, }) .expect("open should succeed"); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/delete.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/delete.rs index 23eaec30d6..ec72b214d7 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/delete.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/delete.rs @@ -5,7 +5,7 @@ pub(in crate::context_bootstrap::indexed_db) fn execute_delete_database_request< scope: &mut v8::PinScope<'s, '_>, request: v8::Local<'s, v8::Object>, storage_scope: IndexedDbStorageScope, - name: String, + name: IndexedDbName, ) { if let Err(error) = validate_storage_bucket_scope(scope, &storage_scope) { let error = request_error_object(scope, &error); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open.rs index 0fda9c896a..e9da8d3b4b 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open.rs @@ -7,7 +7,7 @@ pub(in crate::context_bootstrap::indexed_db) fn execute_open_request<'s>( scope: &mut v8::PinScope<'s, '_>, request: v8::Local<'s, v8::Object>, storage_scope: IndexedDbStorageScope, - name: String, + name: IndexedDbName, version: Option, ) { let database_name = name.clone(); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/info.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/info.rs index 607b0a6dce..3da1de00d7 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/info.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/info.rs @@ -2,7 +2,7 @@ use super::*; pub(super) fn opened_database_info( scope: &mut v8::PinScope<'_, '_>, - database_name: &str, + database_name: &IndexedDbName, opened: &moli_indexeddb::OpenResult, ) -> Result { match with_indexed_db_manager(scope, |manager| manager.database_info(opened.database)) { @@ -17,7 +17,7 @@ pub(super) fn opened_database_info( old_version: 0, new_version, } => Ok(DatabaseInfo { - name: database_name.to_string(), + name: database_name.clone(), version: *new_version, object_store_names: Vec::new(), }), diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/queue/blocked.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/queue/blocked.rs index 7ab8d64c0f..621ec726b7 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/queue/blocked.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/queue/blocked.rs @@ -34,7 +34,7 @@ pub(in crate::context_bootstrap::indexed_db) fn enqueue_blocked_open_task<'s>( scope: &mut v8::PinScope<'s, '_>, request: v8::Local<'s, v8::Object>, origin: &str, - name: &str, + name: &IndexedDbName, version: Option, ) { let context = request @@ -59,7 +59,7 @@ pub(in crate::context_bootstrap::indexed_db) fn enqueue_blocked_delete_task<'s>( scope: &mut v8::PinScope<'s, '_>, request: v8::Local<'s, v8::Object>, origin: &str, - name: &str, + name: &IndexedDbName, ) { let context = request .get_creation_context(scope) diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/typed_state.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/typed_state.rs index 42bc0301a5..6772c7ee73 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/typed_state.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/typed_state.rs @@ -278,7 +278,7 @@ impl IndexedDbOpenTaskPayload { struct IndexedDbBlockedTaskPayload { request: RealmValueHandle, origin: String, - name: String, + name: IndexedDbName, version: Option, old_version: u64, new_version: Option, @@ -294,7 +294,7 @@ impl IndexedDbBlockedTaskPayload { scope: &mut v8::PinScope<'_, '_>, request: v8::Local<'_, v8::Object>, origin: impl Into, - name: impl Into, + name: impl Into, version: Option, old_version: u64, new_version: Option, @@ -319,7 +319,7 @@ impl IndexedDbBlockedTaskPayload { pub(super) struct IndexedDbBlockedTaskPayloadLocals<'s> { pub(super) request: v8::Local<'s, v8::Object>, pub(super) origin: String, - pub(super) name: String, + pub(super) name: IndexedDbName, pub(super) version: Option, pub(super) old_version: u64, pub(super) new_version: Option, @@ -1515,7 +1515,7 @@ fn register_indexed_db_blocked_task<'s>( kind: IndexedDbTaskKind, request: v8::Local<'s, v8::Object>, origin: &str, - name: &str, + name: &IndexedDbName, version: Option, old_version: u64, new_version: Option, @@ -1546,7 +1546,7 @@ pub(super) fn register_indexed_db_blocked_open_task<'s>( task: v8::Local<'s, v8::Object>, request: v8::Local<'s, v8::Object>, origin: &str, - name: &str, + name: &IndexedDbName, version: Option, old_version: u64, new_version: u64, @@ -1569,7 +1569,7 @@ pub(super) fn register_indexed_db_blocked_delete_task<'s>( task: v8::Local<'s, v8::Object>, request: v8::Local<'s, v8::Object>, origin: &str, - name: &str, + name: &IndexedDbName, old_version: u64, ) { register_indexed_db_blocked_task( diff --git a/moli-renderer-v8/src/native_bridge/context_host/indexed_db_tasks.rs b/moli-renderer-v8/src/native_bridge/context_host/indexed_db_tasks.rs index 7a1851c6ac..cdabb67bf2 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/indexed_db_tasks.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/indexed_db_tasks.rs @@ -387,7 +387,7 @@ mod tests { .lock() .open(moli_indexeddb::OpenOptions { origin: "origin".to_owned(), - name: "db".to_owned(), + name: "db".into(), version: None, }) .unwrap(); diff --git a/moli-storage-service/src/buckets.rs b/moli-storage-service/src/buckets.rs index e57e5f01fc..06d1d786b5 100644 --- a/moli-storage-service/src/buckets.rs +++ b/moli-storage-service/src/buckets.rs @@ -2765,7 +2765,7 @@ mod tests { let mut manager = manager.lock(); let opened = manager.open(OpenOptions { origin: origin.to_owned(), - name: "bucket-db".to_owned(), + name: "bucket-db".into(), version: None, })?; let upgrade = opened