mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 23:37:33 +09:00
Compare commits
4 commits
be7e2afbcf
...
14b52e7fc8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14b52e7fc8 | ||
|
|
80cfaf0891 | ||
|
|
dfe7760702 | ||
|
|
810e55b503 |
19 changed files with 517 additions and 32 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<img src="browser/branding/unofficial/content/about-wordmark.svg" alt="Dactyloidae web browser" height="60">
|
||||
<br>
|
||||
<a href="https://discord.gg/ecx">Official Discord server</a>
|
||||
<a href="https://discord.gg/ycmQAMej77">Official Discord server</a>
|
||||
<br><br>
|
||||
<img src="https://dactyloidae.xyz/demo.jpg" height="500">
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -94,13 +94,24 @@ ConsoleAPIStorageService.prototype = {
|
|||
getEvents: function CS_getEvents(aId)
|
||||
{
|
||||
if (aId != null) {
|
||||
return (_consoleStorage.get(aId) || []).slice(0);
|
||||
let storage = _consoleStorage.get(aId);
|
||||
if (!storage) {
|
||||
return [];
|
||||
}
|
||||
let { events, next } = storage;
|
||||
return next === 0 ? events.slice() :
|
||||
events.slice(next).concat(events.slice(0, next));
|
||||
}
|
||||
|
||||
let result = [];
|
||||
|
||||
for (let [id, events] of _consoleStorage) {
|
||||
result.push.apply(result, events);
|
||||
for (let { events, next } of _consoleStorage.values()) {
|
||||
for (let i = next; i < events.length; i++) {
|
||||
result.push(events[i]);
|
||||
}
|
||||
for (let i = 0; i < next; i++) {
|
||||
result.push(events[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result.sort(function(a, b) {
|
||||
|
|
@ -122,16 +133,21 @@ ConsoleAPIStorageService.prototype = {
|
|||
*/
|
||||
recordEvent: function CS_recordEvent(aId, aOuterId, aEvent)
|
||||
{
|
||||
if (!_consoleStorage.has(aId)) {
|
||||
_consoleStorage.set(aId, []);
|
||||
let storage = _consoleStorage.get(aId);
|
||||
if (!storage) {
|
||||
storage = { events: [], next: 0 };
|
||||
_consoleStorage.set(aId, storage);
|
||||
}
|
||||
|
||||
let storage = _consoleStorage.get(aId);
|
||||
storage.push(aEvent);
|
||||
|
||||
// truncate
|
||||
if (storage.length > STORAGE_MAX_EVENTS) {
|
||||
storage.shift();
|
||||
// Overwrite the oldest event once full, without moving the other entries.
|
||||
// Advance before notifying observers, which may read or reenter storage.
|
||||
if (storage.events.length < STORAGE_MAX_EVENTS) {
|
||||
storage.events.push(aEvent);
|
||||
} else {
|
||||
storage.events[storage.next] = aEvent;
|
||||
if (++storage.next === STORAGE_MAX_EVENTS) {
|
||||
storage.next = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(aEvent, "console-api-log-event", aOuterId);
|
||||
|
|
|
|||
|
|
@ -40,5 +40,6 @@ LOCAL_INCLUDES += [
|
|||
|
||||
MOCHITEST_MANIFESTS += [ 'tests/mochitest.ini' ]
|
||||
MOCHITEST_CHROME_MANIFESTS += [ 'tests/chrome.ini' ]
|
||||
XPCSHELL_TESTS_MANIFESTS += [ 'tests/xpcshell.ini' ]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
|
|||
53
dom/console/tests/test_console_storage_ring.js
Normal file
53
dom/console/tests/test_console_storage_ring.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
function run_test() {
|
||||
const storage = Components.classes["@mozilla.org/consoleAPI-storage;1"]
|
||||
.getService(Components.interfaces.nsIConsoleAPIStorage);
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
storage.clearEvents();
|
||||
try {
|
||||
for (let i = 0; i < 3501; i++) {
|
||||
storage.recordEvent("ring-test", "outer", { timeStamp: i });
|
||||
if (i === 998 || i === 999 || i === 1000 || i === 1999 || i === 3500) {
|
||||
let events = storage.getEvents("ring-test");
|
||||
equal(events.length, Math.min(i + 1, 1000));
|
||||
for (let j = 0; j < events.length; j++) {
|
||||
equal(events[j].timeStamp, i + 1 - events.length + j);
|
||||
}
|
||||
events.length = 0;
|
||||
equal(storage.getEvents("ring-test").length, Math.min(i + 1, 1000));
|
||||
}
|
||||
}
|
||||
storage.recordEvent("other", "outer", { timeStamp: 2500.5 });
|
||||
let all = storage.getEvents();
|
||||
equal(all.length, 1001);
|
||||
equal(all[0].timeStamp, 2500.5);
|
||||
equal(all[1].timeStamp, 2501);
|
||||
equal(all[1000].timeStamp, 3500);
|
||||
|
||||
let reentered = false;
|
||||
let observer = {
|
||||
observe(subject, topic, data) {
|
||||
if (data === "ring-test" && !reentered) {
|
||||
equal(storage.getEvents(data)[999].timeStamp, 3501);
|
||||
reentered = true;
|
||||
storage.recordEvent("ring-test", "outer", { timeStamp: 3502 });
|
||||
}
|
||||
}
|
||||
};
|
||||
Services.obs.addObserver(observer, "console-storage-cache-event", false);
|
||||
try {
|
||||
storage.recordEvent("ring-test", "outer", { timeStamp: 3501 });
|
||||
} finally {
|
||||
Services.obs.removeObserver(observer, "console-storage-cache-event");
|
||||
}
|
||||
equal(reentered, true);
|
||||
equal(storage.getEvents("ring-test")[999].timeStamp, 3502);
|
||||
storage.clearEvents("ring-test");
|
||||
equal(storage.getEvents("ring-test").length, 0);
|
||||
equal(storage.getEvents("other").length, 1);
|
||||
storage.recordEvent("ring-test", "outer", { timeStamp: 4000 });
|
||||
equal(storage.getEvents("ring-test")[0].timeStamp, 4000);
|
||||
} finally {
|
||||
storage.clearEvents();
|
||||
}
|
||||
equal(storage.getEvents().length, 0);
|
||||
}
|
||||
5
dom/console/tests/xpcshell.ini
Normal file
5
dom/console/tests/xpcshell.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
|
||||
[test_console_storage_ring.js]
|
||||
|
|
@ -81,9 +81,10 @@ function MapGroupBy(items, callbackfn) {
|
|||
var key = callContentFunction(callbackfn, undefined, value, k);
|
||||
|
||||
// Steps 6.c-d.
|
||||
var elements;
|
||||
if (callFunction(std_Map_has, groups, key)) {
|
||||
elements = callFunction(std_Map_get, groups, key);
|
||||
// Group values are always arrays, so undefined also tells us whether
|
||||
// this is a new key without a second hash-table lookup.
|
||||
var elements = callFunction(std_Map_get, groups, key);
|
||||
if (elements !== undefined) {
|
||||
callFunction(std_Array_push, elements, value);
|
||||
} else {
|
||||
elements = [value];
|
||||
|
|
|
|||
61
js/src/devtools/number-to-string-bench.js
Normal file
61
js/src/devtools/number-to-string-bench.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Run with: js number-to-string-bench.js
|
||||
// Also supports: xpcshell -f number-to-string-bench.js
|
||||
// Compare identical optimized builds, alternating baseline and patched runs.
|
||||
// Reports milliseconds (lower is better); this is not a browser-suite score.
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
var iterations = 500000;
|
||||
var samples = 7;
|
||||
var checksum = 0;
|
||||
|
||||
function convert(values, radix, count) {
|
||||
var total = 0;
|
||||
for (var i = 0; i < count; i++)
|
||||
total += values[i % values.length].toString(radix).length;
|
||||
return total;
|
||||
}
|
||||
|
||||
function unique(count) {
|
||||
var total = 0;
|
||||
for (var i = 0; i < count; i++)
|
||||
total += (123456.125 + i).toString().length;
|
||||
return total;
|
||||
}
|
||||
|
||||
function measure(name, run) {
|
||||
checksum += run(20000);
|
||||
var times = [];
|
||||
var expected = run(iterations);
|
||||
for (var sample = 0; sample < samples; sample++) {
|
||||
if (typeof gc === "function")
|
||||
gc();
|
||||
var start = Date.now();
|
||||
var result = run(iterations);
|
||||
times.push(Date.now() - start);
|
||||
if (result !== expected)
|
||||
throw new Error("inconsistent conversion: " + name);
|
||||
checksum += result;
|
||||
}
|
||||
times.sort(function(a, b) { return a - b; });
|
||||
print(name + ": median=" + times[3] + " ms; samples=" + times.join(","));
|
||||
}
|
||||
|
||||
for (var size of [1, 2, 4, 5, 16]) {
|
||||
var values = [];
|
||||
for (var i = 0; i < size; i++)
|
||||
values.push(123456.125 + i);
|
||||
measure("decimal working set " + size, function(count) {
|
||||
return convert(values, 10, count);
|
||||
});
|
||||
}
|
||||
measure("unique decimals", unique);
|
||||
var integers = [123456, 654321, 123457, 654322];
|
||||
measure("integer working set 4", function(count) {
|
||||
return convert(integers, 10, count);
|
||||
});
|
||||
measure("hexadecimal working set 4", function(count) {
|
||||
return convert(integers, 16, count);
|
||||
});
|
||||
print("checksum=" + checksum);
|
||||
})();
|
||||
38
js/src/devtools/speedometer21-json-bench.js
Normal file
38
js/src/devtools/speedometer21-json-bench.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Focused JSON workloads motivated by Speedometer 2.1's in-memory TodoMVC store.
|
||||
// Run with a JS shell, or xpcshell -f. Lower times are better. This does not
|
||||
// measure Speedometer's DOM, layout, event dispatch, or overall suite score.
|
||||
(function() {
|
||||
var iterations = 2000;
|
||||
var checksum = 0;
|
||||
function measure(name, records) {
|
||||
var text = JSON.stringify(records);
|
||||
var samples = [];
|
||||
for (var i = 0; i < 100; i++)
|
||||
checksum += JSON.parse(text).length;
|
||||
for (var sample = 0; sample < 7; sample++) {
|
||||
if (typeof gc === 'function')
|
||||
gc();
|
||||
var start = Date.now();
|
||||
for (var i = 0; i < iterations; i++)
|
||||
checksum += JSON.parse(text).length;
|
||||
samples.push(Date.now() - start);
|
||||
}
|
||||
samples.sort(function(a, b) { return a - b; });
|
||||
print(name + ': median=' + samples[3] + ' ms; samples=' + samples.join(','));
|
||||
}
|
||||
var todos = [], unicode = [], collisions = [], unique = [];
|
||||
for (var i = 0; i < 100; i++) {
|
||||
todos.push({ id: i, title: 'Something to do ' + i, completed: false });
|
||||
unicode.push({ '\u0101name': i, '\u03bbvalue': 'value', '\u4e2d': false });
|
||||
collisions.push({ item: i, identifier: i + 1, index: i + 2 });
|
||||
var record = {};
|
||||
record['uniqueName' + i] = i;
|
||||
unique.push(record);
|
||||
}
|
||||
measure('TodoMVC-shaped records', todos);
|
||||
measure('two-byte names', unicode);
|
||||
measure('cache collisions', collisions);
|
||||
measure('unique names', unique);
|
||||
measure('small parse', [todos[0]]);
|
||||
print('checksum=' + checksum);
|
||||
})();
|
||||
43
js/src/jit-test/tests/basic/json-property-name-cache.js
Normal file
43
js/src/jit-test/tests/basic/json-property-name-cache.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Repeated names, collisions, encodings, and escaped names must all agree.
|
||||
var keys = ['id', 'title', 'completed', 'items', 'item', '', 'identifier',
|
||||
'i', 'a', 'same', 'samesize', '\u00e9', '\u0101', '\ud800',
|
||||
'quote"key', 'slash\\key', 'line\nkey', '__proto__'];
|
||||
var records = [];
|
||||
for (var i = 0; i < 100; i++) {
|
||||
var record = Object.create(null);
|
||||
for (var k = 0; k < keys.length; k++)
|
||||
record[keys[k]] = i + k;
|
||||
records.push(record);
|
||||
}
|
||||
var text = JSON.stringify(records);
|
||||
for (var repeat = 0; repeat < 30; repeat++) {
|
||||
var parsed = JSON.parse(text);
|
||||
assertEq(parsed.length, records.length);
|
||||
for (var i = 0; i < parsed.length; i++) {
|
||||
for (var k = 0; k < keys.length; k++)
|
||||
assertEq(parsed[i][keys[k]], records[i][keys[k]]);
|
||||
}
|
||||
}
|
||||
assertEq(JSON.parse('[{"title":1},{"titleLonger":2}]')[1].titleLonger, 2);
|
||||
assertEq(JSON.parse('[{"titleLonger":1},{"title":2}]')[1].title, 2);
|
||||
assertEq(JSON.parse('[{"title":1},{"t\\u0069tle":2}]')[1].title, 2);
|
||||
assertEq(JSON.parse('{"id":1,"id":2}').id, 2);
|
||||
assertEq(JSON.parse('{"id":1}', function(key, value) {
|
||||
return key === 'id' ? JSON.parse('{"id":2}').id : value;
|
||||
}).id, 2);
|
||||
|
||||
for (var bad of ['[{"title":1},{"title', '[{"title":1},{"title"',
|
||||
'[{"title":1},{"titleX":}]', '[{"title":1},{"ti\ntle":2}]']) {
|
||||
var threw = false;
|
||||
try { JSON.parse(bad); } catch (e) { threw = e instanceof SyntaxError; }
|
||||
assertEq(threw, true);
|
||||
}
|
||||
|
||||
// Keep cached atoms alive across allocations made while parsing later values.
|
||||
var large = '[{"uncommonPropertyForGC":0},' +
|
||||
'{"other":[' + new Array(20000).fill('"allocation"').join(',') + ']},' +
|
||||
'{"uncommonPropertyForGC":1}]';
|
||||
for (var i = 0; i < 3; i++) {
|
||||
gc();
|
||||
assertEq(JSON.parse(large)[2].uncommonPropertyForGC, 1);
|
||||
}
|
||||
47
js/src/jit-test/tests/collections/Map-groupBy-lookup.js
Normal file
47
js/src/jit-test/tests/collections/Map-groupBy-lookup.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Repeated keys must reuse their group, including SameValueZero keys.
|
||||
var objectKey = {};
|
||||
var symbolKey = Symbol();
|
||||
var keys = [undefined, null, false, 0, -0, NaN, "key", objectKey, symbolKey];
|
||||
for (var iteration = 0; iteration < 100; iteration++) {
|
||||
var input = [];
|
||||
for (var repeat = 0; repeat < 4; repeat++) {
|
||||
for (var key of keys)
|
||||
input.push(key);
|
||||
}
|
||||
var calls = 0;
|
||||
var groups = Map.groupBy(input, function(value, index) {
|
||||
assertEq(index, calls++);
|
||||
return value;
|
||||
});
|
||||
assertEq(calls, input.length);
|
||||
assertEq(groups.size, 8);
|
||||
for (var key of keys)
|
||||
assertEq(groups.get(key).length, key === 0 ? 8 : 4);
|
||||
assertEq(groups.get(objectKey)[0], objectKey);
|
||||
assertEq(groups.get(symbolKey)[0], symbolKey);
|
||||
assertEq(groups.get(undefined)[0], undefined);
|
||||
var order = Array.from(groups.keys());
|
||||
assertEq(order[0], undefined);
|
||||
assertEq(order[3], 0);
|
||||
assertEq(order[4], NaN);
|
||||
assertEq(order[7], symbolKey);
|
||||
}
|
||||
|
||||
// A throwing callback must still close the input iterator.
|
||||
var closed = false;
|
||||
function* values() {
|
||||
try {
|
||||
yield 1;
|
||||
yield 2;
|
||||
} finally {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
var sentinel = {};
|
||||
try {
|
||||
Map.groupBy(values(), function() { throw sentinel; });
|
||||
throw new Error("callback did not throw");
|
||||
} catch (error) {
|
||||
assertEq(error, sentinel);
|
||||
}
|
||||
assertEq(closed, true);
|
||||
|
|
@ -66,6 +66,56 @@ BEGIN_TEST(testIndexToString)
|
|||
}
|
||||
END_TEST(testIndexToString)
|
||||
|
||||
BEGIN_TEST(testDtoaCacheInterleaved)
|
||||
{
|
||||
JS::RootedString first(cx, js::NumberToString<js::CanGC>(cx, 1234.5));
|
||||
CHECK(first);
|
||||
JS::RootedString second(cx, js::NumberToString<js::CanGC>(cx, 6789.5));
|
||||
CHECK(second);
|
||||
JS::RootedString third(cx, js::IndexToString(cx, 123456));
|
||||
CHECK(third);
|
||||
JS::RootedString fourth(cx, js::IndexToString(cx, 654321));
|
||||
CHECK(fourth);
|
||||
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
CHECK(js::NumberToString<js::CanGC>(cx, 1234.5) == first);
|
||||
CHECK(js::NumberToString<js::CanGC>(cx, 6789.5) == second);
|
||||
CHECK(js::IndexToString(cx, 123456) == third);
|
||||
CHECK(js::IndexToString(cx, 654321) == fourth);
|
||||
}
|
||||
|
||||
// Every raw string pointer must be invalidated, not just the latest one.
|
||||
JS_GC(cx);
|
||||
CHECK(!cx->compartment()->dtoaCache.lookup(10, 1234.5));
|
||||
CHECK(!cx->compartment()->dtoaCache.lookup(10, 6789.5));
|
||||
CHECK(!cx->compartment()->dtoaCache.lookup(10, 123456));
|
||||
CHECK(!cx->compartment()->dtoaCache.lookup(10, 654321));
|
||||
|
||||
// The radix is part of the key. Signed zero can share its string.
|
||||
js::DtoaCache cache;
|
||||
cache.cache(10, 0.0, &first->asFlat());
|
||||
cache.cache(16, 0.0, &second->asFlat());
|
||||
CHECK(cache.lookup(10, -0.0) == first);
|
||||
CHECK(cache.lookup(16, -0.0) == second);
|
||||
CHECK(!cache.lookup(2, 0.0));
|
||||
cache.purge();
|
||||
CHECK(!cache.lookup(10, 0.0));
|
||||
CHECK(!cache.lookup(16, 0.0));
|
||||
|
||||
for (size_t i = 0; i < 12; i++)
|
||||
cache.cache(10, double(i), &first->asFlat());
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
CHECK(!cache.lookup(10, double(i)));
|
||||
for (size_t i = 8; i < 12; i++)
|
||||
CHECK(cache.lookup(10, double(i)) == first);
|
||||
|
||||
// A failed string allocation must never turn into a cache hit.
|
||||
cache.cache(10, 12.0, nullptr);
|
||||
CHECK(!cache.lookup(10, 12.0));
|
||||
return true;
|
||||
}
|
||||
END_TEST(testDtoaCacheInterleaved)
|
||||
|
||||
BEGIN_TEST(testStringIsIndex)
|
||||
{
|
||||
for (size_t i = 0, sz = ArrayLength(tests); i < sz; i++) {
|
||||
|
|
|
|||
|
|
@ -214,7 +214,9 @@ JSCompartment::ensureJitCompartmentExists(JSContext* cx)
|
|||
void
|
||||
js::DtoaCache::checkCacheAfterMovingGC()
|
||||
{
|
||||
MOZ_ASSERT(!s || !IsForwarded(s));
|
||||
MOZ_ASSERT(!recent.s || !IsForwarded(recent.s));
|
||||
for (const auto& entry : previous)
|
||||
MOZ_ASSERT(!entry.s || !IsForwarded(entry.s));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
|
|
|||
|
|
@ -38,29 +38,47 @@ class ScriptSourceObject;
|
|||
struct NativeIterator;
|
||||
|
||||
/*
|
||||
* A single-entry cache for some base-10 double-to-string conversions. This
|
||||
* helps date-format-xparb.js. It also avoids skewing the results for
|
||||
* v8-splay.js when measured by the SunSpider harness, where the splay tree
|
||||
* initialization (which includes many repeated double-to-string conversions)
|
||||
* is erroneously included in the measurement; see bug 562553.
|
||||
* A small cache for number-to-string conversions, keyed by number and radix.
|
||||
* Keep several results so interleaved conversions do not evict each other.
|
||||
* These strings are not traced: every entry must be cleared before GC.
|
||||
*/
|
||||
class DtoaCache {
|
||||
double d;
|
||||
int base;
|
||||
JSFlatString* s; // if s==nullptr, d and base are not valid
|
||||
struct Entry {
|
||||
double d;
|
||||
int base;
|
||||
JSFlatString* s; // if s == nullptr, d and base are not valid
|
||||
};
|
||||
static const size_t NumPrevious = 3;
|
||||
Entry recent;
|
||||
Entry previous[NumPrevious];
|
||||
size_t next;
|
||||
|
||||
public:
|
||||
DtoaCache() : s(nullptr) {}
|
||||
void purge() { s = nullptr; }
|
||||
DtoaCache() { purge(); }
|
||||
void purge() {
|
||||
recent.s = nullptr;
|
||||
for (auto& entry : previous)
|
||||
entry.s = nullptr;
|
||||
next = 0;
|
||||
}
|
||||
|
||||
JSFlatString* lookup(int base, double d) {
|
||||
return this->s && base == this->base && d == this->d ? this->s : nullptr;
|
||||
// Preserve the cheap path for consecutive conversions of one value.
|
||||
if (recent.s && base == recent.base && d == recent.d)
|
||||
return recent.s;
|
||||
for (const auto& entry : previous) {
|
||||
if (entry.s && base == entry.base && d == entry.d)
|
||||
return entry.s;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void cache(int base, double d, JSFlatString* s) {
|
||||
this->base = base;
|
||||
this->d = d;
|
||||
this->s = s;
|
||||
if (recent.s) {
|
||||
previous[next] = recent;
|
||||
next = (next + 1) % NumPrevious;
|
||||
}
|
||||
recent = { d, base, s };
|
||||
}
|
||||
|
||||
#ifdef JSGC_HASH_TABLE_CHECKS
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ JSONParserBase::~JSONParserBase()
|
|||
void
|
||||
JSONParserBase::trace(JSTracer* trc)
|
||||
{
|
||||
for (auto& atom : propertyNameCache) {
|
||||
if (atom)
|
||||
TraceRoot(trc, &atom, "JSONParser cached property name");
|
||||
}
|
||||
for (size_t i = 0; i < stack.length(); i++) {
|
||||
if (stack[i].state == FinishArrayElement) {
|
||||
ElementVector& elements = stack[i].elements();
|
||||
|
|
@ -123,6 +127,24 @@ JSONParser<CharT>::readString()
|
|||
return token(Error);
|
||||
}
|
||||
|
||||
// Arrays of records repeatedly use the same property names. Verify the
|
||||
// entire name and closing quote before skipping scanning and atomization.
|
||||
// Only the unescaped path below populates this cache.
|
||||
if (ST == JSONParser::PropertyName && *current != '"') {
|
||||
JSAtom* atom = propertyNameCache[size_t(*current) % PropertyNameCacheSize];
|
||||
if (atom && size_t(end - current) > atom->length() &&
|
||||
current[atom->length()] == '"') {
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
bool matches = atom->hasLatin1Chars()
|
||||
? EqualChars(atom->latin1Chars(nogc), current.get(), atom->length())
|
||||
: EqualChars(atom->twoByteChars(nogc), current.get(), atom->length());
|
||||
if (matches) {
|
||||
current += atom->length() + 1;
|
||||
return stringToken(atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Optimization: if the source contains no escaped characters, create the
|
||||
* string directly from the source text.
|
||||
|
|
@ -137,6 +159,9 @@ JSONParser<CharT>::readString()
|
|||
: NewStringCopyN<CanGC>(cx, start.get(), length);
|
||||
if (!str)
|
||||
return token(OOM);
|
||||
if (ST == JSONParser::PropertyName && length) {
|
||||
propertyNameCache[size_t(*start) % PropertyNameCacheSize] = &str->asAtom();
|
||||
}
|
||||
return stringToken(str);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ class MOZ_STACK_CLASS JSONParserBase
|
|||
|
||||
const ErrorHandling errorHandling;
|
||||
|
||||
// Reuse unescaped property names within this parse. The cache is bounded
|
||||
// and traced with the parser, including during compacting GC.
|
||||
static const size_t PropertyNameCacheSize = 8;
|
||||
JSAtom* propertyNameCache[PropertyNameCacheSize];
|
||||
|
||||
enum Token { String, Number, True, False, Null,
|
||||
ArrayOpen, ArrayClose,
|
||||
ObjectOpen, ObjectClose,
|
||||
|
|
@ -109,6 +114,7 @@ class MOZ_STACK_CLASS JSONParserBase
|
|||
JSONParserBase(JSContext* cx, ErrorHandling errorHandling)
|
||||
: cx(cx),
|
||||
errorHandling(errorHandling),
|
||||
propertyNameCache{},
|
||||
stack(cx),
|
||||
freeElements(cx),
|
||||
freeProperties(cx)
|
||||
|
|
@ -129,8 +135,10 @@ class MOZ_STACK_CLASS JSONParserBase
|
|||
#ifdef DEBUG
|
||||
, lastToken(mozilla::Move(other.lastToken))
|
||||
#endif
|
||||
{}
|
||||
|
||||
{
|
||||
for (size_t i = 0; i < PropertyNameCacheSize; i++)
|
||||
propertyNameCache[i] = other.propertyNameCache[i];
|
||||
}
|
||||
|
||||
Value numberValue() const {
|
||||
MOZ_ASSERT(lastToken == Number);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue