Compare commits

..

43 commits

Author SHA1 Message Date
wuggy
be7e2afbcf accept any MIME type for a style sheet to match modern browsers 2026-08-07 20:27:22 -07:00
wuggy
adbd77d7eb Fix Next.js sites like Grok (Grok is still broken but less broken) 2026-08-07 19:42:39 -07:00
wuggy
cd6be07458 Revert "Optimise WebExtension implementaion pt.1"
This reverts commit 3be08bc94b.
2026-08-07 13:13:43 -07:00
wuggy
3be08bc94b Optimise WebExtension implementaion pt.1 2026-08-07 13:09:19 -07:00
wuggy
fe26390483 enable async pan/zoom (Issue #29 @ lun3r) 2026-08-06 17:32:38 -07:00
wuggy
34040dee89 Fix compile error 2026-08-06 17:00:28 -07:00
Basilisk-Dev
c835378c6e Issue MoonchildProductions/UXP#2306: Fix spacing 2026-08-06 16:03:21 -07:00
Basilisk-Dev
3cf82e2ca1 Issue MoonchildProductions/UXP#2306: Add pref-gated WeakRef with safe stub when disabled
* Add javascript.options.weakrefs and plumb it through context options, XPConnect, and workers
* Keep referents alive via strong tracing when the pref is off so deref() still returns the target
* Retain weak-edge semantics when the pref is enabled
2026-08-06 16:03:20 -07:00
Basilisk-Dev
2a6ec52f2f Issue MoonchildProductions/UXP#2306: Implement WeakRef functionality 2026-08-06 16:03:20 -07:00
Basilisk-Dev
b44a606987 Fix ArrayBuffer storage and error types 2026-08-06 15:35:03 -07:00
Basilisk-Dev
3e05786b77 Fix ArrayBuffer slice after resizable source shrink 2026-08-06 15:35:03 -07:00
Basilisk-Dev
d8e52b4fce Validate typed array set and constructors on resizable buffers 2026-08-06 15:35:02 -07:00
Basilisk-Dev
6c6023cac9 Validate typed array methods on resizable buffers 2026-08-06 15:34:59 -07:00
Basilisk-Dev
3751321a0e Fix resizable DataView out-of-bounds semantics 2026-08-06 15:34:43 -07:00
Basilisk-Dev
93085352cd Support BigInt Atomics waiters 2026-08-06 15:34:43 -07:00
Basilisk-Dev
b16be96044 Support DataView on shared array buffers 2026-08-06 15:34:42 -07:00
Basilisk-Dev
dece5e87c7 Guard typed array JIT paths for resizable buffers 2026-08-06 15:34:39 -07:00
Basilisk-Dev
2e2d399564 Implement resizable buffer view semantics 2026-08-06 15:34:22 -07:00
Basilisk-Dev
f20f604ce4 Implement Atomics.waitAsync 2026-08-06 15:34:18 -07:00
Basilisk-Dev
4d11dcb096 Implement growable SharedArrayBuffer 2026-08-06 15:33:59 -07:00
Basilisk-Dev
f92b3d152e Allow symbols as weak collection keys 2026-08-06 15:33:59 -07:00
Basilisk-Dev
e886e4c1db Implement ES2024 ArrayBuffer transfer APIs 2026-08-06 15:33:51 -07:00
Basilisk-Dev
1e463a1d16 Implement ES2024 grouping and resolver builtins 2026-08-06 15:32:54 -07:00
Basilisk-Dev
0d06f59dc1 Fix JS shell module hook build 2026-08-06 15:32:33 -07:00
ownedbywuigi
bcff9710c8 Fix MSVC builderr pt 2 2026-08-06 14:05:51 -07:00
ownedbywuigi
89b3948962 Fix MSVC builderr on Stream.cpp 2026-08-06 14:05:40 -07:00
Basilisk-Dev
9ea6c8f278 Acknowledge port-backed readable close 2026-08-06 14:05:10 -07:00
Basilisk-Dev
563735e432 Do not wait for transferred readable cancel 2026-08-06 14:05:09 -07:00
Basilisk-Dev
a6b18bcd14 Fix stream transfer over message ports 2026-08-06 14:05:09 -07:00
Basilisk-Dev
7d8bf841ed Support readable stream transfer to workers 2026-08-06 14:05:09 -07:00
Basilisk-Dev
f9e1af95c2 Fix readable stream transfer queueing 2026-08-06 14:05:08 -07:00
Basilisk-Dev
58e0e6a192 Add same-thread stream transfer support 2026-08-06 14:05:08 -07:00
Basilisk-Dev
8c4ece641e Tighten stream helper interface realms 2026-08-06 14:05:08 -07:00
Basilisk-Dev
8c6e09601c Fix stream cross-realm globals 2026-08-06 14:05:04 -07:00
Basilisk-Dev
29056ef112 Silently release internal stream readers 2026-08-06 14:04:43 -07:00
Basilisk-Dev
c7f575cdbe Suppress internal stream release rejections 2026-08-06 14:04:43 -07:00
Basilisk-Dev
abeed80755 Install stream extras lazily 2026-08-06 14:04:42 -07:00
Basilisk-Dev
23756fca47 Guard missing process global script load 2026-08-06 14:04:42 -07:00
Basilisk-Dev
7ed26747c2 Propagate transform stream errors 2026-08-06 14:04:42 -07:00
Basilisk-Dev
936a862591 Harden stream pipe cancellation 2026-08-06 14:04:41 -07:00
Basilisk-Dev
ca56c09e85 Tighten writable stream behavior 2026-08-06 14:04:41 -07:00
Basilisk-Dev
d132fe4728 Add stream constructor polyfills 2026-08-06 14:04:34 -07:00
wuggy
85f7844b5a Merge pull request 'Catch up with UXP (ES2023 and nested CSS)' (#27) from uxpcatchup into main
Reviewed-on: https://repo.dactyloidae.xyz/Dactyloidae/UXP/pulls/27
2026-07-31 19:58:41 +01:00
64 changed files with 5920 additions and 570 deletions

View file

@ -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<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
if (!global) {
return false;
}
// Resolve the stream extras lazily before looking up the internal helper.
JS::Rooted<JS::Value> ignored(aCx);
if (!JS_GetProperty(aCx, global, "WritableStream", &ignored)) {
return false;
}
JS::Rooted<JS::Value> helper(aCx);
if (!JS_GetProperty(aCx, global, aHelperName, &helper)) {
return false;
}
if (!helper.isObject() || !JS::IsCallable(&helper.toObject())) {
aResult.setUndefined();
return true;
}
JS::Rooted<JS::Value> 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<JSObject*> aObj,
const char* aHelperName,
uint32_t aTransferTag,
uint32_t* aTag,
JS::TransferableOwnership* aOwnership,
void** aContent,
uint64_t* aExtraData,
bool* aHandled)
{
*aHandled = false;
JS::Rooted<JS::Value> argument(aCx, JS::ObjectValue(*aObj));
JS::Rooted<JS::Value> transferRecord(aCx);
if (!CallStreamTransferHelper(aCx, aHelperName, argument, &transferRecord)) {
return false;
}
if (transferRecord.isUndefined()) {
return true;
}
if (!transferRecord.isObject()) {
return false;
}
JS::Rooted<JSObject*> 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<JSObject*> aObj,
nsTArray<MessagePortIdentifier>& aPortIdentifiers,
uint32_t* aTag,
JS::TransferableOwnership* aOwnership,
void** aContent,
uint64_t* aExtraData,
bool* aHandled)
{
*aHandled = false;
JS::Rooted<JS::Value> argument(aCx, JS::ObjectValue(*aObj));
JS::Rooted<JS::Value> transferPortValue(aCx);
if (!CallStreamTransferHelper(aCx, "__uxpTransferReadableStreamPort",
argument, &transferPortValue)) {
return false;
}
if (transferPortValue.isUndefined()) {
return true;
}
if (!transferPortValue.isObject()) {
return false;
}
JS::Rooted<JSObject*> 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<SameThreadStreamTransferData*>(aContent);
JS::Rooted<JS::Value> record(aCx, JS::ObjectValue(*data->mRecord));
JS::Rooted<JS::Value> 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<MessagePortIdentifier>& aPortIdentifiers,
uint64_t aExtraData,
JS::MutableHandleObject aReturnObject)
{
if (aExtraData >= aPortIdentifiers.Length()) {
return false;
}
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
ErrorResult rv;
RefPtr<MessagePort> port =
MessagePort::Create(global, aPortIdentifiers[aExtraData], rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return false;
}
JS::Rooted<JS::Value> portValue(aCx);
if (!GetOrCreateDOMReflector(aCx, port, &portValue)) {
JS_ClearPendingException(aCx);
return false;
}
JS::Rooted<JS::Value> 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<SameThreadStreamTransferData*>(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

View file

@ -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.

View file

@ -1929,6 +1929,9 @@ public:
{
ProcessGlobal* global = ProcessGlobal::Get();
MOZ_ASSERT(!aRunInGlobalScope);
if (NS_WARN_IF(!global)) {
return false;
}
global->LoadScript(aURL);
return true;
}

View file

@ -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);

View file

@ -301,6 +301,7 @@ LoadContextOptions(const char* aPrefName, void* /* aClosure */)
.setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
.setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
.setStreams(GetWorkerPref<bool>(NS_LITERAL_CSTRING("streams")))
.setWeakRefs(GetWorkerPref<bool>(NS_LITERAL_CSTRING("weakrefs")))
.setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")))
.setArrayProtoValues(GetWorkerPref<bool>(
NS_LITERAL_CSTRING("array_prototype_values")));

View file

@ -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)

View file

@ -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<TypedArrayObject*> view,
return true;
}
static bool
IsWaitableTypedArray(Scalar::Type viewType)
{
return viewType == Scalar::Int32 || viewType == Scalar::BigInt64;
}
static uint32_t
GetWaiterByteOffset(Handle<TypedArrayObject*> 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<int64_t>(bits))
: BigInt::createFromUint64(cx, bits);
if (!bigint)
return false;
result.setBigInt(bigint);
return true;
}
static bool
WaitValueMatches(Handle<TypedArrayObject*> view, uint32_t offset, uint64_t expected)
{
SharedMem<void*> viewData = view->viewDataShared();
if (view->type() == Scalar::BigInt64) {
return jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<uint64_t*>() + offset) ==
expected;
}
return uint32_t(jit::AtomicOperations::loadSafeWhenRacy(viewData.cast<int32_t*>() + offset)) ==
uint32_t(expected);
}
static int32_t
CompareExchange(Scalar::Type viewType, int32_t oldCandidate, int32_t newCandidate,
SharedMem<void*> 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<uint64_t*>() + 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<int64_t*>() + 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<uint64_t*>() + 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<uint64_t*>() + offset,
value);
r.setBigInt(bigint);
return true;
}
uint64_t result = jit::AtomicOperations::exchangeSeqCst(
view->viewDataShared().cast<uint64_t*>() + 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<uint64_t*> addr = view->viewDataShared().cast<uint64_t*>() + 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<js::Mutex>& 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<mozilla::TimeDuration> timeout_;
ConditionVariable cond_;
State state_;
bool isInWaiterList_;
public:
AtomicsWaitAsyncTask(JSContext* cx, Handle<PromiseObject*> promise,
SharedArrayRawBuffer* sarb, uint32_t offset,
mozilla::Maybe<mozilla::TimeDuration>& 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<PromiseObject*> 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<mozilla::TimeDuration>* 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<PlainObject>(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<TypedArrayObject*> 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<mozilla::TimeDuration> 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<mozilla::TimeDuration> 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<int32_t*> addr = view->viewDataShared().cast<int32_t*>() + 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<SharedArrayBufferObject*> 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<TypedArrayObject*> 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<mozilla::TimeDuration> timeout;
if (!GetWaitTimeout(cx, timeoutv, &timeout))
return false;
Rooted<PromiseObject*> 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<SharedArrayBufferObject*> 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<AtomicsWaitAsyncTask>(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<TypedArrayObject*> 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<double>();
@ -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

View file

@ -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; }

View file

@ -55,6 +55,49 @@ 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.
var elements;
if (callFunction(std_Map_has, groups, key)) {
elements = callFunction(std_Map_get, groups, key);
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() {

View file

@ -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<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
const JSPropertySpec* properties, const JSFunctionSpec* methods,
const JSFunctionSpec* staticMethods,
const JSPropertySpec* staticProperties)
{
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
@ -343,8 +349,13 @@ InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSPro
Rooted<JSFunction*> 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<GlobalObject*> global(cx, &obj->as<GlobalObject>());
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<GlobalObject*> global(cx, &obj->as<GlobalObject>());
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);

View file

@ -109,8 +109,10 @@ class MapObject : public NativeObject {
static MOZ_MUST_USE bool getKeysAndValuesInterleaved(JSContext* cx, HandleObject obj,
JS::MutableHandle<GCVector<JS::Value>> 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<ValueMap*>(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

View file

@ -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++;
}

View file

@ -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<PromiseCapability> capability(cx);
if (!NewPromiseCapability(cx, C, &capability, false))
return false;
// Step 3.
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(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
};

File diff suppressed because it is too large Load diff

View file

@ -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 */

View file

@ -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);

View file

@ -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_;

View file

@ -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<WeakMapObject>();
}
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<WeakMapObject*> mapObj)
{
if (mapObj->getData())
return true;
auto data = cx->make_unique<WeakMapObject::Data>(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<WeakMapObject>().getMap()) {
JSObject* key = &args[0].toObject();
if (map->has(key)) {
args.rval().setBoolean(true);
return true;
WeakMapObject& weakMap = args.thisv().toObject().as<WeakMapObject>();
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<WeakMapObject>().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<WeakMapObject>();
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<WeakMapObject>().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<WeakMapObject>();
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<WeakMapObject*> mapObj,
HandleObject key, HandleValue value)
static bool
SetWeakMapObjectEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
HandleObject key, HandleValue value)
{
if (!EnsureWeakMapData(cx, mapObj))
return false;
ObjectValueMap* map = mapObj->getMap();
if (!map) {
auto newMap = cx->make_unique<ObjectValueMap>(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<WeakMapObject*> mapObj,
return true;
}
MOZ_ALWAYS_INLINE bool
WeakMap_set_impl(JSContext* cx, const CallArgs& args)
static bool
SetWeakMapSymbolEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
Handle<JS::Symbol*> 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<WeakMapObject*> 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<JS::Symbol*> 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<JSObject*> thisObj(cx, &args.thisv().toObject());
Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
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<IsWeakMap, WeakMap_set_impl>(cx, args);
}
bool
js::SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val)
{
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
}
JS_FRIEND_API(bool)
JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
{
@ -205,11 +314,11 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
RootedObject arr(cx, NewDenseEmptyArray(cx));
if (!arr)
return false;
ObjectValueMap* map = obj->as<WeakMapObject>().getMap();
if (map) {
WeakMapObject::Data* data = obj->as<WeakMapObject>().getData();
if (data) {
// Prevent GC from mutating the weakmap while iterating.
AutoSuppressGC suppress(cx);
for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
for (ObjectValueMap::Base::Range r = data->objectMap.all(); !r.empty(); r.popFront()) {
JS::ExposeObjectToActiveJS(r.front().key());
RootedObject key(cx, r.front().key());
if (!cx->compartment()->wrap(cx, &key))
@ -217,6 +326,14 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
return false;
}
for (SymbolValueMap::Base::Range r = data->symbolMap.all(); !r.empty(); r.popFront()) {
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(r.front().key().get()));
RootedValue key(cx, SymbolValue(r.front().key()));
if (!cx->compartment()->wrap(cx, &key))
return false;
if (!NewbornArrayPush(cx, arr, key))
return false;
}
}
ret.set(arr);
return true;
@ -225,21 +342,23 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHan
static void
WeakMap_mark(JSTracer* trc, JSObject* obj)
{
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap())
map->trace(trc);
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
data->objectMap.trace(trc);
data->symbolMap.trace(trc);
}
}
static void
WeakMap_finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->maybeOffMainThread());
if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap()) {
if (WeakMapObject::Data* data = obj->as<WeakMapObject>().getData()) {
#ifdef DEBUG
map->~ObjectValueMap();
memset(static_cast<void*>(map), 0xdc, sizeof(*map));
fop->free_(map);
data->~Data();
memset(static_cast<void*>(data), 0xdc, sizeof(*data));
fop->free_(data);
#else
fop->delete_(map);
fop->delete_(data);
#endif
}
}
@ -282,7 +401,8 @@ JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
CHECK_REQUEST(cx);
assertSameCompartment(cx, key, val);
Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
return SetWeakMapEntryInternal(cx, rootedMap, key, val);
RootedValue keyValue(cx, ObjectValue(*key));
return SetWeakMapEntryInternal(cx, rootedMap, keyValue, val);
}
static bool
@ -386,4 +506,3 @@ js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
{
return InitWeakMapClass(cx, obj, false);
}

View file

@ -16,7 +16,11 @@ class WeakMapObject : public NativeObject
public:
static const Class class_;
ObjectValueMap* getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
struct Data;
Data* getData() { return static_cast<Data*>(getPrivate()); }
ObjectValueMap* getMap();
SymbolValueMap* getSymbolMap();
};
} // namespace js

View file

@ -0,0 +1,208 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "builtin/WeakRefObject.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "gc/Nursery.h"
#include "gc/Tracer.h"
#include "vm/GlobalObject.h"
#include "jsobjinlines.h"
#include "vm/Interpreter-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
static WeakRefObject::Referent*
GetReferent(JSObject* obj)
{
return obj->as<WeakRefObject>().getData();
}
static MOZ_ALWAYS_INLINE bool
IsWeakRef(HandleValue v)
{
return v.isObject() && v.toObject().is<WeakRefObject>();
}
static bool
WeakRef_deref_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsWeakRef(args.thisv()));
WeakRefObject::Referent* data = GetReferent(&args.thisv().toObject());
JSObject* target = data ? data->target.get() : nullptr;
if (target)
args.rval().setObject(*target);
else
args.rval().setUndefined();
return true;
}
const JSPropertySpec WeakRefObject::properties[] = {
JS_PS_END
};
const JSFunctionSpec WeakRefObject::methods[] = {
JS_FN("deref", WeakRefObject::deref, 0, 0),
JS_FS_END
};
static JSObject*
InitWeakRefClass(JSContext* cx, HandleObject obj, bool defineMembers)
{
Handle<GlobalObject*> global = obj.as<GlobalObject>();
RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
if (!proto)
return nullptr;
RootedFunction ctor(cx, GlobalObject::createConstructor(cx, WeakRefObject::construct,
ClassName(JSProto_WeakRef, cx), 1));
if (!ctor)
return nullptr;
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return nullptr;
if (defineMembers) {
if (!DefinePropertiesAndFunctions(cx, proto, WeakRefObject::properties, WeakRefObject::methods))
return nullptr;
if (!DefineToStringTag(cx, proto, cx->names().WeakRef))
return nullptr;
}
if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakRef, ctor, proto))
return nullptr;
return proto;
}
/* static */ WeakRefObject*
WeakRefObject::create(JSContext* cx, HandleObject target, HandleObject proto /* = nullptr */)
{
Rooted<WeakRefObject*> obj(cx, NewObjectWithClassProto<WeakRefObject>(cx, proto));
if (!obj)
return nullptr;
Referent* data = cx->new_<Referent>(target, cx->options().weakRefs());
if (!data)
return nullptr;
obj->setPrivate(data);
return obj;
}
/* static */ bool
WeakRefObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "WeakRef"))
return false;
if (!args.get(0).isObject()) {
UniqueChars bytes =
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), nullptr);
if (!bytes)
return false;
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
bytes.get());
return false;
}
RootedObject target(cx, &args[0].toObject());
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
Rooted<WeakRefObject*> obj(cx, WeakRefObject::create(cx, target, proto));
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
/* static */ bool
WeakRefObject::deref(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsWeakRef, WeakRef_deref_impl>(cx, args);
}
/* static */ void
WeakRefObject::trace(JSTracer* trc, JSObject* obj)
{
if (Referent* data = GetReferent(obj)) {
JSObject* target = data->target.unbarrieredGet();
if (!target)
return;
// When pref-disabled, keep referent alive via strong trace so deref()
// stays usable as a stub without touching GC internals.
if (!data->enabled) {
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef stub referent");
} else if (IsInsideNursery(target)) {
// Weak edges must be tenured; trace strongly while referent is in the nursery.
TraceManuallyBarrieredEdge(trc, data->target.unsafeGet(), "WeakRef nursery referent");
} else {
TraceWeakEdge(trc, &data->target, "WeakRef referent");
}
}
}
/* static */ void
WeakRefObject::finalize(FreeOp* fop, JSObject* obj)
{
if (Referent* data = GetReferent(obj))
fop->delete_(data);
}
static const ClassOps WeakRefObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
WeakRefObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
WeakRefObject::trace
};
const Class WeakRefObject::class_ = {
"WeakRef",
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_CACHED_PROTO(JSProto_WeakRef) |
JSCLASS_BACKGROUND_FINALIZE,
&WeakRefObjectClassOps
};
/* static */ JSObject*
WeakRefObject::initClass(JSContext* cx, HandleObject obj)
{
return ::InitWeakRefClass(cx, obj, true);
}
JSObject*
js::InitWeakRefClass(JSContext* cx, HandleObject obj)
{
return WeakRefObject::initClass(cx, obj);
}
JSObject*
js::InitBareWeakRefCtor(JSContext* cx, HandleObject obj)
{
return ::InitWeakRefClass(cx, obj, false);
}

View file

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef builtin_WeakRefObject_h
#define builtin_WeakRefObject_h
#include "gc/Barrier.h"
#include "vm/NativeObject.h"
namespace js {
class WeakRefObject : public NativeObject
{
public:
struct Referent {
explicit Referent(JSObject* obj, bool enabled)
: target(obj), enabled(enabled) {}
WeakRef<JSObject*> target;
bool enabled;
};
static const Class class_;
static JSObject* initClass(JSContext* cx, HandleObject obj);
static WeakRefObject* create(JSContext* cx, HandleObject target, HandleObject proto = nullptr);
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(FreeOp* fop, JSObject* obj);
[[nodiscard]] static bool construct(JSContext* cx, unsigned argc, Value* vp);
static bool deref(JSContext* cx, unsigned argc, Value* vp);
Referent* getData() const {
return static_cast<Referent*>(getPrivate());
}
WeakRef<JSObject*>& target() {
MOZ_ASSERT(getData());
return getData()->target;
}
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
private:
};
extern JSObject*
InitWeakRefClass(JSContext* cx, HandleObject obj);
extern JSObject*
InitBareWeakRefCtor(JSContext* cx, HandleObject obj);
static inline bool
CanBeHeldWeakly(const Value& value)
{
return value.isObject() || value.isSymbol();
}
} // namespace js
#endif /* builtin_WeakRefObject_h */

View file

@ -32,7 +32,7 @@ function WeakSet_add(value) {
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "add", typeof S);
// Step 5.
if (!IsObject(value))
if (!CanBeHeldWeakly(value))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value));
// Steps 7-8.
@ -55,7 +55,7 @@ function WeakSet_delete(value) {
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "delete", typeof S);
// Step 5.
if (!IsObject(value))
if (!CanBeHeldWeakly(value))
return false;
// Steps 7-8.
@ -75,7 +75,7 @@ function WeakSet_has(value) {
ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "WeakSet", "has", typeof S);
// Step 6.
if (!IsObject(value))
if (!CanBeHeldWeakly(value))
return false;
// Steps 7-8.

View file

@ -12,6 +12,7 @@
#include "builtin/MapObject.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/WeakMapObject.h"
#include "builtin/WeakRefObject.h"
#include "vm/GlobalObject.h"
#include "vm/SelfHosting.h"
@ -108,7 +109,6 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
if (optimized) {
RootedValue keyVal(cx);
RootedObject keyObject(cx);
RootedValue placeholder(cx, BooleanValue(true));
RootedObject map(cx, &obj->getReservedSlot(WEAKSET_MAP_SLOT).toObject());
RootedArrayObject array(cx, &iterable.toObject().as<ArrayObject>());
@ -116,7 +116,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
keyVal.set(array->getDenseElement(index));
MOZ_ASSERT(!keyVal.isMagic(JS_ELEMENTS_HOLE));
if (keyVal.isPrimitive()) {
if (!CanBeHeldWeakly(keyVal)) {
UniqueChars bytes =
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, nullptr);
if (!bytes)
@ -126,8 +126,7 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp)
return false;
}
keyObject = &keyVal.toObject();
if (!SetWeakMapEntry(cx, map, keyObject, placeholder))
if (!SetWeakMapEntryValue(cx, map, keyVal, placeholder))
return false;
}
} else {

View file

@ -1470,6 +1470,12 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
res.isNumber() &&
!TypedArrayGetElemStubExists(stub, obj))
{
if (obj->is<TypedArrayObject>() &&
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
{
return true;
}
if (!cx->runtime()->jitSupportsFloatingPoint &&
(TypedThingRequiresFloatingPoint(obj) || rhs.isDouble()))
{
@ -2160,6 +2166,8 @@ ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
Register obj = masm.extractObject(R0, ExtractTemp0);
masm.loadPtr(Address(ICStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg);
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
if (layout_ == Layout_TypedArray)
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
// Ensure the index is an integer.
if (cx->runtime()->jitSupportsFloatingPoint) {
@ -2631,6 +2639,9 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
bool expectOutOfBounds;
double idx = index.toNumber();
if (obj->is<TypedArrayObject>()) {
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
return true;
expectOutOfBounds = (idx < 0 || idx >= double(obj->as<TypedArrayObject>().length()));
} else {
// Typed objects throw on out of bounds accesses. Don't attach
@ -3221,6 +3232,8 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
Register obj = masm.extractObject(R0, ExtractTemp0);
masm.loadPtr(Address(ICStubReg, ICSetElem_TypedArray::offsetOfShape()), scratchReg);
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
if (layout_ == Layout_TypedArray)
GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure);
// Ensure the index is an integer.
if (cx->runtime()->jitSupportsFloatingPoint) {

View file

@ -8889,9 +8889,17 @@ CodeGenerator::branchIfNotEmptyObjectElements(Register obj, Label* target)
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElements),
&emptyObj);
masm.branchPtr(Assembler::NotEqual,
masm.branchPtr(Assembler::Equal,
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElementsShared),
&emptyObj);
masm.branchPtr(Assembler::Equal,
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElementsResizableOrGrowable),
&emptyObj);
masm.branchPtr(Assembler::NotEqual,
Address(obj, NativeObject::offsetOfElements()),
ImmPtr(js::emptyObjectElementsSharedResizableOrGrowable),
target);
masm.bind(&emptyObj);
}

View file

@ -537,8 +537,7 @@ class CodeGenerator final : public CodeGeneratorSpecific
Label* ifDoesntEmulateUndefined,
Register scratch, OutOfLineTestObject* ool);
// Branch to target unless obj has an emptyObjectElements or emptyObjectElementsShared
// elements pointer.
// Branch to target unless obj has one of the empty elements pointers.
void branchIfNotEmptyObjectElements(Register obj, Label* target);
void emitStoreElementTyped(const LAllocation* value, MIRType valueType, MIRType elementType,

View file

@ -1245,6 +1245,7 @@ GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAtta
masm.branchPtr(Assembler::AboveOrEqual, tmpReg,
ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]),
failures);
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, failures);
// Load length.
masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output);
@ -1644,6 +1645,9 @@ GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript
if (!JSID_IS_ATOM(id, cx->names().length))
return true;
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
return true;
if (hasTypedArrayLengthStub(obj))
return true;
@ -4038,6 +4042,12 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i
if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
return false;
if (obj->is<TypedArrayObject>() &&
obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
{
return false;
}
MOZ_ASSERT(idval.isInt32() || idval.isString());
// Don't emit a stub if the access is out of bounds. We make to make
@ -4092,6 +4102,9 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
// Decide to what type index the stub should be optimized
Register tmpReg = output.scratchReg().gpr();
MOZ_ASSERT(tmpReg != InvalidReg);
if (array->is<TypedArrayObject>())
GuardResizableOrGrowableTypedArray(masm, object, tmpReg, &failures);
Register indexReg = tmpReg;
if (idval.isString()) {
MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);
@ -4575,6 +4588,7 @@ GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::Stub
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures);
GuardResizableOrGrowableTypedArray(masm, object, temp, &failures);
// Ensure the index is an int32.
Register indexReg;
@ -4661,6 +4675,9 @@ SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScrip
if (!IsTypedArrayElementSetInlineable(obj, idval, val))
return true;
if (obj->as<TypedArrayObject>().hasResizableOrGrowableBuffer())
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);

View file

@ -365,13 +365,9 @@ IonBuilder::inlineNativeGetter(CallInfo& callInfo, JSFunction* target)
// Try to optimize typed array lengths.
if (TypedArrayObject::isOriginalLengthGetter(native)) {
Scalar::Type type = thisTypes->getTypedArrayType(constraints());
if (type == Scalar::MaxTypedArrayViewType)
return InliningStatus_NotInlined;
MInstruction* length = addTypedArrayLength(thisArg);
current->push(length);
return InliningStatus_Inlined;
// RAB/GSAB views can have dynamic length or temporarily become
// out-of-bounds. Let the property IC/VM path handle the getter.
return InliningStatus_NotInlined;
}
// Try to optimize RegExp getters.
@ -2480,21 +2476,10 @@ IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def)
IonBuilder::InliningStatus
IonBuilder::inlinePossiblyWrappedTypedArrayLength(CallInfo& callInfo)
{
MOZ_ASSERT(!callInfo.constructing());
MOZ_ASSERT(callInfo.argc() == 1);
if (callInfo.getArg(0)->type() != MIRType::Object)
return InliningStatus_NotInlined;
if (getInlineReturnType() != MIRType::Int32)
return InliningStatus_NotInlined;
(void) callInfo;
if (!IsTypedArrayObject(constraints(), callInfo.getArg(0)))
return InliningStatus_NotInlined;
MInstruction* length = addTypedArrayLength(callInfo.getArg(0));
current->push(length);
callInfo.setImplicitlyUsedUnchecked();
return InliningStatus_Inlined;
// RAB/GSAB views require dynamic length semantics.
return InliningStatus_NotInlined;
}
IonBuilder::InliningStatus
@ -3251,45 +3236,14 @@ bool
IonBuilder::atomicsMeetsPreconditions(CallInfo& callInfo, Scalar::Type* arrayType,
bool* requiresTagCheck, AtomicCheckResult checkResult)
{
if (!JitSupportsAtomics())
return false;
(void) callInfo;
(void) arrayType;
(void) requiresTagCheck;
(void) checkResult;
if (callInfo.getArg(0)->type() != MIRType::Object)
return false;
if (callInfo.getArg(1)->type() != MIRType::Int32)
return false;
// Ensure that the first argument is a TypedArray that maps shared
// memory.
//
// Then check both that the element type is something we can
// optimize and that the return type is suitable for that element
// type.
TemporaryTypeSet* arg0Types = callInfo.getArg(0)->resultTypeSet();
if (!arg0Types)
return false;
TemporaryTypeSet::TypedArraySharedness sharedness;
*arrayType = arg0Types->getTypedArrayType(constraints(), &sharedness);
*requiresTagCheck = sharedness != TemporaryTypeSet::KnownShared;
switch (*arrayType) {
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Int32;
case Scalar::Uint32:
// Bug 1077305: it would be attractive to allow inlining even
// if the inline return type is Int32, which it will frequently
// be.
return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Double;
default:
// Excludes floating types and Uint8Clamped.
return false;
}
// Atomics bounds checks through addTypedArrayLengthAndData assume stable
// SharedArrayBuffer lengths. Avoid this path now that growable SAB exists.
return false;
}
void

View file

@ -5505,25 +5505,15 @@ jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id,
Scalar::Type* arrayType)
{
if (obj->mightBeType(MIRType::String))
return false;
(void) constraints;
(void) obj;
(void) id;
(void) arrayType;
if (id->type() != MIRType::Int32 && id->type() != MIRType::Double)
return false;
TemporaryTypeSet* types = obj->resultTypeSet();
if (!types)
return false;
*arrayType = types->getTypedArrayType(constraints);
// FIXME: https://bugzil.la/1536699
if (*arrayType == Scalar::MaxTypedArrayViewType ||
Scalar::isBigIntType(*arrayType)) {
return false;
}
return true;
// Resizable ArrayBuffer and growable SharedArrayBuffer views need dynamic
// length/out-of-bounds handling. This older MIR path assumes stable
// typed-array length/data slots, so keep element accesses on IC/VM paths.
return false;
}
bool

View file

