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
This commit is contained in:
Gregory Terzian 2019-05-12 17:37:19 +08:00
parent 973a3448a4
commit 571beec179
14 changed files with 402 additions and 220 deletions

View file

@ -145,11 +145,11 @@ use script_traits::{
use script_traits::{ use script_traits::{
ConstellationControlMsg, ConstellationMsg as FromCompositorMsg, DiscardBrowsingContext, 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::{ use script_traits::{
IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState, TimerSchedulerMsg,
}; };
use script_traits::{IFrameSizeMsg, WindowSizeData, WindowSizeType};
use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory}; use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, ScriptThreadFactory};
use script_traits::{SWManagerMsg, ScopeThings, UpdatePipelineIdReason, WebDriverCommandMsg}; use script_traits::{SWManagerMsg, ScopeThings, UpdatePipelineIdReason, WebDriverCommandMsg};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -171,7 +171,7 @@ use style_traits::viewport::ViewportConstraints;
use style_traits::CSSPixel; use style_traits::CSSPixel;
use webvr_traits::{WebVREvent, WebVRMsg}; 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 /// Servo supports tabs (referred to as browsers), so `Constellation` needs to
/// store browser specific data for bookkeeping. /// 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; // 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. // However, if the id is not encompassed by another change, it will be.
FromCompositorMsg::LoadUrl(top_level_browsing_context_id, url) => { 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 ctx_id = BrowsingContextId::from(top_level_browsing_context_id);
let pipeline_id = match self.browsing_contexts.get(&ctx_id) { let pipeline_id = match self.browsing_contexts.get(&ctx_id) {
Some(ctx) => ctx.pipeline_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, // Since this is a top-level load, initiated by the embedder, go straight to load_url,
// bypassing schedule_navigation. // 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) => { FromCompositorMsg::IsReadyToSaveImage(pipeline_states) => {
let is_ready = self.handle_is_ready_to_save_image(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"); warn!("creating replacement pipeline for about:failure");
let new_pipeline_id = PipelineId::new(); 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 sandbox = IFrameSandboxState::IFrameSandboxed;
let is_private = false; let is_private = false;
self.new_pipeline( self.new_pipeline(
@ -2035,7 +2040,7 @@ where
); );
self.embedder_proxy.send(msg); self.embedder_proxy.send(msg);
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); 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 sandbox = IFrameSandboxState::IFrameUnsandboxed;
let is_private = false; let is_private = false;
let is_visible = true; let is_visible = true;
@ -2196,7 +2201,9 @@ where
// see https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded // see https://html.spec.whatwg.org/multipage/#the-iframe-element:completely-loaded
debug!("checking old pipeline? {:?}", load_info.old_pipeline_id); debug!("checking old pipeline? {:?}", load_info.old_pipeline_id);
if let Some(old_pipeline) = old_pipeline { if let Some(old_pipeline) = old_pipeline {
replace |= !old_pipeline.completely_loaded; if !old_pipeline.completely_loaded {
replace = HistoryEntryReplacement::Enabled;
}
debug!( debug!(
"old pipeline is {}completely loaded", "old pipeline is {}completely loaded",
if old_pipeline.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 is_parent_private = {
let parent_browsing_context_id = match self.pipelines.get(&parent_pipeline_id) { let parent_browsing_context_id = match self.pipelines.get(&parent_pipeline_id) {
Some(pipeline) => pipeline.browsing_context_id, Some(pipeline) => pipeline.browsing_context_id,
@ -2250,10 +2247,11 @@ where
}, },
}; };
let replace = if replace { let replace = match replace {
Some(NeedsToReload::No(browsing_context.pipeline_id)) HistoryEntryReplacement::Enabled => {
} else { Some(NeedsToReload::No(browsing_context.pipeline_id))
None },
HistoryEntryReplacement::Disabled => None,
}; };
// https://github.com/rust-lang/rust/issues/59159 // https://github.com/rust-lang/rust/issues/59159
@ -2268,7 +2266,7 @@ where
Some(parent_pipeline_id), Some(parent_pipeline_id),
None, None,
browsing_context_size, browsing_context_size,
load_data, load_info.load_data,
load_info.sandbox, load_info.sandbox,
is_private, is_private,
browsing_context_is_visible, browsing_context_is_visible,
@ -2285,7 +2283,7 @@ where
fn handle_script_new_iframe( fn handle_script_new_iframe(
&mut self, &mut self,
load_info: IFrameLoadInfo, load_info: IFrameLoadInfoWithData,
layout_sender: IpcSender<LayoutControlMsg>, layout_sender: IpcSender<LayoutControlMsg>,
) { ) {
let IFrameLoadInfo { let IFrameLoadInfo {
@ -2295,12 +2293,7 @@ where
top_level_browsing_context_id, top_level_browsing_context_id,
is_private, is_private,
.. ..
} = load_info; } = load_info.info;
let url = ServoUrl::parse("about:blank").expect("infallible");
// TODO: Referrer?
let load_data = LoadData::new(url.clone(), Some(parent_pipeline_id), None, None);
let (script_sender, parent_browsing_context_id) = let (script_sender, parent_browsing_context_id) =
match self.pipelines.get(&parent_pipeline_id) { match self.pipelines.get(&parent_pipeline_id) {
@ -2326,9 +2319,8 @@ where
script_sender, script_sender,
layout_sender, layout_sender,
self.compositor_proxy.clone(), self.compositor_proxy.clone(),
url,
is_parent_visible, is_parent_visible,
load_data, load_info.load_data,
); );
assert!(!self.pipelines.contains_key(&new_pipeline_id)); assert!(!self.pipelines.contains_key(&new_pipeline_id));
@ -2353,17 +2345,13 @@ where
layout_sender: IpcSender<LayoutControlMsg>, layout_sender: IpcSender<LayoutControlMsg>,
) { ) {
let AuxiliaryBrowsingContextLoadInfo { let AuxiliaryBrowsingContextLoadInfo {
load_data,
opener_pipeline_id, opener_pipeline_id,
new_top_level_browsing_context_id, new_top_level_browsing_context_id,
new_browsing_context_id, new_browsing_context_id,
new_pipeline_id, new_pipeline_id,
} = load_info; } = 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) = let (script_sender, opener_browsing_context_id) =
match self.pipelines.get(&opener_pipeline_id) { match self.pipelines.get(&opener_pipeline_id) {
Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id), Some(pipeline) => (pipeline.event_loop.clone(), pipeline.browsing_context_id),
@ -2392,7 +2380,6 @@ where
script_sender, script_sender,
layout_sender, layout_sender,
self.compositor_proxy.clone(), self.compositor_proxy.clone(),
url,
is_opener_visible, is_opener_visible,
load_data, load_data,
); );
@ -2496,7 +2483,7 @@ where
top_level_browsing_context_id: TopLevelBrowsingContextId, top_level_browsing_context_id: TopLevelBrowsingContextId,
source_id: PipelineId, source_id: PipelineId,
load_data: LoadData, load_data: LoadData,
replace: bool, replace: HistoryEntryReplacement,
) { ) {
match self.pending_approval_navigations.entry(source_id) { match self.pending_approval_navigations.entry(source_id) {
Entry::Occupied(_) => { Entry::Occupied(_) => {
@ -2522,13 +2509,15 @@ where
top_level_browsing_context_id: TopLevelBrowsingContextId, top_level_browsing_context_id: TopLevelBrowsingContextId,
source_id: PipelineId, source_id: PipelineId,
load_data: LoadData, load_data: LoadData,
replace: bool, replace: HistoryEntryReplacement,
) -> Option<PipelineId> { ) -> Option<PipelineId> {
let replace_debug = match replace {
HistoryEntryReplacement::Enabled => "",
HistoryEntryReplacement::Disabled => "not",
};
debug!( debug!(
"Loading {} in pipeline {}, {}replacing.", "Loading {} in pipeline {}, {}replacing.",
load_data.url, load_data.url, source_id, replace_debug
source_id,
if replace { "" } else { "not " }
); );
// If this load targets an iframe, its framing element may exist // If this load targets an iframe, its framing element may exist
// in a separate script thread than the framed document that initiated // in a separate script thread than the framed document that initiated
@ -2569,7 +2558,7 @@ where
Some(parent_pipeline_id) => { Some(parent_pipeline_id) => {
// Find the script thread for the pipeline containing the iframe // Find the script thread for the pipeline containing the iframe
// and issue an iframe load through there. // and issue an iframe load through there.
let msg = ConstellationControlMsg::Navigate( let msg = ConstellationControlMsg::NavigateIframe(
parent_pipeline_id, parent_pipeline_id,
browsing_context_id, browsing_context_id,
load_data, load_data,
@ -2612,10 +2601,9 @@ where
// Create the new pipeline // Create the new pipeline
let replace = if replace { let replace = match replace {
Some(NeedsToReload::No(pipeline_id)) HistoryEntryReplacement::Enabled => Some(NeedsToReload::No(pipeline_id)),
} else { HistoryEntryReplacement::Disabled => None,
None
}; };
let new_pipeline_id = PipelineId::new(); let new_pipeline_id = PipelineId::new();
@ -2730,7 +2718,7 @@ where
&mut self, &mut self,
pipeline_id: PipelineId, pipeline_id: PipelineId,
new_url: ServoUrl, new_url: ServoUrl,
replacement_enabled: bool, replacement_enabled: HistoryEntryReplacement,
) { ) {
let (top_level_browsing_context_id, old_url) = match self.pipelines.get_mut(&pipeline_id) { let (top_level_browsing_context_id, old_url) = match self.pipelines.get_mut(&pipeline_id) {
Some(pipeline) => { Some(pipeline) => {
@ -2745,15 +2733,18 @@ where
}, },
}; };
if !replacement_enabled { match replacement_enabled {
let diff = SessionHistoryDiff::HashDiff { HistoryEntryReplacement::Disabled => {
pipeline_reloader: NeedsToReload::No(pipeline_id), let diff = SessionHistoryDiff::HashDiff {
new_url, pipeline_reloader: NeedsToReload::No(pipeline_id),
old_url, new_url,
}; old_url,
self.get_joint_session_history(top_level_browsing_context_id) };
.push_diff(diff); self.get_joint_session_history(top_level_browsing_context_id)
self.notify_history_changed(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) => { 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) => { WebDriverCommandMsg::Refresh(top_level_browsing_context_id, reply) => {
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
@ -3412,7 +3408,12 @@ where
Some(pipeline) => pipeline.load_data.clone(), Some(pipeline) => pipeline.load_data.clone(),
None => return warn!("Pipeline {} refresh after closure.", pipeline_id), 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) => { WebDriverCommandMsg::ScriptCommand(browsing_context_id, cmd) => {
let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {
@ -3595,7 +3596,7 @@ where
top_level_browsing_context_id: TopLevelBrowsingContextId, top_level_browsing_context_id: TopLevelBrowsingContextId,
load_data: LoadData, load_data: LoadData,
reply: IpcSender<webdriver_msg::LoadStatus>, reply: IpcSender<webdriver_msg::LoadStatus>,
replace: bool, replace: HistoryEntryReplacement,
) { ) {
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) { let pipeline_id = match self.browsing_contexts.get(&browsing_context_id) {

View file

@ -215,8 +215,6 @@ impl Pipeline {
device_pixel_ratio: state.device_pixel_ratio, device_pixel_ratio: state.device_pixel_ratio,
}; };
let url = state.load_data.url.clone();
let (script_chan, sampler_chan) = match state.event_loop { let (script_chan, sampler_chan) = match state.event_loop {
Some(script_chan) => { Some(script_chan) => {
let new_layout_info = NewLayoutInfo { let new_layout_info = NewLayoutInfo {
@ -336,7 +334,6 @@ impl Pipeline {
script_chan, script_chan,
pipeline_chan, pipeline_chan,
state.compositor_proxy, state.compositor_proxy,
url,
state.prev_visibility, state.prev_visibility,
state.load_data, state.load_data,
); );
@ -356,7 +353,6 @@ impl Pipeline {
event_loop: Rc<EventLoop>, event_loop: Rc<EventLoop>,
layout_chan: IpcSender<LayoutControlMsg>, layout_chan: IpcSender<LayoutControlMsg>,
compositor_proxy: CompositorProxy, compositor_proxy: CompositorProxy,
url: ServoUrl,
is_visible: bool, is_visible: bool,
load_data: LoadData, load_data: LoadData,
) -> Pipeline { ) -> Pipeline {
@ -368,7 +364,7 @@ impl Pipeline {
event_loop: event_loop, event_loop: event_loop,
layout_chan: layout_chan, layout_chan: layout_chan,
compositor_proxy: compositor_proxy, compositor_proxy: compositor_proxy,
url: url, url: load_data.url.clone(),
children: vec![], children: vec![],
running_animations: false, running_animations: false,
load_data: load_data, load_data: load_data,

View file

@ -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::MouseEventBinding::MouseEventMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::document::determine_policy_for_token; 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::element::Element;
use crate::dom::event::Event; use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget; use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlimageelement::HTMLImageElement; use crate::dom::htmlimageelement::HTMLImageElement;
use crate::dom::mouseevent::MouseEvent; use crate::dom::mouseevent::MouseEvent;
use crate::dom::node::{document_from_node, Node}; use crate::dom::node::{document_from_node, Node};
use crate::dom::urlhelper::UrlHelper; use crate::dom::urlhelper::UrlHelper;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
use crate::task_source::TaskSource;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix}; use html5ever::{LocalName, Prefix};
use net_traits::request::Referrer; use net_traits::request::Referrer;
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin};
use servo_url::ServoUrl; use servo_url::ServoUrl;
use std::default::Default; use std::default::Default;
use style::attr::AttrValue; use style::attr::AttrValue;
@ -624,8 +628,19 @@ pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>) {
// Step 7. // Step 7.
let (maybe_chosen, replace) = match target_attribute_value { let (maybe_chosen, replace) = match target_attribute_value {
Some(name) => source.choose_browsing_context(name.Value(), noopener), Some(name) => {
None => (Some(window.window_proxy()), false), 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. // Step 8.
@ -667,7 +682,23 @@ pub fn follow_hyperlink(subject: &Element, hyperlink_suffix: Option<String>) {
}; };
// Step 14 // Step 14
debug!("following hyperlink to {}", url); let pipeline_id = target_window.upcast::<GlobalScope>().pipeline_id();
target_window.load_url(url, replace, false, referrer, referrer_policy); 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();
}; };
} }

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom::bindings::cell::DomRefCell; 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::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; 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::HTMLFormElementBinding::HTMLFormElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods; 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::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::reflector::DomObject;
@ -46,7 +48,6 @@ use crate::dom::node::{UnbindContext, VecPreOrderInsertionHelper};
use crate::dom::validitystate::ValidationFlags; use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_thread::MainThreadScriptMsg;
use crate::task_source::TaskSource; use crate::task_source::TaskSource;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use encoding_rs::{Encoding, UTF_8}; use encoding_rs::{Encoding, UTF_8};
@ -57,7 +58,7 @@ use hyper::Method;
use mime::{self, Mime}; use mime::{self, Mime};
use net_traits::http_percent_encode; use net_traits::http_percent_encode;
use net_traits::request::Referrer; use net_traits::request::Referrer;
use script_traits::LoadData; use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin};
use servo_rand::random; use servo_rand::random;
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cell::Cell; use std::cell::Cell;
@ -400,6 +401,7 @@ impl HTMLFormElement {
}; };
let target_window = target_document.window(); let target_window = target_document.window();
let mut load_data = LoadData::new( let mut load_data = LoadData::new(
LoadOrigin::Script(doc.origin().immutable().clone()),
action_components, action_components,
None, None,
Some(Referrer::ReferrerUrl(target_document.url())), Some(Referrer::ReferrerUrl(target_document.url())),
@ -530,7 +532,7 @@ impl HTMLFormElement {
} }
/// [Planned navigation](https://html.spec.whatwg.org/multipage/#planned-navigation) /// [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 // Step 1
// Each planned navigation task is tagged with a generation ID, and // Each planned navigation task is tagged with a generation ID, and
// before the task is handled, it first checks whether the HTMLFormElement's // 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); let generation_id = GenerationId(self.generation_id.get().0 + 1);
self.generation_id.set(generation_id); 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 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 this = Trusted::new(self);
let window = Trusted::new(target);
let task = task!(navigate_to_form_planned_navigation: move || { let task = task!(navigate_to_form_planned_navigation: move || {
if generation_id != this.root().generation_id.get() { if generation_id != this.root().generation_id.get() {
return; return;
} }
script_chan.send(MainThreadScriptMsg::Navigate( window
pipeline_id, .root()
load_data, .load_url(
false, HistoryEntryReplacement::Disabled,
)).unwrap(); false,
load_data,
);
}); });
// Step 3. // Step 3.

View file

@ -37,8 +37,8 @@ use profile_traits::ipc as ProfiledIpc;
use script_layout_interface::message::ReflowGoal; use script_layout_interface::message::ReflowGoal;
use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed}; use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
use script_traits::{ use script_traits::{
IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, UpdatePipelineIdReason, HistoryEntryReplacement, IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData,
WindowSizeData, LoadOrigin, UpdatePipelineIdReason, WindowSizeData,
}; };
use script_traits::{NewLayoutInfo, ScriptMsg}; use script_traits::{NewLayoutInfo, ScriptMsg};
use servo_url::ServoUrl; use servo_url::ServoUrl;
@ -109,9 +109,9 @@ impl HTMLIFrameElement {
pub fn navigate_or_reload_child_browsing_context( pub fn navigate_or_reload_child_browsing_context(
&self, &self,
mut load_data: Option<LoadData>, mut load_data: LoadData,
nav_type: NavigationType, nav_type: NavigationType,
replace: bool, replace: HistoryEntryReplacement,
) { ) {
let sandboxed = if self.is_sandboxed() { let sandboxed = if self.is_sandboxed() {
IFrameSandboxed IFrameSandboxed
@ -136,30 +136,27 @@ impl HTMLIFrameElement {
// document; the new navigation will continue blocking it. // document; the new navigation will continue blocking it.
LoadBlocker::terminate(&mut load_blocker); LoadBlocker::terminate(&mut load_blocker);
if let Some(ref mut load_data) = load_data { if load_data.url.scheme() == "javascript" {
let is_javascript = load_data.url.scheme() == "javascript"; let window_proxy = self.GetContentWindow();
if is_javascript { if let Some(window_proxy) = window_proxy {
let window_proxy = self.GetContentWindow(); // Important re security. See https://github.com/servo/servo/issues/23373
if let Some(window_proxy) = window_proxy { // TODO: check according to https://w3c.github.io/webappsec-csp/#should-block-navigation-request
ScriptThread::eval_js_url(&window_proxy.global(), load_data); 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. match load_data.js_eval_result {
// The iframe should always have access to the nested context's active Some(JsEvalResult::NoContent) => (),
// document URL through the browsing context. _ => {
if let Some(ref load_data) = load_data { *load_blocker = Some(LoadBlocker::new(
match load_data.js_eval_result { &*document,
Some(JsEvalResult::NoContent) => (), LoadType::Subframe(load_data.url.clone()),
_ => { ));
*load_blocker = Some(LoadBlocker::new( },
&*document, };
LoadType::Subframe(load_data.url.clone()),
));
},
};
}
let window = window_from_node(self); let window = window_from_node(self);
let old_pipeline_id = self.pipeline_id(); let old_pipeline_id = self.pipeline_id();
@ -182,6 +179,12 @@ impl HTMLIFrameElement {
self.about_blank_pipeline_id.set(Some(new_pipeline_id)); 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 global_scope
.script_to_constellation_chan() .script_to_constellation_chan()
.send(ScriptMsg::ScriptNewIFrame(load_info, pipeline_sender)) .send(ScriptMsg::ScriptNewIFrame(load_info, pipeline_sender))
@ -193,7 +196,7 @@ impl HTMLIFrameElement {
browsing_context_id: browsing_context_id, browsing_context_id: browsing_context_id,
top_level_browsing_context_id: top_level_browsing_context_id, top_level_browsing_context_id: top_level_browsing_context_id,
opener: None, opener: None,
load_data: load_data.unwrap(), load_data: load_data,
pipeline_port: pipeline_receiver, pipeline_port: pipeline_receiver,
content_process_shutdown_chan: None, content_process_shutdown_chan: None,
window_size: WindowSizeData { window_size: WindowSizeData {
@ -270,6 +273,7 @@ impl HTMLIFrameElement {
let document = document_from_node(self); let document = document_from_node(self);
let load_data = LoadData::new( let load_data = LoadData::new(
LoadOrigin::Script(document.origin().immutable().clone()),
url, url,
creator_pipeline_id, creator_pipeline_id,
Some(Referrer::ReferrerUrl(document.url())), Some(Referrer::ReferrerUrl(document.url())),
@ -281,12 +285,12 @@ impl HTMLIFrameElement {
// see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3 // see https://html.spec.whatwg.org/multipage/#the-iframe-element:about:blank-3
let is_about_blank = let is_about_blank =
pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get(); pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
let replace = is_about_blank; let replace = if is_about_blank {
self.navigate_or_reload_child_browsing_context( HistoryEntryReplacement::Enabled
Some(load_data), } else {
NavigationType::Regular, HistoryEntryReplacement::Disabled
replace, };
); self.navigate_or_reload_child_browsing_context(load_data, NavigationType::Regular, replace);
} }
fn create_nested_browsing_context(&self) { fn create_nested_browsing_context(&self) {
@ -296,6 +300,7 @@ impl HTMLIFrameElement {
let window = window_from_node(self); let window = window_from_node(self);
let pipeline_id = Some(window.upcast::<GlobalScope>().pipeline_id()); let pipeline_id = Some(window.upcast::<GlobalScope>().pipeline_id());
let load_data = LoadData::new( let load_data = LoadData::new(
LoadOrigin::Script(document.origin().immutable().clone()),
url, url,
pipeline_id, pipeline_id,
Some(Referrer::ReferrerUrl(document.url().clone())), Some(Referrer::ReferrerUrl(document.url().clone())),
@ -309,9 +314,9 @@ impl HTMLIFrameElement {
.set(Some(top_level_browsing_context_id)); .set(Some(top_level_browsing_context_id));
self.browsing_context_id.set(Some(browsing_context_id)); self.browsing_context_id.set(Some(browsing_context_id));
self.navigate_or_reload_child_browsing_context( self.navigate_or_reload_child_browsing_context(
Some(load_data), load_data,
NavigationType::InitialAboutBlank, NavigationType::InitialAboutBlank,
false, HistoryEntryReplacement::Disabled,
); );
} }

View file

@ -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::LocationBinding::LocationMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; 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::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::str::{DOMString, USVString};
@ -14,6 +15,7 @@ use crate::dom::urlhelper::UrlHelper;
use crate::dom::window::Window; use crate::dom::window::Window;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use net_traits::request::Referrer; use net_traits::request::Referrer;
use script_traits::{HistoryEntryReplacement, LoadData, LoadOrigin};
use servo_url::{MutableOrigin, ServoUrl}; use servo_url::{MutableOrigin, ServoUrl};
#[dom_struct] #[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 { fn get_url(&self) -> ServoUrl {
self.window.get_url() self.window.get_url()
} }
@ -46,7 +71,7 @@ impl Location {
let mut url = self.window.get_url(); let mut url = self.window.get_url();
let referrer = Referrer::ReferrerUrl(url.clone()); let referrer = Referrer::ReferrerUrl(url.clone());
setter(&mut url, value); 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 { fn check_same_origin_domain(&self) -> ErrorResult {
@ -66,7 +91,7 @@ impl Location {
pub fn reload_without_origin_check(&self) { pub fn reload_without_origin_check(&self) {
let url = self.get_url(); let url = self.get_url();
let referrer = Referrer::ReferrerUrl(url.clone()); 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)] #[allow(dead_code)]
@ -84,7 +109,7 @@ impl LocationMethods for Location {
let base_url = self.window.get_url(); let base_url = self.window.get_url();
if let Ok(url) = base_url.join(&url.0) { if let Ok(url) = base_url.join(&url.0) {
let referrer = Referrer::ReferrerUrl(base_url.clone()); let referrer = Referrer::ReferrerUrl(base_url.clone());
self.window.load_url(url, false, false, referrer, None); self.navigate(url, referrer, HistoryEntryReplacement::Disabled, false);
Ok(()) Ok(())
} else { } else {
Err(Error::Syntax) Err(Error::Syntax)
@ -96,7 +121,7 @@ impl LocationMethods for Location {
self.check_same_origin_domain()?; self.check_same_origin_domain()?;
let url = self.get_url(); let url = self.get_url();
let referrer = Referrer::ReferrerUrl(url.clone()); let referrer = Referrer::ReferrerUrl(url.clone());
self.window.load_url(url, true, true, referrer, None); self.navigate(url, referrer, HistoryEntryReplacement::Enabled, true);
Ok(()) Ok(())
} }
@ -108,7 +133,7 @@ impl LocationMethods for Location {
let base_url = self.window.get_url(); let base_url = self.window.get_url();
if let Ok(url) = base_url.join(&url.0) { if let Ok(url) = base_url.join(&url.0) {
let referrer = Referrer::ReferrerUrl(base_url.clone()); let referrer = Referrer::ReferrerUrl(base_url.clone());
self.window.load_url(url, true, false, referrer, None); self.navigate(url, referrer, HistoryEntryReplacement::Enabled, false);
Ok(()) Ok(())
} else { } else {
Err(Error::Syntax) Err(Error::Syntax)
@ -178,7 +203,7 @@ impl LocationMethods for Location {
Err(e) => return Err(Error::Type(format!("Couldn't parse URL: {}", e))), Err(e) => return Err(Error::Type(format!("Couldn't parse URL: {}", e))),
}; };
let referrer = Referrer::ReferrerUrl(current_url.clone()); let referrer = Referrer::ReferrerUrl(current_url.clone());
self.window.load_url(url, false, false, referrer, None); self.navigate(url, referrer, HistoryEntryReplacement::Disabled, false);
Ok(()) Ok(())
} }

View file

@ -88,9 +88,8 @@ use js::rust::HandleValue;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse}; use net_traits::image_cache::{ImageCache, ImageResponder, ImageResponse};
use net_traits::image_cache::{PendingImageId, PendingImageResponse}; use net_traits::image_cache::{PendingImageId, PendingImageResponse};
use net_traits::request::Referrer;
use net_traits::storage_thread::StorageType; use net_traits::storage_thread::StorageType;
use net_traits::{ReferrerPolicy, ResourceThreads}; use net_traits::ResourceThreads;
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use profile_traits::ipc as ProfiledIpc; use profile_traits::ipc as ProfiledIpc;
use profile_traits::mem::ProfilerChan as MemProfilerChan; use profile_traits::mem::ProfilerChan as MemProfilerChan;
@ -102,7 +101,7 @@ use script_layout_interface::rpc::{
}; };
use script_layout_interface::{PendingImageState, TrustedNodeAddress}; use script_layout_interface::{PendingImageState, TrustedNodeAddress};
use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; 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::{ScriptMsg, ScriptToConstellationChan, ScrollState, TimerEvent, TimerEventId};
use script_traits::{TimerSchedulerMsg, WindowSizeData, WindowSizeType}; use script_traits::{TimerSchedulerMsg, WindowSizeData, WindowSizeType};
use selectors::attr::CaseSensitivity; 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. /// 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( pub fn load_url(
&self, &self,
url: ServoUrl, replace: HistoryEntryReplacement,
replace: bool,
force_reload: bool, force_reload: bool,
referrer: Referrer, load_data: LoadData,
referrer_policy: Option<ReferrerPolicy>,
) { ) {
let doc = self.Document(); let doc = self.Document();
let referrer_policy = referrer_policy.or(doc.get_referrer_policy()); // TODO: Important re security. See https://github.com/servo/servo/issues/23373
// https://html.spec.whatwg.org/multipage/#navigating-across-documents // Step 3: check that the source browsing-context is "allowed to navigate" this window.
if !force_reload && 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 // Step 6
if let Some(fragment) = url.fragment() { if let Some(fragment) = load_data.url.fragment() {
self.send_to_constellation(ScriptMsg::NavigatedToFragment(url.clone(), replace)); self.send_to_constellation(ScriptMsg::NavigatedToFragment(
load_data.url.clone(),
replace,
));
doc.check_and_scroll_fragment(fragment); doc.check_and_scroll_fragment(fragment);
let this = Trusted::new(self); let this = Trusted::new(self);
let old_url = doc.url().into_string(); 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 task = task!(hashchange_event: move || {
let this = this.root(); let this = this.root();
let event = HashChangeEvent::new( let event = HashChangeEvent::new(
@ -1772,7 +1775,7 @@ impl Window {
self.pipeline_id(), self.pipeline_id(),
TaskSourceName::DOMManipulation, TaskSourceName::DOMManipulation,
)); ));
doc.set_url(url.clone()); doc.set_url(load_data.url.clone());
return; return;
} }
} }
@ -1797,13 +1800,9 @@ impl Window {
// then put it in the delaying load events mode. // then put it in the delaying load events mode.
self.window_proxy().start_delaying_load_events_mode(); self.window_proxy().start_delaying_load_events_mode();
} }
self.main_thread_script_chan() // TODO: step 11, navigationType.
.send(MainThreadScriptMsg::Navigate( // Step 12, 13
pipeline_id, ScriptThread::navigate(pipeline_id, load_data, replace);
LoadData::new(url, Some(pipeline_id), Some(referrer), referrer_policy),
replace,
))
.unwrap();
}; };
} }

View file

@ -46,7 +46,10 @@ use msg::constellation_msg::BrowsingContextId;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use msg::constellation_msg::TopLevelBrowsingContextId; use msg::constellation_msg::TopLevelBrowsingContextId;
use net_traits::request::Referrer; 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 servo_url::ServoUrl;
use std::cell::Cell; use std::cell::Cell;
use std::ptr; use std::ptr;
@ -267,24 +270,28 @@ impl WindowProxy {
let new_browsing_context_id = let new_browsing_context_id =
BrowsingContextId::from(new_top_level_browsing_context_id); BrowsingContextId::from(new_top_level_browsing_context_id);
let new_pipeline_id = PipelineId::new(); 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 let document = self
.currently_active .currently_active
.get() .get()
.and_then(|id| ScriptThread::find_document(id)) .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 blank_url = ServoUrl::parse("about:blank").ok().unwrap();
let load_data = LoadData::new( let load_data = LoadData::new(
LoadOrigin::Script(document.origin().immutable().clone()),
blank_url, blank_url,
None, None,
Some(Referrer::ReferrerUrl(document.url().clone())), Some(Referrer::ReferrerUrl(document.url().clone())),
document.get_referrer_policy(), 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 (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap();
let new_layout_info = NewLayoutInfo { let new_layout_info = NewLayoutInfo {
parent_info: None, parent_info: None,
@ -436,13 +443,21 @@ impl WindowProxy {
Referrer::Client Referrer::Client
}; };
// Step 14.5 // 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, url,
new, Some(pipeline_id),
false, Some(referrer),
referrer, referrer_policy,
target_document.get_referrer_policy(),
); );
let replacement_flag = if new {
HistoryEntryReplacement::Enabled
} else {
HistoryEntryReplacement::Disabled
};
target_window.load_url(replacement_flag, false, load_data);
} }
if noopener { if noopener {
// Step 15 (Dis-owning has been done in create_auxiliary_browsing_context). // Step 15 (Dis-owning has been done in create_auxiliary_browsing_context).

View file

@ -32,6 +32,7 @@ use crate::dom::bindings::conversions::{
}; };
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite; use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::ThreadLocalStackRoots; use crate::dom::bindings::root::ThreadLocalStackRoots;
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom, RootCollection}; 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::remote_event::RemoteEventTaskSource;
use crate::task_source::user_interaction::UserInteractionTaskSource; use crate::task_source::user_interaction::UserInteractionTaskSource;
use crate::task_source::websocket::WebsocketTaskSource; use crate::task_source::websocket::WebsocketTaskSource;
use crate::task_source::TaskSource;
use crate::task_source::TaskSourceName; use crate::task_source::TaskSourceName;
use crate::webdriver_handlers; use crate::webdriver_handlers;
use bluetooth_traits::BluetoothRequest; use bluetooth_traits::BluetoothRequest;
@ -128,8 +130,10 @@ use script_traits::CompositorEvent::{
WheelEvent, WheelEvent,
}; };
use script_traits::{CompositorEvent, ConstellationControlMsg}; use script_traits::{CompositorEvent, ConstellationControlMsg};
use script_traits::{DiscardBrowsingContext, DocumentActivity, EventResult}; use script_traits::{
use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData}; DiscardBrowsingContext, DocumentActivity, EventResult, HistoryEntryReplacement,
};
use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData, LoadOrigin};
use script_traits::{MouseButton, MouseEventType, NewLayoutInfo}; use script_traits::{MouseButton, MouseEventType, NewLayoutInfo};
use script_traits::{Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory}; use script_traits::{Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory};
use script_traits::{ScriptToConstellationChan, TimerEvent, TimerSchedulerMsg}; use script_traits::{ScriptToConstellationChan, TimerEvent, TimerSchedulerMsg};
@ -263,10 +267,6 @@ enum MixedMessage {
pub enum MainThreadScriptMsg { pub enum MainThreadScriptMsg {
/// Common variants associated with the script messages /// Common variants associated with the script messages
Common(CommonScriptMsg), 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 /// Notifies the script thread that a new worklet has been loaded, and thus the page should be
/// reflowed. /// reflowed.
WorkletLoaded(PipelineId), 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) { pub fn process_attach_layout(new_layout_info: NewLayoutInfo, origin: MutableOrigin) {
SCRIPT_THREAD_ROOT.with(|root| { SCRIPT_THREAD_ROOT.with(|root| {
if let Some(script_thread) = root.get() { if let Some(script_thread) = root.get() {
@ -1474,7 +1542,7 @@ impl ScriptThread {
SetDocumentActivity(id, ..) => Some(id), SetDocumentActivity(id, ..) => Some(id),
ChangeFrameVisibilityStatus(id, ..) => Some(id), ChangeFrameVisibilityStatus(id, ..) => Some(id),
NotifyVisibilityChange(id, ..) => Some(id), NotifyVisibilityChange(id, ..) => Some(id),
Navigate(id, ..) => Some(id), NavigateIframe(id, ..) => Some(id),
PostMessage { target: id, .. } => Some(id), PostMessage { target: id, .. } => Some(id),
UpdatePipelineId(_, _, _, id, _) => Some(id), UpdatePipelineId(_, _, _, id, _) => Some(id),
UpdateHistoryState(id, ..) => Some(id), UpdateHistoryState(id, ..) => Some(id),
@ -1504,7 +1572,6 @@ impl ScriptThread {
pipeline_id pipeline_id
}, },
MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None, MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None,
MainThreadScriptMsg::Navigate(pipeline_id, ..) => Some(pipeline_id),
MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(pipeline_id), MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(pipeline_id),
MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(pipeline_id), MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(pipeline_id),
MainThreadScriptMsg::DispatchJobQueue { .. } => None, MainThreadScriptMsg::DispatchJobQueue { .. } => None,
@ -1614,14 +1681,14 @@ impl ScriptThread {
_ => unreachable!(), _ => unreachable!(),
}; };
}, },
ConstellationControlMsg::Navigate( ConstellationControlMsg::NavigateIframe(
parent_pipeline_id, parent_pipeline_id,
browsing_context_id, browsing_context_id,
load_data, load_data,
replace, replace,
) => self.handle_navigate( ) => self.handle_navigate_iframe(
parent_pipeline_id, parent_pipeline_id,
Some(browsing_context_id), browsing_context_id,
load_data, load_data,
replace, replace,
), ),
@ -1736,9 +1803,6 @@ impl ScriptThread {
fn handle_msg_from_script(&self, msg: MainThreadScriptMsg) { fn handle_msg_from_script(&self, msg: MainThreadScriptMsg) {
match msg { 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::Task(_, task, _, _)) => task.run_box(),
MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(chan)) => { MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(chan)) => {
self.collect_reports(chan) self.collect_reports(chan)
@ -3248,50 +3312,30 @@ impl ScriptThread {
document.handle_wheel_event(self.js_runtime.rt(), wheel_delta, point, node_address); document.handle_wheel_event(self.js_runtime.rt(), wheel_delta, point, node_address);
} }
/// <https://html.spec.whatwg.org/multipage/#navigating-across-documents> /// Handle a "navigate an iframe" message from the constellation.
/// The entry point for content to notify that a new load has been requested fn handle_navigate_iframe(
/// for the given pipeline (specifically the "navigate" algorithm).
fn handle_navigate(
&self, &self,
parent_pipeline_id: PipelineId, parent_pipeline_id: PipelineId,
browsing_context_id: Option<BrowsingContextId>, browsing_context_id: BrowsingContextId,
mut load_data: LoadData, load_data: LoadData,
replace: bool, replace: HistoryEntryReplacement,
) { ) {
let is_javascript = load_data.url.scheme() == "javascript"; let iframe = self
if is_javascript { .documents
let window = self.documents.borrow().find_window(parent_pipeline_id); .borrow()
if let Some(window) = window { .find_iframe(parent_pipeline_id, browsing_context_id);
ScriptThread::eval_js_url(window.upcast::<GlobalScope>(), &mut load_data); if let Some(iframe) = iframe {
} iframe.navigate_or_reload_child_browsing_context(
} load_data,
NavigationType::Regular,
match browsing_context_id { replace,
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();
},
} }
} }
/// 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) { 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 URLs serialization is equivalent to (5.) to (7.): // This slice of the URLs serialization is equivalent to (5.) to (7.):
// Start with the scheme data of the parsed URL; // Start with the scheme data of the parsed URL;
// append question mark and query component, if any; // append question mark and query component, if any;

View file

@ -62,7 +62,8 @@ use webrender_api::{
use webvr_traits::{WebVREvent, WebVRMsg}; use webvr_traits::{WebVREvent, WebVRMsg};
pub use crate::script_msg::{ pub use crate::script_msg::{
DOMMessage, SWManagerMsg, SWManagerSenders, ScopeThings, ServiceWorkerMsg, DOMMessage, HistoryEntryReplacement, SWManagerMsg, SWManagerSenders, ScopeThings,
ServiceWorkerMsg,
}; };
pub use crate::script_msg::{ pub use crate::script_msg::{
EventResult, IFrameSize, IFrameSizeMsg, LayoutMsg, LogEntry, ScriptMsg, EventResult, IFrameSize, IFrameSizeMsg, LayoutMsg, LogEntry, ScriptMsg,
@ -117,10 +118,24 @@ pub enum LayoutControlMsg {
PaintMetric(Epoch, u64), 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 /// can be passed to `LoadUrl` to load a page with GET/POST
/// parameters or headers /// parameters or headers
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct LoadData { pub struct LoadData {
/// The origin where the load started.
pub load_origin: LoadOrigin,
/// The URL. /// The URL.
pub url: ServoUrl, pub url: ServoUrl,
/// The creator pipeline id if this is an about:blank load. /// The creator pipeline id if this is an about:blank load.
@ -160,12 +175,14 @@ pub enum JsEvalResult {
impl LoadData { impl LoadData {
/// Create a new `LoadData` object. /// Create a new `LoadData` object.
pub fn new( pub fn new(
load_origin: LoadOrigin,
url: ServoUrl, url: ServoUrl,
creator_pipeline_id: Option<PipelineId>, creator_pipeline_id: Option<PipelineId>,
referrer: Option<Referrer>, referrer: Option<Referrer>,
referrer_policy: Option<ReferrerPolicy>, referrer_policy: Option<ReferrerPolicy>,
) -> LoadData { ) -> LoadData {
LoadData { LoadData {
load_origin,
url: url, url: url,
creator_pipeline_id: creator_pipeline_id, creator_pipeline_id: creator_pipeline_id,
method: Method::GET, method: Method::GET,
@ -289,7 +306,12 @@ pub enum ConstellationControlMsg {
NotifyVisibilityChange(PipelineId, BrowsingContextId, bool), NotifyVisibilityChange(PipelineId, BrowsingContextId, bool),
/// Notifies script thread that a url should be loaded in this iframe. /// Notifies script thread that a url should be loaded in this iframe.
/// PipelineId is for the parent, BrowsingContextId is for the nested browsing context /// 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. /// Post a message to a given window.
PostMessage { PostMessage {
/// The target of the message. /// The target of the message.
@ -376,7 +398,7 @@ impl fmt::Debug for ConstellationControlMsg {
SetDocumentActivity(..) => "SetDocumentActivity", SetDocumentActivity(..) => "SetDocumentActivity",
ChangeFrameVisibilityStatus(..) => "ChangeFrameVisibilityStatus", ChangeFrameVisibilityStatus(..) => "ChangeFrameVisibilityStatus",
NotifyVisibilityChange(..) => "NotifyVisibilityChange", NotifyVisibilityChange(..) => "NotifyVisibilityChange",
Navigate(..) => "Navigate", NavigateIframe(..) => "NavigateIframe",
PostMessage { .. } => "PostMessage", PostMessage { .. } => "PostMessage",
UpdatePipelineId(..) => "UpdatePipelineId", UpdatePipelineId(..) => "UpdatePipelineId",
UpdateHistoryState(..) => "UpdateHistoryState", UpdateHistoryState(..) => "UpdateHistoryState",
@ -659,6 +681,8 @@ pub enum IFrameSandboxState {
/// Specifies the information required to load an auxiliary browsing context. /// Specifies the information required to load an auxiliary browsing context.
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct AuxiliaryBrowsingContextLoadInfo { pub struct AuxiliaryBrowsingContextLoadInfo {
/// Load data containing the url to load
pub load_data: LoadData,
/// The pipeline opener browsing context. /// The pipeline opener browsing context.
pub opener_pipeline_id: PipelineId, pub opener_pipeline_id: PipelineId,
/// The new top-level ID for the auxiliary. /// The new top-level ID for the auxiliary.
@ -684,7 +708,7 @@ pub struct IFrameLoadInfo {
pub is_private: bool, pub is_private: bool,
/// Wether this load should replace the current entry (reload). If true, the current /// Wether this load should replace the current entry (reload). If true, the current
/// entry will be replaced instead of a new entry being added. /// 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. /// 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. /// The information required to load an iframe.
pub info: IFrameLoadInfo, pub info: IFrameLoadInfo,
/// Load data containing the url to load /// 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. /// The old pipeline ID for this iframe, if a page was previously loaded.
pub old_pipeline_id: Option<PipelineId>, pub old_pipeline_id: Option<PipelineId>,
/// Sandbox type of this iframe /// Sandbox type of this iframe

View file

@ -5,7 +5,6 @@
use crate::AnimationState; use crate::AnimationState;
use crate::AuxiliaryBrowsingContextLoadInfo; use crate::AuxiliaryBrowsingContextLoadInfo;
use crate::DocumentState; use crate::DocumentState;
use crate::IFrameLoadInfo;
use crate::IFrameLoadInfoWithData; use crate::IFrameLoadInfoWithData;
use crate::LayoutControlMsg; use crate::LayoutControlMsg;
use crate::LoadData; use crate::LoadData;
@ -97,6 +96,15 @@ pub enum LogEntry {
Warn(String), 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. /// Messages from the script to the constellation.
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub enum ScriptMsg { pub enum ScriptMsg {
@ -145,7 +153,7 @@ pub enum ScriptMsg {
LoadComplete, LoadComplete,
/// A new load has been requested, with an option to replace the current entry once loaded /// A new load has been requested, with an option to replace the current entry once loaded
/// instead of adding a new entry. /// instead of adding a new entry.
LoadUrl(LoadData, bool), LoadUrl(LoadData, HistoryEntryReplacement),
/// Abort loading after sending a LoadUrl message. /// Abort loading after sending a LoadUrl message.
AbortLoadUrl, AbortLoadUrl,
/// Post a message to the currently active window of a given browsing context. /// Post a message to the currently active window of a given browsing context.
@ -160,7 +168,7 @@ pub enum ScriptMsg {
data: Vec<u8>, data: Vec<u8>,
}, },
/// Inform the constellation that a fragment was navigated to and whether or not it was a replacement navigation. /// 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. /// HTMLIFrameElement Forward or Back traversal.
TraverseHistory(TraversalDirection), TraverseHistory(TraversalDirection),
/// Inform the constellation of a pushed history state. /// Inform the constellation of a pushed history state.
@ -177,7 +185,7 @@ pub enum ScriptMsg {
/// A load has been requested in an IFrame. /// A load has been requested in an IFrame.
ScriptLoadedURLInIFrame(IFrameLoadInfoWithData), ScriptLoadedURLInIFrame(IFrameLoadInfoWithData),
/// A load of the initial `about:blank` has been completed in an IFrame. /// 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. /// Script has opened a new auxiliary browsing context.
ScriptNewAuxiliary( ScriptNewAuxiliary(
AuxiliaryBrowsingContextLoadInfo, AuxiliaryBrowsingContextLoadInfo,

View file

@ -30,7 +30,7 @@ use script_traits::webdriver_msg::{LoadStatus, WebDriverCookieError, WebDriverFr
use script_traits::webdriver_msg::{ use script_traits::webdriver_msg::{
WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverScriptCommand, 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::de::{Deserialize, Deserializer, MapAccess, Visitor};
use serde::ser::{Serialize, Serializer}; use serde::ser::{Serialize, Serializer};
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -581,7 +581,7 @@ impl Handler {
let (sender, receiver) = ipc::channel().unwrap(); 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 = let cmd_msg =
WebDriverCommandMsg::LoadUrl(top_level_browsing_context_id, load_data, sender.clone()); WebDriverCommandMsg::LoadUrl(top_level_browsing_context_id, load_data, sender.clone());
self.constellation_chan self.constellation_chan

View file

@ -624872,7 +624872,7 @@
"testharness" "testharness"
], ],
"html/semantics/scripting-1/the-script-element/execution-timing/029.html": [ "html/semantics/scripting-1/the-script-element/execution-timing/029.html": [
"c74665ec1e5f37d4e9ec0ecc65f626e79d4942d4", "33548e566ac67823f9c19af7785a13e394c4964b",
"testharness" "testharness"
], ],
"html/semantics/scripting-1/the-script-element/execution-timing/030.html": [ "html/semantics/scripting-1/the-script-element/execution-timing/030.html": [

View file

@ -10,6 +10,9 @@
<div id="log">FAILED (This TC requires JavaScript enabled)</div> <div id="log">FAILED (This TC requires JavaScript enabled)</div>
<p><a href="javascript:log('JS URL')"></a></p> <p><a href="javascript:log('JS URL')"></a></p>
<script>log('inline script #1'); <script>log('inline script #1');
window.addEventListener("beforeunload", function( event ) {
log('beforeunload event');
});
if(document.links[0].click){ if(document.links[0].click){
document.links[0].click(); document.links[0].click();
}else{ }else{
@ -25,13 +28,26 @@
log( 'inline script #2' ); log( 'inline script #2' );
var t = async_test() 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, [ assert_any(assert_array_equals, eventOrder, [
['inline script #1', 'end script #1', 'JS URL', 'inline script #2'], ['inline script #1', 'end script #1', 'beforeunload event', 'inline script #2', 'JS URL'],
['inline script #1', 'end script #1', 'inline script #2', 'JS URL']]); ['inline script #1', 'end script #1', 'inline script #2', 'beforeunload event', 'JS URL']]);
t.done(); 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> </script>
</body></html> </body></html>