aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/htmlformelement.rs284
-rw-r--r--components/script/dom/webidls/HTMLFormElement.webidl2
-rw-r--r--components/script/dom/window.rs3
-rw-r--r--components/script/script_task.rs8
4 files changed, 284 insertions, 13 deletions
diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs
index 7b2ee466bab..5b35e30a00d 100644
--- a/components/script/dom/htmlformelement.rs
+++ b/components/script/dom/htmlformelement.rs
@@ -4,17 +4,28 @@
use dom::bindings::codegen::Bindings::HTMLFormElementBinding;
use dom::bindings::codegen::Bindings::HTMLFormElementBinding::HTMLFormElementMethods;
-use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFormElementDerived};
+use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, EventTargetCast, HTMLFormElementDerived, NodeCast};
+use dom::bindings::codegen::InheritTypes::HTMLInputElementCast;
+use dom::bindings::global::Window;
use dom::bindings::js::{JSRef, Temporary};
use dom::bindings::utils::{Reflectable, Reflector};
-use dom::document::Document;
-use dom::element::{Element, AttributeHandlers, HTMLFormElementTypeId};
-use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::document::{Document, DocumentHelpers};
+use dom::element::{Element, AttributeHandlers, HTMLFormElementTypeId, HTMLTextAreaElementTypeId, HTMLDataListElementTypeId};
+use dom::element::{HTMLInputElementTypeId, HTMLButtonElementTypeId, HTMLObjectElementTypeId, HTMLSelectElementTypeId};
+use dom::event::Event;
+use dom::eventtarget::{EventTarget, EventTargetHelpers, NodeTargetTypeId};
use dom::htmlelement::HTMLElement;
-use dom::node::{Node, ElementNodeTypeId, window_from_node};
+use dom::htmlinputelement::HTMLInputElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId, document_from_node, window_from_node};
+use http::method::Post;
+use servo_msg::constellation_msg::LoadData;
use servo_util::str::DOMString;
+use script_task::{ScriptChan, TriggerLoadMsg};
use std::ascii::OwnedStrAsciiExt;
-
+use std::str::StrSlice;
+use url::UrlParser;
+use url::form_urlencoded::serialize;
#[jstraceable]
#[must_root]
@@ -52,7 +63,7 @@ impl<'a> HTMLFormElementMethods for JSRef<'a, HTMLFormElement> {
// https://html.spec.whatwg.org/multipage/forms.html#dom-fs-action
fn Action(self) -> DOMString {
let element: JSRef<Element> = ElementCast::from_ref(self);
- let url = element.get_url_attribute("src");
+ let url = element.get_url_attribute("action");
match url.as_slice() {
"" => {
let window = window_from_node(self).root();
@@ -135,6 +146,196 @@ impl<'a> HTMLFormElementMethods for JSRef<'a, HTMLFormElement> {
// 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(true, FormElement(self));
+ }
+}
+
+pub trait HTMLFormElementHelpers {
+ // https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit
+ fn submit(self, from_submit_method: bool, 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>;
+}
+
+impl<'a> HTMLFormElementHelpers for JSRef<'a, HTMLFormElement> {
+ fn submit(self, _from_submit_method: bool, 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(&Window(win),
+ "submit".to_string(),
+ true, true).root();
+ let target: JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ target.dispatch_event_with_target(None, *event).ok();
+ if event.canceled.get() {
+ return;
+ }
+ // Step 6
+ let form_data = self.get_form_dataset(Some(submitter));
+ // Step 7-8
+ let mut action = submitter.action();
+ if action.len() == 0 {
+ 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 {
+ UrlEncoded => {
+ serialize(form_data.iter().map(|d| (d.name.as_slice(), d.value.as_slice())), None)
+ }
+ _ => "".to_string() // TODO: Add serializers for the other encoding types
+ };
+
+ let mut load_data = LoadData::new(action_components);
+ // Step 18
+ match (scheme.as_slice(), method) {
+ (_, FormDialog) => return, // Unimplemented
+ ("http", FormGet) | ("https", FormGet) => { load_data.url.query = Some(parsed_data); },
+ ("http", FormPost) | ("https", 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", FormGet) => (),
+ _ => return // Unimplemented (data and mailto)
+ }
+
+ // This is wrong. https://html.spec.whatwg.org/multipage/forms.html#planned-navigation
+ let ScriptChan(ref script_chan) = win.script_chan;
+ script_chan.send(TriggerLoadMsg(win.page.id, load_data));
+ }
+
+ fn get_form_dataset(self, _submitter: Option<FormSubmitter>) -> 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 = "".to_string();
+ let mut prev = ' ';
+ for ch in s.chars() {
+ match ch {
+ '\n' if prev != '\r' => {
+ buf.push_char('\r');
+ buf.push_char('\n');
+ },
+ '\n' => {
+ buf.push_char('\n');
+ },
+ // This character isn't LF but is
+ // preceded by CR
+ _ if prev == '\r' => {
+ buf.push_char('\r');
+ buf.push_char('\n');
+ buf.push_char(ch);
+ },
+ _ => buf.push_char(ch)
+ };
+ prev = ch;
+ }
+ // In case the last character was CR
+ if prev == '\r' {
+ buf.push_char('\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() == ElementNodeTypeId(HTMLDataListElementTypeId)) {
+ return None;
+ }
+ // XXXManishearth don't include it if it is a button but not the submitter
+ match child.type_id() {
+ ElementNodeTypeId(HTMLInputElementTypeId) => {
+ let input: JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(child).unwrap();
+ let elem: JSRef<Element> = ElementCast::from_ref(input);
+ match elem.get_string_attribute("type").as_slice() {
+ "radio" | "checkbox" => {
+ if !input.Checked() {
+ return None;
+ }
+ },
+ "image" => (),
+ _ => {
+ if elem.get_string_attribute("name").len() == 0 {
+ return None;
+ }
+ }
+ }
+ let ty = input.Type(); // IDL attr, not HTML attr
+ let mut value_attr = elem.get_string_attribute("value");
+ let name = elem.get_string_attribute("name");
+ match ty.as_slice() {
+ "image" => None, // Unimplemented
+ "radio" | "checkbox" => {
+ if value_attr.len() == 0 {
+ value_attr = "on".to_string();
+ }
+ Some(FormDatum {
+ ty: ty,
+ name: name,
+ value: value_attr
+ })
+ },
+ "file" => None, // Unimplemented
+ _ => Some(FormDatum {
+ ty: ty,
+ name: name,
+ value: input.Value()
+ })
+ }
+ }
+ ElementNodeTypeId(HTMLButtonElementTypeId) => {
+ // Unimplemented
+ None
+ }
+ ElementNodeTypeId(HTMLSelectElementTypeId) => {
+ // Unimplemented
+ None
+ }
+ ElementNodeTypeId(HTMLObjectElementTypeId) => {
+ // Unimplemented
+ None
+ }
+ ElementNodeTypeId(HTMLTextAreaElementTypeId) => {
+ // 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
+ }
}
impl Reflectable for HTMLFormElement {
@@ -142,3 +343,72 @@ impl Reflectable for HTMLFormElement {
self.htmlelement.reflector()
}
}
+
+// 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>)
+ // TODO: Submit buttons, image submit, etc etc
+}
+
+impl<'a> FormSubmitter<'a> {
+ fn action(&self) -> DOMString {
+ match *self {
+ FormElement(form) => {
+ let element: JSRef<Element> = ElementCast::from_ref(form);
+ element.get_url_attribute("action")
+ }
+ }
+ }
+
+ fn enctype(&self) -> FormEncType {
+ match *self {
+ FormElement(form) => {
+ let element: JSRef<Element> = ElementCast::from_ref(form);
+ match element.get_string_attribute("enctype").into_ascii_lower().as_slice() {
+ "multipart/form-data" => FormDataEncoded,
+ "text/plain" => TextPlainEncoded,
+ // https://html.spec.whatwg.org/multipage/forms.html#attr-fs-enctype
+ // urlencoded is the default
+ _ => UrlEncoded
+ }
+ }
+ }
+ }
+
+ fn method(&self) -> FormMethod {
+ match *self {
+ FormElement(form) => {
+ let element: JSRef<Element> = ElementCast::from_ref(form);
+ match element.get_string_attribute("method").into_ascii_lower().as_slice() {
+ "dialog" => FormDialog,
+ "post" => FormPost,
+ _ => FormGet
+ }
+ }
+ }
+ }
+
+ fn target(&self) -> DOMString {
+ match *self {
+ FormElement(form) => form.Target()
+ }
+ }
+}
diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl
index 0878a6931a2..1971680b5be 100644
--- a/components/script/dom/webidls/HTMLFormElement.webidl
+++ b/components/script/dom/webidls/HTMLFormElement.webidl
@@ -21,7 +21,7 @@ interface HTMLFormElement : HTMLElement {
//getter Element (unsigned long index);
//getter (RadioNodeList or Element) (DOMString name);
- //void submit();
+ void submit();
//void reset();
//boolean checkValidity();
//boolean reportValidity();
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 02166f328e4..f3edfc04943 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -24,6 +24,7 @@ use script_task::{ExitWindowMsg, FireTimerMsg, ScriptChan, TriggerLoadMsg, Trigg
use script_traits::ScriptControlChan;
use servo_msg::compositor_msg::ScriptListener;
+use servo_msg::constellation_msg::LoadData;
use servo_net::image_cache_task::ImageCacheTask;
use servo_util::str::{DOMString,HTML_SPACE_CHARACTERS};
use servo_util::task::{spawn_named};
@@ -432,7 +433,7 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
if href.as_slice().starts_with("#") {
script_chan.send(TriggerFragmentMsg(self.page.id, url));
} else {
- script_chan.send(TriggerLoadMsg(self.page.id, url));
+ script_chan.send(TriggerLoadMsg(self.page.id, LoadData::new(url)));
}
}
diff --git a/components/script/script_task.rs b/components/script/script_task.rs
index 29e1fec40ba..e62c818943c 100644
--- a/components/script/script_task.rs
+++ b/components/script/script_task.rs
@@ -81,7 +81,7 @@ pub enum ScriptMsg {
TriggerFragmentMsg(PipelineId, Url),
/// Begins a content-initiated load on the specified pipeline (only
/// dispatched to ScriptTask).
- TriggerLoadMsg(PipelineId, Url),
+ TriggerLoadMsg(PipelineId, LoadData),
/// Instructs the script task to send a navigate message to
/// the constellation (only dispatched to ScriptTask).
NavigateMsg(NavigationDirection),
@@ -494,7 +494,7 @@ impl ScriptTask {
// TODO(tkuehn) need to handle auxiliary layouts for iframes
FromConstellation(AttachLayoutMsg(_)) => fail!("should have handled AttachLayoutMsg already"),
FromConstellation(LoadMsg(id, load_data)) => self.load(id, load_data),
- FromScript(TriggerLoadMsg(id, url)) => self.trigger_load(id, url),
+ FromScript(TriggerLoadMsg(id, load_data)) => self.trigger_load(id, load_data),
FromScript(TriggerFragmentMsg(id, url)) => self.trigger_fragment(id, url),
FromConstellation(SendEventMsg(id, event)) => self.handle_event(id, event),
FromScript(FireTimerMsg(id, timer_id)) => self.handle_fire_timer_msg(id, timer_id),
@@ -1067,9 +1067,9 @@ impl ScriptTask {
/// The entry point for content to notify that a new load has been requested
/// for the given pipeline.
- fn trigger_load(&self, pipeline_id: PipelineId, url: Url) {
+ fn trigger_load(&self, pipeline_id: PipelineId, load_data: LoadData) {
let ConstellationChan(ref const_chan) = self.constellation_chan;
- const_chan.send(LoadUrlMsg(pipeline_id, LoadData::new(url)));
+ const_chan.send(LoadUrlMsg(pipeline_id, load_data));
}
/// The entry point for content to notify that a fragment url has been requested