mirror of
https://github.com/servo/servo.git
synced 2025-08-07 22:45:34 +01:00
Initial IndexedDB Support (#33044)
Adds indexeddb support to servo. At the moment heed is being used as the backend, although this can be swapped out by implementing `KvsEngine`. This PR adds a thread + a thread pool for Indexeddb related operations. Also `database_access_task_source` is added for Indexeddb related operations. This is a partial rewrite of #25214. (Reopened due to branching issue) Fixes #6963 --------- Signed-off-by: Ashwin Naren <arihant2math@gmail.com> Signed-off-by: Josh Matthews <josh@joshmatthews.net> Co-authored-by: Rasmus Viitanen <rasviitanen@gmail.com> Co-authored-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
parent
d33cf8fbd1
commit
ed9a79f3f4
259 changed files with 11141 additions and 8 deletions
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::IpcSend;
|
||||
use net_traits::indexeddb_thread::{IndexedDBThreadMsg, SyncOperation};
|
||||
use profile_traits::ipc;
|
||||
use stylo_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::{DomGlobal, reflect_dom_object};
|
||||
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::js::conversions::ToJSValConvertible;
|
||||
use crate::realms::enter_realm;
|
||||
use crate::script_runtime::CanGc;
|
||||
|
||||
#[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,
|
||||
can_gc: CanGc,
|
||||
) -> (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,
|
||||
can_gc,
|
||||
);
|
||||
|
||||
// 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, CanGc::note());
|
||||
// Step 11
|
||||
(Ok(connection), true)
|
||||
} else {
|
||||
// Step 11
|
||||
(Ok(connection), false)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_delete_db(&self, result: Result<(), ()>, can_gc: CanGc) {
|
||||
let open_request = self.open_request.root();
|
||||
let global = open_request.global();
|
||||
open_request.idbrequest.set_ready_state_done();
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let _ac = enter_realm(&*open_request);
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
open_request
|
||||
.set_result(js::gc::Handle::from_raw(js::jsapi::UndefinedHandleValue));
|
||||
}
|
||||
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("success"),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable,
|
||||
can_gc,
|
||||
);
|
||||
event.fire(open_request.upcast(), can_gc);
|
||||
},
|
||||
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,
|
||||
can_gc,
|
||||
);
|
||||
event.fire(open_request.upcast(), can_gc);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[dom_struct]
|
||||
pub struct IDBOpenDBRequest {
|
||||
idbrequest: IDBRequest,
|
||||
}
|
||||
|
||||
impl IDBOpenDBRequest {
|
||||
pub fn new_inherited() -> IDBOpenDBRequest {
|
||||
IDBOpenDBRequest {
|
||||
idbrequest: IDBRequest::new_inherited(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<IDBOpenDBRequest> {
|
||||
reflect_dom_object(Box::new(IDBOpenDBRequest::new_inherited()), global, can_gc)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
// https://www.w3.org/TR/IndexedDB-2/#run-an-upgrade-transaction
|
||||
fn upgrade_db_version(&self, connection: &IDBDatabase, version: u64, can_gc: CanGc) {
|
||||
let global = self.global();
|
||||
// Step 2
|
||||
let transaction = IDBTransaction::new(
|
||||
&global,
|
||||
connection,
|
||||
IDBTransactionMode::Versionchange,
|
||||
&connection.object_stores(),
|
||||
can_gc,
|
||||
);
|
||||
|
||||
// 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.task_manager().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),
|
||||
CanGc::note(),
|
||||
);
|
||||
|
||||
// Step 8.4
|
||||
txn.set_active_flag(true);
|
||||
// Step 8.5
|
||||
let _did_throw = event.upcast::<Event>().fire(this.upcast(), CanGc::note());
|
||||
// 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);
|
||||
}),
|
||||
);
|
||||
|
||||
// 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, can_gc: CanGc) {
|
||||
self.idbrequest.set_error(error, can_gc);
|
||||
}
|
||||
|
||||
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
|
||||
.task_manager()
|
||||
.database_access_task_source()
|
||||
.to_sendable();
|
||||
|
||||
let trusted_request = Trusted::new(self);
|
||||
let name = name.to_string();
|
||||
ROUTER.add_typed_route(
|
||||
receiver.to_ipc_receiver(),
|
||||
Box::new(move |message| {
|
||||
let trusted_request = trusted_request.clone();
|
||||
let response_listener = response_listener.clone();
|
||||
let name = name.clone();
|
||||
|
||||
task_source.queue(
|
||||
task!(set_request_result_to_database: move || {
|
||||
let (result, did_upgrade) =
|
||||
response_listener.handle_open_db(name, version, message.unwrap(), CanGc::note());
|
||||
// If an upgrade event was created, it will be responsible for
|
||||
// dispatching the success event
|
||||
if did_upgrade {
|
||||
return;
|
||||
}
|
||||
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, CanGc::note());
|
||||
let event = Event::new(
|
||||
&global,
|
||||
Atom::from("error"),
|
||||
EventBubbles::Bubbles,
|
||||
EventCancelable::Cancelable,
|
||||
CanGc::note()
|
||||
);
|
||||
event.fire(request.upcast(), CanGc::note());
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
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
|
||||
.task_manager()
|
||||
.database_access_task_source()
|
||||
.to_sendable();
|
||||
let response_listener = OpenRequestListener {
|
||||
open_request: Trusted::new(self),
|
||||
};
|
||||
|
||||
let delete_operation =
|
||||
SyncOperation::DeleteDatabase(sender, global.origin().immutable().clone(), name);
|
||||
|
||||
ROUTER.add_typed_route(
|
||||
receiver.to_ipc_receiver(),
|
||||
Box::new(move |message| {
|
||||
let response_listener = response_listener.clone();
|
||||
task_source.queue(task!(request_callback: move || {
|
||||
response_listener.handle_delete_db(message.unwrap(), CanGc::note());
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
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.task_manager().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,
|
||||
CanGc::note()
|
||||
);
|
||||
event.fire(this.upcast(), CanGc::note());
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl IDBOpenDBRequestMethods<crate::DomTypeHolder> 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue