mirror of
https://github.com/lexmount/moli.git
synced 2026-10-09 00:01:06 +00:00
fix(indexeddb): restore upgrade metadata and handle state on abort
Expose the requested version on provisional connections, snapshot the prior schema, and synchronously restore database/transaction/store metadata on abort. Track created and deleted store/index handles so replacements with the same name are not revived. Use native transaction ownership during rollback. Keep the open request transaction through its abort event, then queue the open failure. Share deletion checks across store/index requests and sort IDB name lists by UTF-16 code units. Cover initial and existing upgrades, schema/data rollback, retained handles, same-name replacements, author property getters, and abort event ordering.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -36,7 +36,7 @@ pub(in crate::context_bootstrap::indexed_db) fn create_database_object<'s>(
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
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)
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
-6
@@ -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") {
|
||||
|
||||
-6
@@ -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") {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
-6
@@ -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") {
|
||||
|
||||
-6
@@ -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") {
|
||||
|
||||
@@ -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());
|
||||
|
||||
-6
@@ -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) {
|
||||
|
||||
-6
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
-4
@@ -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 {
|
||||
|
||||
-4
@@ -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) => {
|
||||
|
||||
+16
@@ -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");
|
||||
|
||||
-12
@@ -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 {
|
||||
|
||||
-6
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,12 @@ pub(super) fn opened_database_info(
|
||||
opened: &moli_indexeddb::OpenResult,
|
||||
) -> Result<DatabaseInfo, moli_indexeddb::IndexedDbError> {
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<v8::Global<v8::Object>>,
|
||||
aborted_open_request: Option<v8::Global<v8::Object>>,
|
||||
handle: Option<TransactionHandle>,
|
||||
active: bool,
|
||||
finished: bool,
|
||||
@@ -367,8 +376,15 @@ struct IndexedDbTransactionLifecycleState {
|
||||
}
|
||||
|
||||
impl IndexedDbTransactionLifecycleState {
|
||||
fn new(handle: Option<TransactionHandle>, started: bool, db_key: Option<String>) -> Self {
|
||||
fn new(
|
||||
database: v8::Global<v8::Object>,
|
||||
handle: Option<TransactionHandle>,
|
||||
started: bool,
|
||||
db_key: Option<String>,
|
||||
) -> 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<String, IndexInfo>,
|
||||
created_in_upgrade: bool,
|
||||
created_indexes: BTreeSet<String>,
|
||||
}
|
||||
|
||||
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<String, IndexedDbObjectStoreMetadata>,
|
||||
upgrade_transaction: Option<v8::Global<v8::Value>>,
|
||||
upgrade_metadata: Option<IndexedDbUpgradeMetadata>,
|
||||
}
|
||||
|
||||
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<v8::Object>,
|
||||
transaction: v8::Global<v8::Value>,
|
||||
database: v8::Global<v8::Value>,
|
||||
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::<v8::Value>::from(transaction)),
|
||||
database: v8::Global::new(scope, v8::Local::<v8::Value>::from(database)),
|
||||
name,
|
||||
metadata,
|
||||
deleted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,6 +540,8 @@ struct IndexedDbIndexLifecycleState {
|
||||
object_store: v8::Global<v8::Value>,
|
||||
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::<v8::Value>::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<TransactionHandle>,
|
||||
started: bool,
|
||||
db_key: Option<String>,
|
||||
@@ -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))
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) struct IndexedDbUpgradeMetadata {
|
||||
version: u64,
|
||||
stores: BTreeMap<String, IndexedDbObjectStoreMetadata>,
|
||||
}
|
||||
|
||||
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::<Vec<_>>();
|
||||
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::<v8::Object>::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::<BTreeSet<_>>();
|
||||
for index in table.indexes.values_mut() {
|
||||
let store = v8::Local::<v8::Object>::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::<v8::Object>::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)
|
||||
}
|
||||
@@ -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/");
|
||||
|
||||
Reference in New Issue
Block a user