Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2023-10-04 10:06:04 +08:00
commit a55674f653
89 changed files with 10466 additions and 916 deletions

View file

@ -398,7 +398,7 @@ FormData::Constructor(const GlobalObject& aGlobal,
NS_IMETHODIMP
FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset)
nsACString& aContentTypeWithCharset, nsACString& aCharset)
{
FSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr);
@ -419,7 +419,7 @@ FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
}
}
fs.GetContentType(aContentType);
fs.GetContentType(aContentTypeWithCharset);
aCharset.Truncate();
*aContentLength = 0;
NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));

View file

@ -11,7 +11,9 @@
#include "nsPluginArray.h"
#include "nsMimeTypeArray.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/BodyExtractor.h"
#include "mozilla/dom/DesktopNotification.h"
#include "mozilla/dom/FetchBinding.h"
#include "mozilla/dom/File.h"
#include "nsGeolocation.h"
#include "nsIClassOfService.h"
@ -38,6 +40,7 @@
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/StorageManager.h"
#include "mozilla/dom/TCPSocket.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/workers/RuntimeService.h"
#include "mozilla/Hal.h"
#include "nsISiteSpecificUserAgent.h"
@ -857,8 +860,52 @@ BeaconStreamListener::OnDataAvailable(nsIRequest *aRequest,
bool
Navigator::SendBeacon(const nsAString& aUrl,
const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
const Nullable<fetch::BodyInit>& aData,
ErrorResult& aRv)
{
if (aData.IsNull()) {
return SendBeaconInternal(aUrl, nullptr, eBeaconTypeOther, aRv);
}
if (aData.Value().IsArrayBuffer()) {
BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
}
if (aData.Value().IsArrayBufferView()) {
BodyExtractor<const ArrayBufferView> body(&aData.Value().GetAsArrayBufferView());
return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
}
if (aData.Value().IsBlob()) {
BodyExtractor<nsIXHRSendable> body(&aData.Value().GetAsBlob());
return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
}
if (aData.Value().IsFormData()) {
BodyExtractor<nsIXHRSendable> body(&aData.Value().GetAsFormData());
return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
}
if (aData.Value().IsUSVString()) {
BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
}
if (aData.Value().IsURLSearchParams()) {
BodyExtractor<nsIXHRSendable> body(&aData.Value().GetAsURLSearchParams());
return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
}
MOZ_CRASH("Invalid data type.");
return false;
}
bool
Navigator::SendBeaconInternal(const nsAString& aUrl,
BodyExtractorBase* aBody,
BeaconType aType,
ErrorResult& aRv)
{
if (!mWindow) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
@ -900,9 +947,9 @@ Navigator::SendBeacon(const nsAString& aUrl,
nsIChannel::LOAD_CLASSIFY_URI;
// No need to use CORS for sendBeacon unless it's a BLOB
nsSecurityFlags securityFlags = (!aData.IsNull() && aData.Value().IsBlob())
? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
nsSecurityFlags securityFlags = aType == eBeaconTypeBlob
? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
nsCOMPtr<nsIChannel> channel;
@ -928,76 +975,30 @@ Navigator::SendBeacon(const nsAString& aUrl,
}
httpChannel->SetReferrer(documentURI);
nsCString mimeType;
if (!aData.IsNull()) {
nsCOMPtr<nsIInputStream> in;
nsCOMPtr<nsIInputStream> in;
nsAutoCString contentTypeWithCharset;
nsAutoCString charset;
uint64_t length = 0;
if (aData.Value().IsString()) {
nsCString stringData = NS_ConvertUTF16toUTF8(aData.Value().GetAsString());
nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
rv = strStream->SetData(stringData.BeginReading(), stringData.Length());
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
mimeType.AssignLiteral("text/plain;charset=UTF-8");
in = strStream;
} else if (aData.Value().IsArrayBufferView()) {
nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
const ArrayBufferView& view = aData.Value().GetAsArrayBufferView();
view.ComputeLengthAndData();
rv = strStream->SetData(reinterpret_cast<char*>(view.Data()),
view.Length());
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
mimeType.AssignLiteral("application/octet-stream");
in = strStream;
} else if (aData.Value().IsBlob()) {
Blob& blob = aData.Value().GetAsBlob();
blob.GetInternalStream(getter_AddRefs(in), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
nsAutoString type;
blob.GetType(type);
mimeType = NS_ConvertUTF16toUTF8(type);
} else if (aData.Value().IsFormData()) {
FormData& form = aData.Value().GetAsFormData();
uint64_t len;
nsAutoCString charset;
form.GetSendInfo(getter_AddRefs(in),
&len,
mimeType,
charset);
} else {
MOZ_ASSERT(false, "switch statements not in sync");
aRv.Throw(NS_ERROR_FAILURE);
if (aBody) {
aRv = aBody->GetAsStream(getter_AddRefs(in), &length,
contentTypeWithCharset, charset);
if (NS_WARN_IF(aRv.Failed())) {
return false;
}
if (aType == eBeaconTypeArrayBuffer) {
MOZ_ASSERT(contentTypeWithCharset.IsEmpty());
MOZ_ASSERT(charset.IsEmpty());
contentTypeWithCharset.Assign("application/octet-stream");
}
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
if (!uploadChannel) {
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
uploadChannel->ExplicitSetUploadStream(in, mimeType, -1,
uploadChannel->ExplicitSetUploadStream(in, contentTypeWithCharset, length,
NS_LITERAL_CSTRING("POST"),
false);
} else {

View file

@ -7,6 +7,7 @@
#define mozilla_dom_Navigator_h
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/ErrorResult.h"
#include "nsIDOMNavigator.h"
@ -30,12 +31,13 @@ class nsIURI;
namespace mozilla {
namespace dom {
class BodyExtractorBase;
class Geolocation;
class systemMessageCallback;
class MediaDevices;
struct MediaStreamConstraints;
class WakeLock;
class ArrayBufferViewOrBlobOrStringOrFormData;
class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
class ServiceWorkerContainer;
class DOMRequest;
} // namespace dom
@ -196,7 +198,7 @@ public:
#endif // MOZ_AUDIO_CHANNEL_MANAGER
bool SendBeacon(const nsAString& aUrl,
const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
const Nullable<fetch::BodyInit>& aData,
ErrorResult& aRv);
void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
@ -255,6 +257,19 @@ private:
bool CheckPermission(const char* type);
static bool CheckPermission(nsPIDOMWindowInner* aWindow, const char* aType);
// This enum helps SendBeaconInternal to apply different behaviors to body
// types.
enum BeaconType {
eBeaconTypeBlob,
eBeaconTypeArrayBuffer,
eBeaconTypeOther
};
bool SendBeaconInternal(const nsAString& aUrl,
BodyExtractorBase* aBody,
BeaconType aType,
ErrorResult& aRv);
RefPtr<nsMimeTypeArray> mMimeTypes;
RefPtr<nsPluginArray> mPlugins;
RefPtr<Permissions> mPermissions;

View file

@ -39,6 +39,8 @@
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/FileSystemSecurity.h"
@ -8735,6 +8737,25 @@ nsContentUtils::PushEnabled(JSContext* aCx, JSObject* aObj)
return workerPrivate->PushEnabled();
}
// static
bool
nsContentUtils::StreamsEnabled(JSContext* aCx, JSObject* aObj)
{
if (NS_IsMainThread()) {
return Preferences::GetBool("dom.streams.enabled", false);
}
using namespace workers;
// Otherwise, check the pref via the WorkerPrivate
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
if (!workerPrivate) {
return false;
}
return workerPrivate->StreamsEnabled();
}
// static
bool
nsContentUtils::IsNonSubresourceRequest(nsIChannel* aChannel)
@ -10062,3 +10083,72 @@ void nsContentUtils::StructuredClone(JSContext* aCx,
nsTArray<RefPtr<MessagePort>> ports = holder.TakeTransferredPorts();
Unused << ports;
}
/* static */ void
nsContentUtils::ExtractErrorValues(JSContext* aCx,
JS::Handle<JS::Value> aValue,
nsACString& aSourceSpecOut,
uint32_t* aLineOut,
uint32_t* aColumnOut,
nsString& aMessageOut)
{
MOZ_ASSERT(aLineOut);
MOZ_ASSERT(aColumnOut);
if (aValue.isObject()) {
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
RefPtr<dom::DOMException> domException;
// Try to process as an Error object. Use the file/line/column values
// from the Error as they will be more specific to the root cause of
// the problem.
JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
if (err) {
// Use xpc to extract the error message only. We don't actually send
// this report anywhere.
RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
report->Init(err,
"<unknown>", // toString result
false, // chrome
0); // window ID
if (!report->mFileName.IsEmpty()) {
CopyUTF16toUTF8(report->mFileName, aSourceSpecOut);
*aLineOut = report->mLineNumber;
*aColumnOut = report->mColumn;
}
aMessageOut.Assign(report->mErrorMsg);
}
// Next, try to unwrap the rejection value as a DOMException.
else if(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, obj, domException))) {
nsAutoString filename;
domException->GetFilename(aCx, filename);
if (!filename.IsEmpty()) {
CopyUTF16toUTF8(filename, aSourceSpecOut);
*aLineOut = domException->LineNumber(aCx);
*aColumnOut = domException->ColumnNumber();
}
domException->GetName(aMessageOut);
aMessageOut.AppendLiteral(": ");
nsAutoString message;
domException->GetMessageMoz(message);
aMessageOut.Append(message);
}
}
// If we could not unwrap a specific error type, then perform default safe
// string conversions on primitives. Objects will result in "[Object]"
// unfortunately.
if (aMessageOut.IsEmpty()) {
nsAutoJSString jsString;
if (jsString.init(aCx, aValue)) {
aMessageOut = jsString;
} else {
JS_ClearPendingException(aCx);
}
}
}

View file

@ -1010,6 +1010,10 @@ public:
static bool PrefetchEnabled(nsIDocShell* aDocShell);
static void ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
nsACString& aSourceSpecOut, uint32_t *aLineOut,
uint32_t *aColumnOut, nsString& aMessageOut);
static nsresult CalculateBufferSizeForImage(const uint32_t& aStride,
const mozilla::gfx::IntSize& aImageSize,
const mozilla::gfx::SurfaceFormat& aFormat,
@ -2679,6 +2683,8 @@ public:
static mozilla::net::ReferrerPolicy GetReferrerPolicyFromHeader(const nsAString& aHeader);
static bool PushEnabled(JSContext* aCx, JSObject* aObj);
static bool StreamsEnabled(JSContext* aCx, JSObject* aObj);
static bool IsNonSubresourceRequest(nsIChannel* aChannel);

View file

@ -116,11 +116,13 @@ DOMInterfaces = {
},
'Cache': {
'implicitJSContext': [ 'add', 'addAll' ],
'implicitJSContext': [ 'add', 'addAll', 'match', 'matchAll', 'put',
'delete', 'keys' ],
'nativeType': 'mozilla::dom::cache::Cache',
},
'CacheStorage': {
'implicitJSContext': [ 'match' ],
'nativeType': 'mozilla::dom::cache::CacheStorage',
},
@ -722,6 +724,7 @@ DOMInterfaces = {
'headers': 'headers_',
'referrerPolicy': 'referrerPolicy_'
},
'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text' ],
},
'ResizeObservation': {
@ -746,6 +749,8 @@ DOMInterfaces = {
'Response': {
'binaryNames': { 'headers': 'headers_' },
'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text',
'clone', 'cloneUnfiltered' ],
},
'RGBColor': {

View file

@ -1156,7 +1156,10 @@ class CGHeaders(CGWrapper):
# just include their header if we need to have functions
# taking references to them declared in that header.
headerSet = declareIncludes
headerSet.add("mozilla/dom/TypedArray.h")
if unrolled.isReadableStream():
headerSet.add("mozilla/dom/ReadableStream.h")
else:
headerSet.add("mozilla/dom/TypedArray.h")
else:
try:
typeDesc = config.getDescriptor(unrolled.inner.identifier.name)
@ -1371,7 +1374,10 @@ def UnionTypes(unionTypes, config):
elif f.isInterface():
if f.isSpiderMonkeyInterface():
headers.add("jsfriendapi.h")
headers.add("mozilla/dom/TypedArray.h")
if f.isReadableStream():
headers.add("mozilla/dom/ReadableStream.h")
else:
headers.add("mozilla/dom/TypedArray.h")
else:
try:
typeDesc = config.getDescriptor(f.inner.identifier.name)
@ -1457,7 +1463,10 @@ def UnionConversions(unionTypes, config):
elif f.isInterface():
if f.isSpiderMonkeyInterface():
headers.add("jsfriendapi.h")
headers.add("mozilla/dom/TypedArray.h")
if f.isReadableStream():
headers.add("mozilla/dom/ReadableStream.h")
else:
headers.add("mozilla/dom/TypedArray.h")
elif f.inner.isExternal():
try:
typeDesc = config.getDescriptor(f.inner.identifier.name)
@ -5582,8 +5591,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if type.isSpiderMonkeyInterface():
assert not isEnforceRange and not isClamp
name = type.unroll().name # unroll() because it may be nullable
arrayType = CGGeneric(name)
declType = arrayType
interfaceType = CGGeneric(name)
declType = interfaceType
if type.nullable():
declType = CGTemplatedType("Nullable", declType)
objRef = "${declName}.SetValue()"
@ -5607,22 +5616,23 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
# This is a bit annoying. In a union we don't want to have a
# holder, since unions don't support that. But if we're optional we
# want to have a holder, so that the callee doesn't see
# Optional<RootedTypedArray<ArrayType> >. So do a holder if we're
# optional and use a RootedTypedArray otherwise.
# Optional<RootedSpiderMonkeyInterface<InterfaceType>>. So do a
# holder if we're optional and use a RootedSpiderMonkeyInterface
# otherwise.
if isOptional:
holderType = CGTemplatedType("TypedArrayRooter", arrayType)
# If our typed array is nullable, this will set the Nullable to
# be not-null, but that's ok because we make an explicit
# SetNull() call on it as needed if our JS value is actually
# null. XXXbz Because "Maybe" takes const refs for constructor
# arguments, we can't pass a reference here; have to pass a
# pointer.
holderType = CGTemplatedType("SpiderMonkeyInterfaceRooter", interfaceType)
# If our SpiderMonkey interface is nullable, this will set the
# Nullable to be not-null, but that's ok because we make an
# explicit SetNull() call on it as needed if our JS value is
# actually null. XXXbz Because "Maybe" takes const refs for
# constructor arguments, we can't pass a reference here; have
# to pass a pointer.
holderArgs = "cx, &%s" % objRef
declArgs = None
else:
holderType = None
holderArgs = None
declType = CGTemplatedType("RootedTypedArray", declType)
declType = CGTemplatedType("RootedSpiderMonkeyInterface", declType)
declArgs = "cx"
else:
holderType = None
@ -6371,7 +6381,7 @@ def getMaybeWrapValueFuncForType(type):
if type.nullable():
return "MaybeWrapObjectOrNullValue"
return "MaybeWrapObjectValue"
# Spidermonkey interfaces are never DOM objects. Neither are sequences or
# SpiderMonkey interfaces are never DOM objects. Neither are sequences or
# dictionaries, since those are always plain JS objects.
if type.isSpiderMonkeyInterface() or type.isDictionary() or type.isSequence():
if type.nullable():
@ -6391,7 +6401,8 @@ recordWrapLevel = 0
def getWrapTemplateForType(type, descriptorProvider, result, successCode,
returnsNewObject, exceptionCode, typedArraysAreStructs,
returnsNewObject, exceptionCode,
spiderMonkeyInterfacesAreStructs,
isConstructorRetval=False):
"""
Reflect a C++ value stored in "result", of IDL type "type" into JS. The
@ -6401,8 +6412,9 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
doing a 'break' if the entire conversion template is inside a block that
the 'break' will exit).
If typedArraysAreStructs is true, then if the type is a typed array,
"result" is one of the dom::TypedArray subclasses, not a JSObject*.
If spiderMonkeyInterfacesAreStructs is true, then if the type is a
SpiderMonkey interface, "result" is one of the
dom::SpiderMonkeyInterfaceObjectStorage subclasses, not a JSObject*.
The resulting string should be used with string.Template. It
needs the following keys when substituting:
@ -6497,7 +6509,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
recTemplate, recInfall = getWrapTemplateForType(type.inner, descriptorProvider,
"%s.Value()" % result, successCode,
returnsNewObject, exceptionCode,
typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs)
code = fill(
"""
@ -6529,7 +6541,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
'returnsNewObject': returnsNewObject,
'exceptionCode': exceptionCode,
'obj': "returnArray",
'typedArraysAreStructs': typedArraysAreStructs
'spiderMonkeyInterfacesAreStructs': spiderMonkeyInterfacesAreStructs
})
sequenceWrapLevel -= 1
code = fill(
@ -6583,7 +6595,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
'returnsNewObject': returnsNewObject,
'exceptionCode': exceptionCode,
'obj': "returnObj",
'typedArraysAreStructs': typedArraysAreStructs
'spiderMonkeyInterfacesAreStructs': spiderMonkeyInterfacesAreStructs
})
recordWrapLevel -= 1
if type.keyType.isByteString():
@ -6749,7 +6761,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
return (head + _setValue(result, wrapAsType=type), False)
if (type.isObject() or (type.isSpiderMonkeyInterface() and
not typedArraysAreStructs)):
not spiderMonkeyInterfacesAreStructs)):
# See comments in GetOrCreateDOMReflector explaining why we need
# to wrap here.
if type.nullable():
@ -6768,21 +6780,21 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
if not (type.isUnion() or type.isPrimitive() or type.isDictionary() or
type.isDate() or
(type.isSpiderMonkeyInterface() and typedArraysAreStructs)):
(type.isSpiderMonkeyInterface() and spiderMonkeyInterfacesAreStructs)):
raise TypeError("Need to learn to wrap %s" % type)
if type.nullable():
recTemplate, recInfal = getWrapTemplateForType(type.inner, descriptorProvider,
"%s.Value()" % result, successCode,
returnsNewObject, exceptionCode,
typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs)
return ("if (%s.IsNull()) {\n" % result +
indent(setNull()) +
"}\n" +
recTemplate, recInfal)
if type.isSpiderMonkeyInterface():
assert typedArraysAreStructs
assert spiderMonkeyInterfacesAreStructs
# See comments in GetOrCreateDOMReflector explaining why we need
# to wrap here.
# NB: setObject(..., some-object-type) calls JS_WrapValue(), so is fallible
@ -6864,7 +6876,7 @@ def wrapForType(type, descriptorProvider, templateValues):
templateValues.get('successCode', None),
templateValues.get('returnsNewObject', False),
templateValues.get('exceptionCode', "return false;\n"),
templateValues.get('typedArraysAreStructs', False),
templateValues.get('spiderMonkeyInterfacesAreStructs', False),
isConstructorRetval=templateValues.get('isConstructorRetval', False))[0]
defaultValues = {'obj': 'obj'}
@ -8193,10 +8205,10 @@ class CGMethodCall(CGThing):
# "object" arg.
# First grab all the overloads that have a non-callback interface
# (which includes typed arrays and arraybuffers) at the
# distinguishing index. We can also include the ones that have an
# "object" here, since if those are present no other object-typed
# argument will be.
# (which includes SpiderMonkey interfaces) at the distinguishing
# index. We can also include the ones that have an "object" here,
# since if those are present no other object-typed argument will
# be.
objectSigs = [
s for s in possibleSignatures
if (distinguishingType(s).isObject() or
@ -8227,16 +8239,17 @@ class CGMethodCall(CGThing):
# There might be more than one thing in objectSigs; we need to check
# which ones we unwrap to.
if len(objectSigs) > 0:
# Here it's enough to guard on our argument being an object. The
# code for unwrapping non-callback interfaces, typed arrays,
# sequences, and Dates will just bail out and move on to
# the next overload if the object fails to unwrap correctly,
# while "object" accepts any object anyway. We could even not
# do the isObject() check up front here, but in cases where we
# have multiple object overloads it makes sense to do it only
# once instead of for each overload. That will also allow the
# unwrapping test to skip having to do codegen for the
# null-or-undefined case, which we already handled above.
# Here it's enough to guard on our argument being an object.
# The code for unwrapping non-callback interfaces, spiderMonkey
# interfaces, sequences, and Dates will just bail out and move
# on to the next overload if the object fails to unwrap
# correctly, while "object" accepts any object anyway. We
# could even not do the isObject() check up front here, but in
# cases where we have multiple object overloads it makes sense
# to do it only once instead of for each overload. That will
# also allow the unwrapping test to skip having to do codegen
# for the null-or-undefined case, which we already handled
# above.
caseBody.append(CGGeneric("if (%s.isObject()) {\n" %
distinguishingArg))
for sig in objectSigs:
@ -10239,7 +10252,7 @@ class CGUnionStruct(CGThing):
"jsvalHandle": "rval",
"obj": "scopeObj",
"result": val,
"typedArraysAreStructs": True
"spiderMonkeyInterfacesAreStructs": True
})
return CGGeneric(wrapCode)
@ -13219,7 +13232,7 @@ class CGDictionary(CGThing):
# 'obj' can just be allowed to be the string "obj", since that
# will be our dictionary object, which is presumably itself in
# the right scope.
'typedArraysAreStructs': True
'spiderMonkeyInterfacesAreStructs': True
})
conversion = CGGeneric(innerTemplate)
conversion = CGWrapper(conversion,
@ -13679,7 +13692,7 @@ class ForwardDeclarationBuilder:
except NoSuchDescriptorError:
pass
# Note: Spidermonkey interfaces are typedefs, so can't be
# Note: SpiderMonkey interfaces are typedefs, so can't be
# forward-declared
elif t.isPromise():
self.addInMozillaDom("Promise")
@ -13988,7 +14001,7 @@ class CGBindingRoot(CGThing):
return {desc.getDescriptor(desc.interface.parent.identifier.name)}
for x in dependencySortObjects(jsImplemented, getParentDescriptor,
lambda d: d.interface.identifier.name):
cgthings.append(CGCallbackInterface(x, typedArraysAreStructs=True))
cgthings.append(CGCallbackInterface(x, spiderMonkeyInterfacesAreStructs=True))
cgthings.append(CGJSImplClass(x))
# And make sure we have the right number of newlines at the end
@ -14050,14 +14063,13 @@ class CGBindingRoot(CGThing):
class CGNativeMember(ClassMethod):
def __init__(self, descriptorProvider, member, name, signature, extendedAttrs,
breakAfter=True, passJSBitsAsNeeded=True, visibility="public",
typedArraysAreStructs=True, variadicIsSequence=False,
resultNotAddRefed=False,
virtual=False,
override=False):
spiderMonkeyInterfacesAreStructs=True,
variadicIsSequence=False, resultNotAddRefed=False,
virtual=False, override=False):
"""
If typedArraysAreStructs is false, typed arrays will be passed as
JS::Handle<JSObject*>. If it's true they will be passed as one of the
dom::TypedArray subclasses.
If spiderMonkeyInterfacesAreStructs is false, SpiderMonkey interfaces
will be passed as JS::Handle<JSObject*>. If it's true they will be
passed as one of the dom::SpiderMonkeyInterfaceObjectStorage subclasses.
If passJSBitsAsNeeded is false, we don't automatically pass in a
JSContext* or a JSObject* based on the return and argument types. We
@ -14068,7 +14080,7 @@ class CGNativeMember(ClassMethod):
self.extendedAttrs = extendedAttrs
self.resultAlreadyAddRefed = not resultNotAddRefed
self.passJSBitsAsNeeded = passJSBitsAsNeeded
self.typedArraysAreStructs = typedArraysAreStructs
self.spiderMonkeyInterfacesAreStructs = spiderMonkeyInterfacesAreStructs
self.variadicIsSequence = variadicIsSequence
breakAfterSelf = "\n" if breakAfter else ""
ClassMethod.__init__(self, name,
@ -14375,7 +14387,7 @@ class CGNativeMember(ClassMethod):
False, False)
if type.isSpiderMonkeyInterface():
if not self.typedArraysAreStructs:
if not self.spiderMonkeyInterfacesAreStructs:
return "JS::Handle<JSObject*>", False, False
# Unroll for the name, in case we're nullable.
@ -15547,16 +15559,16 @@ class CGFastCallback(CGClass):
class CGCallbackInterface(CGCallback):
def __init__(self, descriptor, typedArraysAreStructs=False):
def __init__(self, descriptor, spiderMonkeyInterfacesAreStructs=False):
iface = descriptor.interface
attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
getters = [CallbackGetter(a, descriptor, typedArraysAreStructs)
getters = [CallbackGetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
for a in attrs]
setters = [CallbackSetter(a, descriptor, typedArraysAreStructs)
setters = [CallbackSetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
for a in attrs if not a.readonly]
methods = [m for m in iface.members
if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()]
methods = [CallbackOperation(m, sig, descriptor, typedArraysAreStructs)
methods = [CallbackOperation(m, sig, descriptor, spiderMonkeyInterfacesAreStructs)
for m in methods for sig in m.signatures()]
if iface.isJSImplemented() and iface.ctor():
sigs = descriptor.interface.ctor().signatures()
@ -15601,7 +15613,8 @@ class CallbackMember(CGNativeMember):
# CallSetup already handled the unmark-gray bits for us. we don't have
# anything better to use for 'obj', really...
def __init__(self, sig, name, descriptorProvider, needThisHandling,
rethrowContentException=False, typedArraysAreStructs=False,
rethrowContentException=False,
spiderMonkeyInterfacesAreStructs=False,
wrapScope='CallbackKnownNotGray()'):
"""
needThisHandling is True if we need to be able to accept a specified
@ -15636,7 +15649,7 @@ class CallbackMember(CGNativeMember):
extendedAttrs={},
passJSBitsAsNeeded=False,
visibility=visibility,
typedArraysAreStructs=typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
# We have to do all the generation of our body now, because
# the caller relies on us throwing if we can't manage it.
self.exceptionCode = ("aRv.Throw(NS_ERROR_UNEXPECTED);\n"
@ -15753,7 +15766,7 @@ class CallbackMember(CGNativeMember):
'obj': self.wrapScope,
'returnsNewObject': False,
'exceptionCode': self.exceptionCode,
'typedArraysAreStructs': self.typedArraysAreStructs
'spiderMonkeyInterfacesAreStructs': self.spiderMonkeyInterfacesAreStructs
})
except MethodNotNewObjectError as err:
raise TypeError("%s being passed as an argument to %s but is not "
@ -15857,10 +15870,11 @@ class CallbackMember(CGNativeMember):
class CallbackMethod(CallbackMember):
def __init__(self, sig, name, descriptorProvider, needThisHandling,
rethrowContentException=False, typedArraysAreStructs=False):
rethrowContentException=False,
spiderMonkeyInterfacesAreStructs=False):
CallbackMember.__init__(self, sig, name, descriptorProvider,
needThisHandling, rethrowContentException,
typedArraysAreStructs=typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getRvalDecl(self):
return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
@ -15919,12 +15933,12 @@ class CallbackOperationBase(CallbackMethod):
"""
def __init__(self, signature, jsName, nativeName, descriptor,
singleOperation, rethrowContentException=False,
typedArraysAreStructs=False):
spiderMonkeyInterfacesAreStructs=False):
self.singleOperation = singleOperation
self.methodName = descriptor.binaryNameFor(jsName)
CallbackMethod.__init__(self, signature, nativeName, descriptor,
singleOperation, rethrowContentException,
typedArraysAreStructs=typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getThisDecl(self):
if not self.singleOperation:
@ -15975,7 +15989,8 @@ class CallbackOperation(CallbackOperationBase):
"""
Codegen actual WebIDL operations on callback interfaces.
"""
def __init__(self, method, signature, descriptor, typedArraysAreStructs):
def __init__(self, method, signature, descriptor,
spiderMonkeyInterfacesAreStructs):
self.ensureASCIIName(method)
self.method = method
jsName = method.identifier.name
@ -15984,7 +15999,7 @@ class CallbackOperation(CallbackOperationBase):
MakeNativeName(descriptor.binaryNameFor(jsName)),
descriptor, descriptor.interface.isSingleOperationInterface(),
rethrowContentException=descriptor.interface.isJSImplemented(),
typedArraysAreStructs=typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getPrettyName(self):
return "%s.%s" % (self.descriptorProvider.interface.identifier.name,
@ -15995,13 +16010,14 @@ class CallbackAccessor(CallbackMember):
"""
Shared superclass for CallbackGetter and CallbackSetter.
"""
def __init__(self, attr, sig, name, descriptor, typedArraysAreStructs):
def __init__(self, attr, sig, name, descriptor,
spiderMonkeyInterfacesAreStructs):
self.ensureASCIIName(attr)
self.attrName = attr.identifier.name
CallbackMember.__init__(self, sig, name, descriptor,
needThisHandling=False,
rethrowContentException=descriptor.interface.isJSImplemented(),
typedArraysAreStructs=typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getPrettyName(self):
return "%s.%s" % (self.descriptorProvider.interface.identifier.name,
@ -16009,12 +16025,12 @@ class CallbackAccessor(CallbackMember):
class CallbackGetter(CallbackAccessor):
def __init__(self, attr, descriptor, typedArraysAreStructs):
def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
CallbackAccessor.__init__(self, attr,
(attr.type, []),
callbackGetterName(attr, descriptor),
descriptor,
typedArraysAreStructs)
spiderMonkeyInterfacesAreStructs)
def getRvalDecl(self):
return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
@ -16036,12 +16052,12 @@ class CallbackGetter(CallbackAccessor):
class CallbackSetter(CallbackAccessor):
def __init__(self, attr, descriptor, typedArraysAreStructs):
def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
CallbackAccessor.__init__(self, attr,
(BuiltinTypes[IDLBuiltinType.Types.void],
[FakeArgument(attr.type, attr)]),
callbackSetterName(attr, descriptor),
descriptor, typedArraysAreStructs)
descriptor, spiderMonkeyInterfacesAreStructs)
def getRvalDecl(self):
# We don't need an rval
@ -16076,7 +16092,7 @@ class CGJSImplInitOperation(CallbackOperationBase):
"__init", "__Init", descriptor,
singleOperation=False,
rethrowContentException=True,
typedArraysAreStructs=True)
spiderMonkeyInterfacesAreStructs=True)
def getPrettyName(self):
return "__init"

View file

@ -0,0 +1,28 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_ReadableStream_h
#define mozilla_dom_ReadableStream_h
#include "mozilla/dom/SpiderMonkeyInterface.h"
namespace mozilla {
namespace dom {
struct ReadableStream : public SpiderMonkeyInterfaceObjectStorage
{
inline bool Init(JSObject* obj)
{
MOZ_ASSERT(!inited());
mImplObj = mWrappedObj = js::UnwrapReadableStream(obj);
return inited();
}
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_ReadableStream_h */

View file

@ -0,0 +1,144 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_SpiderMonkeyInterface_h
#define mozilla_dom_SpiderMonkeyInterface_h
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/TracingAPI.h"
namespace mozilla {
namespace dom {
/*
* Class that just handles the JSObject storage and tracing for spidermonkey
* interfaces
*/
struct SpiderMonkeyInterfaceObjectStorage
{
protected:
JSObject* mImplObj;
JSObject* mWrappedObj;
SpiderMonkeyInterfaceObjectStorage()
: mImplObj(nullptr),
mWrappedObj(nullptr)
{
}
SpiderMonkeyInterfaceObjectStorage(SpiderMonkeyInterfaceObjectStorage&& aOther)
: mImplObj(aOther.mImplObj),
mWrappedObj(aOther.mWrappedObj)
{
aOther.mImplObj = nullptr;
aOther.mWrappedObj = nullptr;
}
public:
inline void TraceSelf(JSTracer* trc)
{
JS::UnsafeTraceRoot(trc, &mImplObj, "SpiderMonkeyInterfaceObjectStorage.mImplObj");
JS::UnsafeTraceRoot(trc, &mWrappedObj, "SpiderMonkeyInterfaceObjectStorage.mWrappedObj");
}
inline bool inited() const
{
return !!mImplObj;
}
inline bool WrapIntoNewCompartment(JSContext* cx)
{
return JS_WrapObject(cx,
JS::MutableHandle<JSObject*>::fromMarkedLocation(&mWrappedObj));
}
inline JSObject *Obj() const
{
MOZ_ASSERT(inited());
return mWrappedObj;
}
private:
SpiderMonkeyInterfaceObjectStorage(const SpiderMonkeyInterfaceObjectStorage&) = delete;
};
// A class for rooting an existing SpiderMonkey Interface struct
template<typename InterfaceType>
class MOZ_RAII SpiderMonkeyInterfaceRooter : private JS::CustomAutoRooter
{
public:
template <typename CX>
SpiderMonkeyInterfaceRooter(const CX& cx,
InterfaceType* aInterface MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mInterface(aInterface)
{
}
virtual void trace(JSTracer* trc) override
{
mInterface->TraceSelf(trc);
}
private:
SpiderMonkeyInterfaceObjectStorage* const mInterface;
};
// And a specialization for dealing with nullable SpiderMonkey interfaces
template<typename Inner> struct Nullable;
template<typename InterfaceType>
class MOZ_RAII SpiderMonkeyInterfaceRooter<Nullable<InterfaceType>> :
private JS::CustomAutoRooter
{
public:
template <typename CX>
SpiderMonkeyInterfaceRooter(const CX& cx,
Nullable<InterfaceType>* aInterface MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mInterface(aInterface)
{
}
virtual void trace(JSTracer* trc) override
{
if (!mInterface->IsNull()) {
mInterface->Value().TraceSelf(trc);
}
}
private:
Nullable<InterfaceType>* const mInterface;
};
// Class for easily setting up a rooted SpiderMonkey interface object on the
// stack
template<typename InterfaceType>
class MOZ_RAII RootedSpiderMonkeyInterface final : public InterfaceType,
private SpiderMonkeyInterfaceRooter<InterfaceType>
{
public:
template <typename CX>
explicit RootedSpiderMonkeyInterface(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: InterfaceType(),
SpiderMonkeyInterfaceRooter<InterfaceType>(cx, this
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
{
}
template <typename CX>
RootedSpiderMonkeyInterface(const CX& cx, JSObject* obj MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: InterfaceType(obj),
SpiderMonkeyInterfaceRooter<InterfaceType>(cx, this
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
{
}
};
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_SpiderMonkeyInterface_h */

View file

@ -6,51 +6,15 @@
#ifndef mozilla_dom_TypedArray_h
#define mozilla_dom_TypedArray_h
#include "jsapi.h"
#include "jsfriendapi.h"
#include "js/RootingAPI.h"
#include "js/TracingAPI.h"
#include "mozilla/Attributes.h"
#include "mozilla/Move.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/SpiderMonkeyInterface.h"
#include "nsWrapperCache.h"
namespace mozilla {
namespace dom {
/*
* Class that just handles the JSObject storage and tracing for typed arrays
*/
struct TypedArrayObjectStorage : AllTypedArraysBase {
protected:
JSObject* mTypedObj;
JSObject* mWrappedObj;
TypedArrayObjectStorage()
: mTypedObj(nullptr),
mWrappedObj(nullptr)
{
}
TypedArrayObjectStorage(TypedArrayObjectStorage&& aOther)
: mTypedObj(aOther.mTypedObj),
mWrappedObj(aOther.mWrappedObj)
{
aOther.mTypedObj = nullptr;
aOther.mWrappedObj = nullptr;
}
public:
inline void TraceSelf(JSTracer* trc)
{
JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mTypedObj");
JS::UnsafeTraceRoot(trc, &mWrappedObj, "TypedArray.mWrappedObj");
}
private:
TypedArrayObjectStorage(const TypedArrayObjectStorage&) = delete;
};
/*
* Various typed array classes for argument conversion. We have a base class
* that has a way of initializing a TypedArray from an existing typed array, and
@ -60,7 +24,9 @@ private:
template<typename T,
JSObject* UnwrapArray(JSObject*),
void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**)>
struct TypedArray_base : public TypedArrayObjectStorage {
struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
AllTypedArraysBase
{
typedef T element_type;
TypedArray_base()
@ -72,7 +38,7 @@ struct TypedArray_base : public TypedArrayObjectStorage {
}
TypedArray_base(TypedArray_base&& aOther)
: TypedArrayObjectStorage(Move(aOther)),
: SpiderMonkeyInterfaceObjectStorage(Move(aOther)),
mData(aOther.mData),
mLength(aOther.mLength),
mShared(aOther.mShared),
@ -94,14 +60,10 @@ public:
inline bool Init(JSObject* obj)
{
MOZ_ASSERT(!inited());
mTypedObj = mWrappedObj = UnwrapArray(obj);
mImplObj = mWrappedObj = UnwrapArray(obj);
return inited();
}
inline bool inited() const {
return !!mTypedObj;
}
// About shared memory:
//
// Any DOM TypedArray as well as any DOM ArrayBufferView that does
@ -173,22 +135,11 @@ public:
return mLength;
}
inline JSObject *Obj() const {
MOZ_ASSERT(inited());
return mWrappedObj;
}
inline bool WrapIntoNewCompartment(JSContext* cx)
{
return JS_WrapObject(cx,
JS::MutableHandle<JSObject*>::fromMarkedLocation(&mWrappedObj));
}
inline void ComputeLengthAndData() const
{
MOZ_ASSERT(inited());
MOZ_ASSERT(!mComputed);
GetLengthAndDataAndSharedness(mTypedObj, &mLength, &mShared, &mData);
GetLengthAndDataAndSharedness(mImplObj, &mLength, &mShared, &mData);
mComputed = true;
}
@ -363,77 +314,6 @@ class TypedArrayCreator
const ArrayType& mArray;
};
// A class for rooting an existing TypedArray struct
template<typename ArrayType>
class MOZ_RAII TypedArrayRooter : private JS::CustomAutoRooter
{
public:
template <typename CX>
TypedArrayRooter(const CX& cx,
ArrayType* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mArray(aArray)
{
}
virtual void trace(JSTracer* trc) override
{
mArray->TraceSelf(trc);
}
private:
TypedArrayObjectStorage* const mArray;
};
// And a specialization for dealing with nullable typed arrays
template<typename Inner> struct Nullable;
template<typename ArrayType>
class MOZ_RAII TypedArrayRooter<Nullable<ArrayType> > :
private JS::CustomAutoRooter
{
public:
template <typename CX>
TypedArrayRooter(const CX& cx,
Nullable<ArrayType>* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mArray(aArray)
{
}
virtual void trace(JSTracer* trc) override
{
if (!mArray->IsNull()) {
mArray->Value().TraceSelf(trc);
}
}
private:
Nullable<ArrayType>* const mArray;
};
// Class for easily setting up a rooted typed array object on the stack
template<typename ArrayType>
class MOZ_RAII RootedTypedArray final : public ArrayType,
private TypedArrayRooter<ArrayType>
{
public:
template <typename CX>
explicit RootedTypedArray(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
ArrayType(),
TypedArrayRooter<ArrayType>(cx, this
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
{
}
template <typename CX>
RootedTypedArray(const CX& cx, JSObject* obj MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
ArrayType(obj),
TypedArrayRooter<ArrayType>(cx, this
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
{
}
};
} // namespace dom
} // namespace mozilla

View file

@ -38,9 +38,11 @@ EXPORTS.mozilla.dom += [
'NonRefcountedDOMObject.h',
'Nullable.h',
'PrimitiveConversions.h',
'ReadableStream.h',
'Record.h',
'RootedDictionary.h',
'SimpleGlobalObject.h',
'SpiderMonkeyInterface.h',
'StructuredClone.h',
'ToJSValue.h',
'TypedArray.h',

View file

@ -2054,6 +2054,9 @@ class IDLType(IDLObject):
def isRecord(self):
return False
def isReadableStream(self):
return False
def isArrayBuffer(self):
return False
@ -2081,12 +2084,12 @@ class IDLType(IDLObject):
def isSpiderMonkeyInterface(self):
""" Returns a boolean indicating whether this type is an 'interface'
type that is implemented in Spidermonkey. At the moment, this
only returns true for the types from the TypedArray spec. """
type that is implemented in SpiderMonkey. """
return self.isInterface() and (self.isArrayBuffer() or
self.isArrayBufferView() or
self.isSharedArrayBuffer() or
self.isTypedArray())
self.isTypedArray() or
self.isReadableStream())
def isDictionary(self):
return False
@ -2275,6 +2278,9 @@ class IDLNullableType(IDLParameterizedType):
def isRecord(self):
return self.inner.isRecord()
def isReadableStream(self):
return self.inner.isReadableStream()
def isArrayBuffer(self):
return self.inner.isArrayBuffer()
@ -2634,6 +2640,9 @@ class IDLTypedefType(IDLType):
def isRecord(self):
return self.inner.isRecord()
def isReadableStream(self):
return self.inner.isReadableStream()
def isDictionary(self):
return self.inner.isDictionary()
@ -2945,7 +2954,8 @@ class IDLBuiltinType(IDLType):
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array'
'Float64Array',
'ReadableStream',
)
TagLookup = {
@ -2980,7 +2990,8 @@ class IDLBuiltinType(IDLType):
Types.Int32Array: IDLType.Tags.interface,
Types.Uint32Array: IDLType.Tags.interface,
Types.Float32Array: IDLType.Tags.interface,
Types.Float64Array: IDLType.Tags.interface
Types.Float64Array: IDLType.Tags.interface,
Types.ReadableStream: IDLType.Tags.interface,
}
def __init__(self, location, name, type):
@ -3027,6 +3038,9 @@ class IDLBuiltinType(IDLType):
return (self._typeTag >= IDLBuiltinType.Types.Int8Array and
self._typeTag <= IDLBuiltinType.Types.Float64Array)
def isReadableStream(self):
return self._typeTag == IDLBuiltinType.Types.ReadableStream
def isInterface(self):
# TypedArray things are interface types per the TypedArray spec,
# but we handle them as builtins because SpiderMonkey implements
@ -3034,7 +3048,8 @@ class IDLBuiltinType(IDLType):
return (self.isArrayBuffer() or
self.isArrayBufferView() or
self.isSharedArrayBuffer() or
self.isTypedArray())
self.isTypedArray() or
self.isReadableStream())
def isNonCallbackInterface(self):
# All the interfaces we can be are non-callback
@ -3104,6 +3119,7 @@ class IDLBuiltinType(IDLType):
# that's not an ArrayBuffer or a callback interface
(self.isArrayBuffer() and not other.isArrayBuffer()) or
(self.isSharedArrayBuffer() and not other.isSharedArrayBuffer()) or
(self.isReadableStream() and not other.isReadableStream()) or
# ArrayBufferView is distinguishable from everything
# that's not an ArrayBufferView or typed array.
(self.isArrayBufferView() and not other.isArrayBufferView() and
@ -3213,7 +3229,10 @@ BuiltinTypes = {
IDLBuiltinType.Types.Float32Array),
IDLBuiltinType.Types.Float64Array:
IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float64Array",
IDLBuiltinType.Types.Float64Array)
IDLBuiltinType.Types.Float64Array),
IDLBuiltinType.Types.ReadableStream:
IDLBuiltinType(BuiltinLocation("<builtin type>"), "ReadableStream",
IDLBuiltinType.Types.ReadableStream),
}
@ -5232,7 +5251,8 @@ class Tokenizer(object):
"maplike": "MAPLIKE",
"setlike": "SETLIKE",
"iterable": "ITERABLE",
"namespace": "NAMESPACE"
"namespace": "NAMESPACE",
"ReadableStream": "READABLESTREAM",
}
tokens.extend(keywords.values())
@ -6420,6 +6440,7 @@ class Parser(Tokenizer):
NonAnyType : PrimitiveType Null
| ARRAYBUFFER Null
| SHAREDARRAYBUFFER Null
| READABLESTREAM Null
| OBJECT Null
"""
if p[1] == "object":
@ -6428,6 +6449,8 @@ class Parser(Tokenizer):
type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer]
elif p[1] == "SharedArrayBuffer":
type = BuiltinTypes[IDLBuiltinType.Types.SharedArrayBuffer]
elif p[1] == "ReadableStream":
type = BuiltinTypes[IDLBuiltinType.Types.ReadableStream]
else:
type = BuiltinTypes[p[1]]

View file

@ -289,9 +289,9 @@ MatchInPutList(InternalRequest* aRequest,
} // namespace
void
AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, Response& aResponse,
ErrorResult& aRv)
AutoChildOpArgs::Add(JSContext* aCx, InternalRequest* aRequest,
BodyAction aBodyAction, SchemeAction aSchemeAction,
Response& aResponse, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
@ -329,7 +329,7 @@ AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction,
mTypeUtils->ToCacheRequest(pair.request(), aRequest, aBodyAction,
aSchemeAction, mStreamCleanupList, aRv);
if (!aRv.Failed()) {
mTypeUtils->ToCacheResponse(pair.response(), aResponse,
mTypeUtils->ToCacheResponse(aCx, pair.response(), aResponse,
mStreamCleanupList, aRv);
}

View file

@ -55,7 +55,7 @@ public:
void Add(InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, ErrorResult& aRv);
void Add(InternalRequest* aRequest, BodyAction aBodyAction,
void Add(JSContext* aCx, InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, Response& aResponse, ErrorResult& aRv);
const CacheOpArgs& SendAsOpArgs();

40
dom/cache/Cache.cpp vendored
View file

@ -184,7 +184,10 @@ public:
// Now store the unwrapped Response list in the Cache.
ErrorResult result;
RefPtr<Promise> put = mCache->PutAll(mRequestList, responseList, result);
// TODO: Here we use the JSContext as received by the ResolvedCallback, and
// its state could be the wrong one. The spec doesn't say anything
// about it, yet (bug 1384006)
RefPtr<Promise> put = mCache->PutAll(aCx, mRequestList, responseList, result);
if (NS_WARN_IF(result.Failed())) {
// TODO: abort the fetch requests we have running (bug 1157434)
mPromise->MaybeReject(result);
@ -245,7 +248,7 @@ Cache::Cache(nsIGlobalObject* aGlobal, CacheChild* aActor)
}
already_AddRefed<Promise>
Cache::Match(const RequestOrUSVString& aRequest,
Cache::Match(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@ -255,7 +258,8 @@ Cache::Match(const RequestOrUSVString& aRequest,
CacheChild::AutoLock actorLock(mActor);
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
RefPtr<InternalRequest> ir =
ToInternalRequest(aCx, aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -274,7 +278,7 @@ Cache::Match(const RequestOrUSVString& aRequest,
}
already_AddRefed<Promise>
Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
Cache::MatchAll(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@ -290,8 +294,8 @@ Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
AutoChildOpArgs args(this, CacheMatchAllArgs(void_t(), params), 1);
if (aRequest.WasPassed()) {
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
IgnoreBody, aRv);
RefPtr<InternalRequest> ir = ToInternalRequest(aCx, aRequest.Value(),
IgnoreBody, aRv);
if (aRv.Failed()) {
return nullptr;
}
@ -390,8 +394,8 @@ Cache::AddAll(JSContext* aContext,
}
already_AddRefed<Promise>
Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
ErrorResult& aRv)
Cache::Put(JSContext* aCx, const RequestOrUSVString& aRequest,
Response& aResponse, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
@ -404,14 +408,14 @@ Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
return nullptr;
}
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, ReadBody, aRv);
RefPtr<InternalRequest> ir = ToInternalRequest(aCx, aRequest, ReadBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
AutoChildOpArgs args(this, CachePutAllArgs(), 1);
args.Add(ir, ReadBody, TypeErrorOnInvalidScheme,
args.Add(aCx, ir, ReadBody, TypeErrorOnInvalidScheme,
aResponse, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
@ -421,7 +425,7 @@ Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
}
already_AddRefed<Promise>
Cache::Delete(const RequestOrUSVString& aRequest,
Cache::Delete(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@ -431,7 +435,8 @@ Cache::Delete(const RequestOrUSVString& aRequest,
CacheChild::AutoLock actorLock(mActor);
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
RefPtr<InternalRequest> ir =
ToInternalRequest(aCx, aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -450,7 +455,7 @@ Cache::Delete(const RequestOrUSVString& aRequest,
}
already_AddRefed<Promise>
Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
Cache::Keys(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@ -466,8 +471,8 @@ Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
AutoChildOpArgs args(this, CacheKeysArgs(void_t(), params), 1);
if (aRequest.WasPassed()) {
RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
IgnoreBody, aRv);
RefPtr<InternalRequest> ir =
ToInternalRequest(aCx, aRequest.Value(), IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -627,7 +632,7 @@ Cache::AddAll(const GlobalObject& aGlobal,
}
already_AddRefed<Promise>
Cache::PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
Cache::PutAll(JSContext* aCx, const nsTArray<RefPtr<Request>>& aRequestList,
const nsTArray<RefPtr<Response>>& aResponseList,
ErrorResult& aRv)
{
@ -644,7 +649,8 @@ Cache::PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
RefPtr<InternalRequest> ir = aRequestList[i]->GetInternalRequest();
args.Add(ir, ReadBody, TypeErrorOnInvalidScheme, *aResponseList[i], aRv);
args.Add(aCx, ir, ReadBody, TypeErrorOnInvalidScheme, *aResponseList[i],
aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}

16
dom/cache/Cache.h vendored
View file

@ -43,10 +43,10 @@ public:
// webidl interface methods
already_AddRefed<Promise>
Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
ErrorResult& aRv);
Match(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
MatchAll(const Optional<RequestOrUSVString>& aRequest,
MatchAll(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
Add(JSContext* aContext, const RequestOrUSVString& aRequest,
@ -55,13 +55,13 @@ public:
AddAll(JSContext* aContext,
const Sequence<OwningRequestOrUSVString>& aRequests, ErrorResult& aRv);
already_AddRefed<Promise>
Put(const RequestOrUSVString& aRequest, Response& aResponse,
Put(JSContext* aCx, const RequestOrUSVString& aRequest, Response& aResponse,
ErrorResult& aRv);
already_AddRefed<Promise>
Delete(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
ErrorResult& aRv);
Delete(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
Keys(const Optional<RequestOrUSVString>& aRequest,
Keys(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aParams, ErrorResult& aRv);
// binding methods
@ -100,7 +100,7 @@ private:
ErrorResult& aRv);
already_AddRefed<Promise>
PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
PutAll(JSContext* aCx, const nsTArray<RefPtr<Request>>& aRequestList,
const nsTArray<RefPtr<Response>>& aResponseList,
ErrorResult& aRv);

View file

@ -74,7 +74,11 @@ CacheOpChild::CacheOpChild(CacheWorkerHolder* aWorkerHolder,
MOZ_DIAGNOSTIC_ASSERT(mPromise);
MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerHolder);
SetWorkerHolder(aWorkerHolder);
RefPtr<CacheWorkerHolder> workerHolder =
CacheWorkerHolder::PreferBehavior(aWorkerHolder,
CacheWorkerHolder::PreventIdleShutdownStart);
SetWorkerHolder(workerHolder);
}
CacheOpChild::~CacheOpChild()
@ -165,7 +169,11 @@ CacheOpChild::Recv__delete__(const ErrorResult& aRv,
break;
}
actor->SetWorkerHolder(GetWorkerHolder());
RefPtr<CacheWorkerHolder> workerHolder =
CacheWorkerHolder::PreferBehavior(GetWorkerHolder(),
CacheWorkerHolder::AllowIdleShutdownStart);
actor->SetWorkerHolder(workerHolder);
RefPtr<Cache> cache = new Cache(mGlobal, actor);
mPromise->MaybeResolve(cache);
break;

View file

@ -204,7 +204,8 @@ CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
}
RefPtr<CacheWorkerHolder> workerHolder =
CacheWorkerHolder::Create(aWorkerPrivate);
CacheWorkerHolder::Create(aWorkerPrivate,
CacheWorkerHolder::AllowIdleShutdownStart);
if (!workerHolder) {
NS_WARNING("Worker thread is shutting down.");
aRv.Throw(NS_ERROR_FAILURE);
@ -315,7 +316,7 @@ CacheStorage::CacheStorage(nsresult aFailureResult)
}
already_AddRefed<Promise>
CacheStorage::Match(const RequestOrUSVString& aRequest,
CacheStorage::Match(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
@ -325,8 +326,8 @@ CacheStorage::Match(const RequestOrUSVString& aRequest,
return nullptr;
}
RefPtr<InternalRequest> request = ToInternalRequest(aRequest, IgnoreBody,
aRv);
RefPtr<InternalRequest> request =
ToInternalRequest(aCx, aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}

View file

@ -59,9 +59,9 @@ public:
DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
// webidl interface methods
already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise>
Match(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise> Has(const nsAString& aKey, ErrorResult& aRv);
already_AddRefed<Promise> Open(const nsAString& aKey, ErrorResult& aRv);
already_AddRefed<Promise> Delete(const nsAString& aKey, ErrorResult& aRv);

View file

@ -18,11 +18,11 @@ using mozilla::dom::workers::WorkerPrivate;
// static
already_AddRefed<CacheWorkerHolder>
CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate)
CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate, Behavior aBehavior)
{
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder();
RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder(aBehavior);
if (NS_WARN_IF(!workerHolder->HoldWorker(aWorkerPrivate, Terminating))) {
return nullptr;
}
@ -30,6 +30,28 @@ CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate)
return workerHolder.forget();
}
// static
already_AddRefed<CacheWorkerHolder>
CacheWorkerHolder::PreferBehavior(CacheWorkerHolder* aCurrentHolder,
Behavior aBehavior)
{
if (!aCurrentHolder) {
return nullptr;
}
RefPtr<CacheWorkerHolder> orig = aCurrentHolder;
if (orig->GetBehavior() == aBehavior) {
return orig.forget();
}
RefPtr<CacheWorkerHolder> replace = Create(orig->mWorkerPrivate, aBehavior);
if (!replace) {
return orig.forget();
}
return replace.forget();
}
void
CacheWorkerHolder::AddActor(ActorChild* aActor)
{
@ -88,8 +110,9 @@ CacheWorkerHolder::Notify(Status aStatus)
return true;
}
CacheWorkerHolder::CacheWorkerHolder()
: mNotified(false)
CacheWorkerHolder::CacheWorkerHolder(Behavior aBehavior)
: WorkerHolder(aBehavior)
, mNotified(false)
{
}

View file

@ -25,7 +25,11 @@ class CacheWorkerHolder final : public workers::WorkerHolder
{
public:
static already_AddRefed<CacheWorkerHolder>
Create(workers::WorkerPrivate* aWorkerPrivate);
Create(workers::WorkerPrivate* aWorkerPrivate,
Behavior aBehavior);
static already_AddRefed<CacheWorkerHolder>
PreferBehavior(CacheWorkerHolder* aCurrentHolder, Behavior aBehavior);
void AddActor(ActorChild* aActor);
void RemoveActor(ActorChild* aActor);
@ -36,7 +40,7 @@ public:
virtual bool Notify(workers::Status aStatus) override;
private:
CacheWorkerHolder();
explicit CacheWorkerHolder(Behavior aBehavior);
~CacheWorkerHolder();
nsTArray<ActorChild*> mActorList;

View file

@ -79,7 +79,7 @@ ToHeadersEntryList(nsTArray<HeadersEntry>& aOut, InternalHeaders* aHeaders)
} // namespace
already_AddRefed<InternalRequest>
TypeUtils::ToInternalRequest(const RequestOrUSVString& aIn,
TypeUtils::ToInternalRequest(JSContext* aCx, const RequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv)
{
if (aIn.IsRequest()) {
@ -87,7 +87,7 @@ TypeUtils::ToInternalRequest(const RequestOrUSVString& aIn,
// Check and set bodyUsed flag immediately because its on Request
// instead of InternalRequest.
CheckAndSetBodyUsed(&request, aBodyAction, aRv);
CheckAndSetBodyUsed(aCx, &request, aBodyAction, aRv);
if (aRv.Failed()) { return nullptr; }
return request.GetInternalRequest();
@ -97,7 +97,8 @@ TypeUtils::ToInternalRequest(const RequestOrUSVString& aIn,
}
already_AddRefed<InternalRequest>
TypeUtils::ToInternalRequest(const OwningRequestOrUSVString& aIn,
TypeUtils::ToInternalRequest(JSContext* aCx,
const OwningRequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv)
{
@ -106,7 +107,7 @@ TypeUtils::ToInternalRequest(const OwningRequestOrUSVString& aIn,
// Check and set bodyUsed flag immediately because its on Request
// instead of InternalRequest.
CheckAndSetBodyUsed(request, aBodyAction, aRv);
CheckAndSetBodyUsed(aCx, request, aBodyAction, aRv);
if (aRv.Failed()) { return nullptr; }
return request->GetInternalRequest();
@ -204,7 +205,7 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
}
void
TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn,
TypeUtils::ToCacheResponse(JSContext* aCx, CacheResponse& aOut, Response& aIn,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
@ -222,7 +223,10 @@ TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn,
nsCOMPtr<nsIInputStream> stream;
ir->GetUnfilteredBody(getter_AddRefs(stream));
if (stream) {
aIn.SetBodyUsed();
aIn.SetBodyUsed(aCx, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, aRv);
@ -426,8 +430,8 @@ TypeUtils::ProcessURL(nsACString& aUrl, bool* aSchemeValidOut,
}
void
TypeUtils::CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
ErrorResult& aRv)
TypeUtils::CheckAndSetBodyUsed(JSContext* aCx, Request* aRequest,
BodyAction aBodyAction, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aRequest);
@ -443,7 +447,10 @@ TypeUtils::CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
nsCOMPtr<nsIInputStream> stream;
aRequest->GetBody(getter_AddRefs(stream));
if (stream) {
aRequest->SetBodyUsed();
aRequest->SetBodyUsed(aCx, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
}

12
dom/cache/TypeUtils.h vendored
View file

@ -73,12 +73,12 @@ public:
GetIPCManager() = 0;
already_AddRefed<InternalRequest>
ToInternalRequest(const RequestOrUSVString& aIn, BodyAction aBodyAction,
ErrorResult& aRv);
ToInternalRequest(JSContext* aCx, const RequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv);
already_AddRefed<InternalRequest>
ToInternalRequest(const OwningRequestOrUSVString& aIn, BodyAction aBodyAction,
ErrorResult& aRv);
ToInternalRequest(JSContext* aCx, const OwningRequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv);
void
ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn,
@ -91,7 +91,7 @@ public:
ErrorResult& aRv);
void
ToCacheResponse(CacheResponse& aOut, Response& aIn,
ToCacheResponse(JSContext* aCx, CacheResponse& aOut, Response& aIn,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
@ -133,7 +133,7 @@ public:
private:
void
CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
CheckAndSetBodyUsed(JSContext* aCx, Request* aRequest, BodyAction aBodyAction,
ErrorResult& aRv);
already_AddRefed<InternalRequest>

View file

@ -2974,8 +2974,8 @@ ValidateRect(double& aX, double& aY, double& aWidth, double& aHeight, bool aIsZe
// The values of canvas API input are in double precision, but Moz2D APIs are
// using float precision. Bypass canvas API calls when the input is out of
// float precision to avoid precision problem
if (!std::isfinite((float)aX) | !std::isfinite((float)aY) |
!std::isfinite((float)aWidth) | !std::isfinite((float)aHeight)) {
if (!std::isfinite((float)aX) || !std::isfinite((float)aY) ||
!std::isfinite((float)aWidth) || !std::isfinite((float)aHeight)) {
return false;
}
@ -5973,7 +5973,7 @@ void
CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
double aDy, ErrorResult& aError)
{
RootedTypedArray<Uint8ClampedArray> arr(RootingCx());
RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
MOZ_ASSERT(inited);
@ -5989,7 +5989,7 @@ CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
double aDirtyHeight,
ErrorResult& aError)
{
RootedTypedArray<Uint8ClampedArray> arr(RootingCx());
RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
MOZ_ASSERT(inited);

View file

@ -929,7 +929,7 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
{
// Copy data into SourceSurface.
RootedTypedArray<Uint8ClampedArray> array(RootingCx());
RootedSpiderMonkeyInterface<Uint8ClampedArray> array(RootingCx());
DebugOnly<bool> inited = array.Init(aImageData.GetDataObject());
MOZ_ASSERT(inited);

View file

@ -475,7 +475,7 @@ WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level,
GLsizei depth, GLint border, const webgl::PackingInfo& pi,
const TexImageSource& src)
{
dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
dom::RootedSpiderMonkeyInterface<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height,
depth, border, pi, src, &scopedArr);
if (!blob)
@ -491,7 +491,7 @@ WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint lev
const webgl::PackingInfo& pi, const TexImageSource& src)
{
const GLint border = 0;
dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
dom::RootedSpiderMonkeyInterface<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height,
depth, border, pi, src, &scopedArr);
if (!blob)

View file

@ -1365,7 +1365,7 @@ public:
mDataIsJwk = false;
// Try ArrayBuffer
RootedTypedArray<ArrayBuffer> ab(aCx);
RootedSpiderMonkeyInterface<ArrayBuffer> ab(aCx);
if (ab.Init(aKeyData)) {
if (!mKeyData.Assign(ab)) {
mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
@ -1374,7 +1374,7 @@ public:
}
// Try ArrayBufferView
RootedTypedArray<ArrayBufferView> abv(aCx);
RootedSpiderMonkeyInterface<ArrayBufferView> abv(aCx);
if (abv.Init(aKeyData)) {
if (!mKeyData.Assign(abv)) {
mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;

207
dom/fetch/BodyExtractor.cpp Normal file
View file

@ -0,0 +1,207 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "BodyExtractor.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/XMLHttpRequest.h"
#include "nsContentUtils.h"
#include "nsIDOMDocument.h"
#include "nsIDOMSerializer.h"
#include "nsIGlobalObject.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIStorageStream.h"
#include "nsStringStream.h"
#include "nsIUnicodeEncoder.h"
namespace mozilla {
namespace dom {
static nsresult
GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength,
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset)
{
aContentType.SetIsVoid(true);
aCharset.Truncate();
*aContentLength = aDataLength;
const char* data = reinterpret_cast<const char*>(aData);
nsCOMPtr<nsIInputStream> stream;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
NS_ASSIGNMENT_COPY);
NS_ENSURE_SUCCESS(rv, rv);
stream.forget(aResult);
return NS_OK;
}
template<> nsresult
BodyExtractor<const ArrayBuffer>::GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const
{
mBody->ComputeLengthAndData();
return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
aResult, aContentLength, aContentTypeWithCharset,
aCharset);
}
template<> nsresult
BodyExtractor<const ArrayBufferView>::GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const
{
mBody->ComputeLengthAndData();
return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
aResult, aContentLength, aContentTypeWithCharset,
aCharset);
}
template<> nsresult
BodyExtractor<nsIDocument>::GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const
{
nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
NS_ENSURE_STATE(domdoc);
aCharset.AssignLiteral("UTF-8");
nsresult rv;
nsCOMPtr<nsIStorageStream> storStream;
rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> output;
rv = storStream->GetOutputStream(0, getter_AddRefs(output));
NS_ENSURE_SUCCESS(rv, rv);
if (mBody->IsHTMLDocument()) {
aContentTypeWithCharset.AssignLiteral("text/html;charset=UTF-8");
nsString serialized;
if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsAutoCString utf8Serialized;
if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
uint32_t written;
rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(written == utf8Serialized.Length());
} else {
aContentTypeWithCharset.AssignLiteral("application/xml;charset=UTF-8");
nsCOMPtr<nsIDOMSerializer> serializer =
do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Make sure to use the encoding we'll send
rv = serializer->SerializeToStream(domdoc, output, aCharset);
NS_ENSURE_SUCCESS(rv, rv);
}
output->Close();
uint32_t length;
rv = storStream->GetLength(&length);
NS_ENSURE_SUCCESS(rv, rv);
*aContentLength = length;
rv = storStream->NewInputStream(0, aResult);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
template<> nsresult
BodyExtractor<const nsAString>::GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const
{
nsCOMPtr<nsIUnicodeEncoder> encoder =
EncodingUtils::EncoderForEncoding("UTF-8");
if (!encoder) {
return NS_ERROR_OUT_OF_MEMORY;
}
int32_t destBufferLen;
nsresult rv = encoder->GetMaxLength(mBody->BeginReading(), mBody->Length(),
&destBufferLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString encoded;
if (!encoded.SetCapacity(destBufferLen, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
char* destBuffer = encoded.BeginWriting();
int32_t srcLen = (int32_t) mBody->Length();
int32_t outLen = destBufferLen;
rv = encoder->Convert(mBody->BeginReading(), &srcLen, destBuffer, &outLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(outLen <= destBufferLen);
encoded.SetLength(outLen);
rv = NS_NewCStringInputStream(aResult, encoded);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
*aContentLength = outLen;
aContentTypeWithCharset.AssignLiteral("text/plain;charset=UTF-8");
aCharset.AssignLiteral("UTF-8");
return NS_OK;
}
template<> nsresult
BodyExtractor<nsIInputStream>::GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const
{
aContentTypeWithCharset.AssignLiteral("text/plain");
aCharset.Truncate();
nsresult rv = mBody->Available(aContentLength);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> stream(mBody);
stream.forget(aResult);
return NS_OK;
}
template<> nsresult
BodyExtractor<nsIXHRSendable>::GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const
{
return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
aCharset);
}
} // dom namespace
} // mozilla namespace

48
dom/fetch/BodyExtractor.h Normal file
View file

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_BodyExtractor_h
#define mozilla_dom_BodyExtractor_h
#include "jsapi.h"
#include "nsString.h"
class nsIInputStream;
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class BodyExtractorBase
{
public:
virtual nsresult GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const = 0;
};
// The implementation versions of this template are:
// ArrayBuffer, ArrayBufferView, nsIXHRSendable (Blob, FormData,
// URLSearchParams), nsAString, nsIDocument, nsIInputStream.
template<typename Type>
class BodyExtractor final : public BodyExtractorBase
{
Type* mBody;
public:
explicit BodyExtractor(Type* aBody) : mBody(aBody)
{}
nsresult GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentTypeWithCharset,
nsACString& aCharset) const override;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_BodyExtractor_h

View file

@ -5,13 +5,13 @@
#include "Fetch.h"
#include "FetchConsumer.h"
#include "FetchStream.h"
#include "nsIDocument.h"
#include "nsIGlobalObject.h"
#include "nsIStreamLoader.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIUnicodeDecoder.h"
#include "nsIUnicodeEncoder.h"
#include "nsDOMString.h"
#include "nsNetUtil.h"
@ -22,7 +22,7 @@
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BodyUtil.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/FetchDriver.h"
#include "mozilla/dom/File.h"
@ -35,8 +35,10 @@
#include "mozilla/dom/Response.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "BodyExtractor.h"
#include "FetchObserver.h"
#include "InternalRequest.h"
#include "InternalResponse.h"
@ -722,154 +724,48 @@ WorkerFetchResolver::FlushConsoleReport()
mReporter->FlushConsoleReports(worker->GetDocument());
}
namespace {
nsresult
ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
nsIInputStream** aStream,
uint64_t& aContentLength)
{
aBuffer.ComputeLengthAndData();
aContentLength = aBuffer.Length();
//XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
return NS_NewByteInputStream(aStream,
reinterpret_cast<char*>(aBuffer.Data()),
aBuffer.Length(), NS_ASSIGNMENT_COPY);
}
nsresult
ExtractFromArrayBufferView(const ArrayBufferView& aBuffer,
nsIInputStream** aStream,
uint64_t& aContentLength)
{
aBuffer.ComputeLengthAndData();
aContentLength = aBuffer.Length();
//XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
return NS_NewByteInputStream(aStream,
reinterpret_cast<char*>(aBuffer.Data()),
aBuffer.Length(), NS_ASSIGNMENT_COPY);
}
nsresult
ExtractFromBlob(const Blob& aBlob,
nsIInputStream** aStream,
nsCString& aContentType,
uint64_t& aContentLength)
{
RefPtr<BlobImpl> impl = aBlob.Impl();
ErrorResult rv;
aContentLength = impl->GetSize(rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
impl->GetInternalStream(aStream, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
nsAutoString type;
impl->GetType(type);
aContentType = NS_ConvertUTF16toUTF8(type);
return NS_OK;
}
nsresult
ExtractFromFormData(FormData& aFormData,
nsIInputStream** aStream,
nsCString& aContentType,
uint64_t& aContentLength)
{
nsAutoCString unusedCharset;
return aFormData.GetSendInfo(aStream, &aContentLength,
aContentType, unusedCharset);
}
nsresult
ExtractFromUSVString(const nsString& aStr,
nsIInputStream** aStream,
nsCString& aContentType,
uint64_t& aContentLength)
{
nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
if (!encoder) {
return NS_ERROR_OUT_OF_MEMORY;
}
int32_t destBufferLen;
nsresult rv = encoder->GetMaxLength(aStr.get(), aStr.Length(), &destBufferLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString encoded;
if (!encoded.SetCapacity(destBufferLen, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
char* destBuffer = encoded.BeginWriting();
int32_t srcLen = (int32_t) aStr.Length();
int32_t outLen = destBufferLen;
rv = encoder->Convert(aStr.get(), &srcLen, destBuffer, &outLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(outLen <= destBufferLen);
encoded.SetLength(outLen);
aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8");
aContentLength = outLen;
return NS_NewCStringInputStream(aStream, encoded);
}
nsresult
ExtractFromURLSearchParams(const URLSearchParams& aParams,
nsIInputStream** aStream,
nsCString& aContentType,
uint64_t& aContentLength)
{
nsAutoString serialized;
aParams.Stringify(serialized);
aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
aContentLength = serialized.Length();
return NS_NewCStringInputStream(aStream, NS_ConvertUTF16toUTF8(serialized));
}
} // namespace
nsresult
ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentType,
nsCString& aContentTypeWithCharset,
uint64_t& aContentLength)
{
MOZ_ASSERT(aStream);
nsAutoCString charset;
aContentTypeWithCharset.SetIsVoid(true);
if (aBodyInit.IsArrayBuffer()) {
const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
return ExtractFromArrayBuffer(buf, aStream, aContentLength);
BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsArrayBufferView()) {
const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
return ExtractFromArrayBufferView(buf, aStream, aContentLength);
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsBlob()) {
const Blob& blob = aBodyInit.GetAsBlob();
return ExtractFromBlob(blob, aStream, aContentType, aContentLength);
Blob& blob = aBodyInit.GetAsBlob();
BodyExtractor<nsIXHRSendable> body(&blob);
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsFormData()) {
FormData& form = aBodyInit.GetAsFormData();
return ExtractFromFormData(form, aStream, aContentType, aContentLength);
FormData& formData = aBodyInit.GetAsFormData();
BodyExtractor<nsIXHRSendable> body(&formData);
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsUSVString()) {
nsAutoString str;
str.Assign(aBodyInit.GetAsUSVString());
return ExtractFromUSVString(str, aStream, aContentType, aContentLength);
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsURLSearchParams()) {
URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength);
URLSearchParams& usp = aBodyInit.GetAsURLSearchParams();
BodyExtractor<nsIXHRSendable> body(&usp);
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
NS_NOTREACHED("Should never reach here");
@ -877,69 +773,228 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa
}
nsresult
ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentType,
nsCString& aContentTypeWithCharset,
uint64_t& aContentLength)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(!*aStream);
nsAutoCString charset;
aContentTypeWithCharset.SetIsVoid(true);
if (aBodyInit.IsArrayBuffer()) {
const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
return ExtractFromArrayBuffer(buf, aStream, aContentLength);
BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsArrayBufferView()) {
const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
return ExtractFromArrayBufferView(buf, aStream, aContentLength);
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsBlob()) {
const Blob& blob = aBodyInit.GetAsBlob();
return ExtractFromBlob(blob, aStream, aContentType, aContentLength);
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsFormData()) {
FormData& form = aBodyInit.GetAsFormData();
return ExtractFromFormData(form, aStream, aContentType, aContentLength);
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsUSVString()) {
nsAutoString str;
str.Assign(aBodyInit.GetAsUSVString());
return ExtractFromUSVString(str, aStream, aContentType, aContentLength);
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsURLSearchParams()) {
URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength);
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsURLSearchParams());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
NS_NOTREACHED("Should never reach here");
return NS_ERROR_FAILURE;
}
nsresult
ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentTypeWithCharset,
uint64_t& aContentLength)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(!*aStream);
// ReadableStreams should be handled by
// BodyExtractorReadableStream::GetAsStream.
MOZ_ASSERT(!aBodyInit.IsReadableStream());
nsAutoCString charset;
aContentTypeWithCharset.SetIsVoid(true);
if (aBodyInit.IsArrayBuffer()) {
BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsArrayBufferView()) {
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsBlob()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsFormData()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsUSVString()) {
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsURLSearchParams()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsURLSearchParams());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
NS_NOTREACHED("Should never reach here");
return NS_ERROR_FAILURE;
}
template <class Derived>
FetchBody<Derived>::FetchBody()
FetchBody<Derived>::FetchBody(nsIGlobalObject* aOwner)
: mWorkerPrivate(nullptr)
, mOwner(aOwner)
, mReadableStreamBody(nullptr)
, mReadableStreamReader(nullptr)
, mBodyUsed(false)
{
MOZ_ASSERT(aOwner);
if (!NS_IsMainThread()) {
mWorkerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(mWorkerPrivate);
} else {
mWorkerPrivate = nullptr;
}
}
template
FetchBody<Request>::FetchBody();
FetchBody<Request>::FetchBody(nsIGlobalObject* aOwner);
template
FetchBody<Response>::FetchBody();
FetchBody<Response>::FetchBody(nsIGlobalObject* aOwner);
template <class Derived>
FetchBody<Derived>::~FetchBody()
{
}
template
FetchBody<Request>::~FetchBody();
template
FetchBody<Response>::~FetchBody();
template <class Derived>
bool
FetchBody<Derived>::BodyUsed() const
{
if (mBodyUsed) {
return true;
}
// If this object is disturbed or locked, return false.
if (mReadableStreamBody) {
AutoJSAPI jsapi;
if (!jsapi.Init(mOwner)) {
return true;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
if (JS::ReadableStreamIsDisturbed(body) ||
JS::ReadableStreamIsLocked(body) ||
!JS::ReadableStreamIsReadable(body)) {
return true;
}
}
return false;
}
template
bool
FetchBody<Request>::BodyUsed() const;
template
bool
FetchBody<Response>::BodyUsed() const;
template <class Derived>
void
FetchBody<Derived>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv)
{
MOZ_ASSERT(aCx);
if (mBodyUsed) {
return;
}
mBodyUsed = true;
// If we already have a ReadableStreamBody and it has been created by DOM, we
// have to lock it now because it can have been shared with other objects.
if (mReadableStreamBody) {
JS::Rooted<JSObject*> readableStreamObj(aCx, mReadableStreamBody);
if (JS::ReadableStreamGetMode(readableStreamObj) ==
JS::ReadableStreamMode::ExternalSource) {
LockStream(aCx, readableStreamObj, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
// If this is not a native ReadableStream, let's activate the
// FetchStreamReader.
MOZ_ASSERT(mFetchStreamReader);
JS::Rooted<JSObject*> reader(aCx);
mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
mReadableStreamReader = reader;
}
}
}
template
void
FetchBody<Request>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
template
void
FetchBody<Response>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
template <class Derived>
already_AddRefed<Promise>
FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
FetchBody<Derived>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv)
{
RefPtr<AbortSignal> signal = DerivedClass()->GetSignal();
if (signal && signal->Aborted()) {
@ -952,11 +1007,15 @@ FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
return nullptr;
}
SetBodyUsed();
SetBodyUsed(aCx, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject();
RefPtr<Promise> promise =
FetchBodyConsumer<Derived>::Create(DerivedClass()->GetParentObject(),
this, signal, aType, aRv);
FetchBodyConsumer<Derived>::Create(global, this, signal, aType, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -966,11 +1025,11 @@ FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
template
already_AddRefed<Promise>
FetchBody<Request>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
FetchBody<Request>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
template
already_AddRefed<Promise>
FetchBody<Response>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
FetchBody<Response>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
template <class Derived>
void
@ -1000,5 +1059,170 @@ template
void
FetchBody<Response>::SetMimeType();
template <class Derived>
void
FetchBody<Derived>::SetReadableStreamBody(JSObject* aBody)
{
MOZ_ASSERT(!mReadableStreamBody);
MOZ_ASSERT(aBody);
mReadableStreamBody = aBody;
}
template
void
FetchBody<Request>::SetReadableStreamBody(JSObject* aBody);
template
void
FetchBody<Response>::SetReadableStreamBody(JSObject* aBody);
template <class Derived>
void
FetchBody<Derived>::GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aBodyOut,
ErrorResult& aRv)
{
if (mReadableStreamBody) {
aBodyOut.set(mReadableStreamBody);
return;
}
nsCOMPtr<nsIInputStream> inputStream;
DerivedClass()->GetBody(getter_AddRefs(inputStream));
if (!inputStream) {
aBodyOut.set(nullptr);
return;
}
JS::Rooted<JSObject*> body(aCx);
FetchStream::Create(aCx, this, DerivedClass()->GetParentObject(),
inputStream, &body, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
MOZ_ASSERT(body);
// If the body has been already consumed, we lock the stream.
if (BodyUsed()) {
LockStream(aCx, body, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
mReadableStreamBody = body;
aBodyOut.set(mReadableStreamBody);
}
template
void
FetchBody<Request>::GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
ErrorResult& aRv);
template
void
FetchBody<Response>::GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
ErrorResult& aRv);
template <class Derived>
void
FetchBody<Derived>::LockStream(JSContext* aCx,
JS::HandleObject aStream,
ErrorResult& aRv)
{
MOZ_ASSERT(JS::ReadableStreamGetMode(aStream) ==
JS::ReadableStreamMode::ExternalSource);
// This is native stream, creating a reader will not execute any JS code.
JS::Rooted<JSObject*> reader(aCx,
JS::ReadableStreamGetReader(aCx, aStream,
JS::ReadableStreamReaderMode::Default));
if (!reader) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
mReadableStreamReader = reader;
}
template
void
FetchBody<Request>::LockStream(JSContext* aCx,
JS::HandleObject aStream,
ErrorResult& aRv);
template
void
FetchBody<Response>::LockStream(JSContext* aCx,
JS::HandleObject aStream,
ErrorResult& aRv);
template <class Derived>
void
FetchBody<Derived>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aBodyOut,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream,
ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aStreamReader);
MOZ_DIAGNOSTIC_ASSERT(aInputStream);
MOZ_DIAGNOSTIC_ASSERT(!BodyUsed());
aBodyOut.set(nullptr);
*aStreamReader = nullptr;
*aInputStream = nullptr;
if (!mReadableStreamBody) {
return;
}
JS::Rooted<JSObject*> stream(aCx, mReadableStreamBody);
// If this is a ReadableStream with an external source, this has been
// generated by a Fetch. In this case, Fetch will be able to recreate it
// again when GetBody() is called.
if (JS::ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource) {
aBodyOut.set(nullptr);
return;
}
JS::Rooted<JSObject*> branch1(aCx);
JS::Rooted<JSObject*> branch2(aCx);
if (!JS::ReadableStreamTee(aCx, stream, &branch1, &branch2)) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
mReadableStreamBody = branch1;
aBodyOut.set(branch2);
aRv = FetchStreamReader::Create(aCx, mOwner, aStreamReader, aInputStream);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
template
void
FetchBody<Request>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream,
ErrorResult& aRv);
template
void
FetchBody<Response>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream,
ErrorResult& aRv);
} // namespace dom
} // namespace mozilla

View file

@ -17,17 +17,23 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/FetchStreamReader.h"
// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None
#undef None
#include "mozilla/dom/RequestBinding.h"
class nsIGlobalObject;
class nsIEventTarget;
namespace mozilla {
namespace dom {
class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString;
class BlobImpl;
class InternalRequest;
class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
class OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
struct ReadableStream;
class RequestOrUSVString;
namespace workers {
@ -41,13 +47,20 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
nsresult
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest);
/* Deal with unwieldy long webIDL-generated type names */
namespace fetch {
typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString BodyInit;
typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString ResponseBodyInit;
typedef OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString OwningBodyInit;
};
/*
* Creates an nsIInputStream based on the fetch specifications 'extract a byte
* stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
* Stores content type in out param aContentType.
*/
nsresult
ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentType,
uint64_t& aContentLength);
@ -56,7 +69,17 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa
* Non-owning version.
*/
nsresult
ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentType,
uint64_t& aContentLength);
/*
* Non-owning version. This method should go away when BodyInit will contain
* ReadableStream.
*/
nsresult
ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentType,
uint64_t& aContentLength);
@ -72,6 +95,19 @@ enum FetchConsumeType
CONSUME_TEXT,
};
class FetchStreamHolder
{
public:
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
virtual void NullifyStream() = 0;
virtual void MarkAsRead() = 0;
virtual JSObject* ReadableStreamBody() = 0;
};
/*
* FetchBody's body consumption uses nsIInputStreamPump to read from the
* underlying stream to a block of memory, which is then adopted by
@ -106,54 +142,82 @@ enum FetchConsumeType
* The pump is always released on the main thread.
*/
template <class Derived>
class FetchBody
class FetchBody : public FetchStreamHolder
{
public:
friend class FetchBodyConsumer<Derived>;
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
bool
BodyUsed() const { return mBodyUsed; }
BodyUsed() const;
already_AddRefed<Promise>
ArrayBuffer(ErrorResult& aRv)
ArrayBuffer(JSContext* aCx, ErrorResult& aRv)
{
return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
return ConsumeBody(aCx, CONSUME_ARRAYBUFFER, aRv);
}
already_AddRefed<Promise>
Blob(ErrorResult& aRv)
Blob(JSContext* aCx, ErrorResult& aRv)
{
return ConsumeBody(CONSUME_BLOB, aRv);
return ConsumeBody(aCx, CONSUME_BLOB, aRv);
}
already_AddRefed<Promise>
FormData(ErrorResult& aRv)
FormData(JSContext* aCx, ErrorResult& aRv)
{
return ConsumeBody(CONSUME_FORMDATA, aRv);
return ConsumeBody(aCx, CONSUME_FORMDATA, aRv);
}
already_AddRefed<Promise>
Json(ErrorResult& aRv)
Json(JSContext* aCx, ErrorResult& aRv)
{
return ConsumeBody(CONSUME_JSON, aRv);
return ConsumeBody(aCx, CONSUME_JSON, aRv);
}
already_AddRefed<Promise>
Text(ErrorResult& aRv)
Text(JSContext* aCx, ErrorResult& aRv)
{
return ConsumeBody(CONSUME_TEXT, aRv);
return ConsumeBody(aCx, CONSUME_TEXT, aRv);
}
void
GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aBodyOut,
ErrorResult& aRv);
// If the body contains a ReadableStream body object, this method produces a
// tee() of it.
void
MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aBodyOut,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream,
ErrorResult& aRv);
// Utility public methods accessed by various runnables.
// This method _must_ be called in order to set the body as used. If the body
// is a ReadableStream, this method will start reading the stream.
// More in details, this method does:
// 1) It uses an internal flag to track if the body is used. This is tracked
// separately from the ReadableStream disturbed state due to purely native
// streams.
// 2) If there is a ReadableStream reflector for the native stream it is
// Locked.
// 3) If there is a JS ReadableStream then we begin pumping it into the native
// body stream. This effectively locks and disturbs the stream.
//
// Note that JSContext is used only if there is a ReadableStream (this can
// happen because the body is a ReadableStream or because attribute body has
// already been used by content). If something goes wrong using
// ReadableStream, errors will be reported via ErrorResult and not as JS
// exceptions in JSContext. This is done in order to have a centralized error
// reporting way.
//
// Exceptions generated when reading from the ReadableStream are directly sent
// to the Console.
void
SetBodyUsed()
{
mBodyUsed = true;
}
SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
const nsCString&
MimeType() const
@ -161,19 +225,54 @@ public:
return mMimeType;
}
// FetchStreamHolder
void
NullifyStream() override
{
mReadableStreamBody = nullptr;
mReadableStreamReader = nullptr;
mFetchStreamReader = nullptr;
}
JSObject*
ReadableStreamBody() override
{
MOZ_ASSERT(mReadableStreamBody);
return mReadableStreamBody;
}
void
MarkAsRead() override
{
mBodyUsed = true;
}
virtual AbortSignal*
GetSignal() const = 0;
protected:
FetchBody();
nsCOMPtr<nsIGlobalObject> mOwner;
explicit FetchBody(nsIGlobalObject* aOwner);
// Always set whenever the FetchBody is created on the worker thread.
workers::WorkerPrivate* mWorkerPrivate;
// This is the ReadableStream exposed to content. Its underlying source is a FetchStream object.
JS::Heap<JSObject*> mReadableStreamBody;
// This is the Reader used to retrieve data from the body.
JS::Heap<JSObject*> mReadableStreamReader;
RefPtr<FetchStreamReader> mFetchStreamReader;
virtual ~FetchBody();
void
SetMimeType();
void
SetReadableStreamBody(JSObject* aBody);
private:
Derived*
DerivedClass() const
@ -182,7 +281,10 @@ private:
}
already_AddRefed<Promise>
ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
void
LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv);
bool
IsOnTargetThread()
@ -199,6 +301,9 @@ private:
// Only ever set once, always on target thread.
bool mBodyUsed;
nsCString mMimeType;
// The main-thread event target for runnable dispatching.
nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
};
} // namespace dom

640
dom/fetch/FetchStream.cpp Normal file
View file

@ -0,0 +1,640 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "FetchStream.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/Maybe.h"
#include "nsNetCID.h"
#include "nsITransport.h"
#include "nsIStreamTransportService.h"
#include "nsProxyRelease.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "Workers.h"
#include "mozilla/dom/DOMError.h"
#define FETCH_STREAM_FLAG 0
static NS_DEFINE_CID(kStreamTransportServiceCID,
NS_STREAMTRANSPORTSERVICE_CID);
namespace mozilla {
namespace dom {
using namespace workers;
namespace {
class FetchStreamWorkerHolder final : public WorkerHolder
{
public:
explicit FetchStreamWorkerHolder(FetchStream* aStream)
: WorkerHolder()
, mStream(aStream)
, mWasNotified(false)
{}
bool Notify(Status aStatus) override
{
if (!mWasNotified) {
mWasNotified = true;
mStream->Close();
}
return true;
}
WorkerPrivate* GetWorkerPrivate() const
{
return mWorkerPrivate;
}
private:
RefPtr<FetchStream> mStream;
bool mWasNotified;
};
class FetchStreamWorkerHolderShutdown final : public WorkerControlRunnable
{
public:
FetchStreamWorkerHolderShutdown(WorkerPrivate* aWorkerPrivate,
UniquePtr<WorkerHolder>&& aHolder,
nsCOMPtr<nsIGlobalObject>&& aGlobal,
RefPtr<FetchStreamHolder>&& aStreamHolder)
: WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
, mHolder(Move(aHolder))
, mGlobal(Move(aGlobal))
, mStreamHolder(Move(aStreamHolder))
{}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
mHolder = nullptr;
mGlobal = nullptr;
mStreamHolder->NullifyStream();
mStreamHolder = nullptr;
return true;
}
// This runnable starts from a JS Thread. We need to disable a couple of
// assertions by overriding the following methods.
bool
PreDispatch(WorkerPrivate* aWorkerPrivate) override
{
return true;
}
void
PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
{}
private:
UniquePtr<WorkerHolder> mHolder;
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<FetchStreamHolder> mStreamHolder;
};
} // anonymous
NS_IMPL_ISUPPORTS(FetchStream, nsIInputStreamCallback, nsIObserver,
nsISupportsWeakReference)
/* static */ void
FetchStream::Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aCx);
MOZ_DIAGNOSTIC_ASSERT(aInputStream);
MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
RefPtr<FetchStream> stream = new FetchStream(aGlobal, aStreamHolder, aInputStream);
if (NS_IsMainThread()) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (NS_WARN_IF(!os)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
aRv = os->AddObserver(stream, DOM_WINDOW_DESTROYED_TOPIC, true);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
} else {
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(workerPrivate);
UniquePtr<FetchStreamWorkerHolder> holder(
new FetchStreamWorkerHolder(stream));
if (NS_WARN_IF(!holder->HoldWorker(workerPrivate, Closing))) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// Note, this will create a ref-cycle between the holder and the stream.
// The cycle is broken when the stream is closed or the worker begins
// shutting down.
stream->mWorkerHolder = Move(holder);
}
if (!JS::HasReadableStreamCallbacks(aCx)) {
JS::SetReadableStreamCallbacks(aCx,
&FetchStream::RequestDataCallback,
&FetchStream::WriteIntoReadRequestCallback,
&FetchStream::CancelCallback,
&FetchStream::ClosedCallback,
&FetchStream::ErroredCallback,
&FetchStream::FinalizeCallback);
}
JS::Rooted<JSObject*> body(aCx,
JS::NewReadableExternalSourceStreamObject(aCx, stream, FETCH_STREAM_FLAG));
if (!body) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
// This will be released in FetchStream::FinalizeCallback(). We are
// guaranteed the jsapi will call FinalizeCallback when ReadableStream
// js object is finalized.
NS_ADDREF(stream.get());
aStream.set(body);
}
/* static */ void
FetchStream::RequestDataCallback(JSContext* aCx,
JS::HandleObject aStream,
void* aUnderlyingSource,
uint8_t aFlags,
size_t aDesiredSize)
{
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
MOZ_DIAGNOSTIC_ASSERT(JS::ReadableStreamIsDisturbed(aStream));
RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
stream->AssertIsOnOwningThread();
MutexAutoLock lock(stream->mMutex);
MOZ_DIAGNOSTIC_ASSERT(stream->mState == eInitializing ||
stream->mState == eWaiting ||
stream->mState == eChecking ||
stream->mState == eReading);
if (stream->mState == eReading) {
// We are already reading data.
return;
}
if (stream->mState == eChecking) {
// If we are looking for more data, there is nothing else we should do:
// let's move this checking operation in a reading.
MOZ_ASSERT(stream->mInputStream);
stream->mState = eReading;
return;
}
if (stream->mState == eInitializing) {
// The stream has been used for the first time.
stream->mStreamHolder->MarkAsRead();
}
stream->mState = eReading;
if (!stream->mInputStream) {
// This is the first use of the stream. Let's convert the
// mOriginalInputStream into an nsIAsyncInputStream.
MOZ_ASSERT(stream->mOriginalInputStream);
bool nonBlocking = false;
nsresult rv = stream->mOriginalInputStream->IsNonBlocking(&nonBlocking);
if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, lock, aStream, rv);
return;
}
nsCOMPtr<nsIAsyncInputStream> asyncStream =
do_QueryInterface(stream->mOriginalInputStream);
if (!nonBlocking || !asyncStream) {
nsCOMPtr<nsIStreamTransportService> sts =
do_GetService(kStreamTransportServiceCID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, lock, aStream, rv);
return;
}
nsCOMPtr<nsITransport> transport;
rv = sts->CreateInputTransport(stream->mOriginalInputStream,
/* aStartOffset */ 0,
/* aReadLimit */ -1,
/* aCloseWhenDone */ true,
getter_AddRefs(transport));
if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, lock, aStream, rv);
return;
}
nsCOMPtr<nsIInputStream> wrapper;
rv = transport->OpenInputStream(/* aFlags */ 0,
/* aSegmentSize */ 0,
/* aSegmentCount */ 0,
getter_AddRefs(wrapper));
if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, lock, aStream, rv);
return;
}
asyncStream = do_QueryInterface(wrapper);
}
stream->mInputStream = asyncStream;
stream->mOriginalInputStream = nullptr;
}
MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream);
nsresult rv =
stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, lock, aStream, rv);
return;
}
// All good.
}
/* static */ void
FetchStream::WriteIntoReadRequestCallback(JSContext* aCx,
JS::HandleObject aStream,
void* aUnderlyingSource,
uint8_t aFlags, void* aBuffer,
size_t aLength, size_t* aByteWritten)
{
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
MOZ_DIAGNOSTIC_ASSERT(aBuffer);
MOZ_DIAGNOSTIC_ASSERT(aByteWritten);
RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
stream->AssertIsOnOwningThread();
MutexAutoLock lock(stream->mMutex);
MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
MOZ_DIAGNOSTIC_ASSERT(stream->mState == eWriting);
stream->mState = eChecking;
uint32_t written;
nsresult rv =
stream->mInputStream->Read(static_cast<char*>(aBuffer), aLength, &written);
if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, lock, aStream, rv);
return;
}
*aByteWritten = written;
if (written == 0) {
stream->CloseAndReleaseObjects(aCx, lock, aStream);
return;
}
rv = stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
if (NS_WARN_IF(NS_FAILED(rv))) {
stream->ErrorPropagation(aCx, lock, aStream, rv);
return;
}
// All good.
}
/* static */ JS::Value
FetchStream::CancelCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags,
JS::HandleValue aReason)
{
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
// This is safe because we created an extra reference in FetchStream::Create()
// that won't be released until FetchStream::FinalizeCallback() is called.
// We are guaranteed that won't happen until the js ReadableStream object
// is finalized.
FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
stream->AssertIsOnOwningThread();
if (stream->mInputStream) {
stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
}
stream->ReleaseObjects();
return JS::UndefinedValue();
}
/* static */ void
FetchStream::ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags)
{
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
}
/* static */ void
FetchStream::ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags,
JS::HandleValue aReason)
{
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
// This is safe because we created an extra reference in FetchStream::Create()
// that won't be released until FetchStream::FinalizeCallback() is called.
// We are guaranteed that won't happen until the js ReadableStream object
// is finalized.
FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
stream->AssertIsOnOwningThread();
if (stream->mState == eInitializing) {
// The stream has been used for the first time.
stream->mStreamHolder->MarkAsRead();
}
if (stream->mInputStream) {
stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
}
stream->ReleaseObjects();
}
void
FetchStream::FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags)
{
MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
// This can be called in any thread.
// This takes ownership of the ref created in FetchStream::Create().
RefPtr<FetchStream> stream =
dont_AddRef(static_cast<FetchStream*>(aUnderlyingSource));
stream->ReleaseObjects();
}
FetchStream::FetchStream(nsIGlobalObject* aGlobal,
FetchStreamHolder* aStreamHolder,
nsIInputStream* aInputStream)
: mMutex("FetchStream::mMutex")
, mState(eInitializing)
, mGlobal(aGlobal)
, mStreamHolder(aStreamHolder)
, mOriginalInputStream(aInputStream)
// TODO: Replace with mGlobal->EventTargetFor(TaskCategory::Other)
// When we have the Dispatcher API in the tree, see Issue #1442
, mOwningEventTarget(NS_GetCurrentThread())
{
MOZ_DIAGNOSTIC_ASSERT(aInputStream);
MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
}
FetchStream::~FetchStream()
{
}
void
FetchStream::ErrorPropagation(JSContext* aCx,
const MutexAutoLock& aProofOfLock,
JS::HandleObject aStream,
nsresult aError)
{
AssertIsOnOwningThread();
// Nothing to do.
if (mState == eClosed) {
return;
}
// Let's close the stream.
if (aError == NS_BASE_STREAM_CLOSED) {
CloseAndReleaseObjects(aCx, aProofOfLock, aStream);
return;
}
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
// Let's use a generic error.
RefPtr<DOMError> error = new DOMError(window, NS_ERROR_DOM_TYPE_ERR);
JS::Rooted<JS::Value> errorValue(aCx);
if (ToJSValue(aCx, error, &errorValue)) {
MutexAutoUnlock unlock(mMutex);
JS::ReadableStreamError(aCx, aStream, errorValue);
}
ReleaseObjects(aProofOfLock);
}
NS_IMETHODIMP
FetchStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
{
AssertIsOnOwningThread();
MOZ_DIAGNOSTIC_ASSERT(aStream);
Maybe<MutexAutoLock> lock;
lock.emplace(mMutex);
// Already closed. We have nothing else to do here.
if (mState == eClosed) {
return NS_OK;
}
nsAutoMicroTask mt;
AutoEntryScript aes(mGlobal, "fetch body data available");
MOZ_DIAGNOSTIC_ASSERT(mInputStream);
MOZ_DIAGNOSTIC_ASSERT(mState == eReading || mState == eChecking);
JSContext* cx = aes.cx();
JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
uint64_t size = 0;
nsresult rv = mInputStream->Available(&size);
if (NS_SUCCEEDED(rv) && size == 0) {
// In theory this should not happen. If size is 0, the stream should be
// considered closed.
rv = NS_BASE_STREAM_CLOSED;
}
// No warning for stream closed.
if (rv == NS_BASE_STREAM_CLOSED || NS_WARN_IF(NS_FAILED(rv))) {
ErrorPropagation(cx, *lock, stream, rv);
return NS_OK;
}
// This extra checking is completed. Let's wait for the next read request.
if (mState == eChecking) {
mState = eWaiting;
return NS_OK;
}
mState = eWriting;
lock.reset();
JS::ReadableStreamUpdateDataAvailableFromSource(cx, stream, size);
return NS_OK;
}
/* static */ nsresult
FetchStream::RetrieveInputStream(void* aUnderlyingReadableStreamSource,
nsIInputStream** aInputStream)
{
MOZ_ASSERT(aUnderlyingReadableStreamSource);
MOZ_ASSERT(aInputStream);
RefPtr<FetchStream> stream =
static_cast<FetchStream*>(aUnderlyingReadableStreamSource);
stream->AssertIsOnOwningThread();
// if mOriginalInputStream is null, the reading already started. We don't want
// to expose the internal inputStream.
if (NS_WARN_IF(!stream->mOriginalInputStream)) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
nsCOMPtr<nsIInputStream> inputStream = stream->mOriginalInputStream;
inputStream.forget(aInputStream);
return NS_OK;
}
void
FetchStream::Close()
{
AssertIsOnOwningThread();
MutexAutoLock lock(mMutex);
if (mState == eClosed) {
return;
}
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
ReleaseObjects(lock);
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
CloseAndReleaseObjects(cx, lock, stream);
}
void
FetchStream::CloseAndReleaseObjects(JSContext* aCx,
const MutexAutoLock& aProofOfLock,
JS::HandleObject aStream)
{
AssertIsOnOwningThread();
MOZ_DIAGNOSTIC_ASSERT(mState != eClosed);
ReleaseObjects(aProofOfLock);
MutexAutoUnlock unlock(mMutex);
if (JS::ReadableStreamIsReadable(aStream)) {
JS::ReadableStreamClose(aCx, aStream);
}
}
void
FetchStream::ReleaseObjects()
{
MutexAutoLock lock(mMutex);
ReleaseObjects(lock);
}
void
FetchStream::ReleaseObjects(const MutexAutoLock& aProofOfLock)
{
// This method can be called on 2 possible threads: the owning one and a JS
// thread used to release resources. If we are on the JS thread, we need to
// dispatch a runnable to go back to the owning thread in order to release
// resources correctly.
if (mState == eClosed) {
// Already gone. Nothing to do.
return;
}
mState = eClosed;
if (mWorkerHolder) {
RefPtr<FetchStreamWorkerHolderShutdown> r =
new FetchStreamWorkerHolderShutdown(
static_cast<FetchStreamWorkerHolder*>(mWorkerHolder.get())->GetWorkerPrivate(),
Move(mWorkerHolder), Move(mGlobal), Move(mStreamHolder));
r->Dispatch();
} else {
RefPtr<FetchStream> self = this;
RefPtr<Runnable> r = NS_NewRunnableFunction(
[self] () {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->RemoveObserver(self, DOM_WINDOW_DESTROYED_TOPIC);
}
self->mGlobal = nullptr;
self->mStreamHolder->NullifyStream();
self->mStreamHolder = nullptr;
});
NS_DispatchToMainThread(r);
}
}
#ifdef DEBUG
void
FetchStream::AssertIsOnOwningThread()
{
NS_ASSERT_OWNINGTHREAD(FetchStream);
}
#endif
// nsIObserver
// -----------
NS_IMETHODIMP
FetchStream::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
AssertIsOnMainThread();
AssertIsOnOwningThread();
MOZ_ASSERT(strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0);
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
if (SameCOMIdentity(aSubject, window)) {
Close();
}
return NS_OK;
}
} // dom namespace
} // mozilla namespace

158
dom/fetch/FetchStream.h Normal file
View file

@ -0,0 +1,158 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 mozilla_dom_FetchStream_h
#define mozilla_dom_FetchStream_h
#include "Fetch.h"
#include "jsapi.h"
#include "nsIAsyncInputStream.h"
#include "nsIObserver.h"
#include "nsISupportsImpl.h"
#include "nsWeakReference.h"
class nsIGlobalObject;
class nsIInputStream;
namespace mozilla {
namespace dom {
namespace workers {
class WorkerHolder;
}
class FetchStreamHolder;
class FetchStream final : public nsIInputStreamCallback
, public nsIObserver
, public nsSupportsWeakReference
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIINPUTSTREAMCALLBACK
NS_DECL_NSIOBSERVER
static void
Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv);
void
Close();
static nsresult
RetrieveInputStream(void* aUnderlyingReadableStreamSource,
nsIInputStream** aInputStream);
private:
FetchStream(nsIGlobalObject* aGlobal,
FetchStreamHolder* aStreamHolder,
nsIInputStream* aInputStream);
~FetchStream();
#ifdef DEBUG
void
AssertIsOnOwningThread();
#else
void
AssertIsOnOwningThread() {}
#endif
static void
RequestDataCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags,
size_t aDesiredSize);
static void
WriteIntoReadRequestCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags,
void* aBuffer, size_t aLength,
size_t* aByteWritten);
static JS::Value
CancelCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags,
JS::HandleValue aReason);
static void
ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags);
static void
ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
void* aUnderlyingSource, uint8_t aFlags,
JS::HandleValue reason);
static void
FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags);
void
ErrorPropagation(JSContext* aCx,
const MutexAutoLock& aProofOfLock,
JS::HandleObject aStream, nsresult aRv);
void
CloseAndReleaseObjects(JSContext* aCx,
const MutexAutoLock& aProofOfLock,
JS::HandleObject aSteam);
class WorkerShutdown;
void
ReleaseObjects(const MutexAutoLock& aProofOfLock);
void
ReleaseObjects();
// Common methods
enum State {
// This is the beginning state before any reading operation.
eInitializing,
// RequestDataCallback has not been called yet. We haven't started to read
// data from the stream yet.
eWaiting,
// We are reading data in a separate I/O thread.
eReading,
// We are ready to write something in the JS Buffer.
eWriting,
// After a writing, we want to check if the stream is closed. After the
// check, we go back to eWaiting. If a reading request happens in the
// meantime, we move to eReading state.
eChecking,
// Operation completed.
eClosed,
};
// We need a mutex because JS engine can release FetchStream on a non-owning
// thread. We must be sure that the releasing of resources doesn't trigger
// race conditions.
Mutex mMutex;
// Protected by mutex.
State mState;
nsCOMPtr<nsIGlobalObject> mGlobal;
RefPtr<FetchStreamHolder> mStreamHolder;
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
// This is the original inputStream received during the CTOR. It will be
// converted into an nsIAsyncInputStream and stored into mInputStream at the
// first use.
nsCOMPtr<nsIInputStream> mOriginalInputStream;
nsCOMPtr<nsIAsyncInputStream> mInputStream;
UniquePtr<workers::WorkerHolder> mWorkerHolder;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_FetchStream_h

View file

@ -0,0 +1,405 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "FetchStreamReader.h"
#include "InternalResponse.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/DOMException.h"
#include "nsContentUtils.h"
#include "nsIScriptError.h"
#include "nsPIDOMWindow.h"
namespace mozilla {
namespace dom {
using namespace workers;
namespace {
class FetchStreamReaderWorkerHolder final : public WorkerHolder
{
public:
explicit FetchStreamReaderWorkerHolder(FetchStreamReader* aReader)
: WorkerHolder(WorkerHolder::Behavior::AllowIdleShutdownStart)
, mReader(aReader)
, mWasNotified(false)
{}
bool Notify(Status aStatus) override
{
if (!mWasNotified) {
mWasNotified = true;
// The WorkerPrivate does have a context available, and we could pass it
// here to trigger cancellation of the reader, but the author of this
// comment chickened out.
mReader->CloseAndRelease(nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
}
return true;
}
private:
RefPtr<FetchStreamReader> mReader;
bool mWasNotified;
};
} // anonymous
NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchStreamReader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchStreamReader)
NS_IMPL_CYCLE_COLLECTION_CLASS(FetchStreamReader)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FetchStreamReader)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FetchStreamReader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FetchStreamReader)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReader)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchStreamReader)
NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStreamCallback)
NS_INTERFACE_MAP_END
/* static */ nsresult
FetchStreamReader::Create(JSContext* aCx, nsIGlobalObject* aGlobal,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream)
{
MOZ_ASSERT(aCx);
MOZ_ASSERT(aGlobal);
MOZ_ASSERT(aStreamReader);
MOZ_ASSERT(aInputStream);
RefPtr<FetchStreamReader> streamReader = new FetchStreamReader(aGlobal);
nsCOMPtr<nsIAsyncInputStream> pipeIn;
nsresult rv = NS_NewPipe2(getter_AddRefs(pipeIn),
getter_AddRefs(streamReader->mPipeOut),
true, true, 0, 0);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
MOZ_ASSERT(workerPrivate);
// We need to know when the worker goes away.
UniquePtr<FetchStreamReaderWorkerHolder> holder(
new FetchStreamReaderWorkerHolder(streamReader));
if (NS_WARN_IF(!holder->HoldWorker(workerPrivate, Closing))) {
streamReader->mPipeOut->CloseWithStatus(NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
// These 2 objects create a ref-cycle here that is broken when the stream is
// closed or the worker shutsdown.
streamReader->mWorkerHolder = Move(holder);
}
pipeIn.forget(aInputStream);
streamReader.forget(aStreamReader);
return NS_OK;
}
FetchStreamReader::FetchStreamReader(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal)
// TODO: Replace with mGlobal->EventTargetFor(TaskCategory::Other)
// When we have the Dispatcher API in the tree, see Issue #1442
, mOwningEventTarget(NS_GetCurrentThread())
, mBufferRemaining(0)
, mBufferOffset(0)
, mStreamClosed(false)
{
MOZ_ASSERT(aGlobal);
}
FetchStreamReader::~FetchStreamReader()
{
CloseAndRelease(nullptr, NS_BASE_STREAM_CLOSED);
}
// If a context is provided, an attempt will be made to cancel the reader. The
// only situation where we don't expect to have a context is when closure is
// being triggered from the destructor or the WorkerHolder is notifying. If
// we're at the destructor, it's far too late to cancel anything. And if the
// WorkerHolder is being notified, the global is going away, so there's also
// no need to do further JS work.
void
FetchStreamReader::CloseAndRelease(JSContext* aCx, nsresult aStatus)
{
NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
if (mStreamClosed) {
// Already closed.
return;
}
RefPtr<FetchStreamReader> kungFuDeathGrip = this;
if (aCx) {
MOZ_ASSERT(mReader);
RefPtr<DOMException> error = DOMException::Create(aStatus);
JS::Rooted<JS::Value> errorValue(aCx);
if (ToJSValue(aCx, error, &errorValue)) {
JS::Rooted<JSObject*> reader(aCx, mReader);
// It's currently safe to cancel an already closed reader because, per the
// comments in ReadableStream::cancel() conveying the spec, step 2 of
// 3.4.3 that specified ReadableStreamCancel is: If stream.[[state]] is
// "closed", return a new promise resolved with undefined.
JS::ReadableStreamReaderCancel(aCx, reader, errorValue);
}
}
mStreamClosed = true;
mGlobal = nullptr;
mPipeOut->CloseWithStatus(aStatus);
mPipeOut = nullptr;
mWorkerHolder = nullptr;
mReader = nullptr;
mBuffer = nullptr;
}
void
FetchStreamReader::StartConsuming(JSContext* aCx,
JS::HandleObject aStream,
JS::MutableHandle<JSObject*> aReader,
ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(!mReader);
MOZ_DIAGNOSTIC_ASSERT(aStream);
JS::Rooted<JSObject*> reader(aCx,
JS::ReadableStreamGetReader(aCx, aStream,
JS::ReadableStreamReaderMode::Default));
if (!reader) {
aRv.StealExceptionFromJSContext(aCx);
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mReader = reader;
aReader.set(reader);
aRv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
// nsIOutputStreamCallback interface
NS_IMETHODIMP
FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
{
NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
MOZ_ASSERT(aStream == mPipeOut);
MOZ_ASSERT(mReader);
if (mStreamClosed) {
return NS_OK;
}
if (mBuffer) {
return WriteBuffer();
}
// TODO: We need to verify this is the correct global per the spec.
// See bug 1385890.
AutoEntryScript aes(mGlobal, "ReadableStreamReader.read", !mWorkerHolder);
JS::Rooted<JSObject*> reader(aes.cx(), mReader);
JS::Rooted<JSObject*> promise(aes.cx(),
JS::ReadableStreamDefaultReaderRead(aes.cx(),
reader));
if (NS_WARN_IF(!promise)) {
// Let's close the stream.
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_ERROR_FAILURE;
}
RefPtr<Promise> domPromise = Promise::CreateFromExisting(mGlobal, promise);
if (NS_WARN_IF(!domPromise)) {
// Let's close the stream.
CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_ERROR_FAILURE;
}
// Let's wait.
domPromise->AppendNativeHandler(this);
return NS_OK;
}
void
FetchStreamReader::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
if (mStreamClosed) {
return;
}
// This promise should be resolved with { done: boolean, value: something },
// "value" is interesting only if done is false.
// We don't want to play with JS api, let's WebIDL bindings doing it for us.
// FetchReadableStreamReadDataDone is a dictionary with just a boolean, if the
// parsing succeeded, we can proceed with the parsing of the "value", which it
// must be a Uint8Array.
FetchReadableStreamReadDataDone valueDone;
if (!valueDone.Init(aCx, aValue)) {
JS_ClearPendingException(aCx);
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (valueDone.mDone) {
// Stream is completed.
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
UniquePtr<FetchReadableStreamReadDataArray> value(
new FetchReadableStreamReadDataArray);
if (!value->Init(aCx, aValue) || !value->mValue.WasPassed()) {
JS_ClearPendingException(aCx);
CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
Uint8Array& array = value->mValue.Value();
array.ComputeLengthAndData();
uint32_t len = array.Length();
if (len == 0) {
// If there is nothing to read, let's do another reading.
OnOutputStreamReady(mPipeOut);
return;
}
MOZ_DIAGNOSTIC_ASSERT(!mBuffer);
mBuffer = Move(value);
mBufferOffset = 0;
mBufferRemaining = len;
nsresult rv = WriteBuffer();
if (NS_FAILED(rv)) {
// DOMException only understands errors from domerr.msg, so we normalize to
// identifying an abort if the write fails.
CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
}
}
nsresult
FetchStreamReader::WriteBuffer()
{
MOZ_ASSERT(mBuffer);
MOZ_ASSERT(mBuffer->mValue.WasPassed());
Uint8Array& array = mBuffer->mValue.Value();
char* data = reinterpret_cast<char*>(array.Data());
while (1) {
uint32_t written = 0;
nsresult rv =
mPipeOut->Write(data + mBufferOffset, mBufferRemaining, &written);
if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
break;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(written <= mBufferRemaining);
mBufferRemaining -= written;
mBufferOffset += written;
if (mBufferRemaining == 0) {
mBuffer = nullptr;
break;
}
}
nsresult rv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void
FetchStreamReader::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
ReportErrorToConsole(aCx, aValue);
CloseAndRelease(aCx, NS_ERROR_FAILURE);
}
void
FetchStreamReader::ReportErrorToConsole(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
nsCString sourceSpec;
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line,
&column, valueString);
nsTArray<nsString> params;
params.AppendElement(valueString);
RefPtr<ConsoleReportCollector> reporter = new ConsoleReportCollector();
reporter->AddConsoleReport(nsIScriptError::errorFlag,
NS_LITERAL_CSTRING("ReadableStreamReader.read"),
nsContentUtils::eDOM_PROPERTIES,
sourceSpec, line, column,
NS_LITERAL_CSTRING("ReadableStreamReadingFailed"),
params);
uint64_t innerWindowId = 0;
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
if (window) {
innerWindowId = window->WindowID();
}
reporter->FlushReportsByWindowId(innerWindowId);
return;
}
WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
if (workerPrivate) {
innerWindowId = workerPrivate->WindowID();
}
RefPtr<Runnable> r = NS_NewRunnableFunction(
[reporter, innerWindowId] () {
reporter->FlushReportsByWindowId(innerWindowId);
});
workerPrivate->DispatchToMainThread(r.forget());
}
} // dom namespace
} // mozilla namespace

View file

@ -0,0 +1,85 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_FetchStreamReader_h
#define mozilla_dom_FetchStreamReader_h
#include "jsapi.h"
#include "mozilla/dom/FetchBinding.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "nsIAsyncOutputStream.h"
namespace mozilla {
namespace dom {
namespace workers {
class WorkerHolder;
}
class FetchStreamReader final : public nsIOutputStreamCallback
, public PromiseNativeHandler
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FetchStreamReader, nsIOutputStreamCallback)
NS_DECL_NSIOUTPUTSTREAMCALLBACK
// This creates a nsIInputStream able to retrieve data from the ReadableStream
// object. The reading starts when StartConsuming() is called.
static nsresult
Create(JSContext* aCx, nsIGlobalObject* aGlobal,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream);
void
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
void
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
// Idempotently close the output stream and null out all state. If aCx is
// provided, the reader will also be canceled. aStatus must be a DOM error
// as understood by DOMException because it will be provided as the
// cancellation reason.
void
CloseAndRelease(JSContext* aCx, nsresult aStatus);
void
StartConsuming(JSContext* aCx,
JS::HandleObject aStream,
JS::MutableHandle<JSObject*> aReader,
ErrorResult& aRv);
private:
explicit FetchStreamReader(nsIGlobalObject* aGlobal);
~FetchStreamReader();
nsresult
WriteBuffer();
void
ReportErrorToConsole(JSContext* aCx, JS::Handle<JS::Value> aValue);
nsCOMPtr<nsIGlobalObject> mGlobal;
nsCOMPtr<nsIEventTarget> mOwningEventTarget;
nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
UniquePtr<workers::WorkerHolder> mWorkerHolder;
JS::Heap<JSObject*> mReader;
UniquePtr<FetchReadableStreamReadDataArray> mBuffer;
uint32_t mBufferRemaining;
uint32_t mBufferOffset;
bool mStreamClosed;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_FetchStreamReader_h

View file

@ -139,18 +139,18 @@ InternalResponse::ToIPC(IPCInternalResponse* aIPCResponse,
}
already_AddRefed<InternalResponse>
InternalResponse::Clone()
InternalResponse::Clone(CloneType aCloneType)
{
RefPtr<InternalResponse> clone = CreateIncompleteCopy();
clone->mHeaders = new InternalHeaders(*mHeaders);
if (mWrappedResponse) {
clone->mWrappedResponse = mWrappedResponse->Clone();
clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType);
MOZ_ASSERT(!mBody);
return clone.forget();
}
if (!mBody) {
if (!mBody || aCloneType == eDontCloneInputStream) {
return clone.forget();
}

View file

@ -46,7 +46,13 @@ public:
M* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
already_AddRefed<InternalResponse> Clone();
enum CloneType
{
eCloneInputStream,
eDontCloneInputStream,
};
already_AddRefed<InternalResponse> Clone(CloneType eCloneType);
static already_AddRefed<InternalResponse>
NetworkError()

View file

@ -29,7 +29,25 @@ using namespace workers;
NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner, mHeaders)
NS_IMPL_CYCLE_COLLECTION_CLASS(Request)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Request)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Request)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Request)
MOZ_DIAGNOSTIC_ASSERT(!tmp->mReadableStreamReader);
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@ -37,8 +55,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
NS_INTERFACE_MAP_END
Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest, AbortSignal* aSignal)
: FetchBody<Request>()
, mOwner(aOwner)
: FetchBody<Request>(aOwner)
, mRequest(aRequest)
, mSignal(aSignal)
{
@ -557,17 +574,15 @@ Request::Constructor(const GlobalObject& aGlobal,
}
if (aInit.mBody.WasPassed()) {
const Nullable<OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& bodyInitNullable =
aInit.mBody.Value();
const Nullable<fetch::OwningBodyInit>& bodyInitNullable = aInit.mBody.Value();
if (!bodyInitNullable.IsNull()) {
const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit =
bodyInitNullable.Value();
const fetch::OwningBodyInit& bodyInit = bodyInitNullable.Value();
nsCOMPtr<nsIInputStream> stream;
nsAutoCString contentType;
nsAutoCString contentTypeWithCharset;
uint64_t contentLengthUnused;
aRv = ExtractByteStreamFromBody(bodyInit,
getter_AddRefs(stream),
contentType,
contentTypeWithCharset,
contentLengthUnused);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
@ -575,10 +590,10 @@ Request::Constructor(const GlobalObject& aGlobal,
temporaryBody = stream;
if (!contentType.IsVoid() &&
if (!contentTypeWithCharset.IsVoid() &&
!requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
requestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"),
contentType, aRv);
contentTypeWithCharset, aRv);
}
if (NS_WARN_IF(aRv.Failed())) {
@ -599,7 +614,10 @@ Request::Constructor(const GlobalObject& aGlobal,
inputReq->GetBody(getter_AddRefs(body));
if (body) {
inputReq->SetBody(nullptr);
inputReq->SetBodyUsed();
inputReq->SetBodyUsed(aGlobal.Context(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
}
}
return domRequest.forget();

View file

@ -128,6 +128,8 @@ public:
void
GetBody(nsIInputStream** aStream) { return mRequest->GetBody(aStream); }
using FetchBody::GetBody;
void
SetBody(nsIInputStream* aStream) { return mRequest->SetBody(aStream); }
@ -162,7 +164,6 @@ public:
private:
~Request();
nsCOMPtr<nsIGlobalObject> mOwner;
RefPtr<InternalRequest> mRequest;
// Lazily created.

View file

@ -12,12 +12,16 @@
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/FetchBinding.h"
#include "mozilla/dom/ResponseBinding.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/URL.h"
#include "nsDOMString.h"
#include "BodyExtractor.h"
#include "FetchStream.h"
#include "FetchStreamReader.h"
#include "InternalResponse.h"
#include "WorkerPrivate.h"
@ -26,7 +30,31 @@ namespace dom {
NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner, mHeaders)
NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Response)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
tmp->mReadableStreamBody = nullptr;
tmp->mReadableStreamReader = nullptr;
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Response)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Response)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@ -34,18 +62,21 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
NS_INTERFACE_MAP_END
Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse, AbortSignal* aSignal)
: FetchBody<Response>()
, mOwner(aGlobal)
: FetchBody<Response>(aGlobal)
, mInternalResponse(aInternalResponse)
, mSignal(aSignal)
{
MOZ_ASSERT(aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
SetMimeType();
// Keep a firm grip!
mozilla::HoldJSObjects(this);
}
Response::~Response()
{
mozilla::DropJSObjects(this);
}
/* static */ already_AddRefed<Response>
@ -106,7 +137,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
return nullptr;
}
Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>> body;
Optional<fetch::ResponseBodyInit> body;
ResponseInit init;
init.mStatus = aStatus;
RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
@ -127,7 +158,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
/*static*/ already_AddRefed<Response>
Response::Constructor(const GlobalObject& aGlobal,
const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody,
const Optional<fetch::ResponseBodyInit>& aBody,
const ResponseInit& aInit, ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
@ -193,29 +224,86 @@ Response::Constructor(const GlobalObject& aGlobal,
}
}
if (aBody.WasPassed() && !aBody.Value().IsNull()) {
if (aBody.WasPassed()) {
if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>();
return nullptr;
}
nsCString contentTypeWithCharset;
nsCOMPtr<nsIInputStream> bodyStream;
nsCString contentType;
uint64_t bodySize = 0;
aRv = ExtractByteStreamFromBody(aBody.Value().Value(),
getter_AddRefs(bodyStream),
contentType,
bodySize);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
if (aBody.Value().IsReadableStream()) {
const ReadableStream& readableStream =
aBody.Value().GetAsReadableStream();
JS::Rooted<JSObject*> readableStreamObj(aGlobal.Context(),
readableStream.Obj());
if (JS::ReadableStreamIsDisturbed(readableStreamObj) ||
JS::ReadableStreamIsLocked(readableStreamObj) ||
!JS::ReadableStreamIsReadable(readableStreamObj)) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
r->SetReadableStreamBody(readableStreamObj);
if (JS::ReadableStreamGetMode(readableStreamObj) ==
JS::ReadableStreamMode::ExternalSource) {
// If this is a DOM generated ReadableStream, we can extract the
// inputStream directly.
void* underlyingSource = nullptr;
if (!JS::ReadableStreamGetExternalUnderlyingSource(aGlobal.Context(),
readableStreamObj,
&underlyingSource)) {
aRv.StealExceptionFromJSContext(aGlobal.Context());
return nullptr;
}
MOZ_ASSERT(underlyingSource);
aRv = FetchStream::RetrieveInputStream(underlyingSource,
getter_AddRefs(bodyStream));
// The releasing of the external source is needed in order to avoid an
// extra stream lock.
JS::ReadableStreamReleaseExternalUnderlyingSource(readableStreamObj);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
} else {
// If this is a JS-created ReadableStream, let's create a
// FetchStreamReader.
aRv = FetchStreamReader::Create(aGlobal.Context(), global,
getter_AddRefs(r->mFetchStreamReader),
getter_AddRefs(bodyStream));
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
}
} else {
uint64_t size = 0;
aRv = ExtractByteStreamFromBody(aBody.Value(),
getter_AddRefs(bodyStream),
contentTypeWithCharset,
size);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
bodySize = size;
}
internalResponse->SetBody(bodyStream, bodySize);
if (!contentType.IsVoid() &&
if (!contentTypeWithCharset.IsVoid() &&
!internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
// Ignore Append() failing here.
ErrorResult error;
internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, error);
internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"),
contentTypeWithCharset, error);
error.SuppressException();
}
@ -229,29 +317,87 @@ Response::Constructor(const GlobalObject& aGlobal,
}
already_AddRefed<Response>
Response::Clone(ErrorResult& aRv) const
Response::Clone(JSContext* aCx, ErrorResult& aRv)
{
if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
RefPtr<InternalResponse> ir = mInternalResponse->Clone();
RefPtr<Response> response = new Response(mOwner, ir, mSignal);
RefPtr<FetchStreamReader> streamReader;
nsCOMPtr<nsIInputStream> inputStream;
JS::Rooted<JSObject*> body(aCx);
MaybeTeeReadableStreamBody(aCx, &body,
getter_AddRefs(streamReader),
getter_AddRefs(inputStream), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
MOZ_ASSERT_IF(body, streamReader);
MOZ_ASSERT_IF(body, inputStream);
RefPtr<InternalResponse> ir =
mInternalResponse->Clone(body
? InternalResponse::eDontCloneInputStream
: InternalResponse::eCloneInputStream);
RefPtr<Response> response = new Response(mOwner, ir, nullptr);
if (body) {
// Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
// if this body is a native stream. In this case, the InternalResponse will
// have a clone of the native body and the ReadableStream will be created
// lazily if needed.
response->SetReadableStreamBody(body);
response->mFetchStreamReader = streamReader;
ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
}
return response.forget();
}
already_AddRefed<Response>
Response::CloneUnfiltered(ErrorResult& aRv) const
Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv)
{
if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
RefPtr<InternalResponse> clone = mInternalResponse->Clone();
RefPtr<FetchStreamReader> streamReader;
nsCOMPtr<nsIInputStream> inputStream;
JS::Rooted<JSObject*> body(aCx);
MaybeTeeReadableStreamBody(aCx, &body,
getter_AddRefs(streamReader),
getter_AddRefs(inputStream), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
MOZ_ASSERT_IF(body, streamReader);
MOZ_ASSERT_IF(body, inputStream);
RefPtr<InternalResponse> clone =
mInternalResponse->Clone(body
? InternalResponse::eDontCloneInputStream
: InternalResponse::eCloneInputStream);
RefPtr<InternalResponse> ir = clone->Unfiltered();
RefPtr<Response> ref = new Response(mOwner, ir, mSignal);
RefPtr<Response> ref = new Response(mOwner, ir, nullptr);
if (body) {
// Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
// if this body is a native stream. In this case, the InternalResponse will
// have a clone of the native body and the ReadableStream will be created
// lazily if needed.
ref->SetReadableStreamBody(body);
ref->mFetchStreamReader = streamReader;
ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
}
return ref.forget();
}

View file

@ -105,6 +105,8 @@ public:
void
GetBody(nsIInputStream** aStream) { return mInternalResponse->GetBody(aStream); }
using FetchBody::GetBody;
static already_AddRefed<Response>
Error(const GlobalObject& aGlobal);
@ -113,7 +115,7 @@ public:
static already_AddRefed<Response>
Constructor(const GlobalObject& aGlobal,
const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody,
const Optional<fetch::ResponseBodyInit>& aBody,
const ResponseInit& aInit, ErrorResult& rv);
nsIGlobalObject* GetParentObject() const
@ -122,10 +124,10 @@ public:
}
already_AddRefed<Response>
Clone(ErrorResult& aRv) const;
Clone(JSContext* aCx, ErrorResult& aRv);
already_AddRefed<Response>
CloneUnfiltered(ErrorResult& aRv) const;
CloneUnfiltered(JSContext* aCx, ErrorResult& aRv);
void
SetBody(nsIInputStream* aBody, int64_t aBodySize);
@ -142,7 +144,6 @@ public:
private:
~Response();
nsCOMPtr<nsIGlobalObject> mOwner;
RefPtr<InternalResponse> mInternalResponse;
// Lazily created

View file

@ -4,11 +4,13 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom += [
'BodyExtractor.h',
'ChannelInfo.h',
'Fetch.h',
'FetchDriver.h',
'FetchIPCTypes.h',
'FetchObserver.h',
'FetchStreamReader.h',
'FetchUtil.h',
'Headers.h',
'InternalHeaders.h',
@ -19,11 +21,14 @@ EXPORTS.mozilla.dom += [
]
UNIFIED_SOURCES += [
'BodyExtractor.cpp',
'ChannelInfo.cpp',
'Fetch.cpp',
'FetchConsumer.cpp',
'FetchDriver.cpp',
'FetchObserver.cpp',
'FetchStream.cpp',
'FetchStreamReader.cpp',
'FetchUtil.cpp',
'Headers.cpp',
'InternalHeaders.cpp',

View file

@ -310,7 +310,7 @@ TCPSocketParent::RecvData(const SendableData& aData,
const nsTArray<uint8_t>& buffer = aData.get_ArrayOfuint8_t();
bool ok = IPC::DeserializeArrayBuffer(autoCx, buffer, &val);
NS_ENSURE_TRUE(ok, true);
RootedTypedArray<ArrayBuffer> data(autoCx);
RootedSpiderMonkeyInterface<ArrayBuffer> data(autoCx);
data.Init(&val.toObject());
Optional<uint32_t> byteLength(buffer.Length());
mSocket->SendWithTrackingNumber(autoCx, data, 0, byteLength, aTrackingNumber, rv);

View file

@ -575,9 +575,10 @@ URLSearchParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
NS_IMETHODIMP
URLSearchParams::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset)
nsACString& aContentTypeWithCharset,
nsACString& aCharset)
{
aContentType.AssignLiteral("application/x-www-form-urlencoded");
aContentTypeWithCharset.AssignLiteral("application/x-www-form-urlencoded;charset=UTF-8");
aCharset.AssignLiteral("UTF-8");
nsAutoString serialized;

View file

@ -8,7 +8,7 @@
*/
typedef object JSON;
typedef (ArrayBuffer or ArrayBufferView or Blob or FormData or USVString or URLSearchParams) BodyInit;
typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) BodyInit;
[NoInterfaceObject, Exposed=(Window,Worker)]
interface Body {
@ -24,3 +24,15 @@ interface Body {
[Throws]
Promise<USVString> text();
};
// These are helper dictionaries for the parsing of a
// getReader().read().then(data) parsing.
// See more about how these 2 helpers are used in
// dom/fetch/FetchStreamReader.cpp
dictionary FetchReadableStreamReadDataDone {
boolean done = false;
};
dictionary FetchReadableStreamReadDataArray {
Uint8Array value;
};

View file

@ -294,7 +294,7 @@ partial interface Navigator {
partial interface Navigator {
[Throws, Pref="beacon.enabled"]
boolean sendBeacon(DOMString url,
optional (ArrayBufferView or Blob or DOMString or FormData)? data = null);
optional BodyInit? data = null);
};
partial interface Navigator {

View file

@ -7,7 +7,9 @@
* https://fetch.spec.whatwg.org/#response-class
*/
[Constructor(optional BodyInit? body, optional ResponseInit init),
// This should be Constructor(optional BodyInit... but BodyInit doesn't include
// ReadableStream yet because we don't want to expose the Streams API to Request.
[Constructor(optional (Blob or BufferSource or FormData or URLSearchParams or ReadableStream or USVString) body, optional ResponseInit init),
Exposed=(Window,Worker)]
interface Response {
[NewObject] static Response error();
@ -30,6 +32,12 @@ interface Response {
};
Response implements Body;
// This should be part of Body but we don't want to expose body to request yet.
partial interface Response {
[GetterThrows, Func="nsContentUtils::StreamsEnabled"]
readonly attribute ReadableStream? body;
};
dictionary ResponseInit {
unsigned short status = 200;
ByteString statusText = "OK";

View file

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

View file

@ -701,9 +701,14 @@ private:
request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
loadInfo.mFullURL.Length());
// This JSContext will not end up executing JS code because here there are
// no ReadableStreams involved.
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult error;
RefPtr<Promise> cachePromise =
mCacheCreator->Cache_()->Put(request, *response, error);
mCacheCreator->Cache_()->Put(jsapi.cx(), request, *response, error);
if (NS_WARN_IF(error.Failed())) {
nsresult rv = error.StealNSResult();
channel->Cancel(rv);
@ -1668,8 +1673,13 @@ CacheScriptLoader::Load(Cache* aCache)
mozilla::dom::CacheQueryOptions params;
// This JSContext will not end up executing JS code because here there are
// no ReadableStreams involved.
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult error;
RefPtr<Promise> promise = aCache->Match(request, params, error);
RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
if (NS_WARN_IF(error.Failed())) {
Fail(error.StealNSResult());
return;

View file

@ -6,6 +6,7 @@
#include "ServiceWorkerEvents.h"
#include "nsAutoPtr.h"
#include "nsContentUtils.h"
#include "nsIConsoleReportCollector.h"
#include "nsIHttpChannelInternal.h"
#include "nsINetworkInterceptController.h"
@ -30,8 +31,6 @@
#include "mozilla/Move.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/BodyUtil.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/FetchEventBinding.h"
#include "mozilla/dom/MessagePort.h"
@ -404,76 +403,6 @@ void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event));
}
namespace {
void
ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
nsACString& aSourceSpecOut, uint32_t *aLineOut,
uint32_t *aColumnOut, nsString& aMessageOut)
{
MOZ_ASSERT(aLineOut);
MOZ_ASSERT(aColumnOut);
if (aValue.isObject()) {
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
RefPtr<DOMException> domException;
// Try to process as an Error object. Use the file/line/column values
// from the Error as they will be more specific to the root cause of
// the problem.
JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
if (err) {
// Use xpc to extract the error message only. We don't actually send
// this report anywhere.
RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
report->Init(err,
"<unknown>", // toString result
false, // chrome
0); // window ID
if (!report->mFileName.IsEmpty()) {
CopyUTF16toUTF8(report->mFileName, aSourceSpecOut);
*aLineOut = report->mLineNumber;
*aColumnOut = report->mColumn;
}
aMessageOut.Assign(report->mErrorMsg);
}
// Next, try to unwrap the rejection value as a DOMException.
else if(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, obj, domException))) {
nsAutoString filename;
domException->GetFilename(aCx, filename);
if (!filename.IsEmpty()) {
CopyUTF16toUTF8(filename, aSourceSpecOut);
*aLineOut = domException->LineNumber(aCx);
*aColumnOut = domException->ColumnNumber();
}
domException->GetName(aMessageOut);
aMessageOut.AppendLiteral(": ");
nsAutoString message;
domException->GetMessageMoz(message);
aMessageOut.Append(message);
}
}
// If we could not unwrap a specific error type, then perform default safe
// string conversions on primitives. Objects will result in "[Object]"
// unfortunately.
if (aMessageOut.IsEmpty()) {
nsAutoJSString jsString;
if (jsString.init(aCx, aValue)) {
aMessageOut = jsString;
} else {
JS_ClearPendingException(aCx);
}
}
}
} // anonymous namespace
class MOZ_STACK_CLASS AutoCancel
{
RefPtr<RespondWithHandler> mOwner;
@ -505,6 +434,44 @@ public:
}
}
// This function steals the error message from a ErrorResult.
void
SetCancelErrorResult(JSContext* aCx, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aRv.Failed());
MOZ_DIAGNOSTIC_ASSERT(!JS_IsExceptionPending(aCx));
// Storing the error as exception in the JSContext.
if (!aRv.MaybeSetPendingException(aCx)) {
return;
}
MOZ_ASSERT(!aRv.Failed());
// Let's take the pending exception.
JS::Rooted<JS::Value> exn(aCx);
if (!JS_GetPendingException(aCx, &exn)) {
return;
}
JS_ClearPendingException(aCx);
// Converting the exception in a js::ErrorReport.
js::ErrorReport report(aCx);
if (!report.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
JS_ClearPendingException(aCx);
return;
}
MOZ_ASSERT(mOwner);
MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
MOZ_ASSERT(mParams.Length() == 1);
// Let's store the error message here.
mMessageName.Assign(report.toStringResult().c_str());
mParams.Clear();
}
template<typename... Params>
void SetCancelMessage(const nsACString& aMessageName, Params&&... aParams)
{
@ -557,7 +524,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
@ -572,7 +540,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
@ -659,7 +628,12 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
ir->GetUnfilteredBody(getter_AddRefs(body));
// Errors and redirects may not have a body.
if (body) {
response->SetBodyUsed();
IgnoredErrorResult error;
response->SetBodyUsed(aCx, error);
if (NS_WARN_IF(error.Failed())) {
autoCancel.SetCancelErrorResult(aCx, error);
return;
}
nsCOMPtr<nsIOutputStream> responseBody;
rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
@ -715,7 +689,8 @@ RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t column = mRespondWithColumnNumber;
nsString valueString;
ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
valueString);
::AsyncLog(mInterceptedChannel, sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptionRejectedResponseWithURL"),
@ -853,7 +828,8 @@ public:
nsCString spec;
uint32_t line = 0;
uint32_t column = 0;
ExtractErrorValues(aCx, aValue, spec, &line, &column, mRejectValue);
nsContentUtils::ExtractErrorValues(aCx, aValue, spec, &line, &column,
mRejectValue);
// only use the extracted location if we found one
if (!spec.IsEmpty()) {

View file

@ -395,7 +395,7 @@ public:
return;
}
WriteToCache(cache);
WriteToCache(aCx, cache);
return;
}
@ -528,7 +528,7 @@ private:
}
void
WriteToCache(Cache* aCache)
WriteToCache(JSContext* aCx, Cache* aCache)
{
AssertIsOnMainThread();
MOZ_ASSERT(aCache);
@ -561,8 +561,10 @@ private:
// For now we have to wait until the Put Promise is fulfilled before we can
// continue since Cache does not yet support starting a read that is being
// written to.
RefPtr<Promise> cachePromise = aCache->Put(request, *response, result);
RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
if (NS_WARN_IF(result.Failed())) {
// No exception here because there are no ReadableStreams involved here.
MOZ_ASSERT(!result.IsJSException());
MOZ_ASSERT(!result.IsErrorWithMessage());
Fail(result.StealNSResult());
return;
@ -903,7 +905,7 @@ CompareCache::ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
request.SetAsUSVString().Rebind(mURL.Data(), mURL.Length());
ErrorResult error;
CacheQueryOptions params;
RefPtr<Promise> promise = cache->Match(request, params, error);
RefPtr<Promise> promise = cache->Match(aCx, request, params, error);
if (NS_WARN_IF(error.Failed())) {
mManager->CacheFinished(error.StealNSResult(), false);
return;

View file

@ -8,8 +8,9 @@
BEGIN_WORKERS_NAMESPACE
WorkerHolder::WorkerHolder()
WorkerHolder::WorkerHolder(Behavior aBehavior)
: mWorkerPrivate(nullptr)
, mBehavior(aBehavior)
{
}
@ -44,6 +45,12 @@ WorkerHolder::ReleaseWorker()
ReleaseWorkerInternal();
}
WorkerHolder::Behavior
WorkerHolder::GetBehavior() const
{
return mBehavior;
}
void
WorkerHolder::ReleaseWorkerInternal()
{

View file

@ -73,7 +73,12 @@ class WorkerHolder
public:
NS_DECL_OWNINGTHREAD
WorkerHolder();
enum Behavior {
AllowIdleShutdownStart,
PreventIdleShutdownStart,
};
explicit WorkerHolder(Behavior aBehavior = PreventIdleShutdownStart);
virtual ~WorkerHolder();
bool HoldWorker(WorkerPrivate* aWorkerPrivate, Status aFailStatus);
@ -81,6 +86,8 @@ public:
virtual bool Notify(Status aStatus) = 0;
Behavior GetBehavior() const;
protected:
void ReleaseWorkerInternal();
@ -88,6 +95,8 @@ protected:
private:
void AssertIsOwningThread() const;
const Behavior mBehavior;
};
END_WORKERS_NAMESPACE

View file

@ -35,6 +35,7 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.testing.enabled", ServiceWorkersTestingEn
WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, OPEN_WINDOW_ENABLED)
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("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

@ -4154,6 +4154,7 @@ WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
, mDebugger(nullptr)
, mJSContext(nullptr)
, mPRThread(nullptr)
, mNumHoldersPreventingShutdownStart(0)
, mDebuggerEventLoopLevel(0)
, mMainThreadEventTarget(do_GetMainThread())
, mErrorHandlerRecursionCount(0)
@ -5382,8 +5383,11 @@ WorkerPrivate::AddHolder(WorkerHolder* aHolder, Status aFailStatus)
MOZ_ASSERT(!mHolders.Contains(aHolder), "Already know about this one!");
if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(true)) {
return false;
if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) {
if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(true)) {
return false;
}
mNumHoldersPreventingShutdownStart += 1;
}
mHolders.AppendElement(aHolder);
@ -5398,8 +5402,11 @@ WorkerPrivate::RemoveHolder(WorkerHolder* aHolder)
MOZ_ASSERT(mHolders.Contains(aHolder), "Didn't know about this one!");
mHolders.RemoveElement(aHolder);
if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
NS_WARNING("Failed to modify busy count!");
if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) {
mNumHoldersPreventingShutdownStart -= 1;
if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(false)) {
NS_WARNING("Failed to modify busy count!");
}
}
}

View file

@ -980,6 +980,7 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
RefPtr<WorkerDebuggerGlobalScope> mDebuggerScope;
nsTArray<ParentType*> mChildWorkers;
nsTObserverArray<WorkerHolder*> mHolders;
uint32_t mNumHoldersPreventingShutdownStart;
nsTArray<nsAutoPtr<TimeoutInfo>> mTimeouts;
uint32_t mDebuggerEventLoopLevel;
RefPtr<ThrottledEventQueue> mMainThreadThrottledEventQueue;

View file

@ -2298,175 +2298,6 @@ XMLHttpRequestMainThread::ChangeStateToDone()
}
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<nsIDocument>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
NS_ENSURE_STATE(domdoc);
aCharset.AssignLiteral("UTF-8");
nsresult rv;
nsCOMPtr<nsIStorageStream> storStream;
rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> output;
rv = storStream->GetOutputStream(0, getter_AddRefs(output));
NS_ENSURE_SUCCESS(rv, rv);
if (mBody->IsHTMLDocument()) {
aContentType.AssignLiteral("text/html");
nsString serialized;
if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsAutoCString utf8Serialized;
if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
uint32_t written;
rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(written == utf8Serialized.Length());
} else {
aContentType.AssignLiteral("application/xml");
nsCOMPtr<nsIDOMSerializer> serializer =
do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Make sure to use the encoding we'll send
rv = serializer->SerializeToStream(domdoc, output, aCharset);
NS_ENSURE_SUCCESS(rv, rv);
}
output->Close();
uint32_t length;
rv = storStream->GetLength(&length);
NS_ENSURE_SUCCESS(rv, rv);
*aContentLength = length;
rv = storStream->NewInputStream(0, aResult);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<const nsAString>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
aContentType.AssignLiteral("text/plain");
aCharset.AssignLiteral("UTF-8");
nsAutoCString converted;
if (!AppendUTF16toUTF8(*mBody, converted, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aContentLength = converted.Length();
nsresult rv = NS_NewCStringInputStream(aResult, converted);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<nsIInputStream>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
aContentType.AssignLiteral("text/plain");
aCharset.Truncate();
nsresult rv = mBody->Available(aContentLength);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> stream(mBody);
stream.forget(aResult);
return NS_OK;
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<Blob>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<FormData>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<URLSearchParams>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<nsIXHRSendable>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
}
static nsresult
GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength,
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset)
{
aContentType.SetIsVoid(true);
aCharset.Truncate();
*aContentLength = aDataLength;
const char* data = reinterpret_cast<const char*>(aData);
nsCOMPtr<nsIInputStream> stream;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
NS_ASSIGNMENT_COPY);
NS_ENSURE_SUCCESS(rv, rv);
stream.forget(aResult);
return NS_OK;
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<const ArrayBuffer>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
mBody->ComputeLengthAndData();
return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
aResult, aContentLength, aContentType, aCharset);
}
template<> nsresult
XMLHttpRequestMainThread::RequestBody<const ArrayBufferView>::GetAsStream(
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset) const
{
mBody->ComputeLengthAndData();
return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
aResult, aContentLength, aContentType, aCharset);
}
nsresult
XMLHttpRequestMainThread::CreateChannel()
{
@ -2793,7 +2624,7 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
// document?
nsCOMPtr<nsIDocument> doc = do_QueryInterface(supports);
if (doc) {
RequestBody<nsIDocument> body(doc);
BodyExtractor<nsIDocument> body(doc);
return SendInternal(&body);
}
@ -2802,21 +2633,21 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
if (wstr) {
nsAutoString string;
wstr->GetData(string);
RequestBody<const nsAString> body(&string);
BodyExtractor<const nsAString> body(&string);
return SendInternal(&body);
}
// nsIInputStream?
nsCOMPtr<nsIInputStream> stream = do_QueryInterface(supports);
if (stream) {
RequestBody<nsIInputStream> body(stream);
BodyExtractor<nsIInputStream> body(stream);
return SendInternal(&body);
}
// nsIXHRSendable?
nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports);
if (sendable) {
RequestBody<nsIXHRSendable> body(sendable);
BodyExtractor<nsIXHRSendable> body(sendable);
return SendInternal(&body);
}
@ -2827,9 +2658,9 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
nsresult rv = aVariant->GetAsJSVal(&realVal);
if (NS_SUCCEEDED(rv) && !realVal.isPrimitive()) {
JS::Rooted<JSObject*> obj(rootingCx, realVal.toObjectOrNull());
RootedTypedArray<ArrayBuffer> buf(rootingCx);
RootedSpiderMonkeyInterface<ArrayBuffer> buf(rootingCx);
if (buf.Init(obj)) {
RequestBody<const ArrayBuffer> body(&buf);
BodyExtractor<const ArrayBuffer> body(&buf);
return SendInternal(&body);
}
}
@ -2846,12 +2677,12 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
nsString string;
string.Adopt(data, len);
RequestBody<const nsAString> body(&string);
BodyExtractor<const nsAString> body(&string);
return SendInternal(&body);
}
nsresult
XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody)
{
NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
@ -2917,13 +2748,6 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
mAuthorRequestHeaders.Get("content-type", uploadContentType);
if (uploadContentType.IsVoid()) {
uploadContentType = defaultContentType;
if (!charset.IsEmpty()) {
// If we are providing the default content type, then we also need to
// provide a charset declaration.
uploadContentType.Append(NS_LITERAL_CSTRING(";charset="));
uploadContentType.Append(charset);
}
}
// We don't want to set a charset for streams.

View file

@ -34,7 +34,11 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/NotNull.h"
#include "mozilla/dom/MutableBlobStorage.h"
#include "mozilla/dom/BodyExtractor.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/XMLHttpRequest.h"
#include "mozilla/dom/XMLHttpRequestBinding.h"
#include "mozilla/dom/XMLHttpRequestEventTarget.h"
@ -54,11 +58,8 @@ class nsIJSID;
namespace mozilla {
namespace dom {
class Blob;
class BlobSet;
class DOMString;
class FormData;
class URLSearchParams;
class XMLHttpRequestUpload;
struct OriginAttributesDictionary;
@ -288,34 +289,7 @@ public:
private:
virtual ~XMLHttpRequestMainThread();
class RequestBodyBase
{
public:
virtual nsresult GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentType,
nsACString& aCharset) const
{
NS_ASSERTION(false, "RequestBodyBase should not be used directly.");
return NS_ERROR_FAILURE;
}
};
template<typename Type>
class RequestBody final : public RequestBodyBase
{
Type* mBody;
public:
explicit RequestBody(Type* aBody) : mBody(aBody)
{
}
nsresult GetAsStream(nsIInputStream** aResult,
uint64_t* aContentLength,
nsACString& aContentType,
nsACString& aCharset) const override;
};
nsresult SendInternal(const RequestBodyBase* aBody);
nsresult SendInternal(const BodyExtractorBase* aBody);
bool IsCrossSiteCORSRequest() const;
bool IsDeniedCrossSiteCORSRequest();
@ -336,7 +310,7 @@ public:
Send(JSContext* /*aCx*/, const ArrayBuffer& aArrayBuffer,
ErrorResult& aRv) override
{
RequestBody<const ArrayBuffer> body(&aArrayBuffer);
BodyExtractor<const ArrayBuffer> body(&aArrayBuffer);
aRv = SendInternal(&body);
}
@ -344,28 +318,28 @@ public:
Send(JSContext* /*aCx*/, const ArrayBufferView& aArrayBufferView,
ErrorResult& aRv) override
{
RequestBody<const ArrayBufferView> body(&aArrayBufferView);
BodyExtractor<const ArrayBufferView> body(&aArrayBufferView);
aRv = SendInternal(&body);
}
virtual void
Send(JSContext* /*aCx*/, Blob& aBlob, ErrorResult& aRv) override
{
RequestBody<Blob> body(&aBlob);
BodyExtractor<nsIXHRSendable> body(&aBlob);
aRv = SendInternal(&body);
}
virtual void Send(JSContext* /*aCx*/, URLSearchParams& aURLSearchParams,
ErrorResult& aRv) override
{
RequestBody<URLSearchParams> body(&aURLSearchParams);
BodyExtractor<nsIXHRSendable> body(&aURLSearchParams);
aRv = SendInternal(&body);
}
virtual void
Send(JSContext* /*aCx*/, nsIDocument& aDoc, ErrorResult& aRv) override
{
RequestBody<nsIDocument> body(&aDoc);
BodyExtractor<nsIDocument> body(&aDoc);
aRv = SendInternal(&body);
}
@ -375,7 +349,7 @@ public:
if (DOMStringIsNull(aString)) {
Send(aCx, aRv);
} else {
RequestBody<const nsAString> body(&aString);
BodyExtractor<const nsAString> body(&aString);
aRv = SendInternal(&body);
}
}
@ -383,7 +357,7 @@ public:
virtual void
Send(JSContext* /*aCx*/, FormData& aFormData, ErrorResult& aRv) override
{
RequestBody<FormData> body(&aFormData);
BodyExtractor<nsIXHRSendable> body(&aFormData);
aRv = SendInternal(&body);
}
@ -391,7 +365,7 @@ public:
Send(JSContext* aCx, nsIInputStream* aStream, ErrorResult& aRv) override
{
NS_ASSERTION(aStream, "Null should go to string version");
RequestBody<nsIInputStream> body(aStream);
BodyExtractor<nsIInputStream> body(aStream);
aRv = SendInternal(&body);
}

View file

@ -326,9 +326,12 @@ interface nsIXMLHttpRequest : nsISupports
[uuid(840d0d00-e83e-4a29-b3c7-67e96e90a499)]
interface nsIXHRSendable : nsISupports {
// contentTypeWithCharset can be set to the contentType or
// contentType+charset based on what the spec says.
// See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
void getSendInfo(out nsIInputStream body,
out uint64_t contentLength,
out ACString contentType,
out ACString contentTypeWithCharset,
out ACString charset);
};

510
js/public/Stream.h Normal file
View file

@ -0,0 +1,510 @@
/* -*- 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/. */
/*
* JSAPI functions and callbacks related to WHATWG Stream objects.
*
* Much of the API here mirrors the JS API of ReadableStream and associated
* classes, e.g. ReadableStreamDefaultReader, ReadableStreamBYOBReader,
* ReadableStreamDefaultController, ReadableByteStreamController, and
* ReadableStreamBYOBRequest.
*
* There are some crucial differences, though: Functionality that's exposed
* as methods/accessors on controllers in JS is exposed as functions taking
* ReadableStream instances instead. This is because an analysis of how
* the API would be used showed that all functions that'd take controllers
* would do so by first getting the controller from the stream instance it's
* associated with and then call the function taking it. I.e., it would purely
* add boilerplate without any gains in ease of use of the API.
*
* Some functions exposed here deal with ReadableStream instances that have an
* embedding-provided underlying source. These instances are largely similar
* to byte streams as created using |new ReadableStream({type: "bytes"})|:
* They enable users to acquire ReadableStreamBYOBReaders and only vend chunks
* that're typed array instances.
*
* When creating an "external readable stream" using
* JS::NewReadableExternalSourceStreamObject, an underlying source and a set
* of flags can be passed to be stored on the stream. The underlying source is
* treated as an opaque void* pointer by the JS engine: it's purely meant as
* a reference to be used by the embedding to identify whatever actual source
* it uses to supply data for the stream. Similarly, the flags aren't
* interpreted by the JS engine, but are passed to some of the callbacks below
* and can be retrieved using JS::ReadableStreamGetEmbeddingFlags.
*
* External readable streams are optimized to allow the embedding to interact
* with them with a minimum of overhead: chunks aren't enqueued as individual
* typed array instances; instead, the embedding only updates the amount of
* data available using ReadableStreamUpdateDataAvailableFromSource.
* When content requests data by reading from a reader,
* WriteIntoReadRequestBufferCallback is invoked, asking the embedding to
* write data directly into the buffer we're about to hand to content.
*
* Additionally, ReadableStreamGetExternalUnderlyingSource can be used to
* get the void* pointer to the underlying source. This is equivalent to
* acquiring a reader for the stream in that it locks the stream until it
* is released again using JS::ReadableStreamReleaseExternalUnderlyingSource.
*
* Embeddings are expected to detect situations where an API exposed to JS
* takes a ReadableStream to read from that has an external underlying source.
* In those situations, it might be preferable to directly perform data
* transfers from the stream's underlying source to whatever sink the
* embedding uses, assuming that such direct transfers can be performed
* more efficiently.
*/
#ifndef js_Stream_h
#define js_Stream_h
#include "jstypes.h"
#include "js/TypeDecls.h"
namespace JS {
/**
* Invoked whenever a reader desires more data from a ReadableStream's
* embedding-provided underlying source.
*
* The given |desiredSize| is the absolute size, not a delta from the previous
* desired size.
*/
typedef void
(* RequestReadableStreamDataCallback)(JSContext* cx, HandleObject stream,
void* underlyingSource, uint8_t flags, size_t desiredSize);
/**
* Invoked to cause the embedding to fill the given |buffer| with data from
* the given embedding-provided underlying source.
*
* This can only happen after the embedding has updated the amount of data
* available using JS::ReadableStreamUpdateDataAvailableFromSource. If at
* least one read request is pending when
* JS::ReadableStreamUpdateDataAvailableFromSource is called,
* the WriteIntoReadRequestBufferCallback is invoked immediately from under
* the call to JS::WriteIntoReadRequestBufferCallback. If not, it is invoked
* if and when a new read request is made.
*
* Note: This callback *must not cause GC*, because that could potentially
* invalidate the |buffer| pointer.
*/
typedef void
(* WriteIntoReadRequestBufferCallback)(JSContext* cx, HandleObject stream,
void* underlyingSource, uint8_t flags, void* buffer,
size_t length, size_t* bytesWritten);
/**
* Invoked in reaction to the ReadableStream being canceled to allow the
* embedding to free the underlying source.
*
* This is equivalent to calling |cancel| on non-external underlying sources
* provided to the ReadableStream constructor in JavaScript.
*
* The given |reason| is the JS::Value that was passed as an argument to
* ReadableStream#cancel().
*
* The returned JS::Value will be used to resolve the Promise returned by
* ReadableStream#cancel().
*/
typedef Value
(* CancelReadableStreamCallback)(JSContext* cx, HandleObject stream,
void* underlyingSource, uint8_t flags, HandleValue reason);
/**
* Invoked in reaction to a ReadableStream with an embedding-provided
* underlying source being closed.
*/
typedef void
(* ReadableStreamClosedCallback)(JSContext* cx, HandleObject stream, void* underlyingSource,
uint8_t flags);
/**
* Invoked in reaction to a ReadableStream with an embedding-provided
* underlying source being errored with the
* given reason.
*/
typedef void
(* ReadableStreamErroredCallback)(JSContext* cx, HandleObject stream, void* underlyingSource,
uint8_t flags, HandleValue reason);
/**
* Invoked in reaction to a ReadableStream with an embedding-provided
* underlying source being finalized. Only the underlying source is passed
* as an argument, while the ReadableStream itself is not to prevent the
* embedding from operating on a JSObject that might not be in a valid state
* anymore.
*
* Note: the ReadableStream might be finalized on a background thread. That
* means this callback might be invoked from an arbitrary thread, which the
* embedding must be able to handle.
*/
typedef void
(* ReadableStreamFinalizeCallback)(void* underlyingSource, uint8_t flags);
/**
* Sets runtime-wide callbacks to use for interacting with embedding-provided
* hooks for operating on ReadableStream instances.
*
* See the documentation for the individual callback types for details.
*/
extern JS_PUBLIC_API(void)
SetReadableStreamCallbacks(JSContext* cx,
RequestReadableStreamDataCallback dataRequestCallback,
WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
CancelReadableStreamCallback cancelCallback,
ReadableStreamClosedCallback closedCallback,
ReadableStreamErroredCallback erroredCallback,
ReadableStreamFinalizeCallback finalizeCallback);
extern JS_PUBLIC_API(bool)
HasReadableStreamCallbacks(JSContext* cx);
/**
* Returns a new instance of the ReadableStream builtin class in the current
* compartment, configured as a default stream.
* If a |proto| is passed, that gets set as the instance's [[Prototype]]
* instead of the original value of |ReadableStream.prototype|.
*/
extern JS_PUBLIC_API(JSObject*)
NewReadableDefaultStreamObject(JSContext* cx, HandleObject underlyingSource = nullptr,
HandleFunction size = nullptr, double highWaterMark = 1,
HandleObject proto = nullptr);
/**
* Returns a new instance of the ReadableStream builtin class in the current
* compartment, configured as a byte stream.
* If a |proto| is passed, that gets set as the instance's [[Prototype]]
* instead of the original value of |ReadableStream.prototype|.
*/
extern JS_PUBLIC_API(JSObject*)
NewReadableByteStreamObject(JSContext* cx, HandleObject underlyingSource = nullptr,
double highWaterMark = 0, HandleObject proto = nullptr);
/**
* Returns a new instance of the ReadableStream builtin class in the current
* compartment, with the right slot layout. If a |proto| is passed, that gets
* set as the instance's [[Prototype]] instead of the original value of
* |ReadableStream.prototype|.
*
* The instance is optimized for operating as a byte stream backed by an
* embedding-provided underlying source, using the callbacks set via
* |JS::SetReadableStreamCallbacks|.
*
* The given |flags| will be passed to all applicable callbacks and can be
* used to disambiguate between different types of stream sources the
* embedding might support.
*
* Note: the embedding is responsible for ensuring that the pointer to the
* underlying source stays valid as long as the stream can be read from.
* The underlying source can be freed if the tree is canceled or errored.
* It can also be freed if the stream is destroyed. The embedding is notified
* of that using ReadableStreamFinalizeCallback.
*/
extern JS_PUBLIC_API(JSObject*)
NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource,
uint8_t flags = 0, HandleObject proto = nullptr);
/**
* Returns the flags that were passed to NewReadableExternalSourceStreamObject
* when creating the given stream.
*
* Asserts that the given stream has an embedding-provided underlying source.
*/
extern JS_PUBLIC_API(uint8_t)
ReadableStreamGetEmbeddingFlags(const JSObject* stream);
/**
* Returns the embedding-provided underlying source of the given |stream|.
*
* Can be used to optimize operations if both the underlying source and the
* intended sink are embedding-provided. In that case it might be
* preferrable to pipe data directly from source to sink without interacting
* with the stream at all.
*
* Locks the stream until ReadableStreamReleaseExternalUnderlyingSource is
* called.
*
* Throws an exception if the stream is locked, i.e. if a reader has been
* acquired for the stream, or if ReadableStreamGetExternalUnderlyingSource
* has been used previously without releasing the external source again.
*
* Throws an exception if the stream isn't readable, i.e if it is errored or
* closed. This is different from ReadableStreamGetReader because we don't
* have a Promise to resolve/reject, which a reader provides.
*
* Asserts that the stream has an embedding-provided underlying source.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject stream, void** source);
/**
* Releases the embedding-provided underlying source of the given |stream|,
* returning the stream into an unlocked state.
*
* Asserts that the stream was locked through
* ReadableStreamGetExternalUnderlyingSource.
*
* Asserts that the stream has an embedding-provided underlying source.
*/
extern JS_PUBLIC_API(void)
ReadableStreamReleaseExternalUnderlyingSource(JSObject* stream);
/**
* Update the amount of data available at the underlying source of the given
* |stream|.
*
* Can only be used for streams with an embedding-provided underlying source.
* The JS engine will use the given value to satisfy read requests for the
* stream by invoking the JS::WriteIntoReadRequestBuffer callback.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, HandleObject stream,
uint32_t availableData);
/**
* Returns true if the given object is an unwrapped ReadableStream object,
* false otherwise.
*/
extern JS_PUBLIC_API(bool)
IsReadableStream(const JSObject* obj);
/**
* Returns true if the given object is an unwrapped
* ReadableStreamDefaultReader or ReadableStreamBYOBReader object,
* false otherwise.
*/
extern JS_PUBLIC_API(bool)
IsReadableStreamReader(const JSObject* obj);
/**
* Returns true if the given object is an unwrapped
* ReadableStreamDefaultReader object, false otherwise.
*/
extern JS_PUBLIC_API(bool)
IsReadableStreamDefaultReader(const JSObject* obj);
/**
* Returns true if the given object is an unwrapped
* ReadableStreamBYOBReader object, false otherwise.
*/
extern JS_PUBLIC_API(bool)
IsReadableStreamBYOBReader(const JSObject* obj);
enum class ReadableStreamMode {
Default,
Byte,
ExternalSource
};
/**
* Returns the stream's ReadableStreamMode. If the mode is |Byte| or
* |ExternalSource|, it's possible to acquire a BYOB reader for more optimized
* operations.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(ReadableStreamMode)
ReadableStreamGetMode(const JSObject* stream);
enum class ReadableStreamReaderMode {
Default,
BYOB
};
/**
* Returns true if the given ReadableStream is readable, false if not.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamIsReadable(const JSObject* stream);
/**
* Returns true if the given ReadableStream is locked, false if not.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamIsLocked(const JSObject* stream);
/**
* Returns true if the given ReadableStream is disturbed, false if not.
*
* Asserts that |stream| is an ReadableStream instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamIsDisturbed(const JSObject* stream);
/**
* Cancels the given ReadableStream with the given reason and returns a
* Promise resolved according to the result.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(JSObject*)
ReadableStreamCancel(JSContext* cx, HandleObject stream, HandleValue reason);
/**
* Creates a reader of the type specified by the mode option and locks the
* stream to the new reader.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(JSObject*)
ReadableStreamGetReader(JSContext* cx, HandleObject stream, ReadableStreamReaderMode mode);
/**
* Tees the given ReadableStream and stores the two resulting streams in
* outparams. Returns false if the operation fails, e.g. because the stream is
* locked.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamTee(JSContext* cx, HandleObject stream,
MutableHandleObject branch1Stream, MutableHandleObject branch2Stream);
/**
* Retrieves the desired combined size of additional chunks to fill the given
* ReadableStream's queue. Stores the result in |value| and sets |hasValue| to
* true on success, returns false on failure.
*
* If the stream is errored, the call will succeed but no value will be stored
* in |value| and |hasValue| will be set to false.
*
* Note: This is semantically equivalent to the |desiredSize| getter on
* the stream controller's prototype in JS. We expose it with the stream
* itself as a target for simplicity.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(void)
ReadableStreamGetDesiredSize(JSObject* stream, bool* hasValue, double* value);
/**
* Closes the given ReadableStream.
*
* Throws a TypeError and returns false if the closing operation fails.
*
* Note: This is semantically equivalent to the |close| method on
* the stream controller's prototype in JS. We expose it with the stream
* itself as a target for simplicity.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamClose(JSContext* cx, HandleObject stream);
/**
* Returns true if the given ReadableStream reader is locked, false otherwise.
*
* Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
* ReadableStreamBYOBReader instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamReaderIsClosed(const JSObject* reader);
/**
* Enqueues the given chunk in the given ReadableStream.
*
* Throws a TypeError and returns false if the enqueing operation fails.
*
* Note: This is semantically equivalent to the |enqueue| method on
* the stream controller's prototype in JS. We expose it with the stream
* itself as a target for simplicity.
*
* If the ReadableStream has an underlying byte source, the given chunk must
* be a typed array or a DataView. Consider using
* ReadableByteStreamEnqueueBuffer.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamEnqueue(JSContext* cx, HandleObject stream, HandleValue chunk);
/**
* Enqueues the given buffer as a chunk in the given ReadableStream.
*
* Throws a TypeError and returns false if the enqueing operation fails.
*
* Note: This is semantically equivalent to the |enqueue| method on
* the stream controller's prototype in JS. We expose it with the stream
* itself as a target for simplicity. Additionally, the JS version only
* takes typed arrays and ArrayBufferView instances as arguments, whereas
* this takes an ArrayBuffer, obviating the need to wrap it into a typed
* array.
*
* Asserts that |stream| is an unwrapped ReadableStream instance and |buffer|
* an unwrapped ArrayBuffer instance.
*/
extern JS_PUBLIC_API(bool)
ReadableByteStreamEnqueueBuffer(JSContext* cx, HandleObject stream, HandleObject buffer);
/**
* Errors the given ReadableStream, causing all future interactions to fail
* with the given error value.
*
* Throws a TypeError and returns false if the erroring operation fails.
*
* Note: This is semantically equivalent to the |error| method on
* the stream controller's prototype in JS. We expose it with the stream
* itself as a target for simplicity.
*
* Asserts that |stream| is an unwrapped ReadableStream instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamError(JSContext* cx, HandleObject stream, HandleValue error);
/**
* Cancels the given ReadableStream reader's associated stream.
*
* Throws a TypeError and returns false if the given reader isn't active.
*
* Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
* ReadableStreamBYOBReader instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, HandleValue reason);
/**
* Cancels the given ReadableStream reader's associated stream.
*
* Throws a TypeError and returns false if the given reader has pending
* read or readInto (for default or byob readers, respectively) requests.
*
* Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
* ReadableStreamBYOBReader instance.
*/
extern JS_PUBLIC_API(bool)
ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader);
/**
* Requests a read from the reader's associated ReadableStream and returns the
* resulting PromiseObject.
*
* Returns a Promise that's resolved with the read result once available or
* rejected immediately if the stream is errored or the operation failed.
*
* Asserts that |reader| is an unwrapped ReadableStreamDefaultReader instance.
*/
extern JS_PUBLIC_API(JSObject*)
ReadableStreamDefaultReaderRead(JSContext* cx, HandleObject reader);
/**
* Requests a read from the reader's associated ReadableStream into the given
* ArrayBufferView and returns the resulting PromiseObject.
*
* Returns a Promise that's resolved with the read result once available or
* rejected immediately if the stream is errored or the operation failed.
*
* Asserts that |reader| is an unwrapped ReadableStreamDefaultReader and
* |view| an unwrapped typed array or DataView instance.
*/
extern JS_PUBLIC_API(JSObject*)
ReadableStreamBYOBReaderRead(JSContext* cx, HandleObject reader, HandleObject view);
} // namespace JS
#endif // js_Realm_h

5488
js/src/builtin/Stream.cpp Normal file

File diff suppressed because it is too large Load diff

170
js/src/builtin/Stream.h Normal file
View file

@ -0,0 +1,170 @@
/* -*- 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_Stream_h
#define builtin_Stream_h
#include "builtin/Promise.h"
#include "vm/NativeObject.h"
namespace js {
class AutoSetNewObjectMetadata;
class ReadableStream : public NativeObject
{
public:
static ReadableStream* createDefaultStream(JSContext* cx, HandleValue underlyingSource,
HandleValue size, HandleValue highWaterMark,
HandleObject proto = nullptr);
static ReadableStream* createByteStream(JSContext* cx, HandleValue underlyingSource,
HandleValue highWaterMark,
HandleObject proto = nullptr);
static ReadableStream* createExternalSourceStream(JSContext* cx, void* underlyingSource,
uint8_t flags, HandleObject proto = nullptr);
bool readable() const;
bool closed() const;
bool errored() const;
bool disturbed() const;
bool locked() const;
void desiredSize(bool* hasSize, double* size) const;
JS::ReadableStreamMode mode() const;
static MOZ_MUST_USE bool close(JSContext* cx, Handle<ReadableStream*> stream);
static MOZ_MUST_USE JSObject* cancel(JSContext* cx, Handle<ReadableStream*> stream,
HandleValue reason);
static MOZ_MUST_USE bool error(JSContext* cx, Handle<ReadableStream*> stream,
HandleValue error);
static MOZ_MUST_USE NativeObject* getReader(JSContext* cx, Handle<ReadableStream*> stream,
JS::ReadableStreamReaderMode mode);
static MOZ_MUST_USE bool tee(JSContext* cx,
Handle<ReadableStream*> stream, bool cloneForBranch2,
MutableHandle<ReadableStream*> branch1Stream,
MutableHandle<ReadableStream*> branch2Stream);
static MOZ_MUST_USE bool enqueue(JSContext* cx, Handle<ReadableStream*> stream,
HandleValue chunk);
static MOZ_MUST_USE bool enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream,
Handle<ArrayBufferObject*> chunk);
static MOZ_MUST_USE bool getExternalSource(JSContext* cx, Handle<ReadableStream*> stream,
void** source);
void releaseExternalSource();
uint8_t embeddingFlags() const;
static MOZ_MUST_USE bool updateDataAvailableFromSource(JSContext* cx,
Handle<ReadableStream*> stream,
uint32_t availableData);
enum State {
Readable = 1 << 0,
Closed = 1 << 1,
Errored = 1 << 2,
Disturbed = 1 << 3
};
private:
static MOZ_MUST_USE ReadableStream* createStream(JSContext* cx, HandleObject proto = nullptr);
public:
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
class ReadableStreamDefaultReader : public NativeObject
{
public:
static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader);
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
class ReadableStreamBYOBReader : public NativeObject
{
public:
static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader,
Handle<ArrayBufferViewObject*> view);
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
bool ReadableStreamReaderIsClosed(const JSObject* reader);
MOZ_MUST_USE bool ReadableStreamReaderCancel(JSContext* cx, HandleObject reader,
HandleValue reason);
MOZ_MUST_USE bool ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader);
class ReadableStreamDefaultController : public NativeObject
{
public:
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
class ReadableByteStreamController : public NativeObject
{
public:
bool hasExternalSource();
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
class ReadableStreamBYOBRequest : public NativeObject
{
public:
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
class ByteLengthQueuingStrategy : public NativeObject
{
public:
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
class CountQueuingStrategy : public NativeObject
{
public:
static bool constructor(JSContext* cx, unsigned argc, Value* vp);
static const ClassSpec classSpec_;
static const Class class_;
static const ClassSpec protoClassSpec_;
static const Class protoClass_;
};
} // namespace js
#endif /* builtin_Stream_h */

View file

@ -1441,6 +1441,14 @@ RejectPromise(JSContext* cx, unsigned argc, Value* vp)
return result;
}
static bool
StreamsAreEnabled(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(cx->options().streams());
return true;
}
static unsigned finalizeCount = 0;
static void
@ -4178,6 +4186,10 @@ JS_FN_HELP("rejectPromise", RejectPromise, 2, 0,
"rejectPromise(promise, reason)",
" Reject a Promise by calling the JSAPI function JS::RejectPromise."),
JS_FN_HELP("streamsAreEnabled", StreamsAreEnabled, 0, 0,
"streamsAreEnabled()",
" Returns a boolean indicating whether WHATWG Streams are enabled for the current compartment."),
JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
"makeFinalizeObserver()",
" Get a special object whose finalization increases the counter returned\n"

View file

@ -2,8 +2,6 @@
* 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 "TypedObjectConstants.h"
function ViewedArrayBufferIfReified(tarray) {
assert(IsTypedArray(tarray), "non-typed array asked for its buffer");
@ -1781,7 +1779,7 @@ function ArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
// Steps 19-21.
ArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
// Step 22.
return new_;
@ -1863,7 +1861,7 @@ function SharedArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, newLen, actualLen);
// Steps 16-18.
SharedArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
SharedArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
// Step 19.
return new_;

View file

@ -23,6 +23,7 @@
*/
#include "SelfHostingDefines.h"
#include "TypedObjectConstants.h"
// Assertions and debug printing, defined here instead of in the header above
// to make `assert` invisible to C++.

View file

@ -613,11 +613,39 @@ MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of i
MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method")
// Async Iteration
MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop should be used with 'of'")
MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_SYNTAXERR, "'for await' loop should be used with 'of'")
MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator")
MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator")
MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value")
// ReadableStream
MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG,0, JSEXN_RANGEERR,"'underlyingSource.type' must be \"bytes\" or undefined.")
MSG_DEF(JSMSG_READABLESTREAM_INVALID_READER_MODE, 0, JSEXN_RANGEERR,"'mode' must be \"byob\" or undefined.")
MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must be a finite, non-negative number.")
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN, 0, JSEXN_RANGEERR, "'bytesWritten' exceeds remaining length.")
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE, 0, JSEXN_RANGEERR, "view size does not match requested data.")
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET, 0, JSEXN_RANGEERR, "view offset does not match requested position.")
MSG_DEF(JSMSG_READABLESTREAM_NOT_LOCKED, 1, JSEXN_TYPEERR, "'{0}' may only be called on a locked stream.")
MSG_DEF(JSMSG_READABLESTREAM_LOCKED, 0, JSEXN_TYPEERR, "A Reader may only be created for an unlocked ReadableStream.")
MSG_DEF(JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableByteStreamController.")
MSG_DEF(JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableStreamDefaultController.")
MSG_DEF(JSMSG_READABLESTREAM_CONTROLLER_SET, 0, JSEXN_TYPEERR, "The ReadableStream already has a controller defined.")
MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_OWNED, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may only be called on a reader owned by a stream.")
MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_EMPTY, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may not be called on a reader with read requests.")
MSG_DEF(JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW, 0, JSEXN_TYPEERR, "ReadableStreamBYOBReader.read() was passed an empty TypedArrayBuffer view.")
MSG_DEF(JSMSG_READABLESTREAMREADER_RELEASED, 0, JSEXN_TYPEERR, "The ReadableStream reader was released.")
MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_CLOSED, 1, JSEXN_TYPEERR, "'{0}' called on a stream already closing.")
MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "'{0}' may only be called on a stream in the 'readable' state.")
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.")
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, 1, JSEXN_TYPEERR, "{0} passed a bad chunk.")
MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.")
MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, 1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.")
MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED, 0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.")
MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented")
// Other Stream-related
MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK, 0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.")
// BigInt
MSG_DEF(JSMSG_BIGINT_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert BigInt to number")
MSG_DEF(JSMSG_NUMBER_TO_BIGINT, 0, JSEXN_RANGEERR, "can't convert non-finite number to BigInt")

View file

@ -126,6 +126,11 @@ if CONFIG['ENABLE_ION']:
'testJitRValueAlloc.cpp',
]
if CONFIG['ENABLE_STREAMS']:
UNIFIED_SOURCES += [
'testReadableStream.cpp',
]
DEFINES['EXPORT_JS_API'] = True
LOCAL_INCLUDES += [

View file

@ -29,6 +29,7 @@
#include "js/RequiredDefines.h"
#include "js/RootingAPI.h"
#include "js/SliceBudget.h"
#include "js/Stream.h"
#include "js/StructuredClone.h"
#include "js/TracingAPI.h"
#include "js/TrackedOptimizationInfo.h"

View file

@ -0,0 +1,676 @@
/* -*- 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 "jsapi.h"
#include "jsapi-tests/tests.h"
using namespace JS;
char test_buffer_data[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static JSObject*
NewDefaultStream(JSContext* cx, HandleObject source = nullptr, HandleFunction size = nullptr,
double highWaterMark = 1, HandleObject proto = nullptr)
{
RootedObject stream(cx, NewReadableDefaultStreamObject(cx, source, size, highWaterMark,
proto));
MOZ_ASSERT_IF(stream, IsReadableStream(stream));
return stream;
}
static JSObject*
NewByteStream(JSContext* cx, double highWaterMark = 0, HandleObject proto = nullptr)
{
RootedObject source(cx, JS_NewPlainObject(cx));
MOZ_ASSERT(source);
RootedObject stream(cx, NewReadableByteStreamObject(cx, source, highWaterMark, proto));
MOZ_ASSERT_IF(stream, IsReadableStream(stream));
return stream;
}
static bool dataRequestCBCalled = false;
static void
DataRequestCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
size_t desiredSize)
{
MOZ_ASSERT(!dataRequestCBCalled, "Invalid test setup");
dataRequestCBCalled = true;
}
static bool writeIntoRequestBufferCBCalled = false;
static void
WriteIntoRequestBufferCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
void* buffer, size_t length, size_t* bytesWritten)
{
MOZ_ASSERT(!writeIntoRequestBufferCBCalled, "Invalid test setup");
MOZ_ASSERT(length <= sizeof(test_buffer_data));
memcpy(buffer, test_buffer_data, length);
writeIntoRequestBufferCBCalled = true;
*bytesWritten = length;
}
static bool cancelStreamCBCalled = false;
static Value cancelStreamReason;
static Value
CancelStreamCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
HandleValue reason)
{
MOZ_ASSERT(!cancelStreamCBCalled, "Invalid test setup");
cancelStreamCBCalled = true;
cancelStreamReason = reason;
return reason;
}
static bool streamClosedCBCalled = false;
static Value streamClosedReason;
static void
StreamClosedCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags)
{
MOZ_ASSERT(!streamClosedCBCalled, "Invalid test setup");
streamClosedCBCalled = true;
}
static bool streamErroredCBCalled = false;
static Value streamErroredReason;
static void
StreamErroredCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
HandleValue reason)
{
MOZ_ASSERT(!streamErroredCBCalled, "Invalid test setup");
streamErroredCBCalled = true;
streamErroredReason = reason;
}
static bool finalizeStreamCBCalled = false;
static void* finalizedStreamUnderlyingSource;
static void
FinalizeStreamCB(void* underlyingSource, uint8_t flags)
{
MOZ_ASSERT(!finalizeStreamCBCalled, "Invalid test setup");
finalizeStreamCBCalled = true;
finalizedStreamUnderlyingSource = underlyingSource;
}
static void
ResetCallbacks()
{
dataRequestCBCalled = false;
writeIntoRequestBufferCBCalled = false;
cancelStreamReason = UndefinedValue();
cancelStreamCBCalled = false;
streamClosedCBCalled = false;
streamErroredCBCalled = false;
finalizeStreamCBCalled = false;
}
static bool
GetIterResult(JSContext* cx, HandleObject promise, MutableHandleValue value, bool* done)
{
RootedObject iterResult(cx, &GetPromiseResult(promise).toObject());
bool found;
if (!JS_HasProperty(cx, iterResult, "value", &found))
return false;
MOZ_ASSERT(found);
if (!JS_HasProperty(cx, iterResult, "done", &found))
return false;
MOZ_ASSERT(found);
RootedValue doneVal(cx);
if (!JS_GetProperty(cx, iterResult, "value", value))
return false;
if (!JS_GetProperty(cx, iterResult, "done", &doneVal))
return false;
*done = doneVal.toBoolean();
MOZ_ASSERT_IF(*done, value.isUndefined());
return true;
}
static JSObject*
GetReadChunk(JSContext* cx, HandleObject readRequest)
{
MOZ_ASSERT(GetPromiseState(readRequest) == PromiseState::Fulfilled);
RootedValue resultVal(cx, GetPromiseResult(readRequest));
MOZ_ASSERT(resultVal.isObject());
RootedObject result(cx, &resultVal.toObject());
RootedValue chunkVal(cx);
JS_GetProperty(cx, result, "value", &chunkVal);
return &chunkVal.toObject();
}
BEGIN_TEST(testReadableStream_NewReadableStream)
{
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Default);
return true;
}
END_TEST(testReadableStream_NewReadableStream)
BEGIN_TEST(testReadableStream_NewReadableByteStream)
{
RootedObject stream(cx, NewByteStream(cx));
CHECK(stream);
CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Byte);
return true;
}
END_TEST(testReadableStream_NewReadableByteStream)
BEGIN_TEST(testReadableStream_ReadableStreamGetReaderDefault)
{
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
CHECK(reader);
CHECK(IsReadableStreamDefaultReader(reader));
CHECK(ReadableStreamIsLocked(stream));
CHECK(!ReadableStreamReaderIsClosed(reader));
return true;
}
END_TEST(testReadableStream_ReadableStreamGetReaderDefault)
BEGIN_TEST(testReadableStream_ReadableStreamGetReaderBYOB)
{
RootedObject stream(cx, NewByteStream(cx));
CHECK(stream);
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB));
CHECK(reader);
CHECK(IsReadableStreamBYOBReader(reader));
CHECK(ReadableStreamIsLocked(stream));
CHECK(!ReadableStreamReaderIsClosed(reader));
return true;
}
END_TEST(testReadableStream_ReadableStreamGetReaderBYOB)
BEGIN_TEST(testReadableStream_ReadableStreamTee)
{
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
RootedObject leftStream(cx);
RootedObject rightStream(cx);
CHECK(ReadableStreamTee(cx, stream, &leftStream, &rightStream));
CHECK(ReadableStreamIsLocked(stream));
CHECK(leftStream);
CHECK(IsReadableStream(leftStream));
CHECK(rightStream);
CHECK(IsReadableStream(rightStream));
return true;
}
END_TEST(testReadableStream_ReadableStreamTee)
BEGIN_TEST(testReadableStream_ReadableStreamEnqueue)
{
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
RootedObject chunk(cx, JS_NewPlainObject(cx));
CHECK(chunk);
RootedValue chunkVal(cx, ObjectValue(*chunk));
CHECK(ReadableStreamEnqueue(cx, stream, chunkVal));
return true;
}
END_TEST(testReadableStream_ReadableStreamEnqueue)
BEGIN_TEST(testReadableStream_ReadableByteStreamEnqueue)
{
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
RootedObject chunk(cx, JS_NewUint8Array(cx, 42));
CHECK(chunk);
CHECK(!ReadableByteStreamEnqueueBuffer(cx, stream, chunk));
CHECK(JS_IsExceptionPending(cx));
return true;
}
END_TEST(testReadableStream_ReadableByteStreamEnqueue)
BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderRead)
{
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
CHECK(reader);
RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
CHECK(request);
CHECK(IsPromiseObject(request));
CHECK(GetPromiseState(request) == PromiseState::Pending);
RootedObject chunk(cx, JS_NewPlainObject(cx));
CHECK(chunk);
RootedValue chunkVal(cx, ObjectValue(*chunk));
CHECK(ReadableStreamEnqueue(cx, stream, chunkVal));
CHECK(GetReadChunk(cx, request) == chunk);
return true;
}
END_TEST(testReadableStream_ReadableStreamDefaultReaderRead)
BEGIN_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead)
{
RootedObject stream(cx, NewByteStream(cx));
CHECK(stream);
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
CHECK(reader);
RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
CHECK(request);
CHECK(IsPromiseObject(request));
CHECK(GetPromiseState(request) == PromiseState::Pending);
size_t length = sizeof(test_buffer_data);
RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data));
CHECK(buffer);
RootedObject chunk(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, length));
CHECK(chunk);
bool isShared;
CHECK(!JS_IsDetachedArrayBufferObject(buffer));
CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, chunk));
CHECK(JS_IsDetachedArrayBufferObject(buffer));
RootedObject readChunk(cx, GetReadChunk(cx, request));
CHECK(JS_IsUint8Array(readChunk));
void* readBufferData;
{
JS::AutoCheckCannotGC autoNoGC(cx);
readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC);
}
CHECK(readBufferData);
CHECK(!memcmp(test_buffer_data, readBufferData, length));
return true;
}
END_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead)
BEGIN_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead)
{
RootedObject stream(cx, NewByteStream(cx));
CHECK(stream);
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB));
CHECK(reader);
size_t length = sizeof(test_buffer_data);
RootedObject targetArray(cx, JS_NewUint8Array(cx, length));
bool isShared;
RootedObject request(cx, ReadableStreamBYOBReaderRead(cx, reader, targetArray));
CHECK(request);
CHECK(IsPromiseObject(request));
CHECK(GetPromiseState(request) == PromiseState::Pending);
CHECK(JS_IsDetachedArrayBufferObject(JS_GetArrayBufferViewBuffer(cx, targetArray, &isShared)));
RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data));
CHECK(buffer);
CHECK(!JS_IsDetachedArrayBufferObject(buffer));
CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, buffer));
CHECK(JS_IsDetachedArrayBufferObject(buffer));
RootedObject readChunk(cx, GetReadChunk(cx, request));
CHECK(JS_IsUint8Array(readChunk));
void* readBufferData;
{
JS::AutoCheckCannotGC autoNoGC(cx);
readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC);
}
CHECK(readBufferData);
CHECK(!memcmp(test_buffer_data, readBufferData, length));
// TODO: eliminate the memcpy that happens here.
// CHECK(readBufferData == test_buffer_data);
return true;
}
END_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead)
BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderClose)
{
SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB,
&CancelStreamCB, &StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB);
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
CHECK(reader);
RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
CHECK(request);
CHECK(IsPromiseObject(request));
CHECK(GetPromiseState(request) == PromiseState::Pending);
CHECK(ReadableStreamClose(cx, stream));
bool done;
RootedValue value(cx);
CHECK(GetPromiseState(request) == PromiseState::Fulfilled);
CHECK(GetIterResult(cx, request, &value, &done));
CHECK(value.isUndefined());
CHECK(done);
// The callbacks are only invoked for external streams.
CHECK(!streamClosedCBCalled);
return true;
}
END_TEST(testReadableStream_ReadableStreamDefaultReaderClose)
BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderError)
{
ResetCallbacks();
SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB,
&CancelStreamCB, &StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB);
RootedObject stream(cx, NewDefaultStream(cx));
CHECK(stream);
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
CHECK(reader);
RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
CHECK(request);
CHECK(IsPromiseObject(request));
CHECK(GetPromiseState(request) == PromiseState::Pending);
CHECK(ReadableStreamIsLocked(stream));
CHECK(ReadableStreamIsReadable(stream));
RootedValue error(cx, Int32Value(42));
CHECK(ReadableStreamError(cx, stream, error));
CHECK(GetPromiseState(request) == PromiseState::Rejected);
RootedValue reason(cx, GetPromiseResult(request));
CHECK(reason.isInt32());
CHECK(reason.toInt32() == 42);
// The callbacks are only invoked for external streams.
CHECK(!streamErroredCBCalled);
return true;
}
END_TEST(testReadableStream_ReadableStreamDefaultReaderError)
static JSObject*
NewExternalSourceStream(JSContext* cx, void* underlyingSource,
RequestReadableStreamDataCallback dataRequestCallback,
WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
CancelReadableStreamCallback cancelCallback,
ReadableStreamClosedCallback closedCallback,
ReadableStreamErroredCallback erroredCallback,
ReadableStreamFinalizeCallback finalizeCallback)
{
SetReadableStreamCallbacks(cx, dataRequestCallback, writeIntoReadRequestCallback,
cancelCallback, closedCallback, erroredCallback,
finalizeCallback);
RootedObject stream(cx, NewReadableExternalSourceStreamObject(cx, underlyingSource));
MOZ_ASSERT_IF(stream, IsReadableStream(stream));
return stream;
}
BEGIN_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource)
{
ResetCallbacks();
RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
&WriteIntoRequestBufferCB, &CancelStreamCB,
&StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB));
CHECK(stream);
CHECK(ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource);
void* underlyingSource;
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
CHECK(underlyingSource == &test_buffer_data);
CHECK(ReadableStreamIsLocked(stream));
ReadableStreamReleaseExternalUnderlyingSource(stream);
return true;
}
END_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource)
BEGIN_TEST(testReadableStream_ExternalSourceCancel)
{
ResetCallbacks();
RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
&WriteIntoRequestBufferCB, &CancelStreamCB,
&StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB));
CHECK(stream);
RootedValue reason(cx, Int32Value(42));
CHECK(ReadableStreamCancel(cx, stream, reason));
CHECK(cancelStreamCBCalled);
CHECK(cancelStreamReason == reason);
return true;
}
END_TEST(testReadableStream_ExternalSourceCancel)
BEGIN_TEST(testReadableStream_ExternalSourceGetReader)
{
ResetCallbacks();
RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
&WriteIntoRequestBufferCB, &CancelStreamCB,
&StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB));
CHECK(stream);
RootedValue streamVal(cx, ObjectValue(*stream));
CHECK(JS_SetProperty(cx, global, "stream", streamVal));
RootedValue rval(cx);
EVAL("stream.getReader()", &rval);
CHECK(rval.isObject());
RootedObject reader(cx, &rval.toObject());
CHECK(IsReadableStreamDefaultReader(reader));
return true;
}
END_TEST(testReadableStream_ExternalSourceGetReader)
BEGIN_TEST(testReadableStream_ExternalSourceUpdateAvailableData)
{
ResetCallbacks();
RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
&WriteIntoRequestBufferCB, &CancelStreamCB,
&StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB));
CHECK(stream);
ReadableStreamUpdateDataAvailableFromSource(cx, stream, 1024);
return true;
}
END_TEST(testReadableStream_ExternalSourceUpdateAvailableData)
struct ReadFromExternalSourceFixture : public JSAPITest
{
virtual ~ReadFromExternalSourceFixture() {}
bool readWithoutDataAvailable(const char* evalSrc, const char* evalSrc2,
uint32_t writtenLength)
{
ResetCallbacks();
definePrint();
RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
&WriteIntoRequestBufferCB,
&CancelStreamCB,
&StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB));
CHECK(stream);
js::RunJobs(cx);
void* underlyingSource;
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
CHECK(underlyingSource == &test_buffer_data);
CHECK(ReadableStreamIsLocked(stream));
ReadableStreamReleaseExternalUnderlyingSource(stream);
RootedValue streamVal(cx, ObjectValue(*stream));
CHECK(JS_SetProperty(cx, global, "stream", streamVal));
RootedValue rval(cx);
EVAL(evalSrc, &rval);
CHECK(dataRequestCBCalled);
CHECK(!writeIntoRequestBufferCBCalled);
CHECK(rval.isObject());
RootedObject promise(cx, &rval.toObject());
CHECK(IsPromiseObject(promise));
CHECK(GetPromiseState(promise) == PromiseState::Pending);
size_t length = sizeof(test_buffer_data);
ReadableStreamUpdateDataAvailableFromSource(cx, stream, length);
CHECK(writeIntoRequestBufferCBCalled);
CHECK(GetPromiseState(promise) == PromiseState::Fulfilled);
RootedValue iterVal(cx);
bool done;
if (!GetIterResult(cx, promise, &iterVal, &done))
return false;
CHECK(!done);
RootedObject chunk(cx, &iterVal.toObject());
CHECK(JS_IsUint8Array(chunk));
{
JS::AutoCheckCannotGC noGC(cx);
bool dummy;
void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC);
CHECK(!memcmp(buffer, test_buffer_data, writtenLength));
}
dataRequestCBCalled = false;
writeIntoRequestBufferCBCalled = false;
EVAL(evalSrc2, &rval);
CHECK(dataRequestCBCalled);
CHECK(!writeIntoRequestBufferCBCalled);
return true;
}
bool readWithDataAvailable(const char* evalSrc, uint32_t writtenLength) {
ResetCallbacks();
definePrint();
RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
&WriteIntoRequestBufferCB, &CancelStreamCB,
&StreamClosedCB, &StreamErroredCB,
&FinalizeStreamCB));
CHECK(stream);
void* underlyingSource;
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
CHECK(underlyingSource == &test_buffer_data);
CHECK(ReadableStreamIsLocked(stream));
ReadableStreamReleaseExternalUnderlyingSource(stream);
size_t length = sizeof(test_buffer_data);
ReadableStreamUpdateDataAvailableFromSource(cx, stream, length);
RootedValue streamVal(cx, ObjectValue(*stream));
CHECK(JS_SetProperty(cx, global, "stream", streamVal));
RootedValue rval(cx);
EVAL(evalSrc, &rval);
CHECK(writeIntoRequestBufferCBCalled);
CHECK(rval.isObject());
RootedObject promise(cx, &rval.toObject());
CHECK(IsPromiseObject(promise));
CHECK(GetPromiseState(promise) == PromiseState::Fulfilled);
RootedValue iterVal(cx);
bool done;
if (!GetIterResult(cx, promise, &iterVal, &done))
return false;
CHECK(!done);
RootedObject chunk(cx, &iterVal.toObject());
CHECK(JS_IsUint8Array(chunk));
{
JS::AutoCheckCannotGC noGC(cx);
bool dummy;
void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC);
CHECK(!memcmp(buffer, test_buffer_data, writtenLength));
}
return true;
}
};
BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable)
{
return readWithoutDataAvailable("r = stream.getReader(); r.read()", "r.read()", sizeof(test_buffer_data));
}
END_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable)
BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceCloseWithPendingRead)
{
CHECK(readWithoutDataAvailable("r = stream.getReader(); request0 = r.read(); "
"request1 = r.read(); request0", "r.read()",
sizeof(test_buffer_data)));
RootedValue val(cx);
CHECK(JS_GetProperty(cx, global, "request1", &val));
CHECK(val.isObject());
RootedObject request(cx, &val.toObject());
CHECK(IsPromiseObject(request));
CHECK(GetPromiseState(request) == PromiseState::Pending);
CHECK(JS_GetProperty(cx, global, "stream", &val));
RootedObject stream(cx, &val.toObject());
ReadableStreamClose(cx, stream);
val = GetPromiseResult(request);
MOZ_ASSERT(val.isObject());
RootedObject result(cx, &val.toObject());
JS_GetProperty(cx, result, "done", &val);
CHECK(val.isBoolean());
CHECK(val.toBoolean() == true);
JS_GetProperty(cx, result, "value", &val);
CHECK(val.isUndefined());
return true;
}
END_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceCloseWithPendingRead)
BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadDefaultWithDataAvailable)
{
return readWithDataAvailable("r = stream.getReader(); r.read()", sizeof(test_buffer_data));
}
END_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadDefaultWithDataAvailable)
BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable)
{
return readWithoutDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(63))", "r.read(new Uint8Array(10))", 10);
}
END_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable)
BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadBYOBWithDataAvailable)
{
return readWithDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(10))", 10);
}
END_FIXTURE_TEST(ReadFromExternalSourceFixture,
testReadableStream_ExternalSourceReadBYOBWithDataAvailable)

View file

@ -81,6 +81,10 @@ JSObject* JSAPITest::createGlobal(JSPrincipals* principals)
/* Create the global object. */
JS::RootedObject newGlobal(cx);
JS::CompartmentOptions options;
#ifdef ENABLE_STREAMS
options.creationOptions().setStreamsEnabled(true);
#endif
printf("enabled\n");
options.behaviors().setVersion(JSVERSION_LATEST);
newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals, JS::FireOnNewGlobalHook,
options);

View file

@ -46,6 +46,7 @@
#include "builtin/MapObject.h"
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
#include "builtin/Stream.h"
#include "builtin/SymbolObject.h"
#ifdef ENABLE_BINARYDATA
# include "builtin/TypedObject.h"
@ -5095,7 +5096,6 @@ CallOriginalPromiseThenImpl(JSContext* cx, JS::HandleObject promiseObj,
return false;
}
return true;
}
JS_PUBLIC_API(JSObject*)
@ -5126,7 +5126,7 @@ JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promiseObj,
* Unforgeable version of Promise.all for internal use.
*
* Takes a dense array of Promise objects and returns a promise that's
* resolved with an array of resolution values when all those promises ahve
* resolved with an array of resolution values when all those promises have
* been resolved, or rejected with the rejection value of the first rejected
* promise.
*
@ -5141,6 +5141,368 @@ JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
return js::GetWaitForAllPromise(cx, promises);
}
JS_PUBLIC_API(JSObject*)
JS::NewReadableDefaultStreamObject(JSContext* cx,
JS::HandleObject underlyingSource /* = nullptr */,
JS::HandleFunction size /* = nullptr */,
double highWaterMark /* = 1 */,
JS::HandleObject proto /* = nullptr */)
{
MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
RootedObject source(cx, underlyingSource);
if (!source) {
source = NewBuiltinClassInstance<PlainObject>(cx);
if (!source)
return nullptr;
}
RootedValue sourceVal(cx, ObjectValue(*source));
RootedValue sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue());
RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark));
return ReadableStream::createDefaultStream(cx, sourceVal, sizeVal, highWaterMarkVal, proto);
}
JS_PUBLIC_API(JSObject*)
JS::NewReadableByteStreamObject(JSContext* cx,
JS::HandleObject underlyingSource /* = nullptr */,
double highWaterMark /* = 1 */,
JS::HandleObject proto /* = nullptr */)
{
MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
RootedObject source(cx, underlyingSource);
if (!source) {
source = NewBuiltinClassInstance<PlainObject>(cx);
if (!source)
return nullptr;
}
RootedValue sourceVal(cx, ObjectValue(*source));
RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark));
return ReadableStream::createByteStream(cx, sourceVal, highWaterMarkVal, proto);
}
extern JS_PUBLIC_API(void)
JS::SetReadableStreamCallbacks(JSContext* cx,
JS::RequestReadableStreamDataCallback dataRequestCallback,
JS::WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
JS::CancelReadableStreamCallback cancelCallback,
JS::ReadableStreamClosedCallback closedCallback,
JS::ReadableStreamErroredCallback erroredCallback,
JS::ReadableStreamFinalizeCallback finalizeCallback)
{
MOZ_ASSERT(dataRequestCallback);
MOZ_ASSERT(writeIntoReadRequestCallback);
MOZ_ASSERT(cancelCallback);
MOZ_ASSERT(closedCallback);
MOZ_ASSERT(erroredCallback);
MOZ_ASSERT(finalizeCallback);
JSRuntime* rt = cx->runtime();
MOZ_ASSERT(!rt->readableStreamDataRequestCallback);
MOZ_ASSERT(!rt->readableStreamWriteIntoReadRequestCallback);
MOZ_ASSERT(!rt->readableStreamCancelCallback);
MOZ_ASSERT(!rt->readableStreamClosedCallback);
MOZ_ASSERT(!rt->readableStreamErroredCallback);
MOZ_ASSERT(!rt->readableStreamFinalizeCallback);
rt->readableStreamDataRequestCallback = dataRequestCallback;
rt->readableStreamWriteIntoReadRequestCallback = writeIntoReadRequestCallback;
rt->readableStreamCancelCallback = cancelCallback;
rt->readableStreamClosedCallback = closedCallback;
rt->readableStreamErroredCallback = erroredCallback;
rt->readableStreamFinalizeCallback = finalizeCallback;
}
JS_PUBLIC_API(bool)
JS::HasReadableStreamCallbacks(JSContext* cx)
{
return cx->runtime()->readableStreamDataRequestCallback;
}
JS_PUBLIC_API(JSObject*)
JS::NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource,
uint8_t flags /* = 0 */,
HandleObject proto /* = nullptr */)
{
MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
#ifdef DEBUG
JSRuntime* rt = cx->runtime();
MOZ_ASSERT(rt->readableStreamDataRequestCallback);
MOZ_ASSERT(rt->readableStreamWriteIntoReadRequestCallback);
MOZ_ASSERT(rt->readableStreamCancelCallback);
MOZ_ASSERT(rt->readableStreamClosedCallback);
MOZ_ASSERT(rt->readableStreamErroredCallback);
MOZ_ASSERT(rt->readableStreamFinalizeCallback);
#endif // DEBUG
return ReadableStream::createExternalSourceStream(cx, underlyingSource, flags, proto);
}
JS_PUBLIC_API(uint8_t)
JS::ReadableStreamGetEmbeddingFlags(const JSObject* stream)
{
return stream->as<ReadableStream>().embeddingFlags();
}
JS_PUBLIC_API(bool)
JS::IsReadableStream(const JSObject* obj)
{
return obj->is<ReadableStream>();
}
JS_PUBLIC_API(bool)
JS::IsReadableStreamReader(const JSObject* obj)
{
return obj->is<ReadableStreamDefaultReader>() || obj->is<ReadableStreamBYOBReader>();
}
JS_PUBLIC_API(bool)
JS::IsReadableStreamDefaultReader(const JSObject* obj)
{
return obj->is<ReadableStreamDefaultReader>();
}
JS_PUBLIC_API(bool)
JS::IsReadableStreamBYOBReader(const JSObject* obj)
{
return obj->is<ReadableStreamBYOBReader>();
}
JS_PUBLIC_API(bool)
JS::ReadableStreamIsReadable(const JSObject* stream)
{
return stream->as<ReadableStream>().readable();
}
JS_PUBLIC_API(bool)
JS::ReadableStreamIsLocked(const JSObject* stream)
{
return stream->as<ReadableStream>().locked();
}
JS_PUBLIC_API(bool)
JS::ReadableStreamIsDisturbed(const JSObject* stream)
{
return stream->as<ReadableStream>().disturbed();
}
JS_PUBLIC_API(JSObject*)
JS::ReadableStreamCancel(JSContext* cx, HandleObject streamObj, HandleValue reason)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
assertSameCompartment(cx, reason);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
return ReadableStream::cancel(cx, stream, reason);
}
JS_PUBLIC_API(JS::ReadableStreamMode)
JS::ReadableStreamGetMode(const JSObject* stream)
{
return stream->as<ReadableStream>().mode();
}
JS_PUBLIC_API(JSObject*)
JS::ReadableStreamGetReader(JSContext* cx, HandleObject streamObj, ReadableStreamReaderMode mode)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
return ReadableStream::getReader(cx, stream, mode);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj, void** source)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
return ReadableStream::getExternalSource(cx, stream, source);
}
JS_PUBLIC_API(void)
JS::ReadableStreamReleaseExternalUnderlyingSource(JSObject* stream)
{
stream->as<ReadableStream>().releaseExternalSource();
}
JS_PUBLIC_API(bool)
JS::ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, JS::HandleObject streamObj,
uint32_t availableData)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
return ReadableStream::updateDataAvailableFromSource(cx, stream, availableData);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamTee(JSContext* cx, HandleObject streamObj,
MutableHandleObject branch1Obj, MutableHandleObject branch2Obj)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
Rooted<ReadableStream*> branch1Stream(cx);
Rooted<ReadableStream*> branch2Stream(cx);
if (!ReadableStream::tee(cx, stream, false, &branch1Stream, &branch2Stream))
return false;
branch1Obj.set(branch1Stream);
branch2Obj.set(branch2Stream);
return true;
}
JS_PUBLIC_API(void)
JS::ReadableStreamGetDesiredSize(JSObject* streamObj, bool* hasValue, double* value)
{
streamObj->as<ReadableStream>().desiredSize(hasValue, value);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamClose(JSContext* cx, HandleObject streamObj)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
return ReadableStream::close(cx, stream);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamEnqueue(JSContext* cx, HandleObject streamObj, HandleValue chunk)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
assertSameCompartment(cx, chunk);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
if (stream->mode() != JS::ReadableStreamMode::Default) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER,
"JS::ReadableStreamEnqueue");
return false;
}
return ReadableStream::enqueue(cx, stream, chunk);
}
JS_PUBLIC_API(bool)
JS::ReadableByteStreamEnqueueBuffer(JSContext* cx, HandleObject streamObj, HandleObject chunkObj)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
assertSameCompartment(cx, chunkObj);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
if (stream->mode() != JS::ReadableStreamMode::Byte) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER,
"JS::ReadableByteStreamEnqueueBuffer");
return false;
}
Rooted<ArrayBufferObject*> buffer(cx);
if (chunkObj->is<ArrayBufferViewObject>()) {
bool dummy;
buffer = &JS_GetArrayBufferViewBuffer(cx, chunkObj, &dummy)->as<ArrayBufferObject>();
} else if (chunkObj->is<ArrayBufferObject>()) {
buffer = &chunkObj->as<ArrayBufferObject>();
} else {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
"JS::ReadableByteStreamEnqueueBuffer");
return false;
}
return ReadableStream::enqueueBuffer(cx, stream, buffer);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamError(JSContext* cx, HandleObject streamObj, HandleValue error)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, streamObj);
assertSameCompartment(cx, error);
Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
return js::ReadableStream::error(cx, stream, error);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamReaderIsClosed(const JSObject* reader)
{
return js::ReadableStreamReaderIsClosed(reader);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, HandleValue reason)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, reader);
assertSameCompartment(cx, reason);
return js::ReadableStreamReaderCancel(cx, reader, reason);
}
JS_PUBLIC_API(bool)
JS::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, reader);
return js::ReadableStreamReaderReleaseLock(cx, reader);
}
JS_PUBLIC_API(JSObject*)
JS::ReadableStreamDefaultReaderRead(JSContext* cx, HandleObject readerObj)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, readerObj);
Rooted<ReadableStreamDefaultReader*> reader(cx, &readerObj->as<ReadableStreamDefaultReader>());
return js::ReadableStreamDefaultReader::read(cx, reader);
}
JS_PUBLIC_API(JSObject*)
JS::ReadableStreamBYOBReaderRead(JSContext* cx, HandleObject readerObj, HandleObject viewObj)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, readerObj);
assertSameCompartment(cx, viewObj);
Rooted<ReadableStreamBYOBReader*> reader(cx, &readerObj->as<ReadableStreamBYOBReader>());
Rooted<ArrayBufferViewObject*> view(cx, &viewObj->as<ArrayBufferViewObject>());
return js::ReadableStreamBYOBReader::read(cx, reader, view);
}
JS_PUBLIC_API(void)
JS::SetAsyncTaskCallbacks(JSContext* cx, JS::StartAsyncTaskCallback start,
JS::FinishAsyncTaskCallback finish)

View file

@ -35,6 +35,7 @@
#include "js/Principals.h"
#include "js/Realm.h"
#include "js/RootingAPI.h"
#include "js/Stream.h"
#include "js/TracingAPI.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
@ -1161,6 +1162,16 @@ class JS_PUBLIC_API(ContextOptions) {
return *this;
}
bool streams() const { return streams_; }
ContextOptions& setStreams(bool flag) {
streams_ = flag;
return *this;
}
ContextOptions& toggleStreams() {
streams_ = !streams_;
return *this;
}
bool nativeRegExp() const { return nativeRegExp_; }
ContextOptions& setNativeRegExp(bool flag) {
nativeRegExp_ = flag;
@ -1243,6 +1254,7 @@ class JS_PUBLIC_API(ContextOptions) {
bool strictMode_ : 1;
bool extraWarnings_ : 1;
bool arrayProtoValues_ : 1;
bool streams_ : 1;
};
JS_PUBLIC_API(ContextOptions&)

View file

@ -1787,6 +1787,9 @@ UnwrapArrayBufferView(JSObject* obj);
extern JS_FRIEND_API(JSObject*)
UnwrapSharedArrayBuffer(JSObject* obj);
extern JS_FRIEND_API(JSObject*)
UnwrapReadableStream(JSObject* obj);
namespace detail {
@ -2005,6 +2008,12 @@ JS_IsArrayBufferViewObject(JSObject* obj);
extern JS_FRIEND_API(uint32_t)
JS_GetArrayBufferViewByteLength(JSObject* obj);
/**
* More generic name for JS_GetTypedArrayByteOffset to cover DataViews as well
*/
extern JS_FRIEND_API(uint32_t)
JS_GetArrayBufferViewByteOffset(JSObject* obj);
/*
* Return a pointer to the start of the data referenced by a typed array. The
* data is still owned by the typed array, and should not be modified on

View file

@ -1003,6 +1003,13 @@ static const JSFunctionSpec number_methods[] = {
JS_FS_END
};
bool
js::IsInteger(const Value& val)
{
return val.isInt32() ||
(mozilla::IsFinite(val.toDouble()) && JS::ToInteger(val.toDouble()) == val.toDouble());
}
// ES6 draft ES6 15.7.3.12
static bool
Number_isInteger(JSContext* cx, unsigned argc, Value* vp)
@ -1013,9 +1020,7 @@ Number_isInteger(JSContext* cx, unsigned argc, Value* vp)
return true;
}
Value val = args[0];
args.rval().setBoolean(val.isInt32() ||
(mozilla::IsFinite(val.toDouble()) &&
JS::ToInteger(val.toDouble()) == val.toDouble()));
args.rval().setBoolean(js::IsInteger(val));
return true;
}

View file

@ -72,6 +72,10 @@ Int32ToString(ExclusiveContext* cx, int32_t i);
extern JSAtom*
Int32ToAtom(ExclusiveContext* cx, int32_t si);
// ES6 15.7.3.12
extern bool
IsInteger(const Value& val);
/*
* Convert an integer or double (contained in the given value) to a string and
* append to the given buffer.

View file

@ -108,6 +108,17 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \
imaginary(WasmMemory, dummy, dummy) \
imaginary(WasmTable, dummy, dummy) \
real(Promise, InitViaClassSpec, OCLASP(Promise)) \
real(ReadableStream, InitViaClassSpec, &js::ReadableStream::class_) \
real(ReadableStreamDefaultReader, InitViaClassSpec, &js::ReadableStreamDefaultReader::class_) \
real(ReadableStreamBYOBReader, InitViaClassSpec, &js::ReadableStreamBYOBReader::class_) \
real(ReadableStreamDefaultController, InitViaClassSpec, &js::ReadableStreamDefaultController::class_) \
real(ReadableByteStreamController, InitViaClassSpec, &js::ReadableByteStreamController::class_) \
real(ReadableStreamBYOBRequest, InitViaClassSpec, &js::ReadableStreamBYOBRequest::class_) \
imaginary(WritableStream, dummy, dummy) \
imaginary(WritableStreamDefaultWriter, dummy, dummy) \
imaginary(WritableStreamDefaultController,dummy, dummy) \
real(ByteLengthQueuingStrategy, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \
real(CountQueuingStrategy, InitViaClassSpec, &js::CountQueuingStrategy::class_) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)

View file

@ -92,6 +92,7 @@ EXPORTS.js += [
'../public/Result.h',
'../public/RootingAPI.h',
'../public/SliceBudget.h',
'../public/Stream.h',
'../public/StructuredClone.h',
'../public/SweepingAPI.h',
'../public/TraceKind.h',
@ -384,6 +385,7 @@ main_deunified_sources = [
SOURCES += [
'builtin/BigInt.cpp',
'builtin/RegExp.cpp',
'builtin/Stream.cpp',
'frontend/Parser.cpp',
'gc/StoreBuffer.cpp',
'jsarray.cpp',

View file

@ -318,6 +318,7 @@ static bool enableUnboxedArrays = false;
static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
static bool enableWasmAlwaysBaseline = false;
static bool enableArrayProtoValues = true;
static bool enableStreams = false;
static bool printTiming = false;
static const char* jsCacheDir = nullptr;
static const char* jsCacheAsmJSPath = nullptr;
@ -7565,6 +7566,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
enableArrayProtoValues = !op.getBoolOption("no-array-proto-values");
enableStreams = op.getBoolOption("enable-streams");
JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
.setIon(enableIon)
@ -7573,7 +7575,8 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
.setUnboxedArrays(enableUnboxedArrays)
.setArrayProtoValues(enableArrayProtoValues);
.setArrayProtoValues(enableArrayProtoValues)
.setStreams(enableStreams);
if (op.getBoolOption("wasm-check-bce"))
jit::JitOptions.wasmAlwaysCheckBounds = true;
@ -7845,7 +7848,8 @@ SetWorkerContextOptions(JSContext* cx)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
.setUnboxedArrays(enableUnboxedArrays)
.setArrayProtoValues(enableArrayProtoValues);
.setArrayProtoValues(enableArrayProtoValues)
.setStreams(enableStreams);
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
@ -8019,6 +8023,7 @@ main(int argc, char** argv, char** envp)
|| !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
|| !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
|| !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values")
|| !op.addBoolOption('\0', "enable-streams", "Enable WHATWG Streams")
#ifdef ENABLE_SHARED_ARRAY_BUFFER
|| !op.addStringOption('\0', "shared-memory", "on/off",
"SharedArrayBuffer and Atomics "

View file

@ -1255,15 +1255,16 @@ ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
}
/* static */ void
ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer,
Handle<ArrayBufferObject*> fromBuffer,
uint32_t fromIndex, uint32_t count)
ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
uint32_t count)
{
MOZ_ASSERT(toBuffer->byteLength() >= count);
MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
memcpy(toBuffer->dataPointer(), fromBuffer->dataPointer() + fromIndex, count);
memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count);
}
/* static */ void
@ -1890,6 +1891,17 @@ JS_GetArrayBufferViewByteLength(JSObject* obj)
: obj->as<TypedArrayObject>().byteLength();
}
JS_FRIEND_API(uint32_t)
JS_GetArrayBufferViewByteOffset(JSObject* obj)
{
obj = CheckedUnwrap(obj);
if (!obj)
return 0;
return obj->is<DataViewObject>()
? obj->as<DataViewObject>().byteOffset()
: obj->as<TypedArrayObject>().byteOffset();
}
JS_FRIEND_API(JSObject*)
JS_GetObjectAsArrayBufferView(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
{

View file

@ -261,9 +261,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
template<typename T>
static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp);
static void copyData(Handle<ArrayBufferObject*> toBuffer,
Handle<ArrayBufferObject*> fromBuffer,
uint32_t fromIndex, uint32_t count);
static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
uint32_t count);
static void trace(JSTracer* trc, JSObject* obj);
static void objectMoved(JSObject* obj, const JSObject* old);

View file

@ -18,6 +18,8 @@
macro(apply, apply, "apply") \
macro(args, args, "args") \
macro(arguments, arguments, "arguments") \
macro(AcquireReadableStreamBYOBReader, AcquireReadableStreamBYOBReader, "AcquireReadableStreamBYOBReader") \
macro(AcquireReadableStreamDefaultReader, AcquireReadableStreamDefaultReader, "AcquireReadableStreamDefaultReader") \
macro(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies") \
macro(ArrayIterator, ArrayIterator, "Array Iterator") \
macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \
@ -35,6 +37,7 @@
macro(AsyncGeneratorFunction, AsyncGeneratorFunction, "AsyncGeneratorFunction") \
macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \
macro(async, async, "async") \
macro(autoAllocateChunkSize, autoAllocateChunkSize, "autoAllocateChunkSize") \
macro(await, await, "await") \
macro(bigint64, bigint64, "bigint64") \
macro(biguint64, biguint64, "biguint64") \
@ -48,6 +51,7 @@
macro(buffer, buffer, "buffer") \
macro(builder, builder, "builder") \
macro(by, by, "by") \
macro(byob, byob, "byob") \
macro(byteAlignment, byteAlignment, "byteAlignment") \
macro(byteLength, byteLength, "byteLength") \
macro(byteOffset, byteOffset, "byteOffset") \
@ -59,6 +63,7 @@
macro(callee, callee, "callee") \
macro(caller, caller, "caller") \
macro(callFunction, callFunction, "callFunction") \
macro(cancel, cancel, "cancel") \
macro(case, case_, "case") \
macro(caseFirst, caseFirst, "caseFirst") \
macro(catch, catch_, "catch") \
@ -180,6 +185,7 @@
macro(has, has, "has") \
macro(hasOwn, hasOwn, "hasOwn") \
macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \
macro(highWaterMark, highWaterMark, "highWaterMark") \
macro(hour, hour, "hour") \
macro(hourCycle, hourCycle, "hourCycle") \
macro(if, if_, "if") \
@ -250,6 +256,7 @@
macro(minusSign, minusSign, "minusSign") \
macro(minute, minute, "minute") \
macro(missingArguments, missingArguments, "missingArguments") \
macro(mode, mode, "mode") \
macro(module, module, "module") \
macro(Module, Module, "Module") \
macro(ModuleInstantiate, ModuleInstantiate, "ModuleInstantiate") \
@ -312,7 +319,52 @@
macro(prototype, prototype, "prototype") \
macro(proxy, proxy, "proxy") \
macro(public, public_, "public") \
macro(pull, pull, "pull") \
macro(raw, raw, "raw") \
macro(ReadableByteStreamControllerGetDesiredSize, \
ReadableByteStreamControllerGetDesiredSize, \
"ReadableByteStreamControllerGetDesiredSize") \
macro(ReadableByteStreamController_close, \
ReadableByteStreamController_close, \
"ReadableByteStreamController_close") \
macro(ReadableByteStreamController_enqueue, \
ReadableByteStreamController_enqueue, \
"ReadableByteStreamController_enqueue") \
macro(ReadableByteStreamController_error, \
ReadableByteStreamController_error, \
"ReadableByteStreamController_error") \
macro(ReadableStreamBYOBReader_cancel, \
ReadableStreamBYOBReader_cancel, \
"ReadableStreamBYOBReader_cancel") \
macro(ReadableStreamBYOBReader_read, \
ReadableStreamBYOBReader_read, \
"ReadableStreamBYOBReader_read") \
macro(ReadableStreamBYOBReader_releaseLock, \
ReadableStreamBYOBReader_releaseLock, \
"ReadableStreamBYOBReader_releaseLock") \
macro(ReadableStream_cancel, ReadableStream_cancel, "ReadableStream_cancel") \
macro(ReadableStreamDefaultControllerGetDesiredSize, \
ReadableStreamDefaultControllerGetDesiredSize, \
"ReadableStreamDefaultControllerGetDesiredSize") \
macro(ReadableStreamDefaultController_close, \
ReadableStreamDefaultController_close, \
"ReadableStreamDefaultController_close") \
macro(ReadableStreamDefaultController_enqueue, \
ReadableStreamDefaultController_enqueue, \
"ReadableStreamDefaultController_enqueue") \
macro(ReadableStreamDefaultController_error, \
ReadableStreamDefaultController_error, \
"ReadableStreamDefaultController_error") \
macro(ReadableStreamDefaultReader_cancel, \
ReadableStreamDefaultReader_cancel, \
"ReadableStreamDefaultReader_cancel") \
macro(ReadableStreamDefaultReader_read, \
ReadableStreamDefaultReader_read, \
"ReadableStreamDefaultReader_read") \
macro(ReadableStreamDefaultReader_releaseLock, \
ReadableStreamDefaultReader_releaseLock, \
"ReadableStreamDefaultReader_releaseLock") \
macro(ReadableStreamTee, ReadableStreamTee, "ReadableStreamTee") \
macro(reason, reason, "reason") \
macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
macro(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \
@ -347,6 +399,7 @@
macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \
macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \
macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \
macro(start, start, "start") \
macro(startTimestamp, startTimestamp, "startTimestamp") \
macro(state, state, "state") \
macro(static, static_, "static") \

View file

@ -23,6 +23,7 @@
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/Stream.h"
#include "builtin/SymbolObject.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakMapObject.h"
@ -98,6 +99,16 @@ GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key)
case JSProto_WebAssembly:
return !wasm::HasSupport(cx);
case JSProto_ReadableStream:
case JSProto_ReadableStreamDefaultReader:
case JSProto_ReadableStreamBYOBReader:
case JSProto_ReadableStreamDefaultController:
case JSProto_ReadableByteStreamController:
case JSProto_ReadableStreamBYOBRequest:
case JSProto_ByteLengthQueuingStrategy:
case JSProto_CountQueuingStrategy:
return !cx->options().streams();
#ifdef ENABLE_SHARED_ARRAY_BUFFER
case JSProto_Atomics:
case JSProto_SharedArrayBuffer:

View file

@ -156,6 +156,12 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
startAsyncTaskCallback(nullptr),
finishAsyncTaskCallback(nullptr),
promiseTasksToDestroy(mutexid::PromiseTaskPtrVector),
readableStreamDataRequestCallback(nullptr),
readableStreamWriteIntoReadRequestCallback(nullptr),
readableStreamCancelCallback(nullptr),
readableStreamClosedCallback(nullptr),
readableStreamErroredCallback(nullptr),
readableStreamFinalizeCallback(nullptr),
exclusiveAccessLock(mutexid::RuntimeExclusiveAccess),
#ifdef DEBUG
mainThreadHasExclusiveAccess(false),

View file

@ -655,6 +655,13 @@ struct JSRuntime : public JS::shadow::Runtime,
JS::FinishAsyncTaskCallback finishAsyncTaskCallback;
js::ExclusiveData<js::PromiseTaskPtrVector> promiseTasksToDestroy;
JS::RequestReadableStreamDataCallback readableStreamDataRequestCallback;
JS::WriteIntoReadRequestBufferCallback readableStreamWriteIntoReadRequestCallback;
JS::CancelReadableStreamCallback readableStreamCancelCallback;
JS::ReadableStreamClosedCallback readableStreamClosedCallback;
JS::ReadableStreamErroredCallback readableStreamErroredCallback;
JS::ReadableStreamFinalizeCallback readableStreamFinalizeCallback;
private:
/*
* Lock taken when using per-runtime or per-zone data that could otherwise

View file

@ -36,6 +36,7 @@
#include "builtin/Promise.h"
#include "builtin/Reflect.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/Stream.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
@ -71,7 +72,6 @@ using JS::AutoCheckCannotGC;
using mozilla::IsInRange;
using mozilla::Maybe;
using mozilla::PodMove;
using mozilla::Maybe;
static void
selfHosting_WarningReporter(JSContext* cx, JSErrorReport* report)
@ -1098,9 +1098,9 @@ static bool
intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 5);
MOZ_ASSERT(args.length() == 6);
bool isWrapped = args[4].toBoolean();
bool isWrapped = args[5].toBoolean();
Rooted<T*> toBuffer(cx);
if (!isWrapped) {
toBuffer = &args[0].toObject().as<T>();
@ -1114,11 +1114,12 @@ intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp)
}
toBuffer = toBufferObj.as<T>();
}
Rooted<T*> fromBuffer(cx, &args[1].toObject().as<T>());
uint32_t fromIndex = uint32_t(args[2].toInt32());
uint32_t count = uint32_t(args[3].toInt32());
uint32_t toIndex = uint32_t(args[1].toInt32());
Rooted<T*> fromBuffer(cx, &args[2].toObject().as<T>());
uint32_t fromIndex = uint32_t(args[3].toInt32());
uint32_t count = uint32_t(args[4].toInt32());
T::copyData(toBuffer, fromBuffer, fromIndex, count);
T::copyData(toBuffer, toIndex, fromBuffer, fromIndex, count);
args.rval().setUndefined();
return true;
@ -2426,14 +2427,14 @@ static const JSFunctionSpec intrinsic_functions[] = {
intrinsic_PossiblyWrappedArrayBufferByteLength<ArrayBufferObject>, 1,0,
IntrinsicPossiblyWrappedArrayBufferByteLength),
JS_FN("ArrayBufferCopyData",
intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 5,0),
intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 6,0),
JS_FN("SharedArrayBufferByteLength",
intrinsic_ArrayBufferByteLength<SharedArrayBufferObject>, 1,0),
JS_FN("PossiblyWrappedSharedArrayBufferByteLength",
intrinsic_PossiblyWrappedArrayBufferByteLength<SharedArrayBufferObject>, 1,0),
JS_FN("SharedArrayBufferCopyData",
intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 5,0),
intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 6,0),
JS_FN("IsUint8TypedArray", intrinsic_IsUint8TypedArray, 1,0),
JS_FN("IsInt8TypedArray", intrinsic_IsInt8TypedArray, 1,0),
@ -2489,6 +2490,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("CallWeakSetMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
JS_FN("IsReadableStreamBYOBRequest",
intrinsic_IsInstanceOfBuiltin<ReadableStreamBYOBRequest>, 1, 0),
// See builtin/TypedObject.h for descriptors of the typedobj functions.
JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0),
JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0),

View file

@ -331,15 +331,16 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi
}
/* static */ void
SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer,
Handle<SharedArrayBufferObject*> fromBuffer,
uint32_t fromIndex, uint32_t count)
SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex,
Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex,
uint32_t count)
{
MOZ_ASSERT(toBuffer->byteLength() >= count);
MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared(),
jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared() + toIndex,
fromBuffer->dataPointerShared() + fromIndex,
count);
}

View file

@ -146,9 +146,9 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
JS::ClassInfo* info);
static void copyData(Handle<SharedArrayBufferObject*> toBuffer,
Handle<SharedArrayBufferObject*> fromBuffer,
uint32_t fromIndex, uint32_t count);
static void copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex,
Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex,
uint32_t count);
SharedArrayRawBuffer* rawBufferObject() const;

View file

@ -1438,6 +1438,8 @@ ReloadPrefsCallback(const char* pref, void* data)
bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror");
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
@ -1463,7 +1465,8 @@ ReloadPrefsCallback(const char* pref, void* data)
.setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
.setWerror(werror)
.setExtraWarnings(extraWarnings)
.setArrayProtoValues(arrayProtoValues);
.setArrayProtoValues(arrayProtoValues)
.setStreams(streams);
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);

View file

@ -1331,6 +1331,9 @@ pref("javascript.options.main_thread_stack_quota_cap", 2097152);
// Dynamic module import.
pref("javascript.options.dynamicImport", true);
// Streams API
pref("javascript.options.streams", true);
// advanced prefs
pref("advanced.mailftp", false);
pref("image.animation_mode", "normal");
@ -4589,6 +4592,9 @@ pref("layout.animated-image-layers.enabled", false);
// Abort API
pref("dom.abortController.enabled", true);
// Streams API
pref("dom.streams.enabled", true);
// Push
pref("dom.push.enabled", false);
pref("dom.push.loglevel", "error");