mirror of
https://github.com/servo/servo.git
synced 2025-06-11 10:00:18 +00:00
This doesn't change any functionality, but it's the first step towards removing SubpageId. Adding this change now will allow us to gradually change over code referencing subpage id rather than in one massive PR. Introduces a namespace for pipeline ID generation - there is a namespace for the constellation thread, and one per script thread.
441 lines
16 KiB
Rust
441 lines
16 KiB
Rust
/* 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 dom::attr::{Attr, AttrHelpersForLayout, AttrValue};
|
|
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding;
|
|
use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
|
|
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
|
use dom::bindings::codegen::InheritTypes::HTMLIFrameElementDerived;
|
|
use dom::bindings::codegen::InheritTypes::{ElementCast, EventCast, NodeCast};
|
|
use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLElementCast};
|
|
use dom::bindings::conversions::ToJSValConvertible;
|
|
use dom::bindings::error::Error::NotSupported;
|
|
use dom::bindings::error::{ErrorResult, Fallible};
|
|
use dom::bindings::global::GlobalRef;
|
|
use dom::bindings::js::{Root};
|
|
use dom::bindings::utils::Reflectable;
|
|
use dom::customevent::CustomEvent;
|
|
use dom::document::Document;
|
|
use dom::element::{self, AttributeMutation, ElementTypeId};
|
|
use dom::eventtarget::{EventTarget, EventTargetTypeId};
|
|
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
|
|
use dom::node::{Node, NodeTypeId, window_from_node};
|
|
use dom::urlhelper::UrlHelper;
|
|
use dom::virtualmethods::VirtualMethods;
|
|
use dom::window::Window;
|
|
use js::jsapi::{JSAutoCompartment, JSAutoRequest, RootedValue};
|
|
use js::jsval::UndefinedValue;
|
|
use msg::constellation_msg::IFrameSandboxState::{IFrameSandboxed, IFrameUnsandboxed};
|
|
use msg::constellation_msg::Msg as ConstellationMsg;
|
|
use msg::constellation_msg::{ConstellationChan, IframeLoadInfo, MozBrowserEvent};
|
|
use msg::constellation_msg::{NavigationDirection, PipelineId, SubpageId};
|
|
use page::IterablePage;
|
|
use std::ascii::AsciiExt;
|
|
use std::borrow::ToOwned;
|
|
use std::cell::Cell;
|
|
use string_cache::Atom;
|
|
use url::{Url, UrlParser};
|
|
use util::prefs;
|
|
use util::str::DOMString;
|
|
use util::str::{self, LengthOrPercentageOrAuto};
|
|
|
|
pub fn mozbrowser_enabled() -> bool {
|
|
prefs::get_pref("dom.mozbrowser.enabled").as_boolean().unwrap_or(false)
|
|
}
|
|
|
|
#[derive(HeapSizeOf)]
|
|
enum SandboxAllowance {
|
|
AllowNothing = 0x00,
|
|
AllowSameOrigin = 0x01,
|
|
AllowTopNavigation = 0x02,
|
|
AllowForms = 0x04,
|
|
AllowScripts = 0x08,
|
|
AllowPointerLock = 0x10,
|
|
AllowPopups = 0x20
|
|
}
|
|
|
|
#[dom_struct]
|
|
pub struct HTMLIFrameElement {
|
|
htmlelement: HTMLElement,
|
|
pipeline_id: Cell<Option<PipelineId>>,
|
|
subpage_id: Cell<Option<SubpageId>>,
|
|
containing_page_pipeline_id: Cell<Option<PipelineId>>,
|
|
sandbox: Cell<Option<u8>>,
|
|
}
|
|
|
|
impl HTMLIFrameElementDerived for EventTarget {
|
|
fn is_htmliframeelement(&self) -> bool {
|
|
*self.type_id() ==
|
|
EventTargetTypeId::Node(
|
|
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement)))
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl HTMLIFrameElement {
|
|
pub fn is_sandboxed(&self) -> bool {
|
|
self.sandbox.get().is_some()
|
|
}
|
|
|
|
pub fn get_url(&self) -> Option<Url> {
|
|
let element = ElementCast::from_ref(self);
|
|
element.get_attribute(&ns!(""), &atom!("src")).and_then(|src| {
|
|
let url = src.r().value();
|
|
if url.is_empty() {
|
|
None
|
|
} else {
|
|
let window = window_from_node(self);
|
|
UrlParser::new().base_url(&window.r().get_url())
|
|
.parse(&url).ok()
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn generate_new_subpage_id(&self) -> (SubpageId, Option<SubpageId>) {
|
|
self.pipeline_id.set(Some(PipelineId::new()));
|
|
|
|
let old_subpage_id = self.subpage_id.get();
|
|
let win = window_from_node(self);
|
|
let subpage_id = win.r().get_next_subpage_id();
|
|
self.subpage_id.set(Some(subpage_id));
|
|
(subpage_id, old_subpage_id)
|
|
}
|
|
|
|
pub fn navigate_child_browsing_context(&self, url: Url) {
|
|
let sandboxed = if self.is_sandboxed() {
|
|
IFrameSandboxed
|
|
} else {
|
|
IFrameUnsandboxed
|
|
};
|
|
|
|
let window = window_from_node(self);
|
|
let window = window.r();
|
|
let (new_subpage_id, old_subpage_id) = self.generate_new_subpage_id();
|
|
let new_pipeline_id = self.pipeline_id.get().unwrap();
|
|
|
|
self.containing_page_pipeline_id.set(Some(window.pipeline()));
|
|
|
|
let ConstellationChan(ref chan) = window.constellation_chan();
|
|
let load_info = IframeLoadInfo {
|
|
url: url,
|
|
containing_pipeline_id: window.pipeline(),
|
|
new_subpage_id: new_subpage_id,
|
|
old_subpage_id: old_subpage_id,
|
|
new_pipeline_id: new_pipeline_id,
|
|
sandbox: sandboxed,
|
|
};
|
|
chan.send(ConstellationMsg::ScriptLoadedURLInIFrame(load_info)).unwrap();
|
|
|
|
if mozbrowser_enabled() {
|
|
// https://developer.mozilla.org/en-US/docs/Web/Events/mozbrowserloadstart
|
|
self.dispatch_mozbrowser_event(MozBrowserEvent::LoadStart);
|
|
}
|
|
}
|
|
|
|
pub fn process_the_iframe_attributes(&self) {
|
|
let url = match self.get_url() {
|
|
Some(url) => url.clone(),
|
|
None => Url::parse("about:blank").unwrap(),
|
|
};
|
|
|
|
self.navigate_child_browsing_context(url);
|
|
}
|
|
|
|
pub fn dispatch_mozbrowser_event(&self, event: MozBrowserEvent) {
|
|
// 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.
|
|
assert!(mozbrowser_enabled());
|
|
|
|
if self.Mozbrowser() {
|
|
let window = window_from_node(self);
|
|
let cx = window.r().get_cx();
|
|
let _ar = JSAutoRequest::new(cx);
|
|
let _ac = JSAutoCompartment::new(cx, window.reflector().get_jsobject().get());
|
|
let mut detail = RootedValue::new(cx, UndefinedValue());
|
|
event.detail().to_jsval(cx, detail.handle_mut());
|
|
let custom_event = CustomEvent::new(GlobalRef::Window(window.r()),
|
|
event.name().to_owned(),
|
|
true,
|
|
true,
|
|
detail.handle());
|
|
let target = EventTargetCast::from_ref(self);
|
|
let event = EventCast::from_ref(custom_event.r());
|
|
event.fire(target);
|
|
}
|
|
}
|
|
|
|
pub fn update_subpage_id(&self, new_subpage_id: SubpageId) {
|
|
self.subpage_id.set(Some(new_subpage_id));
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
pub fn get_width(&self) -> LengthOrPercentageOrAuto {
|
|
unsafe {
|
|
element::get_attr_for_layout(ElementCast::from_ref(&*self),
|
|
&ns!(""),
|
|
&atom!("width")).map(|attribute| {
|
|
str::parse_length(&**attribute.value_for_layout())
|
|
}).unwrap_or(LengthOrPercentageOrAuto::Auto)
|
|
}
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
pub fn get_height(&self) -> LengthOrPercentageOrAuto {
|
|
unsafe {
|
|
element::get_attr_for_layout(ElementCast::from_ref(&*self),
|
|
&ns!(""),
|
|
&atom!("height")).map(|attribute| {
|
|
str::parse_length(&**attribute.value_for_layout())
|
|
}).unwrap_or(LengthOrPercentageOrAuto::Auto)
|
|
}
|
|
}
|
|
|
|
fn new_inherited(localName: DOMString,
|
|
prefix: Option<DOMString>,
|
|
document: &Document) -> HTMLIFrameElement {
|
|
HTMLIFrameElement {
|
|
htmlelement:
|
|
HTMLElement::new_inherited(HTMLElementTypeId::HTMLIFrameElement, localName, prefix, document),
|
|
pipeline_id: Cell::new(None),
|
|
subpage_id: Cell::new(None),
|
|
containing_page_pipeline_id: Cell::new(None),
|
|
sandbox: Cell::new(None),
|
|
}
|
|
}
|
|
|
|
#[allow(unrooted_must_root)]
|
|
pub fn new(localName: DOMString,
|
|
prefix: Option<DOMString>,
|
|
document: &Document) -> Root<HTMLIFrameElement> {
|
|
let element = HTMLIFrameElement::new_inherited(localName, prefix, document);
|
|
Node::reflect_node(box element, document, HTMLIFrameElementBinding::Wrap)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn containing_page_pipeline_id(&self) -> Option<PipelineId> {
|
|
self.containing_page_pipeline_id.get()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn subpage_id(&self) -> Option<SubpageId> {
|
|
self.subpage_id.get()
|
|
}
|
|
}
|
|
|
|
pub fn Navigate(iframe: &HTMLIFrameElement, direction: NavigationDirection) -> Fallible<()> {
|
|
if iframe.Mozbrowser() {
|
|
let node = NodeCast::from_ref(iframe);
|
|
if node.is_in_doc() {
|
|
let window = window_from_node(iframe);
|
|
let window = window.r();
|
|
|
|
let pipeline_info = Some((iframe.containing_page_pipeline_id().unwrap(),
|
|
iframe.subpage_id().unwrap()));
|
|
let ConstellationChan(ref chan) = window.constellation_chan();
|
|
let msg = ConstellationMsg::Navigate(pipeline_info, direction);
|
|
chan.send(msg).unwrap();
|
|
}
|
|
|
|
Ok(())
|
|
} else {
|
|
debug!("this frame is not mozbrowser (or experimental_enabled is false)");
|
|
Err(NotSupported)
|
|
}
|
|
}
|
|
|
|
impl HTMLIFrameElementMethods for HTMLIFrameElement {
|
|
// https://html.spec.whatwg.org/multipage/#dom-iframe-src
|
|
fn Src(&self) -> DOMString {
|
|
let element = ElementCast::from_ref(self);
|
|
element.get_string_attribute(&atom!("src"))
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-iframe-src
|
|
fn SetSrc(&self, src: DOMString) {
|
|
let element = ElementCast::from_ref(self);
|
|
element.set_url_attribute(&atom!("src"), src)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox
|
|
fn Sandbox(&self) -> DOMString {
|
|
let element = ElementCast::from_ref(self);
|
|
element.get_string_attribute(&atom!("sandbox"))
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-iframe-sandbox
|
|
fn SetSandbox(&self, sandbox: DOMString) {
|
|
let element = ElementCast::from_ref(self);
|
|
element.set_tokenlist_attribute(&atom!("sandbox"), sandbox);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-iframe-contentwindow
|
|
fn GetContentWindow(&self) -> Option<Root<Window>> {
|
|
self.subpage_id.get().and_then(|subpage_id| {
|
|
let window = window_from_node(self);
|
|
let window = window.r();
|
|
let children = window.page().children.borrow();
|
|
children.iter().find(|page| {
|
|
let window = page.window();
|
|
window.r().subpage() == Some(subpage_id)
|
|
}).map(|page| page.window())
|
|
})
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-iframe-contentdocument
|
|
fn GetContentDocument(&self) -> Option<Root<Document>> {
|
|
self.GetContentWindow().and_then(|window| {
|
|
let self_url = match self.get_url() {
|
|
Some(self_url) => self_url,
|
|
None => return None,
|
|
};
|
|
let win_url = window_from_node(self).r().get_url();
|
|
|
|
if UrlHelper::SameOrigin(&self_url, &win_url) {
|
|
Some(window.r().Document())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
// 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
|
|
|
|
// TODO(gw): Use experimental codegen when it is available to avoid
|
|
// exposing these APIs. See https://github.com/servo/servo/issues/5264.
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-mozbrowser
|
|
fn Mozbrowser(&self) -> bool {
|
|
if mozbrowser_enabled() {
|
|
let element = ElementCast::from_ref(self);
|
|
element.has_attribute(&Atom::from_slice("mozbrowser"))
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-mozbrowser
|
|
fn SetMozbrowser(&self, value: bool) -> ErrorResult {
|
|
if mozbrowser_enabled() {
|
|
let element = ElementCast::from_ref(self);
|
|
element.set_bool_attribute(&Atom::from_slice("mozbrowser"), value);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/goBack
|
|
fn GoBack(&self) -> Fallible<()> {
|
|
Navigate(self, NavigationDirection::Back)
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/goForward
|
|
fn GoForward(&self) -> Fallible<()> {
|
|
Navigate(self, NavigationDirection::Forward)
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/reload
|
|
fn Reload(&self, _hardReload: bool) -> Fallible<()> {
|
|
Err(NotSupported)
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/stop
|
|
fn Stop(&self) -> Fallible<()> {
|
|
Err(NotSupported)
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-dim-width
|
|
make_getter!(Width);
|
|
// https://html.spec.whatwg.org/multipage/#dom-dim-width
|
|
make_setter!(SetWidth, "width");
|
|
|
|
// https://html.spec.whatwg.org/multipage/#dom-dim-height
|
|
make_getter!(Height);
|
|
// https://html.spec.whatwg.org/multipage/#dom-dim-height
|
|
make_setter!(SetHeight, "height");
|
|
}
|
|
|
|
impl VirtualMethods for HTMLIFrameElement {
|
|
fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
|
|
let htmlelement: &HTMLElement = HTMLElementCast::from_ref(self);
|
|
Some(htmlelement as &VirtualMethods)
|
|
}
|
|
|
|
fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
|
|
self.super_type().unwrap().attribute_mutated(attr, mutation);
|
|
match attr.local_name() {
|
|
&atom!(sandbox) => {
|
|
self.sandbox.set(mutation.new_value(attr).map(|value| {
|
|
let mut modes = SandboxAllowance::AllowNothing as u8;
|
|
for token in value.as_tokens() {
|
|
modes |= match &*token.to_ascii_lowercase() {
|
|
"allow-same-origin" => SandboxAllowance::AllowSameOrigin,
|
|
"allow-forms" => SandboxAllowance::AllowForms,
|
|
"allow-pointer-lock" => SandboxAllowance::AllowPointerLock,
|
|
"allow-popups" => SandboxAllowance::AllowPopups,
|
|
"allow-scripts" => SandboxAllowance::AllowScripts,
|
|
"allow-top-navigation" => SandboxAllowance::AllowTopNavigation,
|
|
_ => SandboxAllowance::AllowNothing
|
|
} as u8;
|
|
}
|
|
modes
|
|
}));
|
|
},
|
|
&atom!(src) => {
|
|
if let AttributeMutation::Set(_) = mutation {
|
|
if NodeCast::from_ref(self).is_in_doc() {
|
|
self.process_the_iframe_attributes();
|
|
}
|
|
}
|
|
},
|
|
_ => {},
|
|
}
|
|
}
|
|
|
|
fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
|
|
match name {
|
|
&atom!("sandbox") => AttrValue::from_serialized_tokenlist(value),
|
|
_ => self.super_type().unwrap().parse_plain_attribute(name, value),
|
|
}
|
|
}
|
|
|
|
fn bind_to_tree(&self, tree_in_doc: bool) {
|
|
if let Some(ref s) = self.super_type() {
|
|
s.bind_to_tree(tree_in_doc);
|
|
}
|
|
|
|
if tree_in_doc {
|
|
self.process_the_iframe_attributes();
|
|
}
|
|
}
|
|
|
|
fn unbind_from_tree(&self, tree_in_doc: bool) {
|
|
if let Some(ref s) = self.super_type() {
|
|
s.unbind_from_tree(tree_in_doc);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/multipage/#a-browsing-context-is-discarded
|
|
match (self.containing_page_pipeline_id(), self.subpage_id()) {
|
|
(Some(containing_pipeline_id), Some(subpage_id)) => {
|
|
let window = window_from_node(self);
|
|
let window = window.r();
|
|
|
|
let ConstellationChan(ref chan) = window.constellation_chan();
|
|
let msg = ConstellationMsg::RemoveIFrame(containing_pipeline_id,
|
|
subpage_id);
|
|
chan.send(msg).unwrap();
|
|
|
|
// Resetting the subpage id to None is required here so that
|
|
// if this iframe is subsequently re-added to the document
|
|
// the load doesn't think that it's a navigation, but instead
|
|
// a new iframe. Without this, the constellation gets very
|
|
// confused.
|
|
self.subpage_id.set(None);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|