mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-19 23:07:33 +09:00
Implement class selector fast path and add related tests for performance optimization
This commit is contained in:
parent
dfe7760702
commit
80cfaf0891
8 changed files with 233 additions and 2 deletions
|
|
@ -2927,6 +2927,35 @@ FindMatchingElementsWithId(const nsAString& aId, nsINode* aRoot,
|
|||
// Actually find elements matching aSelectorList (which must not be
|
||||
// null) and which are descendants of aRoot and put them in aList. If
|
||||
// onlyFirstMatch, then stop once the first one is found.
|
||||
template<bool onlyFirstMatch, class Collector, class T>
|
||||
static void
|
||||
FindMatchingElementsWithClass(nsINode* aRoot, nsIAtom* aClass,
|
||||
nsCaseTreatment aCaseTreatment, T& aList)
|
||||
{
|
||||
Collector results;
|
||||
for (nsIContent* cur = aRoot->GetFirstChild(); cur;
|
||||
cur = cur->GetNextNode(aRoot)) {
|
||||
if (!cur->IsElement()) {
|
||||
continue;
|
||||
}
|
||||
const nsAttrValue* classes = cur->AsElement()->GetClasses();
|
||||
if (classes && classes->Contains(aClass, aCaseTreatment)) {
|
||||
if (onlyFirstMatch) {
|
||||
aList.AppendElement(cur->AsElement());
|
||||
return;
|
||||
}
|
||||
results.AppendElement(cur->AsElement());
|
||||
}
|
||||
}
|
||||
const uint32_t len = results.Length();
|
||||
if (len) {
|
||||
aList.SetCapacity(len);
|
||||
for (uint32_t i = 0; i < len; ++i) {
|
||||
aList.AppendElement(results.ElementAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<bool onlyFirstMatch, class Collector, class T>
|
||||
MOZ_ALWAYS_INLINE static void
|
||||
FindMatchingElements(nsINode* aRoot, nsCSSSelectorList* aSelectorList, T &aList,
|
||||
|
|
@ -2934,6 +2963,23 @@ FindMatchingElements(nsINode* aRoot, nsCSSSelectorList* aSelectorList, T &aList,
|
|||
{
|
||||
nsIDocument* doc = aRoot->OwnerDoc();
|
||||
|
||||
// Parsed selectors are already cached by the document. A lone class needs
|
||||
// only an atom lookup per element, not the general CSS matching context.
|
||||
nsCSSSelector* selector = aSelectorList->mSelectors;
|
||||
if (!aSelectorList->mNext && !selector->mNext &&
|
||||
selector->mClassList && !selector->mClassList->mNext &&
|
||||
!selector->mLowercaseTag && !selector->mIDList &&
|
||||
!selector->mAttrList && !selector->mPseudoClassList &&
|
||||
!selector->mNegations && selector->mNameSpace == kNameSpaceID_Unknown &&
|
||||
selector->IsRestrictedSelector() && !selector->IsHybridPseudoElement()) {
|
||||
nsCaseTreatment caseTreatment =
|
||||
doc->GetCompatibilityMode() == eCompatibility_NavQuirks
|
||||
? eIgnoreCase : eCaseMatters;
|
||||
FindMatchingElementsWithClass<onlyFirstMatch, Collector>(
|
||||
aRoot, selector->mClassList->mAtom, caseTreatment, aList);
|
||||
return;
|
||||
}
|
||||
|
||||
TreeMatchContext matchingContext(false, nsRuleWalker::eRelevantLinkUnvisited,
|
||||
doc, TreeMatchContext::eNeverMatchVisited);
|
||||
doc->FlushPendingLinkUpdates();
|
||||
|
|
|
|||
59
dom/base/test/file_class_selector_checks.js
Normal file
59
dom/base/test/file_class_selector_checks.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
function checkClassSelectors(parse, equal) {
|
||||
function check(root, selector, expected) {
|
||||
var all = root.querySelectorAll(selector);
|
||||
equal(all.length, expected.length, selector + " count");
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
equal(all[i].id, expected[i], selector + " order " + i);
|
||||
}
|
||||
equal(root.querySelector(selector), all.length ? all[0] : null,
|
||||
selector + " first match");
|
||||
}
|
||||
|
||||
var doc = parse('<!doctype html><html><body>' +
|
||||
'<section id="root" class="toggle"><div id="a" class="toggle completed">' +
|
||||
'<span id="b" class="other toggle"></span></div>' +
|
||||
'<div id="c" class="TOGGLE"></div><button id="d" class="toggle\t extra"></button>' +
|
||||
'<svg xmlns="http://www.w3.org/2000/svg"><g id="svg" class="toggle"/></svg>' +
|
||||
'</section></body></html>', 'text/html');
|
||||
var root = doc.getElementById('root');
|
||||
for (var repeat = 0; repeat < 10; repeat++) {
|
||||
check(root, '.toggle', ['a', 'b', 'd', 'svg']);
|
||||
check(root, '*|*.toggle', ['a', 'b', 'd', 'svg']);
|
||||
check(root, '.t\\6f ggle', ['a', 'b', 'd', 'svg']);
|
||||
check(root, '.TOGGLE', ['c']);
|
||||
check(root, '.missing', []);
|
||||
check(root, '.toggle.completed', ['a']);
|
||||
check(root, 'button.toggle', ['d']);
|
||||
check(root, '.toggle:not(.completed)', ['b', 'd', 'svg']);
|
||||
check(root, '.toggle, .other', ['a', 'b', 'd', 'svg']);
|
||||
}
|
||||
check(doc, '.toggle', ['root', 'a', 'b', 'd', 'svg']);
|
||||
|
||||
var snapshot = root.querySelectorAll('.toggle');
|
||||
doc.getElementById('a').className = '';
|
||||
root.removeChild(doc.getElementById('d'));
|
||||
doc.getElementById('c').className = 'toggle';
|
||||
check(root, '.toggle', ['b', 'c', 'svg']);
|
||||
equal(snapshot.length, 4, 'querySelectorAll remains a static snapshot');
|
||||
equal(snapshot[0].id, 'a', 'snapshot retains a changed element');
|
||||
equal(snapshot[2].id, 'd', 'snapshot retains a removed element');
|
||||
|
||||
var fragment = doc.createDocumentFragment();
|
||||
fragment.appendChild(root);
|
||||
check(fragment, '.toggle', ['root', 'b', 'c', 'svg']);
|
||||
check(root, '.toggle', ['b', 'c', 'svg']);
|
||||
|
||||
var quirks = parse('<html><body><div id="lower" class="toggle"></div>' +
|
||||
'<div id="upper" class="TOGGLE"></div></body></html>', 'text/html');
|
||||
equal(quirks.compatMode, 'BackCompat', 'quirks document');
|
||||
check(quirks, '.toggle', ['lower', 'upper']);
|
||||
check(quirks, '.TOGGLE', ['lower', 'upper']);
|
||||
|
||||
var xml = parse('<root><item id="plain" class="toggle"/>' +
|
||||
'<item id="upper" class="TOGGLE"/>' +
|
||||
'<item xmlns="urn:test" id="namespaced" class="toggle"/></root>',
|
||||
'application/xml');
|
||||
check(xml, '.toggle', ['plain', 'namespaced']);
|
||||
check(xml, '|*.toggle', ['plain']);
|
||||
check(xml, '*|*.toggle', ['plain', 'namespaced']);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
file_class_selector_checks.js
|
||||
audio.ogg
|
||||
audioEndedDuringPlaying.webm
|
||||
iframe_bug962251.html
|
||||
|
|
@ -615,6 +616,7 @@ skip-if = os == "mac" # Different tab focus behavior on mac
|
|||
[test_caretPositionFromPoint.html]
|
||||
[test_change_policy.html]
|
||||
[test_classList.html]
|
||||
[test_class_selector_fast_path.html]
|
||||
[test_clearTimeoutIntervalNoArg.html]
|
||||
[test_constructor-assignment.html]
|
||||
[test_constructor.html]
|
||||
|
|
|
|||
10
dom/base/test/test_class_selector_fast_path.html
Normal file
10
dom/base/test/test_class_selector_fast_path.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<title>Class selector query fast path</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="file_class_selector_checks.js"></script>
|
||||
<script>
|
||||
checkClassSelectors(function(source, type) {
|
||||
return new DOMParser().parseFromString(source, type);
|
||||
}, is);
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue