mirror of
https://github.com/servo/servo.git
synced 2025-06-21 07:38:59 +01:00
Add initial IndexedDB Support
Signed-off-by: Ashwin Naren <arihant2math@gmail.com> Co-authored-by: Rasmus Viitanen <rasviitanen@gmail.com> Signed-off-by: Ashwin Naren <arihant2math@gmail.com>
This commit is contained in:
parent
cdff75bbd4
commit
0a79918849
36 changed files with 3108 additions and 9 deletions
|
@ -86,6 +86,10 @@ pub(crate) fn throw_dom_exception(
|
|||
},
|
||||
None => DOMErrorName::DataCloneError,
|
||||
},
|
||||
Error::Data => DOMErrorName::DataError,
|
||||
Error::TransactionInactive => DOMErrorName::TransactionInactiveError,
|
||||
Error::ReadOnly => DOMErrorName::ReadOnlyError,
|
||||
Error::Version => DOMErrorName::VersionError,
|
||||
Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError,
|
||||
Error::QuotaExceeded => DOMErrorName::QuotaExceededError,
|
||||
Error::TypeMismatch => DOMErrorName::TypeMismatchError,
|
||||
|
@ -303,4 +307,4 @@ impl ErrorToJsval for Error {
|
|||
JS_ClearPendingException(*cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,6 +49,10 @@ pub(crate) enum DOMErrorName {
|
|||
TimeoutError = DOMExceptionConstants::TIMEOUT_ERR,
|
||||
InvalidNodeTypeError = DOMExceptionConstants::INVALID_NODE_TYPE_ERR,
|
||||
DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR,
|
||||
DataError,
|
||||
TransactionInactiveError,
|
||||
ReadOnlyError,
|
||||
VersionError,
|
||||
EncodingError,
|
||||
NotReadableError,
|
||||
DataError,
|
||||
|
@ -81,6 +85,10 @@ impl DOMErrorName {
|
|||
"TimeoutError" => Some(DOMErrorName::TimeoutError),
|
||||
"InvalidNodeTypeError" => Some(DOMErrorName::InvalidNodeTypeError),
|
||||
"DataCloneError" => Some(DOMErrorName::DataCloneError),
|
||||
"DataError" => Some(DOMErrorName::DataError),
|
||||
"TransactionInactiveError" => Some(DOMErrorName::TransactionInactiveError),
|
||||
"ReadOnlyError" => Some(DOMErrorName::ReadOnlyError),
|
||||
"VersionError" => Some(DOMErrorName::VersionError),
|
||||
"EncodingError" => Some(DOMErrorName::EncodingError),
|
||||
"NotReadableError" => Some(DOMErrorName::NotReadableError),
|
||||
"DataError" => Some(DOMErrorName::DataError),
|
||||
|
@ -129,6 +137,14 @@ impl DOMException {
|
|||
"The supplied node is incorrect or has an incorrect ancestor for this operation."
|
||||
},
|
||||
DOMErrorName::DataCloneError => "The object can not be cloned.",
|
||||
DOMErrorName::DataError => "Provided data is inadequate.",
|
||||
DOMErrorName::TransactionInactiveError => {
|
||||
"A request was placed against a transaction which is currently not active, or which is finished."
|
||||
},
|
||||
DOMErrorName::ReadOnlyError => "The mutating operation was attempted in a \"readonly\" transaction.",
|
||||
DOMErrorName::VersionError => {
|
||||
"An attempt was made to open a database using a lower version than the existing version."
|
||||
},
|
||||
DOMErrorName::EncodingError => {
|
||||
"The encoding operation (either encoded or decoding) failed."
|
||||
},
|
||||
|
|
290
components/script/dom/idbdatabase.rs
Normal file
290
components/script/dom/idbdatabase.rs
Normal file
|
@ -0,0 +1,290 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use net_traits::indexeddb_thread::{IndexedDBThreadMsg, SyncOperation};
|
||||
use net_traits::IpcSend;
|
||||
use profile_traits::ipc;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::{
|
||||
IDBDatabaseMethods, IDBObjectStoreParameters, IDBTransactionOptions,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
|
||||
use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::domstringlist::DOMStringList;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbobjectstore::IDBObjectStore;
|
||||
use crate::dom::idbtransaction::IDBTransaction;
|
||||
use crate::dom::idbversionchangeevent::IDBVersionChangeEvent;
|
||||
use crate::task_source::TaskSource;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBDatabase {
|
||||
eventtarget: EventTarget,
|
||||
name: DOMString,
|
||||
version: Cell<u64>,
|
||||
object_store_names: DomRefCell<Vec<DOMString>>,
|
||||
|
||||
// No specification below this line
|
||||
upgrade_transaction: MutNullableDom<IDBTransaction>,
|
||||
|
||||
// Flags
|
||||
closing: Cell<bool>,
|
||||
}
|
||||
|
||||
impl IDBDatabase {
|
||||
pub fn new_inherited(name: DOMString, version: u64) -> IDBDatabase {
|
||||
IDBDatabase {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
name,
|
||||
version: Cell::new(version),
|
||||
object_store_names: Default::default(),
|
||||
|
||||
upgrade_transaction: Default::default(),
|
||||
closing: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope, name: DOMString, version: u64) -> DomRoot<IDBDatabase> {
|
||||
reflect_dom_object(Box::new(IDBDatabase::new_inherited(name, version)), global)
|
||||
}
|
||||
|
||||
fn get_idb_thread(&self) -> IpcSender<IndexedDBThreadMsg> {
|
||||
self.global().resource_threads().sender()
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> DOMString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn object_stores(&self) -> DomRoot<DOMStringList> {
|
||||
DOMStringList::new(&self.global(), self.object_store_names.borrow().clone())
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u64 {
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
let operation = SyncOperation::Version(
|
||||
sender,
|
||||
self.global().origin().immutable().clone(),
|
||||
self.name.to_string(),
|
||||
);
|
||||
|
||||
self.get_idb_thread()
|
||||
.send(IndexedDBThreadMsg::Sync(operation))
|
||||
.unwrap();
|
||||
|
||||
receiver.recv().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_transaction(&self, transaction: &IDBTransaction) {
|
||||
self.upgrade_transaction.set(Some(transaction));
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // This will be used once we allow multiple concurrent connections
|
||||
pub fn dispatch_versionchange(&self, old_version: u64, new_version: Option<u64>) {
|
||||
let global = self.global();
|
||||
let this = Trusted::new(self);
|
||||
global
|
||||
.database_access_task_source()
|
||||
.queue(
|
||||
task!(send_versionchange_notification: move || {
|
||||
let this = this.root();
|
||||
let global = this.global();
|
||||
let event = IDBVersionChangeEvent::new(
|
||||
&global,
|
||||
Atom::from("versionchange"),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
old_version,
|
||||
new_version,
|
||||
);
|
||||
event.upcast::<Event>().fire(this.upcast());
|
||||
}),
|
||||
global.upcast(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBDatabaseMethods for IDBDatabase {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-transaction
|
||||
fn Transaction(
|
||||
&self,
|
||||
store_names: StringOrStringSequence,
|
||||
mode: IDBTransactionMode,
|
||||
options: &IDBTransactionOptions,
|
||||
) -> DomRoot<IDBTransaction> {
|
||||
// FIXIME:(arihant2math) use options
|
||||
// Step 1: Check if upgrade transaction is running
|
||||
// FIXME:(rasviitanen)
|
||||
|
||||
// Step 2: if close flag is set, throw error
|
||||
// FIXME:(rasviitanen)
|
||||
|
||||
// Step 3
|
||||
match store_names {
|
||||
StringOrStringSequence::String(name) => IDBTransaction::new(
|
||||
&self.global(),
|
||||
&self,
|
||||
mode,
|
||||
DOMStringList::new(&self.global(), vec![name]),
|
||||
),
|
||||
StringOrStringSequence::StringSequence(sequence) => {
|
||||
// FIXME:(rasviitanen) Remove eventual duplicated names
|
||||
// from the sequence
|
||||
IDBTransaction::new(
|
||||
&self.global(),
|
||||
&self,
|
||||
mode,
|
||||
DOMStringList::new(&self.global(), sequence),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-createobjectstore
|
||||
fn CreateObjectStore(
|
||||
&self,
|
||||
name: DOMString,
|
||||
options: &IDBObjectStoreParameters,
|
||||
) -> Fallible<DomRoot<IDBObjectStore>> {
|
||||
// FIXME:(arihant2math) ^^ Change idl to match above.
|
||||
// Step 2
|
||||
let upgrade_transaction = match self.upgrade_transaction.get() {
|
||||
Some(txn) => txn,
|
||||
None => return Err(Error::InvalidState),
|
||||
};
|
||||
|
||||
// Step 3
|
||||
if !upgrade_transaction.is_active() {
|
||||
return Err(Error::TransactionInactive);
|
||||
}
|
||||
|
||||
// Step 4
|
||||
let key_path = options.keyPath.as_ref();
|
||||
|
||||
// Step 5
|
||||
if let Some(ref path) = key_path {
|
||||
if !IDBObjectStore::is_valid_key_path(path) {
|
||||
return Err(Error::Syntax);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6 FIXME:(rasviitanen)
|
||||
// If an object store named name already exists in database throw a "ConstraintError" DOMException.
|
||||
|
||||
// Step 7
|
||||
let auto_increment = options.autoIncrement;
|
||||
|
||||
// Step 8
|
||||
if auto_increment == true {
|
||||
match key_path {
|
||||
Some(StringOrStringSequence::String(path)) => {
|
||||
if path == "" {
|
||||
return Err(Error::InvalidAccess);
|
||||
}
|
||||
},
|
||||
Some(StringOrStringSequence::StringSequence(_)) => {
|
||||
return Err(Error::InvalidAccess);
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Step 9
|
||||
let object_store = IDBObjectStore::new(
|
||||
&self.global(),
|
||||
self.name.clone(),
|
||||
name.clone(),
|
||||
Some(options),
|
||||
);
|
||||
object_store.set_transaction(&upgrade_transaction);
|
||||
|
||||
// FIXME:(rasviitanen!!!) Move this to constructor
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
|
||||
let operation = SyncOperation::CreateObjectStore(
|
||||
sender,
|
||||
self.global().origin().immutable().clone(),
|
||||
self.name.to_string(),
|
||||
name.to_string(),
|
||||
auto_increment,
|
||||
);
|
||||
|
||||
self.get_idb_thread()
|
||||
.send(IndexedDBThreadMsg::Sync(operation))
|
||||
.unwrap();
|
||||
|
||||
if receiver
|
||||
.recv()
|
||||
.expect("Could not receive object store creation status")
|
||||
.is_err()
|
||||
{
|
||||
warn!("Object store creation failed in idb thread");
|
||||
};
|
||||
|
||||
self.object_store_names.borrow_mut().push(name);
|
||||
Ok(object_store)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-deleteobjectstore
|
||||
fn DeleteObjectStore(&self, _name: DOMString) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-name
|
||||
fn Name(&self) -> DOMString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-version
|
||||
fn Version(&self) -> u64 {
|
||||
self.version()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-objectstorenames
|
||||
fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
|
||||
DOMStringList::new(&self.global(), self.object_store_names.borrow().clone())
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-close
|
||||
fn Close(&self) {
|
||||
// Step 1: Set the close pending flag of connection.
|
||||
self.closing.set(true);
|
||||
|
||||
// Step 2: Handle force flag
|
||||
// FIXME:(rasviitanen)
|
||||
|
||||
// Step 3: Wait for all transactions by this db to finish
|
||||
// FIXME:(rasviitanen)
|
||||
|
||||
// Step 4: If force flag is set, fire a close event
|
||||
// FIXME:(rasviitanen)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onabort
|
||||
event_handler!(abort, GetOnabort, SetOnabort);
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onclose
|
||||
event_handler!(close, GetOnclose, SetOnclose);
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onerror
|
||||
event_handler!(error, GetOnerror, SetOnerror);
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbdatabase-onversionchange
|
||||
event_handler!(versionchange, GetOnversionchange, SetOnversionchange);
|
||||
}
|
102
components/script/dom/idbfactory.rs
Normal file
102
components/script/dom/idbfactory.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleValue;
|
||||
use servo_url::origin::ImmutableOrigin;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::IDBFactoryBinding::IDBFactoryMethods;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbopendbrequest::IDBOpenDBRequest;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBFactory {
|
||||
reflector_: Reflector,
|
||||
}
|
||||
|
||||
impl IDBFactory {
|
||||
pub fn new_inherited() -> IDBFactory {
|
||||
IDBFactory {
|
||||
reflector_: Reflector::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope) -> DomRoot<IDBFactory> {
|
||||
reflect_dom_object(Box::new(IDBFactory::new_inherited()), global)
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBFactoryMethods for IDBFactory {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbfactory-open
|
||||
#[allow(unsafe_code)]
|
||||
fn Open(&self, name: DOMString, version: Option<u64>) -> Fallible<DomRoot<IDBOpenDBRequest>> {
|
||||
// Step 1: If version is 0 (zero), throw a TypeError.
|
||||
if version == Some(0) {
|
||||
return Err(Error::Type(
|
||||
"The version must be an integer >= 1".to_owned(),
|
||||
));
|
||||
};
|
||||
|
||||
// Step 2: Let origin be the origin of the global scope used to
|
||||
// access this IDBFactory.
|
||||
let global = self.global();
|
||||
let origin = global.origin();
|
||||
|
||||
// Step 3: if origin is an opaque origin,
|
||||
// throw a "SecurityError" DOMException and abort these steps.
|
||||
if let ImmutableOrigin::Opaque(_) = origin.immutable() {
|
||||
return Err(Error::Security);
|
||||
}
|
||||
|
||||
// Step 4: Let request be a new open request.
|
||||
let request = IDBOpenDBRequest::new(&self.global());
|
||||
|
||||
// Step 5: Runs in parallel
|
||||
request.open_database(name, version);
|
||||
|
||||
// Step 6
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbfactory-deletedatabase
|
||||
fn DeleteDatabase(&self, name: DOMString) -> Fallible<DomRoot<IDBOpenDBRequest>> {
|
||||
// Step 1: Let origin be the origin of the global scope used to
|
||||
// access this IDBFactory.
|
||||
let global = self.global();
|
||||
let origin = global.origin();
|
||||
|
||||
// Step 2: if origin is an opaque origin,
|
||||
// throw a "SecurityError" DOMException and abort these steps.
|
||||
if let ImmutableOrigin::Opaque(_) = origin.immutable() {
|
||||
return Err(Error::Security);
|
||||
}
|
||||
|
||||
// Step 3: Let request be a new open request
|
||||
let request = IDBOpenDBRequest::new(&self.global());
|
||||
|
||||
// Step 4: Runs in parallel
|
||||
request.delete_database(name.to_string());
|
||||
|
||||
// Step 5: Return request
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbfactory-databases
|
||||
fn Databases(&self) -> Rc<Promise> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbfactory-cmp
|
||||
fn Cmp(&self, _cx: SafeJSContext, _first: HandleValue, _second: HandleValue) -> i16 {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
571
components/script/dom/idbobjectstore.rs
Normal file
571
components/script/dom/idbobjectstore.rs
Normal file
|
@ -0,0 +1,571 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::conversions::ToJSValConvertible;
|
||||
use js::jsapi::{
|
||||
ESClass, GetBuiltinClass, IsArrayBufferObject, JSObject, JS_DeleteUCProperty,
|
||||
JS_GetOwnUCPropertyDescriptor, JS_GetStringLength, JS_IsArrayBufferViewObject, ObjectOpResult,
|
||||
ObjectOpResult_SpecialCodes, PropertyDescriptor,
|
||||
};
|
||||
use js::jsval::{JSVal, UndefinedValue};
|
||||
use js::rust::{HandleValue, MutableHandleValue};
|
||||
use net_traits::indexeddb_thread::{
|
||||
AsyncOperation, IndexedDBKeyType, IndexedDBThreadMsg, SyncOperation,
|
||||
};
|
||||
use net_traits::IpcSend;
|
||||
use profile_traits::ipc;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBObjectStoreParameters;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBObjectStoreBinding::IDBObjectStoreMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
|
||||
// We need to alias this name, otherwise test-tidy complains at &String reference.
|
||||
use crate::dom::bindings::codegen::UnionTypes::StringOrStringSequence as StrOrStringSequence;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bindings::structuredclone;
|
||||
use crate::dom::domstringlist::DOMStringList;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbrequest::IDBRequest;
|
||||
use crate::dom::idbtransaction::IDBTransaction;
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
pub enum KeyPath {
|
||||
String(DOMString),
|
||||
StringSequence(Vec<DOMString>),
|
||||
}
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBObjectStore {
|
||||
reflector_: Reflector,
|
||||
name: DomRefCell<DOMString>,
|
||||
key_path: Option<KeyPath>,
|
||||
index_names: DomRoot<DOMStringList>,
|
||||
transaction: MutNullableDom<IDBTransaction>,
|
||||
auto_increment: bool,
|
||||
|
||||
// We store the db name in the object store to be able to find the correct
|
||||
// store in the idb thread when checking if we have a key generator
|
||||
db_name: DOMString,
|
||||
}
|
||||
|
||||
impl IDBObjectStore {
|
||||
pub fn new_inherited(
|
||||
global: &GlobalScope,
|
||||
db_name: DOMString,
|
||||
name: DOMString,
|
||||
options: Option<&IDBObjectStoreParameters>,
|
||||
) -> IDBObjectStore {
|
||||
let key_path: Option<KeyPath> = match options {
|
||||
Some(options) => options.keyPath.as_ref().map(|path| match path {
|
||||
StrOrStringSequence::String(inner) => KeyPath::String(inner.clone()),
|
||||
StrOrStringSequence::StringSequence(inner) => {
|
||||
KeyPath::StringSequence(inner.clone())
|
||||
},
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
|
||||
IDBObjectStore {
|
||||
reflector_: Reflector::new(),
|
||||
name: DomRefCell::new(name),
|
||||
key_path: key_path,
|
||||
|
||||
index_names: DOMStringList::new(global, Vec::new()),
|
||||
transaction: Default::default(),
|
||||
auto_increment: false,
|
||||
|
||||
db_name: db_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
db_name: DOMString,
|
||||
name: DOMString,
|
||||
options: Option<&IDBObjectStoreParameters>,
|
||||
) -> DomRoot<IDBObjectStore> {
|
||||
reflect_dom_object(
|
||||
Box::new(IDBObjectStore::new_inherited(
|
||||
global, db_name, name, options,
|
||||
)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> DOMString {
|
||||
self.name.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn set_transaction(&self, transaction: &IDBTransaction) {
|
||||
self.transaction.set(Some(transaction));
|
||||
}
|
||||
|
||||
pub fn transaction(&self) -> Option<DomRoot<IDBTransaction>> {
|
||||
self.transaction.get()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#valid-key-path
|
||||
pub fn is_valid_key_path(key_path: &StrOrStringSequence) -> bool {
|
||||
let is_valid = |path: &DOMString| {
|
||||
let path = path.to_string();
|
||||
let mut identifiers = path.split('.').into_iter();
|
||||
|
||||
while let Some(_identifier) = identifiers.next() {
|
||||
// FIXME:(rasviitanen) Implement this properly
|
||||
}
|
||||
|
||||
true
|
||||
};
|
||||
|
||||
match key_path {
|
||||
StrOrStringSequence::StringSequence(paths) => {
|
||||
if paths.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for path in paths {
|
||||
if !is_valid(path) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
StrOrStringSequence::String(path) => is_valid(path),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
// https://www.w3.org/TR/IndexedDB-2/#convert-value-to-key
|
||||
fn convert_value_to_key(
|
||||
cx: SafeJSContext,
|
||||
input: HandleValue,
|
||||
seen: Option<Vec<HandleValue>>,
|
||||
) -> Result<IndexedDBKeyType, Error> {
|
||||
// Step 1: If seen was not given, then let seen be a new empty set.
|
||||
let _seen = seen.unwrap_or(Vec::new());
|
||||
|
||||
// Step 2: If seen contains input, then return invalid.
|
||||
// FIXME:(rasviitanen)
|
||||
// Check if we have seen this key
|
||||
// Does not currently work with HandleValue,
|
||||
// as it does not implement PartialEq
|
||||
|
||||
// Step 3
|
||||
// FIXME:(rasviitanen) Accept buffer, array and date as well
|
||||
if input.is_number() {
|
||||
// FIXME:(rasviitanen) check for NaN
|
||||
let key = structuredclone::write(cx, input, None).expect("Could not serialize key");
|
||||
return Ok(IndexedDBKeyType::Number(key.serialized.clone()));
|
||||
}
|
||||
|
||||
if input.is_string() {
|
||||
let key = structuredclone::write(cx, input, None).expect("Could not serialize key");
|
||||
return Ok(IndexedDBKeyType::String(key.serialized.clone()));
|
||||
}
|
||||
|
||||
if input.is_object() {
|
||||
rooted!(in(*cx) let object = input.to_object());
|
||||
unsafe {
|
||||
let mut built_in_class = ESClass::Other;
|
||||
|
||||
if !GetBuiltinClass(*cx, object.handle().into(), &mut built_in_class) {
|
||||
return Err(Error::Data);
|
||||
}
|
||||
|
||||
if let ESClass::Date = built_in_class {
|
||||
// FIXME:(rasviitanen)
|
||||
unimplemented!("Dates as keys is currently unsupported");
|
||||
}
|
||||
|
||||
if IsArrayBufferObject(*object) || JS_IsArrayBufferViewObject(*object) {
|
||||
let key =
|
||||
structuredclone::write(cx, input, None).expect("Could not serialize key");
|
||||
// FIXME:(rasviitanen) Return the correct type here
|
||||
// it doesn't really matter at the moment...
|
||||
return Ok(IndexedDBKeyType::Number(key.serialized.clone()));
|
||||
}
|
||||
|
||||
if let ESClass::Array = built_in_class {
|
||||
// FIXME:(rasviitanen)
|
||||
unimplemented!("Arrays as keys is currently unsupported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::Data)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#evaluate-a-key-path-on-a-value
|
||||
#[allow(unsafe_code)]
|
||||
fn evaluate_key_path_on_value(
|
||||
cx: SafeJSContext,
|
||||
value: HandleValue,
|
||||
mut return_val: MutableHandleValue,
|
||||
key_path: &KeyPath,
|
||||
) {
|
||||
// The implementation is translated from gecko:
|
||||
// https://github.com/mozilla/gecko-dev/blob/master/dom/indexedDB/KeyPath.cpp
|
||||
*return_val = *value;
|
||||
|
||||
rooted!(in(*cx) let mut target_object = ptr::null_mut::<JSObject>());
|
||||
rooted!(in(*cx) let mut current_val = *value);
|
||||
rooted!(in(*cx) let mut object = ptr::null_mut::<JSObject>());
|
||||
|
||||
let mut target_object_prop_name: Option<String> = None;
|
||||
|
||||
match key_path {
|
||||
KeyPath::String(path) => {
|
||||
// Step 3
|
||||
let path_as_string = path.to_string();
|
||||
let mut tokenizer = path_as_string.split('.').into_iter().peekable();
|
||||
|
||||
while let Some(token) = tokenizer.next() {
|
||||
if target_object.get().is_null() {
|
||||
if token == "length" && current_val.is_string() {
|
||||
rooted!(in(*cx) let input_val = current_val.to_string());
|
||||
unsafe {
|
||||
let string_len = JS_GetStringLength(*input_val) as f32;
|
||||
string_len.to_jsval(*cx, return_val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if !current_val.is_object() {
|
||||
// FIXME:(rasviitanen) Return a proper error
|
||||
return;
|
||||
}
|
||||
|
||||
*object = current_val.to_object();
|
||||
rooted!(in(*cx) let mut desc = PropertyDescriptor::default());
|
||||
rooted!(in(*cx) let mut intermediate = UndefinedValue());
|
||||
|
||||
// So rust says that this value is never read, but it is.
|
||||
#[allow(unused)]
|
||||
let mut has_prop = false;
|
||||
|
||||
unsafe {
|
||||
let prop_name_as_utf16: Vec<u16> = token.encode_utf16().collect();
|
||||
let ok = JS_GetOwnUCPropertyDescriptor(
|
||||
*cx,
|
||||
object.handle().into(),
|
||||
prop_name_as_utf16.as_ptr(),
|
||||
prop_name_as_utf16.len(),
|
||||
desc.handle_mut().into(),
|
||||
&mut false,
|
||||
);
|
||||
|
||||
if !ok {
|
||||
// FIXME:(arihant2math) Handle this
|
||||
return;
|
||||
}
|
||||
|
||||
if desc.hasValue_() {
|
||||
*intermediate = desc.handle().value_;
|
||||
has_prop = true;
|
||||
} else {
|
||||
// If we get here it means the object doesn't have the property or the
|
||||
// property is available throuch a getter. We don't want to call any
|
||||
// getters to avoid potential re-entrancy.
|
||||
// The blob object is special since its properties are available
|
||||
// only through getters but we still want to support them for key
|
||||
// extraction. So they need to be handled manually.
|
||||
unimplemented!("Blob tokens are not yet supported");
|
||||
}
|
||||
}
|
||||
|
||||
if has_prop {
|
||||
// Treat undefined as an error
|
||||
if intermediate.is_undefined() {
|
||||
// FIXME:(rasviitanen) Throw/return error
|
||||
return;
|
||||
}
|
||||
|
||||
if tokenizer.peek().is_some() {
|
||||
// ...and walk to it if there are more steps...
|
||||
*current_val = *intermediate;
|
||||
} else {
|
||||
// ...otherwise use it as key
|
||||
*return_val = *intermediate;
|
||||
}
|
||||
} else {
|
||||
*target_object = *object;
|
||||
target_object_prop_name = Some(token.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if !target_object.get().is_null() {
|
||||
// We have started inserting new objects or are about to just insert
|
||||
// the first one.
|
||||
// FIXME:(rasviitanen) Implement this piece
|
||||
unimplemented!("keyPath tokens that requires insertion are not supported.");
|
||||
}
|
||||
} // All tokens processed
|
||||
|
||||
if !target_object.get().is_null() {
|
||||
// If this fails, we lose, and the web page sees a magical property
|
||||
// appear on the object :-(
|
||||
unsafe {
|
||||
let prop_name_as_utf16: Vec<u16> =
|
||||
target_object_prop_name.unwrap().encode_utf16().collect();
|
||||
let mut succeeded = ObjectOpResult {
|
||||
code_: ObjectOpResult_SpecialCodes::Uninitialized as usize,
|
||||
};
|
||||
if !JS_DeleteUCProperty(
|
||||
*cx,
|
||||
target_object.handle().into(),
|
||||
prop_name_as_utf16.as_ptr(),
|
||||
prop_name_as_utf16.len(),
|
||||
&mut succeeded,
|
||||
) {
|
||||
// FIXME:(rasviitanen) Throw/return error
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
KeyPath::StringSequence(_) => {
|
||||
unimplemented!("String sequence keyPath is currently unsupported");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn has_key_generator(&self) -> bool {
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
|
||||
let operation = SyncOperation::HasKeyGenerator(
|
||||
sender,
|
||||
self.global().origin().immutable().clone(),
|
||||
self.db_name.to_string(),
|
||||
self.name.borrow().to_string(),
|
||||
);
|
||||
|
||||
self.global()
|
||||
.resource_threads()
|
||||
.sender()
|
||||
.send(IndexedDBThreadMsg::Sync(operation))
|
||||
.unwrap();
|
||||
|
||||
receiver.recv().unwrap()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#extract-a-key-from-a-value-using-a-key-path
|
||||
fn extract_key(
|
||||
cx: SafeJSContext,
|
||||
input: HandleValue,
|
||||
key_path: &KeyPath,
|
||||
multi_entry: Option<bool>,
|
||||
) -> Result<IndexedDBKeyType, Error> {
|
||||
// Step 1: Evaluate key path
|
||||
// FIXME:(rasviitanen) Do this propertly
|
||||
rooted!(in(*cx) let mut r = UndefinedValue());
|
||||
IDBObjectStore::evaluate_key_path_on_value(cx, input, r.handle_mut(), key_path);
|
||||
|
||||
if let Some(_multi_entry) = multi_entry {
|
||||
// FIXME:(rasviitanen) handle multi_entry cases
|
||||
unimplemented!("multiEntry keys are not yet supported");
|
||||
} else {
|
||||
IDBObjectStore::convert_value_to_key(cx, r.handle(), None)
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#object-store-in-line-keys
|
||||
fn uses_inline_keys(&self) -> bool {
|
||||
self.key_path.is_some()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
|
||||
fn put(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
value: HandleValue,
|
||||
key: HandleValue,
|
||||
overwrite: bool,
|
||||
) -> Fallible<DomRoot<IDBRequest>> {
|
||||
// Step 1: Let transaction be this object store handle's transaction.
|
||||
let transaction = self
|
||||
.transaction
|
||||
.get()
|
||||
.expect("No transaction in Object Store");
|
||||
|
||||
// Step 2: Let store be this object store handle's object store.
|
||||
// This is resolved in the `execute_async` function.
|
||||
|
||||
// Step 3: If store has been deleted, throw an "InvalidStateError" DOMException.
|
||||
// FIXME:(rasviitanen)
|
||||
|
||||
// Step 4-5: If transaction is not active, throw a "TransactionInactiveError" DOMException.
|
||||
if !transaction.is_active() {
|
||||
return Err(Error::TransactionInactive);
|
||||
}
|
||||
|
||||
// Step 5: If transaction is a read-only transaction, throw a "ReadOnlyError" DOMException.
|
||||
if let IDBTransactionMode::Readonly = transaction.get_mode() {
|
||||
return Err(Error::ReadOnly);
|
||||
}
|
||||
|
||||
// Step 6: If store uses in-line keys and key was given, throw a "DataError" DOMException.
|
||||
if !key.is_undefined() && self.uses_inline_keys() {
|
||||
return Err(Error::Data);
|
||||
}
|
||||
|
||||
// Step 7: If store uses out-of-line keys and has no key generator
|
||||
// and key was not given, throw a "DataError" DOMException.
|
||||
if !self.uses_inline_keys() && !self.has_key_generator() && key.is_undefined() {
|
||||
return Err(Error::Data);
|
||||
}
|
||||
|
||||
// Step 8: If key was given, then: convert a value to a key with key
|
||||
let serialized_key: IndexedDBKeyType;
|
||||
|
||||
if !key.is_undefined() {
|
||||
serialized_key = IDBObjectStore::convert_value_to_key(cx, key, None)?;
|
||||
} else {
|
||||
// Step 11: We should use in-line keys instead
|
||||
if let Ok(kpk) = IDBObjectStore::extract_key(
|
||||
cx,
|
||||
value,
|
||||
self.key_path.as_ref().expect("No key path"),
|
||||
None,
|
||||
) {
|
||||
serialized_key = kpk;
|
||||
} else {
|
||||
// FIXME:(rasviitanen)
|
||||
// Check if store has a key generator
|
||||
// Check if we can inject a key
|
||||
return Err(Error::Data);
|
||||
}
|
||||
}
|
||||
|
||||
let serialized_value =
|
||||
structuredclone::write(cx, value, None).expect("Could not serialize value");
|
||||
|
||||
IDBRequest::execute_async(
|
||||
&*self,
|
||||
AsyncOperation::PutItem(
|
||||
serialized_key,
|
||||
serialized_value.serialized.clone(),
|
||||
overwrite,
|
||||
),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBObjectStoreMethods for IDBObjectStore {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-put
|
||||
fn Put(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
value: HandleValue,
|
||||
key: HandleValue,
|
||||
) -> Fallible<DomRoot<IDBRequest>> {
|
||||
self.put(cx, value, key, true)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-add
|
||||
fn Add(
|
||||
&self,
|
||||
cx: SafeJSContext,
|
||||
value: HandleValue,
|
||||
key: HandleValue,
|
||||
) -> Fallible<DomRoot<IDBRequest>> {
|
||||
self.put(cx, value, key, false)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-delete
|
||||
fn Delete(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
|
||||
let serialized_query =
|
||||
structuredclone::write(cx, query, None).expect("Could not serialize value");
|
||||
|
||||
IDBRequest::execute_async(
|
||||
&*self,
|
||||
AsyncOperation::RemoveItem(serialized_query.serialized.clone()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-clear
|
||||
fn Clear(&self) -> DomRoot<IDBRequest> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-get
|
||||
fn Get(&self, cx: SafeJSContext, query: HandleValue) -> Fallible<DomRoot<IDBRequest>> {
|
||||
let serialized_query =
|
||||
structuredclone::write(cx, query, None).expect("Could not serialize value");
|
||||
|
||||
IDBRequest::execute_async(
|
||||
&*self,
|
||||
AsyncOperation::GetItem(serialized_query.serialized.clone()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getkey
|
||||
fn GetKey(&self, _cx: SafeJSContext, _query: HandleValue) -> DomRoot<IDBRequest> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getall
|
||||
fn GetAll(
|
||||
&self,
|
||||
_cx: SafeJSContext,
|
||||
_query: HandleValue,
|
||||
_count: Option<u32>,
|
||||
) -> DomRoot<IDBRequest> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-getallkeys
|
||||
fn GetAllKeys(
|
||||
&self,
|
||||
_cx: SafeJSContext,
|
||||
_query: HandleValue,
|
||||
_count: Option<u32>,
|
||||
) -> DomRoot<IDBRequest> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-count
|
||||
fn Count(&self, _cx: SafeJSContext, _query: HandleValue) -> DomRoot<IDBRequest> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-name
|
||||
fn Name(&self) -> DOMString {
|
||||
self.name.borrow().clone()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-setname
|
||||
fn SetName(&self, value: DOMString) {
|
||||
std::mem::replace(&mut *self.name.borrow_mut(), value);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-keypath
|
||||
fn KeyPath(&self, _cx: SafeJSContext) -> JSVal {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-indexnames
|
||||
fn IndexNames(&self) -> DomRoot<DOMStringList> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-transaction
|
||||
fn Transaction(&self) -> DomRoot<IDBTransaction> {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbobjectstore-autoincrement
|
||||
fn AutoIncrement(&self) -> bool {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
371
components/script/dom/idbopendbrequest.rs
Normal file
371
components/script/dom/idbopendbrequest.rs
Normal file
|
@ -0,0 +1,371 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::HandleValue;
|
||||
use net_traits::indexeddb_thread::{IndexedDBThreadMsg, SyncOperation};
|
||||
use net_traits::IpcSend;
|
||||
use profile_traits::ipc;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::IDBOpenDBRequestBinding::IDBOpenDBRequestMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbdatabase::IDBDatabase;
|
||||
use crate::dom::idbrequest::IDBRequest;
|
||||
use crate::dom::idbtransaction::IDBTransaction;
|
||||
use crate::dom::idbversionchangeevent::IDBVersionChangeEvent;
|
||||
use crate::enter_realm;
|
||||
use crate::js::conversions::ToJSValConvertible;
|
||||
use crate::task_source::database_access::DatabaseAccessTaskSource;
|
||||
use crate::task_source::TaskSource;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct OpenRequestListener {
|
||||
open_request: Trusted<IDBOpenDBRequest>,
|
||||
}
|
||||
|
||||
impl OpenRequestListener {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#open-a-database
|
||||
fn handle_open_db(
|
||||
&self,
|
||||
name: String,
|
||||
request_version: Option<u64>,
|
||||
db_version: u64,
|
||||
) -> (Fallible<DomRoot<IDBDatabase>>, bool) {
|
||||
// Step 5-6
|
||||
let request_version = match request_version {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
if db_version == 0 {
|
||||
1
|
||||
} else {
|
||||
db_version
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Step 7
|
||||
if request_version < db_version {
|
||||
return (Err(Error::Version), false);
|
||||
}
|
||||
|
||||
// Step 8-9
|
||||
let open_request = self.open_request.root();
|
||||
let global = open_request.global();
|
||||
let connection = IDBDatabase::new(
|
||||
&global,
|
||||
DOMString::from_string(name.clone()),
|
||||
request_version,
|
||||
);
|
||||
|
||||
// Step 10
|
||||
if request_version > db_version {
|
||||
// FIXME:(rasviitanen) Do step 10.1-10.5
|
||||
// connection.dispatch_versionchange(db_version, Some(request_version));
|
||||
// Step 10.6
|
||||
open_request.upgrade_db_version(&*connection, request_version);
|
||||
// Step 11
|
||||
(Ok(connection), true)
|
||||
} else {
|
||||
// Step 11
|
||||
(Ok(connection), false)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_delete_db(&self, result: Result<(), ()>) {
|
||||
let open_request = self.open_request.root();
|
||||
let global = open_request.global();
|
||||
open_request.idbrequest.set_ready_state_done();
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let cx = GlobalScope::get_cx();
|
||||
let _ac = enter_realm(&*open_request);
|
||||
rooted!(in(*cx) let mut answer = UndefinedValue());
|
||||
open_request.set_result(answer.handle());
|
||||
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("success"),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
);
|
||||
event.upcast::<Event>().fire(open_request.upcast());
|
||||
},
|
||||
Err(_e) => {
|
||||
// FIXME(rasviitanen) Set the error of request to the
|
||||
// appropriate error
|
||||
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("error"),
|
||||
EventBubbles::Bubbles,
|
||||
EventCancelable::Cancelable,
|
||||
);
|
||||
event.upcast::<Event>().fire(open_request.upcast());
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBOpenDBRequest {
|
||||
idbrequest: IDBRequest,
|
||||
}
|
||||
|
||||
impl IDBOpenDBRequest {
|
||||
pub fn new_inherited() -> IDBOpenDBRequest {
|
||||
IDBOpenDBRequest {
|
||||
idbrequest: IDBRequest::new_inherited(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope) -> DomRoot<IDBOpenDBRequest> {
|
||||
reflect_dom_object(Box::new(IDBOpenDBRequest::new_inherited()), global)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
// https://www.w3.org/TR/IndexedDB-2/#run-an-upgrade-transaction
|
||||
fn upgrade_db_version(&self, connection: &IDBDatabase, version: u64) {
|
||||
let global = self.global();
|
||||
// Step 2
|
||||
let transaction = IDBTransaction::new(
|
||||
&global,
|
||||
&connection,
|
||||
IDBTransactionMode::Versionchange,
|
||||
connection.object_stores(),
|
||||
);
|
||||
|
||||
// Step 3
|
||||
connection.set_transaction(&transaction);
|
||||
|
||||
// Step 4
|
||||
transaction.set_active_flag(false);
|
||||
|
||||
// Step 5-7
|
||||
let old_version = connection.version();
|
||||
transaction.upgrade_db_version(version);
|
||||
|
||||
// Step 8
|
||||
let this = Trusted::new(self);
|
||||
let connection = Trusted::new(connection);
|
||||
let trusted_transaction = Trusted::new(&*transaction);
|
||||
global
|
||||
.database_access_task_source()
|
||||
.queue(
|
||||
task!(send_upgradeneeded_notification: move || {
|
||||
let this = this.root();
|
||||
let txn = trusted_transaction.root();
|
||||
let conn = connection.root();
|
||||
let global = this.global();
|
||||
let cx = GlobalScope::get_cx();
|
||||
|
||||
// Step 8.1
|
||||
let _ac = enter_realm(&*conn);
|
||||
rooted!(in(*cx) let mut connection_val = UndefinedValue());
|
||||
unsafe {
|
||||
conn.to_jsval(*cx, connection_val.handle_mut());
|
||||
}
|
||||
this.idbrequest.set_result(connection_val.handle());
|
||||
|
||||
// Step 8.2
|
||||
this.idbrequest.set_transaction(&txn);
|
||||
|
||||
// Step 8.3
|
||||
this.idbrequest.set_ready_state_done();
|
||||
|
||||
let event = IDBVersionChangeEvent::new(
|
||||
&global,
|
||||
Atom::from("upgradeneeded"),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
old_version,
|
||||
Some(version),
|
||||
);
|
||||
|
||||
// Step 8.4
|
||||
txn.set_active_flag(true);
|
||||
// Step 8.5
|
||||
let _did_throw = event.upcast::<Event>().fire(this.upcast());
|
||||
// FIXME:(rasviitanen) Handle throw (Step 8.5)
|
||||
// https://www.w3.org/TR/IndexedDB-2/#run-an-upgrade-transaction
|
||||
// Step 8.6
|
||||
txn.set_active_flag(false);
|
||||
|
||||
// Implementation specific: we fire the success on db here
|
||||
// to make sure the success event occurs after the upgrade event.
|
||||
txn.wait();
|
||||
this.dispatch_success(&*conn);
|
||||
}),
|
||||
global.upcast(),
|
||||
)
|
||||
.expect("Could not queue task");
|
||||
|
||||
// Step 9: Starts and waits for the transaction to finish
|
||||
transaction.wait();
|
||||
}
|
||||
|
||||
pub fn set_result(&self, result: HandleValue) {
|
||||
self.idbrequest.set_result(result);
|
||||
}
|
||||
|
||||
pub fn set_error(&self, error: Error) {
|
||||
self.idbrequest.set_error(error);
|
||||
}
|
||||
|
||||
pub fn open_database(&self, name: DOMString, version: Option<u64>) {
|
||||
let global = self.global();
|
||||
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
let response_listener = OpenRequestListener {
|
||||
open_request: Trusted::new(self),
|
||||
};
|
||||
|
||||
let open_operation = SyncOperation::OpenDatabase(
|
||||
sender,
|
||||
global.origin().immutable().clone(),
|
||||
name.to_string(),
|
||||
version,
|
||||
);
|
||||
|
||||
let task_source = global.database_access_task_source();
|
||||
let canceller = global.task_canceller(DatabaseAccessTaskSource::NAME);
|
||||
|
||||
let trusted_request = Trusted::new(self);
|
||||
let name = name.to_string();
|
||||
ROUTER.add_route(
|
||||
receiver.to_opaque(),
|
||||
Box::new(move |message| {
|
||||
let trusted_request = trusted_request.clone();
|
||||
let response_listener = response_listener.clone();
|
||||
let name = name.clone();
|
||||
|
||||
task_source.queue_with_canceller(
|
||||
task!(set_request_result_to_database: move || {
|
||||
let (result, did_upgrade) =
|
||||
response_listener.handle_open_db(name, version, message.to().unwrap());
|
||||
// If an upgrade event was created, it will be responsible for
|
||||
// dispatching the success event
|
||||
if !did_upgrade {
|
||||
let request = trusted_request.root();
|
||||
let global = request.global();
|
||||
match result {
|
||||
Ok(db) => {
|
||||
request.dispatch_success(&*db);
|
||||
},
|
||||
Err(dom_exception) => {
|
||||
request.set_result(HandleValue::undefined());
|
||||
request.set_error(dom_exception);
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("error"),
|
||||
EventBubbles::Bubbles,
|
||||
EventCancelable::Cancelable,
|
||||
);
|
||||
event.upcast::<Event>().fire(request.upcast());
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
&canceller,
|
||||
).unwrap();
|
||||
}),
|
||||
);
|
||||
|
||||
global
|
||||
.resource_threads()
|
||||
.sender()
|
||||
.send(IndexedDBThreadMsg::Sync(open_operation))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn delete_database(&self, name: String) {
|
||||
let global = self.global();
|
||||
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
let task_source = global.database_access_task_source();
|
||||
let response_listener = OpenRequestListener {
|
||||
open_request: Trusted::new(self),
|
||||
};
|
||||
|
||||
let delete_operation =
|
||||
SyncOperation::DeleteDatabase(sender, global.origin().immutable().clone(), name);
|
||||
let canceller = global.task_canceller(DatabaseAccessTaskSource::NAME);
|
||||
|
||||
ROUTER.add_route(
|
||||
receiver.to_opaque(),
|
||||
Box::new(move |message| {
|
||||
let response_listener = response_listener.clone();
|
||||
task_source
|
||||
.queue_with_canceller(
|
||||
task!(request_callback: move || {
|
||||
response_listener.handle_delete_db(message.to().unwrap());
|
||||
}),
|
||||
&canceller,
|
||||
)
|
||||
.unwrap();
|
||||
}),
|
||||
);
|
||||
|
||||
global
|
||||
.resource_threads()
|
||||
.sender()
|
||||
.send(IndexedDBThreadMsg::Sync(delete_operation))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
pub fn dispatch_success(&self, result: &IDBDatabase) {
|
||||
let global = self.global();
|
||||
let this = Trusted::new(self);
|
||||
let result = Trusted::new(result);
|
||||
|
||||
global
|
||||
.database_access_task_source()
|
||||
.queue(
|
||||
task!(send_success_notification: move || {
|
||||
let this = this.root();
|
||||
let result = result.root();
|
||||
this.idbrequest.set_ready_state_done();
|
||||
let global = this.global();
|
||||
let cx = GlobalScope::get_cx();
|
||||
|
||||
let _ac = enter_realm(&*result);
|
||||
rooted!(in(*cx) let mut result_val = UndefinedValue());
|
||||
unsafe {
|
||||
result.to_jsval(*cx, result_val.handle_mut());
|
||||
}
|
||||
this.set_result(result_val.handle());
|
||||
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("success"),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
);
|
||||
event.upcast::<Event>().fire(this.upcast());
|
||||
}),
|
||||
global.upcast(),
|
||||
)
|
||||
.expect("Could not queue success task");
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBOpenDBRequestMethods for IDBOpenDBRequest {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onblocked
|
||||
event_handler!(blocked, GetOnblocked, SetOnblocked);
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbopendbrequest-onupgradeneeded
|
||||
event_handler!(upgradeneeded, GetOnupgradeneeded, SetOnupgradeneeded);
|
||||
}
|
270
components/script/dom/idbrequest.rs
Normal file
270
components/script/dom/idbrequest.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use js::jsapi::Heap;
|
||||
use js::jsval::{JSVal, UndefinedValue};
|
||||
use js::rust::HandleValue;
|
||||
use net_traits::indexeddb_thread::{AsyncOperation, IndexedDBThreadMsg, IndexedDBTxnMode};
|
||||
use net_traits::IpcSend;
|
||||
use profile_traits::ipc;
|
||||
use script_traits::StructuredSerializedData;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::IDBRequestBinding::{
|
||||
IDBRequestMethods, IDBRequestReadyState,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::IDBTransactionMode;
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
||||
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::structuredclone;
|
||||
use crate::dom::domexception::{DOMErrorName, DOMException};
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbobjectstore::IDBObjectStore;
|
||||
use crate::dom::idbtransaction::IDBTransaction;
|
||||
use crate::enter_realm;
|
||||
use crate::script_runtime::JSContext as SafeJSContext;
|
||||
use crate::task_source::database_access::DatabaseAccessTaskSource;
|
||||
use crate::task_source::TaskSource;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RequestListener {
|
||||
request: Trusted<IDBRequest>,
|
||||
}
|
||||
|
||||
impl RequestListener {
|
||||
fn handle_async_request_finished(&self, result: Option<Vec<u8>>) {
|
||||
let request = self.request.root();
|
||||
let global = request.global();
|
||||
let cx = GlobalScope::get_cx();
|
||||
|
||||
request.set_ready_state_done();
|
||||
|
||||
let _ac = enter_realm(&*request);
|
||||
rooted!(in(*cx) let mut answer = UndefinedValue());
|
||||
|
||||
if let Some(serialized_data) = result {
|
||||
let data = StructuredSerializedData {
|
||||
serialized: serialized_data,
|
||||
ports: None,
|
||||
blobs: None,
|
||||
};
|
||||
|
||||
if let Err(_) = structuredclone::read(&global, data, answer.handle_mut()) {
|
||||
warn!("Error reading structuredclone data");
|
||||
}
|
||||
|
||||
request.set_result(answer.handle());
|
||||
|
||||
let transaction = request
|
||||
.transaction
|
||||
.get()
|
||||
.expect("Request unexpectedly has no transaction");
|
||||
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("success"),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
);
|
||||
|
||||
transaction.set_active_flag(true);
|
||||
event.upcast::<Event>().fire(request.upcast());
|
||||
transaction.set_active_flag(false);
|
||||
} else {
|
||||
request.set_result(answer.handle());
|
||||
|
||||
// FIXME:(rasviitanen)
|
||||
// Set the error of request to result
|
||||
|
||||
let transaction = request
|
||||
.transaction
|
||||
.get()
|
||||
.expect("Request has no transaction");
|
||||
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("error"),
|
||||
EventBubbles::Bubbles,
|
||||
EventCancelable::Cancelable,
|
||||
);
|
||||
|
||||
transaction.set_active_flag(true);
|
||||
event.upcast::<Event>().fire(request.upcast());
|
||||
transaction.set_active_flag(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBRequest {
|
||||
eventtarget: EventTarget,
|
||||
#[ignore_malloc_size_of = "mozjs"]
|
||||
result: Heap<JSVal>,
|
||||
error: MutNullableDom<DOMException>,
|
||||
source: MutNullableDom<IDBObjectStore>,
|
||||
transaction: MutNullableDom<IDBTransaction>,
|
||||
ready_state: Cell<IDBRequestReadyState>,
|
||||
}
|
||||
|
||||
impl IDBRequest {
|
||||
pub fn new_inherited() -> IDBRequest {
|
||||
IDBRequest {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
|
||||
result: Heap::default(),
|
||||
error: Default::default(),
|
||||
source: Default::default(),
|
||||
transaction: Default::default(),
|
||||
ready_state: Cell::new(IDBRequestReadyState::Pending),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope) -> DomRoot<IDBRequest> {
|
||||
reflect_dom_object(Box::new(IDBRequest::new_inherited()), global)
|
||||
}
|
||||
|
||||
pub fn set_source(&self, source: Option<&IDBObjectStore>) {
|
||||
self.source.set(source);
|
||||
}
|
||||
|
||||
pub fn set_ready_state_done(&self) {
|
||||
self.ready_state.set(IDBRequestReadyState::Done);
|
||||
}
|
||||
|
||||
pub fn set_result(&self, result: HandleValue) {
|
||||
self.result.set(result.get());
|
||||
}
|
||||
|
||||
pub fn set_error(&self, error: Error) {
|
||||
// FIXME:(rasviitanen) Support all error types
|
||||
if let Error::Version = error {
|
||||
self.error.set(Some(&DOMException::new(
|
||||
&self.global(),
|
||||
DOMErrorName::VersionError,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_transaction(&self, transaction: &IDBTransaction) {
|
||||
self.transaction.set(Some(transaction));
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#asynchronously-execute-a-request
|
||||
pub fn execute_async(
|
||||
source: &IDBObjectStore,
|
||||
operation: AsyncOperation,
|
||||
request: Option<DomRoot<IDBRequest>>,
|
||||
) -> Fallible<DomRoot<IDBRequest>> {
|
||||
// Step 1: Let transaction be the transaction associated with source.
|
||||
let transaction = source.transaction().expect("Store has no transaction");
|
||||
let global = transaction.global();
|
||||
|
||||
// Step 2: Assert: transaction is active.
|
||||
if !transaction.is_active() {
|
||||
return Err(Error::TransactionInactive);
|
||||
}
|
||||
|
||||
// Step 3: If request was not given, let request be a new request with source as source.
|
||||
let request = request.unwrap_or({
|
||||
let new_request = IDBRequest::new(&global);
|
||||
new_request.set_source(Some(source));
|
||||
new_request.set_transaction(&transaction);
|
||||
new_request
|
||||
});
|
||||
|
||||
// Step 4: Add request to the end of transaction’s request list.
|
||||
transaction.add_request(&request);
|
||||
|
||||
// Step 5: Run the operation, and queue a returning task in parallel
|
||||
// the result will be put into `receiver`
|
||||
let transaction_mode = match transaction.get_mode() {
|
||||
IDBTransactionMode::Readonly => IndexedDBTxnMode::Readonly,
|
||||
IDBTransactionMode::Readwrite => IndexedDBTxnMode::Readwrite,
|
||||
IDBTransactionMode::Versionchange => IndexedDBTxnMode::Versionchange,
|
||||
};
|
||||
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
|
||||
let response_listener = RequestListener {
|
||||
request: Trusted::new(&request),
|
||||
};
|
||||
|
||||
let task_source = global.database_access_task_source();
|
||||
let canceller = global.task_canceller(DatabaseAccessTaskSource::NAME);
|
||||
|
||||
ROUTER.add_route(
|
||||
receiver.to_opaque(),
|
||||
Box::new(move |message| {
|
||||
let response_listener = response_listener.clone();
|
||||
let _ = task_source.queue_with_canceller(
|
||||
task!(request_callback: move || {
|
||||
response_listener.handle_async_request_finished(
|
||||
message.to().expect("Could not unwrap message"));
|
||||
}),
|
||||
&canceller,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
transaction
|
||||
.global()
|
||||
.resource_threads()
|
||||
.sender()
|
||||
.send(IndexedDBThreadMsg::Async(
|
||||
sender,
|
||||
global.origin().immutable().clone(),
|
||||
transaction.get_db_name().to_string(),
|
||||
source.get_name().to_string(),
|
||||
transaction.get_serial_number(),
|
||||
transaction_mode,
|
||||
operation,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
// Step 6
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBRequestMethods for IDBRequest {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-result
|
||||
fn Result(&self, _cx: SafeJSContext) -> JSVal {
|
||||
self.result.get()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-error
|
||||
fn GetError(&self) -> Option<DomRoot<DOMException>> {
|
||||
self.error.get()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-source
|
||||
fn GetSource(&self) -> Option<DomRoot<IDBObjectStore>> {
|
||||
self.source.get()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-transaction
|
||||
fn GetTransaction(&self) -> Option<DomRoot<IDBTransaction>> {
|
||||
self.transaction.get()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-readystate
|
||||
fn ReadyState(&self) -> IDBRequestReadyState {
|
||||
self.ready_state.get()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onsuccess
|
||||
event_handler!(success, GetOnsuccess, SetOnsuccess);
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbrequest-onerror
|
||||
event_handler!(error, GetOnerror, SetOnerror);
|
||||
}
|
320
components/script/dom/idbtransaction.rs
Normal file
320
components/script/dom/idbtransaction.rs
Normal file
|
@ -0,0 +1,320 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use net_traits::indexeddb_thread::{IndexedDBThreadMsg, IndexedDBThreadReturnType, SyncOperation};
|
||||
use net_traits::IpcSend;
|
||||
use profile_traits::ipc;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::DOMStringListBinding::DOMStringListMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBDatabaseBinding::IDBTransactionDurability;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBTransactionBinding::{
|
||||
IDBTransactionMethods, IDBTransactionMode,
|
||||
};
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::domexception::DOMException;
|
||||
use crate::dom::domstringlist::DOMStringList;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbdatabase::IDBDatabase;
|
||||
use crate::dom::idbobjectstore::IDBObjectStore;
|
||||
use crate::dom::idbrequest::IDBRequest;
|
||||
use crate::task_source::TaskSource;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBTransaction {
|
||||
eventtarget: EventTarget,
|
||||
object_store_names: DomRoot<DOMStringList>,
|
||||
mode: IDBTransactionMode,
|
||||
db: Dom<IDBDatabase>,
|
||||
error: MutNullableDom<DOMException>,
|
||||
|
||||
// Not specified in WebIDL below this line
|
||||
store_handles: DomRefCell<HashMap<String, Dom<IDBObjectStore>>>,
|
||||
// https://www.w3.org/TR/IndexedDB-2/#transaction-request-list
|
||||
requests: DomRefCell<Vec<Dom<IDBRequest>>>,
|
||||
// https://www.w3.org/TR/IndexedDB-2/#transaction-active-flag
|
||||
active: Cell<bool>,
|
||||
// https://www.w3.org/TR/IndexedDB-2/#transaction-finish
|
||||
finished: Cell<bool>,
|
||||
// An unique identifier, used to commit and revert this transaction
|
||||
// FIXME:(rasviitanen) Replace this with a channel
|
||||
serial_number: u64,
|
||||
}
|
||||
|
||||
impl IDBTransaction {
|
||||
fn new_inherited(
|
||||
connection: &IDBDatabase,
|
||||
mode: IDBTransactionMode,
|
||||
scope: DomRoot<DOMStringList>,
|
||||
serial_number: u64,
|
||||
) -> IDBTransaction {
|
||||
IDBTransaction {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
object_store_names: scope,
|
||||
mode: mode,
|
||||
db: Dom::from_ref(connection),
|
||||
error: Default::default(),
|
||||
|
||||
store_handles: Default::default(),
|
||||
requests: Default::default(),
|
||||
active: Cell::new(true),
|
||||
finished: Cell::new(false),
|
||||
serial_number: serial_number,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
connection: &IDBDatabase,
|
||||
mode: IDBTransactionMode,
|
||||
scope: DomRoot<DOMStringList>,
|
||||
) -> DomRoot<IDBTransaction> {
|
||||
let serial_number = IDBTransaction::register_new(&global, connection.get_name());
|
||||
reflect_dom_object(
|
||||
Box::new(IDBTransaction::new_inherited(
|
||||
connection,
|
||||
mode,
|
||||
scope,
|
||||
serial_number,
|
||||
)),
|
||||
global,
|
||||
)
|
||||
}
|
||||
|
||||
// Registers a new transaction in the idb thread, and gets an unique serial number in return.
|
||||
// The serial number is used when placing requests against a transaction
|
||||
// and allows us to commit/abort transactions running in our idb thread.
|
||||
// FIXME:(rasviitanen) We could probably replace this with a channel instead,
|
||||
// and queue requests directly to that channel.
|
||||
fn register_new(global: &GlobalScope, db_name: DOMString) -> u64 {
|
||||
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
|
||||
|
||||
global
|
||||
.resource_threads()
|
||||
.sender()
|
||||
.send(IndexedDBThreadMsg::Sync(SyncOperation::RegisterNewTxn(
|
||||
sender,
|
||||
global.origin().immutable().clone(),
|
||||
db_name.to_string(),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
receiver.recv().unwrap()
|
||||
}
|
||||
|
||||
// Runs the transaction and waits for it to finish
|
||||
pub fn wait(&self) {
|
||||
// Start the transaction
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
|
||||
let start_operation = SyncOperation::StartTransaction(
|
||||
sender,
|
||||
self.global().origin().immutable().clone(),
|
||||
self.db.get_name().to_string(),
|
||||
self.serial_number,
|
||||
);
|
||||
|
||||
self.get_idb_thread()
|
||||
.send(IndexedDBThreadMsg::Sync(start_operation))
|
||||
.unwrap();
|
||||
|
||||
// Wait for transaction to complete
|
||||
if receiver.recv().is_err() {
|
||||
warn!("IDBtransaction failed to run");
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_active_flag(&self, status: bool) {
|
||||
self.active.set(status)
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.active.get()
|
||||
}
|
||||
|
||||
pub fn get_mode(&self) -> IDBTransactionMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
pub fn get_db_name(&self) -> DOMString {
|
||||
self.db.get_name()
|
||||
}
|
||||
|
||||
pub fn get_serial_number(&self) -> u64 {
|
||||
self.serial_number
|
||||
}
|
||||
|
||||
pub fn add_request(&self, request: &IDBRequest) {
|
||||
self.requests.borrow_mut().push(Dom::from_ref(request));
|
||||
}
|
||||
|
||||
pub fn upgrade_db_version(&self, version: u64) {
|
||||
// Runs the previous request and waits for them to finish
|
||||
self.wait();
|
||||
// Queue a request to upgrade the db version
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
let upgrade_version_operation = SyncOperation::UpgradeVersion(
|
||||
sender,
|
||||
self.global().origin().immutable().clone(),
|
||||
self.db.get_name().to_string(),
|
||||
self.serial_number,
|
||||
version,
|
||||
);
|
||||
self.get_idb_thread()
|
||||
.send(IndexedDBThreadMsg::Sync(upgrade_version_operation))
|
||||
.unwrap();
|
||||
// Wait for the version to be updated
|
||||
receiver.recv().unwrap();
|
||||
}
|
||||
|
||||
fn dispatch_complete(&self) {
|
||||
let global = self.global();
|
||||
let this = Trusted::new(self);
|
||||
global
|
||||
.database_access_task_source()
|
||||
.queue(
|
||||
task!(send_complete_notification: move || {
|
||||
let this = this.root();
|
||||
let global = this.global();
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("complete"),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
);
|
||||
event.upcast::<Event>().fire(this.upcast());
|
||||
}),
|
||||
global.upcast(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn get_idb_thread(&self) -> IpcSender<IndexedDBThreadMsg> {
|
||||
self.global().resource_threads().sender()
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBTransactionMethods for IDBTransaction {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-db
|
||||
fn Db(&self) -> DomRoot<IDBDatabase> {
|
||||
DomRoot::from_ref(&*self.db)
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-objectstore
|
||||
fn ObjectStore(&self, name: DOMString) -> Fallible<DomRoot<IDBObjectStore>> {
|
||||
// Step 1: Handle the case where transaction has finised
|
||||
if self.finished.get() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
// Step 2: Check that the object store exists
|
||||
if !self.object_store_names.Contains(name.clone()) {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
// Step 3: Each call to this method on the same
|
||||
// IDBTransaction instance with the same name
|
||||
// returns the same IDBObjectStore instance.
|
||||
let mut store_handles = self.store_handles.borrow_mut();
|
||||
let store = store_handles.entry(name.to_string()).or_insert({
|
||||
let store = IDBObjectStore::new(&self.global(), self.db.get_name(), name, None);
|
||||
store.set_transaction(&self);
|
||||
Dom::from_ref(&*store)
|
||||
});
|
||||
|
||||
Ok(DomRoot::from_ref(&*store))
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#commit-transaction
|
||||
fn Commit(&self) -> Fallible<()> {
|
||||
// Step 1
|
||||
let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap();
|
||||
let start_operation = SyncOperation::Commit(
|
||||
sender,
|
||||
self.global().origin().immutable().clone(),
|
||||
self.db.get_name().to_string(),
|
||||
self.serial_number,
|
||||
);
|
||||
|
||||
self.get_idb_thread()
|
||||
.send(IndexedDBThreadMsg::Sync(start_operation))
|
||||
.unwrap();
|
||||
|
||||
let result = receiver.recv().unwrap();
|
||||
|
||||
// Step 2
|
||||
if let IndexedDBThreadReturnType::Commit(Err(_result)) = result {
|
||||
// FIXME:(rasviitanen) also support Unknown error
|
||||
return Err(Error::QuotaExceeded);
|
||||
}
|
||||
|
||||
// Step 3
|
||||
// FIXME:(rasviitanen) https://www.w3.org/TR/IndexedDB-2/#commit-a-transaction
|
||||
|
||||
// Steps 3.1 and 3.3
|
||||
self.dispatch_complete();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-abort
|
||||
fn Abort(&self) -> Fallible<()> {
|
||||
// FIXME:(rasviitanen)
|
||||
// This only sets the flags, and does not abort the transaction
|
||||
// see https://www.w3.org/TR/IndexedDB-2/#abort-a-transaction
|
||||
if self.finished.get() {
|
||||
return Err(Error::InvalidState);
|
||||
}
|
||||
|
||||
self.active.set(false);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-objectstorenames
|
||||
fn ObjectStoreNames(&self) -> DomRoot<DOMStringList> {
|
||||
self.object_store_names.clone()
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode
|
||||
fn Mode(&self) -> IDBTransactionMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-mode
|
||||
fn Durability(&self) -> IDBTransactionDurability {
|
||||
// FIXME:(arihant2math) Durability is not implemented at all
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-error
|
||||
fn GetError(&self) -> Option<DomRoot<DOMException>> {
|
||||
// FIXME:(arihant2math) ???
|
||||
// It's weird that the WebIDL specifies that this isn't returning an Option.
|
||||
// "The error attribute’s getter must return this transaction's error, or null if none."
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onabort
|
||||
event_handler!(abort, GetOnabort, SetOnabort);
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-oncomplete
|
||||
event_handler!(complete, GetOncomplete, SetOncomplete);
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbtransaction-onerror
|
||||
event_handler!(error, GetOnerror, SetOnerror);
|
||||
}
|
104
components/script/dom/idbversionchangeevent.rs
Normal file
104
components/script/dom/idbversionchangeevent.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use servo_atoms::Atom;
|
||||
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::IDBVersionChangeEventBinding::{
|
||||
IDBVersionChangeEventInit, IDBVersionChangeEventMethods,
|
||||
};
|
||||
use crate::dom::bindings::import::module::HandleObject;
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::reflector::{
|
||||
reflect_dom_object, reflect_dom_object_with_proto, DomObject,
|
||||
};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::window::Window;
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBVersionChangeEvent {
|
||||
event: Event,
|
||||
old_version: u64,
|
||||
new_version: Option<u64>,
|
||||
}
|
||||
|
||||
impl IDBVersionChangeEvent {
|
||||
pub fn new_inherited(old_version: u64, new_version: Option<u64>) -> IDBVersionChangeEvent {
|
||||
IDBVersionChangeEvent {
|
||||
event: Event::new_inherited(),
|
||||
old_version: old_version,
|
||||
new_version: new_version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
type_: Atom,
|
||||
bubbles: EventBubbles,
|
||||
cancelable: EventCancelable,
|
||||
old_version: u64,
|
||||
new_version: Option<u64>,
|
||||
) -> DomRoot<IDBVersionChangeEvent> {
|
||||
let ev = reflect_dom_object(
|
||||
Box::new(IDBVersionChangeEvent::new_inherited(
|
||||
old_version,
|
||||
new_version,
|
||||
)),
|
||||
global,
|
||||
);
|
||||
{
|
||||
let event = ev.upcast::<Event>();
|
||||
event.init_event(type_, bool::from(bubbles), bool::from(cancelable));
|
||||
}
|
||||
ev
|
||||
}
|
||||
|
||||
fn new_with_proto(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
type_: Atom,
|
||||
init: &IDBVersionChangeEventInit,
|
||||
) -> DomRoot<Self> {
|
||||
let ev = reflect_dom_object_with_proto(
|
||||
Box::new(Self::new_inherited(init.oldVersion, init.newVersion)),
|
||||
global,
|
||||
proto,
|
||||
);
|
||||
{
|
||||
let event = ev.upcast::<Event>();
|
||||
event.init_event(type_, init.parent.bubbles, init.parent.cancelable);
|
||||
}
|
||||
ev
|
||||
}
|
||||
|
||||
pub fn Constructor(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
type_: DOMString,
|
||||
init: &IDBVersionChangeEventInit,
|
||||
) -> DomRoot<Self> {
|
||||
Self::new_with_proto(&window.global(), proto, Atom::from(type_), init)
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBVersionChangeEventMethods for IDBVersionChangeEvent {
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbversionchangeevent-oldversion
|
||||
fn OldVersion(&self) -> u64 {
|
||||
self.old_version
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/IndexedDB-2/#dom-idbversionchangeevent-newversion
|
||||
fn GetNewVersion(&self) -> Option<u64> {
|
||||
self.new_version
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-event-istrusted
|
||||
fn IsTrusted(&self) -> bool {
|
||||
self.event.IsTrusted()
|
||||
}
|
||||
}
|
|
@ -417,6 +417,13 @@ pub(crate) mod htmltrackelement;
|
|||
pub(crate) mod htmlulistelement;
|
||||
pub(crate) mod htmlunknownelement;
|
||||
pub(crate) mod htmlvideoelement;
|
||||
pub(crate) mod idbdatabase;
|
||||
pub(crate) mod idbfactory;
|
||||
pub(crate) mod idbobjectstore;
|
||||
pub(crate) mod idbopendbrequest;
|
||||
pub(crate) mod idbrequest;
|
||||
pub(crate) mod idbtransaction;
|
||||
pub(crate) mod idbversionchangeevent;
|
||||
pub(crate) mod iirfilternode;
|
||||
pub(crate) mod imagebitmap;
|
||||
pub(crate) mod imagedata;
|
||||
|
|
|
@ -136,6 +136,7 @@ use crate::dom::hashchangeevent::HashChangeEvent;
|
|||
use crate::dom::history::History;
|
||||
use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection};
|
||||
use crate::dom::htmliframeelement::HTMLIFrameElement;
|
||||
use crate::dom::idbfactory::IDBFactory;
|
||||
use crate::dom::location::Location;
|
||||
use crate::dom::medialist::MediaList;
|
||||
use crate::dom::mediaquerylist::{MediaQueryList, MediaQueryListMatchState};
|
||||
|
@ -246,6 +247,7 @@ pub(crate) struct Window {
|
|||
document: MutNullableDom<Document>,
|
||||
location: MutNullableDom<Location>,
|
||||
history: MutNullableDom<History>,
|
||||
indexeddb: MutNullableDom<IDBFactory>,
|
||||
custom_element_registry: MutNullableDom<CustomElementRegistry>,
|
||||
performance: MutNullableDom<Performance>,
|
||||
#[no_trace]
|
||||
|
@ -1077,6 +1079,14 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
|
|||
self.history.or_init(|| History::new(self, CanGc::note()))
|
||||
}
|
||||
|
||||
// https://w3c.github.io/IndexedDB/#factory-interface
|
||||
fn IndexedDB(&self) -> DomRoot<IDBFactory> {
|
||||
self.indexeddb.or_init(|| {
|
||||
let global_scope = self.upcast::<GlobalScope>();
|
||||
IDBFactory::new(global_scope)
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-window-customelements
|
||||
fn CustomElements(&self) -> DomRoot<CustomElementRegistry> {
|
||||
self.custom_element_registry
|
||||
|
@ -3069,6 +3079,7 @@ impl Window {
|
|||
navigator: Default::default(),
|
||||
location: Default::default(),
|
||||
history: Default::default(),
|
||||
indexeddb: Default::default(),
|
||||
custom_element_registry: Default::default(),
|
||||
window_proxy: Default::default(),
|
||||
document: Default::default(),
|
||||
|
|
|
@ -53,6 +53,7 @@ use crate::dom::bindings::trace::RootedTraceableBox;
|
|||
use crate::dom::crypto::Crypto;
|
||||
use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::idbfactory::IDBFactory;
|
||||
use crate::dom::performance::Performance;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::trustedscripturl::TrustedScriptURL;
|
||||
|
@ -127,6 +128,7 @@ pub(crate) struct WorkerGlobalScope {
|
|||
#[no_trace]
|
||||
navigation_start: CrossProcessInstant,
|
||||
performance: MutNullableDom<Performance>,
|
||||
indexeddb: MutNullableDom<IDBFactory>,
|
||||
trusted_types: MutNullableDom<TrustedTypePolicyFactory>,
|
||||
|
||||
/// A [`TimerScheduler`] used to schedule timers for this [`WorkerGlobalScope`].
|
||||
|
@ -188,6 +190,7 @@ impl WorkerGlobalScope {
|
|||
_devtools_sender: init.from_devtools_sender,
|
||||
navigation_start: CrossProcessInstant::now(),
|
||||
performance: Default::default(),
|
||||
indexeddb: Default::default(),
|
||||
timer_scheduler: RefCell::default(),
|
||||
insecure_requests_policy,
|
||||
trusted_types: Default::default(),
|
||||
|
@ -274,6 +277,14 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
|
|||
DomRoot::from_ref(self)
|
||||
}
|
||||
|
||||
// https://w3c.github.io/IndexedDB/#factory-interface
|
||||
fn IndexedDB(&self) -> DomRoot<IDBFactory> {
|
||||
self.indexeddb.or_init(|| {
|
||||
let global_scope = self.upcast::<GlobalScope>();
|
||||
IDBFactory::new(global_scope)
|
||||
})
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-location
|
||||
fn Location(&self) -> DomRoot<WorkerLocation> {
|
||||
self.location
|
||||
|
@ -586,4 +597,4 @@ impl WorkerGlobalScope {
|
|||
.task_manager()
|
||||
.cancel_all_tasks_and_ignore_future_tasks();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue