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:
Ashwin Naren 2024-08-13 12:14:39 -07:00
parent cdff75bbd4
commit 0a79918849
No known key found for this signature in database
GPG key ID: D96D7DE56FBCB6B6
36 changed files with 3108 additions and 9 deletions

View file

@ -85,6 +85,7 @@ pub struct Preferences {
pub dom_fullscreen_test: bool, pub dom_fullscreen_test: bool,
pub dom_gamepad_enabled: bool, pub dom_gamepad_enabled: bool,
pub dom_imagebitmap_enabled: bool, pub dom_imagebitmap_enabled: bool,
pub dom_indexeddb_enabled: bool,
pub dom_intersection_observer_enabled: bool, pub dom_intersection_observer_enabled: bool,
pub dom_microdata_testing_enabled: bool, pub dom_microdata_testing_enabled: bool,
pub dom_mouse_event_which_enabled: bool, pub dom_mouse_event_which_enabled: bool,
@ -262,6 +263,7 @@ impl Preferences {
dom_fullscreen_test: false, dom_fullscreen_test: false,
dom_gamepad_enabled: true, dom_gamepad_enabled: true,
dom_imagebitmap_enabled: false, dom_imagebitmap_enabled: false,
dom_indexeddb_enabled: false,
dom_intersection_observer_enabled: false, dom_intersection_observer_enabled: false,
dom_microdata_testing_enabled: false, dom_microdata_testing_enabled: false,
dom_mouse_event_which_enabled: false, dom_mouse_event_which_enabled: false,

View file

@ -47,7 +47,8 @@ mod font_context {
let (system_font_service, system_font_service_proxy) = MockSystemFontService::spawn(); let (system_font_service, system_font_service_proxy) = MockSystemFontService::spawn();
let (core_sender, _) = ipc::channel().unwrap(); let (core_sender, _) = ipc::channel().unwrap();
let (storage_sender, _) = ipc::channel().unwrap(); let (storage_sender, _) = ipc::channel().unwrap();
let mock_resource_threads = ResourceThreads::new(core_sender, storage_sender); let (indexeddb_sender, _) = ipc::channel().unwrap();
let mock_resource_threads = ResourceThreads::new(core_sender, storage_sender, indexeddb_sender);
let mock_compositor_api = CrossProcessCompositorApi::dummy(); let mock_compositor_api = CrossProcessCompositorApi::dummy();
let proxy_clone = Arc::new(system_font_service_proxy.to_sender().to_proxy()); let proxy_clone = Arc::new(system_font_service_proxy.to_sender().to_proxy());

View file

@ -53,6 +53,7 @@ net_traits = { workspace = true }
pixels = { path = "../pixels" } pixels = { path = "../pixels" }
profile_traits = { workspace = true } profile_traits = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
rkv = "0.10"
rustls = { workspace = true } rustls = { workspace = true }
rustls-pemfile = { workspace = true } rustls-pemfile = { workspace = true }
rustls-pki-types = { workspace = true } rustls-pki-types = { workspace = true }

View file

@ -0,0 +1,66 @@
/* 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::collections::VecDeque;
use ipc_channel::ipc::IpcSender;
use net_traits::indexeddb_thread::{AsyncOperation, IndexedDBTxnMode};
use tokio::sync::oneshot;
pub use self::rkv::RkvEngine;
mod rkv;
#[derive(Eq, Hash, PartialEq)]
pub struct SanitizedName {
name: String,
}
impl SanitizedName {
pub fn new(name: String) -> SanitizedName {
let name = name.replace("https://", "");
let name = name.replace("http://", "");
// FIXME:(rasviitanen) Disallowing special characters might be a big problem,
// but better safe than sorry. E.g. the db name '../other_origin/db',
// would let us access databases from another origin.
let name = name
.chars()
.map(|c| match c {
'A'..='Z' => c,
'a'..='z' => c,
'0'..='9' => c,
'-' => c,
'_' => c,
_ => '-',
})
.collect();
SanitizedName { name }
}
pub fn to_string(&self) -> String {
self.name.clone()
}
}
pub struct KvsOperation {
pub sender: IpcSender<Option<Vec<u8>>>,
pub store_name: SanitizedName,
pub operation: AsyncOperation,
}
pub struct KvsTransaction {
pub mode: IndexedDBTxnMode,
pub requests: VecDeque<KvsOperation>,
}
pub trait KvsEngine {
fn create_store(&self, store_name: SanitizedName, auto_increment: bool);
fn process_transaction(
&self,
transaction: KvsTransaction,
) -> oneshot::Receiver<Option<Vec<u8>>>;
fn has_key_generator(&self, store_name: SanitizedName) -> bool;
}

View file

@ -0,0 +1,211 @@
/* 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::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use log::warn;
use net_traits::indexeddb_thread::{AsyncOperation, IndexedDBKeyType, IndexedDBTxnMode};
use rkv::{Manager, Rkv, SingleStore, StoreOptions, Value};
use tokio::sync::oneshot;
use super::{KvsEngine, KvsTransaction, SanitizedName};
// A simple store that also has a key generator that can be used if no key
// is provided for the stored objects
#[derive(Clone)]
struct Store {
inner: SingleStore,
key_generator: Option<u64>, // https://www.w3.org/TR/IndexedDB-2/#key-generator
}
pub struct RkvEngine {
rkv_handle: Arc<RwLock<Rkv>>,
open_stores: Arc<RwLock<HashMap<SanitizedName, Store>>>,
pool: rayon::ThreadPool,
}
impl RkvEngine {
pub fn new(base_dir: &Path, db_file_name: &Path) -> Self {
let mut db_dir = PathBuf::new();
db_dir.push(base_dir);
db_dir.push(db_file_name);
std::fs::create_dir_all(&db_dir).expect("Could not create OS directory for idb");
let rkv_handle = Manager::singleton()
.write()
.expect("Could not get write lock")
.get_or_create(db_dir.as_path(), Rkv::new)
.expect("Could not create database with this origin");
// FIXME:(rasviitanen) What is a reasonable number of threads?
RkvEngine {
rkv_handle,
open_stores: Arc::new(RwLock::new(HashMap::new())),
pool: rayon::ThreadPoolBuilder::new()
.num_threads(16)
.build()
.expect("Could not create IDBTransaction thread pool"),
}
}
}
impl KvsEngine for RkvEngine {
fn create_store(&self, store_name: SanitizedName, auto_increment: bool) {
let rkv = self.rkv_handle.read().unwrap();
let new_store = rkv
.open_single(&*store_name.to_string(), StoreOptions::create())
.unwrap();
let key_generator = {
if auto_increment {
Some(0)
} else {
None
}
};
let store = Store {
inner: new_store,
key_generator,
};
self.open_stores
.write()
.expect("Could not aquire lock")
.insert(store_name, store);
}
fn has_key_generator(&self, store_name: SanitizedName) -> bool {
let stores = self
.open_stores
.read()
.expect("Could not aquire read lock on stores");
stores
.get(&store_name)
.expect("Store not found")
.key_generator
.is_some()
}
// Starts a transaction, processes all operations for that transaction,
// and commits the changes.
fn process_transaction(
&self,
transaction: KvsTransaction,
) -> oneshot::Receiver<Option<Vec<u8>>> {
let db_handle = self.rkv_handle.clone();
let stores = self.open_stores.clone();
let (tx, rx) = oneshot::channel();
self.pool.spawn(move || {
let db = db_handle
.read()
.expect("Could not aquire read lock on idb handle");
let stores = stores.read().expect("Could not aquire read lock on stores");
if let IndexedDBTxnMode::Readonly = transaction.mode {
let reader = db.read().expect("Could not create reader for idb");
for request in transaction.requests {
match request.operation {
AsyncOperation::GetItem(key) => {
let store = stores
.get(&request.store_name)
.expect("Could not get store");
let result = store.inner.get(&reader, key).expect("Could not get item");
if let Some(Value::Blob(blob)) = result {
request.sender.send(Some(blob.to_vec())).unwrap();
} else {
request.sender.send(None).unwrap();
}
},
_ => {
// We cannot reach this, as checks are made earlier so that
// no modifying requests are executed on readonly transactions
unreachable!(
"Cannot execute modifying request with readonly transactions"
);
},
}
}
} else {
// Aquiring a writer will block the thread if another `readwrite` transaction is active
let mut writer = db.write().expect("Could not create writer for idb");
for request in transaction.requests {
match request.operation {
AsyncOperation::PutItem(key, value, overwrite) => {
let store = stores
.get(&request.store_name)
.expect("Could not get store");
let key = match key {
IndexedDBKeyType::String(inner) => inner,
IndexedDBKeyType::Number(inner) => inner,
IndexedDBKeyType::Binary(inner) => inner,
};
if overwrite {
let result = store
.inner
.put(&mut writer, key.clone(), &Value::Blob(&value))
.ok()
.and(Some(key));
request.sender.send(result).unwrap();
} else {
// FIXME:(rasviitanen) We should be able to set some flags in
// `rkv` in order to do this without running a get request first
if store
.inner
.get(&writer, key.clone())
.expect("Could not get item")
.is_none()
{
let result = store
.inner
.put(&mut writer, key.clone(), &Value::Blob(&value))
.ok()
.and(Some(key));
request.sender.send(result).unwrap();
} else {
request.sender.send(None).unwrap();
}
}
},
AsyncOperation::GetItem(key) => {
let store = stores
.get(&request.store_name)
.expect("Could not get store");
let result = store.inner.get(&writer, key).expect("Could not get item");
if let Some(Value::Blob(blob)) = result {
request.sender.send(Some(blob.to_vec())).unwrap();
} else {
request.sender.send(None).unwrap();
}
},
AsyncOperation::RemoveItem(key) => {
let store = stores
.get(&request.store_name)
.expect("Could not get store");
let result = store
.inner
.delete(&mut writer, key.clone())
.ok()
.and(Some(key));
request.sender.send(result).unwrap();
},
}
}
writer.commit().expect("Failed to commit to database");
}
if tx.send(None).is_err() {
warn!("IDBTransaction's execution channel is dropped");
};
});
rx
}
}

View file

@ -0,0 +1,323 @@
/* 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::borrow::ToOwned;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, VecDeque};
use std::path::PathBuf;
use std::thread;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use log::warn;
use net_traits::indexeddb_thread::{
AsyncOperation, IndexedDBThreadMsg, IndexedDBThreadReturnType, IndexedDBTxnMode, SyncOperation,
};
use servo_config::pref;
use servo_url::origin::ImmutableOrigin;
use crate::indexeddb::engines::{
KvsEngine, KvsOperation, KvsTransaction, RkvEngine, SanitizedName,
};
pub trait IndexedDBThreadFactory {
fn new(config_dir: Option<PathBuf>) -> Self;
}
impl IndexedDBThreadFactory for IpcSender<IndexedDBThreadMsg> {
fn new(config_dir: Option<PathBuf>) -> IpcSender<IndexedDBThreadMsg> {
let (chan, port) = ipc::channel().unwrap();
let mut idb_base_dir = PathBuf::new();
config_dir.map(|p| idb_base_dir.push(p));
idb_base_dir.push("IndexedDB");
thread::Builder::new()
.name("IndexedDBManager".to_owned())
.spawn(move || {
IndexedDBManager::new(port, idb_base_dir).start();
})
.expect("Thread spawning failed");
chan
}
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct IndexedDBDescription {
origin: ImmutableOrigin,
name: String,
}
impl IndexedDBDescription {
// Converts the database description to a folder name where all
// data for this database is stored
fn as_path(&self) -> PathBuf {
let mut path = PathBuf::new();
let sanitized_origin = SanitizedName::new(self.origin.ascii_serialization());
let sanitized_name = SanitizedName::new(self.name.clone());
path.push(sanitized_origin.to_string());
path.push(sanitized_name.to_string());
path
}
}
struct IndexedDBEnvironment<E: KvsEngine> {
engine: E,
version: u64,
transactions: HashMap<u64, KvsTransaction>,
serial_number_counter: u64,
}
impl<E: KvsEngine> IndexedDBEnvironment<E> {
fn new(engine: E, version: u64) -> IndexedDBEnvironment<E> {
IndexedDBEnvironment {
engine,
version,
transactions: HashMap::new(),
serial_number_counter: 0,
}
}
fn queue_operation(
&mut self,
sender: IpcSender<Option<Vec<u8>>>,
store_name: SanitizedName,
serial_number: u64,
mode: IndexedDBTxnMode,
operation: AsyncOperation,
) {
self.transactions
.entry(serial_number)
.or_insert(KvsTransaction {
requests: VecDeque::new(),
mode,
})
.requests
.push_back(KvsOperation {
sender,
operation,
store_name,
});
}
// Executes all requests for a transaction (without committing)
fn start_transaction(&mut self, txn: u64, sender: Option<IpcSender<Result<(), ()>>>) {
// FIXME:(rasviitanen)
// This executes in a thread pool, and `readwrite` transactions
// will block their thread if the writer is occupied, so we can
// probably do some smart things here in order to optimize.
// Queueuing 8 writers will for example block 7 threads,
// so we should probably reserve write operations for just one thread,
// so that the rest of the threads can work in parallel with read txns.
self.transactions.remove(&txn).map(|txn| {
self.engine.process_transaction(txn).blocking_recv();
});
// We have a sender if the transaction is started manually, and they
// probably want to know when it is finished
if let Some(sender) = sender {
if sender.send(Ok(())).is_err() {
warn!("IDBTransaction starter dropped its channel");
}
}
}
fn has_key_generator(&self, store_name: SanitizedName) -> bool {
self.engine.has_key_generator(store_name)
}
fn create_object_store(
&mut self,
sender: IpcSender<Result<(), ()>>,
store_name: SanitizedName,
auto_increment: bool,
) {
self.engine.create_store(store_name, auto_increment);
sender.send(Ok(())).unwrap();
}
}
struct IndexedDBManager {
port: IpcReceiver<IndexedDBThreadMsg>,
idb_base_dir: PathBuf,
databases: HashMap<IndexedDBDescription, IndexedDBEnvironment<RkvEngine>>,
}
impl IndexedDBManager {
fn new(port: IpcReceiver<IndexedDBThreadMsg>, idb_base_dir: PathBuf) -> IndexedDBManager {
IndexedDBManager {
port: port,
idb_base_dir: idb_base_dir,
databases: HashMap::new(),
}
}
}
impl IndexedDBManager {
fn start(&mut self) {
if pref!(dom.indexeddb.enabled) {
loop {
let message = self.port.recv().expect("No message");
match message {
IndexedDBThreadMsg::Sync(operation) => {
self.handle_sync_operation(operation);
},
IndexedDBThreadMsg::Async(
sender,
origin,
db_name,
store_name,
txn,
mode,
operation,
) => {
let store_name = SanitizedName::new(store_name);
self.get_database_mut(origin, db_name).map(|db| {
// Queues an operation for a transaction without starting it
db.queue_operation(sender, store_name, txn, mode, operation);
// FIXME:(rasviitanen) Schedule transactions properly:
// for now, we start them directly.
db.start_transaction(txn, None);
});
},
}
}
}
}
fn get_database(
&self,
origin: ImmutableOrigin,
db_name: String,
) -> Option<&IndexedDBEnvironment<RkvEngine>> {
let idb_description = IndexedDBDescription {
origin: origin,
name: db_name,
};
self.databases.get(&idb_description)
}
fn get_database_mut(
&mut self,
origin: ImmutableOrigin,
db_name: String,
) -> Option<&mut IndexedDBEnvironment<RkvEngine>> {
let idb_description = IndexedDBDescription {
origin: origin,
name: db_name,
};
self.databases.get_mut(&idb_description)
}
fn handle_sync_operation(&mut self, operation: SyncOperation) {
match operation {
SyncOperation::OpenDatabase(sender, origin, db_name, version) => {
let idb_description = IndexedDBDescription {
origin: origin,
name: db_name,
};
let idb_base_dir = self.idb_base_dir.as_path();
match self.databases.entry(idb_description.clone()) {
Entry::Vacant(e) => {
let db = IndexedDBEnvironment::new(
RkvEngine::new(idb_base_dir, &idb_description.as_path()),
version.unwrap_or(0),
);
sender.send(db.version).unwrap();
e.insert(db);
},
Entry::Occupied(db) => {
sender.send(db.get().version).unwrap();
},
}
},
SyncOperation::DeleteDatabase(sender, origin, db_name) => {
let idb_description = IndexedDBDescription {
origin: origin,
name: db_name,
};
self.databases.remove(&idb_description);
// FIXME:(rasviitanen) Possible sercurity issue?
let mut db_dir = self.idb_base_dir.clone();
db_dir.push(&idb_description.as_path());
if std::fs::remove_dir_all(&db_dir).is_err() {
sender.send(Err(())).unwrap();
} else {
sender.send(Ok(())).unwrap();
}
},
SyncOperation::HasKeyGenerator(sender, origin, db_name, store_name) => {
let store_name = SanitizedName::new(store_name);
let result = self
.get_database(origin, db_name)
.map(|db| db.has_key_generator(store_name))
.expect("No Database");
sender.send(result).expect("Could not send generator info");
},
SyncOperation::Commit(sender, _origin, _db_name, _txn) => {
// FIXME:(rasviitanen) This does nothing at the moment
sender
.send(IndexedDBThreadReturnType::Commit(Err(())))
.expect("Could not send commit status");
},
SyncOperation::UpgradeVersion(sender, origin, db_name, _txn, version) => {
self.get_database_mut(origin, db_name).map(|db| {
db.version = version;
});
// FIXME:(rasviitanen) Get the version from the database instead
// We never fail as of now, so we can just return it like this
// for now...
sender
.send(IndexedDBThreadReturnType::UpgradeVersion(Ok(version)))
.expect("Could not upgrade version");
},
SyncOperation::CreateObjectStore(
sender,
origin,
db_name,
store_name,
auto_increment,
) => {
let store_name = SanitizedName::new(store_name);
self.get_database_mut(origin, db_name)
.map(|db| db.create_object_store(sender, store_name, auto_increment));
},
SyncOperation::StartTransaction(sender, origin, db_name, txn) => {
self.get_database_mut(origin, db_name).map(|db| {
db.start_transaction(txn, Some(sender));
});
},
SyncOperation::Version(sender, origin, db_name) => {
self.get_database(origin, db_name)
.map(|db| {
sender.send(db.version).unwrap();
})
.unwrap();
},
SyncOperation::RegisterNewTxn(sender, origin, db_name) => self
.get_database_mut(origin, db_name)
.map(|db| {
db.serial_number_counter += 1;
sender
.send(db.serial_number_counter)
.expect("Could not send serial number");
})
.unwrap(),
SyncOperation::Exit(sender) => {
// FIXME:(rasviitanen) Nothing to do?
let _ = sender.send(IndexedDBThreadReturnType::Exit).unwrap();
},
}
}
}

View file

@ -0,0 +1,9 @@
/* 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/. */
pub use self::idb_thread::IndexedDBThreadFactory;
mod engines;
pub mod idb_thread;

View file

@ -15,6 +15,7 @@ pub mod hsts;
pub mod http_cache; pub mod http_cache;
pub mod http_loader; pub mod http_loader;
pub mod image_cache; pub mod image_cache;
pub mod indexeddb;
pub mod local_directory_listing; pub mod local_directory_listing;
pub mod mime_classifier; pub mod mime_classifier;
pub mod protocols; pub mod protocols;

View file

@ -23,6 +23,7 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender};
use log::{debug, trace, warn}; use log::{debug, trace, warn};
use net_traits::blob_url_store::parse_blob_url; use net_traits::blob_url_store::parse_blob_url;
use net_traits::filemanager_thread::FileTokenCheck; use net_traits::filemanager_thread::FileTokenCheck;
use net_traits::indexeddb_thread::IndexedDBThreadMsg;
use net_traits::pub_domains::public_suffix_list_size_of; use net_traits::pub_domains::public_suffix_list_size_of;
use net_traits::request::{Destination, RequestBuilder, RequestId}; use net_traits::request::{Destination, RequestBuilder, RequestId};
use net_traits::response::{Response, ResponseInit}; use net_traits::response::{Response, ResponseInit};
@ -57,6 +58,7 @@ use crate::hsts::{self, HstsList};
use crate::http_cache::HttpCache; use crate::http_cache::HttpCache;
use crate::http_loader::{HttpState, http_redirect_fetch}; use crate::http_loader::{HttpState, http_redirect_fetch};
use crate::protocols::ProtocolRegistry; use crate::protocols::ProtocolRegistry;
use crate::indexeddb::idb_thread::IndexedDBThreadFactory;
use crate::request_interceptor::RequestInterceptor; use crate::request_interceptor::RequestInterceptor;
use crate::storage_thread::StorageThreadFactory; use crate::storage_thread::StorageThreadFactory;
use crate::websocket_loader; use crate::websocket_loader;
@ -104,11 +106,12 @@ pub fn new_resource_threads(
ignore_certificate_errors, ignore_certificate_errors,
protocols, protocols,
); );
let idb: IpcSender<IndexedDBThreadMsg> = IndexedDBThreadFactory::new(config_dir.clone());
let storage: IpcSender<StorageThreadMsg> = let storage: IpcSender<StorageThreadMsg> =
StorageThreadFactory::new(config_dir, mem_profiler_chan); StorageThreadFactory::new(config_dir, mem_profiler_chan);
( (
ResourceThreads::new(public_core, storage.clone()), ResourceThreads::new(public_core, storage.clone(), idb.clone()),
ResourceThreads::new(private_core, storage), ResourceThreads::new(private_core, storage, idb),
) )
} }

View file

@ -86,6 +86,10 @@ pub(crate) fn throw_dom_exception(
}, },
None => DOMErrorName::DataCloneError, None => DOMErrorName::DataCloneError,
}, },
Error::Data => DOMErrorName::DataError,
Error::TransactionInactive => DOMErrorName::TransactionInactiveError,
Error::ReadOnly => DOMErrorName::ReadOnlyError,
Error::Version => DOMErrorName::VersionError,
Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError, Error::NoModificationAllowed => DOMErrorName::NoModificationAllowedError,
Error::QuotaExceeded => DOMErrorName::QuotaExceededError, Error::QuotaExceeded => DOMErrorName::QuotaExceededError,
Error::TypeMismatch => DOMErrorName::TypeMismatchError, Error::TypeMismatch => DOMErrorName::TypeMismatchError,

View file

@ -49,6 +49,10 @@ pub(crate) enum DOMErrorName {
TimeoutError = DOMExceptionConstants::TIMEOUT_ERR, TimeoutError = DOMExceptionConstants::TIMEOUT_ERR,
InvalidNodeTypeError = DOMExceptionConstants::INVALID_NODE_TYPE_ERR, InvalidNodeTypeError = DOMExceptionConstants::INVALID_NODE_TYPE_ERR,
DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR, DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR,
DataError,
TransactionInactiveError,
ReadOnlyError,
VersionError,
EncodingError, EncodingError,
NotReadableError, NotReadableError,
DataError, DataError,
@ -81,6 +85,10 @@ impl DOMErrorName {
"TimeoutError" => Some(DOMErrorName::TimeoutError), "TimeoutError" => Some(DOMErrorName::TimeoutError),
"InvalidNodeTypeError" => Some(DOMErrorName::InvalidNodeTypeError), "InvalidNodeTypeError" => Some(DOMErrorName::InvalidNodeTypeError),
"DataCloneError" => Some(DOMErrorName::DataCloneError), "DataCloneError" => Some(DOMErrorName::DataCloneError),
"DataError" => Some(DOMErrorName::DataError),
"TransactionInactiveError" => Some(DOMErrorName::TransactionInactiveError),
"ReadOnlyError" => Some(DOMErrorName::ReadOnlyError),
"VersionError" => Some(DOMErrorName::VersionError),
"EncodingError" => Some(DOMErrorName::EncodingError), "EncodingError" => Some(DOMErrorName::EncodingError),
"NotReadableError" => Some(DOMErrorName::NotReadableError), "NotReadableError" => Some(DOMErrorName::NotReadableError),
"DataError" => Some(DOMErrorName::DataError), "DataError" => Some(DOMErrorName::DataError),
@ -129,6 +137,14 @@ impl DOMException {
"The supplied node is incorrect or has an incorrect ancestor for this operation." "The supplied node is incorrect or has an incorrect ancestor for this operation."
}, },
DOMErrorName::DataCloneError => "The object can not be cloned.", 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 => { DOMErrorName::EncodingError => {
"The encoding operation (either encoded or decoding) failed." "The encoding operation (either encoded or decoding) failed."
}, },

View 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);
}

