Bug 1611236 - Optimize adoptedStyleSheets append case r=emilio

- Generalize enumeration over unique sheets to take any array of sheets.
- Add check if the new adoptedStyleSheets only appends sheets.
- Handle append case for new adoptedStyleSheets.
- Add WPT test case for duplicate sheets in the ShadowRoot.
- Add WPT test cases for appending sheets.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Erik Nordin 2020-03-02 20:38:28 +00:00
parent f1666a5ee9
commit ae085e705a
3 changed files with 112 additions and 12 deletions

View File

@ -127,14 +127,50 @@ void DocumentOrShadowRoot::SetAdoptedStyleSheets(
auto* shadow = ShadowRoot::FromNode(AsNode());
MOZ_ASSERT((mKind == Kind::ShadowRoot) == !!shadow);
ClearAdoptedStyleSheets();
StyleSheetSet set(aAdoptedStyleSheets.Length());
size_t commonPrefix = 0;
// Find the index at which the new array differs from the old array.
// We don't want to do extra work for the sheets that both arrays have.
size_t min =
std::min(aAdoptedStyleSheets.Length(), mAdoptedStyleSheets.Length());
for (size_t i = 0; i < min; ++i) {
if (aAdoptedStyleSheets[i] != mAdoptedStyleSheets[i]) {
break;
}
++commonPrefix;
set.PutEntry(mAdoptedStyleSheets[i]);
}
// Try to truncate the sheets to a common prefix.
// If the prefix contains duplicates of sheets that we are removing,
// we are just going to re-build everything from scratch.
if (commonPrefix != mAdoptedStyleSheets.Length()) {
StyleSheetSet removedSet(mAdoptedStyleSheets.Length() - commonPrefix);
for (size_t i = mAdoptedStyleSheets.Length(); i != commonPrefix; --i) {
RefPtr<StyleSheet> sheetToRemove = mAdoptedStyleSheets.PopLastElement();
if (MOZ_UNLIKELY(set.Contains(sheetToRemove))) {
// Fixing duplicate sheets would require insertions/removals from the
// style set. We may as well just rebuild the whole thing from scratch.
set.Clear();
// Note that setting this to zero means we'll continue the loop until
// all the sheets are cleared.
commonPrefix = 0;
}
if (MOZ_LIKELY(removedSet.EnsureInserted(sheetToRemove))) {
RemoveSheetFromStylesIfApplicable(*sheetToRemove);
sheetToRemove->RemoveAdopter(*this);
}
}
mAdoptedStyleSheets.TruncateLength(commonPrefix);
}
// 3. Set the adopted style sheets to the new sheets
mAdoptedStyleSheets.SetCapacity(aAdoptedStyleSheets.Length());
AdoptedStyleSheetSet set(aAdoptedStyleSheets.Length());
for (const OwningNonNull<StyleSheet>& sheet : aAdoptedStyleSheets) {
if (MOZ_UNLIKELY(!set.EnsureInserted(sheet.get()))) {
// Only add sheets that are not already in the common prefix.
for (const auto& sheet : MakeSpan(aAdoptedStyleSheets).From(commonPrefix)) {
if (MOZ_UNLIKELY(!set.EnsureInserted(sheet))) {
// The idea is that this case is rare, so we pay the price of removing the
// old sheet from the styles and append it later rather than the other way
// around.

View File

@ -231,7 +231,7 @@ class DocumentOrShadowRoot {
// with them.
template <typename Callback>
void EnumerateUniqueAdoptedStyleSheetsBackToFront(Callback aCallback) {
AdoptedStyleSheetSet set(mAdoptedStyleSheets.Length());
StyleSheetSet set(mAdoptedStyleSheets.Length());
for (StyleSheet* sheet : Reversed(mAdoptedStyleSheets)) {
if (MOZ_UNLIKELY(!set.EnsureInserted(sheet))) {
continue;
@ -241,7 +241,7 @@ class DocumentOrShadowRoot {
}
protected:
using AdoptedStyleSheetSet = nsTHashtable<nsPtrHashKey<const StyleSheet>>;
using StyleSheetSet = nsTHashtable<nsPtrHashKey<const StyleSheet>>;
void RemoveSheetFromStylesIfApplicable(StyleSheet&);
void ClearAdoptedStyleSheets();

View File

@ -4,17 +4,81 @@
<link rel="help" href="https://wicg.github.io/construct-stylesheets/">
<script src = '/resources/testharness.js'></script>
<script src = '/resources/testharnessreport.js'></script>
<div></div>
<div id="target"></div>
<script>
test(function() {
let sheets = [];
for (let i = 0; i < 2; ++i) {
function attachShadowDiv(host) {
const shadowRoot = host.attachShadow({mode: 'open'});
const shadowDiv = document.createElement("div");
shadowRoot.appendChild(shadowDiv);
return shadowDiv;
}
function blueSheetsWithIncreasingZIndex(n) {
let sheets = [];
for (let i = 0; i < n; ++i) {
sheets.push(new CSSStyleSheet());
sheets[i].replaceSync("div { z-index: " + i + " }");
sheets[i].replaceSync("div { z-index: " + i + "; color: blue; }");
}
return sheets;
}
let sheets = [];
test(function() {
sheets = blueSheetsWithIncreasingZIndex(2);
document.adoptedStyleSheets = [sheets[1], sheets[0], sheets[1]];
assert_equals(getComputedStyle(document.querySelector("div")).zIndex, "1", "duplicate stylesheet should take right position in the cascade");
});
document.adoptedStyleSheets = [];
}, "Duplicate stylesheets have the right cascade position in the Document");
test(function() {
sheets = blueSheetsWithIncreasingZIndex(2);
const sheet = new CSSStyleSheet();
sheet.replaceSync("div { color: red; }");
document.adoptedStyleSheets = [sheets[1], sheets[0]];
assert_equals(getComputedStyle(document.querySelector("div")).zIndex, "0", "backmost stylesheet should take precedence");
assert_equals(getComputedStyle(document.querySelector("div")).color, "rgb(0, 0, 255)", "backmost stylesheet should take precedence");
document.adoptedStyleSheets = [sheets[1], sheets[0], sheets[1], sheet];
assert_equals(getComputedStyle(document.querySelector("div")).zIndex, "1", "duplicate stylesheet should take the right position in the cascade");
assert_equals(getComputedStyle(document.querySelector("div")).color, "rgb(255, 0, 0)", "backmost stylesheet should take precedence");
document.adoptedStyleSheets = [];
}, "Appending duplicate stylesheets yields the correct cascade position in the Document");
test(function() {
sheets = blueSheetsWithIncreasingZIndex(2);
const span = document.createElement("span");
target.appendChild(span);
attachShadowDiv(span);
span.shadowRoot.adoptedStyleSheets = [sheets[1], sheets[0], sheets[1]];
assert_equals(getComputedStyle(span.shadowRoot.querySelector("div")).zIndex, "1", "duplicate stylesheet should take right position in the cascade");
}, "Duplicate stylesheets have the right cascade position in the ShadowRoot");
test(function() {
sheets = blueSheetsWithIncreasingZIndex(2);
const sheet = new CSSStyleSheet();
sheet.replaceSync("div { color: red; }");
const span = document.createElement("span");
target.appendChild(span);
attachShadowDiv(span);
span.shadowRoot.adoptedStyleSheets = [sheets[1], sheets[0]];
assert_equals(getComputedStyle(span.shadowRoot.querySelector("div")).zIndex, "0", "backmost stylesheet should take precedence");
assert_equals(getComputedStyle(span.shadowRoot.querySelector("div")).color, "rgb(0, 0, 255)", "backmost stylesheet should take precedence");
span.shadowRoot.adoptedStyleSheets = [sheets[1], sheets[0], sheets[1], sheet];
assert_equals(getComputedStyle(span.shadowRoot.querySelector("div")).zIndex, "1", "duplicate stylesheet should take right position in the cascade");
assert_equals(getComputedStyle(span.shadowRoot.querySelector("div")).color, "rgb(255, 0, 0)", "backmost stylesheet should take precedence");
}, "Appending duplicate stylesheets yields the correct cascade position in the ShadowRoot");
</script>