diff options
author | Gregory Terzian <gterzian@users.noreply.github.com> | 2019-05-12 17:37:19 +0800 |
---|---|---|
committer | Gregory Terzian <gterzian@users.noreply.github.com> | 2019-07-18 12:03:45 +0800 |
commit | 571beec179fe9fd5fff2c12b3c5dfa0a5d93df01 (patch) | |
tree | 2eda42b78fa99fd2cd51d733519d5ae9d8678a66 | |
parent | 973a3448a459464b79ea0ef5fb46141176cc7643 (diff) | |
download | servo-571beec179fe9fd5fff2c12b3c5dfa0a5d93df01.tar.gz servo-571beec179fe9fd5fff2c12b3c5dfa0a5d93df01.zip |
clean-up navigation
security: check target and source origin before executing JS url
implement replacement-enabled flag as a HistoryEntryReplacement enum
add source origin string on loaddata
add LoadOrigin
iframe: remove optional load-data
auxiliaries: add load-data into info
constellation: remove url from Pipeline::new
check load origin: link to whatwg issue
switch loadorigin toplevel to constellation
-rw-r--r-- | components/constellation/constellation.rs | 123 | ||||
-rw-r--r-- | components/constellation/pipeline.rs | 6 | ||||
-rw-r--r-- | components/script/dom/htmlanchorelement.rs | 39 | ||||
-rwxr-xr-x | components/script/dom/htmlformelement.rs | 38 | ||||
-rw-r--r-- | components/script/dom/htmliframeelement.rs | 71 | ||||
-rw-r--r-- | components/script/dom/location.rs | 37 | ||||
-rw-r--r-- | components/script/dom/window.rs | 41 | ||||
-rw-r--r-- | components/script/dom/windowproxy.rs | 41 | ||||
-rw-r--r-- | components/script/script_thread.rs | 146 | ||||
-rw-r--r-- | components/script_traits/lib.rs | 34 | ||||
-rw-r--r-- | components/script_traits/script_msg.rs | 16 | ||||
-rw-r--r-- | components/webdriver_server/lib.rs | 4 | ||||
-rw-r--r-- | tests/wpt/metadata/MANIFEST.json | 2 | ||||
-rw-r--r-- | tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/execution-timing/029.html | 24 |
14 files changed, 402 insertions, 220 deletions
diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index 2c5b2b6c603..85f5455dc88 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -145,11 +145,11 @@ use script_traits::{ use script_traits::{ ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext, }; -use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData}; +use script_traits::{DocumentActivity, DocumentState, LayoutControlMsg, LoadData, LoadOrigin}; +use script_traits::{HistoryEntryReplacement, IFrameSizeMsg, WindowSizeData, WindowSizeType}; use script_traits::{ IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg, }; -use script_traits::{IFrameSizeMsg, WindowSizeData, WindowSizeType}; use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory}; use script_traits::{SWManagerMsg, ScopeThings, UpdatePipelineIdReason, WebDriverCommandMsg}; use serde::{Deserialize, Serialize}; @@ -171,7 +171,7 @@ use style_traits::viewport::ViewportConstraints; use style_traits::CSSPixel; use webvr_traits::{WebVREvent, WebVRMsg}; -type PendingApprovalNavigations = HashMap<PipelineId, (LoadData, bool)>; +type PendingApprovalNavigations = HashMap<PipelineId, (LoadData, HistoryEntryReplacement)>; /// Servo supports tabs (referred to as browsers), so `Constellation` needs to /// store browser specific data for bookkeeping. @@ -1320,7 +1320,7 @@ where // If there is already a pending page (self.pending_changes), it will not be overridden; // However, if the id is not encompassed by another change, it will be. FromCompositorMsg::LoadUrl(top_level_browsing_context_id, url) => { - let load_data = LoadData::new(url, None, None, None); + let load_data = LoadData::new(LoadOrigin::Constellation, url, None, None, None); let ctx_id = BrowsingContextId::from(top_level_browsing_context_id); let pipeline_id = match self.browsing_contexts.get(&ctx_id) { Some(ctx) => ctx.pipeline_id, @@ -1333,7 +1333,12 @@ where }; // Since this is a top-level load, initiated by the embedder, go straight to load_url, // bypassing schedule_navigation. - self.load_url(top_level_browsing_context_id, pipeline_id, load_data, false); + self.load_url( + top_level_browsing_context_id, + pipeline_id, + load_data, + HistoryEntryReplacement::Disabled, + ); }, FromCompositorMsg::IsReadyToSaveImage(pipeline_states) => { let is_ready = self.handle_is_ready_to_save_image(pipeline_states); @@ -1916,7 +1921,7 @@ where warn!("creating replacement pipeline for about:failure"); let new_pipeline_id = PipelineId::new(); - let load_data = LoadData::new(failure_url, None, None, None); + let load_data = LoadData::new(LoadOrigin::Constellation, failure_url, None, None, None); let sandbox = IFrameSandboxState::IFrameSandboxed; let is_private = false; self.new_pipeline( @@ -2035,7 +2040,7 @@ where ); self.embedder_proxy.send(msg); let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); - let load_data = LoadData::new(url, None, None, None); + let load_data = LoadData::new(LoadOrigin::Constellation, url, None, None, None); let sandbox = IFrameSandboxState::IFrameUnsandboxed; let is_private = false; let is_visible = true; @@ -2196,7 +2201,9 @@ where // see https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded debug!("checking old pipeline? {:?}", load_info.old_pipeline_id); if let Some(old_pipeline) = old_pipeline { - replace |= !old_pipeline.completely_loaded; + if !old_pipeline.completely_loaded { + replace = HistoryEntryReplacement::Enabled; + } debug!( "old pipeline is {}completely loaded", if old_pipeline.completely_loaded { @@ -2207,16 +2214,6 @@ where ); } - let load_data = load_info.load_data.unwrap_or_else(|| { - let url = match old_pipeline { - Some(old_pipeline) => old_pipeline.url.clone(), - None => ServoUrl::parse("about:blank").expect("infallible"), - }; - - // TODO - loaddata here should have referrer info (not None, None) - LoadData::new(url, Some(parent_pipeline_id), None, None) - }); - let is_parent_private = { let parent_browsing_context_id = match self.pipelines.get(&parent_pipeline_id) { Some(pipeline) => pipeline.browsing_context_id, @@ -2250,10 +2247,11 @@ where }, }; - let replace = if replace { - Some(NeedsToReload::No(browsing_context.pipeline_id)) - } else { - None + let replace = match replace { + HistoryEntryReplacement::Enabled => { + Some(NeedsToReload::No(browsing_context.pipeline_id)) + }, + HistoryEntryReplacement::Disabled => None, }; // https://github.com/rust-lang/rust/issues/59159 @@ -2268,7 +2266,7 @@ where Some(parent_pipeline_id), None, browsing_context_size, - load_data, + load_info.load_data, load_info.sandbox, is_private, browsing_context_is_visible, @@ -2285,7 +2283,7 @@ where fn handle_script_new_iframe( &mut self, - load_info: IFrameLoadInfo, + load_info: IFrameLoadInfoWithData, layout_sender: IpcSender<LayoutControlMsg>, ) { let IFrameLoadInfo { @@ -2295,12 +2293,7 @@ where top_level_browsing_context_id, is_private, .. - } = load_info; - - let url = ServoUrl::parse("about:blank").expect("infallible"); - - // TODO: Referrer? - let load_data = LoadData::new(url.clone(), Some(parent_pipeline_id), None, None); + } = load_info.info; let (script_sender, parent_browsing_context_id) = match self.pipelines.get(&parent_pipeline_id) { @@ -2326,9 +2319,8 @@ where script_sender, layout_sender, self.compositor_proxy.clone(), - url, is_parent_visible, - load_data, + load_info.load_data, ); assert!(!self.pipelines.contains_key(&new_pipeline_id)); @@ -2353,17 +2345,13 @@ where layout_sender: IpcSender<LayoutControlMsg>, ) { let AuxiliaryBrowsingContextLoadInfo { + load_data, opener_pipeline_id, new_top_level_browsing_context_id, new_browsing_context_id, new_pipeline_id, } = load_info; - let url = ServoUrl::parse("about:blank").expect("infallible"); - - // TODO: Referrer? - let load_data = LoadData::new(url.clone(), None, None, None); - let (script_sender, opener_browsing_context_id) = match self.pipelines.get(&opener_pipeline_id) { Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id), @@ -2392,7 +2380,6 @@ where script_sender, layout_sender, self.compositor_proxy.clone(), - url, is_opener_visible, load_data, ); @@ -2496,7 +2483,7 @@ where top_level_browsing_context_id: TopLevelBrowsingContextId, source_id: PipelineId, load_data: LoadData, - replace: bool, + replace: HistoryEntryReplacement, ) { match self.pending_approval_navigations.entry(source_id) { Entry::Occupied(_) => { @@ -2522,13 +2509,15 @@ where top_level_browsing_context_id: TopLevelBrowsingContextId, source_id: PipelineId, load_data: LoadData, - replace: bool, + replace: HistoryEntryReplacement, ) -> Option<PipelineId> { + let replace_debug = match replace { + HistoryEntryReplacement::Enabled => "", + HistoryEntryReplacement::Disabled => "not", + }; debug!( "Loading {} in pipeline {}, {}replacing.", - load_data.url, - source_id, - if replace { "" } else { "not " } + load_data.url, source_id, replace_debug ); // If this load targets an iframe, its framing element may exist // in a separate script thread than the framed document that initiated @@ -2569,7 +2558,7 @@ where Some(parent_pipeline_id) => { // Find the script thread for the pipeline containing the iframe // and issue an iframe load through there. - let msg = ConstellationControlMsg::Navigate( + let msg = ConstellationControlMsg::NavigateIframe( parent_pipeline_id, browsing_context_id, load_data, @@ -2612,10 +2601,9 @@ where // Create the new pipeline - let replace = if replace { - Some(NeedsToReload::No(pipeline_id)) - } else { - None + let replace = match replace { + HistoryEntryReplacement::Enabled => Some(NeedsToReload::No(pipeline_id)), + HistoryEntryReplacement::Disabled => None, }; let new_pipeline_id = PipelineId::new(); @@ -2730,7 +2718,7 @@ where &mut self, pipeline_id: PipelineId, new_url: ServoUrl, - replacement_enabled: bool, + replacement_enabled: HistoryEntryReplacement, ) { let (top_level_browsing_context_id, old_url) = match self.pipelines.get_mut(&pipeline_id) { Some(pipeline) => { @@ -2745,15 +2733,18 @@ where }, }; - if !replacement_enabled { - let diff = SessionHistoryDiff::HashDiff { - pipeline_reloader: NeedsToReload::No(pipeline_id), - new_url, - old_url, - }; - self.get_joint_session_history(top_level_browsing_context_id) - .push_diff(diff); - self.notify_history_changed(top_level_browsing_context_id); + match replacement_enabled { + HistoryEntryReplacement::Disabled => { + let diff = SessionHistoryDiff::HashDiff { + pipeline_reloader: NeedsToReload::No(pipeline_id), + new_url, + old_url, + }; + self.get_joint_session_history(top_level_browsing_context_id) + .push_diff(diff); + self.notify_history_changed(top_level_browsing_context_id); + }, + HistoryEntryReplacement::Enabled => {}, } } @@ -3395,7 +3386,12 @@ where )); }, WebDriverCommandMsg::LoadUrl(top_level_browsing_context_id, load_data, reply) => { - self.load_url_for_webdriver(top_level_browsing_context_id, load_data, reply, false); + self.load_url_for_webdriver( + top_level_browsing_context_id, + load_data, + reply, + HistoryEntryReplacement::Disabled, + ); }, WebDriverCommandMsg::Refresh(top_level_browsing_context_id, reply) => { let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); @@ -3412,7 +3408,12 @@ where Some(pipeline) => pipeline.load_data.clone(), None => return warn!("Pipeline {} refresh after closure.", pipeline_id), }; - self.load_url_for_webdriver(top_level_browsing_context_id, load_data, reply, true); + self.load_url_for_webdriver( + top_level_browsing_context_id, + load_data, + reply, + HistoryEntryReplacement::Enabled, + ); }, WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => { let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { @@ -3595,7 +3596,7 @@ where top_level_browsing_context_id: TopLevelBrowsingContextId, load_data: LoadData, reply: IpcSender<webdriver_msg::LoadStatus>, - replace: bool, + replace: HistoryEntryReplacement, ) { let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { diff --git a/components/constellation/pipeline.rs b/components/constellation/pipeline.rs index 8c770a1c274..a5757664497 100644 --- a/components/constellation/pipeline.rs +++ b/components/constellation/pipeline.rs @@ -215,8 +215,6 @@ impl Pipeline { device_pixel_ratio: state.device_pixel_ratio, }; - let url = state.load_data.url.clone(); - let (script_chan, sampler_chan) = match state.event_loop { Some(script_chan) => { let new_layout_info = NewLayoutInfo { @@ -336,7 +334,6 @@ impl Pipeline { script_chan, pipeline_chan, state.compositor_proxy, - url, state.prev_visibility, state.load_data, ); @@ -356,7 +353,6 @@ impl Pipeline { event_loop: Rc<EventLoop>, layout_chan: IpcSender<LayoutControlMsg>, compositor_proxy: CompositorProxy, - url: ServoUrl, is_visible: bool, load_data: LoadData, ) -> Pipeline { @@ -368,7 +364,7 @@ impl Pipeline { event_loop: event_loop, layout_chan: layout_chan, compositor_proxy: compositor_proxy, - url: url, + url: load_data.url.clone(), children: vec![], running_animations: false, load_data: load_data, diff --git a/components/script/dom/htmlanchorelement.rs b/components/script/dom/htmlanchorelement.rs index 9f64a93c0ca..cb197a054f3 100644 --- a/components/script/dom/htmlanchorelement.rs +++ b/components/script/dom/htmlanchorelement.rs @@ -11,6 +11,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAncho use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::document::determine_policy_for_token; @@ -19,16 +20,19 @@ use crate::dom::domtokenlist::DOMTokenList; use crate::dom::element::Element; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlimageelement::HTMLImageElement; use crate::dom::mouseevent::MouseEvent; use crate::dom::node::{document_from_node, Node}; use crate::dom::urlhelper::UrlHelper; use crate::dom::virtualmethods::VirtualMethods; +use crate::task_source::TaskSource; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; use net_traits::request::Referrer; use num_traits::ToPrimitive; +use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin}; use servo_url::ServoUrl; use std::default::Default; use style::attr::AttrValue; @@ -624,8 +628,19 @@ pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>) { // Step 7. let (maybe_chosen, replace) = match target_attribute_value { - Some(name) => source.choose_browsing_context(name.Value(), noopener), - None => (Some(window.window_proxy()), false), + Some(name) => { + let (maybe_chosen, new) = source.choose_browsing_context(name.Value(), noopener); + let replace = if new { + HistoryEntryReplacement::Enabled + } else { + HistoryEntryReplacement::Disabled + }; + (maybe_chosen, replace) + }, + None => ( + Some(window.window_proxy()), + HistoryEntryReplacement::Disabled, + ), }; // Step 8. @@ -667,7 +682,23 @@ pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>) { }; // Step 14 - debug!("following hyperlink to {}", url); - target_window.load_url(url, replace, false, referrer, referrer_policy); + let pipeline_id = target_window.upcast::<GlobalScope>().pipeline_id(); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + Some(pipeline_id), + Some(referrer), + referrer_policy, + ); + let target = Trusted::new(target_window); + let task = task!(navigate_follow_hyperlink: move || { + debug!("following hyperlink to {}", load_data.url); + target.root().load_url(replace, false, load_data); + }); + target_window + .task_manager() + .dom_manipulation_task_source() + .queue(task, target_window.upcast()) + .unwrap(); }; } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 9e988515ac0..f12b1398e61 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; @@ -12,6 +13,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding; use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomObject; @@ -46,7 +48,6 @@ use crate::dom::node::{UnbindContext, VecPreOrderInsertionHelper}; use crate::dom::validitystate::ValidationFlags; use crate::dom::virtualmethods::VirtualMethods; use crate::dom::window::Window; -use crate::script_thread::MainThreadScriptMsg; use crate::task_source::TaskSource; use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; @@ -57,7 +58,7 @@ use hyper::Method; use mime::{self, Mime}; use net_traits::http_percent_encode; use net_traits::request::Referrer; -use script_traits::LoadData; +use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin}; use servo_rand::random; use std::borrow::ToOwned; use std::cell::Cell; @@ -400,6 +401,7 @@ impl HTMLFormElement { }; let target_window = target_document.window(); let mut load_data = LoadData::new( + LoadOrigin::Script(doc.origin().immutable().clone()), action_components, None, Some(Referrer::ReferrerUrl(target_document.url())), @@ -530,7 +532,7 @@ impl HTMLFormElement { } /// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) - fn plan_to_navigate(&self, load_data: LoadData, target: &Window) { + fn plan_to_navigate(&self, mut load_data: LoadData, target: &Window) { // Step 1 // Each planned navigation task is tagged with a generation ID, and // before the task is handled, it first checks whether the HTMLFormElement's @@ -538,19 +540,35 @@ impl HTMLFormElement { let generation_id = GenerationId(self.generation_id.get().0 + 1); self.generation_id.set(generation_id); - // Step 2. + // Step 2 + let elem = self.upcast::<Element>(); + let referrer = match elem.get_attribute(&ns!(), &local_name!("rel")) { + Some(ref link_types) if link_types.Value().contains("noreferrer") => { + Referrer::NoReferrer + }, + _ => Referrer::Client, + }; + + let referrer_policy = target.Document().get_referrer_policy(); let pipeline_id = target.upcast::<GlobalScope>().pipeline_id(); - let script_chan = target.main_thread_script_chan().clone(); + load_data.creator_pipeline_id = Some(pipeline_id); + load_data.referrer = Some(referrer); + load_data.referrer_policy = referrer_policy; + + // Step 4. let this = Trusted::new(self); + let window = Trusted::new(target); let task = task!(navigate_to_form_planned_navigation: move || { if generation_id != this.root().generation_id.get() { return; } - script_chan.send(MainThreadScriptMsg::Navigate( - pipeline_id, - load_data, - false, - )).unwrap(); + window + .root() + .load_url( + HistoryEntryReplacement::Disabled, + false, + load_data, + ); }); // Step 3. diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index a85b9bfa915..58ddd48c8c9 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -37,8 +37,8 @@ use profile_traits::ipc as ProfiledIpc; use script_layout_interface::message::ReflowGoal; use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed}; use script_traits::{ - IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, UpdatePipelineIdReason, - WindowSizeData, + HistoryEntryReplacement, IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, + LoadOrigin, UpdatePipelineIdReason, WindowSizeData, }; use script_traits::{NewLayoutInfo, ScriptMsg}; use servo_url::ServoUrl; @@ -109,9 +109,9 @@ impl HTMLIFrameElement { pub fn navigate_or_reload_child_browsing_context( &self, - mut load_data: Option<LoadData>, + mut load_data: LoadData, nav_type: NavigationType, - replace: bool, + replace: HistoryEntryReplacement, ) { let sandboxed = if self.is_sandboxed() { IFrameSandboxed @@ -136,30 +136,27 @@ impl HTMLIFrameElement { // document; the new navigation will continue blocking it. LoadBlocker::terminate(&mut load_blocker); - if let Some(ref mut load_data) = load_data { - let is_javascript = load_data.url.scheme() == "javascript"; - if is_javascript { - let window_proxy = self.GetContentWindow(); - if let Some(window_proxy) = window_proxy { - ScriptThread::eval_js_url(&window_proxy.global(), load_data); + if load_data.url.scheme() == "javascript" { + let window_proxy = self.GetContentWindow(); + if let Some(window_proxy) = window_proxy { + // Important re security. See https://github.com/servo/servo/issues/23373 + // TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request + if ScriptThread::check_load_origin(&load_data.load_origin, &document.url().origin()) + { + ScriptThread::eval_js_url(&window_proxy.global(), &mut load_data); } } } - //TODO(#9592): Deal with the case where an iframe is being reloaded so url is None. - // The iframe should always have access to the nested context's active - // document URL through the browsing context. - if let Some(ref load_data) = load_data { - match load_data.js_eval_result { - Some(JsEvalResult::NoContent) => (), - _ => { - *load_blocker = Some(LoadBlocker::new( - &*document, - LoadType::Subframe(load_data.url.clone()), - )); - }, - }; - } + match load_data.js_eval_result { + Some(JsEvalResult::NoContent) => (), + _ => { + *load_blocker = Some(LoadBlocker::new( + &*document, + LoadType::Subframe(load_data.url.clone()), + )); + }, + }; let window = window_from_node(self); let old_pipeline_id = self.pipeline_id(); @@ -182,6 +179,12 @@ impl HTMLIFrameElement { self.about_blank_pipeline_id.set(Some(new_pipeline_id)); + let load_info = IFrameLoadInfoWithData { + info: load_info, + load_data: load_data.clone(), + old_pipeline_id: old_pipeline_id, + sandbox: sandboxed, + }; global_scope .script_to_constellation_chan() .send(ScriptMsg::ScriptNewIFrame(load_info, pipeline_sender)) @@ -193,7 +196,7 @@ impl HTMLIFrameElement { browsing_context_id: browsing_context_id, top_level_browsing_context_id: top_level_browsing_context_id, opener: None, - load_data: load_data.unwrap(), + load_data: load_data, pipeline_port: pipeline_receiver, content_process_shutdown_chan: None, window_size: WindowSizeData { @@ -270,6 +273,7 @@ impl HTMLIFrameElement { let document = document_from_node(self); let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), url, creator_pipeline_id, Some(Referrer::ReferrerUrl(document.url())), @@ -281,12 +285,12 @@ impl HTMLIFrameElement { // see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3 let is_about_blank = pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get(); - let replace = is_about_blank; - self.navigate_or_reload_child_browsing_context( - Some(load_data), - NavigationType::Regular, - replace, - ); + let replace = if is_about_blank { + HistoryEntryReplacement::Enabled + } else { + HistoryEntryReplacement::Disabled + }; + self.navigate_or_reload_child_browsing_context(load_data, NavigationType::Regular, replace); } fn create_nested_browsing_context(&self) { @@ -296,6 +300,7 @@ impl HTMLIFrameElement { let window = window_from_node(self); let pipeline_id = Some(window.upcast::<GlobalScope>().pipeline_id()); let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), url, pipeline_id, Some(Referrer::ReferrerUrl(document.url().clone())), @@ -309,9 +314,9 @@ impl HTMLIFrameElement { .set(Some(top_level_browsing_context_id)); self.browsing_context_id.set(Some(browsing_context_id)); self.navigate_or_reload_child_browsing_context( - Some(load_data), + load_data, NavigationType::InitialAboutBlank, - false, + HistoryEntryReplacement::Disabled, ); } diff --git a/components/script/dom/location.rs b/components/script/dom/location.rs index d6df671f451..65d39a15755 100644 --- a/components/script/dom/location.rs +++ b/components/script/dom/location.rs @@ -6,6 +6,7 @@ use crate::dom::bindings::codegen::Bindings::LocationBinding; use crate::dom::bindings::codegen::Bindings::LocationBinding::LocationMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +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::{DOMString, USVString}; @@ -14,6 +15,7 @@ use crate::dom::urlhelper::UrlHelper; use crate::dom::window::Window; use dom_struct::dom_struct; use net_traits::request::Referrer; +use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin}; use servo_url::{MutableOrigin, ServoUrl}; #[dom_struct] @@ -38,6 +40,29 @@ impl Location { ) } + /// https://html.spec.whatwg.org/multipage/#location-object-navigate + fn navigate( + &self, + url: ServoUrl, + referrer: Referrer, + replacement_flag: HistoryEntryReplacement, + reload_triggered: bool, + ) { + let document = self.window.Document(); + let referrer_policy = document.get_referrer_policy(); + let pipeline_id = self.window.upcast::<GlobalScope>().pipeline_id(); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + Some(pipeline_id), + Some(referrer), + referrer_policy, + ); + // TODO: rethrow exceptions, set exceptions enabled flag. + self.window + .load_url(replacement_flag, reload_triggered, load_data); + } + fn get_url(&self) -> ServoUrl { self.window.get_url() } @@ -46,7 +71,7 @@ impl Location { let mut url = self.window.get_url(); let referrer = Referrer::ReferrerUrl(url.clone()); setter(&mut url, value); - self.window.load_url(url, false, false, referrer, None); + self.navigate(url, referrer, HistoryEntryReplacement::Disabled, false); } fn check_same_origin_domain(&self) -> ErrorResult { @@ -66,7 +91,7 @@ impl Location { pub fn reload_without_origin_check(&self) { let url = self.get_url(); let referrer = Referrer::ReferrerUrl(url.clone()); - self.window.load_url(url, true, true, referrer, None); + self.navigate(url, referrer, HistoryEntryReplacement::Enabled, true); } #[allow(dead_code)] @@ -84,7 +109,7 @@ impl LocationMethods for Location { let base_url = self.window.get_url(); if let Ok(url) = base_url.join(&url.0) { let referrer = Referrer::ReferrerUrl(base_url.clone()); - self.window.load_url(url, false, false, referrer, None); + self.navigate(url, referrer, HistoryEntryReplacement::Disabled, false); Ok(()) } else { Err(Error::Syntax) @@ -96,7 +121,7 @@ impl LocationMethods for Location { self.check_same_origin_domain()?; let url = self.get_url(); let referrer = Referrer::ReferrerUrl(url.clone()); - self.window.load_url(url, true, true, referrer, None); + self.navigate(url, referrer, HistoryEntryReplacement::Enabled, true); Ok(()) } @@ -108,7 +133,7 @@ impl LocationMethods for Location { let base_url = self.window.get_url(); if let Ok(url) = base_url.join(&url.0) { let referrer = Referrer::ReferrerUrl(base_url.clone()); - self.window.load_url(url, true, false, referrer, None); + self.navigate(url, referrer, HistoryEntryReplacement::Enabled, false); Ok(()) } else { Err(Error::Syntax) @@ -178,7 +203,7 @@ impl LocationMethods for Location { Err(e) => return Err(Error::Type(format!("Couldn't parse URL: {}", e))), }; let referrer = Referrer::ReferrerUrl(current_url.clone()); - self.window.load_url(url, false, false, referrer, None); + self.navigate(url, referrer, HistoryEntryReplacement::Disabled, false); Ok(()) } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 881c126da1f..3d16bf053a2 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -88,9 +88,8 @@ use js::rust::HandleValue; use msg::constellation_msg::PipelineId; use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse}; use net_traits::image_cache::{PendingImageId, PendingImageResponse}; -use net_traits::request::Referrer; use net_traits::storage_thread::StorageType; -use net_traits::{ReferrerPolicy, ResourceThreads}; +use net_traits::ResourceThreads; use num_traits::ToPrimitive; use profile_traits::ipc as ProfiledIpc; use profile_traits::mem::ProfilerChan as MemProfilerChan; @@ -102,7 +101,7 @@ use script_layout_interface::rpc::{ }; use script_layout_interface::{PendingImageState, TrustedNodeAddress}; use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; -use script_traits::{ConstellationControlMsg, DocumentState, LoadData}; +use script_traits::{ConstellationControlMsg, DocumentState, HistoryEntryReplacement, LoadData}; use script_traits::{ScriptMsg, ScriptToConstellationChan, ScrollState, TimerEvent, TimerEventId}; use script_traits::{TimerSchedulerMsg, WindowSizeData, WindowSizeType}; use selectors::attr::CaseSensitivity; @@ -1729,27 +1728,31 @@ impl Window { } /// Commence a new URL load which will either replace this window or scroll to a fragment. + /// + /// https://html.spec.whatwg.org/multipage/#navigating-across-documents pub fn load_url( &self, - url: ServoUrl, - replace: bool, + replace: HistoryEntryReplacement, force_reload: bool, - referrer: Referrer, - referrer_policy: Option<ReferrerPolicy>, + load_data: LoadData, ) { let doc = self.Document(); - let referrer_policy = referrer_policy.or(doc.get_referrer_policy()); - // https://html.spec.whatwg.org/multipage/#navigating-across-documents + // TODO: Important re security. See https://github.com/servo/servo/issues/23373 + // Step 3: check that the source browsing-context is "allowed to navigate" this window. if !force_reload && - url.as_url()[..Position::AfterQuery] == doc.url().as_url()[..Position::AfterQuery] + load_data.url.as_url()[..Position::AfterQuery] == + doc.url().as_url()[..Position::AfterQuery] { // Step 6 - if let Some(fragment) = url.fragment() { - self.send_to_constellation(ScriptMsg::NavigatedToFragment(url.clone(), replace)); + if let Some(fragment) = load_data.url.fragment() { + self.send_to_constellation(ScriptMsg::NavigatedToFragment( + load_data.url.clone(), + replace, + )); doc.check_and_scroll_fragment(fragment); let this = Trusted::new(self); let old_url = doc.url().into_string(); - let new_url = url.clone().into_string(); + let new_url = load_data.url.clone().into_string(); let task = task!(hashchange_event: move || { let this = this.root(); let event = HashChangeEvent::new( @@ -1772,7 +1775,7 @@ impl Window { self.pipeline_id(), TaskSourceName::DOMManipulation, )); - doc.set_url(url.clone()); + doc.set_url(load_data.url.clone()); return; } } @@ -1797,13 +1800,9 @@ impl Window { // then put it in the delaying load events mode. self.window_proxy().start_delaying_load_events_mode(); } - self.main_thread_script_chan() - .send(MainThreadScriptMsg::Navigate( - pipeline_id, - LoadData::new(url, Some(pipeline_id), Some(referrer), referrer_policy), - replace, - )) - .unwrap(); + // TODO: step 11, navigationType. + // Step 12, 13 + ScriptThread::navigate(pipeline_id, load_data, replace); }; } diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index 7b53656e918..6e042a16f4b 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -46,7 +46,10 @@ use msg::constellation_msg::BrowsingContextId; use msg::constellation_msg::PipelineId; use msg::constellation_msg::TopLevelBrowsingContextId; use net_traits::request::Referrer; -use script_traits::{AuxiliaryBrowsingContextLoadInfo, LoadData, NewLayoutInfo, ScriptMsg}; +use script_traits::{ + AuxiliaryBrowsingContextLoadInfo, HistoryEntryReplacement, LoadData, LoadOrigin, +}; +use script_traits::{NewLayoutInfo, ScriptMsg}; use servo_url::ServoUrl; use std::cell::Cell; use std::ptr; @@ -267,24 +270,28 @@ impl WindowProxy { let new_browsing_context_id = BrowsingContextId::from(new_top_level_browsing_context_id); let new_pipeline_id = PipelineId::new(); - let load_info = AuxiliaryBrowsingContextLoadInfo { - opener_pipeline_id: self.currently_active.get().unwrap(), - new_browsing_context_id: new_browsing_context_id, - new_top_level_browsing_context_id: new_top_level_browsing_context_id, - new_pipeline_id: new_pipeline_id, - }; let document = self .currently_active .get() .and_then(|id| ScriptThread::find_document(id)) - .unwrap(); + .expect("A WindowProxy creating an auxiliary to have an active document"); + let blank_url = ServoUrl::parse("about:blank").ok().unwrap(); let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), blank_url, None, Some(Referrer::ReferrerUrl(document.url().clone())), document.get_referrer_policy(), ); + let load_info = AuxiliaryBrowsingContextLoadInfo { + load_data: load_data.clone(), + opener_pipeline_id: self.currently_active.get().unwrap(), + new_browsing_context_id: new_browsing_context_id, + new_top_level_browsing_context_id: new_top_level_browsing_context_id, + new_pipeline_id: new_pipeline_id, + }; + let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); let new_layout_info = NewLayoutInfo { parent_info: None, @@ -436,13 +443,21 @@ impl WindowProxy { Referrer::Client }; // Step 14.5 - target_window.load_url( + let referrer_policy = target_document.get_referrer_policy(); + let pipeline_id = target_window.upcast::<GlobalScope>().pipeline_id(); + let load_data = LoadData::new( + LoadOrigin::Script(existing_document.origin().immutable().clone()), url, - new, - false, - referrer, - target_document.get_referrer_policy(), + Some(pipeline_id), + Some(referrer), + referrer_policy, ); + let replacement_flag = if new { + HistoryEntryReplacement::Enabled + } else { + HistoryEntryReplacement::Disabled + }; + target_window.load_url(replacement_flag, false, load_data); } if noopener { // Step 15 (Dis-owning has been done in create_auxiliary_browsing_context). diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index d0dad1cd9ba..2265bc8a379 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -32,6 +32,7 @@ use crate::dom::bindings::conversions::{ }; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::ThreadLocalStackRoots; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom, RootCollection}; @@ -82,6 +83,7 @@ use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; use crate::task_source::remote_event::RemoteEventTaskSource; use crate::task_source::user_interaction::UserInteractionTaskSource; use crate::task_source::websocket::WebsocketTaskSource; +use crate::task_source::TaskSource; use crate::task_source::TaskSourceName; use crate::webdriver_handlers; use bluetooth_traits::BluetoothRequest; @@ -128,8 +130,10 @@ use script_traits::CompositorEvent::{ WheelEvent, }; use script_traits::{CompositorEvent, ConstellationControlMsg}; -use script_traits::{DiscardBrowsingContext, DocumentActivity, EventResult}; -use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData}; +use script_traits::{ + DiscardBrowsingContext, DocumentActivity, EventResult, HistoryEntryReplacement, +}; +use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData, LoadOrigin}; use script_traits::{MouseButton, MouseEventType, NewLayoutInfo}; use script_traits::{Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory}; use script_traits::{ScriptToConstellationChan, TimerEvent, TimerSchedulerMsg}; @@ -263,10 +267,6 @@ enum MixedMessage { pub enum MainThreadScriptMsg { /// Common variants associated with the script messages Common(CommonScriptMsg), - /// Begins a content-initiated load on the specified pipeline (only - /// dispatched to ScriptThread). Allows for a replace bool to be passed. If true, - /// the current entry will be replaced instead of a new entry being added. - Navigate(PipelineId, LoadData, bool), /// Notifies the script thread that a new worklet has been loaded, and thus the page should be /// reflowed. WorkletLoaded(PipelineId), @@ -855,6 +855,74 @@ impl ScriptThread { }); } + /// Check that two origins are "similar enough", + /// for now only used to prevent cross-origin JS url evaluation. + /// + /// https://github.com/whatwg/html/issues/2591 + pub fn check_load_origin(source: &LoadOrigin, target: &ImmutableOrigin) -> bool { + match (source, target) { + (LoadOrigin::Constellation, _) | (LoadOrigin::WebDriver, _) => { + // Always allow loads initiated by the constellation or webdriver. + true + }, + (_, ImmutableOrigin::Opaque(_)) => { + // If the target is opaque, allow. + // This covers newly created about:blank auxiliaries, and iframe with no src. + // TODO: https://github.com/servo/servo/issues/22879 + true + }, + (LoadOrigin::Script(source_origin), _) => source_origin == target, + } + } + + /// Step 13 of https://html.spec.whatwg.org/multipage/#navigate + pub fn navigate( + pipeline_id: PipelineId, + mut load_data: LoadData, + replace: HistoryEntryReplacement, + ) { + SCRIPT_THREAD_ROOT.with(|root| { + let script_thread = match root.get() { + None => return, + Some(script) => script, + }; + let script_thread = unsafe { &*script_thread }; + let is_javascript = load_data.url.scheme() == "javascript"; + // If resource is a request whose url's scheme is "javascript" + // https://html.spec.whatwg.org/multipage/#javascript-protocol + if is_javascript { + let window = match script_thread.documents.borrow().find_window(pipeline_id) { + None => return, + Some(window) => window, + }; + let global = window.upcast::<GlobalScope>(); + let trusted_global = Trusted::new(global); + let sender = script_thread.script_sender.clone(); + let task = task!(navigate_javascript: move || { + // Important re security. See https://github.com/servo/servo/issues/23373 + // TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request + if let Some(window) = trusted_global.root().downcast::<Window>() { + if ScriptThread::check_load_origin(&load_data.load_origin, &window.get_url().origin()) { + ScriptThread::eval_js_url(&trusted_global.root(), &mut load_data); + sender + .send((pipeline_id, ScriptMsg::LoadUrl(load_data, replace))) + .unwrap(); + } + } + }); + global + .dom_manipulation_task_source() + .queue(task, global.upcast()) + .expect("Enqueing navigate js task on the DOM manipulation task source failed"); + } else { + script_thread + .script_sender + .send((pipeline_id, ScriptMsg::LoadUrl(load_data, replace))) + .expect("Sending a LoadUrl message to the constellation failed"); + } + }); + } + pub fn process_attach_layout(new_layout_info: NewLayoutInfo, origin: MutableOrigin) { SCRIPT_THREAD_ROOT.with(|root| { if let Some(script_thread) = root.get() { @@ -1474,7 +1542,7 @@ impl ScriptThread { SetDocumentActivity(id, ..) => Some(id), ChangeFrameVisibilityStatus(id, ..) => Some(id), NotifyVisibilityChange(id, ..) => Some(id), - Navigate(id, ..) => Some(id), + NavigateIframe(id, ..) => Some(id), PostMessage { target: id, .. } => Some(id), UpdatePipelineId(_, _, _, id, _) => Some(id), UpdateHistoryState(id, ..) => Some(id), @@ -1504,7 +1572,6 @@ impl ScriptThread { pipeline_id }, MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None, - MainThreadScriptMsg::Navigate(pipeline_id, ..) => Some(pipeline_id), MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(pipeline_id), MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(pipeline_id), MainThreadScriptMsg::DispatchJobQueue { .. } => None, @@ -1614,14 +1681,14 @@ impl ScriptThread { _ => unreachable!(), }; }, - ConstellationControlMsg::Navigate( + ConstellationControlMsg::NavigateIframe( parent_pipeline_id, browsing_context_id, load_data, replace, - ) => self.handle_navigate( + ) => self.handle_navigate_iframe( parent_pipeline_id, - Some(browsing_context_id), + browsing_context_id, load_data, replace, ), @@ -1736,9 +1803,6 @@ impl ScriptThread { fn handle_msg_from_script(&self, msg: MainThreadScriptMsg) { match msg { - MainThreadScriptMsg::Navigate(parent_pipeline_id, load_data, replace) => { - self.handle_navigate(parent_pipeline_id, None, load_data, replace) - }, MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, task, _, _)) => task.run_box(), MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(chan)) => { self.collect_reports(chan) @@ -3248,50 +3312,30 @@ impl ScriptThread { document.handle_wheel_event(self.js_runtime.rt(), wheel_delta, point, node_address); } - /// <https://html.spec.whatwg.org/multipage/#navigating-across-documents> - /// The entry point for content to notify that a new load has been requested - /// for the given pipeline (specifically the "navigate" algorithm). - fn handle_navigate( + /// Handle a "navigate an iframe" message from the constellation. + fn handle_navigate_iframe( &self, parent_pipeline_id: PipelineId, - browsing_context_id: Option<BrowsingContextId>, - mut load_data: LoadData, - replace: bool, + browsing_context_id: BrowsingContextId, + load_data: LoadData, + replace: HistoryEntryReplacement, ) { - let is_javascript = load_data.url.scheme() == "javascript"; - if is_javascript { - let window = self.documents.borrow().find_window(parent_pipeline_id); - if let Some(window) = window { - ScriptThread::eval_js_url(window.upcast::<GlobalScope>(), &mut load_data); - } - } - - match browsing_context_id { - Some(browsing_context_id) => { - let iframe = self - .documents - .borrow() - .find_iframe(parent_pipeline_id, browsing_context_id); - if let Some(iframe) = iframe { - iframe.navigate_or_reload_child_browsing_context( - Some(load_data), - NavigationType::Regular, - replace, - ); - } - }, - None => { - self.script_sender - .send((parent_pipeline_id, ScriptMsg::LoadUrl(load_data, replace))) - .unwrap(); - }, + let iframe = self + .documents + .borrow() + .find_iframe(parent_pipeline_id, browsing_context_id); + if let Some(iframe) = iframe { + iframe.navigate_or_reload_child_browsing_context( + load_data, + NavigationType::Regular, + replace, + ); } } + /// Turn javascript: URL into JS code to eval, according to the steps in + /// https://html.spec.whatwg.org/multipage/#javascript-protocol pub fn eval_js_url(global_scope: &GlobalScope, load_data: &mut LoadData) { - // Turn javascript: URL into JS code to eval, according to the steps in - // https://html.spec.whatwg.org/multipage/#javascript-protocol - // This slice of the URL’s serialization is equivalent to (5.) to (7.): // Start with the scheme data of the parsed URL; // append question mark and query component, if any; diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index 28a6dbbfad4..4910c97721a 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -62,7 +62,8 @@ use webrender_api::{ use webvr_traits::{WebVREvent, WebVRMsg}; pub use crate::script_msg::{ - DOMMessage, SWManagerMsg, SWManagerSenders, ScopeThings, ServiceWorkerMsg, + DOMMessage, HistoryEntryReplacement, SWManagerMsg, SWManagerSenders, ScopeThings, + ServiceWorkerMsg, }; pub use crate::script_msg::{ EventResult, IFrameSize, IFrameSizeMsg, LayoutMsg, LogEntry, ScriptMsg, @@ -117,10 +118,24 @@ pub enum LayoutControlMsg { PaintMetric(Epoch, u64), } +/// The origin where a given load was initiated. +/// Useful for origin checks, for example before evaluation a JS URL. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum LoadOrigin { + /// A load originating in the constellation. + Constellation, + /// A load originating in webdriver. + WebDriver, + /// A load originating in script. + Script(ImmutableOrigin), +} + /// can be passed to `LoadUrl` to load a page with GET/POST /// parameters or headers #[derive(Clone, Debug, Deserialize, Serialize)] pub struct LoadData { + /// The origin where the load started. + pub load_origin: LoadOrigin, /// The URL. pub url: ServoUrl, /// The creator pipeline id if this is an about:blank load. @@ -160,12 +175,14 @@ pub enum JsEvalResult { impl LoadData { /// Create a new `LoadData` object. pub fn new( + load_origin: LoadOrigin, url: ServoUrl, creator_pipeline_id: Option<PipelineId>, referrer: Option<Referrer>, referrer_policy: Option<ReferrerPolicy>, ) -> LoadData { LoadData { + load_origin, url: url, creator_pipeline_id: creator_pipeline_id, method: Method::GET, @@ -289,7 +306,12 @@ pub enum ConstellationControlMsg { NotifyVisibilityChange(PipelineId, BrowsingContextId, bool), /// Notifies script thread that a url should be loaded in this iframe. /// PipelineId is for the parent, BrowsingContextId is for the nested browsing context - Navigate(PipelineId, BrowsingContextId, LoadData, bool), + NavigateIframe( + PipelineId, + BrowsingContextId, + LoadData, + HistoryEntryReplacement, + ), /// Post a message to a given window. PostMessage { /// The target of the message. @@ -376,7 +398,7 @@ impl fmt::Debug for ConstellationControlMsg { SetDocumentActivity(..) => "SetDocumentActivity", ChangeFrameVisibilityStatus(..) => "ChangeFrameVisibilityStatus", NotifyVisibilityChange(..) => "NotifyVisibilityChange", - Navigate(..) => "Navigate", + NavigateIframe(..) => "NavigateIframe", PostMessage { .. } => "PostMessage", UpdatePipelineId(..) => "UpdatePipelineId", UpdateHistoryState(..) => "UpdateHistoryState", @@ -659,6 +681,8 @@ pub enum IFrameSandboxState { /// Specifies the information required to load an auxiliary browsing context. #[derive(Debug, Deserialize, Serialize)] pub struct AuxiliaryBrowsingContextLoadInfo { + /// Load data containing the url to load + pub load_data: LoadData, /// The pipeline opener browsing context. pub opener_pipeline_id: PipelineId, /// The new top-level ID for the auxiliary. @@ -684,7 +708,7 @@ pub struct IFrameLoadInfo { pub is_private: bool, /// Wether this load should replace the current entry (reload). If true, the current /// entry will be replaced instead of a new entry being added. - pub replace: bool, + pub replace: HistoryEntryReplacement, } /// Specifies the information required to load a URL in an iframe. @@ -693,7 +717,7 @@ pub struct IFrameLoadInfoWithData { /// The information required to load an iframe. pub info: IFrameLoadInfo, /// Load data containing the url to load - pub load_data: Option<LoadData>, + pub load_data: LoadData, /// The old pipeline ID for this iframe, if a page was previously loaded. pub old_pipeline_id: Option<PipelineId>, /// Sandbox type of this iframe diff --git a/components/script_traits/script_msg.rs b/components/script_traits/script_msg.rs index 48862e8378f..fe620247b0d 100644 --- a/components/script_traits/script_msg.rs +++ b/components/script_traits/script_msg.rs @@ -5,7 +5,6 @@ use crate::AnimationState; use crate::AuxiliaryBrowsingContextLoadInfo; use crate::DocumentState; -use crate::IFrameLoadInfo; use crate::IFrameLoadInfoWithData; use crate::LayoutControlMsg; use crate::LoadData; @@ -97,6 +96,15 @@ pub enum LogEntry { Warn(String), } +/// https://html.spec.whatwg.org/multipage/#replacement-enabled +#[derive(Debug, Deserialize, Serialize)] +pub enum HistoryEntryReplacement { + /// Traverse the history with replacement enabled. + Enabled, + /// Traverse the history with replacement disabled. + Disabled, +} + /// Messages from the script to the constellation. #[derive(Deserialize, Serialize)] pub enum ScriptMsg { @@ -145,7 +153,7 @@ pub enum ScriptMsg { LoadComplete, /// A new load has been requested, with an option to replace the current entry once loaded /// instead of adding a new entry. - LoadUrl(LoadData, bool), + LoadUrl(LoadData, HistoryEntryReplacement), /// Abort loading after sending a LoadUrl message. AbortLoadUrl, /// Post a message to the currently active window of a given browsing context. @@ -160,7 +168,7 @@ pub enum ScriptMsg { data: Vec<u8>, }, /// Inform the constellation that a fragment was navigated to and whether or not it was a replacement navigation. - NavigatedToFragment(ServoUrl, bool), + NavigatedToFragment(ServoUrl, HistoryEntryReplacement), /// HTMLIFrameElement Forward or Back traversal. TraverseHistory(TraversalDirection), /// Inform the constellation of a pushed history state. @@ -177,7 +185,7 @@ pub enum ScriptMsg { /// A load has been requested in an IFrame. ScriptLoadedURLInIFrame(IFrameLoadInfoWithData), /// A load of the initial `about:blank` has been completed in an IFrame. - ScriptNewIFrame(IFrameLoadInfo, IpcSender<LayoutControlMsg>), + ScriptNewIFrame(IFrameLoadInfoWithData, IpcSender<LayoutControlMsg>), /// Script has opened a new auxiliary browsing context. ScriptNewAuxiliary( AuxiliaryBrowsingContextLoadInfo, diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index dcce929652b..6b2bcb2264b 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -30,7 +30,7 @@ use script_traits::webdriver_msg::{LoadStatus, WebDriverCookieError, WebDriverFr use script_traits::webdriver_msg::{ WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverScriptCommand, }; -use script_traits::{ConstellationMsg, LoadData, WebDriverCommandMsg}; +use script_traits::{ConstellationMsg, LoadData, LoadOrigin, WebDriverCommandMsg}; use serde::de::{Deserialize, Deserializer, MapAccess, Visitor}; use serde::ser::{Serialize, Serializer}; use serde_json::{json, Value}; @@ -581,7 +581,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); - let load_data = LoadData::new(url, None, None, None); + let load_data = LoadData::new(LoadOrigin::WebDriver, url, None, None, None); let cmd_msg = WebDriverCommandMsg::LoadUrl(top_level_browsing_context_id, load_data, sender.clone()); self.constellation_chan diff --git a/tests/wpt/metadata/MANIFEST.json b/tests/wpt/metadata/MANIFEST.json index e8f9fa35edb..b94cfcac341 100644 --- a/tests/wpt/metadata/MANIFEST.json +++ b/tests/wpt/metadata/MANIFEST.json @@ -624872,7 +624872,7 @@ "testharness" ], "html/semantics/scripting-1/the-script-element/execution-timing/029.html": [ - "c74665ec1e5f37d4e9ec0ecc65f626e79d4942d4", + "33548e566ac67823f9c19af7785a13e394c4964b", "testharness" ], "html/semantics/scripting-1/the-script-element/execution-timing/030.html": [ diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/execution-timing/029.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/execution-timing/029.html index c74665ec1e5..33548e566ac 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/execution-timing/029.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/execution-timing/029.html @@ -10,6 +10,9 @@ <div id="log">FAILED (This TC requires JavaScript enabled)</div> <p><a href="javascript:log('JS URL')"></a></p> <script>log('inline script #1'); + window.addEventListener("beforeunload", function( event ) { + log('beforeunload event'); + }); if(document.links[0].click){ document.links[0].click(); }else{ @@ -25,13 +28,26 @@ log( 'inline script #2' ); var t = async_test() - function test() { + function final_test() { + // The JS URL part is required to run in an additional task, + // altough that is not fully consistently implemented, + // see https://github.com/whatwg/html/issues/3730#issuecomment-492071447 assert_any(assert_array_equals, eventOrder, [ - ['inline script #1', 'end script #1', 'JS URL', 'inline script #2'], - ['inline script #1', 'end script #1', 'inline script #2', 'JS URL']]); + ['inline script #1', 'end script #1', 'beforeunload event', 'inline script #2', 'JS URL'], + ['inline script #1', 'end script #1', 'inline script #2', 'beforeunload event', 'JS URL']]); t.done(); } - onload = t.step_func(test) + + function test_on_load() { + // When the page loads, a task to run the navigate steps + // previously enqueued as part of following-hyperlinks, + // should have run, and have enqueued another task to execute the JS URL. + assert_any(assert_array_equals, eventOrder, [ + ['inline script #1', 'end script #1', 'beforeunload event', 'inline script #2'], + ['inline script #1', 'end script #1', 'inline script #2', 'beforeunload event']]); + t.step_timeout(final_test, 1000) + } + onload = t.step_func(test_on_load); </script> </body></html> |