View 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!();
}
}

View 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!();
}
}

View 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);
}

View 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 transactions 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);
}

View 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 attributes 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);
}

View 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()
}
}

View file

@ -417,6 +417,13 @@ pub(crate) mod htmltrackelement;
pub(crate) mod htmlulistelement; pub(crate) mod htmlulistelement;
pub(crate) mod htmlunknownelement; pub(crate) mod htmlunknownelement;
pub(crate) mod htmlvideoelement; 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 iirfilternode;
pub(crate) mod imagebitmap; pub(crate) mod imagebitmap;
pub(crate) mod imagedata; pub(crate) mod imagedata;

View file

@ -136,6 +136,7 @@ use crate::dom::hashchangeevent::HashChangeEvent;
use crate::dom::history::History; use crate::dom::history::History;
use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection};
use crate::dom::htmliframeelement::HTMLIFrameElement; use crate::dom::htmliframeelement::HTMLIFrameElement;
use crate::dom::idbfactory::IDBFactory;
use crate::dom::location::Location; use crate::dom::location::Location;
use crate::dom::medialist::MediaList; use crate::dom::medialist::MediaList;
use crate::dom::mediaquerylist::{MediaQueryList, MediaQueryListMatchState}; use crate::dom::mediaquerylist::{MediaQueryList, MediaQueryListMatchState};
@ -246,6 +247,7 @@ pub(crate) struct Window {
document: MutNullableDom<Document>, document: MutNullableDom<Document>,
location: MutNullableDom<Location>, location: MutNullableDom<Location>,
history: MutNullableDom<History>, history: MutNullableDom<History>,
indexeddb: MutNullableDom<IDBFactory>,
custom_element_registry: MutNullableDom<CustomElementRegistry>, custom_element_registry: MutNullableDom<CustomElementRegistry>,
performance: MutNullableDom<Performance>, performance: MutNullableDom<Performance>,
#[no_trace] #[no_trace]
@ -1077,6 +1079,14 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
self.history.or_init(|| History::new(self, CanGc::note())) 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 // https://html.spec.whatwg.org/multipage/#dom-window-customelements
fn CustomElements(&self) -> DomRoot<CustomElementRegistry> { fn CustomElements(&self) -> DomRoot<CustomElementRegistry> {
self.custom_element_registry self.custom_element_registry
@ -3069,6 +3079,7 @@ impl Window {
navigator: Default::default(), navigator: Default::default(),
location: Default::default(), location: Default::default(),
history: Default::default(), history: Default::default(),
indexeddb: Default::default(),
custom_element_registry: Default::default(), custom_element_registry: Default::default(),
window_proxy: Default::default(), window_proxy: Default::default(),
document: Default::default(), document: Default::default(),

View file

@ -53,6 +53,7 @@ use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::crypto::Crypto; use crate::dom::crypto::Crypto;
use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope; use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::idbfactory::IDBFactory;
use crate::dom::performance::Performance; use crate::dom::performance::Performance;
use crate::dom::promise::Promise; use crate::dom::promise::Promise;
use crate::dom::trustedscripturl::TrustedScriptURL; use crate::dom::trustedscripturl::TrustedScriptURL;
@ -127,6 +128,7 @@ pub(crate) struct WorkerGlobalScope {
#[no_trace] #[no_trace]
navigation_start: CrossProcessInstant, navigation_start: CrossProcessInstant,
performance: MutNullableDom<Performance>, performance: MutNullableDom<Performance>,
indexeddb: MutNullableDom<IDBFactory>,
trusted_types: MutNullableDom<TrustedTypePolicyFactory>, trusted_types: MutNullableDom<TrustedTypePolicyFactory>,
/// A [`TimerScheduler`] used to schedule timers for this [`WorkerGlobalScope`]. /// A [`TimerScheduler`] used to schedule timers for this [`WorkerGlobalScope`].
@ -188,6 +190,7 @@ impl WorkerGlobalScope {
_devtools_sender: init.from_devtools_sender, _devtools_sender: init.from_devtools_sender,
navigation_start: CrossProcessInstant::now(), navigation_start: CrossProcessInstant::now(),
performance: Default::default(), performance: Default::default(),
indexeddb: Default::default(),
timer_scheduler: RefCell::default(), timer_scheduler: RefCell::default(),
insecure_requests_policy, insecure_requests_policy,
trusted_types: Default::default(), trusted_types: Default::default(),
@ -274,6 +277,14 @@ impl WorkerGlobalScopeMethods<crate::DomTypeHolder> for WorkerGlobalScope {
DomRoot::from_ref(self) 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 // https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-location
fn Location(&self) -> DomRoot<WorkerLocation> { fn Location(&self) -> DomRoot<WorkerLocation> {
self.location self.location

View file

@ -81,6 +81,8 @@ mod xpath;
mod svgpath; mod svgpath;
pub use init::init; pub use init::init;
pub(crate) use realms::enter_realm;
pub(crate) use script_bindings::DomTypes; pub(crate) use script_bindings::DomTypes;
pub use script_runtime::JSEngineSetup; pub use script_runtime::JSEngineSetup;
pub use script_thread::ScriptThread; pub use script_thread::ScriptThread;

View file

@ -99,6 +99,7 @@ static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks {
pub(crate) enum ScriptThreadEventCategory { pub(crate) enum ScriptThreadEventCategory {
AttachLayout, AttachLayout,
ConstellationMsg, ConstellationMsg,
DatabaseAccessEvent,
DevtoolsMsg, DevtoolsMsg,
DocumentEvent, DocumentEvent,
FileRead, FileRead,

View file

@ -134,6 +134,7 @@ impl TaskManager {
task_source_functions!(self, canvas_blob_task_source, Canvas); task_source_functions!(self, canvas_blob_task_source, Canvas);
task_source_functions!(self, clipboard_task_source, Clipboard); task_source_functions!(self, clipboard_task_source, Clipboard);
task_source_functions!(self, database_access_task_source, DatabaseAccess);
task_source_functions!(self, dom_manipulation_task_source, DOMManipulation); task_source_functions!(self, dom_manipulation_task_source, DOMManipulation);
task_source_functions!(self, file_reading_task_source, FileReading); task_source_functions!(self, file_reading_task_source, FileReading);
task_source_functions!(self, font_loading_task_source, FontLoading); task_source_functions!(self, font_loading_task_source, FontLoading);

View file

@ -24,6 +24,7 @@ use crate::task_manager::TaskManager;
pub(crate) enum TaskSourceName { pub(crate) enum TaskSourceName {
Canvas, Canvas,
Clipboard, Clipboard,
DatabaseAccess,
DOMManipulation, DOMManipulation,
FileReading, FileReading,
/// <https://drafts.csswg.org/css-font-loading/#task-source> /// <https://drafts.csswg.org/css-font-loading/#task-source>
@ -50,6 +51,7 @@ impl From<TaskSourceName> for ScriptThreadEventCategory {
match value { match value {
TaskSourceName::Canvas => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::Canvas => ScriptThreadEventCategory::ScriptEvent,
TaskSourceName::Clipboard => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::Clipboard => ScriptThreadEventCategory::ScriptEvent,
TaskSourceName::DatabaseAccess => ScriptThreadEventCategory::ScriptEvent,
TaskSourceName::DOMManipulation => ScriptThreadEventCategory::ScriptEvent, TaskSourceName::DOMManipulation => ScriptThreadEventCategory::ScriptEvent,
TaskSourceName::FileReading => ScriptThreadEventCategory::FileRead, TaskSourceName::FileReading => ScriptThreadEventCategory::FileRead,
TaskSourceName::FontLoading => ScriptThreadEventCategory::FontLoading, TaskSourceName::FontLoading => ScriptThreadEventCategory::FontLoading,

View file

@ -0,0 +1,47 @@
/* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#idbdatabase
*
*/
// https://w3c.github.io/IndexedDB/#idbdatabase
[Pref="dom.indexeddb.enabled", Exposed=(Window,Worker)]
interface IDBDatabase : EventTarget {
readonly attribute DOMString name;
readonly attribute unsigned long long version;
readonly attribute DOMStringList objectStoreNames;
[NewObject] IDBTransaction transaction((DOMString or sequence<DOMString>) storeNames
,
optional IDBTransactionMode mode
= "readonly",
optional IDBTransactionOptions options
= {});
undefined close();
[Throws, NewObject] IDBObjectStore createObjectStore(
DOMString name,
optional IDBObjectStoreParameters options = {}
);
undefined deleteObjectStore(DOMString name);
// Event handlers:
attribute EventHandler onabort;
attribute EventHandler onclose;
attribute EventHandler onerror;
attribute EventHandler onversionchange;
};
enum IDBTransactionDurability { "default", "strict", "relaxed" };
dictionary IDBTransactionOptions {
IDBTransactionDurability durability = "default";
};
dictionary IDBObjectStoreParameters {
(DOMString or sequence<DOMString>)? keyPath = null;
boolean autoIncrement = false;
};

View file

@ -0,0 +1,31 @@
/* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#idbfactory
*
*/
// https://w3c.github.io/IndexedDB/#idbfactory
partial interface mixin WindowOrWorkerGlobalScope {
[Pref="dom.indexeddb.enabled", SameObject] readonly attribute IDBFactory indexedDB;
};
// https://w3c.github.io/IndexedDB/#idbfactory
[Pref="dom.indexeddb.enabled", Exposed=(Window,Worker)]
interface IDBFactory {
[NewObject, Throws] IDBOpenDBRequest open(DOMString name,
optional [EnforceRange] unsigned long long version);
[NewObject, Throws] IDBOpenDBRequest deleteDatabase(DOMString name);
Promise<sequence<IDBDatabaseInfo>> databases();
short cmp(any first, any second);
};
// https://w3c.github.io/IndexedDB/#idbfactory
dictionary IDBDatabaseInfo {
DOMString name;
unsigned long long version;
};

View file

@ -0,0 +1,48 @@
/* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#idbobjectstore
*
*/
// https://w3c.github.io/IndexedDB/#idbobjectstore
[Pref="dom.indexeddb.enabled", Exposed=(Window,Worker)]
interface IDBObjectStore {
attribute DOMString name;
readonly attribute any keyPath;
readonly attribute DOMStringList indexNames;
[SameObject] readonly attribute IDBTransaction transaction;
readonly attribute boolean autoIncrement;
[NewObject, Throws] IDBRequest put(any value, optional any key);
[NewObject, Throws] IDBRequest add(any value, optional any key);
[NewObject, Throws] IDBRequest delete(any query);
[NewObject] IDBRequest clear();
[NewObject, Throws] IDBRequest get(any query);
[NewObject] IDBRequest getKey(any query);
[NewObject] IDBRequest getAll(optional any query,
optional [EnforceRange] unsigned long count);
[NewObject] IDBRequest getAllKeys(optional any query,
optional [EnforceRange] unsigned long count);
[NewObject] IDBRequest count(optional any query);
// [NewObject] IDBRequest openCursor(optional any query,
// optional IDBCursorDirection direction = "next");
// [NewObject] IDBRequest openKeyCursor(optional any query,
// optional IDBCursorDirection direction = "next");
// IDBIndex index(DOMString name);
// [NewObject] IDBIndex createIndex(DOMString name,
// (DOMString or sequence<DOMString>) keyPath,
// optional IDBIndexParameters options = {});
// void deleteIndex(DOMString name);
};
// https://w3c.github.io/IndexedDB/#idbobjectstore
dictionary IDBIndexParameters {
boolean unique = false;
boolean multiEntry = false;
};

View file

@ -0,0 +1,16 @@
/* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#idbopendbrequest
*
*/
// https://w3c.github.io/IndexedDB/#idbopendbrequest
[Pref="dom.indexeddb.enabled", Exposed=(Window,Worker)]
interface IDBOpenDBRequest : IDBRequest {
// Event handlers:
attribute EventHandler onblocked;
attribute EventHandler onupgradeneeded;
};

View file

@ -0,0 +1,29 @@
/* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#idbrequest
*
*/
// https://w3c.github.io/IndexedDB/#idbrequest
[Pref="dom.indexeddb.enabled", Exposed=(Window,Worker)]
interface IDBRequest : EventTarget {
readonly attribute any result;
readonly attribute DOMException? error;
// readonly attribute (IDBObjectStore or IDBIndex or IDBCursor)? source;
readonly attribute IDBObjectStore? source;
readonly attribute IDBTransaction? transaction;
readonly attribute IDBRequestReadyState readyState;
// Event handlers:
attribute EventHandler onsuccess;
attribute EventHandler onerror;
};
// https://w3c.github.io/IndexedDB/#idbrequest
enum IDBRequestReadyState {
"pending",
"done"
};

View file

@ -0,0 +1,33 @@
/* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#idbtransaction
*
*/
// https://w3c.github.io/IndexedDB/#idbtransaction
[Pref="dom.indexeddb.enabled", Exposed=(Window,Worker)]
interface IDBTransaction : EventTarget {
readonly attribute DOMStringList objectStoreNames;
readonly attribute IDBTransactionMode mode;
readonly attribute IDBTransactionDurability durability;
[SameObject] readonly attribute IDBDatabase db;
readonly attribute DOMException? error;
[Throws] IDBObjectStore objectStore(DOMString name);
[Throws] undefined commit();
[Throws] undefined abort();
// Event handlers:
attribute EventHandler onabort;
attribute EventHandler oncomplete;
attribute EventHandler onerror;
};
enum IDBTransactionMode {
"readonly",
"readwrite",
"versionchange"
};

View file

@ -0,0 +1,25 @@
/* 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/. */
/*
* The origin of this IDL file is
* https://w3c.github.io/IndexedDB/#idbversionchangeevent
*
*/
// FIXME:(arihant2math) Expose to Worker too
// https://w3c.github.io/IndexedDB/#idbversionchangeevent
[Pref="dom.indexeddb.enabled", Exposed=(Window)]
interface IDBVersionChangeEvent : Event {
constructor(DOMString type, optional IDBVersionChangeEventInit eventInitDict = {});
readonly attribute unsigned long long oldVersion;
readonly attribute unsigned long long? newVersion;
};
// https://w3c.github.io/IndexedDB/#idbversionchangeevent
dictionary IDBVersionChangeEventInit : EventInit {
unsigned long long oldVersion = 0;
unsigned long long? newVersion = null;
};

View file

@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};
pub enum ScriptHangAnnotation { pub enum ScriptHangAnnotation {
AttachLayout, AttachLayout,
ConstellationMsg, ConstellationMsg,
DatabaseAccessEvent,
DevtoolsMsg, DevtoolsMsg,
DocumentEvent, DocumentEvent,
FileRead, FileRead,

View file

@ -0,0 +1,150 @@
/* 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 ipc_channel::ipc::IpcSender;
use serde::{Deserialize, Serialize};
use servo_url::origin::ImmutableOrigin;
#[derive(Debug, Deserialize, Serialize)]
pub enum IndexedDBThreadReturnType {
Open(Option<u64>),
NextSerialNumber(u64),
StartTransaction(Result<(), ()>),
Commit(Result<(), ()>),
Version(u64),
CreateObjectStore(Option<String>),
UpgradeVersion(Result<u64, ()>),
KVResult(Option<Vec<u8>>),
Exit,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum IndexedDBTxnMode {
Readonly,
Readwrite,
Versionchange,
}
// https://www.w3.org/TR/IndexedDB-2/#key-type
#[derive(Debug, Deserialize, Serialize)]
pub enum IndexedDBKeyType {
Number(Vec<u8>),
String(Vec<u8>),
Binary(Vec<u8>),
// FIXME:(arihant2math) implement Date(),
// FIXME:(arihant2math) implment Array(),
}
// Operations that are not executed instantly, but rather added to a
// queue that is eventually run.
#[derive(Debug, Deserialize, Serialize)]
pub enum AsyncOperation {
/// Gets the value associated with the given key in the associated idb data
GetItem(
Vec<u8>, // Key
),
/// Sets the value of the given key in the associated idb data
PutItem(
IndexedDBKeyType, // Key
Vec<u8>, // Value
bool, // Should overwrite
),
/// Removes the key/value pair for the given key in the associated idb data
RemoveItem(
Vec<u8>, // Key
),
}
#[derive(Debug, Deserialize, Serialize)]
pub enum SyncOperation {
// Upgrades the version of the database
UpgradeVersion(
IpcSender<IndexedDBThreadReturnType>,
ImmutableOrigin,
String, // Database
u64, // Serial number for the transaction
u64, // Version to upgrade to
),
// Checks if an object store has a key generator, used in e.g. Put
HasKeyGenerator(
IpcSender<bool>,
ImmutableOrigin,
String, // Database
String, // Store
),
// Commits changes of a transaction to the database
Commit(
IpcSender<IndexedDBThreadReturnType>,
ImmutableOrigin,
String, // Database
u64, // Transaction serial number
),
// Creates a new store for the database
CreateObjectStore(
IpcSender<Result<(), ()>>,
ImmutableOrigin,
String, // Database
String, // Store
bool,
),
OpenDatabase(
IpcSender<u64>, // Returns the version
ImmutableOrigin,
String, // Database
Option<u64>, // Eventual version
),
// Deletes the database
DeleteDatabase(
IpcSender<Result<(), ()>>,
ImmutableOrigin,
String, // Database
),
// Returns an unique identifier that is used to be able to
// commit/abort transactions.
RegisterNewTxn(
IpcSender<u64>,
ImmutableOrigin,
String, // Database
),
// Starts executing the requests of a transaction
// https://www.w3.org/TR/IndexedDB-2/#transaction-start
StartTransaction(
IpcSender<Result<(), ()>>,
ImmutableOrigin,
String, // Database
u64, // The serial number of the mutating transaction
),
// Returns the version of the database
Version(
IpcSender<u64>,
ImmutableOrigin,
String, // Database
),
/// Send a reply when done cleaning up thread resources and then shut it down
Exit(IpcSender<IndexedDBThreadReturnType>),
}
#[derive(Debug, Deserialize, Serialize)]
pub enum IndexedDBThreadMsg {
Sync(SyncOperation),
Async(
IpcSender<Option<Vec<u8>>>, // Sender to send the result of the async operation
ImmutableOrigin,
String, // Database
String, // ObjectStore
u64, // Serial number of the transaction that requests this operation
IndexedDBTxnMode,
AsyncOperation,
),
}

View file

@ -32,6 +32,7 @@ use servo_url::{ImmutableOrigin, ServoUrl};
use crate::filemanager_thread::FileManagerThreadMsg; use crate::filemanager_thread::FileManagerThreadMsg;
use crate::http_status::HttpStatus; use crate::http_status::HttpStatus;
use crate::indexeddb_thread::IndexedDBThreadMsg;
use crate::request::{Request, RequestBuilder}; use crate::request::{Request, RequestBuilder};
use crate::response::{HttpsState, Response, ResponseInit}; use crate::response::{HttpsState, Response, ResponseInit};
use crate::storage_thread::StorageThreadMsg; use crate::storage_thread::StorageThreadMsg;
@ -40,6 +41,7 @@ pub mod blob_url_store;
pub mod filemanager_thread; pub mod filemanager_thread;
pub mod http_status; pub mod http_status;
pub mod image_cache; pub mod image_cache;
pub mod indexeddb_thread;
pub mod policy_container; pub mod policy_container;
pub mod pub_domains; pub mod pub_domains;
pub mod quality; pub mod quality;
@ -413,13 +415,19 @@ where
pub struct ResourceThreads { pub struct ResourceThreads {
pub core_thread: CoreResourceThread, pub core_thread: CoreResourceThread,
storage_thread: IpcSender<StorageThreadMsg>, storage_thread: IpcSender<StorageThreadMsg>,
idb_thread: IpcSender<IndexedDBThreadMsg>,
} }
impl ResourceThreads { impl ResourceThreads {
pub fn new(c: CoreResourceThread, s: IpcSender<StorageThreadMsg>) -> ResourceThreads { pub fn new(
c: CoreResourceThread,
s: IpcSender<StorageThreadMsg>,
i: IpcSender<IndexedDBThreadMsg>,
) -> ResourceThreads {
ResourceThreads { ResourceThreads {
core_thread: c, core_thread: c,
storage_thread: s, storage_thread: s,
idb_thread: i,
} }
} }
@ -438,6 +446,16 @@ impl IpcSend<CoreResourceMsg> for ResourceThreads {
} }
} }
impl IpcSend<IndexedDBThreadMsg> for ResourceThreads {
fn send(&self, msg: IndexedDBThreadMsg) -> IpcSendResult {
self.idb_thread.send(msg)
}
fn sender(&self) -> IpcSender<IndexedDBThreadMsg> {
self.idb_thread.clone()
}
}
impl IpcSend<StorageThreadMsg> for ResourceThreads { impl IpcSend<StorageThreadMsg> for ResourceThreads {
fn send(&self, msg: StorageThreadMsg) -> IpcSendResult { fn send(&self, msg: StorageThreadMsg) -> IpcSendResult {
self.storage_thread.send(msg) self.storage_thread.send(msg)

View file

@ -71,6 +71,7 @@ pub enum ProfilerCategory {
ImageSaving = 0x51, ImageSaving = 0x51,
ScriptAttachLayout = 0x60, ScriptAttachLayout = 0x60,
ScriptConstellationMsg = 0x61, ScriptConstellationMsg = 0x61,
ScriptDatabaseAccessEvent = 0x85,
ScriptDevtoolsMsg = 0x62, ScriptDevtoolsMsg = 0x62,
ScriptDocumentEvent = 0x63, ScriptDocumentEvent = 0x63,