diff --git a/servo/components/net/storage_task.rs b/servo/components/net/storage_task.rs index 6fcc40ec0861..3d638ad0df9d 100644 --- a/servo/components/net/storage_task.rs +++ b/servo/components/net/storage_task.rs @@ -12,6 +12,8 @@ use url::Url; use util::str::DOMString; use util::task::spawn_named; +const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024; + pub trait StorageTaskFactory { fn new() -> Self; } @@ -29,8 +31,8 @@ impl StorageTaskFactory for StorageTask { struct StorageManager { port: IpcReceiver, - session_data: HashMap>, - local_data: HashMap>, + session_data: HashMap)>, + local_data: HashMap)>, } impl StorageManager { @@ -76,7 +78,7 @@ impl StorageManager { } fn select_data(&self, storage_type: StorageType) - -> &HashMap> { + -> &HashMap)> { match storage_type { StorageType::Session => &self.session_data, StorageType::Local => &self.local_data @@ -84,7 +86,7 @@ impl StorageManager { } fn select_data_mut(&mut self, storage_type: StorageType) - -> &mut HashMap> { + -> &mut HashMap)> { match storage_type { StorageType::Session => &mut self.session_data, StorageType::Local => &mut self.local_data @@ -94,7 +96,7 @@ impl StorageManager { fn length(&self, sender: IpcSender, url: Url, storage_type: StorageType) { let origin = self.origin_as_string(url); let data = self.select_data(storage_type); - sender.send(data.get(&origin).map_or(0, |entry| entry.len())).unwrap(); + sender.send(data.get(&origin).map_or(0, |&(_, ref entry)| entry.len())).unwrap(); } fn key(&self, @@ -105,7 +107,7 @@ impl StorageManager { let origin = self.origin_as_string(url); let data = self.select_data(storage_type); sender.send(data.get(&origin) - .and_then(|entry| entry.keys().nth(index as usize)) + .and_then(|&(_, ref entry)| entry.keys().nth(index as usize)) .map(|key| key.clone())).unwrap(); } @@ -116,35 +118,59 @@ impl StorageManager { let origin = self.origin_as_string(url); let data = self.select_data(storage_type); let keys = data.get(&origin) - .map_or(vec![], |entry| entry.keys().cloned().collect()); + .map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect()); sender.send(keys).unwrap(); } + + fn set_item(&mut self, - sender: IpcSender<(bool, Option)>, + sender: IpcSender), ()>>, url: Url, storage_type: StorageType, name: DOMString, value: DOMString) { let origin = self.origin_as_string(url); + + let current_total_size = { + let local_data = self.select_data(StorageType::Local); + let session_data = self.select_data(StorageType::Session); + let local_data_size = local_data.get(&origin).map_or(0, |&(total, _)| total); + let session_data_size = session_data.get(&origin).map_or(0, |&(total, _)| total); + local_data_size + session_data_size + }; + let data = self.select_data_mut(storage_type); if !data.contains_key(&origin) { - data.insert(origin.clone(), BTreeMap::new()); + data.insert(origin.clone(), (0, BTreeMap::new())); } - let (changed, old_value) = data.get_mut(&origin).map(|entry| { - entry.insert(name, value.clone()).map_or( - (true, None), + let message = data.get_mut(&origin).map(|&mut (ref mut total, ref mut entry)| { + let mut new_total_size = current_total_size + value.as_bytes().len(); + if let Some(old_value) = entry.get(&name) { + new_total_size -= old_value.as_bytes().len(); + } else { + new_total_size += name.as_bytes().len(); + } + + if new_total_size > QUOTA_SIZE_LIMIT { + return Err(()); + } + + let message = entry.insert(name.clone(), value.clone()).map_or( + Ok((true, None)), |old| if old == value { - (false, None) + Ok((false, None)) } else { - (true, Some(old)) - }) + Ok((true, Some(old))) + }); + *total = new_total_size; + message }).unwrap(); - sender.send((changed, old_value)).unwrap(); + sender.send(message).unwrap(); } fn request_item(&self, @@ -155,7 +181,7 @@ impl StorageManager { let origin = self.origin_as_string(url); let data = self.select_data(storage_type); sender.send(data.get(&origin) - .and_then(|entry| entry.get(&name)) + .and_then(|&(_, ref entry)| entry.get(&name)) .map(|value| value.to_string())).unwrap(); } @@ -167,8 +193,11 @@ impl StorageManager { name: DOMString) { let origin = self.origin_as_string(url); let data = self.select_data_mut(storage_type); - let old_value = data.get_mut(&origin).and_then(|entry| { - entry.remove(&name) + let old_value = data.get_mut(&origin).and_then(|&mut (ref mut total, ref mut entry)| { + entry.remove(&name).and_then(|old| { + *total -= name.as_bytes().len() + old.as_bytes().len(); + Some(old) + }) }); sender.send(old_value).unwrap(); } @@ -177,9 +206,10 @@ impl StorageManager { let origin = self.origin_as_string(url); let data = self.select_data_mut(storage_type); sender.send(data.get_mut(&origin) - .map_or(false, |entry| { + .map_or(false, |&mut (ref mut total, ref mut entry)| { if !entry.is_empty() { entry.clear(); + *total = 0; true } else { false diff --git a/servo/components/net_traits/storage_task.rs b/servo/components/net_traits/storage_task.rs index 2b91c106a664..33aff19af604 100644 --- a/servo/components/net_traits/storage_task.rs +++ b/servo/components/net_traits/storage_task.rs @@ -28,8 +28,7 @@ pub enum StorageTaskMsg { GetItem(IpcSender>, Url, StorageType, DOMString), - - SetItem(IpcSender<(bool, Option)>, Url, StorageType, DOMString, DOMString), + SetItem(IpcSender), ()>>, Url, StorageType, DOMString, DOMString), RemoveItem(IpcSender>, Url, StorageType, DOMString), diff --git a/servo/components/script/dom/storage.rs b/servo/components/script/dom/storage.rs index 1fd42aaced84..c4bd252464e3 100644 --- a/servo/components/script/dom/storage.rs +++ b/servo/components/script/dom/storage.rs @@ -5,6 +5,7 @@ use dom::bindings::codegen::Bindings::StorageBinding; use dom::bindings::codegen::Bindings::StorageBinding::StorageMethods; use dom::bindings::codegen::InheritTypes::{EventCast, EventTargetCast}; +use dom::bindings::error::{Error, ErrorResult}; use dom::bindings::global::{GlobalField, GlobalRef}; use dom::bindings::js::{Root, RootedReference}; use dom::bindings::refcounted::Trusted; @@ -82,14 +83,19 @@ impl StorageMethods for Storage { } // https://html.spec.whatwg.org/multipage/#dom-storage-setitem - fn SetItem(&self, name: DOMString, value: DOMString) { + fn SetItem(&self, name: DOMString, value: DOMString) -> ErrorResult { let (sender, receiver) = ipc::channel().unwrap(); let msg = StorageTaskMsg::SetItem(sender, self.get_url(), self.storage_type, name.clone(), value.clone()); self.get_storage_task().send(msg).unwrap(); - let (changed, old_value) = receiver.recv().unwrap(); - if changed { - self.broadcast_change_notification(Some(name), old_value, Some(value)); + match receiver.recv().unwrap() { + Err(_) => Err(Error::QuotaExceeded), + Ok((changed, old_value)) => { + if changed { + self.broadcast_change_notification(Some(name), old_value, Some(value)); + } + Ok(()) + } } } @@ -129,8 +135,8 @@ impl StorageMethods for Storage { item } - fn NamedSetter(&self, name: DOMString, value: DOMString) { - self.SetItem(name, value); + fn NamedSetter(&self, name: DOMString, value: DOMString) -> ErrorResult { + self.SetItem(name, value) } fn NamedDeleter(&self, name: DOMString) { diff --git a/servo/components/script/dom/webidls/Storage.webidl b/servo/components/script/dom/webidls/Storage.webidl index 8cc6d9ddb75c..a2dc1b08818b 100644 --- a/servo/components/script/dom/webidls/Storage.webidl +++ b/servo/components/script/dom/webidls/Storage.webidl @@ -16,6 +16,7 @@ interface Storage { getter DOMString? getItem(DOMString name); + [Throws] setter void setItem(DOMString name, DOMString value); deleter void removeItem(DOMString name);