зеркало из https://github.com/mozilla/gecko-dev.git
servo: Merge #8039 - Move Stylesheet loading and ownership from the layout task into HTML elements (from tschneidereit:script-owns-stylesheets); r=jdm
Stylesheets for `HTMLLinkElement`s are now loaded by the resource task, triggered by the element in question. Stylesheets are owned by the elements they're associated with, which can be `HTMLStyleElement`, `HTMLLinkElement`, and `HTMLMetaElement` (for `<meta name="viewport">). Additionally, the quirks mode stylesheet (just as the user and user agent stylesheets a couple of commits ago), is implemented as a lazy static, loaded once per process and shared between all documents. This all has various nice consequences: - Stylesheet loading becomes a non-blocking operation. - Stylesheets are removed when the element they're associated with is removed from the document. - It'll be possible to implement the CSSOM APIs that require direct access to the stylesheets (i.e., ~ all of them). - Various subtle correctness issues are fixed. One piece of interesting follow-up work would be to move parsing of external stylesheets to the resource task, too. Right now, it happens in the link element once loading is complete, so blocks the script task. Moving it to the resource task would probably be fairly straight-forward as it doesn't require access to any external state. Depends on #7979 because without that loading stylesheets asynchronously breaks lots of content. Source-Repo: https://github.com/servo/servo Source-Revision: 7ff3a17524e0e703e3ac279441729c185444be24
This commit is contained in:
Родитель
d1fefa91f1
Коммит
6b102412c1
|
@ -16,8 +16,6 @@ use context::{SharedLayoutContext, StylistWrapper, heap_size_of_local_context};
|
|||
use cssparser::ToCss;
|
||||
use data::LayoutDataWrapper;
|
||||
use display_list_builder::ToGfxColor;
|
||||
use encoding::EncodingRef;
|
||||
use encoding::all::UTF_8;
|
||||
use euclid::Matrix4;
|
||||
use euclid::point::Point2D;
|
||||
use euclid::rect::Rect;
|
||||
|
@ -42,7 +40,6 @@ use msg::compositor_msg::{Epoch, LayerId, ScrollPolicy};
|
|||
use msg::constellation_msg::Msg as ConstellationMsg;
|
||||
use msg::constellation_msg::{ConstellationChan, Failure, PipelineId};
|
||||
use net_traits::image_cache_task::{ImageCacheChan, ImageCacheResult, ImageCacheTask};
|
||||
use net_traits::{PendingAsyncLoad, load_bytes_iter};
|
||||
use opaque_node::OpaqueNodeMethods;
|
||||
use parallel::{self, WorkQueueData};
|
||||
use profile_traits::mem::{self, Report, ReportKind, ReportsChan};
|
||||
|
@ -56,7 +53,6 @@ use script::layout_interface::Animation;
|
|||
use script::layout_interface::{LayoutChan, LayoutRPC, OffsetParentResponse};
|
||||
use script::layout_interface::{Msg, NewLayoutTaskInfo, Reflow, ReflowGoal, ReflowQueryType};
|
||||
use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress};
|
||||
use script_traits::StylesheetLoadResponder;
|
||||
use script_traits::{ConstellationControlMsg, LayoutControlMsg, OpaqueScriptLayoutChannel};
|
||||
use selectors::parser::PseudoElement;
|
||||
use sequential;
|
||||
|
@ -72,13 +68,12 @@ use std::sync::mpsc::{channel, Sender, Receiver};
|
|||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use string_cache::Atom;
|
||||
use style::computed_values::{self, filter, mix_blend_mode};
|
||||
use style::media_queries::{Device, MediaQueryList, MediaType};
|
||||
use style::media_queries::{Device, MediaType};
|
||||
use style::properties::longhands::{display, position};
|
||||
use style::properties::style_structs;
|
||||
use style::selector_matching::Stylist;
|
||||
use style::stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet};
|
||||
use style::selector_matching::{Stylist, USER_OR_USER_AGENT_STYLESHEETS};
|
||||
use style::stylesheets::{CSSRuleIteratorExt, Stylesheet};
|
||||
use style::values::AuExtensionMethods;
|
||||
use style::viewport::ViewportRule;
|
||||
use url::Url;
|
||||
use util::geometry::{MAX_RECT, ZERO_POINT};
|
||||
use util::ipc::OptionalIpcSender;
|
||||
|
@ -381,8 +376,8 @@ impl LayoutTask {
|
|||
|
||||
let stylist = box Stylist::new(device);
|
||||
let outstanding_web_fonts_counter = Arc::new(AtomicUsize::new(0));
|
||||
for user_or_user_agent_stylesheet in stylist.stylesheets() {
|
||||
add_font_face_rules(user_or_user_agent_stylesheet,
|
||||
for stylesheet in &*USER_OR_USER_AGENT_STYLESHEETS {
|
||||
add_font_face_rules(stylesheet,
|
||||
&stylist.device,
|
||||
&font_cache_task,
|
||||
&font_cache_sender,
|
||||
|
@ -577,20 +572,10 @@ impl LayoutTask {
|
|||
LayoutTaskData>>)
|
||||
-> bool {
|
||||
match request {
|
||||
Msg::AddStylesheet(sheet, mq) => {
|
||||
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data)
|
||||
}
|
||||
Msg::LoadStylesheet(url, mq, pending, link_element) => {
|
||||
self.handle_load_stylesheet(url,
|
||||
mq,
|
||||
pending,
|
||||
link_element,
|
||||
possibly_locked_rw_data)
|
||||
Msg::AddStylesheet(style_info) => {
|
||||
self.handle_add_stylesheet(style_info, possibly_locked_rw_data)
|
||||
}
|
||||
Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data),
|
||||
Msg::AddMetaViewport(translated_rule) => {
|
||||
self.handle_add_meta_viewport(translated_rule, possibly_locked_rw_data)
|
||||
}
|
||||
Msg::GetRPC(response_chan) => {
|
||||
response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as
|
||||
Box<LayoutRPC + Send>).unwrap();
|
||||
|
@ -744,75 +729,31 @@ impl LayoutTask {
|
|||
response_port.recv().unwrap()
|
||||
}
|
||||
|
||||
fn handle_load_stylesheet<'a>(&'a self,
|
||||
url: Url,
|
||||
mq: MediaQueryList,
|
||||
pending: PendingAsyncLoad,
|
||||
responder: Box<StylesheetLoadResponder + Send>,
|
||||
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;
|
||||
|
||||
// TODO we don't really even need to load this if mq does not match
|
||||
let (metadata, iter) = load_bytes_iter(pending);
|
||||
let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s);
|
||||
let final_url = metadata.final_url;
|
||||
|
||||
let sheet = Stylesheet::from_bytes_iter(iter,
|
||||
final_url,
|
||||
protocol_encoding_label,
|
||||
Some(environment_encoding),
|
||||
Origin::Author);
|
||||
|
||||
//TODO: mark critical subresources as blocking load as well (#5974)
|
||||
self.script_chan.send(ConstellationControlMsg::StylesheetLoadComplete(self.id,
|
||||
url,
|
||||
responder)).unwrap();
|
||||
|
||||
self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data);
|
||||
}
|
||||
|
||||
fn handle_add_stylesheet<'a>(&'a self,
|
||||
sheet: Stylesheet,
|
||||
mq: MediaQueryList,
|
||||
stylesheet: Arc<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!)
|
||||
// GWTODO: Need to handle unloading web fonts.
|
||||
|
||||
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
if mq.evaluate(&rw_data.stylist.device) {
|
||||
add_font_face_rules(&sheet,
|
||||
let rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
if stylesheet.is_effective_for_device(&rw_data.stylist.device) {
|
||||
add_font_face_rules(&*stylesheet,
|
||||
&rw_data.stylist.device,
|
||||
&self.font_cache_task,
|
||||
&self.font_cache_sender,
|
||||
&rw_data.outstanding_web_fonts);
|
||||
rw_data.stylist.add_stylesheet(sheet);
|
||||
}
|
||||
|
||||
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
|
||||
}
|
||||
|
||||
fn handle_add_meta_viewport<'a>(&'a self,
|
||||
translated_rule: ViewportRule,
|
||||
possibly_locked_rw_data:
|
||||
&mut Option<MutexGuard<'a, LayoutTaskData>>)
|
||||
{
|
||||
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
rw_data.stylist.add_stylesheet(Stylesheet {
|
||||
rules: vec![CSSRule::Viewport(translated_rule)],
|
||||
origin: Origin::Author
|
||||
});
|
||||
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
|
||||
}
|
||||
|
||||
/// Sets quirks mode for the document, causing the quirks mode stylesheet to be loaded.
|
||||
/// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
|
||||
fn handle_set_quirks_mode<'a>(&'a self,
|
||||
possibly_locked_rw_data:
|
||||
&mut Option<MutexGuard<'a, LayoutTaskData>>) {
|
||||
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
rw_data.stylist.add_quirks_mode_stylesheet();
|
||||
rw_data.stylist.set_quirks_mode(true);
|
||||
LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data);
|
||||
}
|
||||
|
||||
|
@ -1076,7 +1017,7 @@ impl LayoutTask {
|
|||
flow_ref::deref_mut(layout_root));
|
||||
let root_size = {
|
||||
let root_flow = flow::base(&**layout_root);
|
||||
if rw_data.stylist.constrain_viewport().is_some() {
|
||||
if rw_data.stylist.viewport_constraints().is_some() {
|
||||
root_flow.position.size.to_physical(root_flow.writing_mode)
|
||||
} else {
|
||||
root_flow.overflow.size
|
||||
|
@ -1152,6 +1093,9 @@ impl LayoutTask {
|
|||
}
|
||||
|
||||
let mut rw_data = self.lock_rw_data(possibly_locked_rw_data);
|
||||
let stylesheets: Vec<&Stylesheet> = data.document_stylesheets.iter().map(|entry| &**entry)
|
||||
.collect();
|
||||
let stylesheets_changed = data.stylesheets_changed;
|
||||
|
||||
let initial_viewport = data.window_size.initial_viewport;
|
||||
let old_viewport_size = rw_data.viewport_size;
|
||||
|
@ -1160,9 +1104,9 @@ impl LayoutTask {
|
|||
|
||||
// Calculate the actual viewport as per DEVICE-ADAPT § 6
|
||||
let device = Device::new(MediaType::Screen, initial_viewport);
|
||||
rw_data.stylist.set_device(device);
|
||||
rw_data.stylist.set_device(device, &stylesheets);
|
||||
|
||||
let constraints = rw_data.stylist.constrain_viewport();
|
||||
let constraints = rw_data.stylist.viewport_constraints().clone();
|
||||
rw_data.viewport_size = match constraints {
|
||||
Some(ref constraints) => {
|
||||
debug!("Viewport constraints: {:?}", constraints);
|
||||
|
@ -1178,9 +1122,6 @@ impl LayoutTask {
|
|||
let viewport_size_changed = rw_data.viewport_size != old_viewport_size;
|
||||
if viewport_size_changed {
|
||||
if let Some(constraints) = constraints {
|
||||
let device = Device::new(MediaType::Screen, constraints.size);
|
||||
rw_data.stylist.set_device(device);
|
||||
|
||||
// let the constellation know about the viewport constraints
|
||||
let ConstellationChan(ref constellation_chan) = rw_data.constellation_chan;
|
||||
constellation_chan.send(ConstellationMsg::ViewportConstrained(
|
||||
|
@ -1189,7 +1130,7 @@ impl LayoutTask {
|
|||
}
|
||||
|
||||
// If the entire flow tree is invalid, then it will be reflowed anyhow.
|
||||
let needs_dirtying = rw_data.stylist.update();
|
||||
let needs_dirtying = rw_data.stylist.update(&stylesheets, stylesheets_changed);
|
||||
let needs_reflow = viewport_size_changed && !needs_dirtying;
|
||||
unsafe {
|
||||
if needs_dirtying {
|
||||
|
@ -1218,7 +1159,7 @@ impl LayoutTask {
|
|||
&self.url,
|
||||
data.reflow_info.goal);
|
||||
|
||||
if node.is_dirty() || node.has_dirty_descendants() || rw_data.stylist.is_dirty() {
|
||||
if node.is_dirty() || node.has_dirty_descendants() {
|
||||
// Recalculate CSS styles and rebuild flows and fragments.
|
||||
profile(time::ProfilerCategory::LayoutStyleRecalc,
|
||||
self.profiler_metadata(),
|
||||
|
|
|
@ -174,7 +174,7 @@ pub fn create_element(name: QualName, prefix: Option<Atom>,
|
|||
atom!("label") => make!(HTMLLabelElement),
|
||||
atom!("legend") => make!(HTMLLegendElement),
|
||||
atom!("li") => make!(HTMLLIElement),
|
||||
atom!("link") => make!(HTMLLinkElement),
|
||||
atom!("link") => make!(HTMLLinkElement, creator),
|
||||
// https://html.spec.whatwg.org/multipage/#other-elements,-attributes-and-apis:listing
|
||||
atom!("listing") => make!(HTMLPreElement),
|
||||
atom!("main") => make!(HTMLElement),
|
||||
|
|
|
@ -48,7 +48,10 @@ use dom::htmlheadelement::HTMLHeadElement;
|
|||
use dom::htmlhtmlelement::HTMLHtmlElement;
|
||||
use dom::htmliframeelement::{self, HTMLIFrameElement};
|
||||
use dom::htmlimageelement::HTMLImageElement;
|
||||
use dom::htmllinkelement::HTMLLinkElement;
|
||||
use dom::htmlmetaelement::HTMLMetaElement;
|
||||
use dom::htmlscriptelement::HTMLScriptElement;
|
||||
use dom::htmlstyleelement::HTMLStyleElement;
|
||||
use dom::htmltitleelement::HTMLTitleElement;
|
||||
use dom::keyboardevent::KeyboardEvent;
|
||||
use dom::location::Location;
|
||||
|
@ -96,8 +99,10 @@ use std::default::Default;
|
|||
use std::iter::FromIterator;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::channel;
|
||||
use string_cache::{Atom, QualName};
|
||||
use style::stylesheets::Stylesheet;
|
||||
use time;
|
||||
use url::Url;
|
||||
use util::str::{DOMString, split_html_space_chars, str_join};
|
||||
|
@ -135,6 +140,10 @@ pub struct Document {
|
|||
scripts: MutNullableHeap<JS<HTMLCollection>>,
|
||||
anchors: MutNullableHeap<JS<HTMLCollection>>,
|
||||
applets: MutNullableHeap<JS<HTMLCollection>>,
|
||||
/// List of stylesheets associated with nodes in this document. |None| if the list needs to be refreshed.
|
||||
stylesheets: DOMRefCell<Option<Vec<Arc<Stylesheet>>>>,
|
||||
/// Whether the list of stylesheets has changed since the last reflow was triggered.
|
||||
stylesheets_changed_since_reflow: Cell<bool>,
|
||||
ready_state: Cell<DocumentReadyState>,
|
||||
/// Whether the DOMContentLoaded event has already been dispatched.
|
||||
domcontentloaded_dispatched: Cell<bool>,
|
||||
|
@ -983,6 +992,21 @@ impl Document {
|
|||
count_cell.set(count_cell.get() - 1);
|
||||
}
|
||||
|
||||
pub fn invalidate_stylesheets(&self) {
|
||||
self.stylesheets_changed_since_reflow.set(true);
|
||||
*self.stylesheets.borrow_mut() = None;
|
||||
// Mark the document element dirty so a reflow will be performed.
|
||||
self.get_html_element().map(|root| {
|
||||
root.upcast::<Node>().dirty(NodeDamage::NodeStyleDamaged);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_and_reset_stylesheets_changed_since_reflow(&self) -> bool {
|
||||
let changed = self.stylesheets_changed_since_reflow.get();
|
||||
self.stylesheets_changed_since_reflow.set(false);
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn set_pending_parsing_blocking_script(&self, script: Option<&HTMLScriptElement>) {
|
||||
assert!(self.get_pending_parsing_blocking_script().is_none() || script.is_none());
|
||||
self.pending_parsing_blocking_script.set(script);
|
||||
|
@ -1100,6 +1124,13 @@ impl Document {
|
|||
if parser.is_suspended() {
|
||||
parser.resume();
|
||||
}
|
||||
} else if self.reflow_timeout.get().is_none() {
|
||||
// If we don't have a parser, and the reflow timer has been reset, explicitly
|
||||
// trigger a reflow.
|
||||
if let LoadType::Stylesheet(_) = load {
|
||||
self.window().reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery,
|
||||
ReflowReason::StylesheetLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
let loader = self.loader.borrow();
|
||||
|
@ -1304,6 +1335,8 @@ impl Document {
|
|||
scripts: Default::default(),
|
||||
anchors: Default::default(),
|
||||
applets: Default::default(),
|
||||
stylesheets: DOMRefCell::new(None),
|
||||
stylesheets_changed_since_reflow: Cell::new(false),
|
||||
ready_state: Cell::new(ready_state),
|
||||
domcontentloaded_dispatched: Cell::new(domcontentloaded_dispatched),
|
||||
possibly_focused: Default::default(),
|
||||
|
@ -1369,6 +1402,31 @@ impl Document {
|
|||
self.GetDocumentElement().and_then(Root::downcast)
|
||||
}
|
||||
|
||||
/// Returns the list of stylesheets associated with nodes in the document.
|
||||
pub fn stylesheets(&self) -> Ref<Vec<Arc<Stylesheet>>> {
|
||||
{
|
||||
let mut stylesheets = self.stylesheets.borrow_mut();
|
||||
if stylesheets.is_none() {
|
||||
let new_stylesheets: Vec<Arc<Stylesheet>> = self.upcast::<Node>()
|
||||
.traverse_preorder()
|
||||
.filter_map(|node| {
|
||||
if let Some(node) = node.downcast::<HTMLStyleElement>() {
|
||||
node.get_stylesheet()
|
||||
} else if let Some(node) = node.downcast::<HTMLLinkElement>() {
|
||||
node.get_stylesheet()
|
||||
} else if let Some(node) = node.downcast::<HTMLMetaElement>() {
|
||||
node.get_stylesheet()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
*stylesheets = Some(new_stylesheets);
|
||||
};
|
||||
}
|
||||
Ref::map(self.stylesheets.borrow(), |t| t.as_ref().unwrap())
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document
|
||||
pub fn appropriate_template_contents_owner_document(&self) -> Root<Document> {
|
||||
self.appropriate_template_contents_owner_document.or_init(|| {
|
||||
|
|
|
@ -5,55 +5,76 @@
|
|||
use cssparser::Parser as CssParser;
|
||||
use document_loader::LoadType;
|
||||
use dom::attr::{Attr, AttrValue};
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::HTMLLinkElementBinding;
|
||||
use dom::bindings::codegen::Bindings::HTMLLinkElementBinding::HTMLLinkElementMethods;
|
||||
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||
use dom::bindings::global::GlobalRef;
|
||||
use dom::bindings::inheritance::Castable;
|
||||
use dom::bindings::js::{JS, MutNullableHeap, Root};
|
||||
use dom::bindings::js::{RootedReference};
|
||||
use dom::bindings::refcounted::Trusted;
|
||||
use dom::document::Document;
|
||||
use dom::domtokenlist::DOMTokenList;
|
||||
use dom::element::{AttributeMutation, Element};
|
||||
use dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use dom::eventtarget::EventTarget;
|
||||
use dom::element::{AttributeMutation, Element, ElementCreator};
|
||||
use dom::htmlelement::HTMLElement;
|
||||
use dom::node::{Node, window_from_node};
|
||||
use dom::node::{Node, document_from_node, window_from_node};
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use encoding::EncodingRef;
|
||||
use encoding::all::UTF_8;
|
||||
use ipc_channel::ipc;
|
||||
use ipc_channel::router::ROUTER;
|
||||
use layout_interface::{LayoutChan, Msg};
|
||||
use msg::constellation_msg::ConstellationChan;
|
||||
use msg::constellation_msg::Msg as ConstellationMsg;
|
||||
use script_traits::StylesheetLoadResponder;
|
||||
use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata};
|
||||
use network_listener::{NetworkListener, PreInvoke};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::Cell;
|
||||
use std::default::Default;
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use string_cache::Atom;
|
||||
use style::media_queries::parse_media_query_list;
|
||||
use url::UrlParser;
|
||||
use style::media_queries::{MediaQueryList, parse_media_query_list};
|
||||
use style::stylesheets::{Origin, Stylesheet};
|
||||
use url::{Url, UrlParser};
|
||||
use util::str::{DOMString, HTML_SPACE_CHARACTERS};
|
||||
|
||||
no_jsmanaged_fields!(Stylesheet);
|
||||
|
||||
#[dom_struct]
|
||||
pub struct HTMLLinkElement {
|
||||
htmlelement: HTMLElement,
|
||||
rel_list: MutNullableHeap<JS<DOMTokenList>>,
|
||||
stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>,
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#a-style-sheet-that-is-blocking-scripts
|
||||
parser_inserted: Cell<bool>,
|
||||
}
|
||||
|
||||
impl HTMLLinkElement {
|
||||
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: &Document) -> HTMLLinkElement {
|
||||
fn new_inherited(localName: DOMString, prefix: Option<DOMString>, document: &Document,
|
||||
creator: ElementCreator) -> HTMLLinkElement {
|
||||
HTMLLinkElement {
|
||||
htmlelement: HTMLElement::new_inherited(localName, prefix, document),
|
||||
rel_list: Default::default(),
|
||||
parser_inserted: Cell::new(creator == ElementCreator::ParserCreated),
|
||||
stylesheet: DOMRefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unrooted_must_root)]
|
||||
pub fn new(localName: DOMString,
|
||||
prefix: Option<DOMString>,
|
||||
document: &Document) -> Root<HTMLLinkElement> {
|
||||
let element = HTMLLinkElement::new_inherited(localName, prefix, document);
|
||||
document: &Document,
|
||||
creator: ElementCreator) -> Root<HTMLLinkElement> {
|
||||
let element = HTMLLinkElement::new_inherited(localName, prefix, document, creator);
|
||||
Node::reflect_node(box element, document, HTMLLinkElementBinding::Wrap)
|
||||
}
|
||||
|
||||
pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
|
||||
self.stylesheet.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_attr(element: &Element, local_name: &Atom) -> Option<String> {
|
||||
|
@ -64,7 +85,7 @@ fn get_attr(element: &Element, local_name: &Atom) -> Option<String> {
|
|||
})
|
||||
}
|
||||
|
||||
fn is_stylesheet(value: &Option<String>) -> bool {
|
||||
fn string_is_stylesheet(value: &Option<String>) -> bool {
|
||||
match *value {
|
||||
Some(ref value) => {
|
||||
value.split(HTML_SPACE_CHARACTERS)
|
||||
|
@ -100,14 +121,14 @@ impl VirtualMethods for HTMLLinkElement {
|
|||
let rel = get_attr(self.upcast(), &atom!(rel));
|
||||
match attr.local_name() {
|
||||
&atom!(href) => {
|
||||
if is_stylesheet(&rel) {
|
||||
if string_is_stylesheet(&rel) {
|
||||
self.handle_stylesheet_url(&attr.value());
|
||||
} else if is_favicon(&rel) {
|
||||
self.handle_favicon_url(&attr.value());
|
||||
}
|
||||
},
|
||||
&atom!(media) => {
|
||||
if is_stylesheet(&rel) {
|
||||
if string_is_stylesheet(&rel) {
|
||||
self.handle_stylesheet_url(&attr.value());
|
||||
}
|
||||
},
|
||||
|
@ -134,7 +155,7 @@ impl VirtualMethods for HTMLLinkElement {
|
|||
let href = get_attr(element, &atom!("href"));
|
||||
|
||||
match (rel, href) {
|
||||
(ref rel, Some(ref href)) if is_stylesheet(rel) => {
|
||||
(ref rel, Some(ref href)) if string_is_stylesheet(rel) => {
|
||||
self.handle_stylesheet_url(href);
|
||||
}
|
||||
(ref rel, Some(ref href)) if is_favicon(rel) => {
|
||||
|
@ -164,13 +185,35 @@ impl HTMLLinkElement {
|
|||
let mut css_parser = CssParser::new(&mq_str);
|
||||
let media = parse_media_query_list(&mut css_parser);
|
||||
|
||||
// TODO: #8085 - Don't load external stylesheets if the node's mq doesn't match.
|
||||
let doc = window.Document();
|
||||
let link_element = Trusted::new(window.get_cx(), self, window.script_chan().clone());
|
||||
let load_dispatcher = StylesheetLoadDispatcher::new(link_element);
|
||||
let script_chan = window.script_chan();
|
||||
let elem = Trusted::new(window.get_cx(), self, script_chan.clone());
|
||||
|
||||
let pending = doc.prepare_async_load(LoadType::Stylesheet(url.clone()));
|
||||
let LayoutChan(ref layout_chan) = window.layout_chan();
|
||||
layout_chan.send(Msg::LoadStylesheet(url, media, pending, box load_dispatcher)).unwrap();
|
||||
let context = Arc::new(Mutex::new(StylesheetContext {
|
||||
elem: elem,
|
||||
media: Some(media),
|
||||
data: vec!(),
|
||||
metadata: None,
|
||||
url: url.clone(),
|
||||
}));
|
||||
|
||||
let (action_sender, action_receiver) = ipc::channel().unwrap();
|
||||
let listener = NetworkListener {
|
||||
context: context,
|
||||
script_chan: script_chan,
|
||||
};
|
||||
let response_target = AsyncResponseTarget {
|
||||
sender: action_sender,
|
||||
};
|
||||
ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
|
||||
listener.notify(message.to().unwrap());
|
||||
});
|
||||
|
||||
if self.parser_inserted.get() {
|
||||
doc.increment_script_blocking_stylesheet_count();
|
||||
}
|
||||
doc.load_async(LoadType::Stylesheet(url), response_target);
|
||||
}
|
||||
Err(e) => debug!("Parsing url {} failed: {}", href, e)
|
||||
}
|
||||
|
@ -190,6 +233,62 @@ impl HTMLLinkElement {
|
|||
}
|
||||
}
|
||||
|
||||
/// The context required for asynchronously loading an external stylesheet.
|
||||
struct StylesheetContext {
|
||||
/// The element that initiated the request.
|
||||
elem: Trusted<HTMLLinkElement>,
|
||||
media: Option<MediaQueryList>,
|
||||
/// The response body received to date.
|
||||
data: Vec<u8>,
|
||||
/// The response metadata received to date.
|
||||
metadata: Option<Metadata>,
|
||||
/// The initial URL requested.
|
||||
url: Url,
|
||||
}
|
||||
|
||||
impl PreInvoke for StylesheetContext {}
|
||||
|
||||
impl AsyncResponseListener for StylesheetContext {
|
||||
fn headers_available(&mut self, metadata: Metadata) {
|
||||
self.metadata = Some(metadata);
|
||||
}
|
||||
|
||||
fn data_available(&mut self, payload: Vec<u8>) {
|
||||
let mut payload = payload;
|
||||
self.data.append(&mut payload);
|
||||
}
|
||||
|
||||
fn response_complete(&mut self, _status: Result<(), String>) {
|
||||
let data = mem::replace(&mut self.data, vec!());
|
||||
let metadata = self.metadata.take().unwrap();
|
||||
// TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
|
||||
let environment_encoding = UTF_8 as EncodingRef;
|
||||
let protocol_encoding_label = metadata.charset.as_ref().map(|s| &**s);
|
||||
let final_url = metadata.final_url;
|
||||
let mut sheet = Stylesheet::from_bytes(&data, final_url, protocol_encoding_label,
|
||||
Some(environment_encoding), Origin::Author);
|
||||
let media = self.media.take().unwrap();
|
||||
sheet.set_media(Some(media));
|
||||
let sheet = Arc::new(sheet);
|
||||
|
||||
let elem = self.elem.root();
|
||||
let elem = elem.r();
|
||||
let document = document_from_node(elem);
|
||||
let document = document.r();
|
||||
|
||||
let win = window_from_node(elem);
|
||||
let LayoutChan(ref layout_chan) = win.r().layout_chan();
|
||||
layout_chan.send(Msg::AddStylesheet(sheet.clone())).unwrap();
|
||||
|
||||
*elem.stylesheet.borrow_mut() = Some(sheet);
|
||||
document.invalidate_stylesheets();
|
||||
if elem.parser_inserted.get() {
|
||||
document.decrement_script_blocking_stylesheet_count();
|
||||
}
|
||||
document.finish_load(LoadType::Stylesheet(self.url.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
impl HTMLLinkElementMethods for HTMLLinkElement {
|
||||
// https://html.spec.whatwg.org/multipage/#dom-link-href
|
||||
make_url_getter!(Href);
|
||||
|
@ -244,27 +343,3 @@ impl HTMLLinkElementMethods for HTMLLinkElement {
|
|||
// https://html.spec.whatwg.org/multipage/#dom-link-target
|
||||
make_setter!(SetTarget, "target");
|
||||
}
|
||||
|
||||
pub struct StylesheetLoadDispatcher {
|
||||
elem: Trusted<HTMLLinkElement>,
|
||||
}
|
||||
|
||||
impl StylesheetLoadDispatcher {
|
||||
pub fn new(elem: Trusted<HTMLLinkElement>) -> StylesheetLoadDispatcher {
|
||||
StylesheetLoadDispatcher {
|
||||
elem: elem,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StylesheetLoadResponder for StylesheetLoadDispatcher {
|
||||
fn respond(self: Box<StylesheetLoadDispatcher>) {
|
||||
let elem = self.elem.root();
|
||||
let window = window_from_node(elem.r());
|
||||
let event = Event::new(GlobalRef::Window(window.r()),
|
||||
DOMString("load".to_owned()),
|
||||
EventBubbles::DoesNotBubble,
|
||||
EventCancelable::NotCancelable);
|
||||
event.fire(elem.upcast::<EventTarget>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* 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::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::HTMLMetaElementBinding;
|
||||
use dom::bindings::codegen::Bindings::HTMLMetaElementBinding::HTMLMetaElementMethods;
|
||||
use dom::bindings::inheritance::Castable;
|
||||
|
@ -9,16 +10,18 @@ use dom::bindings::js::{Root, RootedReference};
|
|||
use dom::document::Document;
|
||||
use dom::element::Element;
|
||||
use dom::htmlelement::HTMLElement;
|
||||
use dom::node::{Node, window_from_node};
|
||||
use dom::node::{Node, document_from_node};
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use layout_interface::{LayoutChan, Msg};
|
||||
use std::ascii::AsciiExt;
|
||||
use std::sync::Arc;
|
||||
use style::stylesheets::{CSSRule, Origin, Stylesheet};
|
||||
use style::viewport::ViewportRule;
|
||||
use util::str::{DOMString, HTML_SPACE_CHARACTERS};
|
||||
|
||||
#[dom_struct]
|
||||
pub struct HTMLMetaElement {
|
||||
htmlelement: HTMLElement,
|
||||
stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>,
|
||||
}
|
||||
|
||||
impl HTMLMetaElement {
|
||||
|
@ -26,7 +29,8 @@ impl HTMLMetaElement {
|
|||
prefix: Option<DOMString>,
|
||||
document: &Document) -> HTMLMetaElement {
|
||||
HTMLMetaElement {
|
||||
htmlelement: HTMLElement::new_inherited(localName, prefix, document)
|
||||
htmlelement: HTMLElement::new_inherited(localName, prefix, document),
|
||||
stylesheet: DOMRefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +42,10 @@ impl HTMLMetaElement {
|
|||
Node::reflect_node(box element, document, HTMLMetaElementBinding::Wrap)
|
||||
}
|
||||
|
||||
pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
|
||||
self.stylesheet.borrow().clone()
|
||||
}
|
||||
|
||||
fn process_attributes(&self) {
|
||||
let element = self.upcast::<Element>();
|
||||
if let Some(name) = element.get_attribute(&ns!(""), &atom!("name")).r() {
|
||||
|
@ -45,22 +53,25 @@ impl HTMLMetaElement {
|
|||
let name = name.trim_matches(HTML_SPACE_CHARACTERS);
|
||||
|
||||
match name {
|
||||
"viewport" => self.translate_viewport(),
|
||||
"viewport" => self.apply_viewport(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_viewport(&self) {
|
||||
fn apply_viewport(&self) {
|
||||
let element = self.upcast::<Element>();
|
||||
if let Some(content) = element.get_attribute(&ns!(""), &atom!("content")).r() {
|
||||
let content = content.value();
|
||||
if !content.is_empty() {
|
||||
if let Some(translated_rule) = ViewportRule::from_meta(&**content) {
|
||||
let win = window_from_node(self);
|
||||
let LayoutChan(ref layout_chan) = win.layout_chan();
|
||||
|
||||
layout_chan.send(Msg::AddMetaViewport(translated_rule)).unwrap();
|
||||
*self.stylesheet.borrow_mut() = Some(Arc::new(Stylesheet {
|
||||
rules: vec![CSSRule::Viewport(translated_rule)],
|
||||
origin: Origin::Author,
|
||||
media: None,
|
||||
}));
|
||||
let doc = document_from_node(self);
|
||||
doc.invalidate_stylesheets();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use cssparser::Parser as CssParser;
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::HTMLStyleElementBinding;
|
||||
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
|
||||
use dom::bindings::inheritance::Castable;
|
||||
|
@ -10,9 +11,10 @@ use dom::bindings::js::Root;
|
|||
use dom::document::Document;
|
||||
use dom::element::Element;
|
||||
use dom::htmlelement::HTMLElement;
|
||||
use dom::node::{ChildrenMutation, Node, window_from_node};
|
||||
use dom::node::{ChildrenMutation, Node, document_from_node, window_from_node};
|
||||
use dom::virtualmethods::VirtualMethods;
|
||||
use layout_interface::{LayoutChan, Msg};
|
||||
use std::sync::Arc;
|
||||
use style::media_queries::parse_media_query_list;
|
||||
use style::stylesheets::{Origin, Stylesheet};
|
||||
use util::str::DOMString;
|
||||
|
@ -20,6 +22,7 @@ use util::str::DOMString;
|
|||
#[dom_struct]
|
||||
pub struct HTMLStyleElement {
|
||||
htmlelement: HTMLElement,
|
||||
stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>,
|
||||
}
|
||||
|
||||
impl HTMLStyleElement {
|
||||
|
@ -27,7 +30,8 @@ impl HTMLStyleElement {
|
|||
prefix: Option<DOMString>,
|
||||
document: &Document) -> HTMLStyleElement {
|
||||
HTMLStyleElement {
|
||||
htmlelement: HTMLElement::new_inherited(localName, prefix, document)
|
||||
htmlelement: HTMLElement::new_inherited(localName, prefix, document),
|
||||
stylesheet: DOMRefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,13 +56,23 @@ impl HTMLStyleElement {
|
|||
Some(a) => String::from(&**a.value()),
|
||||
None => String::new(),
|
||||
};
|
||||
let mut css_parser = CssParser::new(&mq_str);
|
||||
let media = parse_media_query_list(&mut css_parser);
|
||||
|
||||
let data = node.GetTextContent().expect("Element.textContent must be a string");
|
||||
let sheet = Stylesheet::from_str(&data, url, Origin::Author);
|
||||
let mut sheet = Stylesheet::from_str(&data, url, Origin::Author);
|
||||
let mut css_parser = CssParser::new(&mq_str);
|
||||
let media = parse_media_query_list(&mut css_parser);
|
||||
sheet.set_media(Some(media));
|
||||
let sheet = Arc::new(sheet);
|
||||
|
||||
let LayoutChan(ref layout_chan) = win.layout_chan();
|
||||
layout_chan.send(Msg::AddStylesheet(sheet, media)).unwrap();
|
||||
layout_chan.send(Msg::AddStylesheet(sheet.clone())).unwrap();
|
||||
*self.stylesheet.borrow_mut() = Some(sheet);
|
||||
let doc = document_from_node(self);
|
||||
doc.r().invalidate_stylesheets();
|
||||
}
|
||||
|
||||
pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> {
|
||||
self.stylesheet.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,6 +99,7 @@ pub enum ReflowReason {
|
|||
WindowResize,
|
||||
DOMContentLoaded,
|
||||
DocumentLoaded,
|
||||
StylesheetLoaded,
|
||||
ImageLoaded,
|
||||
RequestAnimationFrame,
|
||||
WebFontLoaded,
|
||||
|
@ -915,6 +916,9 @@ impl Window {
|
|||
debug_reflow_events(&goal, &query_type, &reason);
|
||||
}
|
||||
|
||||
let document = self.Document();
|
||||
let stylesheets_changed = document.get_and_reset_stylesheets_changed_since_reflow();
|
||||
|
||||
// Send new document and relevant styles to layout.
|
||||
let reflow = ScriptReflow {
|
||||
reflow_info: Reflow {
|
||||
|
@ -922,6 +926,8 @@ impl Window {
|
|||
page_clip_rect: self.page_clip_rect.get(),
|
||||
},
|
||||
document: self.Document().upcast::<Node>().to_trusted_node_address(),
|
||||
document_stylesheets: document.stylesheets().clone(),
|
||||
stylesheets_changed: stylesheets_changed,
|
||||
window_size: window_size,
|
||||
script_join_chan: join_chan,
|
||||
query_type: query_type,
|
||||
|
@ -1325,6 +1331,7 @@ fn debug_reflow_events(goal: &ReflowGoal, query_type: &ReflowQueryType, reason:
|
|||
ReflowReason::WindowResize => "\tWindowResize",
|
||||
ReflowReason::DOMContentLoaded => "\tDOMContentLoaded",
|
||||
ReflowReason::DocumentLoaded => "\tDocumentLoaded",
|
||||
ReflowReason::StylesheetLoaded => "\tStylesheetLoaded",
|
||||
ReflowReason::ImageLoaded => "\tImageLoaded",
|
||||
ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame",
|
||||
ReflowReason::WebFontLoaded => "\tWebFontLoaded",
|
||||
|
|
|
@ -16,32 +16,24 @@ use msg::compositor_msg::Epoch;
|
|||
use msg::compositor_msg::LayerId;
|
||||
use msg::constellation_msg::{ConstellationChan, Failure, PipelineId};
|
||||
use msg::constellation_msg::{WindowSizeData};
|
||||
use net_traits::PendingAsyncLoad;
|
||||
use net_traits::image_cache_task::ImageCacheTask;
|
||||
use profile_traits::mem::ReportsChan;
|
||||
use script_traits::{ConstellationControlMsg, LayoutControlMsg};
|
||||
use script_traits::{OpaqueScriptLayoutChannel, StylesheetLoadResponder, UntrustedNodeAddress};
|
||||
use script_traits::{OpaqueScriptLayoutChannel, UntrustedNodeAddress};
|
||||
use selectors::parser::PseudoElement;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||
use string_cache::Atom;
|
||||
use style::animation::PropertyAnimation;
|
||||
use style::media_queries::MediaQueryList;
|
||||
use style::stylesheets::Stylesheet;
|
||||
use style::viewport::ViewportRule;
|
||||
use url::Url;
|
||||
pub use dom::node::TrustedNodeAddress;
|
||||
|
||||
/// Asynchronous messages that script can send to layout.
|
||||
pub enum Msg {
|
||||
/// Adds the given stylesheet to the document.
|
||||
AddStylesheet(Stylesheet, MediaQueryList),
|
||||
|
||||
/// Adds the given stylesheet to the document.
|
||||
LoadStylesheet(Url, MediaQueryList, PendingAsyncLoad, Box<StylesheetLoadResponder + Send>),
|
||||
|
||||
/// Adds a @viewport rule (translated from a <META name="viewport"> element) to the document.
|
||||
AddMetaViewport(ViewportRule),
|
||||
AddStylesheet(Arc<Stylesheet>),
|
||||
|
||||
/// Puts a document into quirks mode, causing the quirks mode stylesheet to be loaded.
|
||||
SetQuirksMode,
|
||||
|
@ -175,6 +167,10 @@ pub struct ScriptReflow {
|
|||
pub reflow_info: Reflow,
|
||||
/// The document node.
|
||||
pub document: TrustedNodeAddress,
|
||||
/// The document's list of stylesheets.
|
||||
pub document_stylesheets: Vec<Arc<Stylesheet>>,
|
||||
/// Whether the document's stylesheets have changed since the last script reflow.
|
||||
pub stylesheets_changed: bool,
|
||||
/// The current window size.
|
||||
pub window_size: WindowSizeData,
|
||||
/// The channel that we send a notification to.
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
use devtools;
|
||||
use devtools_traits::ScriptToDevtoolsControlMsg;
|
||||
use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo};
|
||||
use document_loader::{DocumentLoader, LoadType};
|
||||
use document_loader::DocumentLoader;
|
||||
use dom::bindings::cell::DOMRefCell;
|
||||
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
|
||||
use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior};
|
||||
|
@ -1001,10 +1001,6 @@ impl ScriptTask {
|
|||
self.handle_tick_all_animations(pipeline_id),
|
||||
ConstellationControlMsg::WebFontLoaded(pipeline_id) =>
|
||||
self.handle_web_font_loaded(pipeline_id),
|
||||
ConstellationControlMsg::StylesheetLoadComplete(id, url, responder) => {
|
||||
responder.respond();
|
||||
self.handle_resource_loaded(id, LoadType::Stylesheet(url));
|
||||
}
|
||||
ConstellationControlMsg::GetCurrentState(sender, pipeline_id) => {
|
||||
let state = self.handle_get_current_state(pipeline_id);
|
||||
sender.send(state).unwrap();
|
||||
|
@ -1153,13 +1149,6 @@ impl ScriptTask {
|
|||
panic!("Page rect message sent to nonexistent pipeline");
|
||||
}
|
||||
|
||||
/// Handle a request to load a page in a new child frame of an existing page.
|
||||
fn handle_resource_loaded(&self, pipeline: PipelineId, load: LoadType) {
|
||||
let page = get_page(&self.root_page(), pipeline);
|
||||
let doc = page.document();
|
||||
doc.finish_load(load);
|
||||
}
|
||||
|
||||
/// Get the current state of a given pipeline.
|
||||
fn handle_get_current_state(&self, pipeline_id: PipelineId) -> ScriptState {
|
||||
// Check if the main page load is still pending
|
||||
|
|
|
@ -41,7 +41,6 @@ use net_traits::storage_task::StorageTask;
|
|||
use profile_traits::mem;
|
||||
use std::any::Any;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use url::Url;
|
||||
use util::mem::HeapSizeOf;
|
||||
|
||||
/// The address of a node. Layout sends these back. They must be validated via
|
||||
|
@ -90,13 +89,6 @@ pub struct NewLayoutInfo {
|
|||
pub layout_shutdown_chan: Sender<()>,
|
||||
}
|
||||
|
||||
/// `StylesheetLoadResponder` is used to notify a responder that a style sheet
|
||||
/// has loaded.
|
||||
pub trait StylesheetLoadResponder {
|
||||
/// Respond to a loaded style sheet.
|
||||
fn respond(self: Box<Self>);
|
||||
}
|
||||
|
||||
/// Used to determine if a script has any pending asynchronous activity.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ScriptState {
|
||||
|
@ -141,8 +133,6 @@ pub enum ConstellationControlMsg {
|
|||
/// Notifies the script task that a new Web font has been loaded, and thus the page should be
|
||||
/// reflowed.
|
||||
WebFontLoaded(PipelineId),
|
||||
/// Notifies script that a stylesheet has finished loading.
|
||||
StylesheetLoadComplete(PipelineId, Url, Box<StylesheetLoadResponder + Send>),
|
||||
/// Get the current state of the script task for a given pipeline.
|
||||
GetCurrentState(Sender<ScriptState>, PipelineId),
|
||||
}
|
||||
|
|
|
@ -9,20 +9,21 @@ use properties::longhands::font_family::parse_one_family;
|
|||
use std::ascii::AsciiExt;
|
||||
use string_cache::Atom;
|
||||
use url::{Url, UrlParser};
|
||||
use util::mem::HeapSizeOf;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, HeapSizeOf, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub enum Source {
|
||||
Url(UrlSource),
|
||||
Local(Atom),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, HeapSizeOf, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct UrlSource {
|
||||
pub url: Url,
|
||||
pub format_hints: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq, Eq)]
|
||||
pub struct FontFaceRule {
|
||||
pub family: Atom,
|
||||
pub sources: Vec<Source>,
|
||||
|
|
|
@ -8,15 +8,16 @@ use euclid::size::{Size2D, TypedSize2D};
|
|||
use properties::longhands;
|
||||
use std::ascii::AsciiExt;
|
||||
use util::geometry::ViewportPx;
|
||||
use util::mem::HeapSizeOf;
|
||||
use values::specified;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq)]
|
||||
pub struct MediaQueryList {
|
||||
pub media_queries: Vec<MediaQuery>
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)]
|
||||
pub enum Range<T> {
|
||||
Min(T),
|
||||
Max(T),
|
||||
|
@ -59,20 +60,20 @@ impl<T: Ord> Range<T> {
|
|||
}
|
||||
|
||||
/// http://dev.w3.org/csswg/mediaqueries-3/#media1
|
||||
#[derive(PartialEq, Copy, Clone, Debug)]
|
||||
#[derive(PartialEq, Copy, Clone, Debug, HeapSizeOf)]
|
||||
pub enum Expression {
|
||||
/// http://dev.w3.org/csswg/mediaqueries-3/#width
|
||||
Width(Range<specified::Length>),
|
||||
}
|
||||
|
||||
/// http://dev.w3.org/csswg/mediaqueries-3/#media0
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)]
|
||||
pub enum Qualifier {
|
||||
Only,
|
||||
Not,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq)]
|
||||
pub struct MediaQuery {
|
||||
pub qualifier: Option<Qualifier>,
|
||||
pub media_type: MediaQueryType,
|
||||
|
@ -91,20 +92,20 @@ impl MediaQuery {
|
|||
}
|
||||
|
||||
/// http://dev.w3.org/csswg/mediaqueries-3/#media0
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)]
|
||||
pub enum MediaQueryType {
|
||||
All, // Always true
|
||||
MediaType(MediaType),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)]
|
||||
pub enum MediaType {
|
||||
Screen,
|
||||
Print,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, HeapSizeOf)]
|
||||
pub struct Device {
|
||||
pub media_type: MediaType,
|
||||
pub viewport_size: TypedSize2D<ViewportPx, f32>,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use legacy::PresentationalHintSynthesis;
|
||||
use media_queries::Device;
|
||||
use media_queries::{Device, MediaType};
|
||||
use node::TElementAttributes;
|
||||
use properties::{PropertyDeclaration, PropertyDeclarationBlock};
|
||||
use restyle_hints::{RestyleHint, StateDependencySet};
|
||||
|
@ -26,16 +26,67 @@ use viewport::{MaybeNew, ViewportRuleCascade};
|
|||
pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
|
||||
|
||||
|
||||
pub struct Stylist {
|
||||
// List of stylesheets (including all media rules)
|
||||
stylesheets: Vec<Stylesheet>,
|
||||
lazy_static! {
|
||||
pub static ref USER_OR_USER_AGENT_STYLESHEETS: Vec<Stylesheet> = {
|
||||
let mut stylesheets = vec!();
|
||||
// FIXME: presentational-hints.css should be at author origin with zero specificity.
|
||||
// (Does it make a difference?)
|
||||
for &filename in &["user-agent.css", "servo.css", "presentational-hints.css"] {
|
||||
match read_resource_file(&[filename]) {
|
||||
Ok(res) => {
|
||||
let ua_stylesheet = Stylesheet::from_bytes(
|
||||
&res,
|
||||
Url::parse(&format!("chrome:///{:?}", filename)).unwrap(),
|
||||
None,
|
||||
None,
|
||||
Origin::UserAgent);
|
||||
stylesheets.push(ua_stylesheet);
|
||||
}
|
||||
Err(..) => {
|
||||
error!("Failed to load UA stylesheet {}!", filename);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &(ref contents, ref url) in &opts::get().user_stylesheets {
|
||||
stylesheets.push(Stylesheet::from_bytes(
|
||||
&contents, url.clone(), None, None, Origin::User));
|
||||
}
|
||||
stylesheets
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref QUIRKS_MODE_STYLESHEET: Stylesheet = {
|
||||
match read_resource_file(&["quirks-mode.css"]) {
|
||||
Ok(res) => {
|
||||
Stylesheet::from_bytes(
|
||||
&res,
|
||||
Url::parse("chrome:///quirks-mode.css").unwrap(),
|
||||
None,
|
||||
None,
|
||||
Origin::UserAgent)
|
||||
},
|
||||
Err(..) => {
|
||||
error!("Stylist failed to load 'quirks-mode.css'!");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Stylist {
|
||||
// Device that the stylist is currently evaluating against.
|
||||
pub device: Device,
|
||||
|
||||
// If true, a stylesheet has been added or the device has
|
||||
// changed, and the stylist needs to be updated.
|
||||
is_dirty: bool,
|
||||
// Viewport constraints based on the current device.
|
||||
viewport_constraints: Option<ViewportConstraints>,
|
||||
|
||||
// If true, the quirks-mode stylesheet is applied.
|
||||
quirks_mode: bool,
|
||||
|
||||
// If true, the device has changed, and the stylist needs to be updated.
|
||||
is_device_dirty: bool,
|
||||
|
||||
// The current selector maps, after evaluating media
|
||||
// rules against the current device.
|
||||
|
@ -51,10 +102,11 @@ pub struct Stylist {
|
|||
impl Stylist {
|
||||
#[inline]
|
||||
pub fn new(device: Device) -> Stylist {
|
||||
let mut stylist = Stylist {
|
||||
stylesheets: vec!(),
|
||||
let stylist = Stylist {
|
||||
viewport_constraints: None,
|
||||
device: device,
|
||||
is_dirty: true,
|
||||
is_device_dirty: true,
|
||||
quirks_mode: false,
|
||||
|
||||
element_map: PerPseudoElementSelectorMap::new(),
|
||||
before_map: PerPseudoElementSelectorMap::new(),
|
||||
|
@ -63,113 +115,93 @@ impl Stylist {
|
|||
state_deps: StateDependencySet::new(),
|
||||
};
|
||||
// FIXME: Add iso-8859-9.css when the document’s encoding is ISO-8859-8.
|
||||
// FIXME: presentational-hints.css should be at author origin with zero specificity.
|
||||
// (Does it make a difference?)
|
||||
for &filename in &["user-agent.css", "servo.css", "presentational-hints.css"] {
|
||||
match read_resource_file(&[filename]) {
|
||||
Ok(res) => {
|
||||
let ua_stylesheet = Stylesheet::from_bytes(
|
||||
&res,
|
||||
Url::parse(&format!("chrome:///{:?}", filename)).unwrap(),
|
||||
None,
|
||||
None,
|
||||
Origin::UserAgent);
|
||||
stylist.add_stylesheet(ua_stylesheet);
|
||||
}
|
||||
Err(..) => {
|
||||
error!("Stylist::new() failed at loading {}!", filename);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for &(ref contents, ref url) in &opts::get().user_stylesheets {
|
||||
stylist.add_stylesheet(Stylesheet::from_bytes(
|
||||
&contents, url.clone(), None, None, Origin::User));
|
||||
}
|
||||
stylist
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn stylesheets(&self) -> &[Stylesheet] {
|
||||
&self.stylesheets
|
||||
}
|
||||
pub fn update(&mut self, doc_stylesheets: &[&Stylesheet],
|
||||
stylesheets_changed: bool) -> bool {
|
||||
if !(self.is_device_dirty || stylesheets_changed) {
|
||||
return false;
|
||||
}
|
||||
self.element_map = PerPseudoElementSelectorMap::new();
|
||||
self.before_map = PerPseudoElementSelectorMap::new();
|
||||
self.after_map = PerPseudoElementSelectorMap::new();
|
||||
self.rules_source_order = 0;
|
||||
self.state_deps.clear();
|
||||
|
||||
pub fn constrain_viewport(&self) -> Option<ViewportConstraints> {
|
||||
let cascaded_rule = self.stylesheets.iter()
|
||||
.flat_map(|s| s.effective_rules(&self.device).viewport())
|
||||
.cascade();
|
||||
|
||||
ViewportConstraints::maybe_new(self.device.viewport_size, &cascaded_rule)
|
||||
}
|
||||
|
||||
pub fn update(&mut self) -> bool {
|
||||
if self.is_dirty {
|
||||
self.element_map = PerPseudoElementSelectorMap::new();
|
||||
self.before_map = PerPseudoElementSelectorMap::new();
|
||||
self.after_map = PerPseudoElementSelectorMap::new();
|
||||
self.rules_source_order = 0;
|
||||
self.state_deps.clear();
|
||||
|
||||
for stylesheet in &self.stylesheets {
|
||||
let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin {
|
||||
Origin::UserAgent => (
|
||||
&mut self.element_map.user_agent,
|
||||
&mut self.before_map.user_agent,
|
||||
&mut self.after_map.user_agent,
|
||||
),
|
||||
Origin::Author => (
|
||||
&mut self.element_map.author,
|
||||
&mut self.before_map.author,
|
||||
&mut self.after_map.author,
|
||||
),
|
||||
Origin::User => (
|
||||
&mut self.element_map.user,
|
||||
&mut self.before_map.user,
|
||||
&mut self.after_map.user,
|
||||
),
|
||||
};
|
||||
let mut rules_source_order = self.rules_source_order;
|
||||
|
||||
// Take apart the StyleRule into individual Rules and insert
|
||||
// them into the SelectorMap of that priority.
|
||||
macro_rules! append(
|
||||
($style_rule: ident, $priority: ident) => {
|
||||
if $style_rule.declarations.$priority.len() > 0 {
|
||||
for selector in &$style_rule.selectors {
|
||||
let map = match selector.pseudo_element {
|
||||
None => &mut element_map,
|
||||
Some(PseudoElement::Before) => &mut before_map,
|
||||
Some(PseudoElement::After) => &mut after_map,
|
||||
};
|
||||
map.$priority.insert(Rule {
|
||||
selector: selector.compound_selectors.clone(),
|
||||
declarations: DeclarationBlock {
|
||||
specificity: selector.specificity,
|
||||
declarations: $style_rule.declarations.$priority.clone(),
|
||||
source_order: rules_source_order,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
for style_rule in stylesheet.effective_rules(&self.device).style() {
|
||||
append!(style_rule, normal);
|
||||
append!(style_rule, important);
|
||||
rules_source_order += 1;
|
||||
for selector in &style_rule.selectors {
|
||||
self.state_deps.note_selector(selector.compound_selectors.clone());
|
||||
}
|
||||
}
|
||||
self.rules_source_order = rules_source_order;
|
||||
}
|
||||
|
||||
self.is_dirty = false;
|
||||
return true;
|
||||
for ref stylesheet in USER_OR_USER_AGENT_STYLESHEETS.iter() {
|
||||
self.add_stylesheet(&stylesheet);
|
||||
}
|
||||
|
||||
false
|
||||
if self.quirks_mode {
|
||||
self.add_stylesheet(&QUIRKS_MODE_STYLESHEET);
|
||||
}
|
||||
|
||||
for ref stylesheet in doc_stylesheets.iter() {
|
||||
self.add_stylesheet(stylesheet);
|
||||
}
|
||||
|
||||
self.is_device_dirty = false;
|
||||
true
|
||||
}
|
||||
|
||||
fn add_stylesheet(&mut self, stylesheet: &Stylesheet) {
|
||||
let device = &self.device;
|
||||
if !stylesheet.is_effective_for_device(device) {
|
||||
return;
|
||||
}
|
||||
let (mut element_map, mut before_map, mut after_map) = match stylesheet.origin {
|
||||
Origin::UserAgent => (
|
||||
&mut self.element_map.user_agent,
|
||||
&mut self.before_map.user_agent,
|
||||
&mut self.after_map.user_agent,
|
||||
),
|
||||
Origin::Author => (
|
||||
&mut self.element_map.author,
|
||||
&mut self.before_map.author,
|
||||
&mut self.after_map.author,
|
||||
),
|
||||
Origin::User => (
|
||||
&mut self.element_map.user,
|
||||
&mut self.before_map.user,
|
||||
&mut self.after_map.user,
|
||||
),
|
||||
};
|
||||
let mut rules_source_order = self.rules_source_order;
|
||||
|
||||
// Take apart the StyleRule into individual Rules and insert
|
||||
// them into the SelectorMap of that priority.
|
||||
macro_rules! append(
|
||||
($style_rule: ident, $priority: ident) => {
|
||||
if $style_rule.declarations.$priority.len() > 0 {
|
||||
for selector in &$style_rule.selectors {
|
||||
let map = match selector.pseudo_element {
|
||||
None => &mut element_map,
|
||||
Some(PseudoElement::Before) => &mut before_map,
|
||||
Some(PseudoElement::After) => &mut after_map,
|
||||
};
|
||||
map.$priority.insert(Rule {
|
||||
selector: selector.compound_selectors.clone(),
|
||||
declarations: DeclarationBlock {
|
||||
specificity: selector.specificity,
|
||||
declarations: $style_rule.declarations.$priority.clone(),
|
||||
source_order: rules_source_order,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
for style_rule in stylesheet.effective_rules(&self.device).style() {
|
||||
append!(style_rule, normal);
|
||||
append!(style_rule, important);
|
||||
rules_source_order += 1;
|
||||
for selector in &style_rule.selectors {
|
||||
self.state_deps.note_selector(selector.compound_selectors.clone());
|
||||
}
|
||||
}
|
||||
self.rules_source_order = rules_source_order;
|
||||
}
|
||||
|
||||
pub fn restyle_hint_for_state_change<E>(&self, element: &E,
|
||||
|
@ -180,35 +212,29 @@ impl Stylist {
|
|||
self.state_deps.compute_hint(element, current_state, old_state)
|
||||
}
|
||||
|
||||
pub fn set_device(&mut self, device: Device) {
|
||||
let is_dirty = self.is_dirty || self.stylesheets.iter()
|
||||
pub fn set_device(&mut self, mut device: Device, stylesheets: &[&Stylesheet]) {
|
||||
let cascaded_rule = stylesheets.iter()
|
||||
.flat_map(|s| s.effective_rules(&self.device).viewport())
|
||||
.cascade();
|
||||
|
||||
self.viewport_constraints = ViewportConstraints::maybe_new(self.device.viewport_size, &cascaded_rule);
|
||||
if let Some(ref constraints) = self.viewport_constraints {
|
||||
device = Device::new(MediaType::Screen, constraints.size);
|
||||
}
|
||||
let is_device_dirty = self.is_device_dirty || stylesheets.iter()
|
||||
.flat_map(|stylesheet| stylesheet.rules().media())
|
||||
.any(|media_rule| media_rule.evaluate(&self.device) != media_rule.evaluate(&device));
|
||||
|
||||
self.device = device;
|
||||
self.is_dirty |= is_dirty;
|
||||
self.is_device_dirty |= is_device_dirty;
|
||||
}
|
||||
|
||||
pub fn add_quirks_mode_stylesheet(&mut self) {
|
||||
match read_resource_file(&["quirks-mode.css"]) {
|
||||
Ok(res) => {
|
||||
self.add_stylesheet(Stylesheet::from_bytes(
|
||||
&res,
|
||||
Url::parse("chrome:///quirks-mode.css").unwrap(),
|
||||
None,
|
||||
None,
|
||||
Origin::UserAgent));
|
||||
}
|
||||
Err(..) => {
|
||||
error!("Stylist::add_quirks_mode_stylesheet() failed at loading 'quirks-mode.css'!");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
pub fn viewport_constraints(&self) -> &Option<ViewportConstraints> {
|
||||
&self.viewport_constraints
|
||||
}
|
||||
|
||||
pub fn add_stylesheet(&mut self, stylesheet: Stylesheet) {
|
||||
self.stylesheets.push(stylesheet);
|
||||
self.is_dirty = true;
|
||||
pub fn set_quirks_mode(&mut self, enabled: bool) {
|
||||
self.quirks_mode = enabled;
|
||||
}
|
||||
|
||||
/// Returns the applicable CSS declarations for the given element. This corresponds to
|
||||
|
@ -227,7 +253,7 @@ impl Stylist {
|
|||
-> bool
|
||||
where E: Element + TElementAttributes,
|
||||
V: VecLike<DeclarationBlock> {
|
||||
assert!(!self.is_dirty);
|
||||
assert!(!self.is_device_dirty);
|
||||
assert!(style_attribute.is_none() || pseudo_element.is_none(),
|
||||
"Style attributes do not apply to pseudo-elements");
|
||||
|
||||
|
@ -294,8 +320,8 @@ impl Stylist {
|
|||
shareable
|
||||
}
|
||||
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.is_dirty
|
||||
pub fn is_device_dirty(&self) -> bool {
|
||||
self.is_device_dirty
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,13 @@ use std::iter::Iterator;
|
|||
use std::slice;
|
||||
use string_cache::{Atom, Namespace};
|
||||
use url::Url;
|
||||
use util::mem::HeapSizeOf;
|
||||
use viewport::ViewportRule;
|
||||
|
||||
/// Each style rule has an origin, which determines where it enters the cascade.
|
||||
///
|
||||
/// http://dev.w3.org/csswg/css-cascade/#cascading-origins
|
||||
#[derive(Clone, PartialEq, Eq, Copy, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Copy, Debug, HeapSizeOf)]
|
||||
pub enum Origin {
|
||||
/// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua
|
||||
UserAgent,
|
||||
|
@ -35,16 +36,18 @@ pub enum Origin {
|
|||
}
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq)]
|
||||
pub struct Stylesheet {
|
||||
/// List of rules in the order they were found (important for
|
||||
/// cascading order)
|
||||
pub rules: Vec<CSSRule>,
|
||||
/// List of media associated with the Stylesheet, if any.
|
||||
pub media: Option<MediaQueryList>,
|
||||
pub origin: Origin,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq)]
|
||||
pub enum CSSRule {
|
||||
Charset(String),
|
||||
Namespace(Option<String>, Namespace),
|
||||
|
@ -54,7 +57,7 @@ pub enum CSSRule {
|
|||
Viewport(ViewportRule),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq)]
|
||||
pub struct MediaRule {
|
||||
pub media_queries: MediaQueryList,
|
||||
pub rules: Vec<CSSRule>,
|
||||
|
@ -67,7 +70,7 @@ impl MediaRule {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq)]
|
||||
pub struct StyleRule {
|
||||
pub selectors: Vec<Selector>,
|
||||
pub declarations: PropertyDeclarationBlock,
|
||||
|
@ -131,6 +134,23 @@ impl Stylesheet {
|
|||
Stylesheet {
|
||||
origin: origin,
|
||||
rules: rules,
|
||||
media: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the MediaQueryList associated with the style-sheet.
|
||||
pub fn set_media(&mut self, media: Option<MediaQueryList>) {
|
||||
self.media = media;
|
||||
}
|
||||
|
||||
/// Returns whether the style-sheet applies for the current device depending
|
||||
/// on the associated MediaQueryList.
|
||||
///
|
||||
/// Always true if no associated MediaQueryList exists.
|
||||
pub fn is_effective_for_device(&self, device: &Device) -> bool {
|
||||
match self.media {
|
||||
Some(ref media) => media.evaluate(device),
|
||||
None => true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,11 @@ use std::str::Chars;
|
|||
use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
|
||||
use stylesheets::Origin;
|
||||
use util::geometry::ViewportPx;
|
||||
use util::mem::HeapSizeOf;
|
||||
use values::computed::{Context, ToComputedValue};
|
||||
use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, HeapSizeOf, PartialEq)]
|
||||
pub enum ViewportDescriptor {
|
||||
MinWidth(ViewportLength),
|
||||
MaxWidth(ViewportLength),
|
||||
|
@ -133,7 +134,7 @@ struct ViewportRuleParser<'a, 'b: 'a> {
|
|||
context: &'a ParserContext<'b>
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, HeapSizeOf, PartialEq)]
|
||||
pub struct ViewportDescriptorDeclaration {
|
||||
pub origin: Origin,
|
||||
pub descriptor: ViewportDescriptor,
|
||||
|
@ -228,7 +229,7 @@ impl<'a, 'b> DeclarationParser for ViewportRuleParser<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, HeapSizeOf, PartialEq)]
|
||||
pub struct ViewportRule {
|
||||
pub declarations: Vec<ViewportDescriptorDeclaration>
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ define_css_keyword_enum!(Orientation:
|
|||
"landscape" => Landscape);
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ViewportConstraints {
|
||||
pub size: TypedSize2D<ViewportPx, f32>,
|
||||
|
||||
|
@ -54,7 +54,7 @@ impl ToCss for ViewportConstraints {
|
|||
|
||||
/// Zoom is a number | percentage | auto
|
||||
/// See http://dev.w3.org/csswg/css-device-adapt/#descdef-viewport-zoom
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, HeapSizeOf, PartialEq)]
|
||||
pub enum Zoom {
|
||||
Number(f32),
|
||||
Percentage(f32),
|
||||
|
|
|
@ -25,6 +25,7 @@ fn test_parse_stylesheet() {
|
|||
let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent);
|
||||
assert_eq!(stylesheet, Stylesheet {
|
||||
origin: Origin::UserAgent,
|
||||
media: None,
|
||||
rules: vec![
|
||||
CSSRule::Namespace(None, ns!(HTML)),
|
||||
CSSRule::Style(StyleRule {
|
||||
|
|
Загрузка…
Ссылка в новой задаче