diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env.rs index 84424aaf66..d9a5b442ff 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env.rs @@ -7,7 +7,7 @@ mod origin; pub(in crate::context_bootstrap::indexed_db) use self::dom_string_list::{ idb_dom_string_list_backing_values, install_dom_string_list_template_bindings, - new_idb_dom_string_list, + new_idb_dom_string_list, new_idb_name_list, }; pub(in crate::context_bootstrap::indexed_db) use self::errors::{ dom_exception_value, request_error_object, diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env/dom_string_list.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env/dom_string_list.rs index 1f43bea348..1d250164d9 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env/dom_string_list.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/env/dom_string_list.rs @@ -69,6 +69,15 @@ pub(in crate::context_bootstrap::indexed_db) fn new_idb_dom_string_list<'s>( object } +pub(in crate::context_bootstrap::indexed_db) fn new_idb_name_list<'s>( + scope: &mut v8::PinScope<'s, '_>, + names: &[String], +) -> v8::Local<'s, v8::Object> { + let mut names = names.to_vec(); + names.sort_by(|left, right| left.encode_utf16().cmp(right.encode_utf16())); + new_idb_dom_string_list(scope, &names) +} + pub(in crate::context_bootstrap::indexed_db) fn install_dom_string_list_template_bindings<'s>( scope: &mut v8::PinScope<'s, '_, ()>, prototype: v8::Local<'s, v8::ObjectTemplate>, 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 40fd91fdad..1c88c290a5 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 @@ -36,7 +36,7 @@ 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 object_store_names = new_idb_dom_string_list(scope, &info.object_store_names); + let object_store_names = new_idb_name_list(scope, &info.object_store_names); let database = IdbDatabaseObjectDeclaration::new(&info.name, info.version as f64, object_store_names) .bind(scope) @@ -55,7 +55,7 @@ pub(in crate::context_bootstrap::indexed_db) fn create_database_object<'s>( database_key.clone(), storage_scope, ); - let _ = refresh_database_surface(scope, database); + let _ = refresh_database_metadata(scope, database, info); register_open_database_connection(scope, owner, handle, database_key, info.version, database); Some(database) } diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/transaction.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/transaction.rs index adcdd584ba..7c8fee271d 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/transaction.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/creation/transaction.rs @@ -36,7 +36,7 @@ pub(in crate::context_bootstrap::indexed_db) fn create_transaction_object<'s>( let handle_raw = handle.map(|handle| handle.into_raw() as f64); let mode: &'static str = mode.into(); let db_key = object_string_property(scope, db, INDEXED_DB_DATABASE_KEY_SLOT); - let object_store_names = new_idb_dom_string_list(scope, store_names); + let object_store_names = new_idb_name_list(scope, store_names); let tx = IdbTransactionObjectDeclaration::new(db, mode, object_store_names) .bind(scope) .ok()?; @@ -50,6 +50,6 @@ pub(in crate::context_bootstrap::indexed_db) fn create_transaction_object<'s>( owner, storage_scope, ); - register_indexed_db_transaction_lifecycle(scope, tx, handle, handle_raw.is_some(), db_key); + register_indexed_db_transaction_lifecycle(scope, tx, db, handle, handle_raw.is_some(), db_key); Some(tx) } diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/database.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/database.rs index 200974b229..f02a5051ef 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/database.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/database.rs @@ -1,4 +1,5 @@ use super::*; +use crate::context_bootstrap::indexed_db::sync_indexed_db_store_handles; pub(in crate::context_bootstrap::indexed_db) fn sync_transaction_object_store_names_from_database< 's, @@ -10,7 +11,7 @@ pub(in crate::context_bootstrap::indexed_db) fn sync_transaction_object_store_na let store_names = object_property_as_object(scope, database, "objectStoreNames") .map(|value| dom_string_list_values(scope, value)) .unwrap_or_default(); - let object_store_names = new_idb_dom_string_list(scope, &store_names); + let object_store_names = new_idb_name_list(scope, &store_names); let _ = transaction.set( scope, v8str(scope, "objectStoreNames").into(), @@ -32,7 +33,7 @@ pub(in crate::context_bootstrap::indexed_db) fn set_database_store_metadata<'s>( if !store_names.iter().any(|name| name == &info.name) { store_names.push(info.name.clone()); } - let object_store_names = new_idb_dom_string_list(scope, &store_names); + let object_store_names = new_idb_name_list(scope, &store_names); let _ = database.set( scope, v8str(scope, "objectStoreNames").into(), @@ -47,11 +48,12 @@ pub(in crate::context_bootstrap::indexed_db) fn remove_database_store_metadata<' store_name: &str, ) -> Option<()> { remove_indexed_db_database_store_metadata(scope, database, store_name)?; + sync_indexed_db_store_handles(scope, database, store_name); let mut store_names = object_property_as_object(scope, database, "objectStoreNames") .map(|value| dom_string_list_values(scope, value)) .unwrap_or_default(); store_names.retain(|name| name != store_name); - let object_store_names = new_idb_dom_string_list(scope, &store_names); + let object_store_names = new_idb_name_list(scope, &store_names); let _ = database.set( scope, v8str(scope, "objectStoreNames").into(), diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/create.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/create.rs index 0a20c0285e..200e4b7f67 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/create.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/create.rs @@ -58,7 +58,7 @@ pub(in crate::context_bootstrap::indexed_db) fn create_object_store_descriptor_o } else { indexes.iter().map(|index| index.name.clone()).collect() }; - let index_names_list = new_idb_dom_string_list(scope, &index_names); + let index_names_list = new_idb_name_list(scope, &index_names); set_indexed_db_internal_object_property( scope, descriptor, diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/sync.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/sync.rs index 5feb1dcafa..a834f8312f 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/sync.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/descriptors/sync.rs @@ -26,7 +26,7 @@ pub(in crate::context_bootstrap::indexed_db) fn sync_store_surface_from_metadata Some(value) => key_path_to_js_value(scope, value)?, None => v8::null(scope).into(), }; - let index_names = new_idb_dom_string_list(scope, &info.index_names); + let index_names = new_idb_name_list(scope, &info.index_names); set_indexed_db_object_store_metadata(scope, store, metadata)?; IdbObjectStoreSurfaceDeclaration::new( &info.name, diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/index.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/index.rs index 7a073080bd..94ebae8bc8 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/index.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/metadata/index.rs @@ -1,4 +1,7 @@ use super::*; +use crate::context_bootstrap::indexed_db::{ + mark_indexed_db_index_handles_deleted, sync_indexed_db_store_handles, +}; pub(in crate::context_bootstrap::indexed_db) fn index_info_from_store_metadata<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -16,7 +19,9 @@ pub(in crate::context_bootstrap::indexed_db) fn set_database_index_metadata<'s>( store_name: &str, info: &IndexInfo, ) -> Option<()> { - set_indexed_db_database_index_metadata(scope, database, store_name, info.clone()) + set_indexed_db_database_index_metadata(scope, database, store_name, info.clone())?; + sync_indexed_db_store_handles(scope, database, store_name); + Some(()) } pub(in crate::context_bootstrap::indexed_db) fn remove_database_index_metadata<'s>( @@ -25,5 +30,8 @@ pub(in crate::context_bootstrap::indexed_db) fn remove_database_index_metadata<' store_name: &str, index_name: &str, ) -> Option<()> { - remove_indexed_db_database_index_metadata(scope, database, store_name, index_name) + remove_indexed_db_database_index_metadata(scope, database, store_name, index_name)?; + mark_indexed_db_index_handles_deleted(scope, database, store_name, index_name); + sync_indexed_db_store_handles(scope, database, store_name); + Some(()) } diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/surface/database.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/surface/database.rs index 11cd7fecb5..edbcb3def6 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/surface/database.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/core/objects/surface/database.rs @@ -23,6 +23,22 @@ pub(in crate::context_bootstrap::indexed_db) fn refresh_database_surface<'s>( return Ok(()); }; let info = with_indexed_db_manager(scope, |manager| manager.database_info(handle))?; + refresh_database_metadata(scope, database, &info)?; + let object_store_names = new_idb_name_list(scope, &info.object_store_names); + IdbDatabaseSurfaceDeclaration::new(&info.name, info.version as f64, object_store_names) + .initialize(scope, database) + .map_err(|error| IndexedDbError::InvalidState(error.to_string()))?; + Ok(()) +} + +pub(in crate::context_bootstrap::indexed_db) fn refresh_database_metadata<'s>( + scope: &mut v8::PinScope<'s, '_>, + database: v8::Local<'s, v8::Object>, + info: &DatabaseInfo, +) -> std::result::Result<(), IndexedDbError> { + let Some(handle) = database_handle_from_value(scope, database.into()) else { + return Ok(()); + }; let mut metadata = Vec::with_capacity(info.object_store_names.len()); for store_name in &info.object_store_names { let store = with_indexed_db_manager(scope, |manager| { @@ -36,11 +52,7 @@ pub(in crate::context_bootstrap::indexed_db) fn refresh_database_surface<'s>( } metadata.push(IndexedDbObjectStoreMetadata::new(store, indexes)); } - let object_store_names = new_idb_dom_string_list(scope, &info.object_store_names); let _ = replace_indexed_db_database_metadata(scope, database, metadata); - IdbDatabaseSurfaceDeclaration::new(&info.name, info.version as f64, object_store_names) - .initialize(scope, database) - .map_err(|error| IndexedDbError::InvalidState(error.to_string()))?; Ok(()) } diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/transactions/lifecycle.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/transactions/lifecycle.rs index 6b1f6a90d1..bcca3e4ead 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/database/transactions/lifecycle.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/database/transactions/lifecycle.rs @@ -1,4 +1,5 @@ use super::*; +use crate::context_bootstrap::indexed_db::restore_indexed_db_upgrade_metadata; use crate::context_bootstrap::indexed_db::schedule_indexed_db_transaction_deactivation_after_microtask_checkpoint; pub(in crate::context_bootstrap::indexed_db) fn finish_transaction_abort<'s>( @@ -6,6 +7,7 @@ pub(in crate::context_bootstrap::indexed_db) fn finish_transaction_abort<'s>( transaction: v8::Local<'s, v8::Object>, error: v8::Local<'s, v8::Value>, ) { + restore_indexed_db_upgrade_metadata(scope, transaction); let _ = transaction.set(scope, v8str(scope, "error").into(), error); set_indexed_db_slot_value( scope, diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores.rs index 2c0906e5d5..53b7534cff 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores.rs @@ -7,9 +7,8 @@ use super::{ create_index_object, create_key_range_object, create_request_object, cursor_direction_to_value, deserialize_js_value, dom_exception_value, enforce_object_store_unique_constraints, enqueue_transaction_operation, execute_object_store_clear_request, - execute_object_store_delete_request, index_info_from_store_metadata, - indexed_db_database_store_metadata, indexed_db_index_info, indexed_db_index_object_store, - indexed_db_object_store_database, indexed_db_object_store_name, + execute_object_store_delete_request, index_info_from_store_metadata, indexed_db_index_info, + indexed_db_index_object_store, indexed_db_object_store_database, indexed_db_object_store_name, indexed_db_object_store_transaction, key_in_range, key_to_js_value, object_bool_property, object_string_property, optional_count_to_value, parse_cursor_direction, parse_cursor_direction_with_context, parse_idb_key, parse_idb_key_path, parse_key_or_range, @@ -17,8 +16,8 @@ use super::{ queue_transaction_request, remove_database_index_metadata, request_error_object, scan_index_entries, scan_object_store_entries, serialize_js_value, set_database_index_metadata, storage_bucket_quota_check_for_object_store, store_request_error, store_request_success, - submit_cursor_open_operation, sync_store_surface_from_metadata, throw_type_error, - transaction_handle_from_value, with_indexed_db_manager, + submit_cursor_open_operation, throw_type_error, transaction_handle_from_value, + with_indexed_db_manager, }; use crate::webidl; diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/common.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/common.rs index 198bc5e2d2..b5c7d55167 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/common.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/common.rs @@ -1,4 +1,5 @@ use super::*; +use crate::context_bootstrap::indexed_db::indexed_db_index_is_deleted; pub(in crate::context_bootstrap::indexed_db) fn create_index_request<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -9,11 +10,22 @@ pub(in crate::context_bootstrap::indexed_db) fn create_index_request<'s>( String, IndexInfo, )> { + if indexed_db_index_is_deleted(scope, index) { + let error = dom_exception_value(scope, "The index has been deleted.", "InvalidStateError"); + scope.throw_exception(error); + return None; + } let store = indexed_db_index_object_store(scope, index)?; let transaction = indexed_db_object_store_transaction(scope, store)?; if !object_bool_property(scope, transaction, INDEXED_DB_TRANSACTION_ACTIVE_SLOT) .unwrap_or(false) { + let error = dom_exception_value( + scope, + "The transaction is not active.", + "TransactionInactiveError", + ); + scope.throw_exception(error); return None; } let request = create_request_object(scope, index.into(), transaction)?; diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/cursor/open.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/cursor/open.rs index 789251ec99..e7efc42b86 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/cursor/open.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/cursor/open.rs @@ -10,12 +10,6 @@ fn idb_index_open_cursor_impl<'s>( let index = args.this(); let Some((request, transaction, store_name, index_info)) = create_index_request(scope, index) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let operation_name = if key_only { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all.rs index f08ec963ca..617d216632 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all.rs @@ -9,12 +9,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_index_get_all_callback<'s>( let index = args.this(); let Some((request, transaction, store_name, index_info)) = create_index_request(scope, index) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let parsed = match parse_collection_request_args(scope, &args, "IDBIndex.getAll") { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all_keys.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all_keys.rs index 639497c0e5..d47bf81062 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all_keys.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/collection/get_all_keys.rs @@ -9,12 +9,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_index_get_all_keys_callback< let index = args.this(); let Some((request, transaction, store_name, index_info)) = create_index_request(scope, index) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let parsed = match parse_collection_request_args(scope, &args, "IDBIndex.getAllKeys") { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/count.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/count.rs index b0487960c4..b8ae82137e 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/count.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/count.rs @@ -19,12 +19,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_index_count_callback<'s>( let index = args.this(); let Some((request, transaction, store_name, index_info)) = create_index_request(scope, index) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let query_value = parsed.query.unwrap_or_else(|| v8::undefined(scope).into()); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get.rs index 1d84b952f3..bb4a1b54f7 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get.rs @@ -18,12 +18,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_index_get_callback<'s>( let index = args.this(); let Some((request, transaction, store_name, index_info)) = create_index_request(scope, index) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let query = match parse_key_or_range(scope, parsed.query) { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get_key.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get_key.rs index dd560b3db1..a1fb86f10f 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get_key.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/index/read/single/get_key.rs @@ -18,12 +18,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_index_get_key_callback<'s>( let index = args.this(); let Some((request, transaction, store_name, index_info)) = create_index_request(scope, index) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let query = match parse_key_or_range(scope, parsed.query) { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/common.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/common.rs index 8ada561ece..6598a76a6b 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/common.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/common.rs @@ -1,13 +1,29 @@ use super::*; +use crate::context_bootstrap::indexed_db::indexed_db_object_store_is_deleted; pub(in crate::context_bootstrap::indexed_db) fn create_store_request<'s>( scope: &mut v8::PinScope<'s, '_>, source: v8::Local<'s, v8::Object>, ) -> Option<(v8::Local<'s, v8::Object>, v8::Local<'s, v8::Object>)> { + if indexed_db_object_store_is_deleted(scope, source) { + let error = dom_exception_value( + scope, + "The object store has been deleted.", + "InvalidStateError", + ); + scope.throw_exception(error); + return None; + } let transaction = indexed_db_object_store_transaction(scope, source)?; if !object_bool_property(scope, transaction, INDEXED_DB_TRANSACTION_ACTIVE_SLOT) .unwrap_or(false) { + let error = dom_exception_value( + scope, + "The transaction is not active.", + "TransactionInactiveError", + ); + scope.throw_exception(error); return None; } let request = create_request_object(scope, source.into(), transaction)?; diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/cursor/open.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/cursor/open.rs index 99c47fb70a..5ab45692c1 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/cursor/open.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/cursor/open.rs @@ -10,12 +10,6 @@ fn idb_object_store_open_cursor_impl<'s>( let store = args.this(); let Some((request, transaction, store_name)) = object_store_operation_common(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let operation_name = if key_only { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all.rs index d72656ad29..a7b079b5f3 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all.rs @@ -9,12 +9,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_get_all_callbac let store = args.this(); let Some((request, transaction, store_name)) = object_store_operation_common(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let parsed = match parse_collection_request_args(scope, &args, "IDBObjectStore.getAll") { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all_keys.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all_keys.rs index 1e0fcb5bdd..fefa629864 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all_keys.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/collection/get_all_keys.rs @@ -9,12 +9,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_get_all_keys_ca let store = args.this(); let Some((request, transaction, store_name)) = object_store_operation_common(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let parsed = match parse_collection_request_args(scope, &args, "IDBObjectStore.getAllKeys") { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/count.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/count.rs index 584651ed42..f7b734a917 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/count.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/count.rs @@ -19,12 +19,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_count_callback< let store = args.this(); let Some((request, transaction, store_name)) = object_store_operation_common(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let query_value = parsed.query.unwrap_or_else(|| v8::undefined(scope).into()); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get.rs index 42802ee890..5de53c29ed 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get.rs @@ -18,12 +18,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_get_callback<'s let store = args.this(); let Some((request, transaction, store_name)) = object_store_operation_common(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let query = match parse_key_or_range(scope, parsed.query) { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get_key.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get_key.rs index 7e8239a157..884df4ed2c 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get_key.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/read/single/get_key.rs @@ -18,12 +18,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_get_key_callbac let store = args.this(); let Some((request, transaction, store_name)) = object_store_operation_common(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let query = match parse_key_or_range(scope, parsed.query) { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/common.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/common.rs index 0f42d568af..ea09d5ca83 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/common.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/common.rs @@ -1,4 +1,5 @@ use super::*; +use crate::context_bootstrap::indexed_db::indexed_db_object_store_is_deleted; pub(in crate::context_bootstrap::indexed_db) fn object_store_versionchange_common<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -9,6 +10,9 @@ pub(in crate::context_bootstrap::indexed_db) fn object_store_versionchange_commo TransactionHandle, String, )> { + if indexed_db_object_store_is_deleted(scope, store) { + return None; + } let transaction = indexed_db_object_store_transaction(scope, store)?; if !object_bool_property(scope, transaction, INDEXED_DB_TRANSACTION_ACTIVE_SLOT) .unwrap_or(false) diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/create_index.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/create_index.rs index 17bc7d0a7a..c501d1ff6b 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/create_index.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/create_index.rs @@ -69,10 +69,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_create_index_ca }) { Ok(info) => { let _ = set_database_index_metadata(scope, database, &store_name, &info); - if let Some(metadata) = indexed_db_database_store_metadata(scope, database, &store_name) - { - let _ = sync_store_surface_from_metadata(scope, store, metadata); - } if let Some(index) = create_index_object(scope, store, &info) { rv.set(index.into()); } else { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/delete_index.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/delete_index.rs index 2a8b614648..09b363f68e 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/delete_index.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/delete_index.rs @@ -34,10 +34,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_delete_index_ca }) { Ok(()) => { let _ = remove_database_index_metadata(scope, database, &store_name, &index_name); - if let Some(metadata) = indexed_db_database_store_metadata(scope, database, &store_name) - { - let _ = sync_store_surface_from_metadata(scope, store, metadata); - } rv.set_undefined(); } Err(error) => { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/lookup.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/lookup.rs index c5bf17f7f9..766751905f 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/lookup.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/schema/lookup.rs @@ -1,4 +1,7 @@ use super::*; +use crate::context_bootstrap::indexed_db::{ + INDEXED_DB_TRANSACTION_FINISHED_SLOT, indexed_db_object_store_is_deleted, +}; use crate::webidl; #[derive(webidl::WebIdlArgs)] @@ -18,6 +21,19 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_index_callback< }; let index_name = parsed.index_name; let store = args.this(); + let finished = indexed_db_object_store_transaction(scope, store).is_none_or(|transaction| { + object_bool_property(scope, transaction, INDEXED_DB_TRANSACTION_FINISHED_SLOT) + .unwrap_or(false) + }); + if indexed_db_object_store_is_deleted(scope, store) || finished { + let error = dom_exception_value( + scope, + "The object store has been deleted or its transaction has finished.", + "InvalidStateError", + ); + scope.throw_exception(error); + return; + } let Some(info) = index_info_from_store_metadata(scope, store, &index_name) else { let error = dom_exception_value(scope, "The requested index was not found.", "NotFoundError"); diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/delete_clear.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/delete_clear.rs index aca4f459ef..37001ea187 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/delete_clear.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/delete_clear.rs @@ -17,12 +17,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_delete_callback }; let store = args.this(); let Some((request, transaction)) = create_store_request(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let Some(store_name) = indexed_db_object_store_name(scope, store) else { @@ -70,12 +64,6 @@ pub(in crate::context_bootstrap::indexed_db) fn idb_object_store_clear_callback< ) { let store = args.this(); let Some((request, transaction)) = create_store_request(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let Some(store_name) = indexed_db_object_store_name(scope, store) else { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/put_add/operation.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/put_add/operation.rs index cb1482d227..681c043295 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/put_add/operation.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/stores/object_store/write/put_add/operation.rs @@ -13,12 +13,6 @@ pub(super) fn object_store_write_callback<'s>( ) { let store = args.this(); let Some((request, transaction)) = create_store_request(scope, store) else { - let error = dom_exception_value( - scope, - "The transaction is not active.", - "TransactionInactiveError", - ); - scope.throw_exception(error); return; }; let Some(store_name) = indexed_db_object_store_name(scope, store) else { diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open.rs index fddeabf151..8a5ffcb252 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open.rs @@ -1,8 +1,10 @@ use super::*; +use crate::context_bootstrap::indexed_db::defer_indexed_db_aborted_open; mod abort; mod commit; mod success; +pub(in crate::context_bootstrap::indexed_db::tasks::dispatch) use abort::finish_aborted_upgrade_open; pub(in crate::context_bootstrap::indexed_db) fn flush_open_task<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -40,7 +42,7 @@ pub(in crate::context_bootstrap::indexed_db) fn flush_open_task<'s>( if object_bool_property(scope, transaction, INDEXED_DB_TRANSACTION_ABORTED_SLOT) .unwrap_or(false) { - abort::finish_aborted_upgrade_open(scope, request); + defer_indexed_db_aborted_open(scope, transaction, request); return; } diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/abort.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/abort.rs index b0aa9d9101..873abb719a 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/abort.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/abort.rs @@ -1,6 +1,6 @@ use super::*; -pub(super) fn finish_aborted_upgrade_open<'s>( +pub(in crate::context_bootstrap::indexed_db::tasks::dispatch) fn finish_aborted_upgrade_open<'s>( scope: &mut v8::PinScope<'s, '_>, request: v8::Local<'s, v8::Object>, ) { @@ -37,6 +37,12 @@ pub(super) fn finish_aborted_upgrade_open<'s>( "result", undefined, ); - let _ = dispatch_idb_named_event(scope, request, "error", |_, _| {}); - release_request_dispatch_refs(scope, request); + set_indexed_db_request_surface_value( + scope, + request, + INDEXED_DB_REQUEST_TRANSACTION_SLOT, + "transaction", + v8::null(scope).into(), + ); + enqueue_request_task(scope, "request-error", request); } diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/commit.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/commit.rs index 704a0758a2..efe09524ad 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/commit.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/open/commit.rs @@ -1,4 +1,5 @@ use super::*; +use crate::context_bootstrap::indexed_db::restore_indexed_db_upgrade_metadata; pub(super) fn commit_upgrade_transaction<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -58,6 +59,7 @@ fn finish_failed_upgrade_commit<'s>( transaction: v8::Local<'s, v8::Object>, error: IndexedDbError, ) -> bool { + restore_indexed_db_upgrade_metadata(scope, transaction); let error_value = request_error_object(scope, &error); let _ = transaction.set(scope, v8str(scope, "error").into(), error_value); set_indexed_db_request_surface_value( diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/transaction/abort.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/transaction/abort.rs index c2f925ffcf..19d9b5b815 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/transaction/abort.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/dispatch/transaction/abort.rs @@ -1,4 +1,6 @@ +use super::super::open::finish_aborted_upgrade_open; use super::*; +use crate::context_bootstrap::indexed_db::take_indexed_db_aborted_open; pub(in crate::context_bootstrap::indexed_db) fn flush_transaction_abort_task<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -7,6 +9,18 @@ pub(in crate::context_bootstrap::indexed_db) fn flush_transaction_abort_task<'s> let Some(transaction) = indexed_db_transaction_task_transaction(scope, task) else { return; }; + let aborted_open = take_indexed_db_aborted_open(scope, transaction); + if let Some((_, database)) = aborted_open { + set_indexed_db_slot_value( + scope, + database, + INDEXED_DB_DATABASE_UPGRADE_TRANSACTION_SLOT, + v8::null(scope).into(), + ); + } let _ = dispatch_idb_named_event(scope, transaction, "abort", |_, _| {}); release_indexed_db_transaction_dispatch_refs(scope, transaction); + if let Some((request, _)) = aborted_open { + finish_aborted_upgrade_open(scope, request); + } } 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 dff0f10f59..607b0a6dce 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 @@ -6,7 +6,12 @@ pub(super) fn opened_database_info( opened: &moli_indexeddb::OpenResult, ) -> Result { match with_indexed_db_manager(scope, |manager| manager.database_info(opened.database)) { - Ok(info) => Ok(info), + Ok(mut info) => { + if let OpenDisposition::UpgradeNeeded { new_version, .. } = opened.disposition { + info.version = new_version; + } + Ok(info) + } Err(error) => match &opened.disposition { OpenDisposition::UpgradeNeeded { old_version: 0, diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/upgrade.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/upgrade.rs index 3498a1d2ba..736b1dd38f 100644 --- a/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/upgrade.rs +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/tasks/open_delete/open/upgrade.rs @@ -1,4 +1,5 @@ use super::*; +use crate::context_bootstrap::indexed_db::save_indexed_db_upgrade_metadata; pub(super) fn enqueue_upgrade_needed_open_task<'s>( scope: &mut v8::PinScope<'s, '_>, @@ -34,6 +35,7 @@ pub(super) fn enqueue_upgrade_needed_open_task<'s>( INDEXED_DB_DATABASE_UPGRADE_TRANSACTION_SLOT, transaction.into(), ); + save_indexed_db_upgrade_metadata(scope, database, old_version); enqueue_open_task( scope, request, 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 c50655119b..9c4d41573e 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 @@ -1,6 +1,13 @@ use super::*; use moli_storage_service::StorageBucketIdentity; -use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; +use std::{ + cell::RefCell, + collections::{BTreeMap, BTreeSet}, + rc::Rc, +}; + +mod upgrade; +pub(super) use upgrade::*; #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] pub(super) struct IndexedDbObjectId(u64); @@ -352,6 +359,8 @@ impl IndexedDbRequestLifecycleState { } struct IndexedDbTransactionLifecycleState { + database: Option>, + aborted_open_request: Option>, handle: Option, active: bool, finished: bool, @@ -367,8 +376,15 @@ struct IndexedDbTransactionLifecycleState { } impl IndexedDbTransactionLifecycleState { - fn new(handle: Option, started: bool, db_key: Option) -> Self { + fn new( + database: v8::Global, + handle: Option, + started: bool, + db_key: Option, + ) -> Self { Self { + database: Some(database), + aborted_open_request: None, handle, active: true, finished: false, @@ -389,6 +405,8 @@ impl IndexedDbTransactionLifecycleState { pub(super) struct IndexedDbObjectStoreMetadata { info: ObjectStoreInfo, indexes: BTreeMap, + created_in_upgrade: bool, + created_indexes: BTreeSet, } impl IndexedDbObjectStoreMetadata { @@ -397,7 +415,12 @@ impl IndexedDbObjectStoreMetadata { .into_iter() .map(|index| (index.name.clone(), index)) .collect(); - Self { info, indexes } + Self { + info, + indexes, + created_in_upgrade: false, + created_indexes: BTreeSet::new(), + } } pub(super) fn info(&self) -> &ObjectStoreInfo { @@ -417,6 +440,7 @@ impl IndexedDbObjectStoreMetadata { } fn set_index(&mut self, info: IndexInfo) { + self.created_indexes.insert(info.name.clone()); if !self.info.index_names.iter().any(|name| name == &info.name) { self.info.index_names.push(info.name.clone()); } @@ -436,6 +460,7 @@ struct IndexedDbDatabaseLifecycleState { closed: bool, metadata: BTreeMap, upgrade_transaction: Option>, + upgrade_metadata: Option, } impl IndexedDbDatabaseLifecycleState { @@ -451,6 +476,7 @@ impl IndexedDbDatabaseLifecycleState { closed: false, metadata: BTreeMap::new(), upgrade_transaction: None, + upgrade_metadata: None, } } } @@ -482,25 +508,30 @@ impl IndexedDbCursorLifecycleState { } struct IndexedDbObjectStoreLifecycleState { + wrapper: v8::Weak, transaction: v8::Global, database: v8::Global, name: String, metadata: IndexedDbObjectStoreMetadata, + deleted: bool, } impl IndexedDbObjectStoreLifecycleState { fn new( scope: &mut v8::PinScope<'_, '_>, + wrapper: v8::Local<'_, v8::Object>, transaction: v8::Local<'_, v8::Object>, database: v8::Local<'_, v8::Object>, metadata: IndexedDbObjectStoreMetadata, ) -> Self { let name = metadata.info.name.clone(); Self { + wrapper: v8::Weak::new(scope, wrapper), transaction: v8::Global::new(scope, v8::Local::::from(transaction)), database: v8::Global::new(scope, v8::Local::::from(database)), name, metadata, + deleted: false, } } } @@ -509,6 +540,8 @@ struct IndexedDbIndexLifecycleState { object_store: v8::Global, info: IndexInfo, marker: bool, + deleted: bool, + created_in_upgrade: bool, } impl IndexedDbIndexLifecycleState { @@ -516,11 +549,14 @@ impl IndexedDbIndexLifecycleState { scope: &mut v8::PinScope<'_, '_>, object_store: v8::Local<'_, v8::Object>, info: IndexInfo, + created_in_upgrade: bool, ) -> Self { Self { object_store: v8::Global::new(scope, v8::Local::::from(object_store)), info, marker: true, + deleted: false, + created_in_upgrade, } } } @@ -685,6 +721,7 @@ pub(super) fn release_indexed_db_request_dispatch_refs<'s>( pub(super) fn register_indexed_db_transaction_lifecycle<'s>( scope: &mut v8::PinScope<'s, '_>, transaction: v8::Local<'s, v8::Object>, + database: v8::Local<'s, v8::Object>, handle: Option, started: bool, db_key: Option, @@ -692,7 +729,12 @@ pub(super) fn register_indexed_db_transaction_lifecycle<'s>( let Some(id) = indexed_db_typed_state_id(scope, transaction) else { return; }; - let state = IndexedDbTransactionLifecycleState::new(handle, started, db_key); + let state = IndexedDbTransactionLifecycleState::new( + v8::Global::new(scope, database), + handle, + started, + db_key, + ); let table = indexed_db_runtime_state_table_for_object(scope, transaction); table.borrow_mut().transactions.insert(id, state); } @@ -760,6 +802,7 @@ pub(super) fn release_indexed_db_transaction_dispatch_refs<'s>( let Some(transaction) = table.transactions.get_mut(&id) else { return; }; + transaction.database = None; transaction.operations_waiting_for_start.clear(); } @@ -838,7 +881,8 @@ pub(super) fn register_indexed_db_object_store_lifecycle<'s>( let Some(id) = indexed_db_typed_state_id(scope, store) else { return; }; - let state = IndexedDbObjectStoreLifecycleState::new(scope, transaction, database, metadata); + let state = + IndexedDbObjectStoreLifecycleState::new(scope, store, transaction, database, metadata); let table = indexed_db_runtime_state_table_for_object(scope, store); table.borrow_mut().object_stores.insert(id, state); } @@ -852,10 +896,14 @@ pub(super) fn register_indexed_db_index_lifecycle<'s>( let Some(id) = indexed_db_typed_state_id(scope, index) else { return; }; + let created_in_upgrade = + indexed_db_object_store_metadata(scope, object_store).is_some_and(|metadata| { + metadata.created_in_upgrade || metadata.created_indexes.contains(&info.name) + }); let table = indexed_db_runtime_state_table_for_object(scope, index); table.borrow_mut().indexes.insert( id, - IndexedDbIndexLifecycleState::new(scope, object_store, info), + IndexedDbIndexLifecycleState::new(scope, object_store, info, created_in_upgrade), ); } @@ -1268,12 +1316,13 @@ pub(super) fn indexed_db_database_store_metadata<'s>( pub(super) fn set_indexed_db_database_store_metadata<'s>( scope: &mut v8::PinScope<'s, '_>, database: v8::Local<'s, v8::Object>, - metadata: IndexedDbObjectStoreMetadata, + mut metadata: IndexedDbObjectStoreMetadata, ) -> Option<()> { let id = indexed_db_typed_state_id(scope, database)?; let table = indexed_db_runtime_state_table_for_object(scope, database); let mut table = table.borrow_mut(); let database = table.databases.get_mut(&id)?; + metadata.created_in_upgrade = database.upgrade_metadata.is_some(); database .metadata .insert(metadata.info.name.clone(), metadata); @@ -1826,6 +1875,7 @@ fn set_indexed_db_typed_database_slot_value( INDEXED_DB_DATABASE_METADATA_SLOT => true, INDEXED_DB_DATABASE_UPGRADE_TRANSACTION_SLOT => { database.upgrade_transaction = if value.is_null_or_undefined() { + database.upgrade_metadata = None; None } else { Some(v8::Global::new(scope, value)) diff --git a/moli-renderer-v8/src/context_bootstrap/indexed_db/typed_state/upgrade.rs b/moli-renderer-v8/src/context_bootstrap/indexed_db/typed_state/upgrade.rs new file mode 100644 index 0000000000..89c32d1d7f --- /dev/null +++ b/moli-renderer-v8/src/context_bootstrap/indexed_db/typed_state/upgrade.rs @@ -0,0 +1,261 @@ +use super::*; + +pub(super) struct IndexedDbUpgradeMetadata { + version: u64, + stores: BTreeMap, +} + +pub(in crate::context_bootstrap::indexed_db) fn defer_indexed_db_aborted_open<'s>( + scope: &mut v8::PinScope<'s, '_>, + transaction: v8::Local<'s, v8::Object>, + request: v8::Local<'s, v8::Object>, +) { + let Some(id) = indexed_db_typed_state_id(scope, transaction) else { + return; + }; + let table = indexed_db_runtime_state_table_for_object(scope, transaction); + if let Some(state) = table.borrow_mut().transactions.get_mut(&id) { + state.aborted_open_request = Some(v8::Global::new(scope, request)); + } +} + +pub(in crate::context_bootstrap::indexed_db) fn take_indexed_db_aborted_open<'s>( + scope: &mut v8::PinScope<'s, '_>, + transaction: v8::Local<'s, v8::Object>, +) -> Option<(v8::Local<'s, v8::Object>, v8::Local<'s, v8::Object>)> { + let id = indexed_db_typed_state_id(scope, transaction)?; + let table = indexed_db_runtime_state_table_for_object(scope, transaction); + let mut table = table.borrow_mut(); + let state = table.transactions.get_mut(&id)?; + let request = state.aborted_open_request.take()?; + Some(( + v8::Local::new(scope, &request), + v8::Local::new(scope, state.database.as_ref()?), + )) +} + +pub(in crate::context_bootstrap::indexed_db) fn save_indexed_db_upgrade_metadata( + scope: &mut v8::PinScope<'_, '_>, + database: v8::Local<'_, v8::Object>, + old_version: u64, +) { + let Some(id) = indexed_db_typed_state_id(scope, database) else { + return; + }; + let table = indexed_db_runtime_state_table_for_object(scope, database); + let mut table = table.borrow_mut(); + let Some(database) = table.databases.get_mut(&id) else { + return; + }; + database.upgrade_metadata = Some(IndexedDbUpgradeMetadata { + version: old_version, + stores: database.metadata.clone(), + }); +} + +pub(in crate::context_bootstrap::indexed_db) fn restore_indexed_db_upgrade_metadata<'s>( + scope: &mut v8::PinScope<'s, '_>, + transaction: v8::Local<'s, v8::Object>, +) { + let Some(transaction_id) = indexed_db_typed_state_id(scope, transaction) else { + return; + }; + let table = indexed_db_runtime_state_table_for_object(scope, transaction); + let database = { + let table = table.borrow(); + let Some(state) = table.transactions.get(&transaction_id) else { + return; + }; + let Some(database) = state.database.as_ref() else { + return; + }; + v8::Local::new(scope, database) + }; + let Some(id) = indexed_db_typed_state_id(scope, database) else { + return; + }; + let table = indexed_db_runtime_state_table_for_object(scope, database); + let (version, names, stores) = { + let mut table = table.borrow_mut(); + let Some(state) = table.databases.get_mut(&id) else { + return; + }; + if !state + .upgrade_transaction + .as_ref() + .is_some_and(|value| v8::Local::new(scope, value) == transaction) + { + return; + } + let Some(snapshot) = state.upgrade_metadata.take() else { + return; + }; + state.metadata = snapshot.stores.clone(); + let names = snapshot.stores.keys().cloned().collect::>(); + let mut stores = Vec::new(); + let mut store_ids = BTreeSet::new(); + for (id, store) in &mut table.object_stores { + if v8::Local::new(scope, &store.transaction) != transaction { + continue; + } + store_ids.insert(*id); + // A replacement with the same name is a different object store. + // Only handles for pre-upgrade stores are restored from the snapshot. + store.deleted = store.metadata.created_in_upgrade; + let metadata = if store.deleted { + let mut metadata = store.metadata.clone(); + metadata.info.index_names.clear(); + metadata.indexes.clear(); + metadata + } else { + snapshot + .stores + .get(&store.name) + .cloned() + .expect("existing store has upgrade snapshot") + }; + store.metadata = metadata.clone(); + if let Some(wrapper) = store.wrapper.to_local(scope) { + stores.push((wrapper, metadata)); + } + } + for index in table.indexes.values_mut() { + let store = + v8::Local::::try_from(v8::Local::new(scope, &index.object_store)) + .expect("index retains its object store"); + if indexed_db_typed_state_id(scope, store).is_some_and(|id| store_ids.contains(&id)) { + index.deleted = index.created_in_upgrade; + } + } + (snapshot.version, names, stores) + }; + let version = v8::Number::new(scope, version as f64); + let _ = database.define_own_property( + scope, + v8str(scope, "version").into(), + version.into(), + v8::PropertyAttribute::NONE, + ); + let database_names = new_idb_name_list(scope, &names); + let transaction_names = new_idb_name_list(scope, &names); + let _ = database.define_own_property( + scope, + v8str(scope, "objectStoreNames").into(), + database_names.into(), + v8::PropertyAttribute::NONE, + ); + let _ = transaction.define_own_property( + scope, + v8str(scope, "objectStoreNames").into(), + transaction_names.into(), + v8::PropertyAttribute::NONE, + ); + for (store, metadata) in stores { + let _ = sync_store_surface_from_metadata(scope, store, metadata); + } +} + +// Update every handle for this store, including separately obtained wrappers. +// V8 surface writes happen after releasing the runtime table's borrow. +pub(in crate::context_bootstrap::indexed_db) fn sync_indexed_db_store_handles<'s>( + scope: &mut v8::PinScope<'s, '_>, + database: v8::Local<'s, v8::Object>, + store_name: &str, +) { + let metadata = indexed_db_database_store_metadata(scope, database, store_name); + let table = indexed_db_runtime_state_table_for_object(scope, database); + let stores = { + let mut table = table.borrow_mut(); + let mut stores = Vec::new(); + for store in table.object_stores.values_mut() { + if store.deleted + || store.name != store_name + || v8::Local::new(scope, &store.database) != database + { + continue; + } + let metadata = match &metadata { + Some(metadata) => metadata.clone(), + None => { + store.deleted = true; + let mut metadata = store.metadata.clone(); + metadata.info.index_names.clear(); + metadata.indexes.clear(); + metadata + } + }; + store.metadata = metadata.clone(); + if let Some(wrapper) = store.wrapper.to_local(scope) { + stores.push((wrapper, metadata)); + } + } + stores + }; + for (store, metadata) in stores { + let _ = sync_store_surface_from_metadata(scope, store, metadata); + } +} + +pub(in crate::context_bootstrap::indexed_db) fn mark_indexed_db_index_handles_deleted( + scope: &mut v8::PinScope<'_, '_>, + database: v8::Local<'_, v8::Object>, + store_name: &str, + index_name: &str, +) { + let table = indexed_db_runtime_state_table_for_object(scope, database); + let mut table = table.borrow_mut(); + let store_ids = table + .object_stores + .iter() + .filter_map(|(id, store)| { + (!store.deleted + && store.name == store_name + && v8::Local::new(scope, &store.database) == database) + .then_some(*id) + }) + .collect::>(); + for index in table.indexes.values_mut() { + let store = v8::Local::::try_from(v8::Local::new(scope, &index.object_store)) + .expect("index retains its object store"); + if index.info.name == index_name + && indexed_db_typed_state_id(scope, store).is_some_and(|id| store_ids.contains(&id)) + { + index.deleted = true; + } + } +} + +pub(in crate::context_bootstrap::indexed_db) fn indexed_db_object_store_is_deleted( + scope: &mut v8::PinScope<'_, '_>, + store: v8::Local<'_, v8::Object>, +) -> bool { + let Some(id) = indexed_db_typed_state_id(scope, store) else { + return true; + }; + let table = indexed_db_runtime_state_table_for_object(scope, store); + table + .borrow() + .object_stores + .get(&id) + .is_none_or(|store| store.deleted) +} + +pub(in crate::context_bootstrap::indexed_db) fn indexed_db_index_is_deleted( + scope: &mut v8::PinScope<'_, '_>, + index: v8::Local<'_, v8::Object>, +) -> bool { + let Some(id) = indexed_db_typed_state_id(scope, index) else { + return true; + }; + let table = indexed_db_runtime_state_table_for_object(scope, index); + let table = table.borrow(); + let Some(index) = table.indexes.get(&id) else { + return true; + }; + let store = v8::Local::::try_from(v8::Local::new(scope, &index.object_store)) + .expect("index retains its object store"); + index.deleted + || indexed_db_typed_state_id(scope, store) + .and_then(|id| table.object_stores.get(&id)) + .is_none_or(|store| store.deleted) +} diff --git a/moli-renderer-v8/src/script_vm/tests/indexed_db.rs b/moli-renderer-v8/src/script_vm/tests/indexed_db.rs index 385af58287..30d2888ef1 100644 --- a/moli-renderer-v8/src/script_vm/tests/indexed_db.rs +++ b/moli-renderer-v8/src/script_vm/tests/indexed_db.rs @@ -5465,6 +5465,177 @@ fn indexed_db_aborted_upgrade_closes_provisional_connection_before_reopen_and_de assert_eq!(result, "AbortError|1|records|deleted"); } +#[test] +fn indexed_db_initial_upgrade_abort_restores_metadata_synchronously() { + let mut vm = new_storage_page_task_executor_test_vm("https://indexeddb-initial-rollback.test/"); + vm.eval(r#" +globalThis.rollback = []; +const open = indexedDB.open('initial-rollback', 7); +let db, tx, store, index; +function snapshot() { + return [db.version, Array.from(db.objectStoreNames), + Array.from(tx.objectStoreNames), Array.from(store.indexNames)]; +} +open.onupgradeneeded = () => { + db = open.result; + tx = open.transaction; + store = db.createObjectStore('records'); + index = store.createIndex('by-value', 'value'); + for (const name of ['\uE000', '\u{10000}', 'z', 'a']) { + db.createObjectStore(name); + store.createIndex(name, 'value'); + } + rollback.push(snapshot()); + tx.onabort = () => rollback.push(['abort', open.transaction === tx, snapshot()]); + Object.defineProperty(tx, 'db', {get() { throw new Error('author db getter'); }}); + tx.abort(); + rollback.push(snapshot()); + for (const source of [store, index]) { + for (const method of ['get', 'getKey', 'getAll', 'getAllKeys', 'count', 'openCursor', 'openKeyCursor']) { + try { source[method](1); rollback.push(method + ':accepted'); } + catch (error) { rollback.push(error.name); } + } + } + for (const method of ['add', 'put', 'delete', 'clear']) { + try { store[method](1, 1); rollback.push(method + ':accepted'); } + catch (error) { rollback.push(error.name); } + } +}; +open.onerror = () => rollback.push(['error', open.error.name, open.result === undefined, + open.transaction === null, snapshot()]); +"#).expect("initial upgrade rollback should schedule"); + let result = vm + .eval_after_selected_page_tasks("JSON.stringify(rollback)") + .expect("initial upgrade rollback should run"); + let result: serde_json::Value = serde_json::from_str(&result).unwrap(); + let entries = result.as_array().unwrap(); + assert_eq!( + entries[0], + serde_json::json!([ + 7, + ["a", "records", "z", "\u{10000}", "\u{e000}"], + ["a", "records", "z", "\u{10000}", "\u{e000}"], + ["a", "by-value", "z", "\u{10000}", "\u{e000}"] + ]) + ); + assert_eq!(entries[1], serde_json::json!([0, [], [], []])); + assert_eq!( + &entries[2..20], + vec![serde_json::json!("InvalidStateError"); 18] + ); + assert!(entries.contains(&serde_json::json!([ + "error", + "AbortError", + true, + true, + [0, [], [], []] + ]))); + assert_eq!( + entries[20], + serde_json::json!(["abort", true, [0, [], [], []]]) + ); + assert_eq!(entries[21][0], "error"); + assert_eq!(entries.len(), 22); +} + +#[test] +fn indexed_db_upgrade_abort_restores_existing_handles_without_reviving_replacements() { + let mut vm = new_storage_page_task_executor_test_vm("https://indexeddb-upgrade-rollback.test/"); + vm.eval(r#" +globalThis.rollback = []; +const initial = indexedDB.open('upgrade-rollback', 9); +initial.onupgradeneeded = () => { + const store = initial.result.createObjectStore('records'); + store.createIndex('by-value', 'value'); + store.put({value: 'original'}, 1); +}; +function failure(operation) { + try { operation(); return 'accepted'; } catch (error) { return error.name; } +} +initial.onsuccess = () => { + initial.result.close(); + const open = indexedDB.open('upgrade-rollback', 10); + let db, tx, store, alias, index, replacement, replacementIndex; + function snapshot() { + return [db.version, Array.from(db.objectStoreNames), Array.from(tx.objectStoreNames), + Array.from(store.indexNames), Array.from(alias.indexNames), Array.from(replacement.indexNames), + failure(() => store.get(1)), failure(() => index.get(1)), + failure(() => replacement.get(1)), failure(() => replacementIndex.get(1))]; + } + open.onupgradeneeded = () => { + db = open.result; + tx = open.transaction; + store = tx.objectStore('records'); + alias = tx.objectStore('records'); + index = store.index('by-value'); + store.put({value: 'changed'}, 1); + store.createIndex('aa-transient', 'value'); + rollback.push([db.version, Array.from(alias.indexNames)]); + store.deleteIndex('by-value'); + const newIndex = alias.createIndex('by-value', 'other'); + rollback.push(failure(() => index.get(1))); + db.deleteObjectStore('records'); + rollback.push([Array.from(store.indexNames), Array.from(alias.indexNames), failure(() => store.get(1))]); + replacement = db.createObjectStore('records', {keyPath: 'id', autoIncrement: true}); + replacementIndex = replacement.createIndex('by-value', 'other'); + db.createObjectStore('temporary'); + tx.abort(); + rollback.push(snapshot()); + rollback.push(failure(() => newIndex.get(1))); + tx.onabort = () => rollback.push(['abort', snapshot()]); + }; + open.onerror = () => { + rollback.push(['error', snapshot()]); + const reopen = indexedDB.open('upgrade-rollback'); + reopen.onsuccess = () => { + const reopened = reopen.result; + const st = reopened.transaction('records').objectStore('records'); + rollback.push([reopened.version, Array.from(reopened.objectStoreNames), st.keyPath, + st.autoIncrement, Array.from(st.indexNames)]); + const get = st.get(1); + get.onsuccess = () => { rollback.push(get.result); reopened.close(); }; + }; + }; +}; +"#).expect("upgrade rollback should schedule"); + let result = vm + .eval_after_selected_page_tasks("JSON.stringify(rollback)") + .expect("upgrade rollback should run"); + let result: serde_json::Value = serde_json::from_str(&result).unwrap(); + let entries = result.as_array().unwrap(); + let restored = serde_json::json!([ + 9, + ["records"], + ["records"], + ["by-value"], + ["by-value"], + [], + "TransactionInactiveError", + "TransactionInactiveError", + "InvalidStateError", + "InvalidStateError" + ]); + assert_eq!( + entries[0], + serde_json::json!([10, ["aa-transient", "by-value"]]) + ); + assert_eq!(entries[1], "InvalidStateError"); + assert_eq!(entries[2], serde_json::json!([[], [], "InvalidStateError"])); + assert_eq!(entries[3], restored); + assert_eq!(entries[4], "InvalidStateError"); + assert!(entries.contains(&serde_json::json!(["error", restored]))); + assert!(entries.contains(&serde_json::json!(["abort", restored]))); + assert!(entries.contains(&serde_json::json!([ + 9, + ["records"], + null, + false, + ["by-value"] + ]))); + assert!(entries.contains(&serde_json::json!({"value": "original"}))); + assert_eq!(entries.len(), 9); +} + #[test] fn indexed_db_upgrade_open_dispatches_versionchange_and_blocked_until_close() { let mut vm = new_storage_page_task_executor_test_vm("https://indexeddb-open-blocked.test/");