@ -3631,6 +3631,17 @@ CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Labe
masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure);
}
void
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
Label* failure)
{
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
masm.branchTest32(Assembler::NonZero,
Address(scratch, ObjectElements::offsetOfFlags()),
Imm32(ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER),
failure);
}
void
LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
{

View file

@ -2307,7 +2307,11 @@ CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, Register
void
CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure);
MOZ_MUST_USE bool
void
GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch,
Label* failure);
[[nodiscard]] bool
DoCallNativeGetter(JSContext* cx, HandleFunction callee, HandleObject obj,
MutableHandleValue result);

View file

@ -548,6 +548,8 @@ MSG_DEF(JSMSG_TOO_LONG_ARRAY, 0, JSEXN_TYPEERR, "Too long array")
// Typed array
MSG_DEF(JSMSG_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
MSG_DEF(JSMSG_ARRAYBUFFER_NOT_RESIZABLE, 0, JSEXN_TYPEERR, "ArrayBuffer is not resizable")
MSG_DEF(JSMSG_ARRAYBUFFER_CANNOT_DETACH, 0, JSEXN_TYPEERR, "ArrayBuffer cannot be detached")
MSG_DEF(JSMSG_NON_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected ArrayBuffer, but species constructor returned non-ArrayBuffer")
MSG_DEF(JSMSG_SAME_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different ArrayBuffer, but species constructor returned same ArrayBuffer")
MSG_DEF(JSMSG_SHORT_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected ArrayBuffer with at least {0} bytes, but species constructor returns ArrayBuffer with {1} bytes")
@ -555,12 +557,15 @@ MSG_DEF(JSMSG_TYPED_ARRAY_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
MSG_DEF(JSMSG_TYPED_ARRAY_NEGATIVE_ARG,1, JSEXN_RANGEERR, "argument {0} must be >= 0")
MSG_DEF(JSMSG_TYPED_ARRAY_DETACHED, 0, JSEXN_TYPEERR, "attempting to access detached ArrayBuffer")
MSG_DEF(JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS, 0, JSEXN_RANGEERR, "attempting to construct out-of-bounds TypedArray on ArrayBuffer")
MSG_DEF(JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds TypedArray")
MSG_DEF(JSMSG_DATA_VIEW_OUT_OF_BOUNDS, 0, JSEXN_TYPEERR, "attempting to access out-of-bounds DataView")
MSG_DEF(JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, 1, JSEXN_TYPEERR, "cannot directly {0} builtin %TypedArray%")
MSG_DEF(JSMSG_NON_TYPED_ARRAY_RETURNED, 0, JSEXN_TYPEERR, "constructor didn't return TypedArray object")
MSG_DEF(JSMSG_SHORT_TYPED_ARRAY_RETURNED, 2, JSEXN_TYPEERR, "expected TypedArray of at least length {0}, but constructor returned TypedArray of length {1}")
// Shared array buffer
MSG_DEF(JSMSG_SHARED_ARRAY_BAD_LENGTH, 0, JSEXN_RANGEERR, "length argument out of range")
MSG_DEF(JSMSG_SHARED_ARRAY_NOT_GROWABLE, 0, JSEXN_TYPEERR, "SharedArrayBuffer is not growable")
MSG_DEF(JSMSG_NON_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected SharedArrayBuffer, but species constructor returned non-SharedArrayBuffer")
MSG_DEF(JSMSG_SAME_SHARED_ARRAY_BUFFER_RETURNED, 0, JSEXN_TYPEERR, "expected different SharedArrayBuffer, but species constructor returned same SharedArrayBuffer")
MSG_DEF(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, 2, JSEXN_TYPEERR, "expected SharedArrayBuffer with at least {0} bytes, but species constructor returns SharedArrayBuffer with {1} bytes")

View file

@ -951,6 +951,20 @@ LookupStdName(const JSAtomState& names, JSAtom* name, const JSStdName* table)
return nullptr;
}
static bool
IsStreamExtraName(JSFlatString* name)
{
return JS_FlatStringEqualsAscii(name, "WritableStream") ||
JS_FlatStringEqualsAscii(name, "WritableStreamDefaultWriter") ||
JS_FlatStringEqualsAscii(name, "WritableStreamDefaultController") ||
JS_FlatStringEqualsAscii(name, "TransformStream") ||
JS_FlatStringEqualsAscii(name, "TransformStreamDefaultController") ||
JS_FlatStringEqualsAscii(name, "TextEncoderStream") ||
JS_FlatStringEqualsAscii(name, "TextDecoderStream") ||
JS_FlatStringEqualsAscii(name, "CompressionStream") ||
JS_FlatStringEqualsAscii(name, "DecompressionStream");
}
/*
* Table of standard classes, indexed by JSProtoKey. For entries where the
* JSProtoKey does not correspond to a class with a meaningful constructor, we
@ -1041,11 +1055,27 @@ JS_ResolveStandardClass(JSContext* cx, HandleObject obj, HandleId id, bool* reso
if (!GlobalObject::ensureConstructor(cx, global, key))
return false;
if (key == JSProto_ReadableStream) {
RootedObject globalObj(cx, global);
if (!InitStreamExtras(cx, globalObj))
return false;
}
*resolved = true;
return true;
}
}
if (cx->options().streams() && IsStreamExtraName(idAtom)) {
RootedObject globalObj(cx, global);
if (!InitStreamExtras(cx, globalObj))
return false;
if (!HasOwnProperty(cx, globalObj, id, resolved))
return false;
return true;
}
// There is no such property to resolve. An ordinary resolve hook would
// just return true at this point. But the global object is special in one
// more way: its prototype chain is lazily initialized. That is,
@ -1075,6 +1105,7 @@ JS_MayResolveStandardClass(const JSAtomState& names, jsid id, JSObject* maybeObj
return atom == names.undefined ||
atom == names.globalThis ||
IsStreamExtraName(atom) ||
LookupStdName(names, atom, standard_class_names) ||
LookupStdName(names, atom, builtin_property_names);
}

View file

@ -998,7 +998,9 @@ class JS_PUBLIC_API(ContextOptions) {
werror_(false),
strictMode_(false),
extraWarnings_(false),
arrayProtoValues_(true)
arrayProtoValues_(true),
streams_(true),
weakRefs_(false)
{
}
@ -1138,6 +1140,16 @@ class JS_PUBLIC_API(ContextOptions) {
return *this;
}
bool weakRefs() const { return weakRefs_; }
ContextOptions& setWeakRefs(bool flag) {
weakRefs_ = flag;
return *this;
}
ContextOptions& toggleWeakRefs() {
weakRefs_ = !weakRefs_;
return *this;
}
private:
bool baseline_ : 1;
bool ion_ : 1;
@ -1155,6 +1167,7 @@ class JS_PUBLIC_API(ContextOptions) {
bool extraWarnings_ : 1;
bool arrayProtoValues_ : 1;
bool streams_ : 1;
bool weakRefs_ : 1;
};
JS_PUBLIC_API(ContextOptions&)

View file

@ -1748,6 +1748,9 @@ JS_IsFloat64Array(JSObject* obj);
extern JS_FRIEND_API(bool)
JS_GetTypedArraySharedness(JSObject* obj);
extern JS_FRIEND_API(uint32_t)
JS_GetTypedArrayLength(JSObject* obj);
/*
* Test for specific typed array types (ArrayBufferView subtypes) and return
* the unwrapped object if so, else nullptr. Never throws.
@ -1814,8 +1817,7 @@ inline void \
Get ## Type ## ArrayLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, type** data) \
{ \
MOZ_ASSERT(GetObjectClass(obj) == detail::Type ## ArrayClassPtr); \
const JS::Value& lenSlot = GetReservedSlot(obj, detail::TypedArrayLengthSlot); \
*length = mozilla::AssertedCast<uint32_t>(lenSlot.toInt32()); \
*length = JS_GetTypedArrayLength(obj); \
*isSharedMemory = JS_GetTypedArraySharedness(obj); \
*data = static_cast<type*>(GetObjectPrivate(obj)); \
}

View file

@ -119,6 +119,7 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \
imaginary(WritableStreamDefaultController,dummy, dummy) \
real(ByteLengthQueuingStrategy, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \
real(CountQueuingStrategy, InitViaClassSpec, &js::CountQueuingStrategy::class_) \
real(WeakRef, InitWeakRefClass, OCLASP(WeakRef)) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)

View file

@ -3344,6 +3344,8 @@ static const JSFunctionSpec string_methods[] = {
JS_SELF_HOSTED_FN("toLocaleUpperCase", "String_toLocaleUpperCase", 0,0),
JS_SELF_HOSTED_FN("localeCompare", "String_localeCompare", 1,0),
JS_SELF_HOSTED_FN("repeat", "String_repeat", 1,0),
JS_SELF_HOSTED_FN("isWellFormed", "String_isWellFormed", 0,0),
JS_SELF_HOSTED_FN("toWellFormed", "String_toWellFormed", 0,0),
JS_FN("normalize", str_normalize, 0,0),
/* Perl-ish methods (search is actually Python-esque). */

View file

@ -16,11 +16,25 @@
#include "gc/Marking.h"
#include "gc/StoreBuffer.h"
#include "js/HashTable.h"
#include "vm/Symbol.h"
namespace js {
class WeakMapBase;
template <>
struct MovableCellHasher<JS::Symbol*>
{
using Key = JS::Symbol*;
using Lookup = JS::Symbol*;
static bool hasHash(const Lookup& l) { return true; }
static bool ensureHash(const Lookup& l) { return true; }
static HashNumber hash(const Lookup& l) { return l->hash(); }
static bool match(const Key& k, const Lookup& l) { return k == l; }
static void rekey(Key& k, const Key& newKey) { k = newKey; }
};
// A subclass template of js::HashMap whose keys and values may be garbage-collected. When
// a key is collected, the table entry disappears, dropping its reference to the value.
//
@ -296,9 +310,16 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
return nullptr;
}
JSObject* getDelegate(JS::Symbol* sym) const {
return nullptr;
}
private:
void exposeGCThingToActiveJS(const JS::Value& v) const { JS::ExposeValueToActiveJS(v); }
void exposeGCThingToActiveJS(JSObject* obj) const { JS::ExposeObjectToActiveJS(obj); }
void exposeGCThingToActiveJS(JS::Symbol* sym) const {
gc::ExposeGCThingToActiveJS(JS::GCCellPtr(sym));
}
bool keyNeedsMark(JSObject* key) const {
JSObject* delegate = getDelegate(key);
@ -313,6 +334,10 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
return false;
}
bool keyNeedsMark(JS::Symbol* sym) const {
return false;
}
bool findZoneEdges() override {
// This is overridden by ObjectValueMap.
return true;
@ -378,6 +403,9 @@ WeakMap_set(JSContext* cx, unsigned argc, Value* vp);
extern bool
WeakMap_delete(JSContext* cx, unsigned argc, Value* vp);
extern bool
SetWeakMapEntryValue(JSContext* cx, HandleObject mapObj, HandleValue key, HandleValue val);
extern JSObject*
InitWeakMapClass(JSContext* cx, HandleObject obj);
@ -394,6 +422,16 @@ class ObjectValueMap : public WeakMap<HeapPtr<JSObject*>, HeapPtr<Value>,
virtual bool findZoneEdges();
};
class SymbolValueMap : public WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
MovableCellHasher<HeapPtr<JS::Symbol*>>>
{
public:
SymbolValueMap(JSContext* cx, JSObject* obj)
: WeakMap<HeapPtr<JS::Symbol*>, HeapPtr<Value>,
MovableCellHasher<HeapPtr<JS::Symbol*>>>(cx, obj)
{}
};
// Generic weak map for mapping objects to other objects.
class ObjectWeakMap

View file

@ -142,6 +142,7 @@ main_deunified_sources = [
'builtin/TestingFunctions.cpp',
'builtin/TypedObject.cpp',
'builtin/WeakMapObject.cpp',
'builtin/WeakRefObject.cpp',
'builtin/WeakSetObject.cpp',
'devtools/sharkctl.cpp',
'ds/LifoAlloc.cpp',

View file

@ -3330,6 +3330,15 @@ static const JSClass sandbox_class = {
&sandbox_classOps
};
enum GlobalAppSlot {
GlobalAppSlotModuleMetadataHook,
GlobalAppSlotModuleDynamicImportHook,
GlobalAppSlotCount
};
static_assert(GlobalAppSlotCount <= JSCLASS_GLOBAL_APPLICATION_SLOTS,
"global application slots overflow");
static void
SetStandardCompartmentOptions(JS::CompartmentOptions& options)
{
@ -4158,7 +4167,7 @@ ParseModule(JSContext* cx, unsigned argc, Value* vp)
const char16_t* chars = stableChars.twoByteRange().begin().get();
JS::SourceBufferHolder srcBuf(chars, scriptContents->length(),
SourceBufferHolder::NoOwnership);
JS::SourceBufferHolder::NoOwnership);
RootedObject module(cx, frontend::CompileModule(cx, options, srcBuf));
if (!module)
@ -4369,7 +4378,7 @@ AbortDynamicModuleImport(JSContext* cx, unsigned argc, Value* vp)
RootedString specifier(cx, args[1].toString());
Rooted<PromiseObject*> promise(cx, &args[2].toObject().as<PromiseObject>());
cx->setPendingException(args[3]);
cx->setPendingException(args[3], nullptr);
return js::FinishDynamicModuleImport(cx, args[0], specifier, promise);
}
@ -8422,7 +8431,7 @@ main(int argc, char** argv, char** envp)
JS::SetModuleResolveHook(cx->runtime(), ShellModuleResolveHook);
JS::SetModuleDynamicImportHook(cx, ShellModuleDynamicImportHook);
JS::SetModuleMetadataHook(cx, ShellModuleMetadataHook);
JS::SetModuleMetadataHook(cx, CallModuleMetadataHook);
result = Shell(cx, &op, envp);

View file

@ -0,0 +1,98 @@
// |reftest| skip-if(!ArrayBuffer.prototype.transfer)
var fixed = new ArrayBuffer(4);
assertEq(fixed.byteLength, 4);
assertEq(fixed.maxByteLength, 4);
assertEq(fixed.resizable, false);
assertEq(fixed.detached, false);
new Uint8Array(fixed).set([1, 2, 3, 4]);
fixed.extra = 17;
assertEq(Array.from(new Uint8Array(fixed)).join(","), "1,2,3,4");
assertThrowsInstanceOf(() => fixed.resize(2), TypeError);
assertEq(ArrayBuffer.prototype.transfer.length, 0);
assertEq(ArrayBuffer.prototype.transferToFixedLength.length, 0);
var resizeArgumentConverted = false;
assertThrowsInstanceOf(() => fixed.resize({ valueOf() { resizeArgumentConverted = true; return 1; } }),
TypeError);
assertEq(resizeArgumentConverted, false);
var resizable = new ArrayBuffer(4, { maxByteLength: 8 });
assertEq(resizable.byteLength, 4);
assertEq(resizable.maxByteLength, 8);
assertEq(resizable.resizable, true);
var bytes = new Uint8Array(resizable);
bytes[0] = 11;
bytes[3] = 44;
resizable.resize(6);
assertEq(resizable.byteLength, 6);
assertEq(new Uint8Array(resizable)[0], 11);
assertEq(new Uint8Array(resizable)[3], 44);
assertEq(new Uint8Array(resizable)[4], 0);
assertThrowsInstanceOf(() => resizable.resize(9), RangeError);
var source = new ArrayBuffer(4);
var sourceBytes = new Uint8Array(source);
sourceBytes[0] = 1;
sourceBytes[1] = 2;
var sourceView = new Uint8Array(source);
var moved = source.transfer(6);
assertEq(source.detached, true);
assertEq(source.byteLength, 0);
assertEq(source.maxByteLength, 0);
assertEq(sourceView.length, 0);
assertEq(moved.byteLength, 6);
assertEq(moved.resizable, false);
assertEq(moved.maxByteLength, 6);
assertEq(new Uint8Array(moved)[0], 1);
assertEq(new Uint8Array(moved)[1], 2);
assertEq(new Uint8Array(moved)[4], 0);
var resizableSource = new ArrayBuffer(4, { maxByteLength: 8 });
new Uint8Array(resizableSource)[0] = 7;
var resizableMoved = resizableSource.transfer();
assertEq(resizableSource.detached, true);
assertEq(resizableSource.resizable, true);
assertEq(resizableMoved.byteLength, 4);
assertEq(resizableMoved.maxByteLength, 8);
assertEq(resizableMoved.resizable, true);
assertEq(new Uint8Array(resizableMoved)[0], 7);
var fixedMoved = resizableMoved.transferToFixedLength(10);
assertEq(resizableMoved.detached, true);
assertEq(fixedMoved.byteLength, 10);
assertEq(fixedMoved.maxByteLength, 10);
assertEq(fixedMoved.resizable, false);
assertEq(new Uint8Array(fixedMoved)[0], 7);
var sliceSource = new ArrayBuffer(8, { maxByteLength: 8 });
var sliceSourceBytes = new Uint8Array(sliceSource);
for (var i = 0; i < sliceSourceBytes.length; i++)
sliceSourceBytes[i] = i + 1;
sliceSource.constructor = {
[Symbol.species]: function(byteLength) {
sliceSource.resize(4);
return new ArrayBuffer(byteLength);
}
};
var sliced = sliceSource.slice(2, 8);
var slicedBytes = new Uint8Array(sliced);
assertEq(sliced.byteLength, 6);
assertEq(slicedBytes[0], 3);
assertEq(slicedBytes[1], 4);
assertEq(slicedBytes[2], 0);
assertEq(slicedBytes[5], 0);
sliceSource.resize(8);
for (var i = 0; i < sliceSourceBytes.length; i++)
sliceSourceBytes[i] = i + 1;
var zeroCopied = sliceSource.slice(6, 8);
assertEq(zeroCopied.byteLength, 2);
assertEq(new Uint8Array(zeroCopied)[0], 0);
assertThrowsInstanceOf(() => new ArrayBuffer(4, { maxByteLength: 3 }), RangeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -0,0 +1,218 @@
// |reftest| skip-if(!this.SharedArrayBuffer)
var rab = new ArrayBuffer(4, { maxByteLength: 16 });
var tracking = new Uint8Array(rab);
var fixed = new Uint8Array(rab, 1, 2);
var bytes = new Uint8Array(rab);
bytes[1] = 11;
bytes[2] = 22;
assertEq(tracking.length, 4);
assertEq(tracking.byteLength, 4);
assertEq(tracking.byteOffset, 0);
assertEq(fixed.length, 2);
assertEq(fixed.byteLength, 2);
assertEq(fixed.byteOffset, 1);
rab.resize(2);
assertEq(tracking.length, 2);
assertEq(tracking.byteLength, 2);
assertEq(fixed.length, 0);
assertEq(fixed.byteLength, 0);
assertEq(fixed.byteOffset, 0);
assertEq(fixed[0], undefined);
rab.resize(8);
assertEq(tracking.length, 8);
assertEq(tracking.byteLength, 8);
assertEq(fixed.length, 2);
assertEq(fixed.byteLength, 2);
assertEq(fixed.byteOffset, 1);
assertEq(fixed[0], 11);
assertEq(fixed[1], 0);
tracking[6] = 66;
assertEq(new Uint8Array(rab)[6], 66);
var dv = new DataView(rab, 4);
assertEq(dv.byteOffset, 4);
assertEq(dv.byteLength, 4);
dv.setUint8(0, 44);
assertEq(tracking[4], 44);
rab.resize(3);
assertThrowsInstanceOf(() => dv.byteOffset, TypeError);
assertThrowsInstanceOf(() => dv.byteLength, TypeError);
assertThrowsInstanceOf(() => dv.getUint8(0), TypeError);
rab.resize(6);
assertEq(dv.byteOffset, 4);
assertEq(dv.byteLength, 2);
assertEq(dv.getUint8(0), 0);
var fixedDv = new DataView(rab, 4, 2);
rab.resize(5);
assertThrowsInstanceOf(() => fixedDv.byteOffset, TypeError);
assertThrowsInstanceOf(() => fixedDv.byteLength, TypeError);
assertThrowsInstanceOf(() => fixedDv.getUint8(0), TypeError);
rab.resize(6);
assertEq(fixedDv.byteOffset, 4);
assertEq(fixedDv.byteLength, 2);
var methodRab = new ArrayBuffer(4, { maxByteLength: 8 });
var methodFixed = new Uint8Array(methodRab, 2, 2);
methodRab.resize(2);
[
() => methodFixed.at(0),
() => methodFixed.copyWithin(0, 0),
() => methodFixed.entries(),
() => methodFixed.every(x => true),
() => methodFixed.fill(1),
() => methodFixed.filter(x => true),
() => methodFixed.find(x => true),
() => methodFixed.findIndex(x => true),
() => methodFixed.findLast(x => true),
() => methodFixed.findLastIndex(x => true),
() => methodFixed.forEach(x => x),
() => methodFixed.includes(0),
() => methodFixed.indexOf(0),
() => methodFixed.join(","),
() => methodFixed.keys(),
() => methodFixed.lastIndexOf(0),
() => methodFixed.map(x => x),
() => methodFixed.reduce((a, b) => a + b, 0),
() => methodFixed.reduceRight((a, b) => a + b, 0),
() => methodFixed.reverse(),
() => methodFixed.set([1], 0),
() => methodFixed.slice(),
() => methodFixed.some(x => true),
() => methodFixed.sort(),
() => methodFixed.subarray(),
() => methodFixed.toLocaleString(),
() => methodFixed.toReversed(),
() => methodFixed.toSorted(),
() => methodFixed.toString(),
() => methodFixed.values(),
() => methodFixed.with(0, 1),
() => methodFixed[Symbol.iterator](),
].forEach(fn => assertThrowsInstanceOf(fn, TypeError));
methodRab.resize(4);
assertEq(methodFixed.length, 2);
var sourceRab = new ArrayBuffer(4, { maxByteLength: 8 });
var oobSource = new Uint8Array(sourceRab, 2, 2);
sourceRab.resize(2);
assertThrowsInstanceOf(() => new Uint8Array(oobSource), TypeError);
assertThrowsInstanceOf(() => new Uint16Array(oobSource), TypeError);
assertThrowsInstanceOf(() => new Uint8Array(4).set(oobSource), TypeError);
sourceRab.resize(4);
assertEq(new Uint8Array(oobSource).length, 2);
var ctorRab = new ArrayBuffer(8, { maxByteLength: 8 });
var ShrinkingNewTarget = new Proxy(function() {}, {
get(target, prop, receiver) {
if (prop === "prototype") {
ctorRab.resize(2);
return DataView.prototype;
}
return Reflect.get(target, prop, receiver);
}
});
assertThrowsInstanceOf(() => Reflect.construct(DataView, [ctorRab, 4], ShrinkingNewTarget),
RangeError);
var fixedCtorRab = new ArrayBuffer(8, { maxByteLength: 8 });
var FixedShrinkingNewTarget = new Proxy(function() {}, {
get(target, prop, receiver) {
if (prop === "prototype") {
fixedCtorRab.resize(5);
return DataView.prototype;
}
return Reflect.get(target, prop, receiver);
}
});
assertThrowsInstanceOf(() => Reflect.construct(DataView, [fixedCtorRab, 4, 2],
FixedShrinkingNewTarget),
RangeError);
var gsab = new SharedArrayBuffer(4, { maxByteLength: 16 });
var sharedTracking = new Uint8Array(gsab);
assertEq(sharedTracking.length, 4);
gsab.grow(8);
assertEq(sharedTracking.length, 8);
sharedTracking[6] = 33;
assertEq(new Uint8Array(gsab)[6], 33);
var sharedDv = new DataView(gsab);
assertEq(sharedDv.buffer, gsab);
assertEq(sharedDv.byteOffset, 0);
assertEq(sharedDv.byteLength, 8);
sharedDv.setUint8(7, 99);
assertEq(sharedTracking[7], 99);
gsab.grow(12);
assertEq(sharedDv.byteLength, 12);
assertEq(sharedDv.getUint8(7), 99);
var fixedSharedDv = new DataView(gsab, 4, 2);
assertEq(fixedSharedDv.byteOffset, 4);
assertEq(fixedSharedDv.byteLength, 2);
gsab.grow(16);
assertEq(fixedSharedDv.byteOffset, 4);
assertEq(fixedSharedDv.byteLength, 2);
function readLength(view) {
return view.length;
}
function readElement(view, index) {
return view[index];
}
function writeElement(view, index, value) {
view[index] = value;
}
var normal = new Uint8Array(4);
normal[0] = 7;
for (var i = 0; i < 2000; i++) {
assertEq(readLength(normal), 4);
assertEq(readElement(normal, 0), 7);
writeElement(normal, 1, 8);
}
var icRab = new ArrayBuffer(4, { maxByteLength: 8 });
var icTracking = new Uint8Array(icRab);
icTracking[0] = 9;
assertEq(readLength(icTracking), 4);
assertEq(readElement(icTracking, 0), 9);
icRab.resize(0);
assertEq(readLength(icTracking), 0);
assertEq(readElement(icTracking, 0), undefined);
writeElement(icTracking, 0, 1);
icRab.resize(4);
assertEq(readLength(icTracking), 4);
assertEq(readElement(icTracking, 0), 0);
writeElement(icTracking, 0, 12);
assertEq(readElement(icTracking, 0), 12);
var icFixed = new Uint8Array(icRab, 1, 2);
assertEq(readLength(icFixed), 2);
icRab.resize(2);
assertEq(readLength(icFixed), 0);
assertEq(readElement(icFixed, 0), undefined);
writeElement(icFixed, 0, 55);
icRab.resize(4);
assertEq(readLength(icFixed), 2);
assertEq(readElement(icFixed, 0), 0);
var icGsab = new SharedArrayBuffer(4, { maxByteLength: 8 });
var icSharedTracking = new Uint8Array(icGsab);
assertEq(readLength(icSharedTracking), 4);
icGsab.grow(8);
assertEq(readLength(icSharedTracking), 8);
writeElement(icSharedTracking, 5, 77);
assertEq(readElement(icSharedTracking, 5), 77);
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,77 @@
// |reftest| skip-if(!this.SharedArrayBuffer || !this.Atomics || !this.drainJobQueue)
if (typeof SharedArrayBuffer === "function" && typeof Atomics === "object" &&
typeof drainJobQueue === "function") {
const sab = new SharedArrayBuffer(4);
const i32 = new Int32Array(sab);
let result = Atomics.waitAsync(i32, 0, 1, 10);
assertEq(result.async, false);
assertEq(result.value, "not-equal");
result = Atomics.waitAsync(i32, 0, 0, 0);
assertEq(result.async, false);
assertEq(result.value, "timed-out");
result = Atomics.waitAsync(i32, 0, 0);
assertEq(result.async, true);
let notified;
result.value.then(value => {
notified = value;
});
assertEq(Atomics.notify(i32, 0, 1), 1);
drainJobQueue();
assertEq(notified, "ok");
result = Atomics.waitAsync(i32, 0, 0, 1);
assertEq(result.async, true);
let timedOut;
result.value.then(value => {
timedOut = value;
});
drainJobQueue();
assertEq(timedOut, "timed-out");
if (typeof BigInt64Array === "function") {
const bigSab = new SharedArrayBuffer(16);
const i64 = new BigInt64Array(bigSab);
const u64 = new BigUint64Array(bigSab);
assertEq(Atomics.store(i64, 0, -1n), -1n);
assertEq(Atomics.load(i64, 0), -1n);
assertEq(Atomics.load(u64, 0), 18446744073709551615n);
assertEq(Atomics.exchange(i64, 0, 7n), -1n);
assertEq(Atomics.compareExchange(i64, 0, 7n, 10n), 7n);
assertEq(Atomics.add(i64, 0, 5n), 10n);
assertEq(Atomics.load(i64, 0), 15n);
assertEq(Atomics.sub(i64, 0, 20n), 15n);
assertEq(Atomics.load(i64, 0), -5n);
assertEq(Atomics.and(u64, 0, 7n), 18446744073709551611n);
assertEq(Atomics.or(u64, 0, 8n), 3n);
assertEq(Atomics.xor(u64, 0, 15n), 11n);
assertThrowsInstanceOf(() => Atomics.waitAsync(u64, 0, 0n), TypeError);
result = Atomics.waitAsync(i64, 1, 1n, 10);
assertEq(result.async, false);
assertEq(result.value, "not-equal");
result = Atomics.waitAsync(i64, 1, 0n, 0);
assertEq(result.async, false);
assertEq(result.value, "timed-out");
let offsetView = new BigInt64Array(bigSab, 8);
result = Atomics.waitAsync(offsetView, 0, 0n);
assertEq(result.async, true);
notified = undefined;
result.value.then(value => {
notified = value;
});
assertEq(Atomics.notify(i64, 1, 1), 1);
drainJobQueue();
assertEq(notified, "ok");
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,31 @@
// |reftest| skip-if(!Promise.withResolvers)
var desc = Object.getOwnPropertyDescriptor(Promise, "withResolvers");
assertEq(desc.enumerable, false);
assertEq(desc.configurable, true);
assertEq(desc.writable, true);
assertEq(Promise.withResolvers.length, 0);
assertEq(Promise.withResolvers.name, "withResolvers");
var capability = Promise.withResolvers();
assertEq(capability.promise instanceof Promise, true);
assertEq(typeof capability.resolve, "function");
assertEq(typeof capability.reject, "function");
assertEqArray(Object.keys(capability), ["promise", "resolve", "reject"]);
capability.resolve(42);
capability.promise.then(v => assertEq(v, 42));
class MyPromise extends Promise {}
var subCapability = Promise.withResolvers.call(MyPromise);
assertEq(subCapability.promise instanceof MyPromise, true);
subCapability.reject("rejected");
subCapability.promise.then(
() => { throw new Error("expected rejection"); },
reason => assertEq(reason, "rejected")
);
assertThrowsInstanceOf(() => Promise.withResolvers.call({}), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -0,0 +1,40 @@
// |reftest| skip-if(!this.SharedArrayBuffer)
if (typeof SharedArrayBuffer === "function") {
const fixed = new SharedArrayBuffer(4);
assertEq(fixed.byteLength, 4);
assertEq(fixed.maxByteLength, 4);
assertEq(fixed.growable, false);
assertThrowsInstanceOf(() => fixed.grow(4), TypeError);
assertThrowsInstanceOf(() => new SharedArrayBuffer(-1), RangeError);
assertThrowsInstanceOf(() => new SharedArrayBuffer(8, {maxByteLength: 4}), RangeError);
let optionGetterCalled = false;
const growable = new SharedArrayBuffer(4, {
get maxByteLength() {
optionGetterCalled = true;
return 16;
}
});
assertEq(optionGetterCalled, true);
assertEq(growable.byteLength, 4);
assertEq(growable.maxByteLength, 16);
assertEq(growable.growable, true);
const before = new Uint8Array(growable);
before[0] = 37;
assertEq(growable.grow(12), undefined);
assertEq(growable.byteLength, 12);
assertEq(growable.maxByteLength, 16);
const after = new Uint8Array(growable);
assertEq(after.length, 12);
assertEq(after[0], 37);
assertThrowsInstanceOf(() => growable.grow(11), RangeError);
assertThrowsInstanceOf(() => growable.grow(17), RangeError);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View file

@ -0,0 +1,26 @@
// |reftest| skip-if(!String.prototype.isWellFormed||!String.prototype.toWellFormed)
assertEq("".isWellFormed(), true);
assertEq("abc".isWellFormed(), true);
assertEq("\uD83D\uDE00".isWellFormed(), true);
assertEq("\uD800".isWellFormed(), false);
assertEq("\uDC00".isWellFormed(), false);
assertEq("\uD800a".isWellFormed(), false);
assertEq("a\uDC00".isWellFormed(), false);
assertEq("\uD800\uD800\uDC00".isWellFormed(), false);
assertEq("abc".toWellFormed(), "abc");
assertEq("\uD83D\uDE00".toWellFormed(), "\uD83D\uDE00");
assertEq("\uD800".toWellFormed(), "\uFFFD");
assertEq("\uDC00".toWellFormed(), "\uFFFD");
assertEq("\uD800a\uDC00".toWellFormed(), "\uFFFDa\uFFFD");
assertEq("\uD800\uD800\uDC00".toWellFormed(), "\uFFFD\uD800\uDC00");
assertEq(String.prototype.isWellFormed.call(123), true);
assertEq(String.prototype.toWellFormed.call({ toString() { return "\uD800x"; } }), "\uFFFDx");
assertThrowsInstanceOf(() => String.prototype.isWellFormed.call(null), TypeError);
assertThrowsInstanceOf(() => String.prototype.toWellFormed.call(undefined), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -0,0 +1,36 @@
var key = Symbol("weak");
var map = new WeakMap();
assertEq(map.has(key), false);
assertEq(map.get(key), undefined);
assertEq(map.set(key, 13), map);
assertEq(map.has(key), true);
assertEq(map.get(key), 13);
assertEq(map.delete(key), true);
assertEq(map.has(key), false);
var constructedKey = Symbol("constructed");
var constructed = new WeakMap([[constructedKey, 7]]);
assertEq(constructed.get(constructedKey), 7);
var registered = Symbol.for("registered");
assertEq(map.has(registered), false);
assertEq(map.get(registered), undefined);
assertEq(map.delete(registered), false);
assertThrowsInstanceOf(() => map.set(registered, 1), TypeError);
assertThrowsInstanceOf(() => new WeakMap([[registered, 1]]), TypeError);
var setKey = Symbol("set");
var set = new WeakSet([setKey]);
assertEq(set.has(setKey), true);
assertEq(set.delete(setKey), true);
assertEq(set.has(setKey), false);
assertEq(set.add(setKey), set);
assertEq(set.has(setKey), true);
assertEq(set.has(registered), false);
assertEq(set.delete(registered), false);
assertThrowsInstanceOf(() => set.add(registered), TypeError);
assertThrowsInstanceOf(() => new WeakSet([registered]), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -0,0 +1,34 @@
// |reftest| skip-if(!Object.groupBy||!Map.groupBy)
var objectGroups = Object.groupBy(["a", "bb", "c"], value => value.length);
assertEq(Object.getPrototypeOf(objectGroups), null);
assertEqArray(objectGroups["1"], ["a", "c"]);
assertEqArray(objectGroups["2"], ["bb"]);
var symbol = Symbol();
var symbolGroups = Object.groupBy([1, 2], value => value === 1 ? symbol : "__proto__");
assertEqArray(symbolGroups[symbol], [1]);
assertEqArray(symbolGroups.__proto__, [2]);
var indexes = [];
var mapGroups = Map.groupBy(["a", "bb", "c"], (value, index) => {
indexes.push(index);
return value.length;
});
assertEq(mapGroups instanceof Map, true);
assertEqArray(indexes, [0, 1, 2]);
assertEqArray(mapGroups.get(1), ["a", "c"]);
assertEqArray(mapGroups.get(2), ["bb"]);
var key = {};
var objectKeyGroups = Map.groupBy([1, 2], value => value === 1 ? key : NaN);
assertEqArray(objectKeyGroups.get(key), [1]);
assertEqArray(objectKeyGroups.get(NaN), [2]);
assertThrowsInstanceOf(() => Object.groupBy(null, x => x), TypeError);
assertThrowsInstanceOf(() => Map.groupBy(undefined, x => x), TypeError);
assertThrowsInstanceOf(() => Object.groupBy([], null), TypeError);
assertThrowsInstanceOf(() => Map.groupBy([], null), TypeError);
if (typeof reportCompare === "function")
reportCompare(0, 0);

View file

@ -22,6 +22,7 @@
# include <valgrind/memcheck.h>
#endif
#include <algorithm>
#include "jsapi.h"
#include "jsarray.h"
#include "jscntxt.h"
@ -45,6 +46,7 @@
#include "vm/Interpreter.h"
#include "vm/SelfHosting.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"
#include "vm/WrapperObject.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmTypes.h"
@ -170,12 +172,18 @@ static const JSPropertySpec static_properties[] = {
static const JSFunctionSpec prototype_functions[] = {
JS_FN("resize", ArrayBufferObject::fun_resize, 1, 0),
JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
JS_FN("transfer", ArrayBufferObject::fun_transfer, 0, 0),
JS_FN("transferToFixedLength", ArrayBufferObject::fun_transferToFixedLength, 0, 0),
JS_FS_END
};
static const JSPropertySpec prototype_properties[] = {
JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
JS_PSG("detached", ArrayBufferObject::detachedGetter, 0),
JS_PSG("maxByteLength", ArrayBufferObject::maxByteLengthGetter, 0),
JS_PSG("resizable", ArrayBufferObject::resizableGetter, 0),
JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
JS_PS_END
};
@ -252,6 +260,52 @@ ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
ArrayBufferObject::detachedGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isDetached());
return true;
}
bool
ArrayBufferObject::detachedGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, detachedGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
ArrayBufferObject& buffer = args.thisv().toObject().as<ArrayBufferObject>();
args.rval().setInt32(buffer.isDetached() ? 0 : int32_t(buffer.maxByteLength()));
return true;
}
bool
ArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, maxByteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
ArrayBufferObject::resizableGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
args.rval().setBoolean(args.thisv().toObject().as<ArrayBufferObject>().isResizable());
return true;
}
bool
ArrayBufferObject::resizableGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, resizableGetterImpl>(cx, args);
}
/*
* ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
*/
@ -264,6 +318,39 @@ ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
GetArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
uint32_t byteLength, uint32_t* maxByteLength,
bool* resizable)
{
*maxByteLength = byteLength;
*resizable = false;
if (!options.isObject())
return true;
RootedObject opts(cx, &options.toObject());
RootedValue maxByteLengthValue(cx);
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
return false;
if (maxByteLengthValue.isUndefined())
return true;
uint64_t max;
if (!ToIndex(cx, maxByteLengthValue, &max))
return false;
if (max > INT32_MAX || max < byteLength) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
*maxByteLength = uint32_t(max);
*resizable = true;
return true;
}
// ES2017 draft 24.1.2.1
bool
@ -287,12 +374,22 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
}
// Step 3.
uint32_t maxByteLength;
bool resizable;
if (!GetArrayBufferMaxByteLengthOption(cx, args.get(1), uint32_t(byteLength),
&maxByteLength, &resizable))
{
return false;
}
// Step 4.
RootedObject proto(cx);
RootedObject newTarget(cx, &args.newTarget().toObject());
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
JSObject* bufobj = create(cx, uint32_t(byteLength), proto);
JSObject* bufobj = create(cx, uint32_t(byteLength), BufferContents::createPlain(nullptr),
OwnsData, proto, GenericObject, maxByteLength, resizable);
if (!bufobj)
return false;
args.rval().setObject(*bufobj);
@ -302,13 +399,66 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
static ArrayBufferObject::BufferContents
AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes)
{
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes);
uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes ? nbytes : 1);
if (!p)
ReportOutOfMemory(cx);
return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(p);
}
static bool
ReportArrayBufferNotResizable(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_NOT_RESIZABLE);
return false;
}
static bool
ReportArrayBufferCannotDetach(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARRAYBUFFER_CANNOT_DETACH);
return false;
}
static bool
ReportArrayBufferLengthOutOfRange(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
static bool
ArrayBufferViewFits(ArrayBufferViewObject* view, uint32_t newByteLength)
{
if (view->is<DataViewObject>()) {
DataViewObject& dataView = view->as<DataViewObject>();
uint32_t byteOffset = dataView.byteOffsetMaybeOutOfBounds();
if (byteOffset > newByteLength)
return false;
uint32_t byteLength = dataView.isLengthTracking()
? newByteLength - byteOffset
: dataView.fixedByteLengthMaybeOutOfBounds();
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
}
if (view->is<TypedArrayObject>()) {
TypedArrayObject& typedArray = view->as<TypedArrayObject>();
if (typedArray.isSharedMemory())
return true;
uint32_t byteOffset = typedArray.byteOffsetMaybeOutOfBounds();
if (byteOffset > newByteLength)
return false;
uint32_t byteLength = typedArray.isLengthTracking()
? newByteLength - byteOffset
: typedArray.fixedLengthMaybeOutOfBounds() *
typedArray.bytesPerElement();
return byteOffset <= newByteLength && byteLength <= newByteLength - byteOffset;
}
// Outline typed objects don't have a recoverable fixed byte range here.
return false;
}
static void
NoteViewBufferWasDetached(ArrayBufferViewObject* view,
ArrayBufferObject::BufferContents newContents,
@ -391,7 +541,8 @@ ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState
void
ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
uint8_t* oldDataPointer, BufferContents newContents)
uint8_t* oldDataPointer, BufferContents newContents,
uint32_t newByteLength)
{
MOZ_ASSERT(!view->isSharedMemory());
@ -402,7 +553,18 @@ ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view
uint8_t* viewDataPointer = view->dataPointerUnshared(nogc);
if (viewDataPointer) {
MOZ_ASSERT(newContents);
ptrdiff_t offset = viewDataPointer - oldDataPointer;
uint32_t offset;
if (view->is<DataViewObject>()) {
offset = view->as<DataViewObject>().byteOffsetMaybeOutOfBounds();
} else if (view->is<TypedArrayObject>()) {
offset = view->as<TypedArrayObject>().byteOffsetMaybeOutOfBounds();
} else {
ptrdiff_t oldOffset = viewDataPointer - oldDataPointer;
MOZ_ASSERT(oldOffset >= 0);
offset = uint32_t(oldOffset);
}
if (offset > newByteLength)
offset = 0;
viewDataPointer = static_cast<uint8_t*>(newContents.data()) + offset;
view->setDataPointerUnshared(viewDataPointer);
}
@ -428,10 +590,180 @@ ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
auto& innerViews = cx->compartment()->innerViews.get();
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
for (size_t i = 0; i < views->length(); i++)
changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
changeViewContents(cx, (*views)[i], oldDataPointer, newContents, byteLength());
}
if (firstView())
changeViewContents(cx, firstView(), oldDataPointer, newContents);
changeViewContents(cx, firstView(), oldDataPointer, newContents, byteLength());
}
void
ArrayBufferObject::changeContentsForResize(JSContext* cx, BufferContents newContents,
OwnsState ownsState, uint32_t newByteLength)
{
MOZ_RELEASE_ASSERT(!isWasm());
MOZ_ASSERT(!forInlineTypedObject());
uint8_t* oldDataPointer = dataPointer();
setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
setByteLength(newByteLength);
auto& innerViews = cx->compartment()->innerViews.get();
if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
for (size_t i = 0; i < views->length(); i++) {
ArrayBufferViewObject* view = (*views)[i];
if (view->is<DataViewObject>() || view->is<TypedArrayObject>())
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
else if (ArrayBufferViewFits(view, newByteLength))
changeViewContents(cx, view, oldDataPointer, newContents, newByteLength);
else
NoteViewBufferWasDetached(view, newContents, cx);
}
}
if (firstView()) {
if (firstView()->is<DataViewObject>() || firstView()->is<TypedArrayObject>())
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
else if (ArrayBufferViewFits(firstView(), newByteLength))
changeViewContents(cx, firstView(), oldDataPointer, newContents, newByteLength);
else
NoteViewBufferWasDetached(firstView(), newContents, cx);
}
}
static bool
ResizeArrayBuffer(JSContext* cx, Handle<ArrayBufferObject*> buffer, uint32_t newByteLength)
{
if (!buffer->isResizable())
return ReportArrayBufferNotResizable(cx);
if (buffer->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (newByteLength > buffer->maxByteLength())
return ReportArrayBufferLengthOutOfRange(cx);
if (!buffer->isPlain() || buffer->isPreparedForAsmJS() || buffer->forInlineTypedObject())
return ReportArrayBufferCannotDetach(cx);
if (newByteLength == buffer->byteLength())
return true;
ArrayBufferObject::BufferContents newContents = AllocateArrayBufferContents(cx, newByteLength);
if (!newContents)
return false;
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
if (copyLength > 0)
memcpy(newContents.data(), buffer->dataPointer(), copyLength);
buffer->changeContentsForResize(cx, newContents, ArrayBufferObject::OwnsData, newByteLength);
return true;
}
bool
ArrayBufferObject::fun_resize_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
if (!buffer->isResizable())
return ReportArrayBufferNotResizable(cx);
uint64_t newByteLength;
if (!ToIndex(cx, args.get(0), &newByteLength))
return false;
if (newByteLength > INT32_MAX)
return ReportArrayBufferLengthOutOfRange(cx);
if (!ResizeArrayBuffer(cx, buffer, uint32_t(newByteLength)))
return false;
args.rval().setUndefined();
return true;
}
bool
ArrayBufferObject::fun_resize(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, fun_resize_impl>(cx, args);
}
static bool
ArrayBufferTransfer(JSContext* cx, const CallArgs& args, bool preserveResizability)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
uint32_t newByteLength = buffer->byteLength();
if (args.hasDefined(0)) {
uint64_t newLength;
if (!ToIndex(cx, args.get(0), &newLength))
return false;
if (newLength > INT32_MAX)
return ReportArrayBufferLengthOutOfRange(cx);
newByteLength = uint32_t(newLength);
}
if (buffer->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (buffer->isWasm() || buffer->isPreparedForAsmJS())
return ReportArrayBufferCannotDetach(cx);
bool newResizable = preserveResizability && buffer->isResizable();
uint32_t newMaxByteLength = newResizable ? buffer->maxByteLength() : newByteLength;
if (newResizable && newByteLength > newMaxByteLength)
return ReportArrayBufferLengthOutOfRange(cx);
Rooted<ArrayBufferObject*> newBuffer(cx,
ArrayBufferObject::create(cx, newByteLength, ArrayBufferObject::BufferContents::createPlain(nullptr),
ArrayBufferObject::OwnsData, nullptr, GenericObject,
newMaxByteLength, newResizable));
if (!newBuffer)
return false;
uint32_t copyLength = std::min(newByteLength, buffer->byteLength());
if (copyLength > 0)
memcpy(newBuffer->dataPointer(), buffer->dataPointer(), copyLength);
ArrayBufferObject::BufferContents detachedContents =
buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
: buffer->contents();
ArrayBufferObject::detach(cx, buffer, detachedContents);
args.rval().setObject(*newBuffer);
return true;
}
bool
ArrayBufferObject::fun_transfer_impl(JSContext* cx, const CallArgs& args)
{
return ArrayBufferTransfer(cx, args, true);
}
bool
ArrayBufferObject::fun_transfer(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, fun_transfer_impl>(cx, args);
}
bool
ArrayBufferObject::fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args)
{
return ArrayBufferTransfer(cx, args, false);
}
bool
ArrayBufferObject::fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, fun_transferToFixedLength_impl>(cx, args);
}
/*
@ -887,6 +1219,14 @@ ArrayBufferObject::byteLength() const
return getSlot(BYTE_LENGTH_SLOT).toInt32();
}
uint32_t
ArrayBufferObject::maxByteLength() const
{
if (!isResizable())
return byteLength();
return getSlot(MAX_BYTE_LENGTH_SLOT).toInt32();
}
void
ArrayBufferObject::setByteLength(uint32_t length)
{
@ -929,7 +1269,7 @@ js::WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
if (buf->is<ArrayBufferObject>())
return buf->as<ArrayBufferObject>().wasmMaxSize();
return Some(buf->as<SharedArrayBufferObject>().byteLength());
return Some(buf->as<SharedArrayBufferObject>().maxByteLength());
}
/* static */ bool
@ -1035,9 +1375,12 @@ ArrayBufferObject*
ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents contents,
OwnsState ownsState /* = OwnsData */,
HandleObject proto /* = nullptr */,
NewObjectKind newKind /* = GenericObject */)
NewObjectKind newKind /* = GenericObject */,
uint32_t maxByteLength /* = 0 */,
bool resizable /* = false */)
{
MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);
MOZ_ASSERT_IF(resizable, maxByteLength >= nbytes);
// 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
// Refuse to allocate too large buffers, currently limited to ~2 GiB.
@ -1046,10 +1389,6 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
return nullptr;
}
// If we need to allocate data, try to use a larger object size class so
// that the array buffer's data can be allocated inline with the object.
// The extra space will be left unused by the object's fixed slots and
// available for the buffer's data, see NewObject().
size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
size_t nslots = reservedSlots;
@ -1066,18 +1405,10 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
}
} else {
MOZ_ASSERT(ownsState == OwnsData);
size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
if (nbytes <= usableSlots * sizeof(Value)) {
int newSlots = (nbytes - 1) / sizeof(Value) + 1;
MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
nslots = reservedSlots + newSlots;
contents = BufferContents::createPlain(nullptr);
} else {
contents = AllocateArrayBufferContents(cx, nbytes);
if (!contents)
return nullptr;
allocated = true;
}
contents = AllocateArrayBufferContents(cx, nbytes);
if (!contents)
return nullptr;
allocated = true;
}
MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
@ -1097,10 +1428,11 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
if (!contents) {
void* data = obj->inlineDataPointer();
js_memset(data, 0, nbytes);
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
memset(data, 0, nbytes);
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData,
maxByteLength, resizable);
} else {
obj->initialize(nbytes, contents, ownsState);
obj->initialize(nbytes, contents, ownsState, maxByteLength, resizable);
}
return obj;
@ -1130,25 +1462,28 @@ ArrayBufferObject::createEmpty(JSContext* cx)
bool
ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsArrayBuffer(args.thisv()));
MOZ_ASSERT(IsAnyArrayBuffer(args.thisv()));
/*
* This method is only called for |DataView(alienBuf, ...)| which calls
* this as |createDataViewForThis.call(alienBuf, byteOffset, byteLength,
* DataView.prototype)|,
* ergo there must be exactly 3 arguments.
* DataView.prototype, lengthTracking)|,
* ergo there must be exactly 4 arguments.
*/
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args.length() == 4);
uint32_t byteOffset = args[0].toPrivateUint32();
uint32_t byteLength = args[1].toPrivateUint32();
Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
bool lengthTracking = args[3].toBoolean();
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx,
&args.thisv().toObject().as<ArrayBufferObjectMaybeShared>());
/*
* Pop off the passed-along prototype and delegate to normal DataViewObject
* construction.
*/
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, &args[2].toObject());
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer,
&args[2].toObject(), lengthTracking);
if (!obj)
return false;
args.rval().setObject(*obj);
@ -1159,7 +1494,7 @@ bool
ArrayBufferObject::createDataViewForThis(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
return CallNonGenericMethod<IsAnyArrayBuffer, createDataViewForThisImpl>(cx, args);
}
/* static */ ArrayBufferObject::BufferContents
@ -1528,6 +1863,8 @@ ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
// The data may or may not be inline with the buffer. The buffer
// can only move during a compacting GC, in which case its
// objectMoved hook has already updated the buffer's data pointer.
if (offset > buf.byteLength())
offset = 0;
obj->initPrivate(buf.dataPointer() + offset);
}
}
@ -1578,6 +1915,8 @@ ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
bool
ArrayBufferViewObject::isSharedMemory()
{
if (is<DataViewObject>())
return as<DataViewObject>().isSharedMemory();
if (is<TypedArrayObject>())
return as<TypedArrayObject>().isSharedMemory();
return false;
@ -1609,7 +1948,7 @@ ArrayBufferViewObject::bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*
return thisObject->as<TypedArrayObject>().bufferEither();
}
MOZ_ASSERT(thisObject->is<DataViewObject>());
return &thisObject->as<DataViewObject>().arrayBuffer();
return &thisObject->as<DataViewObject>().arrayBufferEither();
}
/* JS Friend API */
@ -1860,7 +2199,7 @@ JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoChe
if (!obj)
return nullptr;
if (obj->is<DataViewObject>()) {
*isSharedMemory = false;
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
return obj->as<DataViewObject>().dataPointer();
}
TypedArrayObject& ta = obj->as<TypedArrayObject>();
@ -1930,7 +2269,7 @@ js::GetArrayBufferViewLengthAndData(JSObject* obj, uint32_t* length, bool* isSha
: obj->as<TypedArrayObject>().byteLength();
if (obj->is<DataViewObject>()) {
*isSharedMemory = false;
*isSharedMemory = obj->as<DataViewObject>().isSharedMemory();
*data = static_cast<uint8_t*>(obj->as<DataViewObject>().dataPointer());
}
else {

View file

@ -82,7 +82,7 @@ ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
class ArrayBufferObjectMaybeShared : public NativeObject
{
public:
uint32_t byteLength() {
uint32_t byteLength() const {
return AnyArrayBufferByteLength(this);
}
@ -128,15 +128,22 @@ typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObj
class ArrayBufferObject : public ArrayBufferObjectMaybeShared
{
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool resizableGetterImpl(JSContext* cx, const CallArgs& args);
static bool detachedGetterImpl(JSContext* cx, const CallArgs& args);
static bool fun_slice_impl(JSContext* cx, const CallArgs& args);
static bool fun_resize_impl(JSContext* cx, const CallArgs& args);
static bool fun_transfer_impl(JSContext* cx, const CallArgs& args);
static bool fun_transferToFixedLength_impl(JSContext* cx, const CallArgs& args);
public:
static const uint8_t DATA_SLOT = 0;
static const uint8_t BYTE_LENGTH_SLOT = 1;
static const uint8_t FIRST_VIEW_SLOT = 2;
static const uint8_t FLAGS_SLOT = 3;
static const uint8_t MAX_BYTE_LENGTH_SLOT = 4;
static const uint8_t RESERVED_SLOTS = 4;
static const uint8_t RESERVED_SLOTS = 5;
static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
@ -189,7 +196,11 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
// This PLAIN or WASM buffer has been prepared for asm.js and cannot
// henceforth be transferred/detached.
FOR_ASMJS = 0x40
FOR_ASMJS = 0x40,
// This buffer was created with [[ArrayBufferMaxByteLength]] and can
// be resized up to that maximum.
RESIZABLE = 0x80
};
static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
@ -230,8 +241,14 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
static const Class class_;
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool resizableGetter(JSContext* cx, unsigned argc, Value* vp);
static bool detachedGetter(JSContext* cx, unsigned argc, Value* vp);
static bool fun_slice(JSContext* cx, unsigned argc, Value* vp);
static bool fun_resize(JSContext* cx, unsigned argc, Value* vp);
static bool fun_transfer(JSContext* cx, unsigned argc, Value* vp);
static bool fun_transferToFixedLength(JSContext* cx, unsigned argc, Value* vp);
static bool fun_isView(JSContext* cx, unsigned argc, Value* vp);
@ -243,7 +260,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
BufferContents contents,
OwnsState ownsState = OwnsData,
HandleObject proto = nullptr,
NewObjectKind newKind = GenericObject);
NewObjectKind newKind = GenericObject,
uint32_t maxByteLength = 0,
bool resizable = false);
static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes,
HandleObject proto = nullptr,
NewObjectKind newKind = GenericObject);
@ -294,6 +313,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
void setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState);
void changeContents(JSContext* cx, BufferContents newContents, OwnsState ownsState);
void changeContentsForResize(JSContext* cx, BufferContents newContents,
OwnsState ownsState, uint32_t newByteLength);
// Detach this buffer from its original memory. (This necessarily makes
// views of this buffer unusable for modifying that original memory.)
@ -302,7 +323,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
private:
void changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
uint8_t* oldDataPointer, BufferContents newContents);
uint8_t* oldDataPointer, BufferContents newContents,
uint32_t newByteLength);
void setFirstView(ArrayBufferViewObject* view);
uint8_t* inlineDataPointer() const;
@ -311,6 +333,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
uint8_t* dataPointer() const;
SharedMem<uint8_t*> dataPointerShared() const;
uint32_t byteLength() const;
uint32_t maxByteLength() const;
BufferContents contents() const {
return BufferContents(dataPointer(), bufferKind());
@ -334,6 +357,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
bool isWasm() const { return bufferKind() == WASM; }
bool isMapped() const { return bufferKind() == MAPPED; }
bool isDetached() const { return flags() & DETACHED; }
bool isResizable() const { return flags() & RESIZABLE; }
bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
// WebAssembly support:
@ -391,12 +415,17 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
void setIsDetached() { setFlags(flags() | DETACHED); }
void setIsPreparedForAsmJS() { setFlags(flags() | FOR_ASMJS); }
void setIsResizable() { setFlags(flags() | RESIZABLE); }
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState,
uint32_t maxByteLength = 0, bool resizable = false) {
setByteLength(byteLength);
setFlags(0);
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(maxByteLength ? maxByteLength : byteLength));
setFirstView(nullptr);
setDataPointer(contents, ownsState);
if (resizable)
setIsResizable();
}
// Note: initialize() may be called after initEmpty(); initEmpty() must
@ -404,6 +433,7 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
void initEmpty() {
setByteLength(0);
setFlags(0);
setFixedSlot(MAX_BYTE_LENGTH_SLOT, Int32Value(0));
setFirstView(nullptr);
setDataPointer(BufferContents::createPlain(nullptr), DoesntOwnData);
}

View file

@ -248,6 +248,7 @@
macro(lookupSetter, lookupSetter, "__lookupSetter__") \
macro(MapConstructorInit, MapConstructorInit, "MapConstructorInit") \
macro(MapIterator, MapIterator, "Map Iterator") \
macro(maxByteLength, maxByteLength, "maxByteLength") \
macro(maximumFractionDigits, maximumFractionDigits, "maximumFractionDigits") \
macro(maximumSignificantDigits, maximumSignificantDigits, "maximumSignificantDigits") \
macro(message, message, "message") \

View file

@ -27,6 +27,7 @@
#include "builtin/SymbolObject.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakMapObject.h"
#include "builtin/WeakRefObject.h"
#include "builtin/WeakSetObject.h"
#include "vm/Debugger.h"
#include "vm/EnvironmentObject.h"
@ -464,6 +465,7 @@ GlobalObject::initStandardClasses(JSContext* cx, Handle<GlobalObject*> global)
if (!ensureConstructor(cx, global, static_cast<JSProtoKey>(k)))
return false;
}
return true;
}
@ -538,6 +540,7 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle<GlobalObject*> globa
InitBareBuiltinCtor(cx, global, JSProto_Int32Array) &&
InitBareSymbolCtor(cx, global) &&
InitBareWeakMapCtor(cx, global) &&
InitBareWeakRefCtor(cx, global) &&
InitStopIterationClass(cx, global) &&
DefineFunctions(cx, global, builtins, AsIntrinsic);
}

