Implement simple layout for text, button, radio, and checkbox inputs. Implement simple interaction for checkboxes and radio buttons.

This commit is contained in:
Josh Matthews 2014-09-29 02:59:39 -04:00
parent 9b20d6e7d2
commit f70bb68503
10 changed files with 422 additions and 50 deletions

View file

@ -1516,7 +1516,7 @@ impl Flow for BlockFlow {
/// any fragments it is responsible for flowing. /// any fragments it is responsible for flowing.
/// ///
/// TODO(pcwalton): Inline blocks. /// 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 _scope = layout_debug_scope!("block::bubble_inline_sizes {:s}", self.base.debug_id());
let mut flags = self.base.flags; let mut flags = self.base.flags;
@ -1572,7 +1572,7 @@ impl Flow for BlockFlow {
max(intrinsic_inline_sizes.preferred_inline_size, max(intrinsic_inline_sizes.preferred_inline_size,
left_float_width + right_float_width); 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, intrinsic_inline_sizes.minimum_inline_size = max(intrinsic_inline_sizes.minimum_inline_size,
fragment_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, intrinsic_inline_sizes.preferred_inline_size = max(intrinsic_inline_sizes.preferred_inline_size,

View file

@ -27,12 +27,12 @@ use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use flow::{Descendants, AbsDescendants}; use flow::{Descendants, AbsDescendants};
use flow; use flow;
use flow_ref::FlowRef; use flow_ref::FlowRef;
use fragment::{InlineBlockFragment, InlineBlockFragmentInfo}; use fragment::{InlineBlockFragment, InlineBlockFragmentInfo, InputFragment};
use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo}; use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo};
use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment}; use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment};
use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo}; use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo};
use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment}; use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment, InputRadioButton};
use fragment::{UnscannedTextFragmentInfo}; use fragment::{UnscannedTextFragmentInfo, InputCheckbox, InputButton, InputText, InputFile};
use inline::{InlineFragments, InlineFlow}; use inline::{InlineFragments, InlineFlow};
use parallel; use parallel;
use table_wrapper::TableWrapperFlow; use table_wrapper::TableWrapperFlow;
@ -49,7 +49,7 @@ use wrapper::{Before, After, Normal};
use gfx::display_list::OpaqueNode; use gfx::display_list::OpaqueNode;
use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId}; 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::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId};
use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId}; use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId};
use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId}; 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") => InputCheckbox(node.get_input_checked()),
Some("button") | Some("submit") | Some("reset") =>
InputButton(node.get_input_value().len() as u32),
Some("radio") => InputRadioButton(node.get_input_checked()),
Some("file") => InputFile(node.get_input_size()),
_ => InputText(node.get_input_size()),
};
InputFragment(data)
}
/// Builds specific `Fragment` info for the given node. /// Builds specific `Fragment` info for the given node.
/// ///
/// This does *not* construct the text for generated content (but, for generated content with /// 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) pub fn build_specific_fragment_info_for_node(&mut self, node: &ThreadSafeLayoutNode)
-> SpecificFragmentInfo { -> SpecificFragmentInfo {
match node.type_id() { match node.type_id() {
Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => {
IframeFragment(IframeFragmentInfo::new(node))
}
Some(ElementNodeTypeId(HTMLImageElementTypeId)) => { Some(ElementNodeTypeId(HTMLImageElementTypeId)) => {
self.build_fragment_info_for_image(node, node.image_url()) self.build_fragment_info_for_image(node, node.image_url())
} }
Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => { Some(ElementNodeTypeId(HTMLInputElementTypeId)) => {
IframeFragment(IframeFragmentInfo::new(node)) self.build_fragment_info_for_input(node)
} }
Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => { Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => {
let data = node.get_object_data(); 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 // Special case: If this is generated content, then we need to initialize the accumulator
// with the fragment corresponding to that content. // 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 fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::new(node));
let mut fragment = Fragment::new_from_specific_info(node, fragment_info); let mut fragment = Fragment::new_from_specific_info(node, fragment_info);
inline_fragment_accumulator.fragments.push(&mut fragment); inline_fragment_accumulator.fragments.push(&mut fragment);

View file

@ -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. /// Info specific to the kind of fragment. Keep this enum small.
#[deriving(Clone)] #[deriving(Clone)]
pub enum SpecificFragmentInfo { pub enum SpecificFragmentInfo {
InlineBlockFragment(InlineBlockFragmentInfo),
GenericFragment, GenericFragment,
ImageFragment(ImageFragmentInfo),
IframeFragment(IframeFragmentInfo), IframeFragment(IframeFragmentInfo),
ImageFragment(ImageFragmentInfo),
InlineBlockFragment(InlineBlockFragmentInfo),
InputFragment(InputFragmentInfo),
ScannedTextFragment(ScannedTextFragmentInfo), ScannedTextFragment(ScannedTextFragmentInfo),
TableFragment, TableFragment,
TableCellFragment, TableCellFragment,
@ -155,6 +156,38 @@ impl InlineBlockFragmentInfo {
} }
} }
/// A fragment that represents a displayable form element
#[deriving(Clone)]
pub enum InputFragmentInfo {
InputButton(u32),
InputText(u32),
InputCheckbox(bool),
InputRadioButton(bool),
InputFile(u32),
}
impl InputFragmentInfo {
fn size(&self) -> Option<u32> {
match self {
&InputText(size) | &InputFile(size) | &InputButton(size) => Some(size),
_ => None,
}
}
/// Returns the original inline-size of the input.
fn input_inline_size(&self, font_style: &FontStyle, layout_context: &LayoutContext) -> Au {
match self.size() {
Some(size) => {
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 * (size as i32 - 1) + metrics.max_advance
}
None => Au::from_px(10)
}
}
}
/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc. /// A fragment that represents a replaced content image and its accompanying borders, shadows, etc.
#[deriving(Clone)] #[deriving(Clone)]
pub struct ImageFragmentInfo { pub struct ImageFragmentInfo {
@ -499,7 +532,8 @@ impl Fragment {
/// replaced elements. /// replaced elements.
fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes { fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes {
let (use_margins, use_padding) = match self.specific { 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), TableFragment | TableCellFragment => (false, true),
TableWrapperFragment => (true, false), TableWrapperFragment => (true, false),
TableRowFragment => (false, false), TableRowFragment => (false, false),
@ -1129,7 +1163,7 @@ impl Fragment {
text_fragment)) text_fragment))
} }
GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment | 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 // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
// should have a real `SERVO_DEBUG` system. // should have a real `SERVO_DEBUG` system.
debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin)) debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
@ -1193,7 +1227,7 @@ impl Fragment {
} }
/// Returns the intrinsic inline-sizes of this 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(); let mut result = self.style_specified_intrinsic_inline_size();
match self.specific { match self.specific {
@ -1214,6 +1248,12 @@ impl Fragment {
result.preferred_inline_size = max(result.preferred_inline_size, result.preferred_inline_size = max(result.preferred_inline_size,
image_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) => { ScannedTextFragment(ref text_fragment_info) => {
let range = &text_fragment_info.range; let range = &text_fragment_info.range;
let min_line_inline_size = text_fragment_info.run.min_width_for_range(range); let min_line_inline_size = text_fragment_info.run.min_width_for_range(range);
@ -1260,7 +1300,7 @@ impl Fragment {
pub fn content_inline_size(&self) -> Au { pub fn content_inline_size(&self) -> Au {
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
TableWrapperFragment | InlineBlockFragment(_) => Au(0), TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => Au(0),
ImageFragment(ref image_fragment_info) => { ImageFragment(ref image_fragment_info) => {
image_fragment_info.computed_inline_size() image_fragment_info.computed_inline_size()
} }
@ -1278,7 +1318,8 @@ impl Fragment {
pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au { pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au {
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => Au(0), TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) |
InputFragment(_) => Au(0),
ImageFragment(ref image_fragment_info) => { ImageFragment(ref image_fragment_info) => {
image_fragment_info.computed_block_size() image_fragment_info.computed_block_size()
} }
@ -1312,7 +1353,7 @@ impl Fragment {
-> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> { -> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment | GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment => None, TableRowFragment | TableWrapperFragment | InputFragment(_) => None,
TableColumnFragment(_) => fail!("Table column fragments do not need to split"), TableColumnFragment(_) => fail!("Table column fragments do not need to split"),
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
InlineBlockFragment(_) => fail!("Inline blocks do not get split"), InlineBlockFragment(_) => fail!("Inline blocks do not get split"),
@ -1353,7 +1394,7 @@ impl Fragment {
-> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> { -> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment | GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) => None, TableRowFragment | TableWrapperFragment | InlineBlockFragment(_) | InputFragment(_) => None,
TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
ScannedTextFragment(ref text_fragment_info) => { ScannedTextFragment(ref text_fragment_info) => {
@ -1457,7 +1498,7 @@ impl Fragment {
container_inline_size: Au) { container_inline_size: Au) {
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment => return, TableRowFragment | TableWrapperFragment | InputFragment(_) => return,
TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
UnscannedTextFragment(_) => { UnscannedTextFragment(_) => {
fail!("Unscanned text fragments should have been scanned by now!") fail!("Unscanned text fragments should have been scanned by now!")
@ -1540,7 +1581,7 @@ impl Fragment {
pub fn assign_replaced_block_size_if_necessary(&mut self) { pub fn assign_replaced_block_size_if_necessary(&mut self) {
match self.specific { match self.specific {
GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment |
TableRowFragment | TableWrapperFragment => return, TableRowFragment | TableWrapperFragment | InputFragment(_) => return,
TableColumnFragment(_) => fail!("Table column fragments do not have block_size"), TableColumnFragment(_) => fail!("Table column fragments do not have block_size"),
UnscannedTextFragment(_) => { UnscannedTextFragment(_) => {
fail!("Unscanned text fragments should have been scanned by now!") fail!("Unscanned text fragments should have been scanned by now!")
@ -1682,7 +1723,7 @@ impl Fragment {
InlineBlockFragment(_) | TableWrapperFragment => false, InlineBlockFragment(_) | TableWrapperFragment => false,
GenericFragment | IframeFragment(_) | ImageFragment(_) | ScannedTextFragment(_) | GenericFragment | IframeFragment(_) | ImageFragment(_) | ScannedTextFragment(_) |
TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment | TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment |
UnscannedTextFragment(_) => true, UnscannedTextFragment(_) | InputFragment(_) => true,
} }
} }
} }
@ -1703,6 +1744,7 @@ impl fmt::Show for Fragment {
TableWrapperFragment => "TableWrapperFragment", TableWrapperFragment => "TableWrapperFragment",
UnscannedTextFragment(_) => "UnscannedTextFragment", UnscannedTextFragment(_) => "UnscannedTextFragment",
InlineBlockFragment(_) => "InlineBlockFragment", InlineBlockFragment(_) => "InlineBlockFragment",
InputFragment(_) => "InputFragment",
})); }));
try!(write!(f, "bp {}", self.border_padding)); try!(write!(f, "bp {}", self.border_padding));
try!(write!(f, " ")); try!(write!(f, " "));

View file

@ -955,7 +955,7 @@ impl Flow for InlineFlow {
self 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 _scope = layout_debug_scope!("inline::bubble_inline_sizes {:s}", self.base.debug_id());
let writing_mode = self.base.writing_mode; let writing_mode = self.base.writing_mode;
@ -968,7 +968,7 @@ impl Flow for InlineFlow {
debug!("Flow: measuring {}", *fragment); debug!("Flow: measuring {}", *fragment);
let fragment_intrinsic_inline_sizes = 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 = max(
intrinsic_inline_sizes.minimum_inline_size, intrinsic_inline_sizes.minimum_inline_size,
fragment_intrinsic_inline_sizes.minimum_inline_size); fragment_intrinsic_inline_sizes.minimum_inline_size);

View file

@ -171,7 +171,7 @@ impl Flow for TableFlow {
/// table layout calculation. /// table layout calculation.
/// The maximum min/pref inline-sizes of each column are set from the rows for the automatic /// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
/// table layout calculation. /// 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}", let _scope = layout_debug_scope!("table::bubble_inline_sizes {:s}",
self.block_flow.base.debug_id()); 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.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
max(min_inline_size, pref_inline_size); max(min_inline_size, pref_inline_size);

View file

@ -36,13 +36,14 @@
use css::node_style::StyledNode; use css::node_style::StyledNode;
use util::{LayoutDataAccess, LayoutDataWrapper, PrivateLayoutData}; 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::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived};
use script::dom::bindings::js::JS; use script::dom::bindings::js::JS;
use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId}; use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId};
use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers}; use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers};
use script::dom::htmliframeelement::HTMLIFrameElement; use script::dom::htmliframeelement::HTMLIFrameElement;
use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
use script::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId}; use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId};
use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, SharedLayoutData, TextNodeTypeId}; use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, SharedLayoutData, TextNodeTypeId};
use script::dom::text::Text; use script::dom::text::Text;
@ -184,11 +185,15 @@ impl<'ln> TLayoutNode for LayoutNode<'ln> {
fn text(&self) -> String { fn text(&self) -> String {
unsafe { 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!") 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) return get_content(&after_style.get_box().content)
} }
} }
self.node.text()
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()
}
} }
} }
@ -732,6 +730,36 @@ impl<'ln> ThreadSafeLayoutNode<'ln> {
_ => false _ => false
} }
} }
pub fn get_input_checked(&self) -> bool {
unsafe {
if !self.get().is_htmlinputelement() {
fail!("not an input element!")
}
let input: JS<HTMLInputElement> = self.get_jsmanaged().transmute_copy();
input.get_checked_for_layout()
}
}
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> { pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {

View file

@ -2,26 +2,55 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; 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::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::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::element::{AttributeHandlers, Element, HTMLInputElementTypeId};
use dom::event::Event;
use dom::eventtarget::{EventTarget, NodeTargetTypeId}; use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement; 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 dom::virtualmethods::VirtualMethods;
use servo_util::str::DOMString; use servo_util::str::{DOMString, parse_unsigned_integer};
use string_cache::Atom; 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] #[jstraceable]
#[must_root] #[must_root]
pub struct HTMLInputElement { pub struct HTMLInputElement {
pub htmlelement: HTMLElement, 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 { impl HTMLInputElementDerived for EventTarget {
@ -30,10 +59,17 @@ impl HTMLInputElementDerived for EventTarget {
} }
} }
static DEFAULT_INPUT_SIZE: u32 = 20;
impl HTMLInputElement { impl HTMLInputElement {
fn new_inherited(localName: DOMString, document: JSRef<Document>) -> HTMLInputElement { fn new_inherited(localName: DOMString, document: JSRef<Document>) -> HTMLInputElement {
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,53 @@ impl HTMLInputElement {
} }
} }
pub trait LayoutHTMLInputElementHelpers {
unsafe fn get_checked_for_layout(&self) -> bool;
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_checked_for_layout(&self) -> bool {
(*self.unsafe_get()).checked.get()
}
#[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 => if self.get_checked_for_layout() {
"[X]"
} else {
"[ ]"
}.to_string(),
InputRadio => if self.get_checked_for_layout() {
"(*)"
} else {
"( )"
}.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("".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> { impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> {
// http://www.whatwg.org/html/#dom-fe-disabled // http://www.whatwg.org/html/#dom-fe-disabled
make_bool_getter!(Disabled) make_bool_getter!(Disabled)
@ -53,6 +136,104 @@ impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> {
let elem: JSRef<Element> = ElementCast::from_ref(self); let elem: JSRef<Element> = ElementCast::from_ref(self);
elem.set_bool_attribute("disabled", disabled) 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> { impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
@ -72,7 +253,41 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
"disabled" => { "disabled" => {
node.set_disabled_state(true); node.set_disabled_state(true);
node.set_enabled_state(false); node.set_enabled_state(false);
}, }
"checked" => {
self.update_checked_state(true);
}
"size" => {
self.size.set(parse_unsigned_integer(value.as_slice().chars()).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 +304,33 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
node.set_disabled_state(false); node.set_disabled_state(false);
node.set_enabled_state(true); node.set_enabled_state(true);
node.check_ancestors_disabled_state_for_form_control(); 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 +358,24 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
node.check_disabled_attribute(); 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 | InputRadio => {
self.SetChecked(!self.checked.get());
}
_ => {}
}
}
}
} }
impl Reflectable for HTMLInputElement { impl Reflectable for HTMLInputElement {

View file

@ -10,7 +10,7 @@ interface HTMLInputElement : HTMLElement {
// attribute DOMString autocomplete; // attribute DOMString autocomplete;
// attribute boolean autofocus; // attribute boolean autofocus;
// attribute boolean defaultChecked; // attribute boolean defaultChecked;
// attribute boolean checked; attribute boolean checked;
// attribute DOMString dirName; // attribute DOMString dirName;
attribute boolean disabled; attribute boolean disabled;
//readonly attribute HTMLFormElement? form; //readonly attribute HTMLFormElement? form;
@ -29,17 +29,17 @@ interface HTMLInputElement : HTMLElement {
// attribute DOMString min; // attribute DOMString min;
// attribute long minLength; // attribute long minLength;
// attribute boolean multiple; // attribute boolean multiple;
// attribute DOMString name; attribute DOMString name;
// attribute DOMString pattern; // attribute DOMString pattern;
// attribute DOMString placeholder; // attribute DOMString placeholder;
// attribute boolean readOnly; // attribute boolean readOnly;
// attribute boolean required; // attribute boolean required;
// attribute unsigned long size; attribute unsigned long size;
// attribute DOMString src; // attribute DOMString src;
// attribute DOMString step; // attribute DOMString step;
// attribute DOMString type; // attribute DOMString type; //XXXjdm need binaryName
// attribute DOMString defaultValue; // attribute DOMString defaultValue;
//[TreatNullAs=EmptyString] attribute DOMString value; [TreatNullAs=EmptyString] attribute DOMString value;
// attribute Date? valueAsDate; // attribute Date? valueAsDate;
// attribute unrestricted double valueAsNumber; // attribute unrestricted double valueAsNumber;
// attribute double valueLow; // attribute double valueLow;

View file

@ -116,4 +116,10 @@ area:link,
link:link { color: blue } link:link { color: blue }
script { display: none } script { display: none }
style { 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; }
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="hidden"] { display: none !important }
input[type="checkbox"],
input[type="radio"] { font-family: monospace !important; border: none !important; background: transparent; }

View file

@ -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>