/* 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::element::{HTMLElementTypeId, HTMLAnchorElementTypeId, HTMLBRElementTypeId, HTMLBodyElementTypeId, HTMLCanvasElementTypeId, HTMLDivElementTypeId, HTMLFontElementTypeId, HTMLFormElementTypeId, HTMLHRElementTypeId, HTMLHeadElementTypeId, HTMLHtmlElementTypeId, HTMLImageElementTypeId, HTMLIframeElementTypeId, HTMLInputElementTypeId, HTMLLinkElementTypeId, HTMLListItemElementTypeId, HTMLMetaElementTypeId, HTMLOListElementTypeId, HTMLOptionElementTypeId, HTMLParagraphElementTypeId, HTMLScriptElementTypeId, HTMLSelectElementTypeId, HTMLSmallElementTypeId, HTMLSpanElementTypeId, HTMLStyleElementTypeId, HTMLTableSectionElementTypeId, HTMLTableCellElementTypeId, HTMLTableElementTypeId, HTMLTableRowElementTypeId, HTMLTextAreaElementTypeId, HTMLTitleElementTypeId, HTMLUListElementTypeId, UnknownElementTypeId}; use dom::element::{HTMLDivElement, HTMLFontElement, HTMLFormElement, HTMLHeadElement, HTMLHeadingElement, HTMLHtmlElement, HTMLInputElement, HTMLLinkElement, HTMLOptionElement, HTMLParagraphElement, HTMLListItemElement, HTMLSelectElement, HTMLSmallElement, HTMLSpanElement, HTMLTableCellElement}; use dom::element::{HTMLHeadingElementTypeId, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6}; use dom::htmlbrelement::HTMLBRElement; use dom::htmlanchorelement::HTMLAnchorElement; use dom::htmlbodyelement::HTMLBodyElement; use dom::htmlcanvaselement::HTMLCanvasElement; use dom::htmlhrelement::HTMLHRElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::htmlimageelement::HTMLImageElement; use dom::htmlmetaelement::HTMLMetaElement; use dom::htmlolistelement::HTMLOListElement; use dom::htmlscriptelement::HTMLScriptElement; use dom::htmlstyleelement::HTMLStyleElement; use dom::htmltableelement::HTMLTableElement; use dom::htmltablerowelement::HTMLTableRowElement; use dom::htmltablesectionelement::HTMLTableSectionElement; use dom::htmltextareaelement::HTMLTextAreaElement; use dom::htmltitleelement::HTMLTitleElement; use dom::htmlulistelement::HTMLUListElement; use dom::element::Element; use dom::htmlelement::HTMLElement; use dom::node::{AbstractNode, Comment, Doctype, ElementNodeTypeId, Node, ScriptView}; use dom::node::{Text}; use dom::bindings::utils::str; use html::cssparse::{InlineProvenance, StylesheetProvenance, UrlProvenance, spawn_css_parser}; use js::jsapi::JSContext; use newcss::stylesheet::Stylesheet; use std::cast; use std::cell::Cell; use std::comm; use std::comm::{Chan, Port, SharedChan}; use std::str::eq_slice; use std::result; use std::task; use hubbub::hubbub; use servo_msg::constellation_msg::SubpageId; use servo_net::image_cache_task::ImageCacheTask; use servo_net::image_cache_task; use servo_net::resource_task::{Done, Load, Payload, ResourceTask}; use servo_util::tree::TreeUtils; use servo_util::url::make_url; use extra::net::url::Url; use extra::net::url; use extra::future::{Future, from_port}; use geom::size::Size2D; macro_rules! handle_element( ($cx: expr, $tag:expr, $string:expr, $type_id:expr, $ctor:ident, [ $(($field:ident : $field_init:expr)),* ]) => ( if eq_slice($tag, $string) { let _element = @$ctor { parent: HTMLElement::new($type_id, ($tag).to_str()), $( $field: $field_init, )* }; unsafe { return Node::as_abstract_node(cx, _element); } } ) ) macro_rules! handle_htmlelement( ($cx: expr, $tag:expr, $string:expr, $type_id:expr, $ctor:ident) => ( if eq_slice($tag, $string) { let _element = @HTMLElement::new($type_id, ($tag).to_str()); unsafe { return Node::as_abstract_node(cx, _element); } } ) ) type JSResult = ~[~[u8]]; enum CSSMessage { CSSTaskNewFile(StylesheetProvenance), CSSTaskExit } enum JSMessage { JSTaskNewFile(Url), JSTaskExit } /// Messages generated by the HTML parser upon discovery of additional resources pub enum HtmlDiscoveryMessage { HtmlDiscoveredStyle(Stylesheet), HtmlDiscoveredIFrame((Url, SubpageId, Future>)), HtmlDiscoveredScript(JSResult) } pub struct HtmlParserResult { root: AbstractNode, discovery_port: Port, } trait NodeWrapping { unsafe fn to_hubbub_node(self) -> hubbub::NodeDataPtr; unsafe fn from_hubbub_node(n: hubbub::NodeDataPtr) -> Self; } impl NodeWrapping for AbstractNode { unsafe fn to_hubbub_node(self) -> hubbub::NodeDataPtr { cast::transmute(self) } unsafe fn from_hubbub_node(n: hubbub::NodeDataPtr) -> AbstractNode { cast::transmute(n) } } /** Runs a task that coordinates parsing links to css stylesheets. This function should be spawned in a separate task and spins waiting for the html builder to find links to css stylesheets and sends off tasks to parse each link. When the html process finishes, it notifies the listener, who then collects the css rules from each task it spawned, collates them, and sends them to the given result channel. # Arguments * `to_parent` - A channel on which to send back the full set of rules. * `from_parent` - A port on which to receive new links. */ fn css_link_listener(to_parent: SharedChan, from_parent: Port, resource_task: ResourceTask) { let mut result_vec = ~[]; loop { match from_parent.recv() { CSSTaskNewFile(provenance) => { result_vec.push(spawn_css_parser(provenance, resource_task.clone())); } CSSTaskExit => { break; } } } // Send the sheets back in order // FIXME: Shouldn't wait until after we've recieved CSSTaskExit to start sending these for result_vec.iter().advance |port| { to_parent.send(HtmlDiscoveredStyle(port.recv())); } } fn js_script_listener(to_parent: SharedChan, from_parent: Port, resource_task: ResourceTask) { let mut result_vec = ~[]; loop { match from_parent.recv() { JSTaskNewFile(url) => { let (result_port, result_chan) = comm::stream(); let resource_task = resource_task.clone(); do task::spawn { let (input_port, input_chan) = comm::stream(); // TODO: change copy to move once we can move into closures resource_task.send(Load(url.clone(), input_chan)); let mut buf = ~[]; loop { match input_port.recv() { Payload(data) => { buf.push_all(data); } Done(Ok(*)) => { result_chan.send(Some(buf)); break; } Done(Err(*)) => { error!("error loading script %s", url.to_str()); result_chan.send(None); break; } } } } result_vec.push(result_port); } JSTaskExit => { break; } } } let js_scripts = result_vec.iter().filter_map(|result_port| result_port.recv()).collect(); to_parent.send(HtmlDiscoveredScript(js_scripts)); } // Silly macros to handle constructing DOM nodes. This produces bad code and should be optimized // via atomization (issue #85). fn build_element_from_tag(cx: *JSContext, tag: &str) -> AbstractNode { // TODO (Issue #85): use atoms handle_element!(cx, tag, "a", HTMLAnchorElementTypeId, HTMLAnchorElement, []); handle_element!(cx, tag, "br", HTMLBRElementTypeId, HTMLBRElement, []); handle_element!(cx, tag, "body", HTMLBodyElementTypeId, HTMLBodyElement, []); handle_element!(cx, tag, "canvas", HTMLCanvasElementTypeId, HTMLCanvasElement, []); handle_element!(cx, tag, "div", HTMLDivElementTypeId, HTMLDivElement, []); handle_element!(cx, tag, "font", HTMLFontElementTypeId, HTMLFontElement, []); handle_element!(cx, tag, "form", HTMLFormElementTypeId, HTMLFormElement, []); handle_element!(cx, tag, "hr", HTMLHRElementTypeId, HTMLHRElement, []); handle_element!(cx, tag, "head", HTMLHeadElementTypeId, HTMLHeadElement, []); handle_element!(cx, tag, "html", HTMLHtmlElementTypeId, HTMLHtmlElement, []); handle_element!(cx, tag, "input", HTMLInputElementTypeId, HTMLInputElement, []); handle_element!(cx, tag, "link", HTMLLinkElementTypeId, HTMLLinkElement, []); handle_element!(cx, tag, "li", HTMLListItemElementTypeId, HTMLListItemElement, []); handle_element!(cx, tag, "meta", HTMLMetaElementTypeId, HTMLMetaElement, []); handle_element!(cx, tag, "ol", HTMLOListElementTypeId, HTMLOListElement, []); handle_element!(cx, tag, "option", HTMLOptionElementTypeId, HTMLOptionElement, []); handle_element!(cx, tag, "p", HTMLParagraphElementTypeId, HTMLParagraphElement, []); handle_element!(cx, tag, "script", HTMLScriptElementTypeId, HTMLScriptElement, []); handle_element!(cx, tag, "select", HTMLSelectElementTypeId, HTMLSelectElement, []); handle_element!(cx, tag, "small", HTMLSmallElementTypeId, HTMLSmallElement, []); handle_element!(cx, tag, "span", HTMLSpanElementTypeId, HTMLSpanElement, []); handle_element!(cx, tag, "style", HTMLStyleElementTypeId, HTMLStyleElement, []); handle_element!(cx, tag, "tbody", HTMLTableSectionElementTypeId, HTMLTableSectionElement, []); handle_element!(cx, tag, "td", HTMLTableCellElementTypeId, HTMLTableCellElement, []); handle_element!(cx, tag, "table", HTMLTableElementTypeId, HTMLTableElement, []); handle_element!(cx, tag, "textarea",HTMLTextAreaElementTypeId, HTMLTextAreaElement, []); handle_element!(cx, tag, "tr", HTMLTableRowElementTypeId, HTMLTableRowElement, []); handle_element!(cx, tag, "title", HTMLTitleElementTypeId, HTMLTitleElement, []); handle_element!(cx, tag, "ul", HTMLUListElementTypeId, HTMLUListElement, []); handle_element!(cx, tag, "img", HTMLImageElementTypeId, HTMLImageElement, [(image: None)]); handle_element!(cx, tag, "iframe", HTMLIframeElementTypeId, HTMLIFrameElement, [(frame: None), (size_future_chan: None), (subpage_id: None)]); handle_element!(cx, tag, "h1", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading1)]); handle_element!(cx, tag, "h2", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading2)]); handle_element!(cx, tag, "h3", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading3)]); handle_element!(cx, tag, "h4", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading4)]); handle_element!(cx, tag, "h5", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading5)]); handle_element!(cx, tag, "h6", HTMLHeadingElementTypeId, HTMLHeadingElement, [(level: Heading6)]); handle_htmlelement!(cx, tag, "aside", HTMLElementTypeId, HTMLElement); handle_htmlelement!(cx, tag, "b", HTMLElementTypeId, HTMLElement); handle_htmlelement!(cx, tag, "i", HTMLElementTypeId, HTMLElement); handle_htmlelement!(cx, tag, "section", HTMLElementTypeId, HTMLElement); unsafe { Node::as_abstract_node(cx, @Element::new(UnknownElementTypeId, tag.to_str())) } } pub fn parse_html(cx: *JSContext, url: Url, resource_task: ResourceTask, image_cache_task: ImageCacheTask, next_subpage_id: SubpageId) -> HtmlParserResult { debug!("Hubbub: parsing %?", url); // Spawn a CSS parser to receive links to CSS style sheets. let resource_task2 = resource_task.clone(); let (discovery_port, discovery_chan) = comm::stream(); let discovery_chan = SharedChan::new(discovery_chan); let stylesheet_chan = Cell::new(discovery_chan.clone()); let (css_msg_port, css_msg_chan) = comm::stream(); let css_msg_port = Cell::new(css_msg_port); do spawn { css_link_listener(stylesheet_chan.take(), css_msg_port.take(), resource_task2.clone()); } let css_chan = SharedChan::new(css_msg_chan); // Spawn a JS parser to receive JavaScript. let resource_task2 = resource_task.clone(); let js_result_chan = Cell::new(discovery_chan.clone()); let (js_msg_port, js_msg_chan) = comm::stream(); let js_msg_port = Cell::new(js_msg_port); do spawn { js_script_listener(js_result_chan.take(), js_msg_port.take(), resource_task2.clone()); } let js_chan = SharedChan::new(js_msg_chan); let url2 = url.clone(); let url3 = url.clone(); // Build the root node. let root = @HTMLHtmlElement { parent: HTMLElement::new(HTMLHtmlElementTypeId, ~"html") }; let root = unsafe { Node::as_abstract_node(cx, root) }; debug!("created new node"); let mut parser = hubbub::Parser("UTF-8", false); debug!("created parser"); parser.set_document_node(unsafe { root.to_hubbub_node() }); parser.enable_scripting(true); parser.enable_styling(true); let (css_chan2, css_chan3, js_chan2) = (css_chan.clone(), css_chan.clone(), js_chan.clone()); let next_subpage_id = Cell::new(next_subpage_id); parser.set_tree_handler(~hubbub::TreeHandler { create_comment: |data: ~str| { debug!("create comment"); unsafe { Node::as_abstract_node(cx, @Comment::new(data)).to_hubbub_node() } }, create_doctype: |doctype: ~hubbub::Doctype| { debug!("create doctype"); let ~hubbub::Doctype {name: name, public_id: public_id, system_id: system_id, force_quirks: force_quirks } = doctype; let node = @Doctype::new(name, public_id, system_id, force_quirks); unsafe { Node::as_abstract_node(cx, node).to_hubbub_node() } }, create_element: |tag: ~hubbub::Tag| { debug!("create element"); let node = build_element_from_tag(cx, tag.name); debug!("-- attach attrs"); do node.as_mut_element |element| { for tag.attributes.iter().advance |attr| { element.set_attr(&str(attr.name.clone()), &str(attr.value.clone())); } } // Spawn additional parsing, network loads, etc. from tag and attrs match node.type_id() { // Handle CSS style sheets from elements ElementNodeTypeId(HTMLLinkElementTypeId) => { do node.with_imm_element |element| { match (element.get_attr("rel"), element.get_attr("href")) { (Some(rel), Some(href)) => { if rel == "stylesheet" { debug!("found CSS stylesheet: %s", href); let url = make_url(href.to_str(), Some(url2.clone())); css_chan2.send(CSSTaskNewFile(UrlProvenance(url))); } } _ => {} } } } ElementNodeTypeId(HTMLIframeElementTypeId) => { let iframe_chan = Cell::new(discovery_chan.clone()); do node.with_mut_iframe_element |iframe_element| { let iframe_chan = iframe_chan.take(); let elem = &mut iframe_element.parent.parent; let src_opt = elem.get_attr("src").map(|x| x.to_str()); for src_opt.iter().advance |src| { let iframe_url = make_url(src.clone(), Some(url2.clone())); iframe_element.frame = Some(iframe_url.clone()); // Size future let (port, chan) = comm::oneshot(); iframe_element.size_future_chan = Some(chan); let size_future = from_port(port); // Subpage Id let subpage_id = next_subpage_id.take(); iframe_element.subpage_id = Some(subpage_id); next_subpage_id.put_back(SubpageId(*subpage_id + 1)); iframe_chan.send(HtmlDiscoveredIFrame((iframe_url, subpage_id, size_future))); } } } ElementNodeTypeId(HTMLImageElementTypeId) => { do node.with_mut_image_element |image_element| { let elem = &mut image_element.parent.parent; let src_opt = elem.get_attr("src").map(|x| x.to_str()); match src_opt { None => {} Some(src) => { let img_url = make_url(src, Some(url2.clone())); image_element.image = Some(img_url.clone()); // inform the image cache to load this, but don't store a handle. // TODO (Issue #84): don't prefetch if we are within a