View file

@ -43,6 +43,22 @@ static const ObjectElements emptyElementsHeaderShared(0, 0, ObjectElements::Shar
HeapSlot* const js::emptyObjectElementsShared =
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements));
static const ObjectElements emptyElementsHeaderResizableOrGrowable(
0, 0, ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
/* Objects with no elements share one empty set of elements. */
HeapSlot* const js::emptyObjectElementsResizableOrGrowable =
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderResizableOrGrowable) +
sizeof(ObjectElements));
static const ObjectElements emptyElementsHeaderSharedResizableOrGrowable(
0, 0, ObjectElements::SHARED_MEMORY | ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER);
/* Objects with no elements share one empty set of elements. */
HeapSlot* const js::emptyObjectElementsSharedResizableOrGrowable =
reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeaderSharedResizableOrGrowable) +
sizeof(ObjectElements));
#ifdef DEBUG
@ -61,10 +77,12 @@ ObjectElements::ConvertElementsToDoubles(JSContext* cx, uintptr_t elementsPtr)
* This function is infallible, but has a fallible interface so that it can
* be called directly from Ion code. Only arrays can have their dense
* elements converted to doubles, and arrays never have empty elements.
*/
*/
HeapSlot* elementsHeapPtr = (HeapSlot*) elementsPtr;
MOZ_ASSERT(elementsHeapPtr != emptyObjectElements &&
elementsHeapPtr != emptyObjectElementsShared);
elementsHeapPtr != emptyObjectElementsShared &&
elementsHeapPtr != emptyObjectElementsResizableOrGrowable &&
elementsHeapPtr != emptyObjectElementsSharedResizableOrGrowable);
ObjectElements* header = ObjectElements::fromElements(elementsHeapPtr);
MOZ_ASSERT(!header->shouldConvertDoubleElements());

View file

@ -185,6 +185,11 @@ class ObjectElements
// These elements are set to integrity level "frozen".
FROZEN = 0x10,
// For TypedArrays only: this TypedArray views a resizable
// ArrayBuffer or growable SharedArrayBuffer. JIT fast paths with
// cached length/data assumptions must fall back for these objects.
RESIZABLE_OR_GROWABLE_BUFFER = 0x20,
};
private:
@ -255,6 +260,10 @@ class ObjectElements
: flags(SHARED_MEMORY), initializedLength(0), capacity(capacity), length(length)
{}
constexpr ObjectElements(uint32_t capacity, uint32_t length, uint32_t flags)
: flags(flags), initializedLength(0), capacity(capacity), length(length)
{}
HeapSlot* elements() {
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
}
@ -269,6 +278,10 @@ class ObjectElements
return flags & SHARED_MEMORY;
}
bool hasResizableOrGrowableBuffer() const {
return flags & RESIZABLE_OR_GROWABLE_BUFFER;
}
GCPtrNativeObject& ownerObject() const {
MOZ_ASSERT(isCopyOnWrite());
return *(GCPtrNativeObject*)(&elements()[initializedLength]);
@ -326,6 +339,8 @@ static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == sizeof(Obj
*/
extern HeapSlot* const emptyObjectElements;
extern HeapSlot* const emptyObjectElementsShared;
extern HeapSlot* const emptyObjectElementsResizableOrGrowable;
extern HeapSlot* const emptyObjectElementsSharedResizableOrGrowable;
struct Class;
class GCMarker;
@ -480,6 +495,12 @@ class NativeObject : public ShapedObject
elements_ = emptyObjectElementsShared;
}
void setHasResizableOrGrowableBuffer() {
MOZ_ASSERT(elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared);
elements_ = isSharedMemory() ? emptyObjectElementsSharedResizableOrGrowable
: emptyObjectElementsResizableOrGrowable;
}
bool isInWholeCellBuffer() const {
const gc::TenuredCell* cell = &asTenured();
gc::ArenaCellSet* cells = cell->arena()->bufferedCells;
@ -1254,7 +1275,10 @@ class NativeObject : public ShapedObject
}
inline bool hasEmptyElements() const {
return elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared;
return elements_ == emptyObjectElements ||
elements_ == emptyObjectElementsShared ||
elements_ == emptyObjectElementsResizableOrGrowable ||
elements_ == emptyObjectElementsSharedResizableOrGrowable;
}
/*

View file

@ -38,6 +38,7 @@
#include "builtin/SelfHostingDefines.h"
#include "builtin/Stream.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakRefObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
#include "gc/Policy.h"
@ -105,6 +106,14 @@ intrinsic_IsObject(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
intrinsic_CanBeHeldWeakly(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(CanBeHeldWeakly(args.get(0)));
return true;
}
static bool
intrinsic_IsArray(JSContext* cx, unsigned argc, Value* vp)
{
@ -270,6 +279,20 @@ intrinsic_GetBuiltinConstructor(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
intrinsic_NewMap(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
Rooted<MapObject*> map(cx, MapObject::create(cx));
if (!map)
return false;
args.rval().setObject(*map);
return true;
}
static bool
intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp)
{
@ -1287,6 +1310,36 @@ intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer(JSContext* cx, unsigned arg
return true;
}
static bool
intrinsic_TypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedObject obj(cx, &args[0].toObject());
MOZ_ASSERT(obj->is<TypedArrayObject>());
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
return true;
}
static bool
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
JSObject* obj = CheckedUnwrap(&args[0].toObject());
if (!obj) {
JS_ReportErrorASCII(cx, "Permission denied to access object");
return false;
}
MOZ_ASSERT(obj->is<TypedArrayObject>());
args.rval().setBoolean(obj->as<TypedArrayObject>().isOutOfBounds());
return true;
}
static bool
intrinsic_MoveTypedArrayElements(JSContext* cx, unsigned argc, Value* vp)
{
@ -1409,6 +1462,10 @@ intrinsic_SetFromTypedArrayApproach(JSContext* cx, unsigned argc, Value* vp)
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (unsafeTypedArrayCrossCompartment->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
// Steps 21, 23.
uint32_t unsafeSrcLengthCrossCompartment = unsafeTypedArrayCrossCompartment->length();
@ -2268,7 +2325,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("std_Math_min", math_min, 2,0, MathMin),
JS_INLINABLE_FN("std_Math_abs", math_abs, 1,0, MathAbs),
JS_FN("std_Map_create", intrinsic_NewMap, 0,0),
JS_FN("std_Map_get", MapObject::get, 1,0),
JS_FN("std_Map_has", MapObject::has, 1,0),
JS_FN("std_Map_set", MapObject::set, 2,0),
JS_FN("std_Map_iterator", MapObject::entries, 0,0),
JS_FN("std_Number_valueOf", num_valueOf, 0,0),
@ -2316,6 +2376,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
// Helper funtions after this point.
JS_INLINABLE_FN("ToObject", intrinsic_ToObject, 1,0, IntrinsicToObject),
JS_INLINABLE_FN("IsObject", intrinsic_IsObject, 1,0, IntrinsicIsObject),
JS_FN("CanBeHeldWeakly", intrinsic_CanBeHeldWeakly, 1,0),
JS_INLINABLE_FN("IsArray", intrinsic_IsArray, 1,0, ArrayIsArray),
JS_INLINABLE_FN("IsWrappedArrayConstructor", intrinsic_IsWrappedArrayConstructor, 1,0,
IntrinsicIsWrappedArrayConstructor),
@ -2475,6 +2536,10 @@ static const JSFunctionSpec intrinsic_functions[] = {
1, 0, IntrinsicPossiblyWrappedTypedArrayLength),
JS_FN("PossiblyWrappedTypedArrayHasDetachedBuffer",
intrinsic_PossiblyWrappedTypedArrayHasDetachedBuffer, 1, 0),
JS_FN("TypedArrayIsOutOfBounds",
intrinsic_TypedArrayIsOutOfBounds, 1, 0),
JS_FN("PossiblyWrappedTypedArrayIsOutOfBounds",
intrinsic_PossiblyWrappedTypedArrayIsOutOfBounds, 1, 0),
JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4,0),
JS_FN("SetFromTypedArrayApproach",intrinsic_SetFromTypedArrayApproach, 4, 0),

View file

@ -8,6 +8,7 @@
#include "mozilla/Atomics.h"
#include "jsfriendapi.h"
#include "jsnum.h"
#include "jsprf.h"
#ifdef XP_WIN
@ -104,15 +105,18 @@ SharedArrayAllocSize(uint32_t length)
}
SharedArrayRawBuffer*
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
SharedArrayRawBuffer::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable)
{
// The value (uint32_t)-1 is used as a signal in various places,
// so guard against it on principle.
MOZ_ASSERT(length != (uint32_t)-1);
MOZ_ASSERT(maxLength != (uint32_t)-1);
MOZ_ASSERT(maxLength >= length);
// Add a page for the header and round to a page boundary.
uint32_t allocSize = SharedArrayAllocSize(length);
if (allocSize <= length)
uint32_t allocationLength = growable ? maxLength : length;
uint32_t allocSize = SharedArrayAllocSize(allocationLength);
if (allocSize <= allocationLength)
return nullptr;
// Test >= to guard against the case where multiple extant runtimes
@ -127,7 +131,8 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
}
}
bool preparedForAsmJS = jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
bool preparedForAsmJS =
!growable && jit::JitOptions.asmJSAtomicsEnable && IsValidAsmJSHeapLength(length);
void* p = nullptr;
if (preparedForAsmJS) {
@ -161,8 +166,9 @@ SharedArrayRawBuffer::New(JSContext* cx, uint32_t length)
uint8_t* buffer = reinterpret_cast<uint8_t*>(p) + gc::SystemPageSize();
uint8_t* base = buffer - sizeof(SharedArrayRawBuffer);
SharedArrayRawBuffer* rawbuf = new (base) SharedArrayRawBuffer(buffer, length, preparedForAsmJS);
MOZ_ASSERT(rawbuf->length == length); // Deallocation needs this
SharedArrayRawBuffer* rawbuf =
new (base) SharedArrayRawBuffer(buffer, length, maxLength, growable, preparedForAsmJS);
MOZ_ASSERT(rawbuf->allocatedByteLength() == allocationLength); // Deallocation needs this.
return rawbuf;
}
@ -201,7 +207,7 @@ SharedArrayRawBuffer::dropReference()
MOZ_ASSERT(p.asValue() % gc::SystemPageSize() == 0);
uint8_t* address = p.unwrap(/*safe - only reference*/);
uint32_t allocSize = SharedArrayAllocSize(this->length);
uint32_t allocSize = SharedArrayAllocSize(this->allocatedByteLength());
if (this->preparedForAsmJS) {
uint32_t mappedSize = SharedArrayMappedSize(allocSize);
@ -221,6 +227,23 @@ SharedArrayRawBuffer::dropReference()
numLive--;
}
bool
SharedArrayRawBuffer::growTo(uint32_t newLength)
{
MOZ_ASSERT(growable);
MOZ_ASSERT(newLength <= maxLength);
for (;;) {
uint32_t oldLength = length;
if (newLength < oldLength)
return false;
if (newLength == oldLength)
return true;
if (length.compareExchange(oldLength, newLength))
return true;
}
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
@ -237,6 +260,112 @@ SharedArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* v
return CallNonGenericMethod<IsSharedArrayBuffer, byteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
args.rval().setInt32(args.thisv().toObject().as<SharedArrayBufferObject>().maxByteLength());
return true;
}
bool
SharedArrayBufferObject::maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsSharedArrayBuffer, maxByteLengthGetterImpl>(cx, args);
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::growableGetterImpl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
args.rval().setBoolean(args.thisv().toObject().as<SharedArrayBufferObject>().isGrowable());
return true;
}
bool
SharedArrayBufferObject::growableGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsSharedArrayBuffer, growableGetterImpl>(cx, args);
}
static bool
ReportSharedArrayBufferNotGrowable(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_NOT_GROWABLE);
return false;
}
static bool
ReportSharedArrayBufferLengthOutOfRange(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
return false;
}
static bool
GetSharedArrayBufferMaxByteLengthOption(JSContext* cx, HandleValue options,
uint32_t byteLength, uint32_t* maxByteLength,
bool* growable)
{
*maxByteLength = byteLength;
*growable = false;
if (!options.isObject())
return true;
RootedObject opts(cx, &options.toObject());
RootedValue maxByteLengthValue(cx);
if (!GetProperty(cx, opts, opts, cx->names().maxByteLength, &maxByteLengthValue))
return false;
if (maxByteLengthValue.isUndefined())
return true;
uint64_t max;
if (!ToIndex(cx, maxByteLengthValue, &max))
return false;
if (max > INT32_MAX || max < byteLength)
return ReportSharedArrayBufferLengthOutOfRange(cx);
*maxByteLength = uint32_t(max);
*growable = true;
return true;
}
MOZ_ALWAYS_INLINE bool
SharedArrayBufferObject::fun_grow_impl(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(IsSharedArrayBuffer(args.thisv()));
Rooted<SharedArrayBufferObject*> buffer(cx,
&args.thisv().toObject().as<SharedArrayBufferObject>());
if (!buffer->isGrowable())
return ReportSharedArrayBufferNotGrowable(cx);
uint64_t newByteLength;
if (!ToIndex(cx, args.get(0), &newByteLength))
return false;
if (newByteLength > INT32_MAX)
return ReportSharedArrayBufferLengthOutOfRange(cx);
uint32_t newLength = uint32_t(newByteLength);
if (newLength > buffer->maxByteLength() || !buffer->growTo(newLength))
return ReportSharedArrayBufferLengthOutOfRange(cx);
args.rval().setUndefined();
return true;
}
bool
SharedArrayBufferObject::fun_grow(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsSharedArrayBuffer, fun_grow_impl>(cx, args);
}
bool
SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
{
@ -245,11 +374,19 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
if (!ThrowIfNotConstructing(cx, args, "SharedArrayBuffer"))
return false;
uint64_t length64;
if (!ToIndex(cx, args.get(0), &length64))
return false;
// Bugs 1068458, 1161298: Limit length to 2^31-1.
uint32_t length;
bool overflow_unused;
if (!ToLengthClamped(cx, args.get(0), &length, &overflow_unused) || length > INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SHARED_ARRAY_BAD_LENGTH);
if (length64 > INT32_MAX)
return ReportSharedArrayBufferLengthOutOfRange(cx);
uint32_t length = uint32_t(length64);
uint32_t maxByteLength;
bool growable;
if (!GetSharedArrayBufferMaxByteLengthOption(cx, args.get(1), length,
&maxByteLength, &growable))
{
return false;
}
@ -258,7 +395,7 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
JSObject* bufobj = New(cx, length, proto);
JSObject* bufobj = New(cx, length, maxByteLength, growable, proto);
if (!bufobj)
return false;
args.rval().setObject(*bufobj);
@ -266,9 +403,10 @@ SharedArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value*
}
SharedArrayBufferObject*
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, HandleObject proto)
SharedArrayBufferObject::New(JSContext* cx, uint32_t length, uint32_t maxLength, bool growable,
HandleObject proto)
{
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length);
SharedArrayRawBuffer* buffer = SharedArrayRawBuffer::New(cx, length, maxLength, growable);
if (!buffer)
return nullptr;
@ -341,7 +479,7 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi
// just live with the risk.
const SharedArrayBufferObject& buf = obj->as<SharedArrayBufferObject>();
info->objectsNonHeapElementsShared +=
buf.byteLength() / buf.rawBufferObject()->refcount();
buf.rawBufferObject()->allocatedByteLength() / buf.rawBufferObject()->refcount();
}
/* static */ void
@ -409,12 +547,15 @@ static const JSPropertySpec static_properties[] = {
};
static const JSFunctionSpec prototype_functions[] = {
JS_FN("grow", SharedArrayBufferObject::fun_grow, 1, 0),
JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0),
JS_FS_END
};
static const JSPropertySpec prototype_properties[] = {
JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
JS_PSG("growable", SharedArrayBufferObject::growableGetter, 0),
JS_PSG("maxByteLength", SharedArrayBufferObject::maxByteLengthGetter, 0),
JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
JS_PS_END
};

View file

@ -44,7 +44,9 @@ class SharedArrayRawBuffer
{
private:
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refcount_;
uint32_t length;
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> length;
uint32_t maxLength;
bool growable;
bool preparedForAsmJS;
// A list of structures representing tasks waiting on some
@ -52,9 +54,12 @@ class SharedArrayRawBuffer
FutexWaiter* waiters_;
protected:
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, bool preparedForAsmJS)
SharedArrayRawBuffer(uint8_t* buffer, uint32_t length, uint32_t maxLength, bool growable,
bool preparedForAsmJS)
: refcount_(1),
length(length),
maxLength(maxLength),
growable(growable),
preparedForAsmJS(preparedForAsmJS),
waiters_(nullptr)
{
@ -62,7 +67,11 @@ class SharedArrayRawBuffer
}
public:
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length);
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length, uint32_t maxLength,
bool growable);
static SharedArrayRawBuffer* New(JSContext* cx, uint32_t length) {
return New(cx, length, length, false);
}
// This may be called from multiple threads. The caller must take
// care of mutual exclusion.
@ -85,6 +94,20 @@ class SharedArrayRawBuffer
return length;
}
uint32_t maxByteLength() const {
return growable ? maxLength : byteLength();
}
uint32_t allocatedByteLength() const {
return growable ? maxLength : byteLength();
}
bool isGrowable() const {
return growable;
}
[[nodiscard]] bool growTo(uint32_t newLength);
bool isPreparedForAsmJS() const {
return preparedForAsmJS;
}
@ -117,6 +140,9 @@ class SharedArrayRawBuffer
class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
{
static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
static bool growableGetterImpl(JSContext* cx, const CallArgs& args);
static bool fun_grow_impl(JSContext* cx, const CallArgs& args);
public:
// RAWBUF_SLOT holds a pointer (as "private" data) to the
@ -128,13 +154,23 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
static const Class class_;
static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
static bool growableGetter(JSContext* cx, unsigned argc, Value* vp);
static bool fun_grow(JSContext* cx, unsigned argc, Value* vp);
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
// Create a SharedArrayBufferObject with a new SharedArrayRawBuffer.
static SharedArrayBufferObject* New(JSContext* cx,
uint32_t length,
uint32_t maxLength,
bool growable,
HandleObject proto = nullptr);
static SharedArrayBufferObject* New(JSContext* cx,
uint32_t length,
HandleObject proto = nullptr) {
return New(cx, length, length, false, proto);
}
// Create a SharedArrayBufferObject using an existing SharedArrayRawBuffer.
static SharedArrayBufferObject* New(JSContext* cx,
@ -164,6 +200,15 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
uint32_t byteLength() const {
return rawBufferObject()->byteLength();
}
uint32_t maxByteLength() const {
return rawBufferObject()->maxByteLength();
}
bool isGrowable() const {
return rawBufferObject()->isGrowable();
}
[[nodiscard]] bool growTo(uint32_t newLength) {
return rawBufferObject()->growTo(newLength);
}
bool isPreparedForAsmJS() const {
return rawBufferObject()->isPreparedForAsmJS();
}

View file

@ -45,6 +45,7 @@
#include "builtin/MapObject.h"
#include "js/Date.h"
#include "js/GCHashTable.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/SavedFrame.h"
#include "vm/SharedArrayObject.h"
#include "vm/TypedArrayObject.h"

View file

@ -1065,16 +1065,40 @@ class TypedArrayMethods
if (!ToInt32(cx, args[1], &offset))
return false;
if (offset < 0 || uint32_t(offset) > target->length()) {
// the given offset is bogus
if (offset < 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
}
if (target->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (target->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
uint32_t targetLength = target->length();
if (uint32_t(offset) > targetLength) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
RootedObject arg0(cx, &args[0].toObject());
if (arg0->is<TypedArrayObject>()) {
if (arg0->as<TypedArrayObject>().length() > target->length() - offset) {
Rooted<TypedArrayObject*> source(cx, &arg0->as<TypedArrayObject>());
if (source->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (source->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
if (source->length() > targetLength - offset) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
@ -1086,7 +1110,7 @@ class TypedArrayMethods
if (!GetLengthProperty(cx, arg0, &len))
return false;
if (uint32_t(offset) > target->length() || len > target->length() - offset) {
if (len > targetLength - offset) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
return false;
}
@ -1099,13 +1123,32 @@ class TypedArrayMethods
return true;
}
static bool
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
uint32_t offset = 0)
{
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
static bool
setFromTypedArray(JSContext* cx, Handle<SomeTypedArray*> target, HandleObject source,
uint32_t offset = 0)
{
MOZ_ASSERT(source->is<TypedArrayObject>(), "use setFromNonTypedArray");
bool isShared = target->isSharedMemory() || source->as<TypedArrayObject>().isSharedMemory();
if (target->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (target->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
Rooted<TypedArrayObject*> sourceArray(cx, &source->as<TypedArrayObject>());
if (sourceArray->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
if (sourceArray->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return false;
}
bool isShared = target->isSharedMemory() || sourceArray->isSharedMemory();
switch (target->type()) {
case Scalar::Int8:

View file

@ -484,8 +484,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static TypedArrayObject*
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset, uint32_t len,
HandleObject proto)
makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
uint32_t byteOffset, uint32_t len, HandleObject proto,
bool lengthTracking = false)
{
MOZ_ASSERT_IF(!buffer, byteOffset == 0);
@ -510,12 +511,19 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr;
bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get());
bool hasResizableOrGrowableBuffer =
buffer &&
((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
(buffer->is<SharedArrayBufferObject>() &&
buffer->as<SharedArrayBufferObject>().isGrowable()));
obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer));
// This is invariant. Self-hosting code that sets BUFFER_SLOT
// (if it does) must maintain it, should it need to.
if (isSharedMemory)
obj->setIsSharedMemory();
if (hasResizableOrGrowableBuffer)
obj->setHasResizableOrGrowableBuffer();
if (buffer) {
obj->initViewData(buffer->dataPointerEither() + byteOffset);
@ -549,7 +557,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject
#endif
}
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(len));
obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT,
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
: int32_t(len)));
obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
#ifdef DEBUG
@ -898,6 +908,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr; // invalid byteOffset
}
bool lengthTracking = false;
uint32_t len;
if (lengthInt == -1) {
len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
@ -906,6 +917,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject
JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS);
return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
}
if ((buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
(buffer->is<SharedArrayBufferObject>() &&
buffer->as<SharedArrayBufferObject>().isGrowable()))
{
lengthTracking = true;
}
} else {
len = uint32_t(lengthInt);
}
@ -924,7 +941,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr; // byteOffset + len is too big for the arraybuffer
}
return makeInstance(cx, buffer, byteOffset, len, proto);
return makeInstance(cx, buffer, byteOffset, len, proto, lengthTracking);
}
static bool
@ -1320,6 +1337,10 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, b
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return nullptr;
}
if (srcArray->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_OUT_OF_BOUNDS);
return nullptr;
}
// Step 9.
uint32_t elementLength = srcArray->length();
@ -1859,7 +1880,8 @@ DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto)
DataViewObject*
DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
Handle<ArrayBufferObject*> arrayBuffer, JSObject* protoArg)
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* protoArg,
bool lengthTracking)
{
if (arrayBuffer->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
@ -1868,8 +1890,18 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
MOZ_ASSERT(byteOffset <= INT32_MAX);
MOZ_ASSERT(byteLength <= INT32_MAX);
MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX);
MOZ_ASSERT(!arrayBuffer || !arrayBuffer->is<SharedArrayBufferObject>());
uint32_t bufferByteLength = arrayBuffer->byteLength();
if (byteOffset > bufferByteLength ||
(!lengthTracking && byteLength > bufferByteLength - byteOffset))
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
"1");
return nullptr;
}
if (lengthTracking)
byteLength = bufferByteLength - byteOffset;
RootedObject proto(cx, protoArg);
RootedObject obj(cx);
@ -1893,57 +1925,72 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
}
}
// Caller should have established these preconditions, and no
// (non-self-hosted) JS code has had an opportunity to run so nothing can
// have invalidated them.
MOZ_ASSERT(byteOffset <= arrayBuffer->byteLength());
MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength());
DataViewObject& dvobj = obj->as<DataViewObject>();
dvobj.setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(byteLength));
dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT,
Int32Value(lengthTracking ? TypedArrayObject::LENGTH_TRACKING
: int32_t(byteLength)));
dvobj.setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*arrayBuffer));
dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset);
auto dataPointer = arrayBuffer->dataPointerEither();
dvobj.initPrivate((dataPointer + byteOffset).unwrap(/*safe - stored as private data*/));
// Include a barrier if the data view's data pointer is in the nursery, as
// is done for typed arrays.
if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(arrayBuffer->dataPointer()))
if (arrayBuffer->is<ArrayBufferObject>() &&
!IsInsideNursery(obj) &&
cx->runtime()->gc.nursery.isInside(dataPointer))
{
cx->runtime()->gc.storeBuffer.putWholeCell(obj);
}
// Verify that the private slot is at the expected place
MOZ_ASSERT(dvobj.numFixedSlots() == TypedArrayObject::DATA_SLOT);
if (!arrayBuffer->addView(cx, &dvobj))
return nullptr;
if (arrayBuffer->is<ArrayBufferObject>()) {
if (!arrayBuffer->as<ArrayBufferObject>().addView(cx, &dvobj))
return nullptr;
}
return &dvobj;
}
bool
DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr)
uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr,
bool* lengthTrackingPtr)
{
if (!IsArrayBuffer(bufobj)) {
if (!IsAnyArrayBuffer(bufobj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
"DataView", "ArrayBuffer", bufobj->getClass()->name);
"DataView", "ArrayBuffer or SharedArrayBuffer",
bufobj->getClass()->name);
return false;
}
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
uint32_t byteOffset = 0;
uint32_t byteLength = buffer->byteLength();
bool isResizableOrGrowable =
(buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isResizable()) ||
(buffer->is<SharedArrayBufferObject>() &&
buffer->as<SharedArrayBufferObject>().isGrowable());
bool lengthTracking = isResizableOrGrowable && !args.hasDefined(2);
if (args.length() > 1) {
if (!ToUint32(cx, args[1], &byteOffset))
uint64_t offset;
if (!ToIndex(cx, args[1], &offset))
return false;
if (byteOffset > INT32_MAX) {
if (offset > INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
"1");
return false;
}
byteOffset = uint32_t(offset);
}
if (buffer->isDetached()) {
if (buffer->is<ArrayBufferObject>() && buffer->as<ArrayBufferObject>().isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
@ -1957,14 +2004,18 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
if (args.get(2).isUndefined()) {
byteLength -= byteOffset;
lengthTracking = isResizableOrGrowable;
} else {
if (!ToUint32(cx, args[2], &byteLength))
uint64_t viewByteLength;
if (!ToIndex(cx, args[2], &viewByteLength))
return false;
if (byteLength > INT32_MAX) {
lengthTracking = false;
if (viewByteLength > INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ARG_INDEX_OUT_OF_RANGE, "2");
return false;
}
byteLength = uint32_t(viewByteLength);
MOZ_ASSERT(byteOffset + byteLength >= byteOffset,
"can't overflow: both numbers are less than INT32_MAX");
@ -1983,6 +2034,7 @@ DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, cons
*byteOffsetPtr = byteOffset;
*byteLengthPtr = byteLength;
*lengthTrackingPtr = lengthTracking;
return true;
}
@ -1994,7 +2046,8 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
assertSameCompartment(cx, bufobj);
uint32_t byteOffset, byteLength;
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength))
bool lengthTracking;
if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength, &lengthTracking))
return false;
RootedObject proto(cx);
@ -2002,8 +2055,9 @@ DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, con
if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
return false;
Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(bufobj));
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto);
Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &bufobj->as<ArrayBufferObjectMaybeShared>());
JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto,
lengthTracking);
if (!obj)
return false;
args.rval().setObject(*obj);
@ -2043,8 +2097,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
// NB: This entails the IsArrayBuffer check
uint32_t byteOffset, byteLength;
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength))
bool lengthTracking;
if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength,
&lengthTracking))
{
return false;
}
// Make sure to get the [[Prototype]] for the created view from this
// compartment.
@ -2060,11 +2118,12 @@ DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallA
return false;
}
FixedInvokeArgs<3> args2(cx);
FixedInvokeArgs<4> args2(cx);
args2[0].set(PrivateUint32Value(byteOffset));
args2[1].set(PrivateUint32Value(byteLength));
args2[2].setObject(*proto);
args2[3].setBoolean(lengthTracking);
RootedValue fval(cx, global->createDataViewForThis());
RootedValue thisv(cx, ObjectValue(*bufobj));
@ -2092,6 +2151,11 @@ template <typename NativeType>
/* static */ uint8_t*
DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset)
{
if (obj->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
return nullptr;
}
const size_t TypeSize = sizeof(NativeType);
if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE,
@ -2255,7 +2319,7 @@ DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj,
bool isLittleEndian = args.length() >= 2 && ToBoolean(args[1]);
// Steps 6-7.
if (obj->arrayBuffer().isDetached()) {
if (obj->arrayBufferEither().isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
@ -2354,7 +2418,7 @@ DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj,
bool isLittleEndian = args.length() >= 3 && ToBoolean(args[2]);
// Steps 7-8.
if (obj->arrayBuffer().isDetached()) {
if (obj->arrayBufferEither().isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
@ -3179,7 +3243,13 @@ template<Value ValueGetter(DataViewObject* view)>
bool
DataViewObject::getterImpl(JSContext* cx, const CallArgs& args)
{
args.rval().set(ValueGetter(&args.thisv().toObject().as<DataViewObject>()));
Rooted<DataViewObject*> view(cx, &args.thisv().toObject().as<DataViewObject>());
if (ValueGetter != bufferValue && view->isOutOfBounds()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DATA_VIEW_OUT_OF_BOUNDS);
return false;
}
args.rval().set(ValueGetter(view));
return true;
}

View file

@ -52,6 +52,8 @@ class TypedArrayObject : public NativeObject
"right buffer slot");
// Slot containing length of the view in number of typed elements.
// Length-tracking views on resizable/growable buffers store
// LENGTH_TRACKING here and compute their visible length from the buffer.
static const size_t LENGTH_SLOT = 1;
static_assert(LENGTH_SLOT == JS_TYPEDARRAYLAYOUT_LENGTH_SLOT,
"self-hosted code with burned-in constants must get the "
@ -65,6 +67,8 @@ class TypedArrayObject : public NativeObject
static const size_t RESERVED_SLOTS = 3;
static const int32_t LENGTH_TRACKING = -1;
#ifdef DEBUG
static const uint8_t ZeroLengthArrayData = 0x4A;
#endif
@ -139,15 +143,13 @@ class TypedArrayObject : public NativeObject
return tarr->getFixedSlot(BUFFER_SLOT);
}
static Value byteOffsetValue(TypedArrayObject* tarr) {
Value v = tarr->getFixedSlot(BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v;
return Int32Value(tarr->byteOffset());
}
static Value byteLengthValue(TypedArrayObject* tarr) {
return Int32Value(tarr->getFixedSlot(LENGTH_SLOT).toInt32() * tarr->bytesPerElement());
return Int32Value(tarr->byteLength());
}
static Value lengthValue(TypedArrayObject* tarr) {
return tarr->getFixedSlot(LENGTH_SLOT);
return Int32Value(tarr->length());
}
static bool
@ -159,14 +161,70 @@ class TypedArrayObject : public NativeObject
JSObject* bufferObject() const {
return bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
}
bool isLengthTracking() const {
return getFixedSlot(LENGTH_SLOT).toInt32() == LENGTH_TRACKING;
}
bool hasResizableOrGrowableBuffer() const {
if (!hasBuffer())
return false;
if (isSharedMemory())
return bufferShared()->isGrowable();
return bufferUnshared()->isResizable();
}
uint32_t byteOffsetMaybeOutOfBounds() const {
Value v = getFixedSlot(BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v.toInt32();
}
uint32_t fixedLengthMaybeOutOfBounds() const {
int32_t length = getFixedSlot(LENGTH_SLOT).toInt32();
MOZ_ASSERT(length >= 0);
return length;
}
uint32_t bufferByteLength() const {
MOZ_ASSERT(hasBuffer());
if (isSharedMemory())
return bufferShared()->byteLength();
return bufferUnshared()->byteLength();
}
bool isOutOfBounds() const {
if (!hasBuffer())
return false;
if (hasDetachedBuffer())
return true;
uint32_t bufferByteLength = this->bufferByteLength();
uint32_t offset = byteOffsetMaybeOutOfBounds();
if (offset > bufferByteLength)
return true;
if (isLengthTracking())
return false;
uint32_t byteLength = fixedLengthMaybeOutOfBounds() * bytesPerElement();
return byteLength > bufferByteLength - offset;
}
uint32_t byteOffset() const {
return byteOffsetValue(const_cast<TypedArrayObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
return byteOffsetMaybeOutOfBounds();
}
uint32_t byteLength() const {
return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
return length() * bytesPerElement();
}
uint32_t length() const {
return lengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
if (!isLengthTracking()) {
if (isOutOfBounds())
return 0;
return fixedLengthMaybeOutOfBounds();
}
if (isOutOfBounds())
return 0;
uint32_t bufferByteLength = this->bufferByteLength();
uint32_t offset = byteOffsetMaybeOutOfBounds();
return (bufferByteLength - offset) / bytesPerElement();
}
bool hasInlineElements() const;
@ -466,28 +524,26 @@ class DataViewObject : public NativeObject
defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto);
static bool getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args,
uint32_t *byteOffset, uint32_t* byteLength);
uint32_t *byteOffset, uint32_t* byteLength,
bool* lengthTracking);
static bool constructSameCompartment(JSContext* cx, HandleObject bufobj, const CallArgs& args);
static bool constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args);
friend bool ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args);
static DataViewObject*
create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
Handle<ArrayBufferObject*> arrayBuffer, JSObject* proto);
Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, JSObject* proto,
bool lengthTracking = false);
public:
static const Class class_;
static Value byteOffsetValue(DataViewObject* view) {
Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v;
return Int32Value(view->byteOffset());
}
static Value byteLengthValue(DataViewObject* view) {
Value v = view->getFixedSlot(TypedArrayObject::LENGTH_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v;
return Int32Value(view->byteLength());
}
static Value bufferValue(DataViewObject* view) {
@ -495,21 +551,66 @@ class DataViewObject : public NativeObject
}
uint32_t byteOffset() const {
return byteOffsetValue(const_cast<DataViewObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
return byteOffsetMaybeOutOfBounds();
}
uint32_t byteLength() const {
return byteLengthValue(const_cast<DataViewObject*>(this)).toInt32();
if (isOutOfBounds())
return 0;
if (isLengthTracking())
return arrayBufferEither().byteLength() - byteOffsetMaybeOutOfBounds();
return fixedByteLengthMaybeOutOfBounds();
}
ArrayBufferObject& arrayBuffer() const {
return bufferValue(const_cast<DataViewObject*>(this)).toObject().as<ArrayBufferObject>();
ArrayBufferObjectMaybeShared& arrayBufferEither() const {
return bufferValue(const_cast<DataViewObject*>(this)).
toObject().as<ArrayBufferObjectMaybeShared>();
}
bool isSharedMemory() const {
return bufferValue(const_cast<DataViewObject*>(this)).
toObject().is<SharedArrayBufferObject>();
}
void* dataPointer() const {
return getPrivate();
}
bool isLengthTracking() const {
return getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32() ==
TypedArrayObject::LENGTH_TRACKING;
}
uint32_t byteOffsetMaybeOutOfBounds() const {
Value v = getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
MOZ_ASSERT(v.toInt32() >= 0);
return v.toInt32();
}
uint32_t fixedByteLengthMaybeOutOfBounds() const {
int32_t length = getFixedSlot(TypedArrayObject::LENGTH_SLOT).toInt32();
MOZ_ASSERT(length >= 0);
return length;
}
bool isOutOfBounds() const {
const ArrayBufferObjectMaybeShared& buffer = arrayBufferEither();
if (buffer.isDetached())
return true;
uint32_t bufferByteLength = buffer.byteLength();
uint32_t offset = byteOffsetMaybeOutOfBounds();
if (offset > bufferByteLength)
return true;
if (isLengthTracking())
return false;
return fixedByteLengthMaybeOutOfBounds() > bufferByteLength - offset;
}
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
static bool getInt8Impl(JSContext* cx, const CallArgs& args);

View file

@ -1446,6 +1446,7 @@ ReloadPrefsCallback(const char* pref, void* data)
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
bool weakRefs = Preferences::GetBool(JS_OPTIONS_DOT_STR "weakrefs");
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
@ -1472,7 +1473,8 @@ ReloadPrefsCallback(const char* pref, void* data)
.setWerror(werror)
.setExtraWarnings(extraWarnings)
.setArrayProtoValues(arrayProtoValues)
.setStreams(streams);
.setStreams(streams)
.setWeakRefs(weakRefs);
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);

View file

@ -885,9 +885,10 @@ SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
}
// In standards mode, a style sheet must have one of these MIME
// types to be processed at all. In quirks mode, we accept any
// MIME type, but only if the style sheet is same-origin with the
// requesting document or parent sheet. See bug 524223.
// types to be processed at all, unless it is same-origin.
// For same-origin sheets, we accept any MIME type to match modern
// browser behavior (Chrome, Firefox). Cross-origin sheets require
// a valid CSS MIME type for security.
bool validType = contentType.EqualsLiteral("text/css") ||
contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) ||
@ -906,7 +907,7 @@ SheetLoadData::OnStreamComplete(nsIUnicharStreamLoader* aLoader,
}
}
if (sameOrigin && mLoader->mCompatMode == eCompatibility_NavQuirks) {
if (sameOrigin) {
errorMessage = "MimeNotCssWarn";
errorFlag = nsIScriptError::warningFlag;
} else {

View file

@ -9,7 +9,7 @@
*
* Platform-specific #ifdefs at the end of this file override the generic
* entries at the top.
*/
*
/*
* SYNTAX HINTS:
@ -639,7 +639,7 @@ pref("layout.event-regions.enabled", false);
// gfx/layers/apz/src/AsyncPanZoomController.cpp.
pref("apz.allow_checkerboarding", true);
pref("apz.allow_immediate_handoff", true);
pref("apz.allow_zooming", false);
pref("apz.allow_zooming", true);
// Whether to lock touch scrolling to one axis at a time
// 0 = FREE (No locking at all)
@ -651,7 +651,7 @@ pref("apz.axis_lock.breakout_threshold", "0.03125"); // 1/32 inches
pref("apz.axis_lock.breakout_angle", "0.3926991"); // PI / 8 (22.5 degrees)
pref("apz.axis_lock.direct_pan_angle", "1.047197"); // PI / 3 (60 degrees)
pref("apz.content_response_timeout", 400);
pref("apz.drag.enabled", false);
pref("apz.drag.enabled", true);
pref("apz.danger_zone_x", 50);
pref("apz.danger_zone_y", 100);
pref("apz.disable_for_scroll_linked_effects", false);
@ -710,7 +710,7 @@ pref("apz.zoom_animation_duration_ms", 250);
pref("apz.scale_repaint_delay_ms", 500);
#if !defined(MOZ_WIDGET_UIKIT)
pref("apz.desktop.enabled", false);
pref("apz.desktop.enabled", true);
#endif
#ifdef XP_MACOSX
@ -1348,6 +1348,9 @@ pref("javascript.options.dynamicImport", true);
// Streams API
pref("javascript.options.streams", true);
// Enable garbage collection of weakrefed objects
pref("javascript.options.weakrefs", false);
// advanced prefs
pref("advanced.mailftp", false);
pref("image.animation_mode", "normal");