Bug 1575062 - Support css use counters for unimplemented properties. r=emilio

For developing properties, we will handle them in an other bug.

Besides, I use an iframe for the test because we create a use counter in
the constructor of Document, which use the prefs to decide what kind of
properties we want to record. So, in the test, we have to reload iframe
to make sure we re-create the document, so does the use counter, to make
sure the prefs work properly.

The two prefs affect the css use counters:
1. layout.css.use-counters.enabled: Allocate use counters, and record
   non-custom properties.
2. layout.css.use-counters-unimplemented.enabled: Record all unimplmented
   properties into the use counters.

If we disable layout.css.use-counters.enblaed, we don't create use counters
object, so layout.css.use-counters-unimplemented.enabled doesn't work,
either.

Differential Revision: https://phabricator.services.mozilla.com/D43860

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Boris Chiou 2019-08-29 23:40:13 +00:00
Родитель 4cfd9c1064
Коммит c8dba8c886
7 изменённых файлов: 391 добавлений и 67 удалений

Просмотреть файл

@ -3,25 +3,42 @@
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<body>
<iframe id="iframe"></iframe>
<script>
const utils = SpecialPowers.getDOMWindowUtils(window);
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{ "set": [["layout.css.use-counters.enabled", true ]]},
run,
);
function assert_recorded(recorded, properties) {
function iframe_reload() {
return new Promise(resolve => {
iframe.addEventListener("load", _ => resolve());
iframe.contentWindow.location.reload();
});
}
function assert_recorded(win, recorded, properties, desc) {
const utils = SpecialPowers.getDOMWindowUtils(win);
for (const prop of properties) {
try {
is(utils.isCssPropertyRecordedInUseCounter(prop), recorded, prop)
is(utils.isCssPropertyRecordedInUseCounter(prop), recorded,
`${desc} - ${prop}`)
} catch(ex) {
ok(false, "Threw: " + prop);
}
}
}
function run() {
// Test implemented properties (i.e. non custom properties).
add_task(async () => {
await SpecialPowers.pushPrefEnv({
"set": [
["layout.css.use-counters.enabled", true],
["layout.css.use-counters-unimplemented.enabled", false]
]
});
let iframe = document.getElementById("iframe");
// Reload iframe to make sure we re-create its document, which makes the
// preference update meaningful.
await iframe_reload();
const style = document.createElement('style');
style.textContent = `
* {
@ -29,27 +46,31 @@ function run() {
-webkit-background-size: 100px 100px; /* longhand alias */
transform-origin: top left; /* longhand */
background: green; /* shorthand */
zoom: 1.0; /* counted unknown */
}
`;
document.body.appendChild(style);
iframe.contentDocument.body.appendChild(style);
assert_recorded(true, [
const win = iframe.contentWindow;
assert_recorded(win, true, [
"grid-gap",
"-webkit-background-size",
"transform-origin",
"background"
]);
], "Test non-custom properties");
// Should only record the aliases, not the non-aliased property.
// Should only record shorthands, not the longhands it expands to.
assert_recorded(false, [
// Should not record counted unknown property.
assert_recorded(win, false, [
"gap",
"background-size",
"-moz-transform-origin",
"-webkit-transform-origin",
"background-color"
]);
"background-color",
"zoom"
], "Test non-custom properties");
// TODO(emilio): Make work (and test) inline style and maybe even CSSOM and
// such?
@ -60,7 +81,58 @@ function run() {
// assert_recorded(true, ["-webkit-transform"]);
// assert_recorded(false, ["transform"]);
//
SimpleTest.finish();
}
style.remove();
await SpecialPowers.flushPrefEnv();
});
// Test unimplemented properties.
add_task(async () => {
await SpecialPowers.pushPrefEnv({
"set": [
["layout.css.use-counters.enabled", true],
["layout.css.use-counters-unimplemented.enabled", true]
]
});
let iframe = document.getElementById("iframe");
// Reload iframe to make sure we re-create its document, which makes the
// preference update meaningful.
await iframe_reload();
const style = document.createElement('style');
style.textContent = `
* {
grid-gap: 1px; /* shorthand alias */
-webkit-background-size: 100px 100px; /* longhand alias */
transform-origin: top left; /* longhand */
background: green; /* shorthand */
-webkit-font-smoothing: auto; /* counted unknown */
zoom: 0.5; /* counted unknown */
}
`;
iframe.contentDocument.body.appendChild(style);
const win = iframe.contentWindow;
assert_recorded(win, true, [
"grid-gap",
"-webkit-background-size",
"transform-origin",
"background",
"-webkit-font-smoothing",
"zoom",
], "Test unimplemented properties");
// Shouldn't record counted unknown properties which doesn't show up.
assert_recorded(win, false, [
"size",
"speak",
], "Test unimplemented properties");
// TODO: Make work (and test) inline style and CSSOM.
style.remove();
await SpecialPowers.flushPrefEnv();
});
</script>
</body>

Просмотреть файл

@ -4765,6 +4765,13 @@
value: @IS_NOT_RELEASE_OR_BETA@
mirror: always
# Are counters for unimplemented CSS properties enabled?
- name: layout.css.use-counters-unimplemented.enabled
type: RelaxedAtomicBool
value: @IS_NOT_RELEASE_OR_BETA@
mirror: always
rust: true
# Should the :visited selector ever match (otherwise :link matches instead)?
- name: layout.css.visited_links_enabled
type: bool

Просмотреть файл

@ -0,0 +1,128 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# The following properties are under development, so they are not in this list.
# FIXME: We should handle the developing properties properly by Bug 1577358:
# "backdrop-filter",
# "text-decoration-skip-ink",
# "column-span",
# "offset-distance",
# "offset-path",
# "offset-rotate"
COUNTED_UNKNOWN_PROPERTIES = [
"-webkit-font-smoothing",
"zoom",
"-webkit-tap-highlight-color",
"speak",
"text-size-adjust",
"-webkit-font-feature-settings",
"-webkit-user-drag",
"size",
"-webkit-clip-path",
"orphans",
"widows",
"-webkit-user-modify",
"-webkit-margin-before",
"-webkit-margin-after",
"tab-size",
"-webkit-margin-start",
"-webkit-column-break-inside",
"-webkit-padding-start",
"-webkit-margin-end",
"-webkit-box-reflect",
"-webkit-print-color-adjust",
"-webkit-mask-box-image",
"-webkit-line-break",
"-webkit-text-security",
"alignment-baseline",
"-webkit-writing-mode",
"baseline-shift",
"-webkit-hyphenate-character",
"page",
"text-underline-position",
"-webkit-highlight",
"background-repeat-x",
"-webkit-padding-end",
"background-repeat-y",
"-webkit-text-emphasis-color",
"-webkit-margin-top-collapse",
"-webkit-rtl-ordering",
"-webkit-padding-before",
"-webkit-text-decorations-in-effect",
"-webkit-border-vertical-spacing",
"-webkit-locale",
"-webkit-padding-after",
"-webkit-border-horizontal-spacing",
"color-rendering",
"-webkit-column-break-before",
"-webkit-transform-origin-x",
"-webkit-transform-origin-y",
"-webkit-text-emphasis-position",
"buffered-rendering",
"-webkit-text-orientation",
"-webkit-text-combine",
"-webkit-text-emphasis-style",
"-webkit-text-emphasis",
"d",
"-webkit-mask-box-image-width",
"-webkit-mask-box-image-source",
"-webkit-mask-box-image-outset",
"-webkit-mask-box-image-slice",
"-webkit-mask-box-image-repeat",
"-webkit-margin-after-collapse",
"-webkit-border-before-color",
"-webkit-border-before-width",
"-webkit-perspective-origin-x",
"-webkit-perspective-origin-y",
"-webkit-margin-before-collapse",
"-webkit-border-before-style",
"scroll-snap-stop",
"-webkit-margin-bottom-collapse",
"-webkit-ruby-position",
"-webkit-column-break-after",
"-webkit-margin-collapse",
"offset",
"-webkit-border-before",
"-webkit-border-end",
"-webkit-border-after",
"-webkit-border-start",
"-webkit-min-logical-width",
"-webkit-logical-height",
"-webkit-transform-origin-z",
"-webkit-font-size-delta",
"-webkit-logical-width",
"-webkit-max-logical-width",
"-webkit-min-logical-height",
"-webkit-max-logical-height",
"-webkit-border-end-color",
"-webkit-border-end-width",
"-webkit-border-start-color",
"-webkit-border-start-width",
"-webkit-border-after-color",
"-webkit-border-after-width",
"-webkit-border-end-style",
"-webkit-border-after-style",
"-webkit-border-start-style",
"-webkit-mask-repeat-x",
"-webkit-mask-repeat-y",
"user-zoom",
"min-zoom",
"-webkit-box-decoration-break",
"orientation",
"max-zoom",
"-webkit-app-region",
"-webkit-column-rule",
"-webkit-column-span",
"-webkit-column-gap",
"-webkit-shape-outside",
"-webkit-column-rule-width",
"-webkit-column-count",
"-webkit-opacity",
"-webkit-column-width",
"-webkit-shape-image-threshold",
"-webkit-column-rule-style",
"-webkit-columns",
"-webkit-column-rule-color",
"-webkit-shape-margin",
]

Просмотреть файл

@ -3,6 +3,7 @@
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import re
from counted_unknown_properties import COUNTED_UNKNOWN_PROPERTIES
PHYSICAL_SIDES = ["top", "right", "bottom", "left"]
LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"]
@ -574,6 +575,7 @@ class PropertiesData(object):
self.shorthands = []
self.shorthands_by_name = {}
self.shorthand_aliases = []
self.counted_unknown_properties = [CountedUnknownProperty(p) for p in COUNTED_UNKNOWN_PROPERTIES]
def new_style_struct(self, *args, **kwargs):
style_struct = StyleStruct(*args, **kwargs)
@ -794,3 +796,9 @@ class PropertyRestrictions:
+ PropertyRestrictions.shorthand(data, "background")
+ PropertyRestrictions.shorthand(data, "outline")
+ PropertyRestrictions.shorthand(data, "font"))
class CountedUnknownProperty:
def __init__(self, name):
self.name = name
self.ident = to_rust_ident(name)
self.camel_case = to_camel_case(self.ident)

Просмотреть файл

@ -38,6 +38,7 @@ use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode};
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
use to_shmem::impl_trivial_to_shmem;
use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
use crate::use_counters::UseCounters;
use crate::values::generics::text::LineHeight;
use crate::values::{computed, resolved};
use crate::values::computed::NonNegativeLength;
@ -427,6 +428,9 @@ pub struct NonCustomPropertyId(usize);
pub const NON_CUSTOM_PROPERTY_ID_COUNT: usize =
${len(data.longhands) + len(data.shorthands) + len(data.all_aliases())};
/// The length of all counted unknown properties.
pub const COUNTED_UNKNOWN_PROPERTY_COUNT: usize = ${len(data.counted_unknown_properties)};
% if engine == "gecko":
#[allow(dead_code)]
unsafe fn static_assert_nscsspropertyid() {
@ -1788,6 +1792,45 @@ impl ToCss for PropertyId {
}
}
/// The counted unknown property list which is used for css use counters.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(u8)]
pub enum CountedUnknownProperty {
% for prop in data.counted_unknown_properties:
/// ${prop.name}
${prop.camel_case},
% endfor
}
impl CountedUnknownProperty {
/// Parse the counted unknown property.
pub fn parse_for_test(property_name: &str) -> Option<Self> {
ascii_case_insensitive_phf_map! {
unknown_id -> CountedUnknownProperty = {
% for property in data.counted_unknown_properties:
"${property.name}" => CountedUnknownProperty::${property.camel_case},
% endfor
}
}
unknown_id(property_name).cloned()
}
/// Returns the underlying index, used for use counter.
pub fn bit(self) -> usize {
self as usize
}
}
#[cfg(feature = "gecko")]
fn is_counted_unknown_use_counters_enabled() -> bool {
static_prefs::pref!("layout.css.use-counters-unimplemented.enabled")
}
#[cfg(feature = "servo")]
fn is_counted_unknown_use_counters_enabled() -> bool {
false
}
impl PropertyId {
/// Return the longhand id that this property id represents.
#[inline]
@ -1801,16 +1844,30 @@ impl PropertyId {
/// Returns a given property from the string `s`.
///
/// Returns Err(()) for unknown non-custom properties.
fn parse_unchecked(property_name: &str) -> Result<Self, ()> {
/// Returns Err(()) for unknown properties.
fn parse_unchecked(
property_name: &str,
use_counters: Option< &UseCounters>,
) -> Result<Self, ()> {
// A special id for css use counters.
// ShorthandAlias is not used in the Servo build.
// That's why we need to allow dead_code.
#[allow(dead_code)]
pub enum StaticId {
Longhand(LonghandId),
Shorthand(ShorthandId),
LonghandAlias(LonghandId, AliasId),
ShorthandAlias(ShorthandId, AliasId),
CountedUnknown(CountedUnknownProperty),
}
ascii_case_insensitive_phf_map! {
property_id -> PropertyId = {
static_id -> StaticId = {
% for (kind, properties) in [("Longhand", data.longhands), ("Shorthand", data.shorthands)]:
% for property in properties:
"${property.name}" => PropertyId::${kind}(${kind}Id::${property.camel_case}),
"${property.name}" => StaticId::${kind}(${kind}Id::${property.camel_case}),
% for alias in property.alias:
"${alias.name}" => {
PropertyId::${kind}Alias(
StaticId::${kind}Alias(
${kind}Id::${property.camel_case},
AliasId::${alias.camel_case},
)
@ -1818,11 +1875,31 @@ impl PropertyId {
% endfor
% endfor
% endfor
% for property in data.counted_unknown_properties:
"${property.name}" => {
StaticId::CountedUnknown(CountedUnknownProperty::${property.camel_case})
},
% endfor
}
}
if let Some(id) = property_id(property_name) {
return Ok(id.clone())
if let Some(id) = static_id(property_name) {
return Ok(match *id {
StaticId::Longhand(id) => PropertyId::Longhand(id),
StaticId::Shorthand(id) => PropertyId::Shorthand(id),
StaticId::LonghandAlias(id, alias) => PropertyId::LonghandAlias(id, alias),
StaticId::ShorthandAlias(id, alias) => PropertyId::ShorthandAlias(id, alias),
StaticId::CountedUnknown(unknown_prop) => {
if is_counted_unknown_use_counters_enabled() {
if let Some(counters) = use_counters {
counters.counted_unknown_properties.record(unknown_prop);
}
}
// Always return Err(()) because these aren't valid custom property names.
return Err(());
}
});
}
let name = crate::custom_properties::parse_name(property_name)?;
@ -1833,7 +1910,7 @@ impl PropertyId {
/// enabled for all content.
#[inline]
pub fn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> {
let id = Self::parse_unchecked(name)?;
let id = Self::parse_unchecked(name, None)?;
if !id.enabled_for_all_content() {
return Err(());
@ -1847,7 +1924,7 @@ impl PropertyId {
/// allowed in this context.
#[inline]
pub fn parse(name: &str, context: &ParserContext) -> Result<Self, ()> {
let id = Self::parse_unchecked(name)?;
let id = Self::parse_unchecked(name, context.use_counters)?;
if !id.allowed_in(context) {
return Err(());
@ -1865,7 +1942,7 @@ impl PropertyId {
name: &str,
context: &ParserContext,
) -> Result<Self, ()> {
let id = Self::parse_unchecked(name)?;
let id = Self::parse_unchecked(name, None)?;
if !id.allowed_in_ignoring_rule_type(context) {
return Err(());

Просмотреть файл

@ -6,6 +6,7 @@
#[cfg(feature = "gecko")]
use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
use crate::properties::{CountedUnknownProperty, COUNTED_UNKNOWN_PROPERTY_COUNT};
use crate::properties::{NonCustomPropertyId, NON_CUSTOM_PROPERTY_ID_COUNT};
use std::cell::Cell;
@ -15,46 +16,62 @@ const BITS_PER_ENTRY: usize = 64;
#[cfg(target_pointer_width = "32")]
const BITS_PER_ENTRY: usize = 32;
/// One bit per each non-custom CSS property.
#[derive(Default)]
pub struct CountedUnknownPropertyUseCounters {
storage: [Cell<usize>; (COUNTED_UNKNOWN_PROPERTY_COUNT - 1 + BITS_PER_ENTRY) / BITS_PER_ENTRY],
}
/// One bit per each non-custom CSS property.
#[derive(Default)]
pub struct NonCustomPropertyUseCounters {
storage: [Cell<usize>; (NON_CUSTOM_PROPERTY_ID_COUNT - 1 + BITS_PER_ENTRY) / BITS_PER_ENTRY],
}
impl NonCustomPropertyUseCounters {
/// Returns the bucket a given property belongs in, and the bitmask for that
/// property.
#[inline(always)]
fn bucket_and_pattern(id: NonCustomPropertyId) -> (usize, usize) {
let bit = id.bit();
let bucket = bit / BITS_PER_ENTRY;
let bit_in_bucket = bit % BITS_PER_ENTRY;
(bucket, 1 << bit_in_bucket)
}
/// Record that a given non-custom property ID has been parsed.
#[inline]
pub fn record(&self, id: NonCustomPropertyId) {
let (bucket, pattern) = Self::bucket_and_pattern(id);
let bucket = &self.storage[bucket];
bucket.set(bucket.get() | pattern)
}
/// Returns whether a given non-custom property ID has been recorded
/// earlier.
#[inline]
pub fn recorded(&self, id: NonCustomPropertyId) -> bool {
let (bucket, pattern) = Self::bucket_and_pattern(id);
self.storage[bucket].get() & pattern != 0
}
/// Merge `other` into `self`.
#[inline]
fn merge(&self, other: &Self) {
for (bucket, other_bucket) in self.storage.iter().zip(other.storage.iter()) {
bucket.set(bucket.get() | other_bucket.get())
macro_rules! property_use_counters_methods {
($id: ident) => {
/// Returns the bucket a given property belongs in, and the bitmask for that
/// property.
#[inline(always)]
fn bucket_and_pattern(id: $id) -> (usize, usize) {
let bit = id.bit();
let bucket = bit / BITS_PER_ENTRY;
let bit_in_bucket = bit % BITS_PER_ENTRY;
(bucket, 1 << bit_in_bucket)
}
}
/// Record that a given property ID has been parsed.
#[inline]
pub fn record(&self, id: $id) {
let (bucket, pattern) = Self::bucket_and_pattern(id);
let bucket = &self.storage[bucket];
bucket.set(bucket.get() | pattern)
}
/// Returns whether a given property ID has been recorded
/// earlier.
#[inline]
pub fn recorded(&self, id: $id) -> bool {
let (bucket, pattern) = Self::bucket_and_pattern(id);
self.storage[bucket].get() & pattern != 0
}
/// Merge `other` into `self`.
#[inline]
fn merge(&self, other: &Self) {
for (bucket, other_bucket) in self.storage.iter().zip(other.storage.iter()) {
bucket.set(bucket.get() | other_bucket.get())
}
}
};
}
impl CountedUnknownPropertyUseCounters {
property_use_counters_methods!(CountedUnknownProperty);
}
impl NonCustomPropertyUseCounters {
property_use_counters_methods!(NonCustomPropertyId);
}
/// The use-counter data related to a given document we want to store.
@ -63,6 +80,8 @@ pub struct UseCounters {
/// The counters for non-custom properties that have been parsed in the
/// document's stylesheets.
pub non_custom_properties: NonCustomPropertyUseCounters,
/// The counters for css properties which we haven't implemented yet.
pub counted_unknown_properties: CountedUnknownPropertyUseCounters,
}
impl UseCounters {
@ -72,7 +91,9 @@ impl UseCounters {
#[inline]
pub fn merge(&self, other: &Self) {
self.non_custom_properties
.merge(&other.non_custom_properties)
.merge(&other.non_custom_properties);
self.counted_unknown_properties
.merge(&other.counted_unknown_properties);
}
}

Просмотреть файл

@ -103,7 +103,7 @@ use style::parser::{self, Parse, ParserContext};
use style::profiler_label;
use style::properties::animated_properties::{AnimationValue, AnimationValueMap};
use style::properties::{parse_one_declaration_into, parse_style_attribute};
use style::properties::{ComputedValues, Importance, NonCustomPropertyId};
use style::properties::{ComputedValues, CountedUnknownProperty, Importance, NonCustomPropertyId};
use style::properties::{LonghandId, LonghandIdSet, PropertyDeclarationBlock, PropertyId};
use style::properties::{PropertyDeclarationId, ShorthandId};
use style::properties::{SourcePropertyDeclaration, StyleBuilder, UnparsedValue};
@ -6553,13 +6553,24 @@ pub unsafe extern "C" fn Servo_IsCssPropertyRecordedInUseCounter(
property: *const nsACString,
known_prop: *mut bool,
) -> bool {
let prop_id = parse_enabled_property_name!(property, known_prop, false);
let non_custom_id = match prop_id.non_custom_id() {
Some(id) => id,
None => return false,
*known_prop = false;
let prop_name = property.as_ref().unwrap().as_str_unchecked();
let non_custom_id = match PropertyId::parse_enabled_for_all_content(prop_name) {
Ok(p) => {
*known_prop = true;
p.non_custom_id()
}
Err(..) => None,
};
use_counters.non_custom_properties.recorded(non_custom_id)
if let Some(id) = non_custom_id {
return use_counters.non_custom_properties.recorded(id);
}
CountedUnknownProperty::parse_for_test(prop_name).map_or(false, |p| {
*known_prop = true;
use_counters.counted_unknown_properties.recorded(p)
})
}
#[no_mangle]