Files
tubestation/servo/components/script/dom/htmlformelement.rs
Josh Matthews b23f44d460 servo: Merge #4057 - Enable refcounting arbitrary DOM types (from jdm:refcountdom); r=Ms2ger
This replaces the specialized TrustedXHRAddress and TrustedWorkerAddress code that was used for the same purpose. A non-zero refcount pins the given DOM object's reflector and prevents it from being GCed even when there are no other outstanding references visible to SpiderMonkey. This will enable us to implement asynchronous operations that refer to particular DOM objects (such as "queue a task to fire a simple event named load at the iframe element" from the spec) safely and conveniently, and paves the way for things like asynchronous network responses.

Some concerns about the resulting size of XHR progress messages have been expressed, but I believe optimizations to reduce that can be implemented in subsequent PRs.

r? @Ms2ger - note in particular the changes to the worker lifetime code. I couldn't figure out how to achieve an identical lifetime to the previous addref/release pairing, and I also was having trouble figuring out why the existing setup was safe. The new implementation now holds the main script task Worker object alive via the TrustedWorkerAddress field in the dedicated worker global scope, which is a significant difference.

Source-Repo: https://github.com/servo/servo
Source-Revision: 2c259f477c41331e66beab8bda865971982a1ff4
2014-12-29 11:57:45 -07:00

518 lines
20 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::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
use dom::bindings::codegen::Bindings::HTMLFormElementBinding;
use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLFormElementDerived, NodeCast};
use dom::bindings::codegen::InheritTypes::{HTMLInputElementCast, HTMLTextAreaElementCast, HTMLFormElementCast};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::document::{Document, DocumentHelpers};
use dom::element::{Element, AttributeHandlers, ElementTypeId};
use dom::event::{Event, EventHelpers, EventBubbles, EventCancelable};
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::HTMLElement;
use dom::htmlinputelement::HTMLInputElement;
use dom::htmltextareaelement::HTMLTextAreaElement;
use dom::node::{Node, NodeHelpers, NodeTypeId, document_from_node, window_from_node};
use hyper::method::Post;
use servo_msg::constellation_msg::LoadData;
use servo_util::str::DOMString;
use script_task::{ScriptChan, ScriptMsg};
use std::ascii::OwnedAsciiExt;
use url::UrlParser;
use url::form_urlencoded::serialize;
use string_cache::Atom;
use std::cell::Cell;
#[dom_struct]
pub struct HTMLFormElement {
htmlelement: HTMLElement,
marked_for_reset: Cell<bool>,
}
impl HTMLFormElementDerived for EventTarget {
fn is_htmlformelement(&self) -> bool {
*self.type_id() == EventTargetTypeId::Node(NodeTypeId::Element(ElementTypeId::HTMLFormElement))
}
}
impl HTMLFormElement {
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> HTMLFormElement {
HTMLFormElement {
htmlelement: HTMLElement::new_inherited(ElementTypeId::HTMLFormElement, localName, prefix, document),
marked_for_reset: Cell::new(false),
}
}
#[allow(unrooted_must_root)]
pub fn new(localName: DOMString, prefix: Option<DOMString>, document: JSRef<Document>) -> Temporary<HTMLFormElement> {
let element = HTMLFormElement::new_inherited(localName, prefix, document);
Node::reflect_node(box element, document, HTMLFormElementBinding::Wrap)
}
}
impl<'a> HTMLFormElementMethods for JSRef<'a, HTMLFormElement> {
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-acceptcharset
make_getter!(AcceptCharset, "accept-charset")
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-acceptcharset
make_setter!(SetAcceptCharset, "accept-charset")
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-action
make_url_or_base_getter!(Action)
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-action
make_setter!(SetAction, "action")
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-autocomplete
make_enumerated_getter!(Autocomplete, "on", "off")
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-autocomplete
make_setter!(SetAutocomplete, "autocomplete")
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-enctype
make_enumerated_getter!(Enctype, "application/x-www-form-urlencoded", "text/plain" | "multipart/form-data")
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-enctype
make_setter!(SetEnctype, "enctype")
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-encoding
fn Encoding(self) -> DOMString {
self.Enctype()
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-encoding
fn SetEncoding(self, value: DOMString) {
self.SetEnctype(value)
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-method
make_enumerated_getter!(Method, "get", "post" | "dialog")
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-method
make_setter!(SetMethod, "method")
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-name
make_getter!(Name)
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-name
make_setter!(SetName, "name")
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-novalidate
make_bool_getter!(NoValidate)
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-novalidate
make_bool_setter!(SetNoValidate, "novalidate")
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-target
make_getter!(Target)
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-target
make_setter!(SetTarget, "target")
// https://html.spec.whatwg.org/multipage/forms.html#the-form-element:concept-form-submit
fn Submit(self) {
self.submit(SubmittedFrom::FromFormSubmitMethod, FormSubmitter::FormElement(self));
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-reset
fn Reset(self) {
self.reset(ResetFrom::FromFormResetMethod);
}
}
pub enum SubmittedFrom {
FromFormSubmitMethod,
NotFromFormSubmitMethod
}
pub enum ResetFrom {
FromFormResetMethod,
NotFromFormResetMethod
}
pub trait HTMLFormElementHelpers {
// https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit
fn submit(self, submit_method_flag: SubmittedFrom, submitter: FormSubmitter);
// https://html.spec.whatwg.org/multipage/forms.html#constructing-the-form-data-set
fn get_form_dataset(self, submitter: Option<FormSubmitter>) -> Vec<FormDatum>;
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-reset
fn reset(self, submit_method_flag: ResetFrom);
}
impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> {
fn submit(self, _submit_method_flag: SubmittedFrom, submitter: FormSubmitter) {
// Step 1
let doc = document_from_node(self).root();
let win = window_from_node(self).root();
let base = doc.url();
// TODO: Handle browsing contexts
// TODO: Handle validation
let event = Event::new(GlobalRef::Window(*win),
"submit".into_string(),
EventBubbles::Bubbles,
EventCancelable::Cancelable).root();
event.set_trusted(true);
let target: JSRef<EventTarget> = EventTargetCast::from_ref(self);
target.DispatchEvent(*event).ok();
if event.DefaultPrevented() {
return;
}
// Step 6
let form_data = self.get_form_dataset(Some(submitter));
// Step 7-8
let mut action = submitter.action();
if action.is_empty() {
action = base.serialize();
}
// TODO: Resolve the url relative to the submitter element
// Step 10-15
let action_components = UrlParser::new().base_url(base).parse(action.as_slice()).unwrap_or(base.clone());
let _action = action_components.serialize();
let scheme = action_components.scheme.clone();
let enctype = submitter.enctype();
let method = submitter.method();
let _target = submitter.target();
// TODO: Handle browsing contexts, partially loaded documents (step 16-17)
let parsed_data = match enctype {
FormEncType::UrlEncoded => serialize(form_data.iter().map(|d| (d.name.as_slice(), d.value.as_slice()))),
_ => "".into_string() // TODO: Add serializers for the other encoding types
};
let mut load_data = LoadData::new(action_components);
// Step 18
match (scheme.as_slice(), method) {
(_, FormMethod::FormDialog) => return, // Unimplemented
("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) => {
load_data.url.query = Some(parsed_data);
},
("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => {
load_data.method = Post;
load_data.data = Some(parsed_data.into_bytes());
},
// https://html.spec.whatwg.org/multipage/forms.html#submit-get-action
("ftp", _) | ("javascript", _) | ("data", FormMethod::FormGet) => (),
_ => return // Unimplemented (data and mailto)
}
// This is wrong. https://html.spec.whatwg.org/multipage/forms.html#planned-navigation
win.script_chan().send(ScriptMsg::TriggerLoad(win.page().id, load_data));
}
fn get_form_dataset<'b>(self, submitter: Option<FormSubmitter<'b>>) -> Vec<FormDatum> {
fn clean_crlf(s: &str) -> DOMString {
// https://html.spec.whatwg.org/multipage/forms.html#constructing-the-form-data-set
// Step 4
let mut buf = "".into_string();
let mut prev = ' ';
for ch in s.chars() {
match ch {
'\n' if prev != '\r' => {
buf.push('\r');
buf.push('\n');
},
'\n' => {
buf.push('\n');
},
// This character isn't LF but is
// preceded by CR
_ if prev == '\r' => {
buf.push('\r');
buf.push('\n');
buf.push(ch);
},
_ => buf.push(ch)
};
prev = ch;
}
// In case the last character was CR
if prev == '\r' {
buf.push('\n');
}
buf
}
let node: JSRef<Node> = NodeCast::from_ref(self);
// TODO: This is an incorrect way of getting controls owned
// by the form, but good enough until html5ever lands
let mut data_set = node.traverse_preorder().filter_map(|child| {
if child.get_disabled_state() {
return None;
}
if child.ancestors().any(|a| a.type_id() == NodeTypeId::Element(ElementTypeId::HTMLDataListElement)) {
return None;
}
// XXXManishearth don't include it if it is a button but not the submitter
match child.type_id() {
NodeTypeId::Element(ElementTypeId::HTMLInputElement) => {
let input: JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(child).unwrap();
let ty = input.Type();
let name = input.Name();
match ty.as_slice() {
"radio" | "checkbox" => {
if !input.Checked() || name.is_empty() {
return None;
}
},
"image" => (),
_ => {
if name.is_empty() {
return None;
}
}
}
let mut value = input.Value();
let is_submitter = match submitter {
Some(FormSubmitter::InputElement(s)) => {
input == s
},
_ => false
};
match ty.as_slice() {
"image" => None, // Unimplemented
"radio" | "checkbox" => {
if value.is_empty() {
value = "on".into_string();
}
Some(FormDatum {
ty: ty,
name: name,
value: value
})
},
// Discard buttons which are not the submitter
"submit" | "button" | "reset" if !is_submitter => None,
"file" => None, // Unimplemented
_ => Some(FormDatum {
ty: ty,
name: name,
value: input.Value()
})
}
}
NodeTypeId::Element(ElementTypeId::HTMLButtonElement) => {
// Unimplemented
None
}
NodeTypeId::Element(ElementTypeId::HTMLSelectElement) => {
// Unimplemented
None
}
NodeTypeId::Element(ElementTypeId::HTMLObjectElement) => {
// Unimplemented
None
}
NodeTypeId::Element(ElementTypeId::HTMLTextAreaElement) => {
// Unimplemented
None
}
_ => None
}
});
// TODO: Handle `dirnames` (needs directionality support)
// https://html.spec.whatwg.org/multipage/dom.html#the-directionality
let mut ret: Vec<FormDatum> = data_set.collect();
for mut datum in ret.iter_mut() {
match datum.ty.as_slice() {
"file" | "textarea" => (),
_ => {
datum.name = clean_crlf(datum.name.as_slice());
datum.value = clean_crlf(datum.value.as_slice());
}
}
};
ret
}
fn reset(self, _reset_method_flag: ResetFrom) {
// https://html.spec.whatwg.org/multipage/forms.html#locked-for-reset
if self.marked_for_reset.get() {
return;
} else {
self.marked_for_reset.set(true);
}
let win = window_from_node(self).root();
let event = Event::new(GlobalRef::Window(*win),
"reset".into_string(),
EventBubbles::Bubbles,
EventCancelable::Cancelable).root();
let target: JSRef<EventTarget> = EventTargetCast::from_ref(self);
target.DispatchEvent(*event).ok();
if event.DefaultPrevented() {
return;
}
let node: JSRef<Node> = NodeCast::from_ref(self);
// TODO: This is an incorrect way of getting controls owned
// by the form, but good enough until html5ever lands
for child in node.traverse_preorder() {
match child.type_id() {
NodeTypeId::Element(ElementTypeId::HTMLInputElement) => {
let input: JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(child)
.unwrap();
input.reset()
}
// TODO HTMLKeygenElement unimplemented
//NodeTypeId::Element(ElementTypeId::HTMLKeygenElement) => {
// // Unimplemented
// {}
//}
NodeTypeId::Element(ElementTypeId::HTMLSelectElement) => {
// Unimplemented
{}
}
NodeTypeId::Element(ElementTypeId::HTMLTextAreaElement) => {
let textarea: JSRef<HTMLTextAreaElement> = HTMLTextAreaElementCast::to_ref(child)
.unwrap();
textarea.reset()
}
NodeTypeId::Element(ElementTypeId::HTMLOutputElement) => {
// Unimplemented
{}
}
_ => {}
}
};
self.marked_for_reset.set(false);
}
}
// TODO: add file support
pub struct FormDatum {
pub ty: DOMString,
pub name: DOMString,
pub value: DOMString
}
pub enum FormEncType {
TextPlainEncoded,
UrlEncoded,
FormDataEncoded
}
pub enum FormMethod {
FormGet,
FormPost,
FormDialog
}
pub enum FormSubmitter<'a> {
FormElement(JSRef<'a, HTMLFormElement>),
InputElement(JSRef<'a, HTMLInputElement>)
// TODO: Submit buttons, image submit, etc etc
}
impl<'a> FormSubmitter<'a> {
fn action(&self) -> DOMString {
match *self {
FormSubmitter::FormElement(form) => form.Action(),
FormSubmitter::InputElement(input_element) => {
// FIXME(pcwalton): Make this a static atom.
input_element.get_form_attribute(&Atom::from_slice("formaction"),
|i| i.FormAction(),
|f| f.Action())
}
}
}
fn enctype(&self) -> FormEncType {
let attr = match *self {
FormSubmitter::FormElement(form) => form.Enctype(),
FormSubmitter::InputElement(input_element) => {
// FIXME(pcwalton): Make this a static atom.
input_element.get_form_attribute(&Atom::from_slice("formenctype"),
|i| i.FormEnctype(),
|f| f.Enctype())
}
};
match attr.as_slice() {
"multipart/form-data" => FormEncType::FormDataEncoded,
"text/plain" => FormEncType::TextPlainEncoded,
// https://html.spec.whatwg.org/multipage/forms.html#attr-fs-enctype
// urlencoded is the default
_ => FormEncType::UrlEncoded
}
}
fn method(&self) -> FormMethod {
let attr = match *self {
FormSubmitter::FormElement(form) => form.Method(),
FormSubmitter::InputElement(input_element) => {
// FIXME(pcwalton): Make this a static atom.
input_element.get_form_attribute(&Atom::from_slice("formmethod"),
|i| i.FormMethod(),
|f| f.Method())
}
};
match attr.as_slice() {
"dialog" => FormMethod::FormDialog,
"post" => FormMethod::FormPost,
_ => FormMethod::FormGet
}
}
fn target(&self) -> DOMString {
match *self {
FormSubmitter::FormElement(form) => form.Target(),
FormSubmitter::InputElement(input_element) => {
// FIXME(pcwalton): Make this a static atom.
input_element.get_form_attribute(&Atom::from_slice("formtarget"),
|i| i.FormTarget(),
|f| f.Target())
}
}
}
}
pub trait FormControl<'a> : Copy {
// FIXME: This is wrong (https://github.com/servo/servo/issues/3553)
// but we need html5ever to do it correctly
fn form_owner(self) -> Option<Temporary<HTMLFormElement>> {
// https://html.spec.whatwg.org/multipage/forms.html#reset-the-form-owner
let elem = self.to_element();
let owner = elem.get_string_attribute(&atom!("form"));
if !owner.is_empty() {
let doc = document_from_node(elem).root();
let owner = doc.GetElementById(owner).root();
match owner {
Some(o) => {
let maybe_form: Option<JSRef<HTMLFormElement>> = HTMLFormElementCast::to_ref(*o);
if maybe_form.is_some() {
return maybe_form.map(Temporary::from_rooted);
}
},
_ => ()
}
}
let node: JSRef<Node> = NodeCast::from_ref(elem);
node.ancestors().filter_map(|a| HTMLFormElementCast::to_ref(a)).next()
.map(Temporary::from_rooted)
}
fn get_form_attribute(self,
attr: &Atom,
input: |Self| -> DOMString,
owner: |JSRef<HTMLFormElement>| -> DOMString) -> DOMString {
if self.to_element().has_attribute(attr) {
input(self)
} else {
self.form_owner().map_or("".into_string(), |t| owner(*t.root()))
}
}
fn to_element(self) -> JSRef<'a, Element>;
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-mutable
fn mutable(self) -> bool;
fn reset(self);
}