mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
re-structure blob, structured serialization
This commit is contained in:
parent
7aa68c8fe7
commit
6e8a85482c
31 changed files with 997 additions and 489 deletions
|
@ -16,12 +16,14 @@ use crate::dom::bindings::settings_stack::{entry_global, incumbent_global, AutoE
|
|||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bindings::structuredclone;
|
||||
use crate::dom::bindings::weakref::{DOMTracker, WeakRef};
|
||||
use crate::dom::blob::Blob;
|
||||
use crate::dom::crypto::Crypto;
|
||||
use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
|
||||
use crate::dom::errorevent::ErrorEvent;
|
||||
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
|
||||
use crate::dom::eventsource::EventSource;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::file::File;
|
||||
use crate::dom::messageevent::MessageEvent;
|
||||
use crate::dom::messageport::MessagePort;
|
||||
use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
|
||||
|
@ -61,10 +63,13 @@ use js::rust::wrappers::EvaluateUtf8;
|
|||
use js::rust::{get_object_class, CompileOptionsWrapper, ParentRuntime, Runtime};
|
||||
use js::rust::{HandleValue, MutableHandleValue};
|
||||
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
|
||||
use msg::constellation_msg::{MessagePortId, MessagePortRouterId, PipelineId};
|
||||
use msg::constellation_msg::{BlobId, MessagePortId, MessagePortRouterId, PipelineId};
|
||||
use net_traits::blob_url_store::{get_blob_origin, BlobBuf};
|
||||
use net_traits::filemanager_thread::{FileManagerThreadMsg, ReadFileProgress, RelativePos};
|
||||
use net_traits::image_cache::ImageCache;
|
||||
use net_traits::{CoreResourceThread, IpcSend, ResourceThreads};
|
||||
use profile_traits::{mem as profile_mem, time as profile_time};
|
||||
use net_traits::{CoreResourceMsg, CoreResourceThread, IpcSend, ResourceThreads};
|
||||
use profile_traits::{ipc as profile_ipc, mem as profile_mem, time as profile_time};
|
||||
use script_traits::serializable::{BlobData, BlobImpl, FileBlob};
|
||||
use script_traits::transferable::MessagePortImpl;
|
||||
use script_traits::{
|
||||
MessagePortMsg, MsDuration, PortMessageTask, ScriptMsg, ScriptToConstellationChan, TimerEvent,
|
||||
|
@ -76,10 +81,13 @@ use std::cell::Cell;
|
|||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ffi::CString;
|
||||
use std::mem;
|
||||
use std::ops::Index;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use time::{get_time, Timespec};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(JSTraceable)]
|
||||
pub struct AutoCloseWorker(Arc<AtomicBool>);
|
||||
|
@ -99,6 +107,9 @@ pub struct GlobalScope {
|
|||
/// The message-port router id for this global, if it is managing ports.
|
||||
message_port_state: DomRefCell<MessagePortState>,
|
||||
|
||||
/// The blobs managed by this global, if any.
|
||||
blob_state: DomRefCell<BlobState>,
|
||||
|
||||
/// Pipeline id associated with this global.
|
||||
pipeline_id: PipelineId,
|
||||
|
||||
|
@ -201,6 +212,36 @@ struct TimerListener {
|
|||
context: Trusted<GlobalScope>,
|
||||
}
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
/// A holder of a weak reference for a DOM blob or file.
|
||||
pub enum BlobTracker {
|
||||
/// A weak ref to a DOM file.
|
||||
File(WeakRef<File>),
|
||||
/// A weak ref to a DOM blob.
|
||||
Blob(WeakRef<Blob>),
|
||||
}
|
||||
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
/// The info pertaining to a blob managed by this global.
|
||||
pub struct BlobInfo {
|
||||
/// The weak ref to the corresponding DOM object.
|
||||
tracker: BlobTracker,
|
||||
/// The data and logic backing the DOM object.
|
||||
blob_impl: BlobImpl,
|
||||
/// Whether this blob has an outstanding URL,
|
||||
/// <https://w3c.github.io/FileAPI/#url>.
|
||||
has_url: bool,
|
||||
}
|
||||
|
||||
/// State representing whether this global is currently managing blobs.
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
pub enum BlobState {
|
||||
/// A map of managed blobs.
|
||||
Managed(HashMap<BlobId, BlobInfo>),
|
||||
/// This global is not managing any blobs at this time.
|
||||
UnManaged,
|
||||
}
|
||||
|
||||
/// Data representing a message-port managed by this global.
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
pub enum ManagedMessagePort {
|
||||
|
@ -345,6 +386,7 @@ impl GlobalScope {
|
|||
) -> Self {
|
||||
Self {
|
||||
message_port_state: DomRefCell::new(MessagePortState::UnManaged),
|
||||
blob_state: DomRefCell::new(BlobState::UnManaged),
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
crypto: Default::default(),
|
||||
next_worker_id: Cell::new(WorkerId(0)),
|
||||
|
@ -445,6 +487,12 @@ impl GlobalScope {
|
|||
}
|
||||
}
|
||||
|
||||
/// Clean-up DOM related resources
|
||||
pub fn perform_a_dom_garbage_collection_checkpoint(&self) {
|
||||
self.perform_a_message_port_garbage_collection_checkpoint();
|
||||
self.perform_a_blob_garbage_collection_checkpoint();
|
||||
}
|
||||
|
||||
/// Update our state to un-managed,
|
||||
/// and tell the constellation to drop the sender to our message-port router.
|
||||
pub fn remove_message_ports_router(&self) {
|
||||
|
@ -814,6 +862,400 @@ impl GlobalScope {
|
|||
}
|
||||
}
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#serialization-steps>
|
||||
/// defined at <https://w3c.github.io/FileAPI/#blob-section>.
|
||||
/// Get the snapshot state and underlying bytes of the blob.
|
||||
pub fn serialize_blob(&self, blob_id: &BlobId) -> BlobImpl {
|
||||
// Note: we combine the snapshot state and underlying bytes into one call,
|
||||
// which seems spec compliant.
|
||||
// See https://w3c.github.io/FileAPI/#snapshot-state
|
||||
let bytes = self
|
||||
.get_blob_bytes(blob_id)
|
||||
.expect("Could not read bytes from blob as part of serialization steps.");
|
||||
let type_string = self.get_blob_type_string(blob_id);
|
||||
|
||||
// Note: the new BlobImpl is a clone, but with it's own BlobId.
|
||||
BlobImpl::new_from_bytes(bytes, type_string)
|
||||
}
|
||||
|
||||
fn track_blob_info(&self, blob_info: BlobInfo, blob_id: BlobId) {
|
||||
let mut blob_state = self.blob_state.borrow_mut();
|
||||
|
||||
match &mut *blob_state {
|
||||
BlobState::UnManaged => {
|
||||
let mut blobs_map = HashMap::new();
|
||||
blobs_map.insert(blob_id, blob_info);
|
||||
*blob_state = BlobState::Managed(blobs_map);
|
||||
},
|
||||
BlobState::Managed(blobs_map) => {
|
||||
blobs_map.insert(blob_id, blob_info);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Start tracking a blob
|
||||
pub fn track_blob(&self, dom_blob: &DomRoot<Blob>, blob_impl: BlobImpl) {
|
||||
let blob_id = blob_impl.blob_id();
|
||||
|
||||
let blob_info = BlobInfo {
|
||||
blob_impl,
|
||||
tracker: BlobTracker::Blob(WeakRef::new(dom_blob)),
|
||||
has_url: false,
|
||||
};
|
||||
|
||||
self.track_blob_info(blob_info, blob_id);
|
||||
}
|
||||
|
||||
/// Start tracking a file
|
||||
pub fn track_file(&self, file: &DomRoot<File>, blob_impl: BlobImpl) {
|
||||
let blob_id = blob_impl.blob_id();
|
||||
|
||||
let blob_info = BlobInfo {
|
||||
blob_impl,
|
||||
tracker: BlobTracker::File(WeakRef::new(file)),
|
||||
has_url: false,
|
||||
};
|
||||
|
||||
self.track_blob_info(blob_info, blob_id);
|
||||
}
|
||||
|
||||
/// Clean-up any file or blob that is unreachable from script,
|
||||
/// unless it has an oustanding blob url.
|
||||
/// <https://w3c.github.io/FileAPI/#lifeTime>
|
||||
fn perform_a_blob_garbage_collection_checkpoint(&self) {
|
||||
let mut blob_state = self.blob_state.borrow_mut();
|
||||
if let BlobState::Managed(blobs_map) = &mut *blob_state {
|
||||
blobs_map.retain(|_id, blob_info| {
|
||||
let garbage_collected = match &blob_info.tracker {
|
||||
BlobTracker::File(weak) => weak.root().is_none(),
|
||||
BlobTracker::Blob(weak) => weak.root().is_none(),
|
||||
};
|
||||
if garbage_collected && !blob_info.has_url {
|
||||
if let BlobData::File(ref f) = blob_info.blob_impl.blob_data() {
|
||||
self.decrement_file_ref(f.get_id());
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
if blobs_map.is_empty() {
|
||||
*blob_state = BlobState::UnManaged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean-up all file related resources on document unload.
|
||||
/// <https://w3c.github.io/FileAPI/#lifeTime>
|
||||
pub fn clean_up_all_file_resources(&self) {
|
||||
let mut blob_state = self.blob_state.borrow_mut();
|
||||
if let BlobState::Managed(blobs_map) = &mut *blob_state {
|
||||
blobs_map.drain().for_each(|(_id, blob_info)| {
|
||||
if let BlobData::File(ref f) = blob_info.blob_impl.blob_data() {
|
||||
self.decrement_file_ref(f.get_id());
|
||||
}
|
||||
});
|
||||
}
|
||||
*blob_state = BlobState::UnManaged;
|
||||
}
|
||||
|
||||
fn decrement_file_ref(&self, id: Uuid) {
|
||||
let origin = get_blob_origin(&self.get_url());
|
||||
|
||||
let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap();
|
||||
|
||||
let msg = FileManagerThreadMsg::DecRef(id, origin, tx);
|
||||
self.send_to_file_manager(msg);
|
||||
let _ = rx.recv();
|
||||
}
|
||||
|
||||
/// Get a slice to the inner data of a Blob,
|
||||
/// In the case of a File-backed blob, this might incur synchronous read and caching.
|
||||
pub fn get_blob_bytes(&self, blob_id: &BlobId) -> Result<Vec<u8>, ()> {
|
||||
let parent = {
|
||||
let blob_state = self.blob_state.borrow();
|
||||
if let BlobState::Managed(blobs_map) = &*blob_state {
|
||||
let blob_info = blobs_map
|
||||
.get(blob_id)
|
||||
.expect("get_blob_bytes for an unknown blob.");
|
||||
match blob_info.blob_impl.blob_data() {
|
||||
BlobData::Sliced(ref parent, ref rel_pos) => {
|
||||
Some((parent.clone(), rel_pos.clone()))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
panic!("get_blob_bytes called on a global not managing any blobs.");
|
||||
}
|
||||
};
|
||||
|
||||
match parent {
|
||||
Some((parent_id, rel_pos)) => self.get_blob_bytes_non_sliced(&parent_id).map(|v| {
|
||||
let range = rel_pos.to_abs_range(v.len());
|
||||
v.index(range).to_vec()
|
||||
}),
|
||||
None => self.get_blob_bytes_non_sliced(blob_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get bytes from a non-sliced blob
|
||||
fn get_blob_bytes_non_sliced(&self, blob_id: &BlobId) -> Result<Vec<u8>, ()> {
|
||||
let blob_state = self.blob_state.borrow();
|
||||
if let BlobState::Managed(blobs_map) = &*blob_state {
|
||||
let blob_info = blobs_map
|
||||
.get(blob_id)
|
||||
.expect("get_blob_bytes_non_sliced called for a unknown blob.");
|
||||
match blob_info.blob_impl.blob_data() {
|
||||
BlobData::File(ref f) => {
|
||||
let (buffer, is_new_buffer) = match f.get_cache() {
|
||||
Some(bytes) => (bytes, false),
|
||||
None => {
|
||||
let bytes = self.read_file(f.get_id())?;
|
||||
(bytes, true)
|
||||
},
|
||||
};
|
||||
|
||||
// Cache
|
||||
if is_new_buffer {
|
||||
f.cache_bytes(buffer.clone());
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
},
|
||||
BlobData::Memory(ref s) => Ok(s.clone()),
|
||||
BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."),
|
||||
}
|
||||
} else {
|
||||
panic!("get_blob_bytes_non_sliced called on a global not managing any blobs.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a copy of the type_string of a blob.
|
||||
pub fn get_blob_type_string(&self, blob_id: &BlobId) -> String {
|
||||
let blob_state = self.blob_state.borrow();
|
||||
if let BlobState::Managed(blobs_map) = &*blob_state {
|
||||
let blob_info = blobs_map
|
||||
.get(blob_id)
|
||||
.expect("get_blob_type_string called for a unknown blob.");
|
||||
blob_info.blob_impl.type_string()
|
||||
} else {
|
||||
panic!("get_blob_type_string called on a global not managing any blobs.");
|
||||
}
|
||||
}
|
||||
|
||||
/// https://w3c.github.io/FileAPI/#dfn-size
|
||||
pub fn get_blob_size(&self, blob_id: &BlobId) -> u64 {
|
||||
let blob_state = self.blob_state.borrow();
|
||||
if let BlobState::Managed(blobs_map) = &*blob_state {
|
||||
let parent = {
|
||||
let blob_info = blobs_map
|
||||
.get(blob_id)
|
||||
.expect("get_blob_size called for a unknown blob.");
|
||||
match blob_info.blob_impl.blob_data() {
|
||||
BlobData::Sliced(ref parent, ref rel_pos) => {
|
||||
Some((parent.clone(), rel_pos.clone()))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
match parent {
|
||||
Some((parent_id, rel_pos)) => {
|
||||
let parent_info = blobs_map
|
||||
.get(&parent_id)
|
||||
.expect("Parent of blob whose size is unknown.");
|
||||
let parent_size = match parent_info.blob_impl.blob_data() {
|
||||
BlobData::File(ref f) => f.get_size(),
|
||||
BlobData::Memory(ref v) => v.len() as u64,
|
||||
BlobData::Sliced(_, _) => panic!("Blob ancestry should be only one level."),
|
||||
};
|
||||
rel_pos.to_abs_range(parent_size as usize).len() as u64
|
||||
},
|
||||
None => {
|
||||
let blob_info = blobs_map.get(blob_id).expect("Blob whose size is unknown.");
|
||||
match blob_info.blob_impl.blob_data() {
|
||||
BlobData::File(ref f) => f.get_size(),
|
||||
BlobData::Memory(ref v) => v.len() as u64,
|
||||
BlobData::Sliced(_, _) => panic!(
|
||||
"It was previously checked that this blob does not have a parent."
|
||||
),
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
panic!("get_blob_size called on a global not managing any blobs.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_blob_url_id(&self, blob_id: &BlobId) -> Uuid {
|
||||
let mut blob_state = self.blob_state.borrow_mut();
|
||||
if let BlobState::Managed(blobs_map) = &mut *blob_state {
|
||||
let parent = {
|
||||
let blob_info = blobs_map
|
||||
.get_mut(blob_id)
|
||||
.expect("get_blob_url_id called for a unknown blob.");
|
||||
|
||||
// Keep track of blobs with outstanding URLs.
|
||||
blob_info.has_url = true;
|
||||
|
||||
match blob_info.blob_impl.blob_data() {
|
||||
BlobData::Sliced(ref parent, ref rel_pos) => {
|
||||
Some((parent.clone(), rel_pos.clone()))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
match parent {
|
||||
Some((parent_id, rel_pos)) => {
|
||||
let parent_file_id = {
|
||||
let parent_info = blobs_map
|
||||
.get_mut(&parent_id)
|
||||
.expect("Parent of blob whose url is requested is unknown.");
|
||||
self.promote(parent_info, /* set_valid is */ false)
|
||||
};
|
||||
let parent_size = self.get_blob_size(&parent_id);
|
||||
let blob_info = blobs_map
|
||||
.get_mut(blob_id)
|
||||
.expect("Blob whose url is requested is unknown.");
|
||||
self.create_sliced_url_id(blob_info, &parent_file_id, &rel_pos, parent_size)
|
||||
},
|
||||
None => {
|
||||
let blob_info = blobs_map
|
||||
.get_mut(blob_id)
|
||||
.expect("Blob whose url is requested is unknown.");
|
||||
self.promote(blob_info, /* set_valid is */ true)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
panic!("get_blob_url_id called on a global not managing any blobs.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a FileID representing sliced parent-blob content
|
||||
fn create_sliced_url_id(
|
||||
&self,
|
||||
blob_info: &mut BlobInfo,
|
||||
parent_file_id: &Uuid,
|
||||
rel_pos: &RelativePos,
|
||||
parent_len: u64,
|
||||
) -> Uuid {
|
||||
let origin = get_blob_origin(&self.get_url());
|
||||
|
||||
let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap();
|
||||
let msg = FileManagerThreadMsg::AddSlicedURLEntry(
|
||||
parent_file_id.clone(),
|
||||
rel_pos.clone(),
|
||||
tx,
|
||||
origin.clone(),
|
||||
);
|
||||
self.send_to_file_manager(msg);
|
||||
match rx.recv().expect("File manager thread is down.") {
|
||||
Ok(new_id) => {
|
||||
*blob_info.blob_impl.blob_data_mut() = BlobData::File(FileBlob::new(
|
||||
new_id.clone(),
|
||||
None,
|
||||
None,
|
||||
rel_pos.to_abs_range(parent_len as usize).len() as u64,
|
||||
));
|
||||
|
||||
// Return the indirect id reference
|
||||
new_id
|
||||
},
|
||||
Err(_) => {
|
||||
// Return dummy id
|
||||
Uuid::new_v4()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Promote non-Slice blob:
|
||||
/// 1. Memory-based: The bytes in data slice will be transferred to file manager thread.
|
||||
/// 2. File-based: If set_valid, then activate the FileID so it can serve as URL
|
||||
/// Depending on set_valid, the returned FileID can be part of
|
||||
/// valid or invalid Blob URL.
|
||||
pub fn promote(&self, blob_info: &mut BlobInfo, set_valid: bool) -> Uuid {
|
||||
let mut bytes = vec![];
|
||||
let global_url = self.get_url();
|
||||
|
||||
match blob_info.blob_impl.blob_data_mut() {
|
||||
BlobData::Sliced(_, _) => {
|
||||
panic!("Sliced blobs should use create_sliced_url_id instead of promote.");
|
||||
},
|
||||
BlobData::File(ref f) => {
|
||||
if set_valid {
|
||||
let origin = get_blob_origin(&global_url);
|
||||
let (tx, rx) = profile_ipc::channel(self.time_profiler_chan().clone()).unwrap();
|
||||
|
||||
let msg = FileManagerThreadMsg::ActivateBlobURL(f.get_id(), tx, origin.clone());
|
||||
self.send_to_file_manager(msg);
|
||||
|
||||
match rx.recv().unwrap() {
|
||||
Ok(_) => return f.get_id(),
|
||||
// Return a dummy id on error
|
||||
Err(_) => return Uuid::new_v4(),
|
||||
}
|
||||
} else {
|
||||
// no need to activate
|
||||
return f.get_id();
|
||||
}
|
||||
},
|
||||
BlobData::Memory(ref mut bytes_in) => mem::swap(bytes_in, &mut bytes),
|
||||
};
|
||||
|
||||
let origin = get_blob_origin(&global_url);
|
||||
|
||||
let blob_buf = BlobBuf {
|
||||
filename: None,
|
||||
type_string: blob_info.blob_impl.type_string(),
|
||||
size: bytes.len() as u64,
|
||||
bytes: bytes.to_vec(),
|
||||
};
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let msg = FileManagerThreadMsg::PromoteMemory(id, blob_buf, set_valid, origin.clone());
|
||||
self.send_to_file_manager(msg);
|
||||
|
||||
*blob_info.blob_impl.blob_data_mut() = BlobData::File(FileBlob::new(
|
||||
id.clone(),
|
||||
None,
|
||||
Some(bytes.to_vec()),
|
||||
bytes.len() as u64,
|
||||
));
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
fn send_to_file_manager(&self, msg: FileManagerThreadMsg) {
|
||||
let resource_threads = self.resource_threads();
|
||||
let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
|
||||
}
|
||||
|
||||
fn read_file(&self, id: Uuid) -> Result<Vec<u8>, ()> {
|
||||
let resource_threads = self.resource_threads();
|
||||
let (chan, recv) =
|
||||
profile_ipc::channel(self.time_profiler_chan().clone()).map_err(|_| ())?;
|
||||
let origin = get_blob_origin(&self.get_url());
|
||||
let check_url_validity = false;
|
||||
let msg = FileManagerThreadMsg::ReadFile(chan, id, check_url_validity, origin);
|
||||
let _ = resource_threads.send(CoreResourceMsg::ToFileManager(msg));
|
||||
|
||||
let mut bytes = vec![];
|
||||
|
||||
loop {
|
||||
match recv.recv().unwrap() {
|
||||
Ok(ReadFileProgress::Meta(mut blob_buf)) => {
|
||||
bytes.append(&mut blob_buf.bytes);
|
||||
},
|
||||
Ok(ReadFileProgress::Partial(mut bytes_in)) => {
|
||||
bytes.append(&mut bytes_in);
|
||||
},
|
||||
Ok(ReadFileProgress::EOF) => {
|
||||
return Ok(bytes);
|
||||
},
|
||||
Err(_) => return Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_worker(&self, closing_worker: Arc<AtomicBool>) {
|
||||
self.list_auto_close_worker
|
||||
.borrow_mut()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue