aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/gfx/font.rs1
-rw-r--r--components/gfx/platform/freetype/font.rs6
-rw-r--r--components/gfx/platform/macos/font.rs9
-rw-r--r--components/layout/block.rs4
-rw-r--r--components/layout/construct.rs31
-rw-r--r--components/layout/fragment.rs50
-rw-r--r--components/layout/inline.rs4
-rw-r--r--components/layout/table.rs5
-rw-r--r--components/layout/wrapper.rs42
-rw-r--r--components/script/dom/htmlinputelement.rs260
-rw-r--r--components/script/dom/webidls/HTMLInputElement.webidl10
-rw-r--r--components/script/script_task.rs11
-rw-r--r--components/style/user-agent.css11
-rw-r--r--tests/html/test-inputs.html17
14 files changed, 409 insertions, 52 deletions
diff --git a/components/gfx/font.rs b/components/gfx/font.rs
index b3f85edf823..b3e30b100fd 100644
--- a/components/gfx/font.rs
+++ b/components/gfx/font.rs
@@ -78,6 +78,7 @@ pub struct FontMetrics {
pub ascent: Au,
pub descent: Au,
pub max_advance: Au,
+ pub average_advance: Au,
pub line_gap: Au,
}
diff --git a/components/gfx/platform/freetype/font.rs b/components/gfx/platform/freetype/font.rs
index 5bc2ce0abe4..b121e869865 100644
--- a/components/gfx/platform/freetype/font.rs
+++ b/components/gfx/platform/freetype/font.rs
@@ -235,6 +235,11 @@ impl FontHandleMethods for FontHandle {
}
}
+ let average_advance = self.glyph_index('0')
+ .and_then(|idx| self.glyph_h_advance(idx))
+ .map(|advance| self.font_units_to_au(advance))
+ .unwrap_or(max_advance);
+
let metrics = FontMetrics {
underline_size: underline_size,
underline_offset: underline_offset,
@@ -246,6 +251,7 @@ impl FontHandleMethods for FontHandle {
ascent: ascent,
descent: -descent, // linux font's seem to use the opposite sign from mac
max_advance: max_advance,
+ average_advance: average_advance,
line_gap: height,
};
diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs
index b025b70566c..dd48e797cd1 100644
--- a/components/gfx/platform/macos/font.rs
+++ b/components/gfx/platform/macos/font.rs
@@ -153,6 +153,12 @@ impl FontHandleMethods for FontHandle {
let scale = px_to_pt(self.ctfont.pt_size() as f64) / (ascent + descent);
let line_gap = (ascent + descent + leading + 0.5).floor();
+ let max_advance_width = Au::from_pt(bounding_rect.size.width as f64);
+ let average_advance = self.glyph_index('0')
+ .and_then(|idx| self.glyph_h_advance(idx))
+ .map(|advance| Au::from_frac_px(advance))
+ .unwrap_or(max_advance_width);
+
let metrics = FontMetrics {
underline_size: Au::from_pt(self.ctfont.underline_thickness() as f64),
// TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table
@@ -168,7 +174,8 @@ impl FontHandleMethods for FontHandle {
em_size: em_size,
ascent: Au::from_pt(ascent * scale),
descent: Au::from_pt(descent * scale),
- max_advance: Au::from_pt(bounding_rect.size.width as f64),
+ max_advance: max_advance_width,
+ average_advance: average_advance,
line_gap: Au::from_frac_px(line_gap),
};
debug!("Font metrics (@{:f} pt): {:?}", self.ctfont.pt_size() as f64, metrics);
diff --git a/components/layout/block.rs b/components/layout/block.rs
index 23a2381e754..40d5a89244f 100644
--- a/components/layout/block.rs
+++ b/components/layout/block.rs
@@ -1516,7 +1516,7 @@ impl Flow for BlockFlow {
/// any fragments it is responsible for flowing.
///
/// TODO(pcwalton): Inline blocks.
- fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ fn bubble_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("block::bubble_inline_sizes {:s}", self.base.debug_id());
let mut flags = self.base.flags;
@@ -1572,7 +1572,7 @@ impl Flow for BlockFlow {
max(intrinsic_inline_sizes.preferred_inline_size,
left_float_width + right_float_width);
- let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes();
+ let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes(layout_context);
intrinsic_inline_sizes.minimum_inline_size = max(intrinsic_inline_sizes.minimum_inline_size,
fragment_intrinsic_inline_sizes.minimum_inline_size);
intrinsic_inline_sizes.preferred_inline_size = max(intrinsic_inline_sizes.preferred_inline_size,
diff --git a/components/layout/construct.rs b/components/layout/construct.rs
index 755f39582f9..818fa1602e6 100644
--- a/components/layout/construct.rs
+++ b/components/layout/construct.rs
@@ -27,12 +27,12 @@ use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use flow::{Descendants, AbsDescendants};
use flow;
use flow_ref::FlowRef;
-use fragment::{InlineBlockFragment, InlineBlockFragmentInfo};
+use fragment::{InlineBlockFragment, InlineBlockFragmentInfo, InputFragment};
use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo};
use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment};
use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo};
use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment};
-use fragment::{UnscannedTextFragmentInfo};
+use fragment::{UnscannedTextFragmentInfo, InputFragmentInfo};
use inline::{InlineFragments, InlineFlow};
use parallel;
use table_wrapper::TableWrapperFlow;
@@ -49,7 +49,7 @@ use wrapper::{Before, After, Normal};
use gfx::display_list::OpaqueNode;
use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId};
-use script::dom::element::{HTMLObjectElementTypeId};
+use script::dom::element::{HTMLObjectElementTypeId, HTMLInputElementTypeId};
use script::dom::element::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId};
use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId};
use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId};
@@ -221,6 +221,21 @@ impl<'a> FlowConstructor<'a> {
}
}
+ fn build_fragment_info_for_input(&mut self, node: &ThreadSafeLayoutNode) -> SpecificFragmentInfo {
+ //FIXME: would it make more sense to use HTMLInputElement::input_type instead of the raw
+ // value? definitely for string comparisons.
+ let elem = node.as_element();
+ let data = match elem.get_attr(&ns!(""), "type") {
+ Some("checkbox") | Some("radio") => None,
+ Some("button") | Some("submit") | Some("reset") =>
+ Some(node.get_input_value().len() as u32),
+ Some("file") => Some(node.get_input_size()),
+ _ => Some(node.get_input_size()),
+ };
+ data.map(|size| InputFragment(InputFragmentInfo { size: size }))
+ .unwrap_or(GenericFragment)
+ }
+
/// Builds specific `Fragment` info for the given node.
///
/// This does *not* construct the text for generated content (but, for generated content with
@@ -230,11 +245,14 @@ impl<'a> FlowConstructor<'a> {
pub fn build_specific_fragment_info_for_node(&mut self, node: &ThreadSafeLayoutNode)
-> SpecificFragmentInfo {
match node.type_id() {
+ Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => {
+ IframeFragment(IframeFragmentInfo::new(node))
+ }
Some(ElementNodeTypeId(HTMLImageElementTypeId)) => {
self.build_fragment_info_for_image(node, node.image_url())
}
- Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => {
- IframeFragment(IframeFragmentInfo::new(node))
+ Some(ElementNodeTypeId(HTMLInputElementTypeId)) => {
+ self.build_fragment_info_for_input(node)
}
Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => {
let data = node.get_object_data();
@@ -445,7 +463,8 @@ impl<'a> FlowConstructor<'a> {
// Special case: If this is generated content, then we need to initialize the accumulator
// with the fragment corresponding to that content.
- if node.get_pseudo_element_type() != Normal {
+ if node.get_pseudo_element_type() != Normal ||
+ node.type_id() == Some(ElementNodeTypeId(HTMLInputElementTypeId)) {
let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::new(node));
let mut fragment = Fragment::new_from_specific_info(node, fragment_info);
inline_fragment_accumulator.fragments.push(&mut fragment);
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
index 59c256022bb..7c2ef5c4a05 100644
--- a/components/layout/fragment.rs
+++ b/components/layout/fragment.rs
@@ -128,10 +128,11 @@ impl<E, S: Encoder<E>> Encodable<S, E> for Fragment {
/// Info specific to the kind of fragment. Keep this enum small.
#[deriving(Clone)]
pub enum SpecificFragmentInfo {
- InlineBlockFragment(InlineBlockFragmentInfo),
GenericFragment,
- ImageFragment(ImageFragmentInfo),
IframeFragment(IframeFragmentInfo),
+ ImageFragment(ImageFragmentInfo),
+ InlineBlockFragment(InlineBlockFragmentInfo),
+ InputFragment(InputFragmentInfo),
ScannedTextFragment(ScannedTextFragmentInfo),
TableFragment,
TableCellFragment,
@@ -155,6 +156,22 @@ impl InlineBlockFragmentInfo {
}
}
+/// A fragment that represents a displayable form element
+#[deriving(Clone)]
+pub struct InputFragmentInfo {
+ pub size: u32,
+}
+
+impl InputFragmentInfo {
+ /// Returns the original inline-size of the input.
+ fn input_inline_size(&self, font_style: &FontStyle, layout_context: &LayoutContext) -> Au {
+ let metrics = text::font_metrics_for_style(layout_context.font_context(), font_style);
+
+ // https://html.spec.whatwg.org/#converting-a-character-width-to-pixels
+ metrics.average_advance * (self.size as i32 - 1) + metrics.max_advance
+ }
+}
+
/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc.
#[deriving(Clone)]
pub struct ImageFragmentInfo {
@@ -499,7 +516,8 @@ impl Fragment {
/// replaced elements.
fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes {
let (use_margins, use_padding) = match self.specific {
- GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) => (true, true),
+ GenericFragment | IframeFragment(_) | ImageFragment(_) | InlineBlockFragment(_) |
+ InputFragment(_) => (true, true),
TableFragment | TableCellFragment => (false, true),
TableWrapperFragment => (true, false),
TableRowFragment => (false, false),
@@ -1129,7 +1147,7 @@ impl Fragment {
text_fragment))
}
GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment |
- TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => {
+ TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => {
// FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
// should have a real `SERVO_DEBUG` system.
debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
@@ -1193,7 +1211,7 @@ impl Fragment {
}
/// Returns the intrinsic inline-sizes of this fragment.
- pub fn intrinsic_inline_sizes(&mut self) -> IntrinsicISizes {
+ pub fn intrinsic_inline_sizes(&mut self, layout_context: &LayoutContext) -> IntrinsicISizes {
let mut result = self.style_specified_intrinsic_inline_size();
match self.specific {
@@ -1214,6 +1232,12 @@ impl Fragment {
result.preferred_inline_size = max(result.preferred_inline_size,
image_inline_size);
}
+ InputFragment(ref input_fragment_info) => {
+ let font_style = text::computed_style_to_font_style(&*self.style);
+ let input_inline_size = input_fragment_info.input_inline_size(&font_style, layout_context);
+ result.minimum_inline_size = input_inline_size;
+ result.preferred_inline_size = input_inline_size;
+ }
ScannedTextFragment(ref text_fragment_info) => {
let range = &text_fragment_info.range;
let min_line_inline_size = text_fragment_info.run.min_width_for_range(range);
@@ -1260,7 +1284,7 @@ impl Fragment {
pub fn content_inline_size(&self) -> Au {
match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
- TableWrapperFragment | InlineBlockFragment(_) => Au(0),
+ TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => Au(0),
ImageFragment(ref image_fragment_info) => {
image_fragment_info.computed_inline_size()
}
@@ -1278,7 +1302,8 @@ impl Fragment {
pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au {
match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
- TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => Au(0),
+ TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) |
+ InputFragment(_) => Au(0),
ImageFragment(ref image_fragment_info) => {
image_fragment_info.computed_block_size()
}
@@ -1312,7 +1337,7 @@ impl Fragment {
-> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
- TableRowFragment | TableWrapperFragment => None,
+ TableRowFragment | TableWrapperFragment | InputFragment(_) => None,
TableColumnFragment(_) => fail!("Table column fragments do not need to split"),
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
InlineBlockFragment(_) => fail!("Inline blocks do not get split"),
@@ -1353,7 +1378,7 @@ impl Fragment {
-> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
- TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => None,
+ TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => None,
TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
ScannedTextFragment(ref text_fragment_info) => {
@@ -1457,7 +1482,7 @@ impl Fragment {
container_inline_size: Au) {
match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
- TableRowFragment | TableWrapperFragment => return,
+ TableRowFragment | TableWrapperFragment | InputFragment(_) => return,
TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
UnscannedTextFragment(_) => {
fail!("Unscanned text fragments should have been scanned by now!")
@@ -1540,7 +1565,7 @@ impl Fragment {
pub fn assign_replaced_block_size_if_necessary(&mut self) {
match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
- TableRowFragment | TableWrapperFragment => return,
+ TableRowFragment | TableWrapperFragment | InputFragment(_) => return,
TableColumnFragment(_) => fail!("Table column fragments do not have block_size"),
UnscannedTextFragment(_) => {
fail!("Unscanned text fragments should have been scanned by now!")
@@ -1682,7 +1707,7 @@ impl Fragment {
InlineBlockFragment(_) | TableWrapperFragment => false,
GenericFragment | IframeFragment(_) | ImageFragment(_) | ScannedTextFragment(_) |
TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment |
- UnscannedTextFragment(_) => true,
+ UnscannedTextFragment(_) | InputFragment(_) => true,
}
}
}
@@ -1703,6 +1728,7 @@ impl fmt::Show for Fragment {
TableWrapperFragment => "TableWrapperFragment",
UnscannedTextFragment(_) => "UnscannedTextFragment",
InlineBlockFragment(_) => "InlineBlockFragment",
+ InputFragment(_) => "InputFragment",
}));
try!(write!(f, "bp {}", self.border_padding));
try!(write!(f, " "));
diff --git a/components/layout/inline.rs b/components/layout/inline.rs
index a683c8574c9..d937a6cef53 100644
--- a/components/layout/inline.rs
+++ b/components/layout/inline.rs
@@ -955,7 +955,7 @@ impl Flow for InlineFlow {
self
}
- fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ fn bubble_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:s}", self.base.debug_id());
let writing_mode = self.base.writing_mode;
@@ -968,7 +968,7 @@ impl Flow for InlineFlow {
debug!("Flow: measuring {}", *fragment);
let fragment_intrinsic_inline_sizes =
- fragment.intrinsic_inline_sizes();
+ fragment.intrinsic_inline_sizes(layout_context);
intrinsic_inline_sizes.minimum_inline_size = max(
intrinsic_inline_sizes.minimum_inline_size,
fragment_intrinsic_inline_sizes.minimum_inline_size);
diff --git a/components/layout/table.rs b/components/layout/table.rs
index d48b845bc98..92e069f1c5c 100644
--- a/components/layout/table.rs
+++ b/components/layout/table.rs
@@ -171,7 +171,7 @@ impl Flow for TableFlow {
/// table layout calculation.
/// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
/// table layout calculation.
- fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ fn bubble_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("table::bubble_inline_sizes {:s}",
self.block_flow.base.debug_id());
@@ -239,7 +239,8 @@ impl Flow for TableFlow {
}
}
- let fragment_intrinsic_inline_sizes = self.block_flow.fragment.intrinsic_inline_sizes();
+ let fragment_intrinsic_inline_sizes =
+ self.block_flow.fragment.intrinsic_inline_sizes(layout_context);
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
max(min_inline_size, pref_inline_size);
diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs
index 18fd74fca9b..eb1be0b09de 100644
--- a/components/layout/wrapper.rs
+++ b/components/layout/wrapper.rs
@@ -36,13 +36,14 @@
use css::node_style::StyledNode;
use util::{LayoutDataAccess, LayoutDataWrapper, PrivateLayoutData};
-use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived};
+use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived, HTMLInputElementDerived};
use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived};
use script::dom::bindings::js::JS;
use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId};
use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers};
use script::dom::htmliframeelement::HTMLIFrameElement;
use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
+use script::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId};
use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, SharedLayoutData, TextNodeTypeId};
use script::dom::text::Text;
@@ -184,11 +185,15 @@ impl<'ln> TLayoutNode for LayoutNode<'ln> {
fn text(&self) -> String {
unsafe {
- if !self.get().is_text() {
+ if self.get().is_text() {
+ let text: JS<Text> = self.get_jsmanaged().transmute_copy();
+ (*text.unsafe_get()).characterdata.data.deref().borrow().clone()
+ } else if self.get().is_htmlinputelement() {
+ let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
+ input.get_value_for_layout()
+ } else {
fail!("not text!")
}
- let text: JS<Text> = self.get_jsmanaged().transmute_copy();
- (*text.unsafe_get()).characterdata.data.deref().borrow().clone()
}
}
}
@@ -567,14 +572,7 @@ impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> {
return get_content(&after_style.get_box().content)
}
}
-
- unsafe {
- if !self.get().is_text() {
- fail!("not text!")
- }
- let text: JS<Text> = self.get_jsmanaged().transmute_copy();
- (*text.unsafe_get()).characterdata.data.deref().borrow().clone()
- }
+ self.node.text()
}
}
@@ -732,6 +730,26 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
_ => false
}
}
+
+ pub fn get_input_value(&self) -> String {
+ unsafe {
+ if !self.get().is_htmlinputelement() {
+ fail!("not an input element!")
+ }
+ let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
+ input.get_value_for_layout()
+ }
+ }
+
+ pub fn get_input_size(&self) -> u32 {
+ unsafe {
+ if !self.get().is_htmlinputelement() {
+ fail!("not an input element!")
+ }
+ let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
+ input.get_size_for_layout()
+ }
+ }
}
pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {
diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
index cc4c85a0791..3bb57dfdb0e 100644
--- a/components/script/dom/htmlinputelement.rs
+++ b/components/script/dom/htmlinputelement.rs
@@ -2,26 +2,55 @@
* 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::AttrBinding::AttrMethods;
+use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
+use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
-use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, HTMLInputElementCast, NodeCast};
use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLFieldSetElementDerived};
-use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, ResultRootable};
use dom::bindings::utils::{Reflectable, Reflector};
-use dom::document::Document;
+use dom::attr::{AttrHelpers};
+use dom::document::{Document, DocumentHelpers};
use dom::element::{AttributeHandlers, Element, HTMLInputElementTypeId};
+use dom::event::Event;
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement;
-use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId};
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, document_from_node};
use dom::virtualmethods::VirtualMethods;
-use servo_util::str::DOMString;
+use servo_util::str::{DOMString, parse_unsigned_integer};
use string_cache::Atom;
+use std::cell::{Cell, RefCell};
+use std::mem;
+
+static DEFAULT_SUBMIT_VALUE: &'static str = "Submit";
+static DEFAULT_RESET_VALUE: &'static str = "Reset";
+
+#[jstraceable]
+#[deriving(PartialEq)]
+enum InputType {
+ InputButton(Option<&'static str>),
+ InputText,
+ InputFile,
+ InputImage,
+ InputCheckbox,
+ InputRadio,
+ InputPassword
+}
+
#[jstraceable]
#[must_root]
pub struct HTMLInputElement {
pub htmlelement: HTMLElement,
+ input_type: Cell<InputType>,
+ checked: Cell<bool>,
+ uncommitted_value: RefCell<Option<String>>,
+ value: RefCell<Option<String>>,
+ size: Cell<u32>,
}
impl HTMLInputElementDerived for EventTarget {
@@ -30,10 +59,17 @@ impl HTMLInputElementDerived for EventTarget {
}
}
+static DEFAULT_INPUT_SIZE: u32 = 20;
+
impl HTMLInputElement {
fn new_inherited(localName: DOMString, document: JSRef<Document>) -> HTMLInputElement {
HTMLInputElement {
- htmlelement: HTMLElement::new_inherited(HTMLInputElementTypeId, localName, document)
+ htmlelement: HTMLElement::new_inherited(HTMLInputElementTypeId, localName, document),
+ input_type: Cell::new(InputText),
+ checked: Cell::new(false),
+ uncommitted_value: RefCell::new(None),
+ value: RefCell::new(None),
+ size: Cell::new(DEFAULT_INPUT_SIZE),
}
}
@@ -44,6 +80,38 @@ impl HTMLInputElement {
}
}
+pub trait LayoutHTMLInputElementHelpers {
+ unsafe fn get_value_for_layout(&self) -> String;
+ unsafe fn get_size_for_layout(&self) -> u32;
+}
+
+impl LayoutHTMLInputElementHelpers for JS<HTMLInputElement> {
+ #[allow(unrooted_must_root)]
+ unsafe fn get_value_for_layout(&self) -> String {
+ unsafe fn get_raw_value(input: &JS<HTMLInputElement>) -> Option<String> {
+ mem::transmute::<&RefCell<Option<String>>, &Option<String>>(&(*input.unsafe_get()).value).clone()
+ }
+
+ match (*self.unsafe_get()).input_type.get() {
+ InputCheckbox | InputRadio => "".to_string(),
+ InputFile | InputImage => "".to_string(),
+ InputButton(ref default) => get_raw_value(self)
+ .or_else(|| default.map(|v| v.to_string()))
+ .unwrap_or_else(|| "".to_string()),
+ InputPassword => {
+ let raw = get_raw_value(self).unwrap_or_else(|| "".to_string());
+ String::from_char(raw.len(), '*')
+ }
+ _ => get_raw_value(self).unwrap_or_else(|| "".to_string()),
+ }
+ }
+
+ #[allow(unrooted_must_root)]
+ unsafe fn get_size_for_layout(&self) -> u32 {
+ (*self.unsafe_get()).size.get()
+ }
+}
+
impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> {
// http://www.whatwg.org/html/#dom-fe-disabled
make_bool_getter!(Disabled)
@@ -53,6 +121,104 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> {
let elem: JSRef<Element> = ElementCast::from_ref(self);
elem.set_bool_attribute("disabled", disabled)
}
+
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-checked
+ make_bool_getter!(Checked)
+
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-checked
+ fn SetChecked(self, checked: bool) {
+ let elem: JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("checked", checked)
+ }
+
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-size
+ make_uint_getter!(Size)
+
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-size
+ fn SetSize(self, size: u32) {
+ let elem: JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_uint_attribute("size", size)
+ }
+
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value
+ make_getter!(Value)
+
+ // https://html.spec.whatwg.org/multipage/forms.html#dom-input-value
+ fn SetValue(self, value: DOMString) {
+ let elem: JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_string_attribute("value", value)
+ }
+
+ // https://html.spec.whatwg.org/multipage/forms.html#attr-fe-name
+ make_getter!(Name)
+
+ // https://html.spec.whatwg.org/multipage/forms.html#attr-fe-name
+ fn SetName(self, name: DOMString) {
+ let elem: JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_string_attribute("name", name)
+ }
+}
+
+trait HTMLInputElementHelpers {
+ fn force_relayout(self);
+ fn radio_group_updated(self, group: Option<&str>);
+ fn get_radio_group(self) -> Option<String>;
+ fn update_checked_state(self, checked: bool);
+}
+
+fn broadcast_radio_checked(broadcaster: JSRef<HTMLInputElement>, group: Option<&str>) {
+ //TODO: if not in document, use root ancestor instead of document
+ let doc = document_from_node(broadcaster).root();
+ let radios = doc.QuerySelectorAll("input[type=\"radio\"]".to_string()).unwrap().root();
+ let mut i = 0;
+ while i < radios.Length() {
+ let node = radios.Item(i).unwrap().root();
+ let radio: JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(*node).unwrap();
+ if radio != broadcaster {
+ //TODO: determine form owner
+ let other_group = radio.get_radio_group();
+ //TODO: ensure compatibility caseless match (https://html.spec.whatwg.org/multipage/infrastructure.html#compatibility-caseless)
+ let group_matches = other_group.as_ref().map(|group| group.as_slice()) == group.as_ref().map(|&group| &*group);
+ if group_matches && radio.Checked() {
+ radio.SetChecked(false);
+ }
+ }
+ i += 1;
+ }
+}
+
+impl<'a> HTMLInputElementHelpers for JSRef<'a, HTMLInputElement> {
+ fn force_relayout(self) {
+ let doc = document_from_node(self).root();
+ doc.content_changed()
+ }
+
+ fn radio_group_updated(self, group: Option<&str>) {
+ if self.Checked() {
+ broadcast_radio_checked(self, group);
+ }
+ }
+
+ // https://html.spec.whatwg.org/multipage/forms.html#radio-button-group
+ fn get_radio_group(self) -> Option<String> {
+ //TODO: determine form owner
+ let elem: JSRef<Element> = ElementCast::from_ref(self);
+ elem.get_attribute(ns!(""), "name")
+ .root()
+ .map(|name| name.Value())
+ }
+
+ fn update_checked_state(self, checked: bool) {
+ self.checked.set(checked);
+ if self.input_type.get() == InputRadio && checked {
+ broadcast_radio_checked(self,
+ self.get_radio_group()
+ .as_ref()
+ .map(|group| group.as_slice()));
+ }
+ //TODO: dispatch change event
+ self.force_relayout();
+ }
}
impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
@@ -72,7 +238,42 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
"disabled" => {
node.set_disabled_state(true);
node.set_enabled_state(false);
- },
+ }
+ "checked" => {
+ self.update_checked_state(true);
+ }
+ "size" => {
+ let parsed = parse_unsigned_integer(value.as_slice().chars());
+ self.size.set(parsed.unwrap_or(DEFAULT_INPUT_SIZE));
+ self.force_relayout();
+ }
+ "type" => {
+ self.input_type.set(match value.as_slice() {
+ "button" => InputButton(None),
+ "submit" => InputButton(Some(DEFAULT_SUBMIT_VALUE)),
+ "reset" => InputButton(Some(DEFAULT_RESET_VALUE)),
+ "file" => InputFile,
+ "radio" => InputRadio,
+ "checkbox" => InputCheckbox,
+ "password" => InputPassword,
+ _ => InputText,
+ });
+ if self.input_type.get() == InputRadio {
+ self.radio_group_updated(self.get_radio_group()
+ .as_ref()
+ .map(|group| group.as_slice()));
+ }
+ self.force_relayout();
+ }
+ "value" => {
+ *self.value.borrow_mut() = Some(value);
+ self.force_relayout();
+ }
+ "name" => {
+ if self.input_type.get() == InputRadio {
+ self.radio_group_updated(Some(value.as_slice()));
+ }
+ }
_ => ()
}
}
@@ -89,7 +290,33 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
node.set_disabled_state(false);
node.set_enabled_state(true);
node.check_ancestors_disabled_state_for_form_control();
- },
+ }
+ "checked" => {
+ self.update_checked_state(false);
+ }
+ "size" => {
+ self.size.set(DEFAULT_INPUT_SIZE);
+ self.force_relayout();
+ }
+ "type" => {
+ if self.input_type.get() == InputRadio {
+ broadcast_radio_checked(*self,
+ self.get_radio_group()
+ .as_ref()
+ .map(|group| group.as_slice()));
+ }
+ self.input_type.set(InputText);
+ self.force_relayout();
+ }
+ "value" => {
+ *self.value.borrow_mut() = None;
+ self.force_relayout();
+ }
+ "name" => {
+ if self.input_type.get() == InputRadio {
+ self.radio_group_updated(None);
+ }
+ }
_ => ()
}
}
@@ -117,6 +344,23 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
node.check_disabled_attribute();
}
}
+
+ fn handle_event(&self, event: JSRef<Event>) {
+ match self.super_type() {
+ Some(s) => {
+ s.handle_event(event);
+ }
+ _ => (),
+ }
+
+ if "click" == event.Type().as_slice() && !event.DefaultPrevented() {
+ match self.input_type.get() {
+ InputCheckbox => self.SetChecked(!self.checked.get()),
+ InputRadio => self.SetChecked(true),
+ _ => {}
+ }
+ }
+ }
}
impl Reflectable for HTMLInputElement {
diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl
index 1caa9137e0b..a418f50928c 100644
--- a/components/script/dom/webidls/HTMLInputElement.webidl
+++ b/components/script/dom/webidls/HTMLInputElement.webidl
@@ -10,7 +10,7 @@ interface HTMLInputElement : HTMLElement {
// attribute DOMString autocomplete;
// attribute boolean autofocus;
// attribute boolean defaultChecked;
- // attribute boolean checked;
+ attribute boolean checked;
// attribute DOMString dirName;
attribute boolean disabled;
//readonly attribute HTMLFormElement? form;
@@ -29,17 +29,17 @@ interface HTMLInputElement : HTMLElement {
// attribute DOMString min;
// attribute long minLength;
// attribute boolean multiple;
- // attribute DOMString name;
+ attribute DOMString name;
// attribute DOMString pattern;
// attribute DOMString placeholder;
// attribute boolean readOnly;
// attribute boolean required;
- // attribute unsigned long size;
+ attribute unsigned long size;
// attribute DOMString src;
// attribute DOMString step;
- // attribute DOMString type;
+ // attribute DOMString type; //XXXjdm need binaryName
// attribute DOMString defaultValue;
- //[TreatNullAs=EmptyString] attribute DOMString value;
+ [TreatNullAs=EmptyString] attribute DOMString value;
// attribute Date? valueAsDate;
// attribute unrestricted double valueAsNumber;
// attribute double valueLow;
diff --git a/components/script/script_task.rs b/components/script/script_task.rs
index 88c84ef5b74..a8c7901bc77 100644
--- a/components/script/script_task.rs
+++ b/components/script/script_task.rs
@@ -928,9 +928,14 @@ impl ScriptTask {
let temp_node =
node::from_untrusted_node_address(
- self.js_runtime.deref().ptr, node_address);
+ self.js_runtime.deref().ptr, node_address).root();
+
+ let maybe_node = if !temp_node.is_element() {
+ temp_node.ancestors().find(|node| node.is_element())
+ } else {
+ Some(*temp_node)
+ };
- let maybe_node = temp_node.root().ancestors().find(|node| node.is_element());
match maybe_node {
Some(node) => {
debug!("clicked on {:s}", node.debug_str());
@@ -945,6 +950,8 @@ impl ScriptTask {
true, true).root();
let eventtarget: JSRef<EventTarget> = EventTargetCast::from_ref(node);
let _ = eventtarget.dispatch_event_with_target(None, *event);
+
+ window.flush_layout(ReflowForDisplay);
}
None => {}
}
diff --git a/components/style/user-agent.css b/components/style/user-agent.css
index c2719a130f3..b1f7c6f10cf 100644
--- a/components/style/user-agent.css
+++ b/components/style/user-agent.css
@@ -116,4 +116,15 @@ area:link,
link:link { color: blue }
script { display: none }
style { display: none }
+input { background: white; min-height: 1.0em; max-height: 1.0em; padding: 0em; padding-left: 0.25em; padding-right: 0.25em; border: solid lightgrey 1px; color: black; white-space: nowrap; }
+input[type="button"],
+input[type="submit"],
+input[type="reset"] { background: lightgrey; border-top: solid 1px #EEEEEE; border-left: solid 1px #CCCCCC; border-right: solid 1px #999999; border-bottom: solid 1px #999999; text-align: center; vertical-align: middle; color: black; }
input[type="hidden"] { display: none !important }
+input[type="checkbox"],
+input[type="radio"] { font-family: monospace !important; border: none !important; background: transparent; }
+
+input[type="checkbox"]::before { content: "[ ]"; padding: 0; }
+input[type="checkbox"][checked]::before { content: "[✓]"; }
+input[type="radio"]::before { content: "( )"; padding: 0; }
+input[type="radio"][checked]::before { content: "(●)"; }
diff --git a/tests/html/test-inputs.html b/tests/html/test-inputs.html
new file mode 100644
index 00000000000..0c94ed87d94
--- /dev/null
+++ b/tests/html/test-inputs.html
@@ -0,0 +1,17 @@
+<style>
+</style>
+<div><input type="checkbox"></div>
+<div><input type="text" size="30" value="placeholder"></div>
+<div><input type="text" size="10" value="whefghijklmnopqrstuvwxyzabcdefg"></div>
+<div><input type="text" value=""><div>
+<div><input type="submit"><input type="reset"><div>
+<div><input type="checkbox"></div>
+<div><input type="checkbox" checked></div>
+<div>group 1
+ <div><input type="radio"></div>
+ <div><input type="radio" checked></div>
+</div>
+<div>group 2
+ <div><input type="radio" name="a" checked></div>
+ <div><input type="radio" name="a"></div>
+</div>