servo/components/script/dom/htmliframeelement.rs
Anthony Ramine 7d6ea83479 Explicitly customise flags of new nodes where needed
Given codegen now generates the various TypeId enums, it seems pointless to
still have to write their respective values in every DOM struct inheriting from
Node just to set the initial IS_IN_DOC flag in Document and IN_ENABLED_STATE in
form controls.
2015-10-15 17:30:41 +02:00

438 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::{ElementCast, EventCast, EventTargetCast};
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast};
use dom::bindings::conversions::ToJSValConvertible;
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{Root, LayoutJS};
use dom::bindings::utils::Reflectable;
use dom::customevent::CustomEvent;
use dom::document::Document;
use dom::element::{self, AttributeMutation};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, 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 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(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 trait HTMLIFrameElementLayoutMethods {
fn pipeline_id(self) -> Option<PipelineId>;
}
impl HTMLIFrameElementLayoutMethods for LayoutJS<HTMLIFrameElement> {
#[inline]
#[allow(unsafe_code)]
fn pipeline_id(self) -> Option<PipelineId> {
unsafe {
(*self.unsafe_get()).pipeline_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(Error::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(Error::NotSupported)
}
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/stop
fn Stop(&self) -> Fallible<()> {
Err(Error::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
if let Some(pipeline_id) = self.pipeline_id.get() {
let window = window_from_node(self);
let window = window.r();
let ConstellationChan(ref chan) = window.constellation_chan();
let msg = ConstellationMsg::RemoveIFrame(pipeline_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);
self.pipeline_id.set(None);
}
}
}