mirror of
https://github.com/servo/servo.git
synced 2025-08-25 15:18:22 +01:00
script: initial CookieStore implementation (#37968)
This is a first draft at implementing the required infrastructure for CookieStore, which requires setting up IPC between script and the resource thread to allow for async/"in parallel" handling of cookie changes that have a promise API. Cookie Store also will need to receive change events when cookies for a url are changed so the architecture needs to support that. Expect this PR to be reworked once the architecture becomes more settled, cookie change events will be implemented in follow up PRs Testing: WPT tests exist for this API Part of #37674 --------- Signed-off-by: Sebastian C <sebsebmc@gmail.com>
This commit is contained in:
parent
a75f3fd09b
commit
b869b7eb96
35 changed files with 917 additions and 305 deletions
|
@ -205,6 +205,27 @@ impl CookieStorage {
|
|||
}
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#query-cookies>
|
||||
pub fn query_cookies(&mut self, url: &ServoUrl, name: Option<String>) -> Vec<Cookie<'static>> {
|
||||
// 1. Retrieve cookie-list given request-uri and "non-HTTP" source
|
||||
let cookie_list = self.cookies_data_for_url(url, CookieSource::NonHTTP);
|
||||
|
||||
// 3. For each cookie in cookie-list, run these steps:
|
||||
// 3.2. If name is given, then run these steps:
|
||||
if let Some(name) = name {
|
||||
// Let cookieName be the result of running UTF-8 decode without BOM on cookie’s name.
|
||||
// If cookieName does not equal name, then continue.
|
||||
cookie_list.filter(|cookie| cookie.name() == name).collect()
|
||||
} else {
|
||||
cookie_list.collect()
|
||||
}
|
||||
|
||||
// Note: we do not convert the list into CookieListItem's here, we do that in script to not not have to define
|
||||
// the binding types in net.
|
||||
|
||||
// Return list
|
||||
}
|
||||
|
||||
pub fn cookies_data_for_url<'a>(
|
||||
&'a mut self,
|
||||
url: &'a ServoUrl,
|
||||
|
|
|
@ -14,6 +14,7 @@ use std::sync::{Arc, Mutex, RwLock, Weak};
|
|||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use base::id::CookieStoreId;
|
||||
use cookie::Cookie;
|
||||
use crossbeam_channel::Sender;
|
||||
use devtools_traits::DevtoolsControlMsg;
|
||||
|
@ -29,9 +30,10 @@ use net_traits::request::{Destination, RequestBuilder, RequestId};
|
|||
use net_traits::response::{Response, ResponseInit};
|
||||
use net_traits::storage_thread::StorageThreadMsg;
|
||||
use net_traits::{
|
||||
AsyncRuntime, CookieSource, CoreResourceMsg, CoreResourceThread, CustomResponseMediator,
|
||||
DiscardFetch, FetchChannels, FetchTaskTarget, ResourceFetchTiming, ResourceThreads,
|
||||
ResourceTimingType, WebSocketDomAction, WebSocketNetworkEvent,
|
||||
AsyncRuntime, CookieAsyncResponse, CookieData, CookieSource, CoreResourceMsg,
|
||||
CoreResourceThread, CustomResponseMediator, DiscardFetch, FetchChannels, FetchTaskTarget,
|
||||
ResourceFetchTiming, ResourceThreads, ResourceTimingType, WebSocketDomAction,
|
||||
WebSocketNetworkEvent,
|
||||
};
|
||||
use profile_traits::mem::{
|
||||
ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, ReportsChan,
|
||||
|
@ -152,6 +154,7 @@ pub fn new_core_resource_thread(
|
|||
ca_certificates,
|
||||
ignore_certificate_errors,
|
||||
cancellation_listeners: Default::default(),
|
||||
cookie_listeners: Default::default(),
|
||||
};
|
||||
|
||||
mem_profiler_chan.run_with_memory_reporting(
|
||||
|
@ -179,6 +182,7 @@ struct ResourceChannelManager {
|
|||
ca_certificates: CACertificates,
|
||||
ignore_certificate_errors: bool,
|
||||
cancellation_listeners: HashMap<RequestId, Weak<CancellationListener>>,
|
||||
cookie_listeners: HashMap<CookieStoreId, IpcSender<CookieAsyncResponse>>,
|
||||
}
|
||||
|
||||
fn create_http_states(
|
||||
|
@ -335,6 +339,20 @@ impl ResourceChannelManager {
|
|||
cancellation_listener
|
||||
}
|
||||
|
||||
fn send_cookie_response(&self, store_id: CookieStoreId, data: CookieData) {
|
||||
let Some(sender) = self.cookie_listeners.get(&store_id) else {
|
||||
warn!(
|
||||
"Async cookie request made for store id that is non-existent {:?}",
|
||||
store_id
|
||||
);
|
||||
return;
|
||||
};
|
||||
let res = sender.send(CookieAsyncResponse { data });
|
||||
if res.is_err() {
|
||||
warn!("Unable to send cookie response to script thread");
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns false if the thread should exit.
|
||||
fn process_msg(
|
||||
&mut self,
|
||||
|
@ -398,6 +416,14 @@ impl ResourceChannelManager {
|
|||
.delete_cookie_with_name(&request, name);
|
||||
return true;
|
||||
},
|
||||
CoreResourceMsg::DeleteCookieAsync(cookie_store_id, url, name) => {
|
||||
http_state
|
||||
.cookie_jar
|
||||
.write()
|
||||
.unwrap()
|
||||
.delete_cookie_with_name(&url, name);
|
||||
self.send_cookie_response(cookie_store_id, CookieData::Delete(Ok(())));
|
||||
},
|
||||
CoreResourceMsg::FetchRedirect(request_builder, res_init, sender) => {
|
||||
let cancellation_listener =
|
||||
self.get_or_create_cancellation_listener(request_builder.id);
|
||||
|
@ -423,6 +449,15 @@ impl ResourceChannelManager {
|
|||
);
|
||||
}
|
||||
},
|
||||
CoreResourceMsg::SetCookieForUrlAsync(cookie_store_id, url, cookie, source) => {
|
||||
self.resource_manager.set_cookie_for_url(
|
||||
&url,
|
||||
cookie.into_inner().to_owned(),
|
||||
source,
|
||||
http_state,
|
||||
);
|
||||
self.send_cookie_response(cookie_store_id, CookieData::Set(Ok(())));
|
||||
},
|
||||
CoreResourceMsg::GetCookiesForUrl(url, consumer, source) => {
|
||||
let mut cookie_jar = http_state.cookie_jar.write().unwrap();
|
||||
cookie_jar.remove_expired_cookies_for_url(&url);
|
||||
|
@ -430,6 +465,33 @@ impl ResourceChannelManager {
|
|||
.send(cookie_jar.cookies_for_url(&url, source))
|
||||
.unwrap();
|
||||
},
|
||||
CoreResourceMsg::GetCookieDataForUrlAsync(cookie_store_id, url, name) => {
|
||||
let mut cookie_jar = http_state.cookie_jar.write().unwrap();
|
||||
cookie_jar.remove_expired_cookies_for_url(&url);
|
||||
let cookie = cookie_jar
|
||||
.query_cookies(&url, name)
|
||||
.into_iter()
|
||||
.map(Serde)
|
||||
.next();
|
||||
self.send_cookie_response(cookie_store_id, CookieData::Get(cookie));
|
||||
},
|
||||
CoreResourceMsg::GetAllCookieDataForUrlAsync(cookie_store_id, url, name) => {
|
||||
let mut cookie_jar = http_state.cookie_jar.write().unwrap();
|
||||
cookie_jar.remove_expired_cookies_for_url(&url);
|
||||
let cookies = cookie_jar
|
||||
.query_cookies(&url, name)
|
||||
.into_iter()
|
||||
.map(Serde)
|
||||
.collect();
|
||||
self.send_cookie_response(cookie_store_id, CookieData::GetAll(cookies));
|
||||
},
|
||||
CoreResourceMsg::NewCookieListener(cookie_store_id, sender, _url) => {
|
||||
// TODO: Use the URL for setting up the actual monitoring
|
||||
self.cookie_listeners.insert(cookie_store_id, sender);
|
||||
},
|
||||
CoreResourceMsg::RemoveCookieListener(cookie_store_id) => {
|
||||
self.cookie_listeners.remove(&cookie_store_id);
|
||||
},
|
||||
CoreResourceMsg::NetworkMediator(mediator_chan, origin) => {
|
||||
self.resource_manager
|
||||
.sw_managers
|
||||
|
|
615
components/script/dom/cookiestore.rs
Normal file
615
components/script/dom/cookiestore.rs
Normal file
|
@ -0,0 +1,615 @@
|
|||
/* 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::Cow;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
use base::id::CookieStoreId;
|
||||
use cookie::Expiration::DateTime;
|
||||
use cookie::{Cookie, SameSite};
|
||||
use dom_struct::dom_struct;
|
||||
use hyper_serde::Serde;
|
||||
use ipc_channel::ipc;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use itertools::Itertools;
|
||||
use js::jsval::NullValue;
|
||||
use net_traits::CookieSource::NonHTTP;
|
||||
use net_traits::{CookieAsyncResponse, CookieData, CoreResourceMsg, IpcSend};
|
||||
use script_bindings::script_runtime::CanGc;
|
||||
use servo_url::ServoUrl;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::CookieStoreBinding::{
|
||||
CookieInit, CookieListItem, CookieSameSite, CookieStoreDeleteOptions, CookieStoreGetOptions,
|
||||
CookieStoreMethods,
|
||||
};
|
||||
use crate::dom::bindings::error::Error;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::USVString;
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::promise::Promise;
|
||||
use crate::dom::window::Window;
|
||||
use crate::task_source::SendableTaskSource;
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/>
|
||||
/// CookieStore provides an async API for pages and service workers to access and modify cookies.
|
||||
/// This requires setting up communication with resource thread's cookie storage that allows for
|
||||
/// the page to have multiple cookie storage promises in flight at the same time.
|
||||
#[dom_struct]
|
||||
pub(crate) struct CookieStore {
|
||||
eventtarget: EventTarget,
|
||||
#[ignore_malloc_size_of = "Rc"]
|
||||
in_flight: DomRefCell<VecDeque<Rc<Promise>>>,
|
||||
// Store an id so that we can send it with requests and the resource thread knows who to respond to
|
||||
#[no_trace]
|
||||
store_id: CookieStoreId,
|
||||
}
|
||||
|
||||
struct CookieListener {
|
||||
// TODO:(whatwg/cookiestore#239) The spec is missing details for what task source to use
|
||||
task_source: SendableTaskSource,
|
||||
context: Trusted<CookieStore>,
|
||||
}
|
||||
|
||||
impl CookieListener {
|
||||
pub(crate) fn handle(&self, message: CookieAsyncResponse) {
|
||||
let context = self.context.clone();
|
||||
self.task_source.queue(task!(cookie_message: move || {
|
||||
let Some(promise) = context.root().in_flight.borrow_mut().pop_front() else {
|
||||
warn!("No promise exists for cookie store response");
|
||||
return;
|
||||
};
|
||||
match message.data {
|
||||
CookieData::Get(cookie) => {
|
||||
// If list is failure, then reject p with a TypeError and abort these steps.
|
||||
// (There is currently no way for list to result in failure)
|
||||
if let Some(cookie) = cookie {
|
||||
// Otherwise, resolve p with the first item of list.
|
||||
promise.resolve_native(&cookie_to_list_item(cookie.into_inner()), CanGc::note());
|
||||
} else {
|
||||
// If list is empty, then resolve p with null.
|
||||
promise.resolve_native(&NullValue(), CanGc::note());
|
||||
}
|
||||
},
|
||||
CookieData::GetAll(cookies) => {
|
||||
// If list is failure, then reject p with a TypeError and abort these steps.
|
||||
promise.resolve_native(
|
||||
&cookies
|
||||
.into_iter()
|
||||
.map(|cookie| cookie_to_list_item(cookie.0))
|
||||
.collect_vec(),
|
||||
CanGc::note());
|
||||
},
|
||||
CookieData::Delete(_) | CookieData::Change(_) | CookieData::Set(_) => {
|
||||
promise.resolve_native(&(), CanGc::note());
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl CookieStore {
|
||||
fn new_inherited() -> CookieStore {
|
||||
CookieStore {
|
||||
eventtarget: EventTarget::new_inherited(),
|
||||
in_flight: Default::default(),
|
||||
store_id: CookieStoreId::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<CookieStore> {
|
||||
let store = reflect_dom_object(Box::new(CookieStore::new_inherited()), global, can_gc);
|
||||
store.setup_route();
|
||||
store
|
||||
}
|
||||
|
||||
fn setup_route(&self) {
|
||||
let (cookie_sender, cookie_receiver) = ipc::channel().expect("ipc channel failure");
|
||||
|
||||
let context = Trusted::new(self);
|
||||
let cs_listener = CookieListener {
|
||||
task_source: self
|
||||
.global()
|
||||
.task_manager()
|
||||
.dom_manipulation_task_source()
|
||||
.to_sendable(),
|
||||
context,
|
||||
};
|
||||
|
||||
ROUTER.add_typed_route(
|
||||
cookie_receiver,
|
||||
Box::new(move |message| match message {
|
||||
Ok(msg) => cs_listener.handle(msg),
|
||||
Err(err) => warn!("Error receiving a CookieStore message: {:?}", err),
|
||||
}),
|
||||
);
|
||||
|
||||
let res = self
|
||||
.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::NewCookieListener(
|
||||
self.store_id,
|
||||
cookie_sender,
|
||||
self.global().creation_url().clone(),
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#create-a-cookielistitem>
|
||||
fn cookie_to_list_item(cookie: Cookie) -> CookieListItem {
|
||||
// TODO: Investigate if we need to explicitly UTF-8 decode without BOM here or if thats
|
||||
// already being done by cookie-rs or implicitly by using rust strings
|
||||
CookieListItem {
|
||||
// Let domain be the result of running UTF-8 decode without BOM on cookie’s domain.
|
||||
domain: cookie
|
||||
.domain()
|
||||
.map(|domain| Some(domain.to_string().into())),
|
||||
|
||||
// Let expires be cookie’s expiry-time (as a timestamp).
|
||||
expires: match cookie.expires() {
|
||||
None | Some(cookie::Expiration::Session) => None,
|
||||
Some(DateTime(time)) => Some(Some(Finite::wrap((time.unix_timestamp() * 1000) as f64))),
|
||||
},
|
||||
|
||||
// Let name be the result of running UTF-8 decode without BOM on cookie’s name.
|
||||
name: Some(cookie.name().to_string().into()),
|
||||
|
||||
// Let partitioned be a boolean indicating that the user agent supports cookie partitioning and that i
|
||||
// that cookie has a partition key.
|
||||
partitioned: Some(false), // Do we support partitioning? Spec says true only if UA supports it
|
||||
|
||||
// Let path be the result of running UTF-8 decode without BOM on cookie’s path.
|
||||
path: cookie.path().map(|path| path.to_string().into()),
|
||||
|
||||
sameSite: match cookie.same_site() {
|
||||
Some(SameSite::None) => Some(CookieSameSite::None),
|
||||
Some(SameSite::Lax) => Some(CookieSameSite::Lax),
|
||||
Some(SameSite::Strict) => Some(CookieSameSite::Strict),
|
||||
None => None, // The spec doesnt handle this case, which implies the default of Lax?
|
||||
},
|
||||
|
||||
// Let secure be cookie’s secure-only-flag.
|
||||
secure: cookie.secure(),
|
||||
|
||||
// Let value be the result of running UTF-8 decode without BOM on cookie’s value.
|
||||
value: Some(cookie.value().to_string().into()),
|
||||
}
|
||||
}
|
||||
|
||||
impl CookieStoreMethods<crate::DomTypeHolder> for CookieStore {
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-get>
|
||||
fn Get(&self, name: USVString, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 5. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 4. Let url be settings’s creation URL.
|
||||
let creation_url = global.creation_url();
|
||||
|
||||
// 6. Run the following steps in parallel:
|
||||
let res = self
|
||||
.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::GetCookieDataForUrlAsync(
|
||||
self.store_id,
|
||||
creation_url.clone(),
|
||||
Some(name.to_string()),
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
// 7. Return p.
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-get-options>
|
||||
fn Get_(&self, options: &CookieStoreGetOptions, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 7. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 4. Let url be settings’s creation URL.
|
||||
let creation_url = global.creation_url();
|
||||
|
||||
// 5. If options is empty, then return a promise rejected with a TypeError.
|
||||
// "is empty" is not strictly defined anywhere in the spec but the only value we require here is "url"
|
||||
if options.url.is_none() && options.name.is_none() {
|
||||
p.reject_error(Error::Type("Options cannot be empty".to_string()), can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
let mut final_url = creation_url.clone();
|
||||
|
||||
// 6. If options["url"] is present, then run these steps:
|
||||
if let Some(get_url) = &options.url {
|
||||
// 6.1. Let parsed be the result of parsing options["url"] with settings’s API base URL.
|
||||
let parsed_url = ServoUrl::parse_with_base(Some(&global.api_base_url()), get_url);
|
||||
|
||||
// 6.2. If this’s relevant global object is a Window object and parsed does not equal url,
|
||||
// then return a promise rejected with a TypeError.
|
||||
if let Some(_window) = DomRoot::downcast::<Window>(self.global()) {
|
||||
if parsed_url
|
||||
.as_ref()
|
||||
.is_ok_and(|parsed| parsed.as_url() != creation_url.as_url())
|
||||
{
|
||||
p.reject_error(
|
||||
Error::Type("URL does not match context".to_string()),
|
||||
can_gc,
|
||||
);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
// 6.3. If parsed’s origin and url’s origin are not the same origin,
|
||||
// then return a promise rejected with a TypeError.
|
||||
if parsed_url
|
||||
.as_ref()
|
||||
.is_ok_and(|parsed| creation_url.origin() != parsed.origin())
|
||||
{
|
||||
p.reject_error(Error::Type("Not same origin".to_string()), can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 6.4. Set url to parsed.
|
||||
if let Ok(url) = parsed_url {
|
||||
final_url = url;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Run the following steps in parallel:
|
||||
let res = self
|
||||
.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::GetCookieDataForUrlAsync(
|
||||
self.store_id,
|
||||
final_url.clone(),
|
||||
options.name.clone().map(|val| val.0),
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-getall>
|
||||
fn GetAll(&self, name: USVString, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 5. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
// 4. Let url be settings’s creation URL.
|
||||
let creation_url = global.creation_url();
|
||||
|
||||
// 6. Run the following steps in parallel:
|
||||
let res =
|
||||
self.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::GetAllCookieDataForUrlAsync(
|
||||
self.store_id,
|
||||
creation_url.clone(),
|
||||
Some(name.to_string()),
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
// 7. Return p.
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-getall-options>
|
||||
fn GetAll_(&self, options: &CookieStoreGetOptions, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 7. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 4. Let url be settings’s creation URL.
|
||||
let creation_url = global.creation_url();
|
||||
|
||||
// 5. If options is empty, then return a promise rejected with a TypeError.
|
||||
// "is empty" is not strictly defined anywhere in the spec but the only value we require here is "url"
|
||||
if options.url.is_none() && options.name.is_none() {
|
||||
p.reject_error(Error::Type("Options cannot be empty".to_string()), can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
let mut final_url = creation_url.clone();
|
||||
|
||||
// 6. If options["url"] is present, then run these steps:
|
||||
if let Some(get_url) = &options.url {
|
||||
// 6.1. Let parsed be the result of parsing options["url"] with settings’s API base URL.
|
||||
let parsed_url = ServoUrl::parse_with_base(Some(&global.api_base_url()), get_url);
|
||||
|
||||
// 6.2. If this’s relevant global object is a Window object and parsed does not equal url,
|
||||
// then return a promise rejected with a TypeError.
|
||||
if let Some(_window) = DomRoot::downcast::<Window>(self.global()) {
|
||||
if parsed_url
|
||||
.as_ref()
|
||||
.is_ok_and(|parsed| parsed.as_url() != creation_url.as_url())
|
||||
{
|
||||
p.reject_error(
|
||||
Error::Type("URL does not match context".to_string()),
|
||||
can_gc,
|
||||
);
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
// 6.3. If parsed’s origin and url’s origin are not the same origin,
|
||||
// then return a promise rejected with a TypeError.
|
||||
if parsed_url
|
||||
.as_ref()
|
||||
.is_ok_and(|parsed| creation_url.origin() != parsed.origin())
|
||||
{
|
||||
p.reject_error(Error::Type("Not same origin".to_string()), can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 6.4. Set url to parsed.
|
||||
if let Ok(url) = parsed_url {
|
||||
final_url = url;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Run the following steps in parallel:
|
||||
let res =
|
||||
self.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::GetAllCookieDataForUrlAsync(
|
||||
self.store_id,
|
||||
final_url.clone(),
|
||||
options.name.clone().map(|val| val.0),
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
// 8. Return p
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-set>
|
||||
fn Set(&self, name: USVString, value: USVString, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 9. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 4. Let url be settings’s creation URL.
|
||||
// 5. Let domain be null.
|
||||
// 6. Let path be "/".
|
||||
// 7. Let sameSite be strict.
|
||||
// 8. Let partitioned be false.
|
||||
let cookie = Cookie::build((Cow::Owned(name.to_string()), Cow::Owned(value.to_string())))
|
||||
.path("/")
|
||||
.secure(true)
|
||||
.same_site(SameSite::Strict)
|
||||
.partitioned(false);
|
||||
// TODO: This currently doesn't implement all the "set a cookie" steps which involves
|
||||
// additional processing of the name and value
|
||||
|
||||
// 10. Run the following steps in parallel:
|
||||
let res = self
|
||||
.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::SetCookieForUrlAsync(
|
||||
self.store_id,
|
||||
self.global().creation_url().clone(),
|
||||
Serde(cookie.build()),
|
||||
NonHTTP,
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
// 11. Return p.
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-set-options>
|
||||
fn Set_(&self, options: &CookieInit, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 5. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 4. Let url be settings’s creation URL.
|
||||
let creation_url = global.creation_url();
|
||||
|
||||
// 6.1. Let r be the result of running set a cookie with url, options["name"], options["value"],
|
||||
// options["expires"], options["domain"], options["path"], options["sameSite"], and options["partitioned"].
|
||||
let cookie = Cookie::build((
|
||||
Cow::Owned(options.name.to_string()),
|
||||
Cow::Owned(options.value.to_string()),
|
||||
));
|
||||
// TODO: This currently doesn't implement all the "set a cookie" steps which involves
|
||||
// additional processing of the name and value
|
||||
|
||||
// 6. Run the following steps in parallel:
|
||||
let res = self
|
||||
.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::SetCookieForUrlAsync(
|
||||
self.store_id,
|
||||
creation_url.clone(),
|
||||
Serde(cookie.build()),
|
||||
NonHTTP,
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
// 7. Return p
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-delete>
|
||||
fn Delete(&self, name: USVString, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 5. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 6. Run the following steps in parallel:
|
||||
// TODO: the spec passes additional parameters to _delete a cookie_ that we don't handle yet
|
||||
let res = global
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::DeleteCookieAsync(
|
||||
self.store_id,
|
||||
global.creation_url().clone(),
|
||||
name.0,
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
// 7. Return p.
|
||||
p
|
||||
}
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#dom-cookiestore-delete-options>
|
||||
fn Delete_(&self, options: &CookieStoreDeleteOptions, can_gc: CanGc) -> Rc<Promise> {
|
||||
// 1. Let settings be this’s relevant settings object.
|
||||
let global = self.global();
|
||||
|
||||
// 2. Let origin be settings’s origin.
|
||||
let origin = global.origin();
|
||||
|
||||
// 5. Let p be a new promise.
|
||||
let p = Promise::new(&global, can_gc);
|
||||
|
||||
// 3. If origin is an opaque origin, then return a promise rejected with a "SecurityError" DOMException.
|
||||
if !origin.is_tuple() {
|
||||
p.reject_error(Error::Security, can_gc);
|
||||
return p;
|
||||
}
|
||||
|
||||
// 6. Run the following steps in parallel:
|
||||
// TODO: the spec passes additional parameters to _delete a cookie_ that we don't handle yet
|
||||
let res = global
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::DeleteCookieAsync(
|
||||
self.store_id,
|
||||
global.creation_url().clone(),
|
||||
options.name.to_string(),
|
||||
));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
} else {
|
||||
self.in_flight.borrow_mut().push_back(p.clone());
|
||||
}
|
||||
|
||||
// 7. Return p.
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CookieStore {
|
||||
fn drop(&mut self) {
|
||||
let res = self
|
||||
.global()
|
||||
.resource_threads()
|
||||
.send(CoreResourceMsg::RemoveCookieListener(self.store_id));
|
||||
if res.is_err() {
|
||||
error!("Failed to send cookiestore message to resource threads");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -115,7 +115,7 @@ use crate::dom::reportingobserver::ReportingObserver;
|
|||
use crate::dom::serviceworker::ServiceWorker;
|
||||
use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
|
||||
use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory;
|
||||
use crate::dom::types::{DebuggerGlobalScope, MessageEvent};
|
||||
use crate::dom::types::{CookieStore, DebuggerGlobalScope, MessageEvent};
|
||||
use crate::dom::underlyingsourcecontainer::UnderlyingSourceType;
|
||||
#[cfg(feature = "webgpu")]
|
||||
use crate::dom::webgpu::gpudevice::GPUDevice;
|
||||
|
@ -209,6 +209,9 @@ pub(crate) struct GlobalScope {
|
|||
HashMapTracedValues<ServiceWorkerRegistrationId, Dom<ServiceWorkerRegistration>>,
|
||||
>,
|
||||
|
||||
/// <https://cookiestore.spec.whatwg.org/#globals>
|
||||
cookie_store: MutNullableDom<CookieStore>,
|
||||
|
||||
/// <https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-object-map>
|
||||
worker_map: DomRefCell<HashMapTracedValues<ServiceWorkerId, Dom<ServiceWorker>>>,
|
||||
|
||||
|
@ -747,6 +750,7 @@ impl GlobalScope {
|
|||
eventtarget: EventTarget::new_inherited(),
|
||||
crypto: Default::default(),
|
||||
registration_map: DomRefCell::new(HashMapTracedValues::new()),
|
||||
cookie_store: Default::default(),
|
||||
worker_map: DomRefCell::new(HashMapTracedValues::new()),
|
||||
pipeline_id,
|
||||
devtools_wants_updates: Default::default(),
|
||||
|
@ -2361,6 +2365,10 @@ impl GlobalScope {
|
|||
self.crypto.or_init(|| Crypto::new(self, can_gc))
|
||||
}
|
||||
|
||||
pub(crate) fn cookie_store(&self, can_gc: CanGc) -> DomRoot<CookieStore> {
|
||||
self.cookie_store.or_init(|| CookieStore::new(self, can_gc))
|
||||
}
|
||||
|
||||
pub(crate) fn live_devtools_updates(&self) -> bool {
|
||||
self.devtools_wants_updates.get()
|
||||
}
|
||||
|
|
|
@ -259,6 +259,7 @@ pub(crate) mod comment;
|
|||
pub(crate) mod compositionevent;
|
||||
pub(crate) mod console;
|
||||
pub(crate) mod constantsourcenode;
|
||||
pub(crate) mod cookiestore;
|
||||
pub(crate) mod countqueuingstrategy;
|
||||
mod create;
|
||||
pub(crate) mod crypto;
|
||||
|
|
|
@ -127,6 +127,7 @@ use crate::dom::bindings::utils::GlobalStaticData;
|
|||
use crate::dom::bindings::weakref::DOMTracker;
|
||||
#[cfg(feature = "bluetooth")]
|
||||
use crate::dom::bluetooth::BluetoothExtraPermissionData;
|
||||
use crate::dom::cookiestore::CookieStore;
|
||||
use crate::dom::crypto::Crypto;
|
||||
use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
|
||||
use crate::dom::customelementregistry::CustomElementRegistry;
|
||||
|
@ -1066,6 +1067,11 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
|
|||
.or_init(|| Storage::new(self, StorageType::Local, CanGc::note()))
|
||||
}
|
||||
|
||||
// https://cookiestore.spec.whatwg.org/#Window
|
||||
fn CookieStore(&self, can_gc: CanGc) -> DomRoot<CookieStore> {
|
||||
self.global().cookie_store(can_gc)
|
||||
}
|
||||
|
||||
// https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html#dfn-GlobalCrypto
|
||||
fn Crypto(&self) -> DomRoot<Crypto> {
|
||||
self.as_global_scope().crypto(CanGc::note())
|
||||
|
|
|
@ -100,6 +100,10 @@ DOMInterfaces = {
|
|||
'canGc': ['Types']
|
||||
},
|
||||
|
||||
'CookieStore': {
|
||||
'canGc': ['Set', 'Set_', 'Get', 'Get_', 'GetAll', 'GetAll_', 'Delete', 'Delete_']
|
||||
},
|
||||
|
||||
'CountQueuingStrategy': {
|
||||
'canGc': ['GetSize'],
|
||||
},
|
||||
|
@ -655,7 +659,7 @@ DOMInterfaces = {
|
|||
},
|
||||
|
||||
'Window': {
|
||||
'canGc': ['Stop', 'Fetch', 'Stop', 'Fetch', 'Open', 'CreateImageBitmap', 'CreateImageBitmap_', 'SetInterval', 'SetTimeout', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'],
|
||||
'canGc': ['CreateImageBitmap', 'CreateImageBitmap_', 'CookieStore', 'Fetch', 'Open', 'SetInterval', 'SetTimeout', 'Stop', 'TrustedTypes', 'WebdriverCallback', 'WebdriverException'],
|
||||
'inRealms': ['Fetch', 'GetOpener', 'WebdriverCallback', 'WebdriverException'],
|
||||
'additionalTraits': ['crate::interfaces::WindowHelpers'],
|
||||
},
|
||||
|
|
71
components/script_bindings/webidls/CookieStore.webidl
Normal file
71
components/script_bindings/webidls/CookieStore.webidl
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* 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/. */
|
||||
|
||||
// https://cookiestore.spec.whatwg.org/
|
||||
|
||||
[Exposed=(ServiceWorker,Window),
|
||||
SecureContext,
|
||||
Pref="dom_cookiestore_enabled"]
|
||||
interface CookieStore : EventTarget {
|
||||
Promise<CookieListItem?> get(USVString name);
|
||||
Promise<CookieListItem?> get(optional CookieStoreGetOptions options = {});
|
||||
|
||||
Promise<CookieList> getAll(USVString name);
|
||||
Promise<CookieList> getAll(optional CookieStoreGetOptions options = {});
|
||||
|
||||
Promise<undefined> set(USVString name, USVString value);
|
||||
Promise<undefined> set(CookieInit options);
|
||||
|
||||
Promise<undefined> delete(USVString name);
|
||||
Promise<undefined> delete(CookieStoreDeleteOptions options);
|
||||
|
||||
// [Exposed=Window]
|
||||
// attribute EventHandler onchange;
|
||||
};
|
||||
|
||||
dictionary CookieStoreGetOptions {
|
||||
USVString name;
|
||||
USVString url;
|
||||
};
|
||||
|
||||
enum CookieSameSite {
|
||||
"strict",
|
||||
"lax",
|
||||
"none"
|
||||
};
|
||||
|
||||
dictionary CookieInit {
|
||||
required USVString name;
|
||||
required USVString value;
|
||||
DOMHighResTimeStamp? expires = null;
|
||||
USVString? domain = null;
|
||||
USVString path = "/";
|
||||
CookieSameSite sameSite = "strict";
|
||||
boolean partitioned = false;
|
||||
};
|
||||
|
||||
dictionary CookieStoreDeleteOptions {
|
||||
required USVString name;
|
||||
USVString? domain = null;
|
||||
USVString path = "/";
|
||||
boolean partitioned = false;
|
||||
};
|
||||
|
||||
dictionary CookieListItem {
|
||||
USVString name;
|
||||
USVString value;
|
||||
USVString? domain;
|
||||
USVString path;
|
||||
DOMHighResTimeStamp? expires;
|
||||
boolean secure;
|
||||
CookieSameSite sameSite;
|
||||
boolean partitioned;
|
||||
};
|
||||
|
||||
typedef sequence<CookieListItem> CookieList;
|
||||
|
||||
[SecureContext]
|
||||
partial interface Window {
|
||||
[SameObject, Pref="dom_cookiestore_enabled"] readonly attribute CookieStore cookieStore;
|
||||
};
|
|
@ -376,6 +376,8 @@ namespace_id! {ImageBitmapId, ImageBitmapIndex, "ImageBitmap"}
|
|||
|
||||
namespace_id! {OffscreenCanvasId, OffscreenCanvasIndex, "OffscreenCanvas"}
|
||||
|
||||
namespace_id! {CookieStoreId, CookieStoreIndex, "CookieStore"}
|
||||
|
||||
// We provide ids just for unit testing.
|
||||
pub const TEST_NAMESPACE: PipelineNamespaceId = PipelineNamespaceId(1234);
|
||||
pub const TEST_PIPELINE_INDEX: Index<PipelineIndex> =
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::sync::{LazyLock, OnceLock};
|
|||
use std::thread::{self, JoinHandle};
|
||||
|
||||
use base::cross_process_instant::CrossProcessInstant;
|
||||
use base::id::HistoryStateId;
|
||||
use base::id::{CookieStoreId, HistoryStateId};
|
||||
use content_security_policy::{self as csp};
|
||||
use cookie::Cookie;
|
||||
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||
|
@ -526,6 +526,12 @@ pub enum CoreResourceMsg {
|
|||
SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
|
||||
/// Store a set of cookies for a given originating URL
|
||||
SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
|
||||
SetCookieForUrlAsync(
|
||||
CookieStoreId,
|
||||
ServoUrl,
|
||||
Serde<Cookie<'static>>,
|
||||
CookieSource,
|
||||
),
|
||||
/// Retrieve the stored cookies for a given URL
|
||||
GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
|
||||
/// Get a cookie by name for a given originating URL
|
||||
|
@ -534,8 +540,13 @@ pub enum CoreResourceMsg {
|
|||
IpcSender<Vec<Serde<Cookie<'static>>>>,
|
||||
CookieSource,
|
||||
),
|
||||
GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
|
||||
GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
|
||||
DeleteCookies(ServoUrl),
|
||||
DeleteCookie(ServoUrl, String),
|
||||
DeleteCookieAsync(CookieStoreId, ServoUrl, String),
|
||||
NewCookieListener(CookieStoreId, IpcSender<CookieAsyncResponse>, ServoUrl),
|
||||
RemoveCookieListener(CookieStoreId),
|
||||
/// Get a history state by a given history state id
|
||||
GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>),
|
||||
/// Set a history state for a given history state id
|
||||
|
@ -976,6 +987,26 @@ pub enum CookieSource {
|
|||
NonHTTP,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct CookieChange {
|
||||
changed: Vec<Serde<Cookie<'static>>>,
|
||||
deleted: Vec<Serde<Cookie<'static>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum CookieData {
|
||||
Change(CookieChange),
|
||||
Get(Option<Serde<Cookie<'static>>>),
|
||||
GetAll(Vec<Serde<Cookie<'static>>>),
|
||||
Set(Result<(), ()>),
|
||||
Delete(Result<(), ()>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct CookieAsyncResponse {
|
||||
pub data: CookieData,
|
||||
}
|
||||
|
||||
/// Network errors that have to be exported out of the loaders
|
||||
#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
|
||||
pub enum NetworkError {
|
||||
|
|
|
@ -138,6 +138,7 @@ WEBIDL_STANDARDS = [
|
|||
b"//gpuweb.github.io",
|
||||
b"//notifications.spec.whatwg.org",
|
||||
b"//testutils.spec.whatwg.org/",
|
||||
b"//cookiestore.spec.whatwg.org/",
|
||||
# Not a URL
|
||||
b"// This interface is entirely internal to Servo, and should not be" + b" accessible to\n// web pages.",
|
||||
]
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[cookieStore_delete.sub.https.html]
|
||||
expected: TIMEOUT
|
||||
[Async Cookies: cookieStore basic API across origins]
|
||||
expected: TIMEOUT
|
|
@ -1,57 +1,37 @@
|
|||
[cookieStore_delete_arguments.https.any.html]
|
||||
[cookieStore.delete with positional name]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with name in options]
|
||||
expected: FAIL
|
||||
|
||||
expected: TIMEOUT
|
||||
[cookieStore.delete domain starts with "."]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with domain that is not equal current host]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with domain set to the current hostname]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with domain set to a subdomain of the current hostname]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with domain set to a non-domain-matching suffix of the current hostname]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with path set to the current directory]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with path set to subdirectory of the current directory]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete does not append / at the end of path]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete can delete a cookie set by document.cookie if document is defined]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with path that does not start with /]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with get result]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with positional empty name]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[cookieStore.delete with empty name in options]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.delete with maximum cookie name size]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.delete with a __Host- prefix should not have a domain]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.delete with whitespace]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
|
||||
[cookieStore_delete_arguments.https.any.serviceworker.html]
|
||||
|
|
|
@ -2,5 +2,3 @@
|
|||
expected: ERROR
|
||||
|
||||
[cookieStore_delete_basic.https.any.html]
|
||||
[cookieStore.delete return type is Promise<void>]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[cookieStore_event_basic.https.window.html]
|
||||
expected: ERROR
|
||||
expected: TIMEOUT
|
||||
[cookieStore fires change event for cookie set by cookieStore.set()]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[cookieStore_event_delete.https.window.html]
|
||||
expected: ERROR
|
||||
expected: TIMEOUT
|
||||
[cookieStore fires change event for cookie deleted by cookieStore.delete()]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[cookieStore does not fire change events for non-existing expired cookies]
|
||||
expected: NOTRUN
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[cookieStore_event_overwrite.https.window.html]
|
||||
expected: ERROR
|
||||
expected: TIMEOUT
|
||||
[cookieStore fires change event for cookie overwritten by cookieStore.set()]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
|
|
@ -8,27 +8,6 @@
|
|||
[cookieStore.getAll with empty options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with positional name]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with name in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with name in both positional arguments and options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with absolute url in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with relative url in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with invalid url path in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with invalid url host in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.getAll with absolute url with fragment in options]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
[cookieStore_getAll_set_basic.https.any.html]
|
||||
[cookieStore.getAll returns the cookie written by cookieStore.set]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[cookieStore_getAll_set_basic.https.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
|
|
@ -2,33 +2,6 @@
|
|||
expected: ERROR
|
||||
|
||||
[cookieStore_get_arguments.https.any.html]
|
||||
[cookieStore.get with no arguments returns TypeError]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with empty options returns TypeError]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with positional name]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with name in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with name in both positional arguments and options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with absolute url in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with relative url in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with invalid url path in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with invalid url host in options]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get with absolute url with fragment in options]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
[cookieStore_get_delete_basic.https.any.html]
|
||||
[cookieStore.get returns null for a cookie deleted by cookieStore.delete]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[cookieStore_get_delete_basic.https.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
[cookieStore_get_set_across_origins.sub.https.html]
|
||||
expected: TIMEOUT
|
||||
[cookieStore.get() sees cookieStore.set() in cross-origin frame]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.get() in cross-origin frame sees cookieStore.set()]
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set() in cross-origin does not overwrite the __Host- cookie]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
||||
[__Host- cookies set via cookieStore.set() in same-site domains don't overwrite each other]
|
||||
expected: NOTRUN
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
[cookieStore_get_set_basic.https.any.html]
|
||||
[cookieStore.get returns the cookie written by cookieStore.set]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[cookieStore_get_set_basic.https.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[cookieStore_get_set_creation_url.https.any.html]
|
||||
[cookieStore.set and cookieStore.get use the creation url]
|
||||
expected: FAIL
|
|
@ -1,7 +0,0 @@
|
|||
[cookieStore_get_set_creation_url.sub.https.html]
|
||||
expected: TIMEOUT
|
||||
[cookieStore.get() option url ignores fragments]
|
||||
expected: TIMEOUT
|
||||
|
||||
[cookieStore.get() option url + pushState()]
|
||||
expected: NOTRUN
|
|
@ -1,11 +1,4 @@
|
|||
[cookieStore_get_set_ordering.https.any.html]
|
||||
expected: ERROR
|
||||
[Set three simple origin session cookies sequentially and ensure they all end up in the cookie jar in order.]
|
||||
expected: FAIL
|
||||
|
||||
[Set three simple origin session cookies in undefined order using Promise.all and ensure they all end up in the cookie jar in any order. ]
|
||||
expected: NOTRUN
|
||||
|
||||
|
||||
[cookieStore_get_set_ordering.https.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[cookieStore_in_detached_frame.https.html]
|
||||
[cookieStore on DOMWindow of detached iframe]
|
||||
expected: FAIL
|
|
@ -2,164 +2,159 @@
|
|||
expected: ERROR
|
||||
|
||||
[cookieStore_set_arguments.https.any.html]
|
||||
[cookieStore.set with positional name and value]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set with name and value in options]
|
||||
expected: FAIL
|
||||
|
||||
expected: TIMEOUT
|
||||
[cookieStore.set fails with empty name and empty value]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[cookieStore.set with empty name and an '=' in value]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with normal name and an '=' in value]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0000]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0001]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0002]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0003]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0004]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0005]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0006]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0007]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0008]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0010]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0011]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0012]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0013]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0014]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0015]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0016]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0017]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0018]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+0019]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+001A]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+001B]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+001C]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+001D]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+001E]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+001F]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+003B]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if name or value contain invalid character U+007F]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with expires set to a future Date]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with expires set to a past Date]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with expires set to a future timestamp]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with expires set to a past timestamp]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set domain starts with "."]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with domain that is not equal current host]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with domain set to the current hostname]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with domain set to a subdomain of the current hostname]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with domain set to a non-domain-matching suffix of the current hostname]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set default domain is null and differs from current hostname]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with path set to the current directory]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with path set to a subdirectory of the current directory]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set default path is /]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set does not add / to path that does not end with /]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set can modify a cookie set by document.cookie if document is defined]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with path that does not start with /]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with get result]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if the path is too long]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set checks if the domain is too long]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with a __Host- prefix should not have a domain]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with whitespace only name and value]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set with whitespace at begining or end]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
[cookieStore_set_domain_parsing.sub.https.html]
|
||||
[cookieStore.set with domain on a IDNA host]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set with domain set to the current hostname but differently cased]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[cookieStore_set_domain_parsing.tentative.sub.https.html]
|
||||
[cookieStore.set with domain on an IP address host]
|
||||
expected: FAIL
|
|
@ -2,14 +2,12 @@
|
|||
expected: ERROR
|
||||
|
||||
[cookieStore_set_limit.https.any.html]
|
||||
[Set max-size cookie with largest possible name and value (4096 bytes)]
|
||||
expected: FAIL
|
||||
|
||||
[Set max-size value-less cookie]
|
||||
expected: FAIL
|
||||
|
||||
[Set max-size cookie with largest possible value (4095 bytes)]
|
||||
expected: FAIL
|
||||
|
||||
expected: TIMEOUT
|
||||
[Set max-size name-less cookie]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[Ignore name-less cookie with value larger than 4096 bytes]
|
||||
expected: NOTRUN
|
||||
|
||||
[Ignore name-less cookie (without leading =) with value larger than 4096 bytes]
|
||||
expected: NOTRUN
|
||||
|
|
|
@ -1,40 +1,5 @@
|
|||
[cookieStore_special_names.https.any.html]
|
||||
[cookieStore.set with __Secure- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set of expired __Secure- cookie name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with __Secure- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set with __secure- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set of expired __secure- cookie name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with __secure- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set with __Host- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set of expired __Host- cookie name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with __Host- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set with __host- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set of expired __host- cookie name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.delete with __host- name on secure origin]
|
||||
expected: FAIL
|
||||
|
||||
expected: TIMEOUT
|
||||
[cookieStore.set with __Host- prefix and a domain option]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -75,34 +40,34 @@
|
|||
expected: FAIL
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Host- prefix]
|
||||
expected: FAIL
|
||||
expected: TIMEOUT
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Secure- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Http- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __HostHttp- prefix]
|
||||
expected: FAIL
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Host- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have \t__Host- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Secure- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have \t__Secure- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Http- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have \t__Http- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __HostHttp- prefix]
|
||||
expected: FAIL
|
||||
|
@ -123,13 +88,13 @@
|
|||
expected: FAIL
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Host-Http- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have __Host-Http- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
[cookieStore.set a nameless cookie cannot have \t__Host-Http- prefix]
|
||||
expected: FAIL
|
||||
expected: NOTRUN
|
||||
|
||||
|
||||
[cookieStore_special_names.https.any.serviceworker.html]
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
[encoding.https.any.html]
|
||||
[BOM not stripped from name]
|
||||
expected: FAIL
|
||||
|
||||
[BOM not stripped from value]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[encoding.https.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
[httponly_cookies.https.window.html]
|
||||
expected: ERROR
|
||||
[HttpOnly cookies are not observed]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -5,48 +5,6 @@
|
|||
[idl_test setup]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: existence and properties of interface object]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface object length]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface object name]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: existence and properties of interface prototype object]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: existence and properties of interface prototype object's "constructor" property]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: existence and properties of interface prototype object's @@unscopables property]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation get(USVString)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation get(optional CookieStoreGetOptions)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation getAll(USVString)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation getAll(optional CookieStoreGetOptions)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation set(USVString, USVString)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation set(CookieInit)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation delete(USVString)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: operation delete(CookieStoreDeleteOptions)]
|
||||
expected: FAIL
|
||||
|
||||
[CookieStore interface: attribute onchange]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -104,9 +62,6 @@
|
|||
[ServiceWorkerRegistration interface: attribute cookies]
|
||||
expected: FAIL
|
||||
|
||||
[Window interface: attribute cookieStore]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[idlharness.https.any.serviceworker.html]
|
||||
expected: ERROR
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue