Merge pull request #3352 from mbrubeck/link-style

Move link rel=stylesheet fetching to layout task. r=jdm
This commit is contained in:
Matt Brubeck 2014-09-16 08:16:29 -07:00
commit 0e2cdc5cca
17 changed files with 154 additions and 196 deletions

1
Cargo.lock generated
View file

@ -256,6 +256,7 @@ dependencies = [
name = "layout"
version = "0.0.1"
dependencies = [
"encoding 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding#12b6610adff6eddc060691888c36017cd3ad57f7)",
"geom 0.1.0 (git+https://github.com/servo/rust-geom#2982b770db6e5e3270305e0fd6b8068f6f80a489)",
"gfx 0.0.1",
"layout_traits 0.0.1",

View file

@ -80,7 +80,7 @@ impl Pipeline {
script_port,
constellation_chan.clone(),
failure.clone(),
resource_task,
resource_task.clone(),
image_cache_task.clone(),
window_size);
ScriptControlChan(script_chan)
@ -117,6 +117,7 @@ impl Pipeline {
failure,
script_chan.clone(),
render_chan.clone(),
resource_task,
image_cache_task,
font_cache_task,
opts.clone(),

View file

@ -31,6 +31,9 @@ path = "../net"
[dependencies.util]
path = "../util"
[dependencies.encoding]
git = "https://github.com/lifthrasiir/rust-encoding"
[dependencies.geom]
git = "https://github.com/servo/rust-geom"

View file

@ -21,6 +21,8 @@ use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods, ToGfxColor};
use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
use collections::dlist::DList;
use encoding::EncodingRef;
use encoding::all::UTF_8;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
@ -33,7 +35,7 @@ use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
use script::dom::bindings::js::JS;
use script::dom::node::{ElementNodeTypeId, LayoutDataRef, Node};
use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId};
use script::layout_interface::{AddStylesheetMsg, ScriptLayoutChan};
use script::layout_interface::{AddStylesheetMsg, LoadStylesheetMsg, ScriptLayoutChan};
use script::layout_interface::{TrustedNodeAddress, ContentBoxesResponse, ExitNowMsg};
use script::layout_interface::{ContentBoxResponse, HitTestResponse, MouseOverResponse};
use script::layout_interface::{ContentChangedDocumentDamage, LayoutChan, Msg, PrepareToExitMsg};
@ -46,6 +48,7 @@ use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
use gfx::font_cache_task::{FontCacheTask};
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
use servo_util::bloom::BloomFilter;
use servo_net::resource_task::{ResourceTask, load_bytes_iter};
use servo_util::geometry::Au;
use servo_util::geometry;
use servo_util::logical_geometry::LogicalPoint;
@ -112,6 +115,9 @@ pub struct LayoutTask {
/// The channel on which messages can be sent to the time profiler.
pub time_profiler_chan: TimeProfilerChan,
/// The channel on which messages can be sent to the resource task.
pub resource_task: ResourceTask,
/// The channel on which messages can be sent to the image cache.
pub image_cache_task: ImageCacheTask,
@ -298,6 +304,7 @@ impl LayoutTaskFactory for LayoutTask {
failure_msg: Failure,
script_chan: ScriptControlChan,
render_chan: RenderChan,
resource_task: ResourceTask,
img_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
opts: Opts,
@ -316,6 +323,7 @@ impl LayoutTaskFactory for LayoutTask {
constellation_chan,
script_chan,
render_chan,
resource_task,
img_cache_task,
font_cache_task,
&opts,
@ -365,6 +373,7 @@ impl LayoutTask {
constellation_chan: ConstellationChan,
script_chan: ScriptControlChan,
render_chan: RenderChan,
resource_task: ResourceTask,
image_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
opts: &Opts,
@ -387,6 +396,7 @@ impl LayoutTask {
script_chan: script_chan,
render_chan: render_chan,
time_profiler_chan: time_profiler_chan,
resource_task: resource_task,
image_cache_task: image_cache_task.clone(),
font_cache_task: font_cache_task,
opts: opts.clone(),
@ -494,6 +504,7 @@ impl LayoutTask {
fn handle_script_request<'a>(&'a self, request: Msg, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) -> bool {
match request {
AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet, possibly_locked_rw_data),
LoadStylesheetMsg(url) => self.handle_load_stylesheet(url, possibly_locked_rw_data),
GetRPCMsg(response_chan) => {
response_chan.send(
box LayoutRPCImpl(
@ -567,6 +578,18 @@ impl LayoutTask {
response_port.recv()
}
fn handle_load_stylesheet<'a>(&'a self, url: Url, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
// TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
let environment_encoding = UTF_8 as EncodingRef;
let (metadata, iter) = load_bytes_iter(&self.resource_task, url);
let protocol_encoding_label = metadata.charset.as_ref().map(|s| s.as_slice());
let final_url = metadata.final_url;
let sheet = Stylesheet::from_bytes_iter(iter, final_url, protocol_encoding_label, Some(environment_encoding));
self.handle_add_stylesheet(sheet, possibly_locked_rw_data);
}
fn handle_add_stylesheet<'a>(&'a self, sheet: Stylesheet, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) {
// Find all font-face rules and notify the font cache of them.
// GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!)

View file

@ -29,6 +29,7 @@ extern crate servo_msg = "msg";
extern crate servo_util = "util";
extern crate collections;
extern crate encoding;
extern crate green;
extern crate libc;
extern crate sync;

View file

@ -23,6 +23,7 @@ use gfx::render_task::RenderChan;
use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
use servo_msg::constellation_msg::Failure;
use servo_net::image_cache_task::ImageCacheTask;
use servo_net::resource_task::ResourceTask;
use servo_util::opts::Opts;
use servo_util::time::TimeProfilerChan;
use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel};
@ -48,6 +49,7 @@ pub trait LayoutTaskFactory {
failure_msg: Failure,
script_chan: ScriptControlChan,
render_chan: RenderChan,
resource_task: ResourceTask,
img_cache_task: ImageCacheTask,
font_cache_task: FontCacheTask,
opts: Opts,

View file

@ -219,6 +219,34 @@ impl ResourceManager {
}
}
/// Load a URL asynchronously and iterate over chunks of bytes from the response.
pub fn load_bytes_iter(resource_task: &ResourceTask, url: Url) -> (Metadata, ProgressMsgPortIterator) {
let (input_chan, input_port) = channel();
resource_task.send(Load(LoadData::new(url), input_chan));
let response = input_port.recv();
let iter = ProgressMsgPortIterator { progress_port: response.progress_port };
(response.metadata, iter)
}
/// Iterator that reads chunks of bytes from a ProgressMsg port
pub struct ProgressMsgPortIterator {
progress_port: Receiver<ProgressMsg>
}
impl Iterator<Vec<u8>> for ProgressMsgPortIterator {
fn next(&mut self) -> Option<Vec<u8>> {
match self.progress_port.recv() {
Payload(data) => Some(data),
Done(Ok(())) => None,
Done(Err(e)) => {
error!("error receiving bytes: {}", e);
None
}
}
}
}
#[test]
fn test_exit() {
let resource_task = new_resource_task();

View file

@ -2,20 +2,25 @@
* 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::attr::AttrHelpers;
use dom::bindings::codegen::Bindings::HTMLLinkElementBinding;
use dom::bindings::codegen::InheritTypes::HTMLLinkElementDerived;
use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast};
use dom::bindings::js::{JSRef, Temporary};
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
use dom::bindings::utils::{Reflectable, Reflector};
use dom::document::Document;
use dom::element::HTMLLinkElementTypeId;
use dom::element::{AttributeHandlers, Element, HTMLLinkElementTypeId};
use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
use dom::virtualmethods::VirtualMethods;
use layout_interface::{LayoutChan, LoadStylesheetMsg};
use servo_util::atom::Atom;
use servo_util::str::DOMString;
use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS};
use servo_util::namespace::Null;
use std::ascii::StrAsciiExt;
use url::UrlParser;
#[deriving(Encodable)]
pub struct HTMLLinkElement {
@ -72,6 +77,55 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLLinkElement> {
_ => ()
}
}
fn bind_to_tree(&self, tree_in_doc: bool) {
match self.super_type() {
Some(ref s) => s.bind_to_tree(tree_in_doc),
_ => ()
}
if tree_in_doc {
let element: &JSRef<Element> = ElementCast::from_ref(self);
// FIXME: workaround for https://github.com/mozilla/rust/issues/13246;
// we get unrooting order failures if these are inside the match.
let rel = {
let rel = element.get_attribute(Null, "rel").root();
rel.map(|rel| rel.deref().value().as_slice().to_string())
};
let href = {
let href = element.get_attribute(Null, "href").root();
href.map(|href| href.deref().value().as_slice().to_string())
};
match (rel, href) {
(Some(ref rel), Some(ref href)) => {
if rel.as_slice().split(HTML_SPACE_CHARACTERS.as_slice())
.any(|s| s.as_slice().eq_ignore_ascii_case("stylesheet")) {
self.handle_stylesheet_url(href.as_slice());
}
}
_ => {}
}
}
}
}
trait PrivateHTMLLinkElementHelpers {
fn handle_stylesheet_url(&self, href: &str);
}
impl<'a> PrivateHTMLLinkElementHelpers for JSRef<'a, HTMLLinkElement> {
fn handle_stylesheet_url(&self, href: &str) {
let window = window_from_node(self).root();
match UrlParser::new().base_url(&window.deref().page().get_url()).parse(href) {
Ok(url) => {
let LayoutChan(ref layout_chan) = *window.deref().page().layout_chan;
layout_chan.send(LoadStylesheetMsg(url));
}
Err(e) => debug!("Parsing url {:s} failed: {:?}", href, e)
}
}
}
impl Reflectable for HTMLLinkElement {

View file

@ -13,9 +13,9 @@ use dom::eventtarget::{EventTarget, NodeTargetTypeId};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
use dom::virtualmethods::VirtualMethods;
use html::cssparse::parse_inline_css;
use layout_interface::{AddStylesheetMsg, LayoutChan};
use servo_util::str::DOMString;
use style::Stylesheet;
#[deriving(Encodable)]
pub struct HTMLStyleElement {
@ -54,7 +54,7 @@ impl<'a> StyleElementHelpers for JSRef<'a, HTMLStyleElement> {
let url = win.deref().page().get_url();
let data = node.GetTextContent().expect("Element.textContent must be a string");
let sheet = parse_inline_css(url, data);
let sheet = Stylesheet::from_str(data.as_slice(), url);
let LayoutChan(ref layout_chan) = *win.deref().page().layout_chan;
layout_chan.send(AddStylesheetMsg(sheet));
}

View file

@ -1,72 +0,0 @@
/* 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/. */
/// Some little helpers for hooking up the HTML parser with the CSS parser.
use std::comm::{channel, Receiver};
use encoding::EncodingRef;
use encoding::all::UTF_8;
use style::Stylesheet;
use servo_net::resource_task::{Load, LoadData, LoadResponse, ProgressMsg, Payload, Done, ResourceTask};
use servo_util::task::spawn_named;
use url::Url;
/// Where a style sheet comes from.
pub enum StylesheetProvenance {
UrlProvenance(Url, ResourceTask),
InlineProvenance(Url, String),
}
// Parses the style data and returns the stylesheet
pub fn parse_inline_css(url: Url, data: String) -> Stylesheet {
parse_css(InlineProvenance(url, data))
}
fn parse_css(provenance: StylesheetProvenance) -> Stylesheet {
// TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
let environment_encoding = UTF_8 as EncodingRef;
match provenance {
UrlProvenance(url, resource_task) => {
debug!("cssparse: loading style sheet at {:s}", url.serialize());
let (input_chan, input_port) = channel();
resource_task.send(Load(LoadData::new(url), input_chan));
let LoadResponse { metadata: metadata, progress_port: progress_port , ..}
= input_port.recv();
let final_url = &metadata.final_url;
let protocol_encoding_label = metadata.charset.as_ref().map(|s| s.as_slice());
let iter = ProgressMsgPortIterator { progress_port: progress_port };
Stylesheet::from_bytes_iter(
iter, final_url.clone(),
protocol_encoding_label, Some(environment_encoding))
}
InlineProvenance(base_url, data) => {
debug!("cssparse: loading inline stylesheet {:s}", data);
Stylesheet::from_str(data.as_slice(), base_url)
}
}
}
pub fn spawn_css_parser(provenance: StylesheetProvenance) -> Receiver<Stylesheet> {
let (result_chan, result_port) = channel();
spawn_named("cssparser", proc() {
result_chan.send(parse_css(provenance));
});
return result_port;
}
struct ProgressMsgPortIterator {
progress_port: Receiver<ProgressMsg>
}
impl Iterator<Vec<u8>> for ProgressMsgPortIterator {
fn next(&mut self) -> Option<Vec<u8>> {
match self.progress_port.recv() {
Payload(data) => Some(data),
Done(..) => None
}
}
}

View file

@ -10,14 +10,13 @@ use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLScriptElementCast};
use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, Root};
use dom::bindings::utils::Reflectable;
use dom::document::{Document, DocumentHelpers};
use dom::element::{AttributeHandlers, HTMLLinkElementTypeId};
use dom::element::AttributeHandlers;
use dom::htmlelement::HTMLElement;
use dom::htmlheadingelement::{Heading1, Heading2, Heading3, Heading4, Heading5, Heading6};
use dom::htmlformelement::HTMLFormElement;
use dom::htmlscriptelement::HTMLScriptElementHelpers;
use dom::node::{ElementNodeTypeId, NodeHelpers};
use dom::node::NodeHelpers;
use dom::types::*;
use html::cssparse::{StylesheetProvenance, UrlProvenance, spawn_css_parser};
use page::Page;
use encoding::all::UTF_8;
@ -29,13 +28,12 @@ use servo_net::resource_task::{Load, LoadData, Payload, Done, ResourceTask, load
use servo_util::atom::Atom;
use servo_util::namespace;
use servo_util::namespace::{Namespace, Null};
use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS};
use servo_util::str::DOMString;
use servo_util::task::spawn_named;
use std::ascii::StrAsciiExt;
use std::mem;
use std::cell::RefCell;
use std::comm::{channel, Sender, Receiver};
use style::Stylesheet;
use url::{Url, UrlParser};
use http::headers::HeaderEnum;
use time;
@ -65,11 +63,6 @@ pub enum HTMLInput {
InputUrl(Url),
}
enum CSSMessage {
CSSTaskNewFile(StylesheetProvenance),
CSSTaskExit
}
enum JSMessage {
JSTaskNewFile(Url),
JSTaskNewInlineScript(String, Option<Url>),
@ -78,7 +71,6 @@ enum JSMessage {
/// Messages generated by the HTML parser upon discovery of additional resources
pub enum HtmlDiscoveryMessage {
HtmlDiscoveredStyle(Stylesheet),
HtmlDiscoveredScript(JSResult)
}
@ -100,43 +92,6 @@ unsafe fn from_hubbub_node<T: Reflectable>(n: hubbub::NodeDataPtr) -> Temporary<
Temporary::new(JS::from_raw(mem::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: Sender<HtmlDiscoveryMessage>,
from_parent: Receiver<CSSMessage>) {
let mut result_vec = vec!();
loop {
match from_parent.recv_opt() {
Ok(CSSTaskNewFile(provenance)) => {
result_vec.push(spawn_css_parser(provenance));
}
Ok(CSSTaskExit) | Err(()) => {
break;
}
}
}
// Send the sheets back in order
// FIXME: Shouldn't wait until after we've recieved CSSTaskExit to start sending these
for port in result_vec.iter() {
assert!(to_parent.send_opt(HtmlDiscoveredStyle(port.recv())).is_ok());
}
}
fn js_script_listener(to_parent: Sender<HtmlDiscoveryMessage>,
from_parent: Receiver<JSMessage>,
resource_task: ResourceTask) {
@ -335,16 +290,9 @@ pub fn parse_html(page: &Page,
resource_task: ResourceTask)
-> HtmlParserResult {
debug!("Hubbub: parsing {:?}", input);
// Spawn a CSS parser to receive links to CSS style sheets.
let (discovery_chan, discovery_port) = channel();
let stylesheet_chan = discovery_chan.clone();
let (css_chan, css_msg_port) = channel();
spawn_named("parse_html:css", proc() {
css_link_listener(stylesheet_chan, css_msg_port);
});
// Spawn a JS parser to receive JavaScript.
let (discovery_chan, discovery_port) = channel();
let resource_task2 = resource_task.clone();
let js_result_chan = discovery_chan.clone();
let (js_chan, js_msg_port) = channel();
@ -395,7 +343,7 @@ pub fn parse_html(page: &Page,
let mut parser = build_parser(unsafe { document.to_hubbub_node() });
debug!("created parser");
let (css_chan2, js_chan2) = (css_chan.clone(), js_chan.clone());
let js_chan2 = js_chan.clone();
let doc_cell = RefCell::new(document);
@ -447,54 +395,6 @@ pub fn parse_html(page: &Page,
prefix.map(|p| p.to_string()));
}
//FIXME: workaround for https://github.com/mozilla/rust/issues/13246;
// we get unrooting order failures if these are inside the match.
let rel = {
let rel = element.deref().get_attribute(Null, "rel").root();
rel.map(|a| a.deref().Value())
};
let href = {
let href= element.deref().get_attribute(Null, "href").root();
href.map(|a| a.deref().Value())
};
// Spawn additional parsing, network loads, etc. from tag and attrs
let type_id = {
let node: &JSRef<Node> = NodeCast::from_ref(&*element);
node.type_id()
};
match type_id {
// Handle CSS style sheets from <link> elements
ElementNodeTypeId(HTMLLinkElementTypeId) => {
match (rel, href) {
(Some(ref rel), Some(ref href)) => {
if rel.as_slice()
.split(HTML_SPACE_CHARACTERS.as_slice())
.any(|s| {
s.as_slice().eq_ignore_ascii_case("stylesheet")
}) {
debug!("found CSS stylesheet: {:s}", *href);
let mut url_parser = UrlParser::new();
match base_url {
None => (),
Some(ref base_url) => {
url_parser.base_url(base_url);
}
}
match url_parser.parse(href.as_slice()) {
Ok(url) => css_chan2.send(CSSTaskNewFile(
UrlProvenance(url, resource_task.clone()))),
Err(e) => debug!("Parsing url {:s} failed: {:?}", *href, e)
};
}
}
_ => {}
}
}
_ => {}
}
unsafe { element.deref().to_hubbub_node() }
},
create_text: |data: String| {
@ -641,7 +541,6 @@ pub fn parse_html(page: &Page,
}
debug!("finished parsing");
css_chan.send(CSSTaskExit);
js_chan.send(JSTaskExit);
HtmlParserResult {

View file

@ -29,6 +29,9 @@ pub enum Msg {
/// Adds the given stylesheet to the document.
AddStylesheetMsg(Stylesheet),
/// Adds the given stylesheet to the document.
LoadStylesheetMsg(Url),
/// Requests a reflow.
ReflowMsg(Box<Reflow>),

View file

@ -199,7 +199,6 @@ pub mod dom {
/// Parsers for HTML and CSS.
pub mod html {
pub mod cssparse;
pub mod hubbub_html_parser;
}

View file

@ -23,10 +23,8 @@ use dom::node::{ElementNodeTypeId, Node, NodeHelpers};
use dom::window::{TimerId, Window, WindowHelpers};
use dom::worker::{Worker, TrustedWorkerAddress};
use dom::xmlhttprequest::{TrustedXHRAddress, XMLHttpRequest, XHRProgress};
use html::hubbub_html_parser::{InputString, InputUrl, HtmlParserResult};
use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredScript};
use html::hubbub_html_parser::{InputString, InputUrl, HtmlParserResult, HtmlDiscoveredScript};
use html::hubbub_html_parser;
use layout_interface::AddStylesheetMsg;
use layout_interface::{ScriptLayoutChan, LayoutChan, MatchSelectorsDocumentDamage};
use layout_interface::{ReflowDocumentDamage, ReflowForDisplay};
use layout_interface::ContentChangedDocumentDamage;
@ -682,10 +680,6 @@ impl ScriptTask {
assert!(js_scripts.is_none());
js_scripts = Some(scripts);
}
Ok(HtmlDiscoveredStyle(sheet)) => {
let LayoutChan(ref chan) = *page.layout_chan;
chan.send(AddStylesheetMsg(sheet));
}
Err(()) => break
}
}

View file

@ -124,3 +124,4 @@ flaky_gpu,flaky_linux == acid2_noscroll.html acid2_ref_broken.html
== inline_block_baseline_a.html inline_block_baseline_ref.html
== float_table_a.html float_table_ref.html
== table_containing_block_a.html table_containing_block_ref.html
== link_style_order.html link_style_order_ref.html

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>link/style order test</title>
<link rel="stylesheet" href="data:text/css,body{background:red;}">
<style>body { background: green; }</style>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>link/style order test</title>
<style>body { background: green; }</style>
</head>
<body>
</body>
</html>