diff options
Diffstat (limited to 'components/script/dom/htmliframeelement.rs')
-rw-r--r-- | components/script/dom/htmliframeelement.rs | 1000 |
1 files changed, 500 insertions, 500 deletions
diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 599f2f69c36..6f5264999be 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -1,75 +1,68 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use document_loader::{LoadBlocker, LoadType}; -use dom::attr::Attr; -use dom::bindings::cell::DOMRefCell; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementErrorEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementIconChangeEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementLocationChangeEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementOpenTabEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementOpenWindowEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementSecurityChangeDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserElementVisibilityChangeEventDetail; -use dom::bindings::codegen::Bindings::BrowserElementBinding::BrowserShowModalPromptEventDetail; -use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding; -use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; -use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; -use dom::bindings::conversions::ToJSValConvertible; -use dom::bindings::error::{Error, ErrorResult, Fallible}; -use dom::bindings::inheritance::Castable; -use dom::bindings::js::{LayoutJS, MutNullableJS, Root}; -use dom::bindings::refcounted::Trusted; -use dom::bindings::reflector::DomObject; -use dom::bindings::str::DOMString; -use dom::browsingcontext::BrowsingContext; -use dom::customevent::CustomEvent; -use dom::document::Document; -use dom::domtokenlist::DOMTokenList; -use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; -use dom::event::Event; -use dom::eventtarget::EventTarget; -use dom::globalscope::GlobalScope; -use dom::htmlelement::HTMLElement; -use dom::node::{Node, NodeDamage, UnbindContext, document_from_node, window_from_node}; -use dom::virtualmethods::VirtualMethods; -use dom::window::{ReflowReason, Window}; + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::document_loader::{LoadBlocker, LoadType}; +use crate::dom::attr::Attr; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::DomObject; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::document::Document; +use crate::dom::domtokenlist::DOMTokenList; +use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers}; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::node::{ + document_from_node, window_from_node, BindContext, Node, NodeDamage, UnbindContext, +}; +use crate::dom::virtualmethods::VirtualMethods; +use crate::dom::window::ReflowReason; +use crate::dom::windowproxy::WindowProxy; +use crate::script_thread::ScriptThread; +use crate::task_source::TaskSource; use dom_struct::dom_struct; -use html5ever_atoms::LocalName; +use html5ever::{LocalName, Prefix}; use ipc_channel::ipc; -use js::jsapi::{JSAutoCompartment, JSContext, MutableHandleValue}; -use js::jsval::{NullValue, UndefinedValue}; -use msg::constellation_msg::{FrameType, FrameId, PipelineId, TraversalDirection}; -use net_traits::response::HttpsState; -use script_layout_interface::message::ReflowQueryType; -use script_thread::{ScriptThread, Runnable}; -use script_traits::{IFrameLoadInfo, IFrameLoadInfoWithData, LoadData}; -use script_traits::{MozBrowserEvent, NewLayoutInfo, ScriptMsg as ConstellationMsg}; +use msg::constellation_msg::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId}; +use profile_traits::ipc as ProfiledIpc; +use script_layout_interface::message::ReflowGoal; use script_traits::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed}; +use script_traits::{ + HistoryEntryReplacement, IFrameLoadInfo, IFrameLoadInfoWithData, JsEvalResult, LoadData, + LoadOrigin, UpdatePipelineIdReason, WindowSizeData, +}; +use script_traits::{NewLayoutInfo, ScriptMsg}; use servo_atoms::Atom; -use servo_config::prefs::PREFS; -use servo_config::servo_version; use servo_url::ServoUrl; use std::cell::Cell; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; -use style::context::ReflowGoal; -use task_source::TaskSource; bitflags! { - #[derive(JSTraceable, HeapSizeOf)] - flags SandboxAllowance: u8 { - const ALLOW_NOTHING = 0x00, - const ALLOW_SAME_ORIGIN = 0x01, - const ALLOW_TOP_NAVIGATION = 0x02, - const ALLOW_FORMS = 0x04, - const ALLOW_SCRIPTS = 0x08, - const ALLOW_POINTER_LOCK = 0x10, - const ALLOW_POPUPS = 0x20 + #[derive(JSTraceable, MallocSizeOf)] + struct SandboxAllowance: u8 { + const ALLOW_NOTHING = 0x00; + const ALLOW_SAME_ORIGIN = 0x01; + const ALLOW_TOP_NAVIGATION = 0x02; + const ALLOW_FORMS = 0x04; + const ALLOW_SCRIPTS = 0x08; + const ALLOW_POINTER_LOCK = 0x10; + const ALLOW_POPUPS = 0x20; } } #[derive(PartialEq)] +pub enum NavigationType { + InitialAboutBlank, + Regular, +} + +#[derive(PartialEq)] enum ProcessingMode { FirstTime, NotFirstTime, @@ -78,11 +71,14 @@ enum ProcessingMode { #[dom_struct] pub struct HTMLIFrameElement { htmlelement: HTMLElement, - frame_id: FrameId, + top_level_browsing_context_id: Cell<Option<TopLevelBrowsingContextId>>, + browsing_context_id: Cell<Option<BrowsingContextId>>, pipeline_id: Cell<Option<PipelineId>>, - sandbox: MutNullableJS<DOMTokenList>, + pending_pipeline_id: Cell<Option<PipelineId>>, + about_blank_pipeline_id: Cell<Option<PipelineId>>, + sandbox: MutNullableDom<DOMTokenList>, sandbox_allowance: Cell<Option<SandboxAllowance>>, - load_blocker: DOMRefCell<Option<LoadBlocker>>, + load_blocker: DomRefCell<Option<LoadBlocker>>, visibility: Cell<bool>, } @@ -95,172 +91,348 @@ impl HTMLIFrameElement { /// step 1. fn get_url(&self) -> ServoUrl { let element = self.upcast::<Element>(); - element.get_attribute(&ns!(), &local_name!("src")).and_then(|src| { - let url = src.value(); - if url.is_empty() { - None - } else { - document_from_node(self).base_url().join(&url).ok() - } - }).unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap()) - } - - pub fn generate_new_pipeline_id(&self) -> (Option<PipelineId>, PipelineId) { - let old_pipeline_id = self.pipeline_id.get(); - let new_pipeline_id = PipelineId::new(); - self.pipeline_id.set(Some(new_pipeline_id)); - debug!("Frame {} created pipeline {}.", self.frame_id, new_pipeline_id); - (old_pipeline_id, new_pipeline_id) + element + .get_attribute(&ns!(), &local_name!("src")) + .and_then(|src| { + let url = src.value(); + if url.is_empty() { + None + } else { + document_from_node(self).base_url().join(&url).ok() + } + }) + .unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap()) } - pub fn navigate_or_reload_child_browsing_context(&self, load_data: Option<LoadData>, replace: bool) { + pub fn navigate_or_reload_child_browsing_context( + &self, + mut load_data: LoadData, + nav_type: NavigationType, + replace: HistoryEntryReplacement, + ) { let sandboxed = if self.is_sandboxed() { IFrameSandboxed } else { IFrameUnsandboxed }; - let document = document_from_node(self); + let browsing_context_id = match self.browsing_context_id() { + None => return warn!("Navigating unattached iframe."), + Some(id) => id, + }; - let mut load_blocker = self.load_blocker.borrow_mut(); - // Any oustanding load is finished from the point of view of the blocked - // document; the new navigation will continue blocking it. - LoadBlocker::terminate(&mut load_blocker); + let top_level_browsing_context_id = match self.top_level_browsing_context_id() { + None => return warn!("Navigating unattached iframe."), + Some(id) => id, + }; + + let document = document_from_node(self); - //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 { - *load_blocker = Some(LoadBlocker::new(&*document, LoadType::Subframe(load_data.url.clone()))); + { + let mut load_blocker = self.load_blocker.borrow_mut(); + // Any oustanding load is finished from the point of view of the blocked + // document; the new navigation will continue blocking it. + LoadBlocker::terminate(&mut load_blocker); + } + + 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); + } + } } + match load_data.js_eval_result { + Some(JsEvalResult::NoContent) => (), + _ => { + let mut load_blocker = self.load_blocker.borrow_mut(); + *load_blocker = Some(LoadBlocker::new( + &*document, + LoadType::Subframe(load_data.url.clone()), + )); + }, + }; + let window = window_from_node(self); - let (old_pipeline_id, new_pipeline_id) = self.generate_new_pipeline_id(); - let private_iframe = self.privatebrowsing(); - let frame_type = if self.Mozbrowser() { FrameType::MozBrowserIFrame } else { FrameType::IFrame }; + let old_pipeline_id = self.pipeline_id(); + let new_pipeline_id = PipelineId::new(); + self.pending_pipeline_id.set(Some(new_pipeline_id)); let global_scope = window.upcast::<GlobalScope>(); let load_info = IFrameLoadInfo { parent_pipeline_id: global_scope.pipeline_id(), - frame_id: self.frame_id, + browsing_context_id: browsing_context_id, + top_level_browsing_context_id: top_level_browsing_context_id, new_pipeline_id: new_pipeline_id, - is_private: private_iframe, - frame_type: frame_type, + is_private: false, // FIXME + inherited_secure_context: load_data.inherited_secure_context, replace: replace, }; - if load_data.as_ref().map_or(false, |d| d.url.as_str() == "about:blank") { - let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); - - global_scope - .constellation_chan() - .send(ConstellationMsg::ScriptLoadedAboutBlankInIFrame(load_info, pipeline_sender)) - .unwrap(); - - let new_layout_info = NewLayoutInfo { - parent_info: Some((global_scope.pipeline_id(), frame_type)), - new_pipeline_id: new_pipeline_id, - frame_id: self.frame_id, - load_data: load_data.unwrap(), - pipeline_port: pipeline_receiver, - content_process_shutdown_chan: None, - window_size: None, - layout_threads: PREFS.get("layout.threads").as_u64().expect("count") as usize, - }; - - ScriptThread::process_attach_layout(new_layout_info, document.origin().clone()); - } else { - let load_info = IFrameLoadInfoWithData { - info: load_info, - load_data: load_data, - old_pipeline_id: old_pipeline_id, - sandbox: sandboxed, - }; - global_scope - .constellation_chan() - .send(ConstellationMsg::ScriptLoadedURLInIFrame(load_info)) - .unwrap(); - } + let window_size = WindowSizeData { + initial_viewport: window + .inner_window_dimensions_query(browsing_context_id) + .unwrap_or_default(), + device_pixel_ratio: window.device_pixel_ratio(), + }; - if PREFS.is_mozbrowser_enabled() { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart - self.dispatch_mozbrowser_event(MozBrowserEvent::LoadStart); + match nav_type { + NavigationType::InitialAboutBlank => { + let (pipeline_sender, pipeline_receiver) = ipc::channel().unwrap(); + + 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, + window_size, + }; + global_scope + .script_to_constellation_chan() + .send(ScriptMsg::ScriptNewIFrame(load_info, pipeline_sender)) + .unwrap(); + + let new_layout_info = NewLayoutInfo { + parent_info: Some(global_scope.pipeline_id()), + new_pipeline_id: new_pipeline_id, + browsing_context_id: browsing_context_id, + top_level_browsing_context_id: top_level_browsing_context_id, + opener: None, + load_data: load_data, + pipeline_port: pipeline_receiver, + window_size, + }; + + self.pipeline_id.set(Some(new_pipeline_id)); + ScriptThread::process_attach_layout(new_layout_info, document.origin().clone()); + }, + NavigationType::Regular => { + let load_info = IFrameLoadInfoWithData { + info: load_info, + load_data: load_data, + old_pipeline_id: old_pipeline_id, + sandbox: sandboxed, + window_size, + }; + global_scope + .script_to_constellation_chan() + .send(ScriptMsg::ScriptLoadedURLInIFrame(load_info)) + .unwrap(); + }, } } - /// https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes + /// <https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes> fn process_the_iframe_attributes(&self, mode: ProcessingMode) { - // TODO: srcdoc - - // https://github.com/whatwg/html/issues/490 - if mode == ProcessingMode::FirstTime && !self.upcast::<Element>().has_attribute(&local_name!("src")) { + if self + .upcast::<Element>() + .has_attribute(&local_name!("srcdoc")) + { + let url = ServoUrl::parse("about:srcdoc").unwrap(); + let document = document_from_node(self); let window = window_from_node(self); - let event_loop = window.dom_manipulation_task_source(); - let _ = event_loop.queue(box IFrameLoadEventSteps::new(self), - window.upcast()); + let pipeline_id = Some(window.upcast::<GlobalScope>().pipeline_id()); + let mut load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + pipeline_id, + window.upcast::<GlobalScope>().get_referrer(), + document.get_referrer_policy(), + Some(window.upcast::<GlobalScope>().is_secure_context()), + ); + let element = self.upcast::<Element>(); + load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc"))); + self.navigate_or_reload_child_browsing_context( + load_data, + NavigationType::Regular, + HistoryEntryReplacement::Disabled, + ); return; } - let url = self.get_url(); + let window = window_from_node(self); - // TODO: check ancestor browsing contexts for same URL + // https://html.spec.whatwg.org/multipage/#attr-iframe-name + // Note: the spec says to set the name 'when the nested browsing context is created'. + // The current implementation sets the name on the window, + // when the iframe attributes are first processed. + if mode == ProcessingMode::FirstTime { + if let Some(window) = self.GetContentWindow() { + window.set_name( + self.upcast::<Element>() + .get_name() + .map_or(DOMString::from(""), |n| DOMString::from(&*n)), + ); + } + } - let document = document_from_node(self); - self.navigate_or_reload_child_browsing_context( - Some(LoadData::new(url, document.get_referrer_policy(), Some(document.url()))), false); - } + // https://github.com/whatwg/html/issues/490 + if mode == ProcessingMode::FirstTime && + !self.upcast::<Element>().has_attribute(&local_name!("src")) + { + let this = Trusted::new(self); + let pipeline_id = self.pipeline_id().unwrap(); + // FIXME(nox): Why are errors silenced here? + let _ = window.task_manager().dom_manipulation_task_source().queue( + task!(iframe_load_event_steps: move || { + this.root().iframe_load_event_steps(pipeline_id); + }), + window.upcast(), + ); + return; + } - #[allow(unsafe_code)] - pub fn dispatch_mozbrowser_event(&self, event: MozBrowserEvent) { - assert!(PREFS.is_mozbrowser_enabled()); + let url = self.get_url(); - if self.Mozbrowser() { - let window = window_from_node(self); - let custom_event = build_mozbrowser_custom_event(&window, event); - custom_event.upcast::<Event>().fire(self.upcast()); + // TODO(#25748): + // By spec, we return early if there's an ancestor browsing context + // "whose active document's url, ignoring fragments, is equal". + // However, asking about ancestor browsing contexts is more nuanced than + // it sounds and not implemented here. + // Within a single origin, we can do it by walking window proxies, + // and this check covers only that single-origin case, protecting + // against simple typo self-includes but nothing more elaborate. + let mut ancestor = window.GetParent(); + while let Some(a) = ancestor { + if let Some(ancestor_url) = a.document().map(|d| d.url()) { + if ancestor_url.scheme() == url.scheme() && + ancestor_url.username() == url.username() && + ancestor_url.password() == url.password() && + ancestor_url.host() == url.host() && + ancestor_url.port() == url.port() && + ancestor_url.path() == url.path() && + ancestor_url.query() == url.query() + { + return; + } + } + ancestor = a.parent().map(|p| DomRoot::from_ref(p)); } + + let creator_pipeline_id = if url.as_str() == "about:blank" { + Some(window.upcast::<GlobalScope>().pipeline_id()) + } else { + None + }; + + let document = document_from_node(self); + let load_data = LoadData::new( + LoadOrigin::Script(document.origin().immutable().clone()), + url, + creator_pipeline_id, + window.upcast::<GlobalScope>().get_referrer(), + document.get_referrer_policy(), + Some(window.upcast::<GlobalScope>().is_secure_context()), + ); + + let pipeline_id = self.pipeline_id(); + // If the initial `about:blank` page is the current page, load with replacement enabled, + // 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 = 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) { // Synchronously create a new context and navigate it to about:blank. let url = ServoUrl::parse("about:blank").unwrap(); let document = document_from_node(self); - let load_data = LoadData::new(url, - document.get_referrer_policy(), - Some(document.url().clone())); - self.navigate_or_reload_child_browsing_context(Some(load_data), false); + 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, + window.upcast::<GlobalScope>().get_referrer(), + document.get_referrer_policy(), + Some(window.upcast::<GlobalScope>().is_secure_context()), + ); + let browsing_context_id = BrowsingContextId::new(); + let top_level_browsing_context_id = window.window_proxy().top_level_browsing_context_id(); + self.pipeline_id.set(None); + self.pending_pipeline_id.set(None); + self.top_level_browsing_context_id + .set(Some(top_level_browsing_context_id)); + self.browsing_context_id.set(Some(browsing_context_id)); + self.navigate_or_reload_child_browsing_context( + load_data, + NavigationType::InitialAboutBlank, + HistoryEntryReplacement::Disabled, + ); } - pub fn update_pipeline_id(&self, new_pipeline_id: PipelineId) { + fn destroy_nested_browsing_context(&self) { + self.pipeline_id.set(None); + self.pending_pipeline_id.set(None); + self.about_blank_pipeline_id.set(None); + self.top_level_browsing_context_id.set(None); + self.browsing_context_id.set(None); + } + + pub fn update_pipeline_id(&self, new_pipeline_id: PipelineId, reason: UpdatePipelineIdReason) { + if self.pending_pipeline_id.get() != Some(new_pipeline_id) && + reason == UpdatePipelineIdReason::Navigation + { + return; + } + self.pipeline_id.set(Some(new_pipeline_id)); - let mut blocker = self.load_blocker.borrow_mut(); - LoadBlocker::terminate(&mut blocker); + // Only terminate the load blocker if the pipeline id was updated due to a traversal. + // The load blocker will be terminated for a navigation in iframe_load_event_steps. + if reason == UpdatePipelineIdReason::Traversal { + let mut blocker = self.load_blocker.borrow_mut(); + LoadBlocker::terminate(&mut blocker); + } self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + let window = window_from_node(self); + window.reflow(ReflowGoal::Full, ReflowReason::FramedContentChanged); } - fn new_inherited(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> HTMLIFrameElement { + fn new_inherited( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> HTMLIFrameElement { HTMLIFrameElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - frame_id: FrameId::new(), + browsing_context_id: Cell::new(None), + top_level_browsing_context_id: Cell::new(None), pipeline_id: Cell::new(None), + pending_pipeline_id: Cell::new(None), + about_blank_pipeline_id: Cell::new(None), sandbox: Default::default(), sandbox_allowance: Cell::new(None), - load_blocker: DOMRefCell::new(None), + load_blocker: DomRefCell::new(None), visibility: Cell::new(true), } } #[allow(unrooted_must_root)] - pub fn new(local_name: LocalName, - prefix: Option<DOMString>, - document: &Document) -> Root<HTMLIFrameElement> { - Node::reflect_node(box HTMLIFrameElement::new_inherited(local_name, prefix, document), - document, - HTMLIFrameElementBinding::Wrap) + pub fn new( + local_name: LocalName, + prefix: Option<Prefix>, + document: &Document, + ) -> DomRoot<HTMLIFrameElement> { + Node::reflect_node( + Box::new(HTMLIFrameElement::new_inherited( + local_name, prefix, document, + )), + document, + ) } #[inline] @@ -269,26 +441,18 @@ impl HTMLIFrameElement { } #[inline] - pub fn frame_id(&self) -> FrameId { - self.frame_id + pub fn browsing_context_id(&self) -> Option<BrowsingContextId> { + self.browsing_context_id.get() + } + + #[inline] + pub fn top_level_browsing_context_id(&self) -> Option<TopLevelBrowsingContextId> { + self.top_level_browsing_context_id.get() } pub fn change_visibility_status(&self, visibility: bool) { if self.visibility.get() != visibility { self.visibility.set(visibility); - - // Visibility changes are only exposed to Mozbrowser iframes - if self.Mozbrowser() { - self.dispatch_mozbrowser_event(MozBrowserEvent::VisibilityChange(visibility)); - } - } - } - - pub fn set_visible(&self, visible: bool) { - if let Some(pipeline_id) = self.pipeline_id.get() { - let window = window_from_node(self); - let msg = ConstellationMsg::SetVisible(pipeline_id, visible); - window.upcast::<GlobalScope>().constellation_chan().send(msg).unwrap(); } } @@ -296,7 +460,9 @@ impl HTMLIFrameElement { pub fn iframe_load_event_steps(&self, loaded_pipeline: PipelineId) { // TODO(#9592): assert that the load blocker is present at all times when we // can guarantee that it's created for the case of iframe.reload(). - if Some(loaded_pipeline) != self.pipeline_id() { return; } + if Some(loaded_pipeline) != self.pending_pipeline_id.get() { + return; + } // TODO A cross-origin child document would not be easily accessible // from this script thread. It's unclear how to implement @@ -313,211 +479,101 @@ impl HTMLIFrameElement { // TODO Step 5 - unset child document `mut iframe load` flag let window = window_from_node(self); - window.reflow(ReflowGoal::ForDisplay, - ReflowQueryType::NoQuery, - ReflowReason::IFrameLoadEvent); - } - - /// Check whether the iframe has the mozprivatebrowsing attribute set - pub fn privatebrowsing(&self) -> bool { - if self.Mozbrowser() { - let element = self.upcast::<Element>(); - element.has_attribute(&LocalName::from("mozprivatebrowsing")) - } else { - false - } + window.reflow(ReflowGoal::Full, ReflowReason::IFrameLoadEvent); } } pub trait HTMLIFrameElementLayoutMethods { fn pipeline_id(self) -> Option<PipelineId>; - fn get_width(&self) -> LengthOrPercentageOrAuto; - fn get_height(&self) -> LengthOrPercentageOrAuto; + fn browsing_context_id(self) -> Option<BrowsingContextId>; + fn get_width(self) -> LengthOrPercentageOrAuto; + fn get_height(self) -> LengthOrPercentageOrAuto; } -impl HTMLIFrameElementLayoutMethods for LayoutJS<HTMLIFrameElement> { +impl HTMLIFrameElementLayoutMethods for LayoutDom<'_, HTMLIFrameElement> { #[inline] #[allow(unsafe_code)] fn pipeline_id(self) -> Option<PipelineId> { - unsafe { - (*self.unsafe_get()).pipeline_id.get() - } - } - - #[allow(unsafe_code)] - fn get_width(&self) -> LengthOrPercentageOrAuto { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("width")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + unsafe { (*self.unsafe_get()).pipeline_id.get() } } + #[inline] #[allow(unsafe_code)] - fn get_height(&self) -> LengthOrPercentageOrAuto { - unsafe { - (*self.upcast::<Element>().unsafe_get()) - .get_attr_for_layout(&ns!(), &local_name!("height")) - .map(AttrValue::as_dimension) - .cloned() - .unwrap_or(LengthOrPercentageOrAuto::Auto) - } + fn browsing_context_id(self) -> Option<BrowsingContextId> { + unsafe { (*self.unsafe_get()).browsing_context_id.get() } } -} - -#[allow(unsafe_code)] -pub fn build_mozbrowser_custom_event(window: &Window, event: MozBrowserEvent) -> Root<CustomEvent> { - // TODO(gw): Support mozbrowser event types that have detail which is not a string. - // See https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API - // for a list of mozbrowser events. - let cx = window.get_cx(); - let _ac = JSAutoCompartment::new(cx, window.reflector().get_jsobject().get()); - rooted!(in(cx) let mut detail = UndefinedValue()); - let event_name = Atom::from(event.name()); - unsafe { build_mozbrowser_event_detail(event, cx, detail.handle_mut()); } - CustomEvent::new(window.upcast(), - event_name, - true, - true, - detail.handle()) -} -#[allow(unsafe_code)] -unsafe fn build_mozbrowser_event_detail(event: MozBrowserEvent, - cx: *mut JSContext, - rval: MutableHandleValue) { - match event { - MozBrowserEvent::AsyncScroll | MozBrowserEvent::Close | MozBrowserEvent::ContextMenu | - MozBrowserEvent::LoadEnd | MozBrowserEvent::LoadStart | - MozBrowserEvent::Connected | MozBrowserEvent::OpenSearch | - MozBrowserEvent::UsernameAndPasswordRequired => { - rval.set(NullValue()); - } - MozBrowserEvent::Error(error_type, description, report) => { - BrowserElementErrorEventDetail { - type_: Some(DOMString::from(error_type.name())), - description: Some(DOMString::from(description)), - report: Some(DOMString::from(report)), - version: Some(DOMString::from_string(servo_version())), - }.to_jsval(cx, rval); - }, - MozBrowserEvent::SecurityChange(https_state) => { - BrowserElementSecurityChangeDetail { - // https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowsersecuritychange - state: Some(DOMString::from(match https_state { - HttpsState::Modern => "secure", - HttpsState::Deprecated => "broken", - HttpsState::None => "insecure", - }.to_owned())), - // FIXME - Not supported yet: - trackingContent: None, - mixedContent: None, - trackingState: None, - extendedValidation: None, - mixedState: None, - }.to_jsval(cx, rval); - } - MozBrowserEvent::TitleChange(ref string) => { - string.to_jsval(cx, rval); - } - MozBrowserEvent::LocationChange(url, can_go_back, can_go_forward) => { - BrowserElementLocationChangeEventDetail { - url: Some(DOMString::from(url)), - canGoBack: Some(can_go_back), - canGoForward: Some(can_go_forward), - }.to_jsval(cx, rval); - } - MozBrowserEvent::OpenTab(url) => { - BrowserElementOpenTabEventDetail { - url: Some(DOMString::from(url)), - }.to_jsval(cx, rval); - } - MozBrowserEvent::OpenWindow(url, target, features) => { - BrowserElementOpenWindowEventDetail { - url: Some(DOMString::from(url)), - target: target.map(DOMString::from), - features: features.map(DOMString::from), - }.to_jsval(cx, rval); - } - MozBrowserEvent::IconChange(rel, href, sizes) => { - BrowserElementIconChangeEventDetail { - rel: Some(DOMString::from(rel)), - href: Some(DOMString::from(href)), - sizes: Some(DOMString::from(sizes)), - }.to_jsval(cx, rval); - } - MozBrowserEvent::ShowModalPrompt(prompt_type, title, message, return_value) => { - BrowserShowModalPromptEventDetail { - promptType: Some(DOMString::from(prompt_type)), - title: Some(DOMString::from(title)), - message: Some(DOMString::from(message)), - returnValue: Some(DOMString::from(return_value)), - }.to_jsval(cx, rval) - } - MozBrowserEvent::VisibilityChange(visibility) => { - BrowserElementVisibilityChangeEventDetail { - visible: Some(visibility), - }.to_jsval(cx, rval); - } + fn get_width(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("width")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) } -} -pub fn Navigate(iframe: &HTMLIFrameElement, direction: TraversalDirection) -> ErrorResult { - if iframe.Mozbrowser() { - if iframe.upcast::<Node>().is_in_doc_with_browsing_context() { - let window = window_from_node(iframe); - let msg = ConstellationMsg::TraverseHistory(iframe.pipeline_id(), direction); - window.upcast::<GlobalScope>().constellation_chan().send(msg).unwrap(); - } - - Ok(()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) + fn get_height(self) -> LengthOrPercentageOrAuto { + self.upcast::<Element>() + .get_attr_for_layout(&ns!(), &local_name!("height")) + .map(AttrValue::as_dimension) + .cloned() + .unwrap_or(LengthOrPercentageOrAuto::Auto) } } impl HTMLIFrameElementMethods for HTMLIFrameElement { // https://html.spec.whatwg.org/multipage/#dom-iframe-src - fn Src(&self) -> DOMString { - self.upcast::<Element>().get_string_attribute(&local_name!("src")) - } + make_url_getter!(Src, "src"); // https://html.spec.whatwg.org/multipage/#dom-iframe-src - fn SetSrc(&self, src: DOMString) { - self.upcast::<Element>().set_url_attribute(&local_name!("src"), src) - } + make_url_setter!(SetSrc, "src"); + + // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc + make_getter!(Srcdoc, "srcdoc"); + + // https://html.spec.whatwg.org/multipage/#dom-iframe-srcdoc + make_setter!(SetSrcdoc, "srcdoc"); // https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox - fn Sandbox(&self) -> Root<DOMTokenList> { - self.sandbox.or_init(|| DOMTokenList::new(self.upcast::<Element>(), &local_name!("sandbox"))) + fn Sandbox(&self) -> DomRoot<DOMTokenList> { + self.sandbox.or_init(|| { + DOMTokenList::new( + self.upcast::<Element>(), + &local_name!("sandbox"), + Some(vec![ + Atom::from("allow-same-origin"), + Atom::from("allow-forms"), + Atom::from("allow-pointer-lock"), + Atom::from("allow-popups"), + Atom::from("allow-scripts"), + Atom::from("allow-top-navigation"), + ]), + ) + }) } // https://html.spec.whatwg.org/multipage/#dom-iframe-contentwindow - fn GetContentWindow(&self) -> Option<Root<BrowsingContext>> { - self.pipeline_id.get().and_then(|_| ScriptThread::find_browsing_context(self.frame_id)) + fn GetContentWindow(&self) -> Option<DomRoot<WindowProxy>> { + self.browsing_context_id + .get() + .and_then(|browsing_context_id| ScriptThread::find_window_proxy(browsing_context_id)) } // https://html.spec.whatwg.org/multipage/#dom-iframe-contentdocument // https://html.spec.whatwg.org/multipage/#concept-bcc-content-document - fn GetContentDocument(&self) -> Option<Root<Document>> { + fn GetContentDocument(&self) -> Option<DomRoot<Document>> { // Step 1. - let pipeline_id = match self.pipeline_id.get() { - None => return None, - Some(pipeline_id) => pipeline_id, - }; + let pipeline_id = self.pipeline_id.get()?; + // Step 2-3. // Note that this lookup will fail if the document is dissimilar-origin, // so we should return None in that case. - let document = match ScriptThread::find_document(pipeline_id) { - None => return None, - Some(document) => document, - }; + let document = ScriptThread::find_document(pipeline_id)?; + // Step 4. - let current = GlobalScope::current().as_window().Document(); + let current = GlobalScope::current() + .expect("No current global object") + .as_window() + .Document(); if !current.origin().same_origin_domain(document.origin()) { return None; } @@ -525,83 +581,11 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement { Some(document) } - // Experimental mozbrowser implementation is based on the webidl - // present in the gecko source tree, and the documentation here: - // https://developer.mozilla.org/en-US/docs/Web/API/Using_the_Browser_API - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-mozbrowser - fn Mozbrowser(&self) -> bool { - if window_from_node(self).is_mozbrowser() { - let element = self.upcast::<Element>(); - element.has_attribute(&local_name!("mozbrowser")) - } else { - false - } - } - - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-mozbrowser - fn SetMozbrowser(&self, value: bool) { - let element = self.upcast::<Element>(); - element.set_bool_attribute(&local_name!("mozbrowser"), value); - } - // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen make_bool_getter!(AllowFullscreen, "allowfullscreen"); // https://html.spec.whatwg.org/multipage/#attr-iframe-allowfullscreen make_bool_setter!(SetAllowFullscreen, "allowfullscreen"); - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/goBack - fn GoBack(&self) -> ErrorResult { - Navigate(self, TraversalDirection::Back(1)) - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/goForward - fn GoForward(&self) -> ErrorResult { - Navigate(self, TraversalDirection::Forward(1)) - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/reload - fn Reload(&self, _hard_reload: bool) -> ErrorResult { - if self.Mozbrowser() { - if self.upcast::<Node>().is_in_doc_with_browsing_context() { - self.navigate_or_reload_child_browsing_context(None, true); - } - Ok(()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) - } - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/setVisible - fn SetVisible(&self, visible: bool) -> ErrorResult { - if self.Mozbrowser() { - self.set_visible(visible); - Ok(()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) - } - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/getVisible - fn GetVisible(&self) -> Fallible<bool> { - if self.Mozbrowser() { - Ok(self.visibility.get()) - } else { - debug!(concat!("this frame is not mozbrowser: mozbrowser attribute missing, or not a top", - "level window, or mozbrowser preference not set (use --pref dom.mozbrowser.enabled)")); - Err(Error::NotSupported) - } - } - - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/stop - fn Stop(&self) -> ErrorResult { - Err(Error::NotSupported) - } - // https://html.spec.whatwg.org/multipage/#dom-dim-width make_getter!(Width, "width"); // https://html.spec.whatwg.org/multipage/#dom-dim-width @@ -617,46 +601,58 @@ impl HTMLIFrameElementMethods for HTMLIFrameElement { // https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:attr-iframe-frameborder make_setter!(SetFrameBorder, "frameborder"); - // check-tidy: no specs after this line - fn SetMozprivatebrowsing(&self, value: bool) { - let element = self.upcast::<Element>(); - element.set_bool_attribute(&LocalName::from("mozprivatebrowsing"), value); - } + // https://html.spec.whatwg.org/multipage/#dom-iframe-name + // A child browsing context checks the name of its iframe only at the time + // it is created; subsequent name sets have no special effect. + make_atomic_setter!(SetName, "name"); - fn Mozprivatebrowsing(&self) -> bool { - if window_from_node(self).is_mozbrowser() { - let element = self.upcast::<Element>(); - element.has_attribute(&LocalName::from("mozprivatebrowsing")) - } else { - false - } - } + // https://html.spec.whatwg.org/multipage/#dom-iframe-name + // This is specified as reflecting the name content attribute of the + // element, not the name of the child browsing context. + make_getter!(Name, "name"); } impl VirtualMethods for HTMLIFrameElement { - fn super_type(&self) -> Option<&VirtualMethods> { - Some(self.upcast::<HTMLElement>() as &VirtualMethods) + fn super_type(&self) -> Option<&dyn VirtualMethods> { + Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); match attr.local_name() { &local_name!("sandbox") => { - self.sandbox_allowance.set(mutation.new_value(attr).map(|value| { - let mut modes = ALLOW_NOTHING; - for token in value.as_tokens() { - modes |= match &*token.to_ascii_lowercase() { - "allow-same-origin" => ALLOW_SAME_ORIGIN, - "allow-forms" => ALLOW_FORMS, - "allow-pointer-lock" => ALLOW_POINTER_LOCK, - "allow-popups" => ALLOW_POPUPS, - "allow-scripts" => ALLOW_SCRIPTS, - "allow-top-navigation" => ALLOW_TOP_NAVIGATION, - _ => ALLOW_NOTHING - }; - } - modes - })); + self.sandbox_allowance + .set(mutation.new_value(attr).map(|value| { + let mut modes = SandboxAllowance::ALLOW_NOTHING; + for token in value.as_tokens() { + modes |= match &*token.to_ascii_lowercase() { + "allow-same-origin" => SandboxAllowance::ALLOW_SAME_ORIGIN, + "allow-forms" => SandboxAllowance::ALLOW_FORMS, + "allow-pointer-lock" => SandboxAllowance::ALLOW_POINTER_LOCK, + "allow-popups" => SandboxAllowance::ALLOW_POPUPS, + "allow-scripts" => SandboxAllowance::ALLOW_SCRIPTS, + "allow-top-navigation" => SandboxAllowance::ALLOW_TOP_NAVIGATION, + _ => SandboxAllowance::ALLOW_NOTHING, + }; + } + modes + })); + }, + &local_name!("srcdoc") => { + // https://html.spec.whatwg.org/multipage/#the-iframe-element:the-iframe-element-9 + // "Whenever an iframe element with a non-null nested browsing context has its + // srcdoc attribute set, changed, or removed, the user agent must process the + // iframe attributes." + // but we can't check that directly, since the child browsing context + // may be in a different script thread. Instead, we check to see if the parent + // is in a document tree and has a browsing context, which is what causes + // the child browsing context to be created. + + // trigger the processing of iframe attributes whenever "srcdoc" attribute is set, changed or removed + if self.upcast::<Node>().is_connected_with_browsing_context() { + debug!("iframe srcdoc modified while in browsing context."); + self.process_the_iframe_attributes(ProcessingMode::NotFirstTime); + } }, &local_name!("src") => { // https://html.spec.whatwg.org/multipage/#the-iframe-element @@ -664,11 +660,11 @@ impl VirtualMethods for HTMLIFrameElement { // but with no srcdoc attribute specified has its src attribute set, changed, or removed, // the user agent must process the iframe attributes," // but we can't check that directly, since the child browsing context - // may be in a different script thread. Instread, we check to see if the parent + // may be in a different script thread. Instead, we check to see if the parent // is in a document tree and has a browsing context, which is what causes // the child browsing context to be created. - if self.upcast::<Node>().is_in_doc_with_browsing_context() { - debug!("iframe {} src set while in browsing context.", self.frame_id); + if self.upcast::<Node>().is_connected_with_browsing_context() { + debug!("iframe src set while in browsing context."); self.process_the_iframe_attributes(ProcessingMode::NotFirstTime); } }, @@ -681,27 +677,35 @@ impl VirtualMethods for HTMLIFrameElement { &local_name!("sandbox") => AttrValue::from_serialized_tokenlist(value.into()), &local_name!("width") => AttrValue::from_dimension(value.into()), &local_name!("height") => AttrValue::from_dimension(value.into()), - _ => self.super_type().unwrap().parse_plain_attribute(name, value), + _ => self + .super_type() + .unwrap() + .parse_plain_attribute(name, value), } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); - } - - // https://html.spec.whatwg.org/multipage/#the-iframe-element - // "When an iframe element is inserted into a document that has - // a browsing context, the user agent must create a new - // browsing context, set the element's nested browsing context - // to the newly-created browsing context, and then process the - // iframe attributes for the "first time"." - if self.upcast::<Node>().is_in_doc_with_browsing_context() { - debug!("iframe {} bound to browsing context.", self.frame_id); - debug_assert!(tree_in_doc, "is_in_doc_with_bc, but not tree_in_doc"); - self.create_nested_browsing_context(); - self.process_the_iframe_attributes(ProcessingMode::FirstTime); - } + s.bind_to_tree(context); + } + + let tree_connected = context.tree_connected; + let iframe = Trusted::new(self); + document_from_node(self).add_delayed_task(task!(IFrameDelayedInitialize: move || { + let this = iframe.root(); + // https://html.spec.whatwg.org/multipage/#the-iframe-element + // "When an iframe element is inserted into a document that has + // a browsing context, the user agent must create a new + // browsing context, set the element's nested browsing context + // to the newly-created browsing context, and then process the + // iframe attributes for the "first time"." + if this.upcast::<Node>().is_connected_with_browsing_context() { + debug!("iframe bound to browsing context."); + debug_assert!(tree_connected, "is_connected_with_bc, but not tree_connected"); + this.create_nested_browsing_context(); + this.process_the_iframe_attributes(ProcessingMode::FirstTime); + } + })); } fn unbind_from_tree(&self, context: &UnbindContext) { @@ -711,24 +715,41 @@ impl VirtualMethods for HTMLIFrameElement { LoadBlocker::terminate(&mut blocker); // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded - debug!("Unbinding frame {}.", self.frame_id); let window = window_from_node(self); - let (sender, receiver) = ipc::channel().unwrap(); + let (sender, receiver) = + ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap(); // Ask the constellation to remove the iframe, and tell us the // pipeline ids of the closed pipelines. - let msg = ConstellationMsg::RemoveIFrame(self.frame_id, sender); - window.upcast::<GlobalScope>().constellation_chan().send(msg).unwrap(); + let browsing_context_id = match self.browsing_context_id() { + None => return warn!("Unbinding already unbound iframe."), + Some(id) => id, + }; + debug!("Unbinding frame {}.", browsing_context_id); + + let msg = ScriptMsg::RemoveIFrame(browsing_context_id, sender); + window + .upcast::<GlobalScope>() + .script_to_constellation_chan() + .send(msg) + .unwrap(); let exited_pipeline_ids = receiver.recv().unwrap(); // The spec for discarding is synchronous, // so we need to discard the browsing contexts now, rather than // when the `PipelineExit` message arrives. for exited_pipeline_id in exited_pipeline_ids { + // https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded if let Some(exited_document) = ScriptThread::find_document(exited_pipeline_id) { - exited_document.window().browsing_context().discard(); + debug!( + "Discarding browsing context for pipeline {}", + exited_pipeline_id + ); + let exited_window = exited_document.window(); + exited_window.discard_browsing_context(); for exited_iframe in exited_document.iter_iframes() { - exited_iframe.pipeline_id.set(None); + debug!("Discarding nested browsing context"); + exited_iframe.destroy_nested_browsing_context(); } } } @@ -738,27 +759,6 @@ impl VirtualMethods for HTMLIFrameElement { // the load doesn't think that it's a navigation, but instead // a new iframe. Without this, the constellation gets very // confused. - self.pipeline_id.set(None); - } -} - -struct IFrameLoadEventSteps { - frame_element: Trusted<HTMLIFrameElement>, - pipeline_id: PipelineId, -} - -impl IFrameLoadEventSteps { - fn new(frame_element: &HTMLIFrameElement) -> IFrameLoadEventSteps { - IFrameLoadEventSteps { - frame_element: Trusted::new(frame_element), - pipeline_id: frame_element.pipeline_id().unwrap(), - } - } -} - -impl Runnable for IFrameLoadEventSteps { - fn handler(self: Box<IFrameLoadEventSteps>) { - let this = self.frame_element.root(); - this.iframe_load_event_steps(self.pipeline_id); + self.destroy_nested_browsing_context(); } } |