diff --git a/README.md b/README.md
index 2c69459f92..bce6376429 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-Official Discord server
+Official Discord server
diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp
index a7afd310cb..7b7dbdf67d 100644
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -138,6 +138,207 @@ StructuredCloneCallbacksError(JSContext* aCx,
NS_WARNING("Failed to clone data.");
}
+struct SameThreadStreamTransferData
+{
+ SameThreadStreamTransferData(JSContext* aCx, JS::HandleObject aRecord)
+ : mRecord(aCx, aRecord)
+ {}
+
+ ~SameThreadStreamTransferData()
+ {
+ mRecord.reset();
+ }
+
+ JS::PersistentRootedObject mRecord;
+};
+
+bool
+CallStreamTransferHelper(JSContext* aCx,
+ const char* aHelperName,
+ JS::HandleValue aArgument,
+ JS::MutableHandleValue aResult)
+{
+ JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx));
+ if (!global) {
+ return false;
+ }
+
+ // Resolve the stream extras lazily before looking up the internal helper.
+ JS::Rooted ignored(aCx);
+ if (!JS_GetProperty(aCx, global, "WritableStream", &ignored)) {
+ return false;
+ }
+
+ JS::Rooted helper(aCx);
+ if (!JS_GetProperty(aCx, global, aHelperName, &helper)) {
+ return false;
+ }
+
+ if (!helper.isObject() || !JS::IsCallable(&helper.toObject())) {
+ aResult.setUndefined();
+ return true;
+ }
+
+ JS::Rooted argument(aCx, aArgument);
+ if (!JS_WrapValue(aCx, &argument)) {
+ return false;
+ }
+
+ return JS::Call(aCx, JS::UndefinedHandleValue, helper,
+ JS::HandleValueArray(argument), aResult);
+}
+
+bool
+TryWriteSameThreadStreamTransfer(JSContext* aCx,
+ JS::Handle aObj,
+ const char* aHelperName,
+ uint32_t aTransferTag,
+ uint32_t* aTag,
+ JS::TransferableOwnership* aOwnership,
+ void** aContent,
+ uint64_t* aExtraData,
+ bool* aHandled)
+{
+ *aHandled = false;
+
+ JS::Rooted argument(aCx, JS::ObjectValue(*aObj));
+ JS::Rooted transferRecord(aCx);
+ if (!CallStreamTransferHelper(aCx, aHelperName, argument, &transferRecord)) {
+ return false;
+ }
+
+ if (transferRecord.isUndefined()) {
+ return true;
+ }
+
+ if (!transferRecord.isObject()) {
+ return false;
+ }
+
+ JS::Rooted record(aCx, &transferRecord.toObject());
+ SameThreadStreamTransferData* data =
+ new SameThreadStreamTransferData(aCx, record);
+
+ *aTag = aTransferTag;
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ *aContent = data;
+ *aExtraData = 0;
+ *aHandled = true;
+ return true;
+}
+
+bool
+TryWriteReadableStreamPortTransfer(JSContext* aCx,
+ JS::Handle aObj,
+ nsTArray& aPortIdentifiers,
+ uint32_t* aTag,
+ JS::TransferableOwnership* aOwnership,
+ void** aContent,
+ uint64_t* aExtraData,
+ bool* aHandled)
+{
+ *aHandled = false;
+
+ JS::Rooted argument(aCx, JS::ObjectValue(*aObj));
+ JS::Rooted transferPortValue(aCx);
+ if (!CallStreamTransferHelper(aCx, "__uxpTransferReadableStreamPort",
+ argument, &transferPortValue)) {
+ return false;
+ }
+
+ if (transferPortValue.isUndefined()) {
+ return true;
+ }
+
+ if (!transferPortValue.isObject()) {
+ return false;
+ }
+
+ JS::Rooted transferPortObj(aCx, &transferPortValue.toObject());
+ MessagePort* port = nullptr;
+ nsresult rv = UNWRAP_OBJECT(MessagePort, &transferPortObj, port);
+ if (NS_FAILED(rv) || !port) {
+ return false;
+ }
+
+ *aExtraData = aPortIdentifiers.Length();
+ MessagePortIdentifier* identifier = aPortIdentifiers.AppendElement();
+ port->CloneAndDisentangle(*identifier);
+
+ *aTag = SCTAG_DOM_TRANSFERRED_READABLESTREAM;
+ *aOwnership = JS::SCTAG_TMO_CUSTOM;
+ *aContent = nullptr;
+ *aHandled = true;
+ return true;
+}
+
+bool
+ReadSameThreadStreamTransfer(JSContext* aCx,
+ void* aContent,
+ const char* aHelperName,
+ JS::MutableHandleObject aReturnObject)
+{
+ MOZ_ASSERT(aContent);
+ SameThreadStreamTransferData* data =
+ static_cast(aContent);
+
+ JS::Rooted record(aCx, JS::ObjectValue(*data->mRecord));
+ JS::Rooted result(aCx);
+ if (!CallStreamTransferHelper(aCx, aHelperName, record, &result)) {
+ return false;
+ }
+
+ if (!result.isObject()) {
+ return false;
+ }
+
+ aReturnObject.set(&result.toObject());
+ delete data;
+ return true;
+}
+
+bool
+ReadReadableStreamPortTransfer(JSContext* aCx,
+ nsISupports* aParent,
+ nsTArray& aPortIdentifiers,
+ uint64_t aExtraData,
+ JS::MutableHandleObject aReturnObject)
+{
+ if (aExtraData >= aPortIdentifiers.Length()) {
+ return false;
+ }
+
+ nsCOMPtr global = do_QueryInterface(aParent);
+
+ ErrorResult rv;
+ RefPtr port =
+ MessagePort::Create(global, aPortIdentifiers[aExtraData], rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return false;
+ }
+
+ JS::Rooted portValue(aCx);
+ if (!GetOrCreateDOMReflector(aCx, port, &portValue)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+
+ JS::Rooted result(aCx);
+ if (!CallStreamTransferHelper(aCx,
+ "__uxpReceiveReadableStreamTransferFromPort",
+ portValue, &result)) {
+ return false;
+ }
+
+ if (!result.isObject()) {
+ return false;
+ }
+
+ aReturnObject.set(&result.toObject());
+ return true;
+}
+
} // anonymous namespace
const JSStructuredCloneCallbacks StructuredCloneHolder::sCallbacks = {
@@ -1375,6 +1576,35 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx,
return true;
}
+ if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread) {
+ if (aTag == SCTAG_DOM_TRANSFERRED_WRITABLESTREAM) {
+ return ReadSameThreadStreamTransfer(aCx, aContent,
+ "__uxpReceiveWritableStreamTransfer",
+ aReturnObject);
+ }
+
+ if (aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM) {
+ return ReadSameThreadStreamTransfer(aCx, aContent,
+ "__uxpReceiveReadableStreamTransfer",
+ aReturnObject);
+ }
+
+ if (aTag == SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM) {
+ return ReadSameThreadStreamTransfer(aCx, aContent,
+ "__uxpReceiveTransformStreamTransfer",
+ aReturnObject);
+ }
+ }
+
+ if ((mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread ||
+ mStructuredCloneScope == StructuredCloneScope::DifferentProcess) &&
+ aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM) {
+ MOZ_ASSERT(!aContent);
+ return ReadReadableStreamPortTransfer(aCx, mParent, mPortIdentifiers,
+ aExtraData,
+ aReturnObject);
+ }
+
return false;
}
@@ -1443,6 +1673,55 @@ StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx,
}
}
+ if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread) {
+ bool handled = false;
+ if (!TryWriteSameThreadStreamTransfer(aCx, obj,
+ "__uxpTransferWritableStream",
+ SCTAG_DOM_TRANSFERRED_WRITABLESTREAM,
+ aTag, aOwnership, aContent,
+ aExtraData, &handled)) {
+ return false;
+ }
+ if (handled) {
+ return true;
+ }
+
+ if (!TryWriteSameThreadStreamTransfer(aCx, obj,
+ "__uxpTransferReadableStream",
+ SCTAG_DOM_TRANSFERRED_READABLESTREAM,
+ aTag, aOwnership, aContent,
+ aExtraData, &handled)) {
+ return false;
+ }
+ if (handled) {
+ return true;
+ }
+
+ if (!TryWriteSameThreadStreamTransfer(aCx, obj,
+ "__uxpTransferTransformStream",
+ SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM,
+ aTag, aOwnership, aContent,
+ aExtraData, &handled)) {
+ return false;
+ }
+ if (handled) {
+ return true;
+ }
+ }
+
+ if (mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread ||
+ mStructuredCloneScope == StructuredCloneScope::DifferentProcess) {
+ bool handled = false;
+ if (!TryWriteReadableStreamPortTransfer(aCx, obj, mPortIdentifiers,
+ aTag, aOwnership, aContent,
+ aExtraData, &handled)) {
+ return false;
+ }
+ if (handled) {
+ return true;
+ }
+ }
+
return false;
}
@@ -1480,6 +1759,26 @@ StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag,
delete data;
return;
}
+
+ if ((aTag == SCTAG_DOM_TRANSFERRED_WRITABLESTREAM ||
+ aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM ||
+ aTag == SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM) &&
+ mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread) {
+ MOZ_ASSERT(aContent);
+ SameThreadStreamTransferData* data =
+ static_cast(aContent);
+ delete data;
+ return;
+ }
+
+ if (aTag == SCTAG_DOM_TRANSFERRED_READABLESTREAM &&
+ (mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread ||
+ mStructuredCloneScope == StructuredCloneScope::DifferentProcess)) {
+ MOZ_ASSERT(!aContent);
+ MOZ_ASSERT(aExtraData < mPortIdentifiers.Length());
+ MessagePort::ForceClose(mPortIdentifiers[aExtraData]);
+ return;
+ }
}
bool
diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h
index 09b91f7afb..15518104f9 100644
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -68,6 +68,11 @@ enum StructuredCloneTags {
// This tag is used by both main thread and workers.
SCTAG_DOM_URLSEARCHPARAMS,
+ // Same-thread transferable stream records. These are not supported by IDB.
+ SCTAG_DOM_TRANSFERRED_READABLESTREAM,
+ SCTAG_DOM_TRANSFERRED_WRITABLESTREAM,
+ SCTAG_DOM_TRANSFERRED_TRANSFORMSTREAM,
+
// When adding a new tag for IDB, please don't add it to the end of the list!
// Tags that are supported by IDB must not ever change. See the static assert
// in IDBObjectStore.cpp, method CommonStructuredCloneReadCallback.
diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp
index ffb5709e46..a80d9164b9 100644
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -1929,6 +1929,9 @@ public:
{
ProcessGlobal* global = ProcessGlobal::Get();
MOZ_ASSERT(!aRunInGlobalScope);
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
global->LoadScript(aURL);
return true;
}
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
index 5218938ca3..740c18417f 100644
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -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
+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
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(
+ aRoot, selector->mClassList->mAtom, caseTreatment, aList);
+ return;
+ }
+
TreeMatchContext matchingContext(false, nsRuleWalker::eRelevantLinkUnvisited,
doc, TreeMatchContext::eNeverMatchVisited);
doc->FlushPendingLinkUpdates();
diff --git a/dom/base/test/file_class_selector_checks.js b/dom/base/test/file_class_selector_checks.js
new file mode 100644
index 0000000000..0be76bc6ef
--- /dev/null
+++ b/dom/base/test/file_class_selector_checks.js
@@ -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('' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '', '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('' +
+ '', 'text/html');
+ equal(quirks.compatMode, 'BackCompat', 'quirks document');
+ check(quirks, '.toggle', ['lower', 'upper']);
+ check(quirks, '.TOGGLE', ['lower', 'upper']);
+
+ var xml = parse(' ' +
+ ' ' +
+ ' ',
+ 'application/xml');
+ check(xml, '.toggle', ['plain', 'namespaced']);
+ check(xml, '|*.toggle', ['plain']);
+ check(xml, '*|*.toggle', ['plain', 'namespaced']);
+}
diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini
index 6f5e0ea835..f903dc5a3d 100644
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -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]
diff --git a/dom/base/test/test_class_selector_fast_path.html b/dom/base/test/test_class_selector_fast_path.html
new file mode 100644
index 0000000000..3eb87d2cf2
--- /dev/null
+++ b/dom/base/test/test_class_selector_fast_path.html
@@ -0,0 +1,10 @@
+
+
+Class selector query fast path
+
+
+
diff --git a/dom/console/ConsoleAPIStorage.js b/dom/console/ConsoleAPIStorage.js
index 31be449e91..b17c6855ec 100644
--- a/dom/console/ConsoleAPIStorage.js
+++ b/dom/console/ConsoleAPIStorage.js
@@ -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);
diff --git a/dom/console/moz.build b/dom/console/moz.build
index d18b089626..ce2ae8d054 100644
--- a/dom/console/moz.build
+++ b/dom/console/moz.build
@@ -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'
diff --git a/dom/console/tests/test_console_storage_ring.js b/dom/console/tests/test_console_storage_ring.js
new file mode 100644
index 0000000000..a4e3dbeee8
--- /dev/null
+++ b/dom/console/tests/test_console_storage_ring.js
@@ -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);
+}
diff --git a/dom/console/tests/xpcshell.ini b/dom/console/tests/xpcshell.ini
new file mode 100644
index 0000000000..7d3af0c96c
--- /dev/null
+++ b/dom/console/tests/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_console_storage_ring.js]
diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp
index c0b970e58a..acf5027949 100644
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2334,8 +2334,7 @@ ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest)
}
if (aRequest->IsModuleRequest()) {
- // For modules, currentScript is set to null.
- AutoCurrentScriptUpdater scriptUpdater(this, nullptr);
+ AutoCurrentScriptUpdater scriptUpdater(this, aRequest->Element());
ModuleLoadRequest* request = aRequest->AsModuleRequest();
MOZ_ASSERT(request->mModuleScript);
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
index f63b1de981..65f778b5f9 100644
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -301,6 +301,7 @@ LoadContextOptions(const char* aPrefName, void* /* aClosure */)
.setAsyncStack(GetWorkerPref(NS_LITERAL_CSTRING("asyncstack")))
.setWerror(GetWorkerPref(NS_LITERAL_CSTRING("werror")))
.setStreams(GetWorkerPref(NS_LITERAL_CSTRING("streams")))
+ .setWeakRefs(GetWorkerPref(NS_LITERAL_CSTRING("weakrefs")))
.setExtraWarnings(GetWorkerPref(NS_LITERAL_CSTRING("strict")))
.setArrayProtoValues(GetWorkerPref(
NS_LITERAL_CSTRING("array_prototype_values")));
diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h
index 87806a6fbc..d870ac8e46 100644
--- a/dom/workers/WorkerPrefs.h
+++ b/dom/workers/WorkerPrefs.h
@@ -35,6 +35,7 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, O
WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
WORKER_SIMPLE_PREF("dom.streams.enabled", StreamsEnabled, STREAMS_ENABLED)
+WORKER_SIMPLE_PREF("javascript.options.weakrefs", WeakRefsEnabled, WEAKREFS_ENABLED)
WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
diff --git a/js/src/builtin/AtomicsObject.cpp b/js/src/builtin/AtomicsObject.cpp
index a4ae0736d4..f917db9b21 100644
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -47,10 +47,12 @@
#include "builtin/AtomicsObject.h"
#include "mozilla/Atomics.h"
+#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/Unused.h"
+#include "builtin/Promise.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "jsnum.h"
@@ -58,7 +60,9 @@
#include "jit/AtomicOperations.h"
#include "jit/InlinableNatives.h"
#include "js/Class.h"
+#include "vm/BigIntType.h"
#include "vm/GlobalObject.h"
+#include "vm/HelperThreads.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "wasm/WasmInstance.h"
@@ -121,6 +125,68 @@ GetTypedArrayIndex(JSContext* cx, HandleValue v, Handle view,
return true;
}
+static bool
+IsWaitableTypedArray(Scalar::Type viewType)
+{
+ return viewType == Scalar::Int32 || viewType == Scalar::BigInt64;
+}
+
+static uint32_t
+GetWaiterByteOffset(Handle view, uint32_t offset)
+{
+ return view->byteOffset() + offset * TypedArrayElemSize(view->type());
+}
+
+static uint64_t
+BigIntToRawBits(Scalar::Type viewType, BigInt* value)
+{
+ if (viewType == Scalar::BigInt64)
+ return uint64_t(BigInt::toInt64(value));
+ MOZ_ASSERT(viewType == Scalar::BigUint64);
+ return BigInt::toUint64(value);
+}
+
+static bool
+BigIntToRawBits(JSContext* cx, Scalar::Type viewType, HandleValue value, uint64_t* bits)
+{
+ MOZ_ASSERT(Scalar::isBigIntType(viewType));
+
+ RootedBigInt bigint(cx, ToBigInt(cx, value));
+ if (!bigint)
+ return false;
+
+ *bits = BigIntToRawBits(viewType, bigint);
+ return true;
+}
+
+static bool
+SetBigIntResult(JSContext* cx, Scalar::Type viewType, uint64_t bits, MutableHandleValue result)
+{
+ MOZ_ASSERT(Scalar::isBigIntType(viewType));
+
+ BigInt* bigint = viewType == Scalar::BigInt64
+ ? BigInt::createFromInt64(cx, mozilla::BitwiseCast(bits))
+ : BigInt::createFromUint64(cx, bits);
+ if (!bigint)
+ return false;
+
+ result.setBigInt(bigint);
+ return true;
+}
+
+static bool
+WaitValueMatches(Handle view, uint32_t offset, uint64_t expected)
+{
+ SharedMem viewData = view->viewDataShared();
+ if (view->type() == Scalar::BigInt64) {
+ return jit::AtomicOperations::loadSafeWhenRacy(viewData.cast() + offset) ==
+ expected;
+ }
+
+ return uint32_t(jit::AtomicOperations::loadSafeWhenRacy(viewData.cast() + offset)) ==
+ uint32_t(expected);
+}
+
static int32_t
CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
SharedMem viewData, uint32_t offset, bool* badArrayType = nullptr)
@@ -191,6 +257,20 @@ js::atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp)
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
+
+ if (Scalar::isBigIntType(view->type())) {
+ uint64_t oldCandidate;
+ if (!BigIntToRawBits(cx, view->type(), oldv, &oldCandidate))
+ return false;
+ uint64_t newCandidate;
+ if (!BigIntToRawBits(cx, view->type(), newv, &newCandidate))
+ return false;
+
+ uint64_t result = jit::AtomicOperations::compareExchangeSeqCst(
+ view->viewDataShared().cast() + offset, oldCandidate, newCandidate);
+ return SetBigIntResult(cx, view->type(), result, r);
+ }
+
int32_t oldCandidate;
if (!ToInt32(cx, oldv, &oldCandidate))
return false;
@@ -259,6 +339,22 @@ js::atomics_load(JSContext* cx, unsigned argc, Value* vp)
r.setNumber(v);
return true;
}
+ case Scalar::BigInt64: {
+ int64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast() + offset);
+ BigInt* bigint = BigInt::createFromInt64(cx, v);
+ if (!bigint)
+ return false;
+ r.setBigInt(bigint);
+ return true;
+ }
+ case Scalar::BigUint64: {
+ uint64_t v = jit::AtomicOperations::loadSeqCst(viewData.cast() + offset);
+ BigInt* bigint = BigInt::createFromUint64(cx, v);
+ if (!bigint)
+ return false;
+ r.setBigInt(bigint);
+ return true;
+ }
default:
return ReportBadArrayType(cx);
}
@@ -337,6 +433,25 @@ ExchangeOrStore(JSContext* cx, unsigned argc, Value* vp)
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
+
+ if (Scalar::isBigIntType(view->type())) {
+ RootedBigInt bigint(cx, ToBigInt(cx, valv));
+ if (!bigint)
+ return false;
+
+ uint64_t value = BigIntToRawBits(view->type(), bigint);
+ if (op == DoStore) {
+ jit::AtomicOperations::storeSeqCst(view->viewDataShared().cast() + offset,
+ value);
+ r.setBigInt(bigint);
+ return true;
+ }
+
+ uint64_t result = jit::AtomicOperations::exchangeSeqCst(
+ view->viewDataShared().cast() + offset, value);
+ return SetBigIntResult(cx, view->type(), result, r);
+ }
+
double integerValue;
if (!ToInteger(cx, valv, &integerValue))
return false;
@@ -380,6 +495,24 @@ AtomicsBinop(JSContext* cx, HandleValue objv, HandleValue idxv, HandleValue valv
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
+
+ if (Scalar::isBigIntType(view->type())) {
+ uint64_t value;
+ if (!BigIntToRawBits(cx, view->type(), valv, &value))
+ return false;
+
+ SharedMem addr = view->viewDataShared().cast() + offset;
+ uint64_t old = jit::AtomicOperations::loadSeqCst(addr);
+ for (;;) {
+ uint64_t replacement = T::operate64(old, value);
+ uint64_t observed = jit::AtomicOperations::compareExchangeSeqCst(addr, old,
+ replacement);
+ if (observed == old)
+ return SetBigIntResult(cx, view->type(), old, r);
+ old = observed;
+ }
+ }
+
int32_t numberValue;
if (!ToInt32(cx, valv, &numberValue))
return false;
@@ -434,6 +567,7 @@ class PerformAdd
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAddSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x + y; }
+ static uint64_t operate64(uint64_t x, uint64_t y) { return x + y; }
};
bool
@@ -448,6 +582,7 @@ class PerformSub
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchSubSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x - y; }
+ static uint64_t operate64(uint64_t x, uint64_t y) { return x - y; }
};
bool
@@ -462,6 +597,7 @@ class PerformAnd
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchAndSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x & y; }
+ static uint64_t operate64(uint64_t x, uint64_t y) { return x & y; }
};
bool
@@ -476,6 +612,7 @@ class PerformOr
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchOrSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x | y; }
+ static uint64_t operate64(uint64_t x, uint64_t y) { return x | y; }
};
bool
@@ -490,6 +627,7 @@ class PerformXor
public:
INTEGRAL_TYPES_FOR_EACH(jit::AtomicOperations::fetchXorSeqCst)
static int32_t perform(int32_t x, int32_t y) { return x ^ y; }
+ static uint64_t operate64(uint64_t x, uint64_t y) { return x ^ y; }
};
bool
@@ -693,11 +831,13 @@ js::atomics_cmpxchg_asm_callout(wasm::Instance* instance, int32_t vt, int32_t of
namespace js {
+class AtomicsWaitAsyncTask;
+
// Represents one waiting worker.
//
// The type is declared opaque in SharedArrayObject.h. Instances of
-// js::FutexWaiter are stack-allocated and linked onto a list across a
-// call to FutexRuntime::wait().
+// js::FutexWaiter are linked onto a list across a call to FutexRuntime::wait()
+// or an async wait task.
//
// The 'waiters' field of the SharedArrayRawBuffer points to the highest
// priority waiter in the list, and lower priority nodes are linked through
@@ -711,14 +851,34 @@ class FutexWaiter
public:
FutexWaiter(uint32_t offset, JSRuntime* rt)
: offset(offset),
+ kind(Sync),
rt(rt),
+ asyncTask(nullptr),
lower_pri(nullptr),
back(nullptr)
{
}
- uint32_t offset; // int32 element index within the SharedArrayBuffer
+ FutexWaiter(uint32_t offset, AtomicsWaitAsyncTask* asyncTask)
+ : offset(offset),
+ kind(Async),
+ rt(nullptr),
+ asyncTask(asyncTask),
+ lower_pri(nullptr),
+ back(nullptr)
+ {
+ }
+
+ bool isWaiting() const;
+ void notify();
+
+ uint32_t offset; // byte offset within the SharedArrayBuffer
+ enum WaiterKind {
+ Sync,
+ Async
+ } kind;
JSRuntime* rt; // The runtime of the waiter
+ AtomicsWaitAsyncTask* asyncTask; // The async waiter task, if any
FutexWaiter* lower_pri; // Lower priority nodes in circular doubly-linked list of waiters
FutexWaiter* back; // Other direction
};
@@ -742,8 +902,195 @@ class AutoLockFutexAPI
js::UniqueLock& unique() { return *unique_; }
};
+static void
+AddWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
+{
+ if (FutexWaiter* waiters = sarb->waiters()) {
+ waiter->lower_pri = waiters;
+ waiter->back = waiters->back;
+ waiters->back->lower_pri = waiter;
+ waiters->back = waiter;
+ } else {
+ waiter->lower_pri = waiter->back = waiter;
+ sarb->setWaiters(waiter);
+ }
+}
+
+static void
+RemoveWaiter(SharedArrayRawBuffer* sarb, FutexWaiter* waiter)
+{
+ if (waiter->lower_pri == waiter) {
+ sarb->setWaiters(nullptr);
+ } else {
+ waiter->lower_pri->back = waiter->back;
+ waiter->back->lower_pri = waiter->lower_pri;
+ if (sarb->waiters() == waiter)
+ sarb->setWaiters(waiter->lower_pri);
+ }
+ waiter->lower_pri = nullptr;
+ waiter->back = nullptr;
+}
+
+class AtomicsWaitAsyncTask : public PromiseTask
+{
+ enum class State {
+ Waiting,
+ Notified,
+ TimedOut
+ };
+
+ SharedArrayRawBuffer* sarb_;
+ FutexWaiter waiter_;
+ mozilla::Maybe timeout_;
+ ConditionVariable cond_;
+ State state_;
+ bool isInWaiterList_;
+
+ public:
+ AtomicsWaitAsyncTask(JSContext* cx, Handle promise,
+ SharedArrayRawBuffer* sarb, uint32_t offset,
+ mozilla::Maybe& timeout)
+ : PromiseTask(cx, promise),
+ sarb_(sarb),
+ waiter_(offset, this),
+ timeout_(timeout),
+ state_(State::Waiting),
+ isInWaiterList_(false)
+ {
+ }
+
+ ~AtomicsWaitAsyncTask() {
+ if (isInWaiterList_) {
+ AutoLockFutexAPI lock;
+ if (isInWaiterList_)
+ removeFromWaiterList();
+ }
+ sarb_->dropReference();
+ }
+
+ FutexWaiter* waiter() {
+ return &waiter_;
+ }
+
+ void setInWaiterList() {
+ isInWaiterList_ = true;
+ }
+
+ bool isWaiting() const {
+ return state_ == State::Waiting;
+ }
+
+ void notify() {
+ MOZ_ASSERT(isWaiting());
+ state_ = State::Notified;
+ cond_.notify_all();
+ }
+
+ void execute() override {
+ AutoLockFutexAPI lock;
+
+ const bool isTimed = timeout_.isSome();
+ auto finalEnd = timeout_.map([](mozilla::TimeDuration& timeout) {
+ return mozilla::TimeStamp::Now() + timeout;
+ });
+ auto maxSlice = mozilla::TimeDuration::FromSeconds(4000.0);
+
+ while (state_ == State::Waiting) {
+ if (isTimed) {
+ auto sliceEnd = finalEnd.map([&](mozilla::TimeStamp& finalEnd) {
+ auto sliceEnd = mozilla::TimeStamp::Now() + maxSlice;
+ if (finalEnd < sliceEnd)
+ sliceEnd = finalEnd;
+ return sliceEnd;
+ });
+
+ mozilla::Unused << cond_.wait_until(lock.unique(), *sliceEnd);
+ if (state_ == State::Waiting && mozilla::TimeStamp::Now() >= *finalEnd) {
+ state_ = State::TimedOut;
+ break;
+ }
+ } else {
+ cond_.wait(lock.unique());
+ }
+ }
+
+ if (isInWaiterList_)
+ removeFromWaiterList();
+ }
+
+ private:
+ void removeFromWaiterList() {
+ MOZ_ASSERT(isInWaiterList_);
+ RemoveWaiter(sarb_, &waiter_);
+ isInWaiterList_ = false;
+ }
+
+ bool finishPromise(JSContext* cx, Handle promise) override {
+ RootedValue result(cx);
+ if (state_ == State::TimedOut)
+ result.setString(cx->names().futexTimedOut);
+ else
+ result.setString(cx->names().futexOK);
+ return PromiseObject::resolve(cx, promise, result);
+ }
+};
+
+bool
+FutexWaiter::isWaiting() const
+{
+ if (kind == Sync)
+ return rt->fx.isWaiting();
+ return asyncTask->isWaiting();
+}
+
+void
+FutexWaiter::notify()
+{
+ if (kind == Sync) {
+ rt->fx.notify(FutexRuntime::NotifyExplicit);
+ return;
+ }
+ asyncTask->notify();
+}
+
} // namespace js
+static bool
+GetWaitTimeout(JSContext* cx, HandleValue timeoutv,
+ mozilla::Maybe* timeout)
+{
+ timeout->reset();
+ if (!timeoutv.isUndefined()) {
+ double timeout_ms;
+ if (!ToNumber(cx, timeoutv, &timeout_ms))
+ return false;
+ if (!mozilla::IsNaN(timeout_ms)) {
+ if (timeout_ms < 0)
+ *timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
+ else if (!mozilla::IsInfinite(timeout_ms))
+ *timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
+ }
+ }
+ return true;
+}
+
+static bool
+CreateWaitAsyncResult(JSContext* cx, bool isAsync, HandleValue value, MutableHandleValue rval)
+{
+ RootedPlainObject obj(cx, NewBuiltinClassInstance(cx));
+ if (!obj)
+ return false;
+
+ RootedValue asyncValue(cx, BooleanValue(isAsync));
+ if (!NativeDefineDataProperty(cx, obj, cx->names().async, asyncValue, JSPROP_ENUMERATE))
+ return false;
+ if (!NativeDefineDataProperty(cx, obj, cx->names().value, value, JSPROP_ENUMERATE))
+ return false;
+
+ rval.setObject(*obj);
+ return true;
+}
+
bool
js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
{
@@ -759,26 +1106,24 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
Rooted view(cx, nullptr);
if (!GetSharedTypedArray(cx, objv, &view))
return false;
- if (view->type() != Scalar::Int32)
+ if (!IsWaitableTypedArray(view->type()))
return ReportBadArrayType(cx);
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
- int32_t value;
- if (!ToInt32(cx, valv, &value))
- return false;
- mozilla::Maybe timeout;
- if (!timeoutv.isUndefined()) {
- double timeout_ms;
- if (!ToNumber(cx, timeoutv, &timeout_ms))
+ uint64_t value = 0;
+ if (view->type() == Scalar::BigInt64) {
+ if (!BigIntToRawBits(cx, view->type(), valv, &value))
return false;
- if (!mozilla::IsNaN(timeout_ms)) {
- if (timeout_ms < 0)
- timeout = mozilla::Some(mozilla::TimeDuration::FromSeconds(0.0));
- else if (!mozilla::IsInfinite(timeout_ms))
- timeout = mozilla::Some(mozilla::TimeDuration::FromMilliseconds(timeout_ms));
- }
+ } else {
+ int32_t int32Value;
+ if (!ToInt32(cx, valv, &int32Value))
+ return false;
+ value = uint32_t(int32Value);
}
+ mozilla::Maybe timeout;
+ if (!GetWaitTimeout(cx, timeoutv, &timeout))
+ return false;
if (!rt->fx.canWait())
return ReportCannotWait(cx);
@@ -787,8 +1132,7 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
// and it provides the necessary memory fence.
AutoLockFutexAPI lock;
- SharedMem addr = view->viewDataShared().cast() + offset;
- if (jit::AtomicOperations::loadSafeWhenRacy(addr) != value) {
+ if (!WaitValueMatches(view, offset, value)) {
r.setString(cx->names().futexNotEqual);
return true;
}
@@ -796,16 +1140,8 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
Rooted sab(cx, view->bufferShared());
SharedArrayRawBuffer* sarb = sab->rawBufferObject();
- FutexWaiter w(offset, rt);
- if (FutexWaiter* waiters = sarb->waiters()) {
- w.lower_pri = waiters;
- w.back = waiters->back;
- waiters->back->lower_pri = &w;
- waiters->back = &w;
- } else {
- w.lower_pri = w.back = &w;
- sarb->setWaiters(&w);
- }
+ FutexWaiter w(GetWaiterByteOffset(view, offset), rt);
+ AddWaiter(sarb, &w);
FutexRuntime::WaitResult result = FutexRuntime::FutexOK;
bool retval = rt->fx.wait(cx, lock.unique(), timeout, &result);
@@ -820,17 +1156,98 @@ js::atomics_wait(JSContext* cx, unsigned argc, Value* vp)
}
}
- if (w.lower_pri == &w) {
- sarb->setWaiters(nullptr);
- } else {
- w.lower_pri->back = w.back;
- w.back->lower_pri = w.lower_pri;
- if (sarb->waiters() == &w)
- sarb->setWaiters(w.lower_pri);
- }
+ RemoveWaiter(sarb, &w);
return retval;
}
+bool
+js::atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue objv = args.get(0);
+ HandleValue idxv = args.get(1);
+ HandleValue valv = args.get(2);
+ HandleValue timeoutv = args.get(3);
+ MutableHandleValue r = args.rval();
+
+ Rooted view(cx, nullptr);
+ if (!GetSharedTypedArray(cx, objv, &view))
+ return false;
+ if (!IsWaitableTypedArray(view->type()))
+ return ReportBadArrayType(cx);
+ uint32_t offset;
+ if (!GetTypedArrayIndex(cx, idxv, view, &offset))
+ return false;
+ uint64_t value = 0;
+ if (view->type() == Scalar::BigInt64) {
+ if (!BigIntToRawBits(cx, view->type(), valv, &value))
+ return false;
+ } else {
+ int32_t int32Value;
+ if (!ToInt32(cx, valv, &int32Value))
+ return false;
+ value = uint32_t(int32Value);
+ }
+ mozilla::Maybe timeout;
+ if (!GetWaitTimeout(cx, timeoutv, &timeout))
+ return false;
+
+ Rooted promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise)
+ return false;
+
+ RootedValue promiseValue(cx, ObjectValue(*promise));
+ RootedValue result(cx);
+ if (!CreateWaitAsyncResult(cx, true, promiseValue, &result))
+ return false;
+
+ Rooted sab(cx, view->bufferShared());
+ SharedArrayRawBuffer* sarb = sab->rawBufferObject();
+ if (!sarb->addReference()) {
+ JS_ReportErrorASCII(cx, "Reference count overflow on SharedArrayBuffer");
+ return false;
+ }
+
+ auto task = cx->make_unique(cx, promise, sarb, offset, timeout);
+ if (!task) {
+ sarb->dropReference();
+ return false;
+ }
+
+ bool isAsync = false;
+ RootedValue immediateResult(cx);
+ {
+ AutoLockFutexAPI lock;
+
+ if (!WaitValueMatches(view, offset, value)) {
+ immediateResult.setString(cx->names().futexNotEqual);
+ } else if (timeout.isSome() && timeout->ToMilliseconds() == 0.0) {
+ immediateResult.setString(cx->names().futexTimedOut);
+ } else {
+ if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback ||
+ !CanUseExtraThreads())
+ {
+ JS_ReportErrorASCII(cx, "Atomics.waitAsync not supported in this runtime.");
+ return false;
+ }
+
+ task->waiter()->offset = GetWaiterByteOffset(view, offset);
+ AddWaiter(sarb, task->waiter());
+ task->setInWaiterList();
+ isAsync = true;
+ }
+ }
+
+ if (!isAsync)
+ return CreateWaitAsyncResult(cx, false, immediateResult, r);
+
+ if (!StartPromiseTask(cx, Move(task)))
+ return false;
+
+ r.set(result);
+ return true;
+}
+
bool
js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
{
@@ -843,11 +1260,12 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
Rooted view(cx, nullptr);
if (!GetSharedTypedArray(cx, objv, &view))
return false;
- if (view->type() != Scalar::Int32)
+ if (!IsWaitableTypedArray(view->type()))
return ReportBadArrayType(cx);
uint32_t offset;
if (!GetTypedArrayIndex(cx, idxv, view, &offset))
return false;
+ offset = GetWaiterByteOffset(view, offset);
double count;
if (countv.isUndefined()) {
count = mozilla::PositiveInfinity();
@@ -870,9 +1288,9 @@ js::atomics_notify(JSContext* cx, unsigned argc, Value* vp)
do {
FutexWaiter* c = iter;
iter = iter->lower_pri;
- if (c->offset != offset || !c->rt->fx.isWaiting())
+ if (c->offset != offset || !c->isWaiting())
continue;
- c->rt->fx.notify(FutexRuntime::NotifyExplicit);
+ c->notify();
++woken;
--count;
} while (count > 0 && iter != waiters);
@@ -1106,6 +1524,7 @@ const JSFunctionSpec AtomicsMethods[] = {
JS_INLINABLE_FN("xor", atomics_xor, 3,0, AtomicsXor),
JS_INLINABLE_FN("isLockFree", atomics_isLockFree, 1,0, AtomicsIsLockFree),
JS_FN("wait", atomics_wait, 4,0),
+ JS_FN("waitAsync", atomics_waitAsync, 4,0),
JS_FN("notify", atomics_notify, 3,0),
JS_FN("wake", atomics_notify, 3,0), //Legacy name
JS_FS_END
diff --git a/js/src/builtin/AtomicsObject.h b/js/src/builtin/AtomicsObject.h
index 18f00dad16..309c97ae91 100644
--- a/js/src/builtin/AtomicsObject.h
+++ b/js/src/builtin/AtomicsObject.h
@@ -24,18 +24,19 @@ class AtomicsObject : public JSObject
static MOZ_MUST_USE bool toString(JSContext* cx, unsigned int argc, Value* vp);
};
-MOZ_MUST_USE bool atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_exchange(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_load(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_store(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_add(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_sub(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_and(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_or(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_xor(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_wait(JSContext* cx, unsigned argc, Value* vp);
-MOZ_MUST_USE bool atomics_notify(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_compareExchange(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_exchange(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_load(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_store(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_add(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_sub(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_and(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_or(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_xor(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_isLockFree(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_wait(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_waitAsync(JSContext* cx, unsigned argc, Value* vp);
+[[nodiscard]] bool atomics_notify(JSContext* cx, unsigned argc, Value* vp);
/* asm.js callouts */
namespace wasm { class Instance; }
diff --git a/js/src/builtin/Map.js b/js/src/builtin/Map.js
index 434cd6529b..8c5afa9f5e 100644
--- a/js/src/builtin/Map.js
+++ b/js/src/builtin/Map.js
@@ -55,6 +55,50 @@ function MapForEach(callbackfn, thisArg = undefined) {
}
}
+// ES2024
+// Map.groupBy ( items, callbackfn )
+function MapGroupBy(items, callbackfn) {
+ // Step 1.
+ RequireObjectCoercible(items);
+
+ // Step 2.
+ if (!IsCallable(callbackfn))
+ ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn));
+
+ // Step 3.
+ var groups = std_Map_create();
+
+ // Step 4.
+ var k = 0;
+
+ // Steps 5-8.
+ for (var value of allowContentIter(items)) {
+ // Step 6.a.
+ if (k >= MAX_NUMERIC_INDEX)
+ ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
+
+ // Step 6.b.
+ var key = callContentFunction(callbackfn, undefined, value, k);
+
+ // Steps 6.c-d.
+ // 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];
+ callFunction(std_Map_set, groups, key, elements);
+ }
+
+ // Step 6.e.
+ k++;
+ }
+
+ // Step 9.
+ return groups;
+}
+
var iteratorTemp = { mapIterationResultPair : null };
function MapIteratorNext() {
diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp
index fe748a6bde..daeb930c86 100644
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -332,9 +332,15 @@ const JSPropertySpec MapObject::staticProperties[] = {
JS_PS_END
};
+const JSFunctionSpec MapObject::staticMethods[] = {
+ JS_SELF_HOSTED_FN("groupBy", "MapGroupBy", 2, 0),
+ JS_FS_END
+};
+
static JSObject*
InitClass(JSContext* cx, Handle global, const Class* clasp, JSProtoKey key, Native construct,
const JSPropertySpec* properties, const JSFunctionSpec* methods,
+ const JSFunctionSpec* staticMethods,
const JSPropertySpec* staticProperties)
{
RootedPlainObject proto(cx, NewBuiltinClassInstance(cx));
@@ -343,8 +349,13 @@ InitClass(JSContext* cx, Handle global, const Class* clasp, JSPro
Rooted ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
if (!ctor ||
- !JS_DefineProperties(cx, ctor, staticProperties) ||
- !LinkConstructorAndPrototype(cx, ctor, proto) ||
+ !JS_DefineProperties(cx, ctor, staticProperties))
+ {
+ return nullptr;
+ }
+ if (staticMethods && !JS_DefineFunctions(cx, ctor, staticMethods))
+ return nullptr;
+ if (!LinkConstructorAndPrototype(cx, ctor, proto) ||
!DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
!GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
{
@@ -359,7 +370,7 @@ MapObject::initClass(JSContext* cx, JSObject* obj)
Rooted global(cx, &obj->as());
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
- staticProperties));
+ staticMethods, staticProperties));
if (proto) {
// Define the "entries" method.
JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
@@ -1084,7 +1095,7 @@ SetObject::initClass(JSContext* cx, JSObject* obj)
Rooted global(cx, &obj->as());
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
- staticProperties));
+ nullptr, staticProperties));
if (proto) {
// Define the "values" method.
JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h
index a9f685ea00..2b46fec2c9 100644
--- a/js/src/builtin/MapObject.h
+++ b/js/src/builtin/MapObject.h
@@ -109,8 +109,10 @@ class MapObject : public NativeObject {
static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
JS::MutableHandle> entries);
- static MOZ_MUST_USE bool entries(JSContext* cx, unsigned argc, Value* vp);
- static MOZ_MUST_USE bool has(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool entries(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool get(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool has(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool set(JSContext* cx, unsigned argc, Value* vp);
static MapObject* create(JSContext* cx, HandleObject proto = nullptr);
// Publicly exposed Map calls for JSAPI access (webidl maplike/setlike
@@ -137,6 +139,7 @@ class MapObject : public NativeObject {
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
+ static const JSFunctionSpec staticMethods[];
static const JSPropertySpec staticProperties[];
ValueMap* getData() { return static_cast(getPrivate()); }
static ValueMap& extract(HandleObject o);
@@ -150,22 +153,20 @@ class MapObject : public NativeObject {
static MOZ_MUST_USE bool iterator_impl(JSContext* cx, const CallArgs& args, IteratorKind kind);
- static MOZ_MUST_USE bool size_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool size(JSContext* cx, unsigned argc, Value* vp);
- static MOZ_MUST_USE bool get_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool get(JSContext* cx, unsigned argc, Value* vp);
- static MOZ_MUST_USE bool has_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool set_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool set(JSContext* cx, unsigned argc, Value* vp);
- static MOZ_MUST_USE bool delete_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool delete_(JSContext* cx, unsigned argc, Value* vp);
- static MOZ_MUST_USE bool keys_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool keys(JSContext* cx, unsigned argc, Value* vp);
- static MOZ_MUST_USE bool values_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool values(JSContext* cx, unsigned argc, Value* vp);
- static MOZ_MUST_USE bool entries_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool clear_impl(JSContext* cx, const CallArgs& args);
- static MOZ_MUST_USE bool clear(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool size_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool size(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool get_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool has_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool set_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool delete_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool delete_(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool keys_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool keys(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool values_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool values(JSContext* cx, unsigned argc, Value* vp);
+ [[nodiscard]] static bool entries_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool clear_impl(JSContext* cx, const CallArgs& args);
+ [[nodiscard]] static bool clear(JSContext* cx, unsigned argc, Value* vp);
};
class MapIteratorObject : public NativeObject
diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js
index 6627aea593..01442144cc 100644
--- a/js/src/builtin/Object.js
+++ b/js/src/builtin/Object.js
@@ -240,12 +240,16 @@ function ObjectGroupBy(items, callbackfn) {
// Steps 5-8.
for (var value of allowContentIter(items)) {
// Step 6.a.
- var key = callContentFunction(callbackfn, undefined, value, k);
+ if (k >= MAX_NUMERIC_INDEX)
+ ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
// Step 6.b.
+ var key = callContentFunction(callbackfn, undefined, value, k);
+
+ // Step 6.c.
key = ToPropertyKey(key);
- // Steps 6.c-d.
+ // Steps 6.d-e.
var elements = groups[key];
if (elements === undefined) {
_DefineDataProperty(groups, key, [value]);
@@ -253,7 +257,7 @@ function ObjectGroupBy(items, callbackfn) {
callFunction(std_Array_push, elements, value);
}
- // Step 6.e.
+ // Step 6.f.
k++;
}
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
index 9660a758a1..56803b5b91 100644
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -3673,6 +3673,48 @@ Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+// ES2024
+// Promise.withResolvers ( )
+static bool
+Promise_static_withResolvers(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!args.thisv().isObject()) {
+ ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, args.thisv(), nullptr);
+ return false;
+ }
+ RootedObject C(cx, &args.thisv().toObject());
+
+ // Step 2.
+ Rooted capability(cx);
+ if (!NewPromiseCapability(cx, C, &capability, false))
+ return false;
+
+ // Step 3.
+ RootedPlainObject obj(cx, NewBuiltinClassInstance(cx));
+ if (!obj)
+ return false;
+
+ // Steps 4-7.
+ RootedValue promise(cx, ObjectValue(*capability.promise()));
+ if (!::JS_DefineProperty(cx, obj, "promise", promise, JSPROP_ENUMERATE))
+ return false;
+
+ RootedValue resolve(cx, ObjectValue(*capability.resolve()));
+ if (!::JS_DefineProperty(cx, obj, "resolve", resolve, JSPROP_ENUMERATE))
+ return false;
+
+ RootedValue reject(cx, ObjectValue(*capability.reject()));
+ if (!::JS_DefineProperty(cx, obj, "reject", reject, JSPROP_ENUMERATE))
+ return false;
+
+ // Step 8.
+ args.rval().setObject(*obj);
+ return true;
+}
+
/**
* Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
*/
@@ -5180,6 +5222,7 @@ static const JSFunctionSpec promise_static_methods[] = {
JS_FN("race", Promise_static_race, 1, 0),
JS_FN("reject", Promise_reject, 1, 0),
JS_FN("resolve", Promise_static_resolve, 1, 0),
+ JS_FN("withResolvers", Promise_static_withResolvers, 0, 0),
JS_FS_END
};
diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp
index 26457709d8..0d5d0b2701 100644
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -5,6 +5,7 @@
#include "builtin/Stream.h"
+#include "js/CompilationAndEvaluation.h"
#include "js/Stream.h"
#include "jscntxt.h"
@@ -16,6 +17,10 @@
#include "vm/NativeObject-inl.h"
+#include "zlib.h"
+
+#include
+
using namespace js;
enum StreamSlots {
@@ -96,14 +101,72 @@ Is(const HandleValue v)
return v.isObject() && v.toObject().is();
}
-#ifdef DEBUG
+template
+MOZ_ALWAYS_INLINE bool
+IsMaybeWrapped(const HandleValue v)
+{
+ if (!v.isObject())
+ return false;
+
+ JSObject* obj = &v.toObject();
+ if (obj->is())
+ return true;
+
+ JSObject* unwrapped = CheckedUnwrap(obj);
+ return unwrapped && unwrapped->is();
+}
+
+template
+MOZ_ALWAYS_INLINE T*
+UnwrapInternalSlotObject(JSObject* obj)
+{
+ JSObject* unwrapped = UncheckedUnwrap(obj);
+ MOZ_ASSERT(unwrapped);
+ MOZ_ASSERT(unwrapped->is());
+ return &unwrapped->as();
+}
+
+template
+MOZ_ALWAYS_INLINE T*
+UnwrapMaybeWrappedArg(const HandleValue v)
+{
+ if (!v.isObject())
+ return nullptr;
+
+ JSObject* obj = &v.toObject();
+ if (obj->is())
+ return &obj->as();
+
+ JSObject* unwrapped = CheckedUnwrap(obj);
+ if (!unwrapped || !unwrapped->is())
+ return nullptr;
+
+ return &unwrapped->as();
+}
+
static bool
IsReadableStreamController(const JSObject* controller)
{
return controller->is() ||
controller->is();
}
-#endif // DEBUG
+
+[[nodiscard]] static NativeObject*
+UnwrapMaybeWrappedReadableStreamController(const HandleValue v)
+{
+ if (!v.isObject())
+ return nullptr;
+
+ JSObject* obj = &v.toObject();
+ if (IsReadableStreamController(obj))
+ return &obj->as();
+
+ JSObject* unwrapped = CheckedUnwrap(obj);
+ if (!unwrapped || !IsReadableStreamController(unwrapped))
+ return nullptr;
+
+ return &unwrapped->as();
+}
static inline uint32_t
ControllerFlags(const NativeObject* controller)
@@ -183,7 +246,8 @@ inline static MOZ_MUST_USE ReadableStream*
StreamFromController(const NativeObject* controller)
{
MOZ_ASSERT(IsReadableStreamController(controller));
- return &controller->getFixedSlot(ControllerSlot_Stream).toObject().as();
+ return UnwrapInternalSlotObject(
+ &controller->getFixedSlot(ControllerSlot_Stream).toObject());
}
inline static MOZ_MUST_USE NativeObject*
@@ -215,15 +279,17 @@ inline static MOZ_MUST_USE ReadableStream*
StreamFromReader(const NativeObject* reader)
{
MOZ_ASSERT(ReaderHasStream(reader));
- return &reader->getFixedSlot(ReaderSlot_Stream).toObject().as();
+ return UnwrapInternalSlotObject(
+ &reader->getFixedSlot(ReaderSlot_Stream).toObject());
}
inline static MOZ_MUST_USE NativeObject*
ReaderFromStream(const NativeObject* stream)
{
Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
- MOZ_ASSERT(JS::IsReadableStreamReader(&readerVal.toObject()));
- return &readerVal.toObject().as();
+ NativeObject* reader = UnwrapInternalSlotObject(&readerVal.toObject());
+ MOZ_ASSERT(JS::IsReadableStreamReader(reader));
+ return reader;
}
inline static bool
@@ -305,26 +371,55 @@ RejectNonGenericMethod(JSContext* cx, const CallArgs& args,
return ReturnPromiseRejectedWithPendingError(cx, args);
}
-inline static MOZ_MUST_USE NativeObject*
+[[nodiscard]] static bool
+ResolveMaybeWrappedPromise(JSContext* cx, HandleObject promise, HandleValue value)
+{
+ RootedObject wrappedPromise(cx, promise);
+ RootedValue wrappedValue(cx, value);
+ if (!JS_WrapObject(cx, &wrappedPromise) || !JS_WrapValue(cx, &wrappedValue))
+ return false;
+ return ResolvePromise(cx, wrappedPromise, wrappedValue);
+}
+
+[[nodiscard]] static bool
+RejectMaybeWrappedPromise(JSContext* cx, HandleObject promise, HandleValue reason)
+{
+ RootedObject wrappedPromise(cx, promise);
+ RootedValue wrappedReason(cx, reason);
+ if (!JS_WrapObject(cx, &wrappedPromise) || !JS_WrapValue(cx, &wrappedReason))
+ return false;
+ return RejectPromise(cx, wrappedPromise, wrappedReason);
+}
+
+[[nodiscard]] inline static NativeObject*
SetNewList(JSContext* cx, HandleNativeObject container, uint32_t slot)
{
- NativeObject* list = NewObjectWithNullTaggedProto(cx);
- if (!list)
- return nullptr;
- container->setFixedSlot(slot, ObjectValue(*list));
+ RootedNativeObject list(cx);
+ {
+ JSAutoCompartment ac(cx, container);
+ list = NewObjectWithNullTaggedProto(cx);
+ if (!list)
+ return nullptr;
+ container->setFixedSlot(slot, ObjectValue(*list));
+ }
return list;
}
inline static MOZ_MUST_USE bool
AppendToList(JSContext* cx, HandleNativeObject list, HandleValue value)
{
+ JSAutoCompartment ac(cx, list);
+ RootedValue wrappedValue(cx, value);
+ if (!JS_WrapValue(cx, &wrappedValue))
+ return false;
+
uint32_t length = list->getDenseInitializedLength();
if (!list->ensureElements(cx, length + 1))
return false;
list->ensureDenseInitializedLength(cx, length, 1);
- list->setDenseElement(length, value);
+ list->setDenseElement(length, wrappedValue);
return true;
}
@@ -341,6 +436,7 @@ template
inline static MOZ_MUST_USE T*
ShiftFromList(JSContext* cx, HandleNativeObject list)
{
+ JSAutoCompartment ac(cx, list);
uint32_t length = list->getDenseInitializedLength();
MOZ_ASSERT(length > 0);
@@ -915,30 +1011,79 @@ ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod, ReadableStream_getReader_impl>(cx, args);
}
+[[nodiscard]] static bool
+CallStreamExtra(JSContext* cx, const char* name, HandleValue streamVal,
+ HandleValue arg0, HandleValue arg1, MutableHandleValue rval)
+{
+ RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ if (!global)
+ return false;
+
+ RootedValue fun(cx);
+ if (!JS_GetProperty(cx, global, name, &fun))
+ return false;
+
+ if (!fun.isObject() || !IsCallable(fun)) {
+ if (!InitStreamExtras(cx, global))
+ return false;
+ if (!JS_GetProperty(cx, global, name, &fun))
+ return false;
+ }
+
+ if (!fun.isObject() || !IsCallable(fun)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, name);
+ return false;
+ }
+
+ FixedInvokeArgs<3> invokeArgs(cx);
+ invokeArgs[0].set(streamVal);
+ invokeArgs[1].set(arg0);
+ invokeArgs[2].set(arg1);
+ return Call(cx, fun, UndefinedHandleValue, invokeArgs, rval);
+}
+
// Streams spec, 3.2.4.4. pipeThrough({ writable, readable }, options)
-static MOZ_MUST_USE bool
+[[nodiscard]] static bool
ReadableStream_pipeThrough(JSContext* cx, unsigned argc, Value* vp)
{
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeThrough");
- return false;
- // // Step 1: Perform ? Invoke(this, "pipeTo", « writable, options »).
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!Is(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "pipeThrough", "");
+ return false;
+ }
- // // Step 2: Return readable.
- // return readable;
+ RootedValue rval(cx);
+ if (!CallStreamExtra(cx, "__uxpStreamsPipeThrough", args.thisv(),
+ args.get(0), args.get(1), &rval))
+ return false;
+
+ args.rval().set(rval);
+ return true;
}
// Streams spec, 3.2.4.5. pipeTo(dest, { preventClose, preventAbort, preventCancel } = {})
-// TODO: Unimplemented since spec is not complete yet.
-static MOZ_MUST_USE bool
+[[nodiscard]] static bool
ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp)
{
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeTo");
- return false;
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!Is(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "pipeTo", "");
+ return false;
+ }
+
+ RootedValue rval(cx);
+ if (!CallStreamExtra(cx, "__uxpStreamsPipeTo", args.thisv(),
+ args.get(0), args.get(1), &rval))
+ return false;
+
+ args.rval().set(rval);
+ return true;
}
-static MOZ_MUST_USE bool
+[[nodiscard]] static bool
ReadableStreamTee(JSContext* cx, Handle stream, bool cloneForBranch2,
MutableHandle branch1, MutableHandle branch2);
@@ -1313,7 +1458,8 @@ ReadableStreamAddReadIntoRequest(JSContext* cx, Handle stream)
{
// Step 1: MOZ_ASSERT: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true.
RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
- RootedNativeObject reader(cx, &val.toObject().as());
+ RootedNativeObject reader(cx, ReaderFromStream(stream));
+ MOZ_ASSERT(reader->is());
// Step 2: MOZ_ASSERT: stream.[[state]] is "readable" or "closed".
MOZ_ASSERT(stream->readable() || stream->closed());
@@ -1326,7 +1472,8 @@ ReadableStreamAddReadIntoRequest(JSContext* cx, Handle stream)
// Step 4: Let readIntoRequest be Record {[[promise]]: promise}.
// Step 5: Append readIntoRequest as the last element of stream.[[reader]].[[readIntoRequests]].
val = reader->getFixedSlot(ReaderSlot_Requests);
- RootedNativeObject readIntoRequests(cx, &val.toObject().as());
+ RootedNativeObject readIntoRequests(cx);
+ readIntoRequests = UnwrapInternalSlotObject(&val.toObject());
// Since [[promise]] is the Record's only field, we store it directly.
val = ObjectValue(*promise);
if (!AppendToList(cx, readIntoRequests, val))
@@ -1356,7 +1503,8 @@ ReadableStreamAddReadRequest(JSContext* cx, Handle stream)
// Step 4: Let readRequest be Record {[[promise]]: promise}.
// Step 5: Append readRequest as the last element of stream.[[reader]].[[readRequests]].
RootedValue val(cx, reader->getFixedSlot(ReaderSlot_Requests));
- RootedNativeObject readRequests(cx, &val.toObject().as());
+ RootedNativeObject readRequests(cx);
+ readRequests = UnwrapInternalSlotObject(&val.toObject());
// Since [[promise]] is the Record's only field, we store it directly.
val = ObjectValue(*promise);
@@ -1400,6 +1548,8 @@ ReadableStream::cancel(JSContext* cx, Handle stream, HandleValu
// with stream.[[storedError]].
if (stream->errored()) {
RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ if (!JS_WrapValue(cx, &storedError))
+ return nullptr;
return PromiseObject::unforgeableReject(cx, storedError);
}
@@ -1443,13 +1593,14 @@ ReadableStreamCloseInternal(JSContext* cx, Handle stream)
return true;
// Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
- RootedNativeObject reader(cx, &val.toObject().as());
+ RootedNativeObject reader(cx, ReaderFromStream(stream));
if (reader->is()) {
// Step a: Repeat for each readRequest that is an element of
// reader.[[readRequests]],
val = reader->getFixedSlot(ReaderSlot_Requests);
if (!val.isUndefined()) {
- RootedNativeObject readRequests(cx, &val.toObject().as());
+ RootedNativeObject readRequests(cx);
+ readRequests = UnwrapInternalSlotObject(&val.toObject());
uint32_t len = readRequests->getDenseInitializedLength();
RootedObject readRequest(cx);
RootedObject resultObj(cx);
@@ -1462,7 +1613,7 @@ ReadableStreamCloseInternal(JSContext* cx, Handle stream)
if (!resultObj)
return false;
resultVal = ObjectValue(*resultObj);
- if (!ResolvePromise(cx, readRequest, resultVal))
+ if (!ResolveMaybeWrappedPromise(cx, readRequest, resultVal))
return false;
}
@@ -1474,7 +1625,7 @@ ReadableStreamCloseInternal(JSContext* cx, Handle stream)
// Step 6: Resolve reader.[[closedPromise]] with undefined.
// Step 7: Return (implicit).
RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject());
- if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue))
+ if (!ResolveMaybeWrappedPromise(cx, closedPromise, UndefinedHandleValue))
return false;
if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
@@ -1502,7 +1653,13 @@ ReadableStreamErrorInternal(JSContext* cx, Handle stream, Handl
SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Errored);
// Step 4: Set stream.[[storedError]] to e.
- stream->setFixedSlot(StreamSlot_StoredError, e);
+ RootedValue storedError(cx, e);
+ {
+ JSAutoCompartment ac(cx, stream);
+ if (!JS_WrapValue(cx, &storedError))
+ return false;
+ stream->setFixedSlot(StreamSlot_StoredError, storedError);
+ }
// Step 5: Let reader be stream.[[reader]].
RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
@@ -1510,20 +1667,21 @@ ReadableStreamErrorInternal(JSContext* cx, Handle stream, Handl
// Step 6: If reader is undefined, return.
if (val.isUndefined())
return true;
- RootedNativeObject reader(cx, &val.toObject().as());
+ RootedNativeObject reader(cx, ReaderFromStream(stream));
// Steps 7,8: (Identical in our implementation.)
// Step a: Repeat for each readRequest that is an element of
// reader.[[readRequests]],
val = reader->getFixedSlot(ReaderSlot_Requests);
- RootedNativeObject readRequests(cx, &val.toObject().as());
- Rooted readRequest(cx);
+ RootedNativeObject readRequests(cx);
+ readRequests = UnwrapInternalSlotObject(&val.toObject());
+ RootedObject readRequest(cx);
uint32_t len = readRequests->getDenseInitializedLength();
for (uint32_t i = 0; i < len; i++) {
// Step i: Reject readRequest.[[promise]] with e.
val = readRequests->getDenseElement(i);
- readRequest = &val.toObject().as();
- if (!PromiseObject::reject(cx, readRequest, e))
+ readRequest = &val.toObject();
+ if (!RejectMaybeWrappedPromise(cx, readRequest, e))
return false;
}
@@ -1533,8 +1691,8 @@ ReadableStreamErrorInternal(JSContext* cx, Handle stream, Handl
// Step 9: Reject reader.[[closedPromise]] with e.
val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
- Rooted closedPromise(cx, &val.toObject().as());
- if (!PromiseObject::reject(cx, closedPromise, e))
+ RootedObject closedPromise(cx, &val.toObject());
+ if (!RejectMaybeWrappedPromise(cx, closedPromise, e))
return false;
if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
@@ -1558,7 +1716,7 @@ ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, HandlegetFixedSlot(StreamSlot_Reader));
- RootedNativeObject reader(cx, &val.toObject().as());
+ RootedNativeObject reader(cx, ReaderFromStream(stream));
// Step 2: Let readIntoRequest be the first element of
// reader.[[readIntoRequests]].
@@ -1566,7 +1724,8 @@ ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, HandlegetFixedSlot(ReaderSlot_Requests);
- RootedNativeObject readIntoRequests(cx, &val.toObject().as());
+ RootedNativeObject readIntoRequests(cx);
+ readIntoRequests = UnwrapInternalSlotObject(&val.toObject());
Rooted readIntoRequest(cx);
readIntoRequest = ShiftFromList(cx, readIntoRequests);
MOZ_ASSERT(readIntoRequest);
@@ -1577,7 +1736,8 @@ ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, HandlegetFixedSlot(ReaderSlot_Requests);
- return readRequests.toObject().as().getDenseInitializedLength();
+ NativeObject* requests = UnwrapInternalSlotObject(&readRequests.toObject());
+ return requests->getDenseInitializedLength();
}
// Stream spec 3.4.10. ReadableStreamHasBYOBReader ( stream )
@@ -1604,7 +1765,10 @@ ReadableStreamHasBYOBReader(ReadableStream* stream)
// Step 3: If ! IsReadableStreamBYOBReader(reader) is false, return false.
// Step 4: Return true.
Value reader = stream->getFixedSlot(StreamSlot_Reader);
- return reader.isObject() && reader.toObject().is();
+ if (!reader.isObject())
+ return false;
+ JSObject* unwrapped = UncheckedUnwrap(&reader.toObject());
+ return unwrapped && unwrapped->is();
}
// Streap spec 3.4.11. ReadableStreamHasDefaultReader ( stream )
@@ -1616,7 +1780,10 @@ ReadableStreamHasDefaultReader(ReadableStream* stream)
// Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
// Step 4: Return true.
Value reader = stream->getFixedSlot(StreamSlot_Reader);
- return reader.isObject() && reader.toObject().is();
+ if (!reader.isObject())
+ return false;
+ JSObject* unwrapped = UncheckedUnwrap(&reader.toObject());
+ return unwrapped && unwrapped->is();
}
static MOZ_MUST_USE bool
@@ -1663,14 +1830,13 @@ ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc, Value* vp
return false;
// Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
- if (!Is(args.get(0))) {
+ Rooted stream(cx, UnwrapMaybeWrappedArg(args.get(0)));
+ if (!stream) {
ReportArgTypeError(cx, "ReadableStreamDefaultReader", "ReadableStream",
args.get(0));
return false;
}
- Rooted stream(cx, &args.get(0).toObject().as());
-
RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, stream));
if (!reader)
return false;
@@ -1680,36 +1846,40 @@ ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc, Value* vp
}
// Streams spec, 3.5.4.1 get closed
-static MOZ_MUST_USE bool
-ReadableStreamDefaultReader_closed(JSContext* cx, unsigned argc, Value* vp)
+[[nodiscard]] static bool
+ReadableStreamDefaultReader_closed_impl(JSContext* cx, const CallArgs& args)
{
- CallArgs args = CallArgsFromVp(argc, vp);
-
- // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
- // rejected with a TypeError exception.
- if (!Is(args.thisv()))
- return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "get closed");
-
// Step 2: Return this.[[closedPromise]].
NativeObject* reader = &args.thisv().toObject().as();
args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise));
return true;
}
-static MOZ_MUST_USE JSObject*
-ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason);
-
-// Streams spec, 3.5.4.2. cancel ( reason )
-static MOZ_MUST_USE bool
-ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp)
+static bool
+ReadableStreamDefaultReader_closed(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
// rejected with a TypeError exception.
- if (!Is(args.thisv()))
- return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "cancel");
+ if (!Is(args.thisv())) {
+ if (IsMaybeWrapped(args.thisv())) {
+ return CallNonGenericMethod,
+ ReadableStreamDefaultReader_closed_impl>(cx, args);
+ }
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "get closed");
+ }
+ return ReadableStreamDefaultReader_closed_impl(cx, args);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason);
+
+// Streams spec, 3.5.4.2. cancel ( reason )
+[[nodiscard]] static bool
+ReadableStreamDefaultReader_cancel_impl(JSContext* cx, const CallArgs& args)
+{
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
// rejected with a TypeError exception.
RootedNativeObject reader(cx, &args.thisv().toObject().as());
@@ -1727,17 +1897,28 @@ ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp)
return true;
}
-// Streams spec, 3.5.4.3 read ( )
-static MOZ_MUST_USE bool
-ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp)
+static bool
+ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
// rejected with a TypeError exception.
- if (!Is(args.thisv()))
- return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "read");
+ if (!Is(args.thisv())) {
+ if (IsMaybeWrapped(args.thisv())) {
+ return CallNonGenericMethod,
+ ReadableStreamDefaultReader_cancel_impl>(cx, args);
+ }
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "cancel");
+ }
+ return ReadableStreamDefaultReader_cancel_impl(cx, args);
+}
+
+// Streams spec, 3.5.4.3 read ( )
+[[nodiscard]] static bool
+ReadableStreamDefaultReader_read_impl(JSContext* cx, const CallArgs& args)
+{
// Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
// rejected with a TypeError exception.
Rooted reader(cx);
@@ -1756,7 +1937,25 @@ ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp)
return true;
}
-static MOZ_MUST_USE bool
+static bool
+ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is(args.thisv())) {
+ if (IsMaybeWrapped(args.thisv())) {
+ return CallNonGenericMethod,
+ ReadableStreamDefaultReader_read_impl>(cx, args);
+ }
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "read");
+ }
+
+ return ReadableStreamDefaultReader_read_impl(cx, args);
+}
+
+[[nodiscard]] static bool
ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader);
// Streams spec, 3.5.4.4. releaseLock ( )
@@ -1811,8 +2010,7 @@ static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
JS_PS_END
};
-CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, ClassSpec::DontDefineConstructor, 0,
- JS_NULL_CLASS_OPS);
+CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, 0, 0, JS_NULL_CLASS_OPS);
// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
@@ -1862,12 +2060,12 @@ ReadableStreamBYOBReader::constructor(JSContext* cx, unsigned argc, Value* vp)
return false;
// Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
- if (!Is(args.get(0))) {
+ Rooted stream(cx, UnwrapMaybeWrappedArg(args.get(0)));
+ if (!stream) {
ReportArgTypeError(cx, "ReadableStreamBYOBReader", "ReadableStream", args.get(0));
return false;
}
- Rooted stream(cx, &args.get(0).toObject().as());
RootedObject reader(cx, CreateReadableStreamBYOBReader(cx, stream));
if (!reader)
return false;
@@ -2026,7 +2224,7 @@ static const JSFunctionSpec ReadableStreamBYOBReader_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
+CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, 0, 0, JS_NULL_CLASS_OPS);
inline static MOZ_MUST_USE bool
ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller);
@@ -2047,7 +2245,21 @@ ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, Hand
// Step 2: Assert: stream is not undefined (implicit).
// Step 3: Return ! ReadableStreamCancel(stream, reason).
- return &ReadableStreamCancel(cx, stream, reason)->as();
+ RootedObject cancelPromise(cx);
+ {
+ JSAutoCompartment ac(cx, stream);
+ RootedValue wrappedReason(cx, reason);
+ if (!JS_WrapValue(cx, &wrappedReason))
+ return nullptr;
+
+ cancelPromise = ReadableStreamCancel(cx, stream, wrappedReason);
+ if (!cancelPromise)
+ return nullptr;
+ }
+
+ if (!JS_WrapObject(cx, &cancelPromise))
+ return nullptr;
+ return cancelPromise;
}
// Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream )
@@ -2056,10 +2268,19 @@ ReadableStreamReaderGenericInitialize(JSContext* cx, HandleNativeObject reader,
Handle stream)
{
// Step 1: Set reader.[[ownerReadableStream]] to stream.
- reader->setFixedSlot(ReaderSlot_Stream, ObjectValue(*stream));
+ RootedValue streamVal(cx, ObjectValue(*stream));
+ if (!JS_WrapValue(cx, &streamVal))
+ return false;
+ reader->setFixedSlot(ReaderSlot_Stream, streamVal);
// Step 2: Set stream.[[reader]] to reader.
- stream->setFixedSlot(StreamSlot_Reader, ObjectValue(*reader));
+ RootedValue readerVal(cx, ObjectValue(*reader));
+ {
+ JSAutoCompartment ac(cx, stream);
+ if (!JS_WrapValue(cx, &readerVal))
+ return false;
+ stream->setFixedSlot(StreamSlot_Reader, readerVal);
+ }
// Step 3: If stream.[[state]] is "readable",
RootedObject promise(cx);
@@ -2080,6 +2301,8 @@ ReadableStreamReaderGenericInitialize(JSContext* cx, HandleNativeObject reader,
// Step ii: Set reader.[[closedPromise]] to a new promise rejected with
// stream.[[storedError]].
RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ if (!JS_WrapValue(cx, &storedError))
+ return false;
promise = PromiseObject::unforgeableReject(cx, storedError);
}
@@ -2098,7 +2321,7 @@ ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader)
Rooted stream(cx, StreamFromReader(reader));
// Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
- MOZ_ASSERT(&stream->getFixedSlot(StreamSlot_Reader).toObject() == reader);
+ MOZ_ASSERT(ReaderFromStream(stream) == reader);
// Create an exception to reject promises with below. We don't have a
// clean way to do this, unfortunately.
@@ -2156,6 +2379,8 @@ ReadableStreamBYOBReader::read(JSContext* cx, Handle
// stream.[[storedError]].
if (stream->errored()) {
RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ if (!JS_WrapValue(cx, &storedError))
+ return nullptr;
return PromiseObject::unforgeableReject(cx, storedError);
}
@@ -2347,40 +2572,12 @@ CreateReadableStreamDefaultController(JSContext* cx, Handle str
bool
ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, Value* vp)
{
- CallArgs args = CallArgsFromVp(argc, vp);
-
- if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultController"))
- return false;
-
- // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
- HandleValue streamVal = args.get(0);
- if (!Is(streamVal)) {
- ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
- args.get(0));
- return false;
- }
-
- Rooted stream(cx, &streamVal.toObject().as());
-
- // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
- // TypeError exception.
- if (HasController(stream)) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLESTREAM_CONTROLLER_SET);
- return false;
- }
-
- // Steps 3-11.
- RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream, args.get(1),
- args.get(2), args.get(3)));
- if (!controller)
- return false;
-
- args.rval().setObject(*controller);
- return true;
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
+ "ReadableStreamDefaultController");
+ return false;
}
-static MOZ_MUST_USE double
+[[nodiscard]] static double
ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller);
// Streams spec, 3.8.4.1. get desiredSize
@@ -2390,7 +2587,8 @@ static MOZ_MUST_USE bool
ReadableStreamController_desiredSize_impl(JSContext* cx, const CallArgs& args)
{
RootedNativeObject controller(cx);
- controller = &args.thisv().toObject().as();
+ controller = UnwrapMaybeWrappedReadableStreamController(args.thisv());
+ MOZ_ASSERT(controller);
// Streams spec, 3.9.8. steps 1-4.
// 3.9.8. Step 1: Let stream be controller.[[controlledReadableStream]].
@@ -2420,8 +2618,12 @@ ReadableStreamDefaultController_desiredSize(JSContext* cx, unsigned argc, Value*
// Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
// TypeError exception.
CallArgs args = CallArgsFromVp(argc, vp);
- return CallNonGenericMethod,
- ReadableStreamController_desiredSize_impl>(cx, args);
+ if (!IsMaybeWrapped(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "ReadableStreamDefaultController", "get desiredSize");
+ return false;
+ }
+ return ReadableStreamController_desiredSize_impl(cx, args);
}
static MOZ_MUST_USE bool
@@ -2456,7 +2658,8 @@ static MOZ_MUST_USE bool
ReadableStreamDefaultController_close_impl(JSContext* cx, const CallArgs& args)
{
Rooted controller(cx);
- controller = &args.thisv().toObject().as();
+ controller = UnwrapMaybeWrappedArg(args.thisv());
+ MOZ_ASSERT(controller);
// Steps 2-3.
if (!VerifyControllerStateForClosing(cx, controller))
@@ -2476,8 +2679,12 @@ ReadableStreamDefaultController_close(JSContext* cx, unsigned argc, Value* vp)
// TypeError exception.
CallArgs args = CallArgsFromVp(argc, vp);
- return CallNonGenericMethod,
- ReadableStreamDefaultController_close_impl>(cx, args);
+ if (!IsMaybeWrapped(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "ReadableStreamDefaultController", "close");
+ return false;
+ }
+ return ReadableStreamDefaultController_close_impl(cx, args);
}
static MOZ_MUST_USE bool
@@ -2490,7 +2697,8 @@ static MOZ_MUST_USE bool
ReadableStreamDefaultController_enqueue_impl(JSContext* cx, const CallArgs& args)
{
Rooted controller(cx);
- controller = &args.thisv().toObject().as();
+ controller = UnwrapMaybeWrappedArg(args.thisv());
+ MOZ_ASSERT(controller);
// Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
@@ -2522,8 +2730,12 @@ ReadableStreamDefaultController_enqueue(JSContext* cx, unsigned argc, Value* vp)
// TypeError exception.
CallArgs args = CallArgsFromVp(argc, vp);
- return CallNonGenericMethod,
- ReadableStreamDefaultController_enqueue_impl>(cx, args);
+ if (!IsMaybeWrapped(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "ReadableStreamDefaultController", "enqueue");
+ return false;
+ }
+ return ReadableStreamDefaultController_enqueue_impl(cx, args);
}
// Streams spec, 3.8.4.4. error ( e )
@@ -2531,7 +2743,8 @@ static MOZ_MUST_USE bool
ReadableStreamDefaultController_error_impl(JSContext* cx, const CallArgs& args)
{
Rooted controller(cx);
- controller = &args.thisv().toObject().as();
+ controller = UnwrapMaybeWrappedArg(args.thisv());
+ MOZ_ASSERT(controller);
// Step 2: Let stream be this.[[controlledReadableStream]].
// Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
@@ -2555,8 +2768,12 @@ ReadableStreamDefaultController_error(JSContext* cx, unsigned argc, Value* vp)
// TypeError exception.
CallArgs args = CallArgsFromVp(argc, vp);
- return CallNonGenericMethod,
- ReadableStreamDefaultController_error_impl>(cx, args);
+ if (!IsMaybeWrapped(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "ReadableStreamDefaultController", "error");
+ return false;
+ }
+ return ReadableStreamDefaultController_error_impl(cx, args);
}
static const JSPropertySpec ReadableStreamDefaultController_properties[] = {
@@ -2571,8 +2788,7 @@ static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ReadableStreamDefaultController, 4, 7, ClassSpec::DontDefineConstructor, 0,
- JS_NULL_CLASS_OPS);
+CLASS_SPEC(ReadableStreamDefaultController, 4, 7, 0, 0, JS_NULL_CLASS_OPS);
/**
* Unified implementation of ReadableStream controllers' [[CancelSteps]] internal
@@ -3116,36 +3332,9 @@ ReadableByteStreamController::hasExternalSource() {
bool
ReadableByteStreamController::constructor(JSContext* cx, unsigned argc, Value* vp)
{
- CallArgs args = CallArgsFromVp(argc, vp);
-
- if (!ThrowIfNotConstructing(cx, args, "ReadableByteStreamController"))
- return false;
-
- // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
- HandleValue streamVal = args.get(0);
- if (!Is(streamVal)) {
- ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
- args.get(0));
- return false;
- }
-
- Rooted stream(cx, &streamVal.toObject().as());
-
- // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
- // TypeError exception.
- if (HasController(stream)) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
- JSMSG_READABLESTREAM_CONTROLLER_SET);
- return false;
- }
-
- RootedObject controller(cx, CreateReadableByteStreamController(cx, stream, args.get(1),
- args.get(2)));
- if (!controller)
- return false;
-
- args.rval().setObject(*controller);
- return true;
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
+ "ReadableByteStreamController");
+ return false;
}
// Version of the ReadableByteStreamConstructor that's specialized for
@@ -3451,8 +3640,8 @@ static const ClassOps ReadableByteStreamControllerClassOps = {
nullptr, /* trace */
};
-CLASS_SPEC(ReadableByteStreamController, 3, 9, ClassSpec::DontDefineConstructor,
- JSCLASS_BACKGROUND_FINALIZE, &ReadableByteStreamControllerClassOps);
+CLASS_SPEC(ReadableByteStreamController, 3, 9, 0, JSCLASS_BACKGROUND_FINALIZE,
+ &ReadableByteStreamControllerClassOps);
// Streams spec, 3.10.5.1. [[PullSteps]] ()
// Unified with 3.8.5.1 above.
@@ -3638,38 +3827,9 @@ CreateReadableStreamBYOBRequest(JSContext* cx, Handle(controllerVal)) {
- ReportArgTypeError(cx, "ReadableStreamBYOBRequest",
- "ReadableByteStreamController", args.get(0));
- return false;
- }
-
- Rooted controller(cx);
- controller = &controllerVal.toObject().as();
-
- if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
- ReportArgTypeError(cx, "ReadableStreamBYOBRequest", "ArrayBuffer view",
- args.get(1));
- return false;
- }
-
- RootedArrayBufferObject view(cx, &viewVal.toObject().as());
-
- RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view));
- if (!request)
- return false;
-
- args.rval().setObject(*request);
- return true;
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
+ "ReadableStreamBYOBRequest");
+ return false;
}
// Streams spec, 3.11.4.1 get view
@@ -3804,8 +3964,7 @@ static const JSFunctionSpec ReadableStreamBYOBRequest_methods[] = {
JS_FS_END
};
-CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, ClassSpec::DontDefineConstructor, 0,
- JS_NULL_CLASS_OPS);
+CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, 0, 0, JS_NULL_CLASS_OPS);
// Streams spec, 3.12.1. IsReadableStreamBYOBRequest ( x )
// Implemented via is()
@@ -4899,7 +5058,15 @@ js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value*
{
CallArgs args = CallArgsFromVp(argc, vp);
- RootedObject strategy(cx, NewBuiltinClassInstance(cx));
+ if (!ThrowIfNotConstructing(cx, args, "ByteLengthQueuingStrategy"))
+ return false;
+
+ RootedObject proto(cx);
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+ return false;
+
+ RootedObject strategy(cx, NewObjectWithClassProto(cx, proto));
if (!strategy)
return false;
@@ -4945,7 +5112,16 @@ js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
- Rooted strategy(cx, NewBuiltinClassInstance(cx));
+ if (!ThrowIfNotConstructing(cx, args, "CountQueuingStrategy"))
+ return false;
+
+ RootedObject proto(cx);
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+ return false;
+
+ Rooted strategy(cx);
+ strategy = NewObjectWithClassProto(cx, proto);
if (!strategy)
return false;
@@ -4998,7 +5174,8 @@ DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chu
// Step 2: Assert: queue is not empty.
RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
- RootedNativeObject queue(cx, &val.toObject().as());
+ RootedNativeObject queue(cx);
+ queue = UnwrapInternalSlotObject(&val.toObject());
MOZ_ASSERT(queue->getDenseInitializedLength() > 0);
// Step 3. Let pair be the first element of queue.
@@ -5020,7 +5197,10 @@ DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chu
container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize));
// Step 7: Return pair.[[value]].
- chunk.set(pair->value());
+ RootedValue chunkVal(cx, pair->value());
+ if (!JS_WrapValue(cx, &chunkVal))
+ return false;
+ chunk.set(chunkVal);
return true;
}
@@ -5049,19 +5229,27 @@ EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue va
// Step 4: Append Record {[[value]]: value, [[size]]: size} as the last element
// of container.[[queue]].
RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
- RootedNativeObject queue(cx, &val.toObject().as());
+ RootedNativeObject queue(cx);
+ queue = UnwrapInternalSlotObject(&val.toObject());
- QueueEntry* entry = QueueEntry::create(cx, value, size);
- if (!entry)
- return false;
- val = ObjectValue(*entry);
- if (!AppendToList(cx, queue, val))
- return false;
+ {
+ JSAutoCompartment ac(cx, queue);
+ RootedValue wrappedValue(cx, value);
+ if (!JS_WrapValue(cx, &wrappedValue))
+ return false;
- // Step 5: Set container.[[queueTotalSize]] to
- // container.[[queueTotalSize]] + size.
- double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
- container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize + size));
+ QueueEntry* entry = QueueEntry::create(cx, wrappedValue, size);
+ if (!entry)
+ return false;
+ val = ObjectValue(*entry);
+ if (!AppendToList(cx, queue, val))
+ return false;
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] + size.
+ double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize + size));
+ }
return true;
}
@@ -5486,3 +5674,2285 @@ js::UnwrapReadableStream(JSObject* obj)
return unwrapped->is() ? unwrapped : nullptr;
return nullptr;
}
+
+namespace {
+
+enum class StreamCompressionFormat {
+ Deflate,
+ DeflateRaw,
+ Gzip
+};
+
+struct StreamCompressionContext
+{
+ z_stream mStream;
+ StreamCompressionFormat mFormat;
+ bool mCompress;
+ bool mFinished;
+ bool mInitialized;
+
+ StreamCompressionContext(StreamCompressionFormat format, bool compress)
+ : mFormat(format),
+ mCompress(compress),
+ mFinished(false),
+ mInitialized(false)
+ {
+ memset(&mStream, 0, sizeof(mStream));
+ }
+
+ ~StreamCompressionContext()
+ {
+ if (!mInitialized)
+ return;
+ if (mCompress)
+ deflateEnd(&mStream);
+ else
+ inflateEnd(&mStream);
+ }
+};
+
+static int
+WindowBitsForFormat(StreamCompressionFormat format)
+{
+ switch (format) {
+ case StreamCompressionFormat::Deflate:
+ return MAX_WBITS;
+ case StreamCompressionFormat::DeflateRaw:
+ return -MAX_WBITS;
+ case StreamCompressionFormat::Gzip:
+ return MAX_WBITS + 16;
+ }
+ MOZ_CRASH("Unknown compression stream format");
+}
+
+static bool
+ParseCompressionFormat(JSContext* cx, HandleValue value,
+ StreamCompressionFormat* format)
+{
+ RootedString str(cx, ToString(cx, value));
+ if (!str)
+ return false;
+
+ bool match = false;
+ if (!JS_StringEqualsAscii(cx, str, "deflate", &match))
+ return false;
+ if (match) {
+ *format = StreamCompressionFormat::Deflate;
+ return true;
+ }
+
+ if (!JS_StringEqualsAscii(cx, str, "deflate-raw", &match))
+ return false;
+ if (match) {
+ *format = StreamCompressionFormat::DeflateRaw;
+ return true;
+ }
+
+ if (!JS_StringEqualsAscii(cx, str, "gzip", &match))
+ return false;
+ if (match) {
+ *format = StreamCompressionFormat::Gzip;
+ return true;
+ }
+
+ if (!JS_StringEqualsAscii(cx, str, "brotli", &match))
+ return false;
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_EXPECTED_TYPE,
+ "CompressionStream", "supported format",
+ match ? "unsupported brotli format" : "unknown format");
+ return false;
+}
+
+static void
+StreamCompressionContextFinalize(FreeOp* fop, JSObject* obj)
+{
+ auto* context = static_cast(JS_GetPrivate(obj));
+ if (context)
+ js_delete(context);
+}
+
+static const ClassOps StreamCompressionContextClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ StreamCompressionContextFinalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ nullptr /* trace */
+};
+
+static const Class StreamCompressionContextClass = {
+ "StreamCompressionContext",
+ JSCLASS_HAS_PRIVATE,
+ &StreamCompressionContextClassOps
+};
+
+static bool
+CreateStreamCompressionContext(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 2) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_MORE_ARGS_NEEDED,
+ "__uxpCreateCompressionContext", "1", "s");
+ return false;
+ }
+
+ StreamCompressionFormat format;
+ if (!ParseCompressionFormat(cx, args.get(0), &format))
+ return false;
+
+ bool compress = ToBoolean(args.get(1));
+ auto* context = js_new(format, compress);
+ if (!context) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ int windowBits = WindowBitsForFormat(format);
+ int zerr = compress
+ ? deflateInit2(&context->mStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ windowBits, 8, Z_DEFAULT_STRATEGY)
+ : inflateInit2(&context->mStream, windowBits);
+ if (zerr != Z_OK) {
+ js_delete(context);
+ JS_ReportErrorASCII(cx, "Failed to initialize compression stream");
+ return false;
+ }
+ context->mInitialized = true;
+
+ RootedObject obj(cx, NewObjectWithGivenProto(cx, &StreamCompressionContextClass, nullptr));
+ if (!obj) {
+ js_delete(context);
+ return false;
+ }
+
+ JS_SetPrivate(obj, context);
+ args.rval().setObject(*obj);
+ return true;
+}
+
+static bool
+GetBufferSourceBytes(JSContext* cx, HandleValue value, Vector* bytes)
+{
+ if (!value.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_EXPECTED_TYPE,
+ "CompressionStream", "BufferSource",
+ InformalValueTypeName(value));
+ return false;
+ }
+
+ RootedObject obj(cx, &value.toObject());
+ if (JS_IsArrayBufferViewObject(obj)) {
+ uint32_t length = JS_GetArrayBufferViewByteLength(obj);
+ if (length == 0)
+ return true;
+
+ JS::AutoCheckCannotGC nogc(cx);
+ bool isShared;
+ uint8_t* data = static_cast(JS_GetArrayBufferViewData(obj, &isShared, nogc));
+ return bytes->append(data, length);
+ }
+
+ if (JS_IsArrayBufferObject(obj)) {
+ uint32_t length = JS_GetArrayBufferByteLength(obj);
+ if (length == 0)
+ return true;
+
+ JS::AutoCheckCannotGC nogc(cx);
+ bool isShared;
+ uint8_t* data = JS_GetArrayBufferData(obj, &isShared, nogc);
+ return bytes->append(data, length);
+ }
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_EXPECTED_TYPE,
+ "CompressionStream", "BufferSource",
+ obj->getClass()->name);
+ return false;
+}
+
+static bool
+AppendUint8Array(JSContext* cx, HandleObject array, uint32_t* index,
+ const uint8_t* data, size_t length)
+{
+ if (length == 0)
+ return true;
+
+ if (length > UINT32_MAX) {
+ ReportAllocationOverflow(cx);
+ return false;
+ }
+
+ RootedObject view(cx, JS_NewUint8Array(cx, length));
+ if (!view)
+ return false;
+
+ {
+ JS::AutoCheckCannotGC nogc(cx);
+ bool isShared;
+ uint8_t* viewData = static_cast(JS_GetArrayBufferViewData(view, &isShared, nogc));
+ memcpy(viewData, data, length);
+ }
+
+ RootedValue viewVal(cx, ObjectValue(*view));
+ return JS_SetElement(cx, array, (*index)++, viewVal);
+}
+
+static bool
+StreamCompressionProcess(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 3 || !args[0].isObject() ||
+ !args[0].toObject().hasClass(&StreamCompressionContextClass)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_EXPECTED_TYPE,
+ "__uxpCompressionProcess",
+ "StreamCompressionContext", "other");
+ return false;
+ }
+
+ auto* context =
+ static_cast(JS_GetPrivate(&args[0].toObject()));
+ if (!context) {
+ JS_ReportErrorASCII(cx, "Compression stream is already finished");
+ return false;
+ }
+
+ bool finish = ToBoolean(args.get(2));
+
+ if (context->mFinished) {
+ if (finish && args.get(1).isUndefined()) {
+ RootedObject emptyChunks(cx, JS_NewArrayObject(cx, 0));
+ if (!emptyChunks)
+ return false;
+ args.rval().setObject(*emptyChunks);
+ return true;
+ }
+ JS_ReportErrorASCII(cx, "Compression stream is already finished");
+ return false;
+ }
+
+ Vector input(cx);
+ if (!args.get(1).isUndefined() && !GetBufferSourceBytes(cx, args.get(1), &input))
+ return false;
+
+ RootedObject chunks(cx, JS_NewArrayObject(cx, 0));
+ if (!chunks)
+ return false;
+
+ z_stream& stream = context->mStream;
+ stream.next_in = input.empty() ? nullptr : input.begin();
+ stream.avail_in = input.length();
+
+ uint32_t chunkIndex = 0;
+ const int flush = finish ? Z_FINISH : Z_NO_FLUSH;
+ bool madeProgress;
+
+ do {
+ uint8_t output[16384];
+ stream.next_out = output;
+ stream.avail_out = sizeof(output);
+
+ uint32_t beforeIn = stream.avail_in;
+ uint32_t beforeOut = stream.avail_out;
+ int zerr = context->mCompress ? deflate(&stream, flush) : inflate(&stream, flush);
+
+ size_t produced = sizeof(output) - stream.avail_out;
+ if (!AppendUint8Array(cx, chunks, &chunkIndex, output, produced))
+ return false;
+
+ madeProgress = produced > 0 || stream.avail_in != beforeIn ||
+ stream.avail_out != beforeOut;
+
+ if (zerr == Z_STREAM_END) {
+ context->mFinished = true;
+ if (!context->mCompress && stream.avail_in != 0) {
+ JS_ReportErrorASCII(cx, "Compressed input has trailing data");
+ return false;
+ }
+ break;
+ }
+
+ if (zerr == Z_BUF_ERROR && !madeProgress)
+ break;
+
+ if (zerr != Z_OK && zerr != Z_BUF_ERROR) {
+ JS_ReportErrorASCII(cx, stream.msg ? stream.msg : "Compression stream error");
+ return false;
+ }
+ } while (stream.avail_out == 0 || stream.avail_in != 0 ||
+ (finish && !context->mFinished && madeProgress));
+
+ if (finish && !context->mFinished) {
+ JS_ReportErrorASCII(cx, "Compressed input ended before the end of stream");
+ return false;
+ }
+
+ args.rval().setObject(*chunks);
+ return true;
+}
+
+bool
+ReleaseReadableStreamReaderSilently(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(true);
+
+ if (!args.get(0).isObject())
+ return true;
+
+ RootedObject readerObj(cx, &args[0].toObject());
+ if (!readerObj->is() &&
+ !readerObj->is())
+ return true;
+
+ RootedNativeObject reader(cx, &readerObj->as());
+ if (!ReaderHasStream(reader))
+ return true;
+
+ Value requestsVal = reader->getFixedSlot(ReaderSlot_Requests);
+ if (!requestsVal.isUndefined()) {
+ NativeObject* requests = &requestsVal.toObject().as();
+ if (requests->getDenseInitializedLength() != 0) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+ }
+
+ Rooted stream(cx, StreamFromReader(reader));
+ MOZ_ASSERT(&stream->getFixedSlot(StreamSlot_Reader).toObject() == reader);
+
+ RootedObject closedPromise(cx, PromiseObject::unforgeableResolve(cx, UndefinedHandleValue));
+ if (!closedPromise)
+ return false;
+ reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*closedPromise));
+
+ stream->setFixedSlot(StreamSlot_Reader, UndefinedValue());
+ reader->setFixedSlot(ReaderSlot_Stream, UndefinedValue());
+ return true;
+}
+
+} // anonymous namespace
+
+bool
+js::InitStreamExtras(JSContext* cx, HandleObject global)
+{
+ if (!JS_DefineFunction(cx, global, "__uxpCreateCompressionContext",
+ CreateStreamCompressionContext, 2, 0))
+ return false;
+
+ if (!JS_DefineFunction(cx, global, "__uxpCompressionProcess",
+ StreamCompressionProcess, 3, 0))
+ return false;
+
+ if (!JS_DefineFunction(cx, global, "__uxpReleaseReadableStreamReaderSilently",
+ ReleaseReadableStreamReaderSilently, 1, 0))
+ return false;
+
+ static const char source[] =
+ R"JS(
+(function(global) {
+ "use strict";
+
+ if (global.__uxpStreamsInitialized)
+ return;
+ Object.defineProperty(global, "__uxpStreamsInitialized",
+ { value: true, configurable: true });
+
+ var objectToString = Object.prototype.toString;
+ var hasOwn = Object.prototype.hasOwnProperty;
+
+ function isObject(value) {
+ return (typeof value === "object" && value !== null) ||
+ typeof value === "function";
+ }
+
+ function requiredObject(value, name) {
+ if (!isObject(value))
+ throw new TypeError(name + " is not an object");
+ return value;
+ }
+
+ function newPromiseCapability() {
+ var resolve, reject;
+ var promise = new Promise(function(res, rej) {
+ resolve = res;
+ reject = rej;
+ });
+ return { promise: promise, resolve: resolve, reject: reject };
+ }
+
+ function promiseCall(fn, receiver, args) {
+ try {
+ return Promise.resolve(fn.apply(receiver, args || []));
+ } catch (e) {
+ return Promise.reject(e);
+ }
+ }
+
+ function getMethod(value, name) {
+ var method = value[name];
+ if (method === undefined || method === null)
+ return undefined;
+ if (typeof method !== "function")
+ throw new TypeError(name + " is not callable");
+ return method;
+ }
+
+ function makeDOMException(message, name) {
+ if (typeof global.DOMException === "function")
+ return new global.DOMException(message, name);
+ var error = new Error(message);
+ error.name = name;
+ return error;
+ }
+
+ function define(name, value) {
+ if (hasOwn.call(global, name) && global[name] !== undefined)
+ return;
+ Object.defineProperty(global, name, {
+ value: value,
+ writable: true,
+ configurable: true
+ });
+ }
+
+ var writableState = new WeakMap();
+ var writerState = new WeakMap();
+ var writableControllerState = new WeakMap();
+
+ function isWritableStream(stream) {
+ return writableState.has(stream);
+ }
+
+ function isWritableStreamDefaultWriter(writer) {
+ return writerState.has(writer);
+ }
+
+ function rejectPromise(error) {
+ return Promise.reject(error);
+ }
+
+ function silenceRejection(promise) {
+ try {
+ if (promise && typeof promise.catch === "function")
+ promise.catch(function() {});
+ } catch (e) {}
+ }
+
+ function releaseReaderLock(reader) {
+ try {
+ if (global.__uxpReleaseReadableStreamReaderSilently(reader))
+ return;
+ } catch (e) {}
+ Promise.resolve().then(function() {
+ try {
+ if (global.__uxpReleaseReadableStreamReaderSilently(reader))
+ return;
+ } catch (e) {}
+ try { silenceRejection(reader.closed); } catch (e) {}
+ try { reader.releaseLock(); } catch (e) {}
+ });
+ }
+
+ function releaseWriterLock(writer) {
+ try { silenceRejection(writer.closed); } catch (e) {}
+ try { silenceRejection(writer.ready); } catch (e) {}
+ try { writer.releaseLock(); } catch (e) {}
+ }
+
+ function syncWritableWriterWithStream(writer, state) {
+ var ws = writerState.get(writer);
+ if (!ws || ws.stream === undefined)
+ return;
+
+ ws.readyPromise = state.readyPromise;
+ if (state.state === "closed") {
+ ws.closedPromise = Promise.resolve(undefined);
+ } else if (state.state === "errored") {
+ ws.closedPromise = Promise.reject(state.storedError);
+ ws.closedPromise.catch(function() {});
+ } else {
+ ws.closedPromise = state.closedPromise;
+ }
+ }
+
+ function writableError(stream, error) {
+ var state = writableState.get(stream);
+ if (!state || state.state === "closed" || state.state === "errored")
+ return;
+ state.state = "errored";
+ state.storedError = error;
+ state.ready.reject(error);
+ state.closed.reject(error);
+ state.readyPromise.catch(function() {});
+ state.closedPromise.catch(function() {});
+ if (state.writer)
+ syncWritableWriterWithStream(state.writer, state);
+ }
+
+ function writableClose(stream) {
+ var state = writableState.get(stream);
+ if (!state || state.state === "closed")
+ return;
+ state.state = "closed";
+ state.ready.resolve(undefined);
+ state.closed.resolve(undefined);
+ if (state.writer)
+ syncWritableWriterWithStream(state.writer, state);
+ }
+
+ function writableStreamWrite(stream, chunk) {
+ var state = writableState.get(stream);
+ if (!state)
+ return rejectPromise(new TypeError("WritableStream expected"));
+ if (state.state === "closed")
+ return rejectPromise(new TypeError("WritableStream is closed"));
+ if (state.state === "errored")
+ return rejectPromise(state.storedError);
+ if (state.state === "closing")
+ return rejectPromise(new TypeError("WritableStream is closing"));
+
+ var size = 1;
+ try {
+ size = state.sizeAlgorithm(chunk);
+ if (!Number.isFinite(size) || size < 0)
+ throw new RangeError("Invalid chunk size");
+ } catch (e) {
+ writableError(stream, e);
+ return rejectPromise(e);
+ }
+
+ state.queueTotalSize += size;
+ if (state.queueTotalSize > state.highWaterMark && state.backpressure === false) {
+ state.backpressure = true;
+ state.ready = newPromiseCapability();
+ state.readyPromise = state.ready.promise;
+ if (state.writer)
+ syncWritableWriterWithStream(state.writer, state);
+ }
+
+ var write = state.sink.write;
+ var controller = state.controller;
+ var writePromise = state.writeChain.then(function() {
+ if (state.state === "errored")
+ throw state.storedError;
+ if (write)
+ return write.call(state.sink, chunk, controller);
+ return undefined;
+ });
+ state.writeChain = writePromise.then(function(value) {
+ state.queueTotalSize -= size;
+ if (state.queueTotalSize <= state.highWaterMark && state.backpressure) {
+ state.backpressure = false;
+ state.ready.resolve(undefined);
+ state.ready = newPromiseCapability();
+ state.ready.resolve(undefined);
+ state.readyPromise = state.ready.promise;
+ if (state.writer)
+ syncWritableWriterWithStream(state.writer, state);
+ }
+ return value;
+ }, function(error) {
+ writableError(stream, error);
+ throw error;
+ });
+ state.writeChain.catch(function() {});
+ return writePromise;
+ }
+
+ function writableStreamClose(stream) {
+ var state = writableState.get(stream);
+ if (!state)
+ return rejectPromise(new TypeError("WritableStream expected"));
+ if (state.state === "closed")
+ return rejectPromise(new TypeError("WritableStream is already closed"));
+ if (state.state === "errored")
+ return rejectPromise(state.storedError);
+ if (state.state === "closing")
+ return rejectPromise(new TypeError("WritableStream is already closing"));
+
+ state.state = "closing";
+ var close = state.sink.close;
+ var closePromise = state.writeChain.then(function() {
+ if (close)
+ return close.call(state.sink);
+ return undefined;
+ }).then(function(value) {
+ writableClose(stream);
+ return value;
+ }, function(error) {
+ writableError(stream, error);
+ throw error;
+ });
+ state.writeChain = closePromise.catch(function() {});
+ return closePromise;
+ }
+
+ function writableStreamAbort(stream, reason) {
+ var state = writableState.get(stream);
+ if (!state)
+ return rejectPromise(new TypeError("WritableStream expected"));
+ if (state.state === "closed")
+ return Promise.resolve(undefined);
+ if (state.state === "errored")
+ return rejectPromise(state.storedError);
+
+ var abort = state.sink.abort;
+ state.state = "errored";
+ state.storedError = reason;
+ if (state.abortController) {
+ try { state.abortController.abort(); } catch (e) {}
+ }
+ state.ready.reject(reason);
+ state.closed.reject(reason);
+ state.readyPromise.catch(function() {});
+ state.closedPromise.catch(function() {});
+ if (state.writer)
+ syncWritableWriterWithStream(state.writer, state);
+
+ var abortPromise = abort ? promiseCall(abort, state.sink, [reason])
+ : Promise.resolve(undefined);
+ return abortPromise;
+ }
+
+ function WritableStreamDefaultController() {
+ throw new TypeError("Illegal constructor");
+ }
+
+ WritableStreamDefaultController.prototype.error = function(error) {
+ var state = writableControllerState.get(this);
+ if (!state)
+ throw new TypeError("WritableStreamDefaultController expected");
+ writableError(state.stream, error);
+ };
+
+ try {
+ Object.defineProperty(WritableStreamDefaultController.prototype, "signal", {
+ configurable: true,
+ get: function() {
+ var state = writableControllerState.get(this);
+ if (!state)
+ throw new TypeError("WritableStreamDefaultController expected");
+ return state.abortController ? state.abortController.signal : undefined;
+ }
+ });
+ } catch (e) {}
+
+ Object.defineProperty(WritableStreamDefaultController.prototype,
+ Symbol.toStringTag,
+ { value: "WritableStreamDefaultController",
+ configurable: true });
+
+ function WritableStream(underlyingSink, strategy) {
+ if (!(this instanceof WritableStream))
+ throw new TypeError("WritableStream must be constructed with new");
+
+ underlyingSink = underlyingSink === undefined ? {} : requiredObject(underlyingSink, "underlyingSink");
+ strategy = strategy === undefined ? {} : requiredObject(strategy, "strategy");
+
+ if (underlyingSink.type !== undefined)
+ throw new RangeError("Invalid writable stream type");
+
+ var highWaterMark = strategy.highWaterMark === undefined ? 1 : Number(strategy.highWaterMark);
+ if (Number.isNaN(highWaterMark) || highWaterMark < 0)
+ throw new RangeError("Invalid highWaterMark");
+
+ var sizeAlgorithm = strategy.size;
+ if (sizeAlgorithm === undefined)
+ sizeAlgorithm = function() { return 1; };
+ if (typeof sizeAlgorithm !== "function")
+ throw new TypeError("strategy.size is not callable");
+
+ var controller = Object.create(WritableStreamDefaultController.prototype);
+ var abortController = typeof global.AbortController === "function"
+ ? new global.AbortController()
+ : undefined;
+ var ready = newPromiseCapability();
+ ready.resolve(undefined);
+ var closed = newPromiseCapability();
+ var state = {
+ sink: underlyingSink,
+ controller: controller,
+ signal: undefined,
+ state: "writable",
+ storedError: undefined,
+ writer: undefined,
+ abortController: abortController,
+ writeChain: Promise.resolve(undefined),
+ ready: ready,
+ readyPromise: ready.promise,
+ closed: closed,
+ closedPromise: closed.promise,
+ highWaterMark: highWaterMark,
+ sizeAlgorithm: sizeAlgorithm,
+ queueTotalSize: 0,
+ backpressure: false
+ };
+ writableState.set(this, state);
+ writableControllerState.set(controller, {
+ stream: this,
+ abortController: abortController
+ });
+
+ var start = underlyingSink.start;
+ if (start !== undefined) {
+ if (typeof start !== "function")
+ throw new TypeError("underlyingSink.start is not callable");
+ state.writeChain = promiseCall(start, underlyingSink, [controller]).catch(function(error) {
+ writableError(this, error);
+ throw error;
+ }.bind(this));
+ state.writeChain.catch(function() {});
+ }
+ }
+
+ Object.defineProperty(WritableStream.prototype, "locked", {
+ configurable: true,
+ get: function() {
+ var state = writableState.get(this);
+ if (!state)
+ throw new TypeError("WritableStream expected");
+ return state.writer !== undefined;
+ }
+ });
+
+ WritableStream.prototype.abort = function(reason) {
+ if (!isWritableStream(this))
+ return rejectPromise(new TypeError("WritableStream expected"));
+ if (writableState.get(this).writer !== undefined)
+ return rejectPromise(new TypeError("WritableStream is locked"));
+ return writableStreamAbort(this, reason);
+ };
+
+ WritableStream.prototype.close = function() {
+ if (!isWritableStream(this))
+ return rejectPromise(new TypeError("WritableStream expected"));
+ if (writableState.get(this).writer !== undefined)
+ return rejectPromise(new TypeError("WritableStream is locked"));
+ return writableStreamClose(this);
+ };
+
+ WritableStream.prototype.getWriter = function() {
+ if (!isWritableStream(this))
+ throw new TypeError("WritableStream expected");
+ return new WritableStreamDefaultWriter(this);
+ };
+
+ Object.defineProperty(WritableStream.prototype, Symbol.toStringTag,
+ { value: "WritableStream", configurable: true });
+
+ function WritableStreamDefaultWriter(stream) {
+ if (!(this instanceof WritableStreamDefaultWriter))
+ throw new TypeError("WritableStreamDefaultWriter must be constructed with new");
+ if (!isWritableStream(stream))
+ throw new TypeError("WritableStream expected");
+
+ var state = writableState.get(stream);
+ if (state.writer !== undefined)
+ throw new TypeError("WritableStream is locked");
+
+ state.writer = this;
+ writerState.set(this, {
+ stream: stream,
+ readyPromise: state.readyPromise,
+ closedPromise: state.closedPromise
+ });
+ }
+
+ Object.defineProperty(WritableStreamDefaultWriter.prototype, "closed", {
+ configurable: true,
+ get: function() {
+ var state = writerState.get(this);
+ if (!state)
+ return rejectPromise(new TypeError("WritableStreamDefaultWriter expected"));
+ return state.closedPromise;
+ }
+ });
+
+ Object.defineProperty(WritableStreamDefaultWriter.prototype, "desiredSize", {
+ configurable: true,
+ get: function() {
+ var state = writerState.get(this);
+ if (!state)
+ throw new TypeError("WritableStreamDefaultWriter expected");
+ if (state.stream === undefined)
+ throw new TypeError("Writer lock is released");
+ var streamState = writableState.get(state.stream);
+ if (streamState.state === "errored")
+ return null;
+ if (streamState.state === "closed")
+ return 0;
+ return streamState.highWaterMark - streamState.queueTotalSize;
+ }
+ });
+
+ Object.defineProperty(WritableStreamDefaultWriter.prototype, "ready", {
+ configurable: true,
+ get: function() {
+ var state = writerState.get(this);
+ if (!state)
+ return rejectPromise(new TypeError("WritableStreamDefaultWriter expected"));
+ return state.readyPromise;
+ }
+ });
+
+ WritableStreamDefaultWriter.prototype.abort = function(reason) {
+ var state = writerState.get(this);
+ if (!state || state.stream === undefined)
+ return rejectPromise(new TypeError("Writer lock is released"));
+ return writableStreamAbort(state.stream, reason);
+ };
+
+ WritableStreamDefaultWriter.prototype.close = function() {
+ var state = writerState.get(this);
+ if (!state || state.stream === undefined)
+ return rejectPromise(new TypeError("Writer lock is released"));
+ return writableStreamClose(state.stream);
+ };
+
+ WritableStreamDefaultWriter.prototype.releaseLock = function() {
+ var state = writerState.get(this);
+ if (!state)
+ throw new TypeError("WritableStreamDefaultWriter expected");
+ if (state.stream === undefined)
+ return;
+ var streamState = writableState.get(state.stream);
+ if (streamState.writer === this)
+ streamState.writer = undefined;
+ state.stream = undefined;
+ state.readyPromise = rejectPromise(new TypeError("Writer lock is released"));
+ state.closedPromise = rejectPromise(new TypeError("Writer lock is released"));
+ state.readyPromise.catch(function() {});
+ state.closedPromise.catch(function() {});
+ };
+
+ WritableStreamDefaultWriter.prototype.write = function(chunk) {
+ var state = writerState.get(this);
+ if (!state || state.stream === undefined)
+ return rejectPromise(new TypeError("Writer lock is released"));
+ return writableStreamWrite(state.stream, chunk);
+ };
+
+)JS"
+R"JS(
+
+ Object.defineProperty(WritableStreamDefaultWriter.prototype, Symbol.toStringTag,
+ { value: "WritableStreamDefaultWriter",
+ configurable: true });
+
+ var transformState = new WeakMap();
+ var transformControllerState = new WeakMap();
+
+ function TransformStreamDefaultController() {
+ throw new TypeError("Illegal constructor");
+ }
+
+ Object.defineProperty(TransformStreamDefaultController.prototype, "desiredSize", {
+ configurable: true,
+ get: function() {
+ var state = transformControllerState.get(this);
+ if (!state)
+ throw new TypeError("TransformStreamDefaultController expected");
+ return state.readableController.desiredSize;
+ }
+ });
+
+ TransformStreamDefaultController.prototype.enqueue = function(chunk) {
+ var state = transformControllerState.get(this);
+ if (!state)
+ throw new TypeError("TransformStreamDefaultController expected");
+ state.readableController.enqueue(chunk);
+ };
+
+ TransformStreamDefaultController.prototype.error = function(reason) {
+ var state = transformControllerState.get(this);
+ if (!state)
+ throw new TypeError("TransformStreamDefaultController expected");
+ state.readableController.error(reason);
+ writableError(state.stream.writable, reason);
+ };
+
+ TransformStreamDefaultController.prototype.terminate = function() {
+ var state = transformControllerState.get(this);
+ if (!state)
+ throw new TypeError("TransformStreamDefaultController expected");
+ try {
+ state.readableController.close();
+ } catch (e) {}
+ writableError(state.stream.writable, new TypeError("TransformStream terminated"));
+ };
+
+ Object.defineProperty(TransformStreamDefaultController.prototype,
+ Symbol.toStringTag,
+ { value: "TransformStreamDefaultController",
+ configurable: true });
+
+ function errorReadableController(controller, error) {
+ try {
+ controller.error(error);
+ } catch (e) {}
+ }
+
+ function TransformStream(transformer, writableStrategy, readableStrategy) {
+ if (!(this instanceof TransformStream))
+ throw new TypeError("TransformStream must be constructed with new");
+
+ transformer = transformer === undefined ? {} : requiredObject(transformer, "transformer");
+ writableStrategy = writableStrategy === undefined ? {} : requiredObject(writableStrategy, "writableStrategy");
+ readableStrategy = readableStrategy === undefined ? {} : requiredObject(readableStrategy, "readableStrategy");
+
+ if (transformer.readableType !== undefined)
+ throw new RangeError("Invalid readableType");
+ if (transformer.writableType !== undefined)
+ throw new RangeError("Invalid writableType");
+
+ var controller = Object.create(TransformStreamDefaultController.prototype);
+ var readableController;
+ var stream = this;
+ var startAlgorithm = getMethod(transformer, "start");
+ var transformAlgorithm = getMethod(transformer, "transform");
+ var flushAlgorithm = getMethod(transformer, "flush");
+ var cancelAlgorithm = getMethod(transformer, "cancel");
+
+ var readable = new ReadableStream({
+ start: function(c) {
+ readableController = c;
+ transformControllerState.set(controller, {
+ stream: stream,
+ readableController: c
+ });
+ },
+ cancel: function(reason) {
+ if (cancelAlgorithm)
+ return cancelAlgorithm.call(transformer, reason);
+ return undefined;
+ }
+ }, readableStrategy);
+
+ var startPromise = startAlgorithm
+ ? promiseCall(startAlgorithm, transformer, [controller])
+ : Promise.resolve(undefined);
+ startPromise = startPromise.then(undefined, function(error) {
+ if (readableController)
+ errorReadableController(readableController, error);
+ throw error;
+ });
+
+ var writable = new WritableStream({
+ start: function() {
+ return startPromise;
+ },
+ write: function(chunk) {
+ var result;
+ try {
+ if (transformAlgorithm) {
+ result = transformAlgorithm.call(transformer, chunk, controller);
+ } else {
+ controller.enqueue(chunk);
+ result = undefined;
+ }
+ } catch (error) {
+ errorReadableController(readableController, error);
+ throw error;
+ }
+ return Promise.resolve(result).then(undefined, function(error) {
+ errorReadableController(readableController, error);
+ throw error;
+ });
+ },
+ close: function() {
+ var result = flushAlgorithm
+ ? promiseCall(flushAlgorithm, transformer, [controller])
+ : Promise.resolve(undefined);
+ return result.then(function() {
+ readableController.close();
+ }, function(error) {
+ errorReadableController(readableController, error);
+ throw error;
+ });
+ },
+ abort: function(reason) {
+ readableController.error(reason);
+ }
+ }, writableStrategy);
+
+ transformState.set(this, {
+ readable: readable,
+ writable: writable,
+ controller: controller
+ });
+ }
+
+ Object.defineProperty(TransformStream.prototype, "readable", {
+ configurable: true,
+ get: function() {
+ var state = transformState.get(this);
+ if (!state)
+ throw new TypeError("TransformStream expected");
+ return state.readable;
+ }
+ });
+
+ Object.defineProperty(TransformStream.prototype, "writable", {
+ configurable: true,
+ get: function() {
+ var state = transformState.get(this);
+ if (!state)
+ throw new TypeError("TransformStream expected");
+ return state.writable;
+ }
+ });
+
+ Object.defineProperty(TransformStream.prototype, Symbol.toStringTag,
+ { value: "TransformStream", configurable: true });
+
+ function isReadableStreamObject(value) {
+ if (!isObject(value) || !global.ReadableStream)
+ return false;
+ try {
+ return value instanceof global.ReadableStream;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ function isUncloneableStreamChunk(value) {
+ return isObject(value) &&
+ (isWritableStream(value) ||
+ isReadableStreamObject(value) ||
+ transformState.has(value));
+ }
+
+ function makeDataCloneError() {
+ return makeDOMException("The object could not be cloned.", "DataCloneError");
+ }
+
+ function cloneTransferReasonError(value) {
+ var constructors = [
+ global.EvalError,
+ global.RangeError,
+ global.ReferenceError,
+ global.SyntaxError,
+ global.TypeError,
+ global.URIError,
+ global.Error
+ ];
+
+ for (var i = 0; i < constructors.length; ++i) {
+ var C = constructors[i];
+ if (typeof C !== "function")
+ continue;
+ try {
+ if (!(value instanceof C))
+ continue;
+ } catch (e) {
+ continue;
+ }
+
+ var message = "";
+ var descriptor;
+ try {
+ descriptor = Object.getOwnPropertyDescriptor(value, "message");
+ } catch (e) {
+ descriptor = undefined;
+ }
+ if (descriptor && hasOwn.call(descriptor, "value"))
+ message = String(descriptor.value);
+ return new C(message);
+ }
+
+ return undefined;
+ }
+
+ function cloneTransferReasonValue(value, memory) {
+ if (typeof value === "function" || typeof value === "symbol")
+ throw makeDataCloneError();
+ if (!isObject(value))
+ return value;
+ if (isUncloneableStreamChunk(value))
+ throw makeDataCloneError();
+
+ memory = memory || new WeakMap();
+ if (memory.has(value))
+ return memory.get(value);
+
+ if (typeof global.DOMException === "function") {
+ try {
+ if (value instanceof global.DOMException) {
+ return new global.DOMException(String(value.message),
+ String(value.name));
+ }
+ } catch (e) {}
+ }
+
+ var errorClone = cloneTransferReasonError(value);
+ if (errorClone !== undefined)
+ return errorClone;
+
+ if (typeof global.ArrayBuffer === "function") {
+ try {
+ if (value instanceof global.ArrayBuffer) {
+ var bufferClone = value.slice(0);
+ memory.set(value, bufferClone);
+ return bufferClone;
+ }
+ } catch (e) {}
+ }
+
+ if (global.ArrayBuffer && global.ArrayBuffer.isView &&
+ global.ArrayBuffer.isView(value)) {
+ var clonedBuffer = cloneTransferReasonValue(value.buffer, memory);
+ var viewClone;
+ if (objectToString.call(value) === "[object DataView]") {
+ viewClone = new global.DataView(clonedBuffer, value.byteOffset,
+ value.byteLength);
+ } else {
+ viewClone = new value.constructor(clonedBuffer, value.byteOffset,
+ value.length);
+ }
+ memory.set(value, viewClone);
+ return viewClone;
+ }
+
+ if (typeof global.Date === "function") {
+ try {
+ if (value instanceof global.Date) {
+ var dateClone = new global.Date(value.getTime());
+ memory.set(value, dateClone);
+ return dateClone;
+ }
+ } catch (e) {}
+ }
+
+ if (typeof global.RegExp === "function") {
+ try {
+ if (value instanceof global.RegExp) {
+ var flags = "";
+ flags += value.global ? "g" : "";
+ flags += value.ignoreCase ? "i" : "";
+ flags += value.multiline ? "m" : "";
+ flags += value.unicode ? "u" : "";
+ flags += value.sticky ? "y" : "";
+ var regexpClone = new global.RegExp(value.source, flags);
+ regexpClone.lastIndex = value.lastIndex;
+ memory.set(value, regexpClone);
+ return regexpClone;
+ }
+ } catch (e) {}
+ }
+
+ if (typeof global.Map === "function") {
+ try {
+ if (value instanceof global.Map) {
+ var mapClone = new global.Map();
+ memory.set(value, mapClone);
+ value.forEach(function(v, k) {
+ mapClone.set(cloneTransferReasonValue(k, memory),
+ cloneTransferReasonValue(v, memory));
+ });
+ return mapClone;
+ }
+ } catch (e) {
+ throw e;
+ }
+ }
+
+ if (typeof global.Set === "function") {
+ try {
+ if (value instanceof global.Set) {
+ var setClone = new global.Set();
+ memory.set(value, setClone);
+ value.forEach(function(v) {
+ setClone.add(cloneTransferReasonValue(v, memory));
+ });
+ return setClone;
+ }
+ } catch (e) {
+ throw e;
+ }
+ }
+
+ var clone = Array.isArray(value) ? [] : {};
+ memory.set(value, clone);
+
+ var keys = Object.keys(value);
+ for (var i = 0; i < keys.length; ++i) {
+ var key = keys[i];
+ clone[key] = cloneTransferReasonValue(value[key], memory);
+ }
+
+ return clone;
+ }
+
+ function abortTransferredWritable(record, reason) {
+ try {
+ return record.writer.abort(reason);
+ } catch (e) {
+ return Promise.reject(e);
+ }
+ }
+
+ function rejectTransferredWritableWrite(record, error) {
+ var abortPromise = abortTransferredWritable(record, error);
+ silenceRejection(abortPromise);
+ return Promise.reject(error);
+ }
+
+ function forwardTransferredWritableWrite(record, chunk) {
+ if (isUncloneableStreamChunk(chunk))
+ return rejectTransferredWritableWrite(record, makeDataCloneError());
+
+ var prior = record.tail || Promise.resolve(undefined);
+ var firstWrite = !record.started;
+ record.started = true;
+
+ var forwarding = prior.then(function() {
+ var writePromise;
+ try {
+ writePromise = Promise.resolve(record.writer.write(chunk));
+ } catch (e) {
+ writePromise = Promise.reject(e);
+ }
+ record.inFlight = writePromise;
+ silenceRejection(writePromise);
+ return writePromise;
+ });
+
+ record.tail = forwarding;
+ silenceRejection(forwarding);
+
+ if (firstWrite)
+ return undefined;
+
+ return prior.then(function() { return undefined; });
+ }
+
+ function closeTransferredWritable(record) {
+ var tail = record.tail || Promise.resolve(undefined);
+ var closePromise = tail.then(function() {
+ return record.writer.close();
+ });
+ record.tail = closePromise;
+ silenceRejection(closePromise);
+ return closePromise;
+ }
+
+)JS"
+R"JS(
+
+ function __uxpTransferWritableStream(stream) {
+ if (!isWritableStream(stream))
+ return undefined;
+ if (stream.locked)
+ return null;
+ try {
+ return {
+ writer: stream.getWriter(),
+ started: false,
+ inFlight: undefined,
+ tail: undefined
+ };
+ } catch (e) {
+ return null;
+ }
+ }
+
+ function __uxpReceiveWritableStreamTransfer(record) {
+ return new WritableStream({
+ write: function(chunk) {
+ return forwardTransferredWritableWrite(record, chunk);
+ },
+ close: function() {
+ return closeTransferredWritable(record);
+ },
+ abort: function(reason) {
+ return abortTransferredWritable(record, reason);
+ }
+ });
+ }
+
+ function __uxpTransferReadableStream(stream) {
+ if (!isReadableStreamObject(stream))
+ return undefined;
+ if (stream.locked)
+ return null;
+ try {
+ var record = {
+ reader: stream.getReader(),
+ pendingRead: undefined,
+ pumping: undefined,
+ closed: false,
+ errored: false,
+ canceled: false
+ };
+ ensureReadableTransferRead(record);
+ return record;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ function ensureReadableTransferRead(record) {
+ if (!record.pendingRead && !record.closed && !record.errored &&
+ !record.canceled) {
+ try {
+ record.pendingRead = Promise.resolve(record.reader.read());
+ } catch (e) {
+ record.pendingRead = Promise.reject(e);
+ }
+ silenceRejection(record.pendingRead);
+ }
+ return record.pendingRead;
+ }
+
+ function errorReadableTransfer(record, controller, error) {
+ if (record.canceled || record.closed || record.errored)
+ return;
+ record.errored = true;
+ try { controller.error(error); } catch (e) {}
+ }
+
+ function pumpReadableTransfer(record, controller) {
+ if (record.pumping || record.closed || record.errored || record.canceled)
+ return;
+
+ var pendingRead = ensureReadableTransferRead(record);
+ if (!pendingRead)
+ return;
+
+ record.pumping = pendingRead.then(function(result) {
+ record.pendingRead = undefined;
+ record.pumping = undefined;
+
+ if (record.canceled || record.errored || record.closed)
+ return;
+
+ if (result.done) {
+ record.closed = true;
+ try { controller.close(); } catch (e) {}
+ return;
+ }
+
+ if (isUncloneableStreamChunk(result.value)) {
+ var cloneError = makeDataCloneError();
+ silenceRejection(record.reader.cancel(cloneError));
+ errorReadableTransfer(record, controller, cloneError);
+ return;
+ }
+
+ try {
+ controller.enqueue(result.value);
+ } catch (e) {
+ silenceRejection(record.reader.cancel(e));
+ errorReadableTransfer(record, controller, e);
+ return;
+ }
+
+ if (controller.desiredSize > 0)
+ pumpReadableTransfer(record, controller);
+ }, function(error) {
+ record.pendingRead = undefined;
+ record.pumping = undefined;
+ errorReadableTransfer(record, controller, error);
+ });
+ silenceRejection(record.pumping);
+ }
+
+ function cancelReadableTransfer(record, reason) {
+ record.canceled = true;
+ var clonedReason;
+ try {
+ clonedReason = cloneTransferReasonValue(reason);
+ } catch (e) {
+ return Promise.reject(e);
+ }
+
+ Promise.resolve().then(function() {
+ try {
+ silenceRejection(record.reader.cancel(clonedReason));
+ } catch (e) {}
+ });
+ return undefined;
+ }
+
+ function closeReadableTransferPort(port) {
+ try { port.close(); } catch (e) {}
+ }
+
+ function startReadableTransferPort(port) {
+ try {
+ if (port.start)
+ port.start();
+ } catch (e) {}
+ }
+
+ function tryPostReadableTransferPort(port, message) {
+ try {
+ port.postMessage(message);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+)JS"
+R"JS(
+
+ function postReadableTransferPortError(record, error) {
+ var posted = tryPostReadableTransferPort(record.port,
+ { type: "error", value: error });
+ if (!posted) {
+ posted = tryPostReadableTransferPort(record.port, {
+ type: "error",
+ name: error && error.name,
+ message: error && error.message
+ });
+ }
+ record.closed = true;
+ if (!posted)
+ closeReadableTransferPort(record.port);
+ }
+
+)JS"
+R"JS(
+
+ function requestReadableTransferPortChunk(state) {
+ if (state.closed || state.errored || state.canceled)
+ return;
+ if (!tryPostReadableTransferPort(state.port, { type: "pull" })) {
+ state.errored = true;
+ if (state.controller)
+ try { state.controller.error(makeDataCloneError()); } catch (e) {}
+ closeReadableTransferPort(state.port);
+ }
+ }
+
+ function errorReadableTransferPortState(state, error) {
+ if (state.errored)
+ return;
+ state.errored = true;
+ try {
+ if (state.controller)
+ state.controller.error(error);
+ } catch (e) {}
+ closeReadableTransferPort(state.port);
+ }
+
+ function pumpReadableTransferPort(record) {
+ if (record.reading || record.closed || record.canceled ||
+ record.credits <= 0)
+ return;
+
+ --record.credits;
+ record.reading = true;
+
+ var readPromise;
+ try {
+ readPromise = Promise.resolve(record.reader.read());
+ } catch (e) {
+ readPromise = Promise.reject(e);
+ }
+ silenceRejection(readPromise);
+
+ readPromise.then(function(result) {
+ record.reading = false;
+
+ if (record.canceled || record.closed)
+ return;
+
+ if (result.done) {
+ record.closed = true;
+ if (!tryPostReadableTransferPort(record.port, { type: "close" }))
+ closeReadableTransferPort(record.port);
+ return;
+ }
+
+ if (isUncloneableStreamChunk(result.value)) {
+ var cloneError = makeDataCloneError();
+ silenceRejection(record.reader.cancel(cloneError));
+ postReadableTransferPortError(record, cloneError);
+ return;
+ }
+
+ if (!tryPostReadableTransferPort(record.port,
+ { type: "chunk", value: result.value })) {
+ record.closed = true;
+ closeReadableTransferPort(record.port);
+ return;
+ }
+
+ pumpReadableTransferPort(record);
+ }, function(error) {
+ record.reading = false;
+ if (record.canceled || record.closed)
+ return;
+ postReadableTransferPortError(record, error);
+ });
+ }
+
+ function __uxpTransferReadableStreamPort(stream) {
+
+)JS"
+R"JS(
+ if (!isReadableStreamObject(stream))
+ return undefined;
+ if (stream.locked || typeof global.MessageChannel !== "function")
+ return null;
+
+ var channel;
+ try {
+ channel = new global.MessageChannel();
+ var record = {
+ reader: stream.getReader(),
+ port: channel.port1,
+ reading: false,
+ credits: 1,
+ closed: false,
+ canceled: false
+ };
+
+ record.port.onmessage = function(event) {
+ var message = event.data;
+ if (!message || record.canceled)
+ return;
+
+ if (message.type === "closed") {
+ record.closed = true;
+ closeReadableTransferPort(record.port);
+ return;
+ }
+
+ if (record.closed)
+ return;
+
+ if (message.type === "pull") {
+ ++record.credits;
+ pumpReadableTransferPort(record);
+ return;
+ }
+
+ if (message.type === "cancel") {
+ record.canceled = true;
+ Promise.resolve().then(function() {
+ try {
+ silenceRejection(record.reader.cancel(message.reason));
+ } catch (e) {}
+ });
+ closeReadableTransferPort(record.port);
+ }
+ };
+
+ startReadableTransferPort(record.port);
+ pumpReadableTransferPort(record);
+ return channel.port2;
+ } catch (e) {
+ if (channel) {
+ closeReadableTransferPort(channel.port1);
+ closeReadableTransferPort(channel.port2);
+ }
+ return null;
+ }
+ }
+
+ function errorFromReadableTransferPortMessage(message) {
+ if ("value" in message)
+ return message.value;
+ return makeDOMException(message.message || "ReadableStream transfer failed",
+ message.name || "DataCloneError");
+ }
+
+ function __uxpReceiveReadableStreamTransferFromPort(port) {
+ var state = {
+ port: port,
+ controller: undefined,
+ closed: false,
+ errored: false,
+ canceled: false
+ };
+
+ port.onmessage = function(event) {
+ var message = event.data;
+ if (!message || state.closed || state.errored || state.canceled)
+ return;
+
+ if (message.type === "chunk") {
+ if (isUncloneableStreamChunk(message.value)) {
+ errorReadableTransferPortState(state, makeDataCloneError());
+ return;
+ }
+
+ try {
+ state.controller.enqueue(message.value);
+ } catch (e) {
+ errorReadableTransferPortState(state, e);
+ return;
+ }
+
+ if (state.controller.desiredSize > 0)
+ requestReadableTransferPortChunk(state);
+ return;
+ }
+
+ if (message.type === "close") {
+ state.closed = true;
+ try { state.controller.close(); } catch (e) {}
+ tryPostReadableTransferPort(port, { type: "closed" });
+ closeReadableTransferPort(port);
+ return;
+ }
+
+ if (message.type === "error") {
+ if (!state.errored) {
+ state.errored = true;
+ try {
+ if (state.controller)
+ state.controller.error(
+ errorFromReadableTransferPortMessage(message));
+ } catch (e) {}
+ }
+ tryPostReadableTransferPort(port, { type: "closed" });
+ closeReadableTransferPort(port);
+ }
+ };
+
+ return new ReadableStream({
+ start: function(controller) {
+ state.controller = controller;
+ startReadableTransferPort(port);
+ requestReadableTransferPortChunk(state);
+ },
+ pull: function() {
+ requestReadableTransferPortChunk(state);
+ },
+ cancel: function(reason) {
+ state.canceled = true;
+ try {
+ port.postMessage({ type: "cancel", reason: reason });
+ } catch (e) {
+ closeReadableTransferPort(port);
+ return Promise.reject(e);
+ }
+ closeReadableTransferPort(port);
+ return undefined;
+ }
+ });
+ }
+
+ function __uxpTransferTransformStream(stream) {
+ if (!isObject(stream) || !transformState.has(stream))
+ return undefined;
+
+ var state = transformState.get(stream);
+ if (state.readable.locked || state.writable.locked)
+ return null;
+
+ var readableRecord = __uxpTransferReadableStream(state.readable);
+ if (!readableRecord)
+ return null;
+
+ var writableRecord = __uxpTransferWritableStream(state.writable);
+ if (!writableRecord)
+ return null;
+
+ return {
+ readable: readableRecord,
+ writable: writableRecord
+ };
+ }
+
+ function __uxpReceiveTransformStreamTransfer(record) {
+ var stream = Object.create(global.TransformStream.prototype);
+ transformState.set(stream, {
+ readable: __uxpReceiveReadableStreamTransfer(record.readable),
+ writable: __uxpReceiveWritableStreamTransfer(record.writable),
+ controller: undefined
+ });
+ return stream;
+ }
+
+ function __uxpReceiveReadableStreamTransfer(record) {
+ return new ReadableStream({
+ start: function(controller) {
+ pumpReadableTransfer(record, controller);
+ },
+ pull: function(controller) {
+ pumpReadableTransfer(record, controller);
+ },
+ cancel: function(reason) {
+ return cancelReadableTransfer(record, reason);
+ }
+ });
+ }
+
+ function pipeTo(readable, destination, options) {
+ options = options === undefined ? {} : requiredObject(options, "options");
+ var signal = options.signal;
+ var preventClose = Boolean(options.preventClose);
+ var preventAbort = Boolean(options.preventAbort);
+ var preventCancel = Boolean(options.preventCancel);
+ var reader;
+ var writer;
+
+ try {
+ destination = requiredObject(destination, "destination");
+ reader = readable.getReader();
+ writer = destination.getWriter();
+ } catch (e) {
+ return rejectPromise(e);
+ }
+
+ var closed = false;
+ function cleanup() {
+ if (closed)
+ return;
+ closed = true;
+ releaseReaderLock(reader);
+ releaseWriterLock(writer);
+ if (signal && signal.removeEventListener)
+ signal.removeEventListener("abort", onAbort);
+ }
+
+ function abortReason() {
+ return signal && "reason" in signal
+ ? signal.reason
+ : makeDOMException("The operation was aborted.", "AbortError");
+ }
+
+ function shutdownWithAction(action, originalError) {
+ return action().then(function() {
+ cleanup();
+ throw originalError;
+ }, function(actionError) {
+ cleanup();
+ throw actionError;
+ });
+ }
+
+ function onAbort() {}
+
+ var pipePromise = new Promise(function(resolve, reject) {
+ onAbort = function() {
+ var reason = abortReason();
+ var actions = [];
+ if (!preventAbort)
+ actions.push(writer.abort(reason));
+ if (!preventCancel)
+ actions.push(reader.cancel(reason));
+ Promise.all(actions).then(function() {
+ cleanup();
+ reject(reason);
+ }, function(error) {
+ cleanup();
+ reject(error);
+ });
+ };
+
+ if (signal && signal.aborted) {
+ onAbort();
+ return;
+ }
+ if (signal && signal.addEventListener)
+ signal.addEventListener("abort", onAbort);
+
+ function pump() {
+ if (closed)
+ return;
+ reader.read().then(function(result) {
+ if (closed)
+ return;
+ if (result.done) {
+ var closePromise = preventClose ? Promise.resolve(undefined)
+ : writer.close();
+ closePromise.then(function() {
+ cleanup();
+ resolve(undefined);
+ }, function(error) {
+ cleanup();
+ reject(error);
+ });
+ return;
+ }
+
+ writer.write(result.value).then(pump, function(error) {
+ if (closed)
+ return;
+ var cancel = preventCancel
+ ? Promise.resolve(undefined)
+ : reader.cancel(error);
+ cancel.then(function() {
+ cleanup();
+ reject(error);
+ }, function(cancelError) {
+ cleanup();
+ reject(cancelError);
+ });
+ });
+ }, function(error) {
+ if (closed)
+ return;
+ var abort = preventAbort
+ ? Promise.resolve(undefined)
+ : writer.abort(error);
+ abort.then(function() {
+ cleanup();
+ reject(error);
+ }, function(abortError) {
+ cleanup();
+ reject(abortError);
+ });
+ });
+ }
+
+ pump();
+ });
+
+ return pipePromise;
+ }
+
+ function pipeThrough(readable, pair, options) {
+ pair = requiredObject(pair, "transform");
+ var writable = pair.writable;
+ var output = pair.readable;
+ if (readable.locked)
+ throw new TypeError("ReadableStream is locked");
+ if (!isWritableStream(writable))
+ throw new TypeError("transform.writable must be a WritableStream");
+ if (!(output instanceof global.ReadableStream))
+ throw new TypeError("transform.readable must be a ReadableStream");
+ if (writable.locked)
+ throw new TypeError("WritableStream is locked");
+ readable.pipeTo(writable, options).catch(function() {});
+ return output;
+ }
+
+ Object.defineProperty(global, "__uxpStreamsPipeTo",
+ { value: pipeTo, configurable: true });
+ Object.defineProperty(global, "__uxpStreamsPipeThrough",
+ { value: pipeThrough, configurable: true });
+ Object.defineProperty(global, "__uxpTransferWritableStream",
+ { value: __uxpTransferWritableStream,
+ configurable: true });
+ Object.defineProperty(global, "__uxpReceiveWritableStreamTransfer",
+ { value: __uxpReceiveWritableStreamTransfer,
+ configurable: true });
+ Object.defineProperty(global, "__uxpTransferReadableStream",
+ { value: __uxpTransferReadableStream,
+ configurable: true });
+ Object.defineProperty(global, "__uxpReceiveReadableStreamTransfer",
+ { value: __uxpReceiveReadableStreamTransfer,
+ configurable: true });
+ Object.defineProperty(global, "__uxpTransferReadableStreamPort",
+ { value: __uxpTransferReadableStreamPort,
+ configurable: true });
+ Object.defineProperty(global, "__uxpReceiveReadableStreamTransferFromPort",
+ { value: __uxpReceiveReadableStreamTransferFromPort,
+ configurable: true });
+ Object.defineProperty(global, "__uxpTransferTransformStream",
+ { value: __uxpTransferTransformStream,
+ configurable: true });
+ Object.defineProperty(global, "__uxpReceiveTransformStreamTransfer",
+ { value: __uxpReceiveTransformStreamTransfer,
+ configurable: true });
+
+ if (global.ReadableStream && !global.ReadableStream.from) {
+ Object.defineProperty(global.ReadableStream, "from", {
+ value: function from(asyncIterable) {
+ if (asyncIterable == null)
+ throw new TypeError("ReadableStream.from requires an iterable");
+ var method = asyncIterable[Symbol.asyncIterator] ||
+ asyncIterable[Symbol.iterator];
+ if (typeof method !== "function")
+ throw new TypeError("ReadableStream.from requires an iterable");
+ var iterator = method.call(asyncIterable);
+ return new ReadableStream({
+ pull: function(controller) {
+ return Promise.resolve(iterator.next()).then(function(result) {
+ if (!isObject(result))
+ throw new TypeError("Iterator result is not an object");
+ if (result.done) {
+ controller.close();
+ } else {
+ controller.enqueue(result.value);
+ }
+ });
+ },
+ cancel: function(reason) {
+ var returnMethod = iterator.return;
+ if (returnMethod)
+ return returnMethod.call(iterator, reason);
+ return undefined;
+ }
+ });
+ },
+ writable: true,
+ configurable: true
+ });
+ }
+
+ if (global.ReadableStream && typeof Symbol === "function" &&
+ Symbol.asyncIterator && !global.ReadableStream.prototype.values) {
+ function ReadableStreamAsyncIterator(stream, preventCancel) {
+ this._reader = stream.getReader();
+ this._preventCancel = preventCancel;
+ this._done = false;
+ }
+
+ ReadableStreamAsyncIterator.prototype.next = function() {
+ if (this._done)
+ return Promise.resolve({ value: undefined, done: true });
+ var self = this;
+ return this._reader.read().then(function(result) {
+ if (result.done) {
+ self._done = true;
+ releaseReaderLock(self._reader);
+ }
+ return result;
+ }, function(error) {
+ self._done = true;
+ releaseReaderLock(self._reader);
+ throw error;
+ });
+ };
+
+ ReadableStreamAsyncIterator.prototype.return = function(value) {
+ if (this._done)
+ return Promise.resolve({ value: value, done: true });
+ this._done = true;
+ var reader = this._reader;
+ if (this._preventCancel) {
+ releaseReaderLock(reader);
+ return Promise.resolve({ value: value, done: true });
+ }
+ return reader.cancel(value).then(function() {
+ releaseReaderLock(reader);
+ return { value: value, done: true };
+ }, function(error) {
+ releaseReaderLock(reader);
+ throw error;
+ });
+ };
+
+ ReadableStreamAsyncIterator.prototype[Symbol.asyncIterator] = function() {
+ return this;
+ };
+
+ Object.defineProperty(global.ReadableStream.prototype, "values", {
+ value: function values(options) {
+ var preventCancel = Boolean(options && options.preventCancel);
+ return new ReadableStreamAsyncIterator(this, preventCancel);
+ },
+ writable: true,
+ configurable: true
+ });
+
+ Object.defineProperty(global.ReadableStream.prototype, Symbol.asyncIterator, {
+ value: global.ReadableStream.prototype.values,
+ writable: true,
+ configurable: true
+ });
+ }
+
+ )JS"
+ R"JS(
+
+ var textEncoderStreamState = new WeakMap();
+ var textDecoderStreamState = new WeakMap();
+
+ function enqueueEncoded(controller, encoder, string) {
+ if (string.length === 0)
+ return;
+ var chunk = encoder.encode(string);
+ if (chunk.byteLength)
+ controller.enqueue(chunk);
+ }
+
+ function TextEncoderStream() {
+ if (!(this instanceof TextEncoderStream))
+ throw new TypeError("TextEncoderStream must be constructed with new");
+
+ var encoder = new TextEncoder();
+ var leadingSurrogate = null;
+ var transform = new TransformStream({
+ transform: function(chunk, controller) {
+ var input = String(chunk);
+ var output = "";
+ var i = 0;
+
+ if (leadingSurrogate !== null) {
+ if (input.length > 0) {
+ var first = input.charCodeAt(0);
+ if (first >= 0xDC00 && first <= 0xDFFF) {
+ output += String.fromCharCode(leadingSurrogate) + input.charAt(0);
+ i = 1;
+ } else {
+ output += "\uFFFD";
+ }
+ } else {
+ return;
+ }
+ leadingSurrogate = null;
+ }
+
+ for (; i < input.length; ++i) {
+ var code = input.charCodeAt(i);
+ if (code >= 0xD800 && code <= 0xDBFF) {
+ if (i + 1 < input.length) {
+ var next = input.charCodeAt(i + 1);
+ if (next >= 0xDC00 && next <= 0xDFFF) {
+ output += input.charAt(i) + input.charAt(i + 1);
+ ++i;
+ } else {
+ output += "\uFFFD";
+ }
+ } else {
+ leadingSurrogate = code;
+ break;
+ }
+ } else if (code >= 0xDC00 && code <= 0xDFFF) {
+ output += "\uFFFD";
+ } else {
+ output += input.charAt(i);
+ }
+ }
+
+ enqueueEncoded(controller, encoder, output);
+ },
+ flush: function(controller) {
+ if (leadingSurrogate !== null) {
+ leadingSurrogate = null;
+ enqueueEncoded(controller, encoder, "\uFFFD");
+ }
+ }
+ });
+
+ textEncoderStreamState.set(this, { transform: transform });
+ }
+
+ Object.defineProperty(TextEncoderStream.prototype, "encoding", {
+ configurable: true,
+ get: function() {
+ if (!textEncoderStreamState.has(this))
+ throw new TypeError("TextEncoderStream expected");
+ return "utf-8";
+ }
+ });
+
+ Object.defineProperty(TextEncoderStream.prototype, "readable", {
+ configurable: true,
+ get: function() {
+ var state = textEncoderStreamState.get(this);
+ if (!state)
+ throw new TypeError("TextEncoderStream expected");
+ return state.transform.readable;
+ }
+ });
+
+ Object.defineProperty(TextEncoderStream.prototype, "writable", {
+ configurable: true,
+ get: function() {
+ var state = textEncoderStreamState.get(this);
+ if (!state)
+ throw new TypeError("TextEncoderStream expected");
+ return state.transform.writable;
+ }
+ });
+
+ Object.defineProperty(TextEncoderStream.prototype, Symbol.toStringTag,
+ { value: "TextEncoderStream", configurable: true });
+
+ function TextDecoderStream(label, options) {
+ if (!(this instanceof TextDecoderStream))
+ throw new TypeError("TextDecoderStream must be constructed with new");
+ label = label === undefined ? "utf-8" : label;
+ options = options === undefined ? {} : options;
+
+ var decoder = new TextDecoder(label, options);
+ var transform = new TransformStream({
+ transform: function(chunk, controller) {
+ var output = decoder.decode(chunk, { stream: true });
+ if (output.length)
+ controller.enqueue(output);
+ },
+ flush: function(controller) {
+ var output = decoder.decode();
+ if (output.length)
+ controller.enqueue(output);
+ }
+ });
+
+ textDecoderStreamState.set(this, {
+ transform: transform,
+ encoding: decoder.encoding,
+ fatal: decoder.fatal,
+ ignoreBOM: Boolean(options && options.ignoreBOM)
+ });
+ }
+
+ Object.defineProperty(TextDecoderStream.prototype, "encoding", {
+ configurable: true,
+ get: function() {
+ var state = textDecoderStreamState.get(this);
+ if (!state)
+ throw new TypeError("TextDecoderStream expected");
+ return state.encoding;
+ }
+ });
+
+ Object.defineProperty(TextDecoderStream.prototype, "fatal", {
+ configurable: true,
+ get: function() {
+ var state = textDecoderStreamState.get(this);
+ if (!state)
+ throw new TypeError("TextDecoderStream expected");
+ return state.fatal;
+ }
+ });
+
+ Object.defineProperty(TextDecoderStream.prototype, "ignoreBOM", {
+ configurable: true,
+ get: function() {
+ var state = textDecoderStreamState.get(this);
+ if (!state)
+ throw new TypeError("TextDecoderStream expected");
+ return state.ignoreBOM;
+ }
+ });
+
+ Object.defineProperty(TextDecoderStream.prototype, "readable", {
+ configurable: true,
+ get: function() {
+ var state = textDecoderStreamState.get(this);
+ if (!state)
+ throw new TypeError("TextDecoderStream expected");
+ return state.transform.readable;
+ }
+ });
+
+ Object.defineProperty(TextDecoderStream.prototype, "writable", {
+ configurable: true,
+ get: function() {
+ var state = textDecoderStreamState.get(this);
+ if (!state)
+ throw new TypeError("TextDecoderStream expected");
+ return state.transform.writable;
+ }
+ });
+
+ Object.defineProperty(TextDecoderStream.prototype, Symbol.toStringTag,
+ { value: "TextDecoderStream", configurable: true });
+
+ var compressionStreamState = new WeakMap();
+ var decompressionStreamState = new WeakMap();
+
+ function enqueueCompressionChunks(nativeContext, chunk, finish, controller) {
+ var chunks = global.__uxpCompressionProcess(nativeContext, chunk, finish);
+ for (var i = 0; i < chunks.length; ++i)
+ controller.enqueue(chunks[i]);
+ }
+
+ function CompressionStream(format) {
+ if (!(this instanceof CompressionStream))
+ throw new TypeError("CompressionStream must be constructed with new");
+ var nativeContext = global.__uxpCreateCompressionContext(format, true);
+ var transform = new TransformStream({
+ transform: function(chunk, controller) {
+ enqueueCompressionChunks(nativeContext, chunk, false, controller);
+ },
+ flush: function(controller) {
+ enqueueCompressionChunks(nativeContext, undefined, true, controller);
+ }
+ });
+ compressionStreamState.set(this, { transform: transform });
+ }
+
+ Object.defineProperty(CompressionStream.prototype, "readable", {
+ configurable: true,
+ get: function() {
+ var state = compressionStreamState.get(this);
+ if (!state)
+ throw new TypeError("CompressionStream expected");
+ return state.transform.readable;
+ }
+ });
+
+ Object.defineProperty(CompressionStream.prototype, "writable", {
+ configurable: true,
+ get: function() {
+ var state = compressionStreamState.get(this);
+ if (!state)
+ throw new TypeError("CompressionStream expected");
+ return state.transform.writable;
+ }
+ });
+
+ Object.defineProperty(CompressionStream.prototype, Symbol.toStringTag,
+ { value: "CompressionStream", configurable: true });
+
+ function DecompressionStream(format) {
+ if (!(this instanceof DecompressionStream))
+ throw new TypeError("DecompressionStream must be constructed with new");
+ var nativeContext = global.__uxpCreateCompressionContext(format, false);
+ var transform = new TransformStream({
+ transform: function(chunk, controller) {
+ enqueueCompressionChunks(nativeContext, chunk, false, controller);
+ },
+ flush: function(controller) {
+ enqueueCompressionChunks(nativeContext, undefined, true, controller);
+ }
+ });
+ decompressionStreamState.set(this, { transform: transform });
+ }
+
+ Object.defineProperty(DecompressionStream.prototype, "readable", {
+ configurable: true,
+ get: function() {
+ var state = decompressionStreamState.get(this);
+ if (!state)
+ throw new TypeError("DecompressionStream expected");
+ return state.transform.readable;
+ }
+ });
+
+ Object.defineProperty(DecompressionStream.prototype, "writable", {
+ configurable: true,
+ get: function() {
+ var state = decompressionStreamState.get(this);
+ if (!state)
+ throw new TypeError("DecompressionStream expected");
+ return state.transform.writable;
+ }
+ });
+
+ Object.defineProperty(DecompressionStream.prototype, Symbol.toStringTag,
+ { value: "DecompressionStream", configurable: true });
+
+ define("WritableStream", WritableStream);
+ define("WritableStreamDefaultWriter", WritableStreamDefaultWriter);
+ define("WritableStreamDefaultController", WritableStreamDefaultController);
+ define("TransformStream", TransformStream);
+ define("TransformStreamDefaultController", TransformStreamDefaultController);
+ define("TextEncoderStream", TextEncoderStream);
+ define("TextDecoderStream", TextDecoderStream);
+ define("CompressionStream", CompressionStream);
+ define("DecompressionStream", DecompressionStream);
+})(this);
+)JS";
+
+ RootedValue rval(cx);
+ JS::CompileOptions options(cx);
+ options.setFileAndLine("builtin/StreamExtras.js", 1);
+ options.setNoScriptRval(true);
+ return JS::Evaluate(cx, options, source, strlen(source), &rval);
+}
diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h
index 0726e78361..50bd7a52a1 100644
--- a/js/src/builtin/Stream.h
+++ b/js/src/builtin/Stream.h
@@ -165,6 +165,8 @@ class CountQueuingStrategy : public NativeObject
static const Class protoClass_;
};
+[[nodiscard]] bool InitStreamExtras(JSContext* cx, HandleObject global);
+
} // namespace js
#endif /* builtin_Stream_h */
diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js
index e1c32482ae..a83a06ad2b 100644
--- a/js/src/builtin/String.js
+++ b/js/src/builtin/String.js
@@ -654,6 +654,82 @@ function String_repeat(count) {
return T;
}
+// ES2024
+// String.prototype.isWellFormed ( )
+function String_isWellFormed() {
+ // Steps 1-2.
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+
+ // Step 3.
+ var length = S.length;
+ for (var k = 0; k < length; k++) {
+ var c = callFunction(std_String_charCodeAt, S, k);
+ if (c >= 0xD800 && c <= 0xDBFF) {
+ if (k + 1 >= length)
+ return false;
+
+ var d = callFunction(std_String_charCodeAt, S, k + 1);
+ if (d < 0xDC00 || d > 0xDFFF)
+ return false;
+
+ k++;
+ } else if (c >= 0xDC00 && c <= 0xDFFF) {
+ return false;
+ }
+ }
+
+ // Step 4.
+ return true;
+}
+
+// ES2024
+// String.prototype.toWellFormed ( )
+function String_toWellFormed() {
+ // Steps 1-2.
+ RequireObjectCoercible(this);
+ var S = ToString(this);
+
+ // Step 3.
+ var length = S.length;
+ var result = "";
+ var copied = 0;
+
+ for (var k = 0; k < length; k++) {
+ var c = callFunction(std_String_charCodeAt, S, k);
+ var isUnpairedSurrogate = false;
+
+ if (c >= 0xD800 && c <= 0xDBFF) {
+ if (k + 1 < length) {
+ var d = callFunction(std_String_charCodeAt, S, k + 1);
+ if (d >= 0xDC00 && d <= 0xDFFF) {
+ k++;
+ continue;
+ }
+ }
+ isUnpairedSurrogate = true;
+ } else if (c >= 0xDC00 && c <= 0xDFFF) {
+ isUnpairedSurrogate = true;
+ }
+
+ if (isUnpairedSurrogate) {
+ if (copied < k)
+ result += callFunction(String_substring, S, copied, k);
+ result += "\uFFFD";
+ copied = k + 1;
+ }
+ }
+
+ if (copied === 0)
+ return S;
+
+ if (copied < length)
+ result += callFunction(String_substring, S, copied, length);
+
+ // Step 4.
+ return result;
+}
+
// ES6 draft specification, section 21.1.3.27, version 2013-09-27.
function String_iterator() {
RequireObjectCoercible(this);
diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js
index c831b74614..4311f27079 100644
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -37,10 +37,25 @@ function TypedArrayLengthMethod() {
return TypedArrayLength(this);
}
+function TypedArrayContentTypeIsBigIntMethod() {
+ return IsBigInt64TypedArray(this) || IsBigUint64TypedArray(this);
+}
+
+function ThrowIfTypedArrayOutOfBounds(tarray) {
+ if (TypedArrayIsOutOfBounds(tarray))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
+}
+
+function ThrowIfPossiblyWrappedTypedArrayOutOfBounds(tarray) {
+ if (PossiblyWrappedTypedArrayIsOutOfBounds(tarray))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
+}
+
function GetAttachedArrayBuffer(tarray) {
var buffer = ViewedArrayBufferIfReified(tarray);
if (IsDetachedBuffer(buffer))
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ ThrowIfTypedArrayOutOfBounds(tarray);
return buffer;
}
@@ -60,6 +75,13 @@ function IsTypedArrayEnsuringArrayBuffer(arg) {
return true;
}
+ if (IsObject(arg) && IsPossiblyWrappedTypedArray(arg)) {
+ if (PossiblyWrappedTypedArrayHasDetachedBuffer(arg))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ ThrowIfPossiblyWrappedTypedArrayOutOfBounds(arg);
+ return false;
+ }
+
callFunction(CallTypedArrayMethodIfWrapped, arg, "GetAttachedArrayBufferMethod");
return false;
}
@@ -79,6 +101,7 @@ function ValidateTypedArray(obj, error) {
if (IsPossiblyWrappedTypedArray(obj)) {
if (PossiblyWrappedTypedArrayHasDetachedBuffer(obj))
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ ThrowIfPossiblyWrappedTypedArrayOutOfBounds(obj);
return false;
}
}
@@ -1024,12 +1047,18 @@ function TypedArraySet(overloaded, offset = 0) {
// Steps 9-10.
var targetBuffer = GetAttachedArrayBuffer(target);
+ ThrowIfTypedArrayOutOfBounds(target);
+
// Step 11.
var targetLength = TypedArrayLength(target);
// Steps 12 et seq.
- if (IsPossiblyWrappedTypedArray(overloaded))
+ if (IsPossiblyWrappedTypedArray(overloaded)) {
+ if (PossiblyWrappedTypedArrayHasDetachedBuffer(overloaded))
+ ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
+ ThrowIfPossiblyWrappedTypedArrayOutOfBounds(overloaded);
return SetFromTypedArray(target, overloaded, targetOffset, targetLength);
+ }
return SetFromNonTypedArray(target, overloaded, targetOffset, targetLength, targetBuffer);
}
@@ -1366,6 +1395,8 @@ function TypedArraySubarray(begin, end) {
"TypedArraySubarray");
}
+ GetAttachedArrayBuffer(obj);
+
// Steps 4-6.
var buffer = TypedArrayBuffer(obj);
var srcLength = TypedArrayLength(obj);
@@ -1846,7 +1877,10 @@ function ArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
// Steps 19-21.
- ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
+ var currentLen = ArrayBufferByteLength(O);
+ var copyLen = first >= currentLen ? 0 : std_Math_min(newLen, currentLen - first);
+ if (copyLen > 0)
+ ArrayBufferCopyData(new_, 0, O, first | 0, copyLen | 0, isWrapped);
// Step 22.
return new_;
diff --git a/js/src/builtin/WeakMapObject.cpp b/js/src/builtin/WeakMapObject.cpp
index 82b6ef1a7c..d68b9e05fd 100644
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -8,6 +8,7 @@
#include "jsapi.h"
#include "jscntxt.h"
+#include "builtin/WeakRefObject.h"
#include "vm/SelfHosting.h"
#include "vm/Interpreter-inl.h"
@@ -21,21 +22,80 @@ IsWeakMap(HandleValue v)
return v.isObject() && v.toObject().is();
}
+struct WeakMapObject::Data
+{
+ ObjectValueMap objectMap;
+ SymbolValueMap symbolMap;
+
+ Data(JSContext* cx, JSObject* owner)
+ : objectMap(cx, owner),
+ symbolMap(cx, owner)
+ {}
+
+ bool init() {
+ return objectMap.init() && symbolMap.init();
+ }
+};
+
+ObjectValueMap*
+WeakMapObject::getMap()
+{
+ Data* data = getData();
+ return data ? &data->objectMap : nullptr;
+}
+
+SymbolValueMap*
+WeakMapObject::getSymbolMap()
+{
+ Data* data = getData();
+ return data ? &data->symbolMap : nullptr;
+}
+
+static bool
+EnsureWeakMapData(JSContext* cx, Handle mapObj)
+{
+ if (mapObj->getData())
+ return true;
+
+ auto data = cx->make_unique(cx, mapObj.get());
+ if (!data)
+ return false;
+
+ if (!data->init()) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+
+ mapObj->setPrivate(data.release());
+ return true;
+}
+
MOZ_ALWAYS_INLINE bool
WeakMap_has_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
- if (!args.get(0).isObject()) {
+ if (!CanBeHeldWeakly(args.get(0))) {
args.rval().setBoolean(false);
return true;
}
- if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) {
- JSObject* key = &args[0].toObject();
- if (map->has(key)) {
- args.rval().setBoolean(true);
- return true;
+ WeakMapObject& weakMap = args.thisv().toObject().as();
+ if (args.get(0).isObject()) {
+ if (ObjectValueMap* map = weakMap.getMap()) {
+ JSObject* key = &args[0].toObject();
+ if (map->has(key)) {
+ args.rval().setBoolean(true);
+ return true;
+ }
+ }
+ } else {
+ if (SymbolValueMap* map = weakMap.getSymbolMap()) {
+ JS::Symbol* key = args[0].toSymbol();
+ if (map->has(key)) {
+ args.rval().setBoolean(true);
+ return true;
+ }
}
}
@@ -55,16 +115,27 @@ WeakMap_get_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
- if (!args.get(0).isObject()) {
+ if (!CanBeHeldWeakly(args.get(0))) {
args.rval().setUndefined();
return true;
}
- if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) {
- JSObject* key = &args[0].toObject();
- if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
- args.rval().set(ptr->value());
- return true;
+ WeakMapObject& weakMap = args.thisv().toObject().as();
+ if (args.get(0).isObject()) {
+ if (ObjectValueMap* map = weakMap.getMap()) {
+ JSObject* key = &args[0].toObject();
+ if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
+ args.rval().set(ptr->value());
+ return true;
+ }
+ }
+ } else {
+ if (SymbolValueMap* map = weakMap.getSymbolMap()) {
+ JS::Symbol* key = args[0].toSymbol();
+ if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
+ args.rval().set(ptr->value());
+ return true;
+ }
}
}
@@ -84,17 +155,29 @@ WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakMap(args.thisv()));
- if (!args.get(0).isObject()) {
+ if (!CanBeHeldWeakly(args.get(0))) {
args.rval().setBoolean(false);
return true;
}
- if (ObjectValueMap* map = args.thisv().toObject().as().getMap()) {
- JSObject* key = &args[0].toObject();
- if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
- map->remove(ptr);
- args.rval().setBoolean(true);
- return true;
+ WeakMapObject& weakMap = args.thisv().toObject().as();
+ if (args.get(0).isObject()) {
+ if (ObjectValueMap* map = weakMap.getMap()) {
+ JSObject* key = &args[0].toObject();
+ if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
+ map->remove(ptr);
+ args.rval().setBoolean(true);
+ return true;
+ }
+ }
+ } else {
+ if (SymbolValueMap* map = weakMap.getSymbolMap()) {
+ JS::Symbol* key = args[0].toSymbol();
+ if (SymbolValueMap::Ptr ptr = map->lookup(key)) {
+ map->remove(ptr);
+ args.rval().setBoolean(true);
+ return true;
+ }
}
}
@@ -126,22 +209,13 @@ TryPreserveReflector(JSContext* cx, HandleObject obj)
return true;
}
-static MOZ_ALWAYS_INLINE bool
-SetWeakMapEntryInternal(JSContext* cx, Handle mapObj,
- HandleObject key, HandleValue value)
+static bool
+SetWeakMapObjectEntryInternal(JSContext* cx, Handle mapObj,
+ HandleObject key, HandleValue value)
{
+ if (!EnsureWeakMapData(cx, mapObj))
+ return false;
ObjectValueMap* map = mapObj->getMap();
- if (!map) {
- auto newMap = cx->make_unique(cx, mapObj.get());
- if (!newMap)
- return false;
- if (!newMap->init()) {
- JS_ReportOutOfMemory(cx);
- return false;
- }
- map = newMap.release();
- mapObj->setPrivate(map);
- }
// Preserve wrapped native keys to prevent wrapper optimization.
if (!TryPreserveReflector(cx, key))
@@ -162,13 +236,28 @@ SetWeakMapEntryInternal(JSContext* cx, Handle mapObj,
return true;
}
-MOZ_ALWAYS_INLINE bool
-WeakMap_set_impl(JSContext* cx, const CallArgs& args)
+static bool
+SetWeakMapSymbolEntryInternal(JSContext* cx, Handle mapObj,
+ Handle key, HandleValue value)
{
- MOZ_ASSERT(IsWeakMap(args.thisv()));
+ if (!EnsureWeakMapData(cx, mapObj))
+ return false;
+ SymbolValueMap* map = mapObj->getSymbolMap();
- if (!args.get(0).isObject()) {
- UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
+ MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
+ if (!map->put(key, value)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ return true;
+}
+
+static MOZ_ALWAYS_INLINE bool
+SetWeakMapEntryInternal(JSContext* cx, Handle mapObj,
+ HandleValue key, HandleValue value)
+{
+ if (!CanBeHeldWeakly(key)) {
+ UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, key, nullptr);
if (!bytes)
return false;
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
@@ -176,11 +265,24 @@ WeakMap_set_impl(JSContext* cx, const CallArgs& args)
return false;
}
- RootedObject key(cx, &args[0].toObject());
+ if (key.isObject()) {
+ RootedObject objectKey(cx, &key.toObject());
+ return SetWeakMapObjectEntryInternal(cx, mapObj, objectKey, value);
+ }
+
+ Rooted symbolKey(cx, key.toSymbol());
+ return SetWeakMapSymbolEntryInternal(cx, mapObj, symbolKey, value);
+}
+
+MOZ_ALWAYS_INLINE bool
+WeakMap_set_impl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsWeakMap(args.thisv()));
+
Rooted thisObj(cx, &args.thisv().toObject());
Rooted map(cx, &thisObj->as());
- if (!SetWeakMapEntryInternal(cx, map, key, args.get(1)))
+ if (!SetWeakMapEntryInternal(cx, map, args.get(0), args.get(1)))
return false;
args.rval().set(args.thisv());
return true;
@@ -193,6 +295,13 @@ js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod