From 585c00f23559e9cf5dea288852515fd001bc1c43 Mon Sep 17 00:00:00 2001 From: Ting-Yu Lin Date: Mon, 31 Jul 2017 16:03:51 +0800 Subject: [PATCH] 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 --- components/script/dom/cssstylerule.rs | 1 + .../style/gecko/non_ts_pseudo_class_list.rs | 26 +++++++------- components/style/gecko/selector_parser.rs | 36 ++++++++++++++----- components/style/selector_parser.rs | 12 ++++++- components/style/stylesheets/mod.rs | 5 +++ components/style/stylesheets/rule_parser.rs | 1 + components/url/lib.rs | 4 +++ tests/unit/style/parsing/selectors.rs | 1 + 8 files changed, 63 insertions(+), 23 deletions(-) diff --git a/components/script/dom/cssstylerule.rs b/components/script/dom/cssstylerule.rs index ea121ef900a..a45e0ad20c8 100644 --- a/components/script/dom/cssstylerule.rs +++ b/components/script/dom/cssstylerule.rs @@ -93,6 +93,7 @@ impl CSSStyleRuleMethods for CSSStyleRule { 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); diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index d6a5a838c8a..9b30e4a5ee2 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -45,7 +45,7 @@ macro_rules! apply_non_ts_list { 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, _), @@ -61,29 +61,29 @@ macro_rules! apply_non_ts_list { ("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, _), @@ -103,7 +103,7 @@ macro_rules! apply_non_ts_list { ("-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, _, _), diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index af2903dddea..26802333007 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -18,9 +18,12 @@ 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, } } @@ -122,14 +125,14 @@ impl SelectorMethods for NonTSPseudoClass { 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),)*]) => { @@ -141,7 +144,7 @@ impl NonTSPseudoClass { } } } - 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 @@ -263,6 +266,21 @@ impl ::selectors::SelectorImpl for SelectorImpl { } } +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>; @@ -286,7 +304,7 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { } } 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()) @@ -331,7 +349,7 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { } } 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()) diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index 3f5a43365cf..8399d5e6778 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -11,7 +11,7 @@ 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. @@ -52,6 +52,9 @@ pub struct SelectorParser<'a> { pub stylesheet_origin: Origin, /// The namespace set of the stylesheet. pub namespaces: &'a Namespaces, + /// The extra URL data of the stylesheet, which is used to look up + /// whether we are parsing a chrome:// URL style sheet. + pub url_data: Option<&'a UrlExtraData>, } impl<'a> SelectorParser<'a> { @@ -65,6 +68,7 @@ impl<'a> SelectorParser<'a> { 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)) @@ -74,6 +78,12 @@ impl<'a> SelectorParser<'a> { 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 { + self.url_data.map_or(false, |d| d.is_chrome()) + } } /// This enumeration determines if a pseudo-element is eagerly cascaded or not. diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index 9a0271c2f54..758bbdeb1a1 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -70,6 +70,11 @@ impl UrlExtraData { // 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. diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 007d7e11026..e96c85c74a9 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -507,6 +507,7 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> { 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(), diff --git a/components/url/lib.rs b/components/url/lib.rs index b862e312bc2..e8e8b1acce1 100644 --- a/components/url/lib.rs +++ b/components/url/lib.rs @@ -85,6 +85,10 @@ impl ServoUrl { scheme == "https" || scheme == "wss" } + pub fn is_chrome(&self) -> bool { + self.scheme() == "chrome" + } + pub fn as_str(&self) -> &str { self.0.as_str() } diff --git a/tests/unit/style/parsing/selectors.rs b/tests/unit/style/parsing/selectors.rs index 15611dd093c..8d6e94fd471 100644 --- a/tests/unit/style/parsing/selectors.rs +++ b/tests/unit/style/parsing/selectors.rs @@ -14,6 +14,7 @@ fn parse_selector<'i, 't>(input: &mut Parser<'i, 't>) -> Result