style: Introduce Chrome UI privilege for parsers draft
authorTing-Yu Lin <tlin@mozilla.com>
Mon, 31 Jul 2017 16:03:51 +0800
changeset 618436 8322dc3ac8c50593d4a94959b98e555feda52bfb
parent 618435 bb6a9dde93e7e5f4cefb229b9094b2cbc9a128b2
child 618437 838437bd0e4df5b9525fcda760f5567c100e9f72
push id71327
push userbmo:tlin@mozilla.com
push dateMon, 31 Jul 2017 13:18:18 +0000
milestone56.0a1
style: Introduce Chrome UI privilege for parsers The motivation is that Chrome XBL stylesheets can be parsed under author level, but we allow some event-state pseudo classes like :-moz-handled-clicktoplay to be used. Also synchronize the privilege of pseudo classes in non_ts_pseudo_class_list.rs and nsCSSPseudoClassList.h (except :fullscreen). MozReview-Commit-ID: 8fUjjC8hbQO
servo/components/script/dom/cssstylerule.rs
servo/components/style/gecko/non_ts_pseudo_class_list.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/selector_parser.rs
servo/components/style/stylesheets/mod.rs
servo/components/style/stylesheets/rule_parser.rs
servo/components/url/lib.rs
servo/tests/unit/style/parsing/selectors.rs
--- a/servo/components/script/dom/cssstylerule.rs
+++ b/servo/components/script/dom/cssstylerule.rs
@@ -88,16 +88,17 @@ impl CSSStyleRuleMethods for CSSStyleRul
     // https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext
     fn SetSelectorText(&self, value: DOMString) {
         // It's not clear from the spec if we should use the stylesheet's namespaces.
         // https://github.com/w3c/csswg-drafts/issues/1511
         let namespaces = self.cssrule.parent_stylesheet().style_stylesheet().contents.namespaces.read();
         let parser = SelectorParser {
             stylesheet_origin: Origin::Author,
             namespaces: &namespaces,
+            url_data: None,
         };
         let mut css_parser = CssParserInput::new(&*value);
         let mut css_parser = CssParser::new(&mut css_parser);
         if let Ok(mut s) = SelectorList::parse(&parser, &mut css_parser) {
             // This mirrors what we do in CSSStyleOwner::mutate_associated_block.
             let mut guard = self.cssrule.shared_lock().write();
             let mut stylerule = self.stylerule.write_with(&mut guard);
             mem::swap(&mut stylerule.selectors, &mut s);
--- a/servo/components/style/gecko/non_ts_pseudo_class_list.rs
+++ b/servo/components/style/gecko/non_ts_pseudo_class_list.rs
@@ -40,55 +40,55 @@
  */
 
 macro_rules! apply_non_ts_list {
     ($apply_macro:ident) => {
         $apply_macro! {
             bare: [
                 ("unresolved", Unresolved, unresolved, IN_UNRESOLVED_STATE, _),
                 ("-moz-table-border-nonzero", MozTableBorderNonzero, mozTableBorderNonzero, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
-                ("-moz-browser-frame", MozBrowserFrame, mozBrowserFrame, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+                ("-moz-browser-frame", MozBrowserFrame, mozBrowserFrame, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
                 ("link", Link, link, IN_UNVISITED_STATE, _),
                 ("any-link", AnyLink, anyLink, IN_VISITED_OR_UNVISITED_STATE, _),
                 ("visited", Visited, visited, IN_VISITED_STATE, _),
                 ("active", Active, active, IN_ACTIVE_STATE, _),
                 ("checked", Checked, checked, IN_CHECKED_STATE, _),
                 ("disabled", Disabled, disabled, IN_DISABLED_STATE, _),
                 ("enabled", Enabled, enabled, IN_ENABLED_STATE, _),
                 ("focus", Focus, focus, IN_FOCUS_STATE, _),
                 ("focus-within", FocusWithin, focusWithin, IN_FOCUS_WITHIN_STATE, _),
                 ("hover", Hover, hover, IN_HOVER_STATE, _),
                 ("-moz-drag-over", MozDragOver, mozDragOver, IN_DRAGOVER_STATE, _),
                 ("target", Target, target, IN_TARGET_STATE, _),
                 ("indeterminate", Indeterminate, indeterminate, IN_INDETERMINATE_STATE, _),
                 ("-moz-devtools-highlighted", MozDevtoolsHighlighted, mozDevtoolsHighlighted, IN_DEVTOOLS_HIGHLIGHTED_STATE, _),
                 ("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, mozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, _),
-                // TODO(emilio): Needs pref check for
-                // full-screen-api.unprefix.enabled!
+                // TODO(emilio): Needs pref check for full-screen-api.unprefix.enabled!
+                // TODO(TYLin): Needs to use CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME?
                 ("fullscreen", Fullscreen, fullscreen, IN_FULLSCREEN_STATE, _),
                 ("-moz-full-screen", MozFullScreen, mozFullScreen, IN_FULLSCREEN_STATE, _),
                 // TODO(emilio): This is inconsistently named (the capital R).
                 ("-moz-focusring", MozFocusRing, mozFocusRing, IN_FOCUSRING_STATE, _),
                 ("-moz-broken", MozBroken, mozBroken, IN_BROKEN_STATE, _),
                 ("-moz-loading", MozLoading, mozLoading, IN_LOADING_STATE, _),
-                ("-moz-suppressed", MozSuppressed, mozSuppressed, IN_SUPPRESSED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+                ("-moz-suppressed", MozSuppressed, mozSuppressed, IN_SUPPRESSED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
                 ("-moz-has-dir-attr", MozHasDirAttr, mozHasDirAttr, IN_HAS_DIR_ATTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
                 ("-moz-dir-attr-ltr", MozDirAttrLTR, mozDirAttrLTR, IN_HAS_DIR_ATTR_LTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
                 ("-moz-dir-attr-rtl", MozDirAttrRTL, mozDirAttrRTL, IN_HAS_DIR_ATTR_RTL_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
                 ("-moz-dir-attr-like-auto", MozDirAttrLikeAuto, mozDirAttrLikeAuto, IN_HAS_DIR_ATTR_LIKE_AUTO_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
-                ("-moz-autofill", MozAutofill, mozAutofill, IN_AUTOFILL_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
-                ("-moz-autofill-preview", MozAutofillPreview, mozAutofillPreview, IN_AUTOFILL_PREVIEW_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+                ("-moz-autofill", MozAutofill, mozAutofill, IN_AUTOFILL_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+                ("-moz-autofill-preview", MozAutofillPreview, mozAutofillPreview, IN_AUTOFILL_PREVIEW_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
 
-                ("-moz-handler-clicktoplay", MozHandlerClickToPlay, mozHandlerClickToPlay, IN_HANDLER_CLICK_TO_PLAY_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
-                ("-moz-handler-vulnerable-updatable", MozHandlerVulnerableUpdatable, mozHandlerVulnerableUpdatable, IN_HANDLER_VULNERABLE_UPDATABLE_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
-                ("-moz-handler-vulnerable-no-update", MozHandlerVulnerableNoUpdate, mozHandlerVulnerableNoUpdate, IN_HANDLER_VULNERABLE_NO_UPDATE_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+                ("-moz-handler-clicktoplay", MozHandlerClickToPlay, mozHandlerClickToPlay, IN_HANDLER_CLICK_TO_PLAY_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+                ("-moz-handler-vulnerable-updatable", MozHandlerVulnerableUpdatable, mozHandlerVulnerableUpdatable, IN_HANDLER_VULNERABLE_UPDATABLE_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+                ("-moz-handler-vulnerable-no-update", MozHandlerVulnerableNoUpdate, mozHandlerVulnerableNoUpdate, IN_HANDLER_VULNERABLE_NO_UPDATE_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
 
-                ("-moz-handler-disabled", MozHandlerDisabled, mozHandlerDisabled, IN_HANDLER_DISABLED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
-                ("-moz-handler-blocked", MozHandlerBlocked, mozHandlerBlocked, IN_HANDLER_BLOCKED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
-                ("-moz-handler-crashed", MozHandlerCrashed, mozHandlerCrashed, IN_HANDLER_CRASHED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+                ("-moz-handler-disabled", MozHandlerDisabled, mozHandlerDisabled, IN_HANDLER_DISABLED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+                ("-moz-handler-blocked", MozHandlerBlocked, mozHandlerBlocked, IN_HANDLER_BLOCKED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
+                ("-moz-handler-crashed", MozHandlerCrashed, mozHandlerCrashed, IN_HANDLER_CRASHED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
                 ("-moz-math-increment-script-level", MozMathIncrementScriptLevel, mozMathIncrementScriptLevel, IN_INCREMENT_SCRIPT_LEVEL_STATE, _),
 
                 ("required", Required, required, IN_REQUIRED_STATE, _),
                 ("optional", Optional, optional, IN_OPTIONAL_STATE, _),
                 ("valid", Valid, valid, IN_VALID_STATE, _),
                 ("invalid", Invalid, invalid, IN_INVALID_STATE, _),
                 ("in-range", InRange, inRange, IN_INRANGE_STATE, _),
                 ("out-of-range", OutOfRange, outOfRange, IN_OUTOFRANGE_STATE, _),
@@ -98,17 +98,17 @@ macro_rules! apply_non_ts_list {
                 ("-moz-read-write", MozReadWrite, mozReadWrite, IN_MOZ_READWRITE_STATE, _),
                 ("-moz-submit-invalid", MozSubmitInvalid, mozSubmitInvalid, IN_MOZ_SUBMITINVALID_STATE, _),
                 ("-moz-ui-valid", MozUIValid, mozUIValid, IN_MOZ_UI_VALID_STATE, _),
                 ("-moz-ui-invalid", MozUIInvalid, mozUIInvalid, IN_MOZ_UI_INVALID_STATE, _),
                 ("-moz-meter-optimum", MozMeterOptimum, mozMeterOptimum, IN_OPTIMUM_STATE, _),
                 ("-moz-meter-sub-optimum", MozMeterSubOptimum, mozMeterSubOptimum, IN_SUB_OPTIMUM_STATE, _),
                 ("-moz-meter-sub-sub-optimum", MozMeterSubSubOptimum, mozMeterSubSubOptimum, IN_SUB_SUB_OPTIMUM_STATE, _),
 
-                ("-moz-user-disabled", MozUserDisabled, mozUserDisabled, IN_USER_DISABLED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
+                ("-moz-user-disabled", MozUserDisabled, mozUserDisabled, IN_USER_DISABLED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
 
                 ("-moz-first-node", MozFirstNode, firstNode, _, _),
                 ("-moz-last-node", MozLastNode, lastNode, _, _),
                 ("-moz-only-whitespace", MozOnlyWhitespace, mozOnlyWhitespace, _, _),
                 ("-moz-native-anonymous", MozNativeAnonymous, mozNativeAnonymous, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
                 ("-moz-use-shadow-tree-root", MozUseShadowTreeRoot, mozUseShadowTreeRoot, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
                 ("-moz-is-html", MozIsHTML, mozIsHTML, _, _),
                 ("-moz-placeholder", MozPlaceholder, mozPlaceholder, _, _),
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -13,19 +13,22 @@ use selectors::visitor::SelectorVisitor;
 use std::fmt;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
 use style_traits::{ParseError, StyleParseError};
 
 pub use gecko::pseudo_element::{PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT};
 pub use gecko::snapshot::SnapshotMap;
 
 bitflags! {
+    // See NonTSPseudoClass::is_enabled_in()
     flags NonTSPseudoClassFlag: u8 {
-        // See NonTSPseudoClass::is_internal()
         const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS = 1 << 0,
+        const PSEUDO_CLASS_ENABLED_IN_CHROME = 1 << 1,
+        const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME =
+            PSEUDO_CLASS_ENABLED_IN_UA_SHEETS.bits | PSEUDO_CLASS_ENABLED_IN_CHROME.bits,
     }
 }
 
 /// The type used for storing pseudo-class string arguments.
 pub type PseudoClassStringArg = Box<[u16]>;
 
 macro_rules! pseudo_class_name {
     (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
@@ -117,36 +120,36 @@ impl SelectorMethods for NonTSPseudoClas
         }
 
         true
     }
 }
 
 
 impl NonTSPseudoClass {
-    /// A pseudo-class is internal if it can only be used inside
-    /// user agent style sheets.
-    pub fn is_internal(&self) -> bool {
+    /// Returns true if this pseudo-class is enabled under the context of
+    /// the given flag.
+    fn is_enabled_in(&self, flag: NonTSPseudoClassFlag) -> bool {
         macro_rules! check_flag {
             (_) => (false);
-            ($flags:expr) => ($flags.contains(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS));
+            ($flags:expr) => ($flags.contains(flag));
         }
-        macro_rules! pseudo_class_check_internal {
+        macro_rules! pseudo_class_check_is_enabled_in {
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
             string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*],
             keyword: [$(($k_css:expr, $k_name:ident, $k_gecko_type:tt, $k_state:tt, $k_flags:tt),)*]) => {
                 match *self {
                     $(NonTSPseudoClass::$name => check_flag!($flags),)*
                     $(NonTSPseudoClass::$s_name(..) => check_flag!($s_flags),)*
                     $(NonTSPseudoClass::$k_name(..) => check_flag!($k_flags),)*
                     NonTSPseudoClass::MozAny(_) => false,
                 }
             }
         }
-        apply_non_ts_list!(pseudo_class_check_internal)
+        apply_non_ts_list!(pseudo_class_check_is_enabled_in)
     }
 
     /// https://drafts.csswg.org/selectors-4/#useraction-pseudos
     ///
     /// We intentionally skip the link-related ones.
     pub fn is_safe_user_action_state(&self) -> bool {
         matches!(*self, NonTSPseudoClass::Hover |
                         NonTSPseudoClass::Active |
@@ -258,16 +261,31 @@ impl ::selectors::SelectorImpl for Selec
 
     #[inline]
     fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
         matches!(*pseudo_class, NonTSPseudoClass::Active |
                                 NonTSPseudoClass::Hover)
     }
 }
 
+impl<'a> SelectorParser<'a> {
+    fn is_pseudo_class_enabled(&self,
+                               pseudo_class: &NonTSPseudoClass)
+                               -> bool {
+        let enabled_in_ua = pseudo_class.is_enabled_in(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS);
+        let enabled_in_chrome = pseudo_class.is_enabled_in(PSEUDO_CLASS_ENABLED_IN_CHROME);
+        if !enabled_in_ua && !enabled_in_chrome {
+            true
+        } else {
+            (enabled_in_ua && self.in_user_agent_stylesheet()) ||
+            (enabled_in_chrome && self.in_chrome_stylesheet())
+        }
+    }
+}
+
 impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
     type Impl = SelectorImpl;
     type Error = StyleParseError<'i>;
 
     fn is_pseudo_element_allows_single_colon(name: &CowRcStr<'i>) -> bool {
         ::selectors::parser::is_css2_pseudo_element(name) ||
             name.starts_with("-moz-tree-") // tree pseudo-elements
     }
@@ -281,17 +299,17 @@ impl<'a, 'i> ::selectors::Parser<'i> for
                 match_ignore_ascii_case! { &name,
                     $($css => NonTSPseudoClass::$name,)*
                     _ => return Err(::selectors::parser::SelectorParseError::UnexpectedIdent(
                         name.clone()).into())
                 }
             }
         }
         let pseudo_class = apply_non_ts_list!(pseudo_class_parse);
-        if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
+        if self.is_pseudo_class_enabled(&pseudo_class) {
             Ok(pseudo_class)
         } else {
             Err(SelectorParseError::UnexpectedIdent(name).into())
         }
     }
 
     fn parse_non_ts_functional_pseudo_class<'t>(&self,
                                                 name: CowRcStr<'i>,
@@ -326,17 +344,17 @@ impl<'a, 'i> ::selectors::Parser<'i> for
                         }
                         NonTSPseudoClass::MozAny(selectors.into_boxed_slice())
                     }
                     _ => return Err(SelectorParseError::UnexpectedIdent(name.clone()).into())
                 }
             }
         }
         let pseudo_class = apply_non_ts_list!(pseudo_class_string_parse);
-        if !pseudo_class.is_internal() || self.in_user_agent_stylesheet() {
+        if self.is_pseudo_class_enabled(&pseudo_class) {
             Ok(pseudo_class)
         } else {
             Err(SelectorParseError::UnexpectedIdent(name).into())
         }
     }
 
     fn parse_pseudo_element(&self, name: CowRcStr<'i>) -> Result<PseudoElement, ParseError<'i>> {
         PseudoElement::from_slice(&name, self.in_user_agent_stylesheet())
--- a/servo/components/style/selector_parser.rs
+++ b/servo/components/style/selector_parser.rs
@@ -6,17 +6,17 @@
 
 #![deny(missing_docs)]
 
 use cssparser::{Parser as CssParser, ParserInput};
 use selectors::Element;
 use selectors::parser::SelectorList;
 use std::fmt::Debug;
 use style_traits::ParseError;
-use stylesheets::{Origin, Namespaces};
+use stylesheets::{Origin, Namespaces, UrlExtraData};
 
 /// A convenient alias for the type that represents an attribute value used for
 /// selector parser implementation.
 pub type AttrValue = <SelectorImpl as ::selectors::SelectorImpl>::AttrValue;
 
 #[cfg(feature = "servo")]
 pub use servo::selector_parser::*;
 
@@ -47,38 +47,51 @@ pub type PreExistingComputedValues = ::g
 
 /// Servo's selector parser.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct SelectorParser<'a> {
     /// The origin of the stylesheet we're parsing.
     pub stylesheet_origin: Origin,
     /// The namespace set of the stylesheet.
     pub namespaces: &'a Namespaces,
+    /// The extra URL data of the stylesheet.
+    pub url_data: Option<&'a UrlExtraData>,
 }
 
 impl<'a> SelectorParser<'a> {
     /// Parse a selector list with an author origin and without taking into
     /// account namespaces.
     ///
     /// This is used for some DOM APIs like `querySelector`.
     pub fn parse_author_origin_no_namespace(input: &str)
                                             -> Result<SelectorList<SelectorImpl>, ParseError> {
         let namespaces = Namespaces::default();
         let parser = SelectorParser {
             stylesheet_origin: Origin::Author,
             namespaces: &namespaces,
+            url_data: None,
         };
         let mut input = ParserInput::new(input);
         SelectorList::parse(&parser, &mut CssParser::new(&mut input))
     }
 
     /// Whether we're parsing selectors in a user-agent stylesheet.
     pub fn in_user_agent_stylesheet(&self) -> bool {
         matches!(self.stylesheet_origin, Origin::UserAgent)
     }
+
+    /// Whether we're parsing selectors in a stylesheet that has chrome
+    /// privilege.
+    pub fn in_chrome_stylesheet(&self) -> bool {
+        if let Some(url_data) = self.url_data {
+            url_data.is_chrome()
+        } else {
+            false
+        }
+    }
 }
 
 /// This enumeration determines if a pseudo-element is eagerly cascaded or not.
 ///
 /// If you're implementing a public selector for `Servo` that the end-user might
 /// customize, then you probably need to make it eager.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum PseudoElementCascadeType {
--- a/servo/components/style/stylesheets/mod.rs
+++ b/servo/components/style/stylesheets/mod.rs
@@ -63,16 +63,21 @@ pub type UrlExtraData =
 impl UrlExtraData {
     /// Returns a string for the url.
     ///
     /// Unimplemented currently.
     pub fn as_str(&self) -> &str {
         // TODO
         "(stylo: not supported)"
     }
+
+    /// True if this URL scheme is chrome.
+    pub fn is_chrome(&self) -> bool {
+        self.mIsChrome
+    }
 }
 
 // XXX We probably need to figure out whether we should mark Eq here.
 // It is currently marked so because properties::UnparsedValue wants Eq.
 #[cfg(feature = "gecko")]
 impl Eq for UrlExtraData {}
 
 /// Each style rule has an origin, which determines where it enters the cascade.
--- a/servo/components/style/stylesheets/rule_parser.rs
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -502,16 +502,17 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i>
     type QualifiedRule = CssRule;
     type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
     fn parse_prelude<'t>(&mut self, input: &mut Parser<'i, 't>)
                          -> Result<QualifiedRuleParserPrelude, ParseError<'i>> {
         let selector_parser = SelectorParser {
             stylesheet_origin: self.stylesheet_origin,
             namespaces: self.context.namespaces.unwrap(),
+            url_data: Some(self.context.url_data),
         };
 
         let location = get_location_with_offset(input.current_source_location(),
                                                 self.context.line_number_offset);
         let selectors = SelectorList::parse(&selector_parser, input)?;
 
         Ok(QualifiedRuleParserPrelude {
             selectors: selectors,
--- a/servo/components/url/lib.rs
+++ b/servo/components/url/lib.rs
@@ -80,16 +80,20 @@ impl ServoUrl {
         self.0.scheme()
     }
 
     pub fn is_secure_scheme(&self) -> bool {
         let scheme = self.scheme();
         scheme == "https" || scheme == "wss"
     }
 
+    pub fn is_chrome(&self) -> bool {
+        self.scheme() == "chrome"
+    }
+
     pub fn as_str(&self) -> &str {
         self.0.as_str()
     }
 
     pub fn as_mut_url(&mut self) -> &mut Url {
         Arc::make_mut(&mut self.0)
     }
 
--- a/servo/tests/unit/style/parsing/selectors.rs
+++ b/servo/tests/unit/style/parsing/selectors.rs
@@ -9,16 +9,17 @@ use style::stylesheets::{Origin, Namespa
 use style_traits::ParseError;
 
 fn parse_selector<'i, 't>(input: &mut Parser<'i, 't>) -> Result<SelectorList<SelectorImpl>, ParseError<'i>> {
     let mut ns = Namespaces::default();
     ns.prefixes.insert("svg".into(), (ns!(svg), ()));
     let parser = SelectorParser {
         stylesheet_origin: Origin::UserAgent,
         namespaces: &ns,
+        url_data: None,
     };
     SelectorList::parse(&parser, input)
 }
 
 #[test]
 fn test_selectors() {
     assert_roundtrip!(parse_selector, "div");
     assert_roundtrip!(parse_selector, "svg|circle");