diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index aba1b99ab5e..46203a031b5 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -60,7 +60,6 @@ impl Blob { blobParts: Option>, blobPropertyBag: &BlobBinding::BlobPropertyBag, ) -> Fallible> { - // TODO: accept other blobParts types - ArrayBuffer or ArrayBufferView let bytes: Vec = match blobParts { None => Vec::new(), Some(blobparts) => match blob_parts_to_bytes(blobparts) { diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index efb3277f1cf..ccb2af4c9b7 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -683,10 +683,25 @@ impl Document { // https://html.spec.whatwg.org/multipage/#fallback-base-url pub fn fallback_base_url(&self) -> ServoUrl { - // Step 1: iframe srcdoc (#4767). - // Step 2: about:blank with a creator browsing context. - // Step 3. - self.url() + let document_url = self.url(); + if let Some(browsing_context) = self.browsing_context() { + // Step 1: If document is an iframe srcdoc document, then return the + // document base URL of document's browsing context's container document. + let container_base_url = browsing_context + .parent() + .and_then(|parent| parent.document()) + .map(|document| document.base_url()); + if document_url.as_str() == "about:srcdoc" && container_base_url.is_some() { + return container_base_url.unwrap(); + } + // Step 2: If document's URL is about:blank, and document's browsing + // context's creator base URL is non-null, then return that creator base URL. + if document_url.as_str() == "about:blank" && browsing_context.has_creator_base_url() { + return browsing_context.creator_base_url().unwrap(); + } + } + // Step 3: Return document's URL. + document_url } // https://html.spec.whatwg.org/multipage/#document-base-url diff --git a/components/script/dom/htmlanchorelement.rs b/components/script/dom/htmlanchorelement.rs index a0f939b93ad..f61d466afd5 100644 --- a/components/script/dom/htmlanchorelement.rs +++ b/components/script/dom/htmlanchorelement.rs @@ -637,7 +637,7 @@ pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option) { if let Some(suffix) = hyperlink_suffix { href.push_str(&suffix); } - let url = match document.url().join(&href) { + let url = match document.base_url().join(&href) { Ok(url) => url, Err(_) => return, }; diff --git a/components/script/dom/location.rs b/components/script/dom/location.rs index e57ecf6cd39..5882a024912 100644 --- a/components/script/dom/location.rs +++ b/components/script/dom/location.rs @@ -9,7 +9,6 @@ use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::USVString; -use crate::dom::document::Document; use crate::dom::globalscope::GlobalScope; use crate::dom::urlhelper::UrlHelper; use crate::dom::window::Window; @@ -66,7 +65,7 @@ impl Location { fn check_same_origin_domain(&self) -> ErrorResult { let this_document = self.window.Document(); if self - .entry_document() + .entry_settings_object() .origin() .same_origin_domain(this_document.origin()) { @@ -76,8 +75,8 @@ impl Location { } } - fn entry_document(&self) -> DomRoot { - GlobalScope::entry().as_window().Document() + fn entry_settings_object(&self) -> DomRoot { + GlobalScope::entry() } // https://html.spec.whatwg.org/multipage/#dom-location-reload @@ -104,7 +103,7 @@ impl LocationMethods for Location { self.check_same_origin_domain()?; // Step 3: Parse url relative to the entry settings object. If that failed, // throw a "SyntaxError" DOMException. - let base_url = self.entry_document().url(); + let base_url = self.entry_settings_object().api_base_url(); let url = match base_url.join(&url.0) { Ok(url) => url, Err(_) => return Err(Error::Syntax), @@ -131,7 +130,7 @@ impl LocationMethods for Location { if self.window.has_document() { // Step 2: Parse url relative to the entry settings object. If that failed, // throw a "SyntaxError" DOMException. - let base_url = self.entry_document().url(); + let base_url = self.entry_settings_object().api_base_url(); let url = match base_url.join(&url.0) { Ok(url) => url, Err(_) => return Err(Error::Syntax), @@ -253,7 +252,7 @@ impl LocationMethods for Location { // Note: no call to self.check_same_origin_domain() // Step 2: Parse the given value relative to the entry settings object. // If that failed, throw a TypeError exception. - let base_url = self.entry_document().url(); + let base_url = self.entry_settings_object().api_base_url(); let url = match base_url.join(&value.0) { Ok(url) => url, Err(e) => return Err(Error::Type(format!("Couldn't parse URL: {}", e))), diff --git a/components/script/dom/url.rs b/components/script/dom/url.rs index b1ceef7cfa3..056f6aa9643 100644 --- a/components/script/dom/url.rs +++ b/components/script/dom/url.rs @@ -155,7 +155,7 @@ impl URL { result.push('/'); // Step 5 - result.push_str(&id.to_simple().to_string()); + result.push_str(&id.to_string()); result } diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index 14f9f80e20a..9aac99dc447 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -52,7 +52,7 @@ use script_traits::{ AuxiliaryBrowsingContextLoadInfo, HistoryEntryReplacement, LoadData, LoadOrigin, }; use script_traits::{NewLayoutInfo, ScriptMsg}; -use servo_url::ServoUrl; +use servo_url::{ImmutableOrigin, ServoUrl}; use std::cell::Cell; use std::ptr; use style::attr::parse_integer; @@ -108,6 +108,15 @@ pub struct WindowProxy { /// https://html.spec.whatwg.org/multipage/#delaying-load-events-mode delaying_load_events_mode: Cell, + + /// The creator browsing context's base url. + creator_base_url: Option, + + /// The creator browsing context's url. + creator_url: Option, + + /// The creator browsing context's origin. + creator_origin: Option, } impl WindowProxy { @@ -118,6 +127,7 @@ impl WindowProxy { frame_element: Option<&Element>, parent: Option<&WindowProxy>, opener: Option, + creator: CreatorBrowsingContextInfo, ) -> WindowProxy { let name = frame_element.map_or(DOMString::new(), |e| { e.get_string_attribute(&local_name!("name")) @@ -135,6 +145,9 @@ impl WindowProxy { parent: parent.map(Dom::from_ref), delaying_load_events_mode: Cell::new(false), opener, + creator_base_url: creator.base_url, + creator_url: creator.url, + creator_origin: creator.origin, } } @@ -146,6 +159,7 @@ impl WindowProxy { frame_element: Option<&Element>, parent: Option<&WindowProxy>, opener: Option, + creator: CreatorBrowsingContextInfo, ) -> DomRoot { unsafe { let WindowProxyHandler(handler) = window.windowproxy_handler(); @@ -173,6 +187,7 @@ impl WindowProxy { frame_element, parent, opener, + creator, )); // The window proxy owns the browsing context. @@ -204,6 +219,7 @@ impl WindowProxy { top_level_browsing_context_id: TopLevelBrowsingContextId, parent: Option<&WindowProxy>, opener: Option, + creator: CreatorBrowsingContextInfo, ) -> DomRoot { unsafe { let handler = CreateWrapperProxyHandler(&XORIGIN_PROXY_HANDLER); @@ -219,6 +235,7 @@ impl WindowProxy { None, parent, opener, + creator, )); // Create a new dissimilar-origin window. @@ -368,6 +385,33 @@ impl WindowProxy { self.is_closing.get() } + /// https://html.spec.whatwg.org/multipage/#creator-base-url + pub fn creator_base_url(&self) -> Option { + self.creator_base_url.clone() + } + + pub fn has_creator_base_url(&self) -> bool { + self.creator_base_url.is_some() + } + + /// https://html.spec.whatwg.org/multipage/#creator-url + pub fn creator_url(&self) -> Option { + self.creator_url.clone() + } + + pub fn has_creator_url(&self) -> bool { + self.creator_base_url.is_some() + } + + /// https://html.spec.whatwg.org/multipage/#creator-origin + pub fn creator_origin(&self) -> Option { + self.creator_origin.clone() + } + + pub fn has_creator_origin(&self) -> bool { + self.creator_origin.is_some() + } + #[allow(unsafe_code)] // https://html.spec.whatwg.org/multipage/#dom-opener pub fn opener(&self, cx: *mut JSContext, in_realm_proof: InRealm) -> JSVal { @@ -378,6 +422,7 @@ impl WindowProxy { Some(opener_browsing_context_id) => opener_browsing_context_id, None => return NullValue(), }; + let parent_browsing_context = self.parent.as_deref(); let opener_proxy = match ScriptThread::find_window_proxy(opener_id) { Some(window_proxy) => window_proxy, None => { @@ -389,12 +434,15 @@ impl WindowProxy { Some(opener_top_id) => { let global_to_clone_from = unsafe { GlobalScope::from_context(cx, in_realm_proof) }; + let creator = + CreatorBrowsingContextInfo::from(parent_browsing_context, None); WindowProxy::new_dissimilar_origin( &*global_to_clone_from, opener_id, opener_top_id, None, None, + creator, ) }, None => return NullValue(), @@ -655,6 +703,54 @@ impl WindowProxy { } } +/// A browsing context can have a creator browsing context, the browsing context that +/// was responsible for its creation. If a browsing context has a parent browsing context, +/// then that is its creator browsing context. Otherwise, if the browsing context has an +/// opener browsing context, then that is its creator browsing context. Otherwise, the +/// browsing context has no creator browsing context. +/// +/// If a browsing context A has a creator browsing context, then the Document that was the +/// active document of that creator browsing context at the time A was created is the creator +/// Document. +/// +/// See: https://html.spec.whatwg.org/multipage/#creating-browsing-contexts +#[derive(Debug, Deserialize, Serialize)] +pub struct CreatorBrowsingContextInfo { + /// Creator document URL. + url: Option, + + /// Creator document base URL. + base_url: Option, + + /// Creator document origin. + origin: Option, +} + +impl CreatorBrowsingContextInfo { + pub fn from( + parent: Option<&WindowProxy>, + opener: Option<&WindowProxy>, + ) -> CreatorBrowsingContextInfo { + let creator = match (parent, opener) { + (Some(parent), _) => parent.document(), + (None, Some(opener)) => opener.document(), + (None, None) => None, + }; + + let base_url = creator.as_deref().map(|document| document.base_url()); + let url = creator.as_deref().map(|document| document.url()); + let origin = creator + .as_deref() + .map(|document| document.origin().immutable().clone()); + + CreatorBrowsingContextInfo { + base_url, + url, + origin, + } + } +} + // https://html.spec.whatwg.org/multipage/#concept-window-open-features-tokenize fn tokenize_open_features(features: DOMString) -> IndexMap { let is_feature_sep = |c: char| c.is_ascii_whitespace() || ['=', ','].contains(&c); diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 6a2c4b09c1e..41dc5406b4a 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -68,7 +68,7 @@ use crate::dom::servoparser::{ParserContext, ServoParser}; use crate::dom::transitionevent::TransitionEvent; use crate::dom::uievent::UIEvent; use crate::dom::window::{ReflowReason, Window}; -use crate::dom::windowproxy::WindowProxy; +use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy}; use crate::dom::worker::TrustedWorkerAddress; use crate::dom::worklet::WorkletThreadPool; use crate::dom::workletglobalscope::WorkletGlobalScopeInit; @@ -3162,7 +3162,7 @@ impl ScriptThread { return Some(DomRoot::from_ref(window_proxy)); } - let parent = parent_pipeline_id.and_then(|parent_id| { + let parent_browsing_context = parent_pipeline_id.and_then(|parent_id| { self.remote_window_proxy( global_to_clone, top_level_browsing_context_id, @@ -3170,12 +3170,21 @@ impl ScriptThread { opener, ) }); + + let opener_browsing_context = opener.and_then(|id| ScriptThread::find_window_proxy(id)); + + let creator = CreatorBrowsingContextInfo::from( + parent_browsing_context.as_deref(), + opener_browsing_context.as_deref(), + ); + let window_proxy = WindowProxy::new_dissimilar_origin( global_to_clone, browsing_context_id, top_level_browsing_context_id, - parent.as_deref(), + parent_browsing_context.as_deref(), opener, + creator, ); self.window_proxies .borrow_mut() @@ -3207,7 +3216,7 @@ impl ScriptThread { .borrow() .find_iframe(parent_id, browsing_context_id) }); - let parent = match (parent_info, iframe.as_ref()) { + let parent_browsing_context = match (parent_info, iframe.as_ref()) { (_, Some(iframe)) => Some(window_from_node(&**iframe).window_proxy()), (Some(parent_id), _) => self.remote_window_proxy( window.upcast(), @@ -3217,13 +3226,22 @@ impl ScriptThread { ), _ => None, }; + + let opener_browsing_context = opener.and_then(|id| ScriptThread::find_window_proxy(id)); + + let creator = CreatorBrowsingContextInfo::from( + parent_browsing_context.as_deref(), + opener_browsing_context.as_deref(), + ); + let window_proxy = WindowProxy::new( &window, browsing_context_id, top_level_browsing_context_id, iframe.as_deref().map(Castable::upcast), - parent.as_deref(), + parent_browsing_context.as_deref(), opener, + creator, ); self.window_proxies .borrow_mut() diff --git a/tests/wpt/metadata/FileAPI/url/sandboxed-iframe.html.ini b/tests/wpt/metadata/FileAPI/url/sandboxed-iframe.html.ini index 3e33fa10885..5119506bbc8 100644 --- a/tests/wpt/metadata/FileAPI/url/sandboxed-iframe.html.ini +++ b/tests/wpt/metadata/FileAPI/url/sandboxed-iframe.html.ini @@ -1,4 +1,43 @@ [sandboxed-iframe.html] expected: TIMEOUT + [Blob URLs can be used in