Bug 1039846 - Request implementation. r=baku

--HG--
extra : transplant_source : %E6%A1%EB%5C3%95%8A%B4%3F%5C%D8%28%B2%98%5E%A1%C2%E5%1C%EE
This commit is contained in:
Nikhil Marathe 2014-09-23 22:03:20 -07:00
Родитель 1d8716d7ba
Коммит 68bb3e1d0c
14 изменённых файлов: 1206 добавлений и 71 удалений

Просмотреть файл

@ -60,3 +60,5 @@ MSG_DEF(MSG_INVALID_HEADER_VALUE, 1, "{0} is an invalid header value.")
MSG_DEF(MSG_INVALID_HEADER_SEQUENCE, 0, "Headers require name/value tuples when being initialized by a sequence.")
MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, "Permission denied to pass cross-origin object as {0}.")
MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, "Missing required {0}.")
MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, "Invalid request method {0}.")
MSG_DEF(MSG_REQUEST_BODY_CONSUMED_ERROR, 0, "Request body has already been consumed.")

100
dom/fetch/Fetch.cpp Normal file
Просмотреть файл

@ -0,0 +1,100 @@
/* -*- Mode: C++; tab-width: 2; 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 "Fetch.h"
#include "nsIStringStream.h"
#include "nsIUnicodeEncoder.h"
#include "nsStringStream.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/URLSearchParams.h"
namespace mozilla {
namespace dom {
nsresult
ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentType)
{
MOZ_ASSERT(aStream);
nsresult rv;
nsCOMPtr<nsIInputStream> byteStream;
if (aBodyInit.IsArrayBuffer()) {
const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
buf.ComputeLengthAndData();
//XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
rv = NS_NewByteInputStream(getter_AddRefs(byteStream),
reinterpret_cast<char*>(buf.Data()),
buf.Length(), NS_ASSIGNMENT_COPY);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else if (aBodyInit.IsArrayBufferView()) {
const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
buf.ComputeLengthAndData();
//XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
rv = NS_NewByteInputStream(getter_AddRefs(byteStream),
reinterpret_cast<char*>(buf.Data()),
buf.Length(), NS_ASSIGNMENT_COPY);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
} else if (aBodyInit.IsScalarValueString()) {
nsString str = aBodyInit.GetAsScalarValueString();
nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
if (!encoder) {
return NS_ERROR_OUT_OF_MEMORY;
}
int32_t destBufferLen;
rv = encoder->GetMaxLength(str.get(), str.Length(), &destBufferLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString encoded;
if (!encoded.SetCapacity(destBufferLen, fallible_t())) {
return NS_ERROR_OUT_OF_MEMORY;
}
char* destBuffer = encoded.BeginWriting();
int32_t srcLen = (int32_t) str.Length();
int32_t outLen = destBufferLen;
rv = encoder->Convert(str.get(), &srcLen, destBuffer, &outLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(outLen <= destBufferLen);
encoded.SetLength(outLen);
rv = NS_NewCStringInputStream(getter_AddRefs(byteStream), encoded);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8");
} else if (aBodyInit.IsURLSearchParams()) {
URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
nsString serialized;
params.Stringify(serialized);
rv = NS_NewStringInputStream(getter_AddRefs(byteStream), serialized);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
}
MOZ_ASSERT(byteStream);
byteStream.forget(aStream);
return NS_OK;
}
} // namespace dom
} // namespace mozilla

29
dom/fetch/Fetch.h Normal file
Просмотреть файл

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 2; 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_Fetch_h
#define mozilla_dom_Fetch_h
#include "mozilla/dom/UnionTypes.h"
class nsIInputStream;
namespace mozilla {
namespace dom {
/*
* 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 OwningArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& aBodyInit,
nsIInputStream** aStream,
nsCString& aContentType);
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_Fetch_h

Просмотреть файл

@ -7,7 +7,6 @@
#include "mozilla/dom/Headers.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/Preferences.h"
@ -83,6 +82,39 @@ Headers::Constructor(const GlobalObject& aGlobal,
return headers.forget();
}
// static
already_AddRefed<Headers>
Headers::Constructor(const GlobalObject& aGlobal,
const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
ErrorResult& aRv)
{
nsRefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports());
if (aInit.IsHeaders()) {
headers->Fill(aInit.GetAsHeaders(), aRv);
} else if (aInit.IsByteStringSequenceSequence()) {
headers->Fill(aInit.GetAsByteStringSequenceSequence(), aRv);
} else if (aInit.IsByteStringMozMap()) {
headers->Fill(aInit.GetAsByteStringMozMap(), aRv);
}
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return headers.forget();
}
Headers::Headers(const Headers& aOther)
: mOwner(aOther.mOwner)
, mGuard(aOther.mGuard)
{
SetIsDOMBinding();
ErrorResult result;
Fill(aOther, result);
MOZ_ASSERT(!result.Failed());
}
void
Headers::Append(const nsACString& aName, const nsACString& aValue,
ErrorResult& aRv)
@ -202,6 +234,12 @@ Headers::Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv
}
}
void
Headers::Clear()
{
mList.Clear();
}
void
Headers::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv)
{
@ -328,6 +366,5 @@ Headers::Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv)
Append(NS_ConvertUTF16toUTF8(keys[i]), aInit.Get(keys[i]), aRv);
}
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -8,6 +8,8 @@
#define mozilla_dom_Headers_h
#include "mozilla/dom/HeadersBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "nsClassHashtable.h"
#include "nsWrapperCache.h"
@ -42,7 +44,7 @@ private:
nsCString mValue;
};
nsRefPtr<nsISupports> mOwner;
nsCOMPtr<nsISupports> mOwner;
HeadersGuardEnum mGuard;
nsTArray<Entry> mList;
@ -54,6 +56,8 @@ public:
SetIsDOMBinding();
}
explicit Headers(const Headers& aOther);
static bool PrefEnabled(JSContext* cx, JSObject* obj);
static already_AddRefed<Headers>
@ -61,6 +65,11 @@ public:
const Optional<HeadersOrByteStringSequenceSequenceOrByteStringMozMap>& aInit,
ErrorResult& aRv);
static already_AddRefed<Headers>
Constructor(const GlobalObject& aGlobal,
const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit,
ErrorResult& aRv);
void Append(const nsACString& aName, const nsACString& aValue,
ErrorResult& aRv);
void Delete(const nsACString& aName, ErrorResult& aRv);
@ -70,6 +79,8 @@ public:
bool Has(const nsACString& aName, ErrorResult& aRv) const;
void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv);
void Clear();
// ChromeOnly
HeadersGuardEnum Guard() const { return mGuard; }
void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv);
@ -77,8 +88,13 @@ public:
virtual JSObject* WrapObject(JSContext* aCx);
nsISupports* GetParentObject() const { return mOwner; }
void Fill(const Headers& aInit, ErrorResult& aRv);
private:
Headers(const Headers& aOther) MOZ_DELETE;
// Since Headers is also an nsISupports, the above constructor can
// accidentally be invoked as new Headers(Headers*[, implied None guard]) when
// the intention is to use the copy constructor. Explicitly disallow it.
Headers(Headers* aOther) MOZ_DELETE;
virtual ~Headers();
static bool IsSimpleHeader(const nsACString& aName,
@ -103,7 +119,6 @@ private:
IsForbiddenResponseHeader(aName);
}
void Fill(const Headers& aInit, ErrorResult& aRv);
void Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv);
void Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv);
};

Просмотреть файл

@ -0,0 +1,60 @@
/* -*- Mode: C++; tab-width: 2; 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 "InternalRequest.h"
#include "nsIContentPolicy.h"
#include "nsIDocument.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/workers/Workers.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
// The global is used to extract the principal.
already_AddRefed<InternalRequest>
InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const
{
nsRefPtr<InternalRequest> copy = new InternalRequest();
copy->mURL.Assign(mURL);
copy->SetMethod(mMethod);
copy->mHeaders = new Headers(*mHeaders);
copy->mBodyStream = mBodyStream;
copy->mPreserveContentCodings = true;
if (NS_IsMainThread()) {
nsIPrincipal* principal = aGlobal->PrincipalOrNull();
MOZ_ASSERT(principal);
aRv = nsContentUtils::GetASCIIOrigin(principal, copy->mOrigin);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
} else {
workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->AssertIsOnWorkerThread();
workers::WorkerPrivate::LocationInfo& location = worker->GetLocationInfo();
copy->mOrigin = NS_ConvertUTF16toUTF8(location.mOrigin);
}
copy->mMode = mMode;
copy->mCredentialsMode = mCredentialsMode;
// FIXME(nsm): Add ContentType fetch to nsIContentPolicy and friends.
// Then set copy's mContext to that.
return copy.forget();
}
InternalRequest::~InternalRequest()
{
}
} // namespace dom
} // namespace mozilla

264
dom/fetch/InternalRequest.h Normal file
Просмотреть файл

@ -0,0 +1,264 @@
/* -*- Mode: C++; tab-width: 2; 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_InternalRequest_h
#define mozilla_dom_InternalRequest_h
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "nsIContentPolicy.h"
#include "nsIInputStream.h"
#include "nsISupportsImpl.h"
class nsIDocument;
class nsPIDOMWindow;
namespace mozilla {
namespace dom {
class FetchBodyStream;
class Request;
class InternalRequest MOZ_FINAL
{
friend class Request;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest)
enum ContextFrameType
{
FRAMETYPE_AUXILIARY = 0,
FRAMETYPE_TOP_LEVEL,
FRAMETYPE_NESTED,
FRAMETYPE_NONE,
};
// Since referrer type can be none, client or a URL.
enum ReferrerType
{
REFERRER_NONE = 0,
REFERRER_CLIENT,
REFERRER_URL,
};
enum ResponseTainting
{
RESPONSETAINT_BASIC,
RESPONSETAINT_CORS,
RESPONSETAINT_OPAQUE,
};
explicit InternalRequest()
: mMethod("GET")
, mHeaders(new Headers(nullptr, HeadersGuardEnum::None))
, mContextFrameType(FRAMETYPE_NONE)
, mReferrerType(REFERRER_CLIENT)
, mMode(RequestMode::No_cors)
, mCredentialsMode(RequestCredentials::Omit)
, mResponseTainting(RESPONSETAINT_BASIC)
, mRedirectCount(0)
, mAuthenticationFlag(false)
, mForceOriginHeader(false)
, mManualRedirect(false)
, mPreserveContentCodings(false)
, mSameOriginDataURL(false)
, mSkipServiceWorker(false)
, mSynchronous(false)
, mUnsafeRequest(false)
, mUseURLCredentials(false)
{
}
explicit InternalRequest(const InternalRequest& aOther)
: mMethod(aOther.mMethod)
, mURL(aOther.mURL)
, mHeaders(aOther.mHeaders)
, mBodyStream(aOther.mBodyStream)
, mContext(aOther.mContext)
, mOrigin(aOther.mOrigin)
, mContextFrameType(aOther.mContextFrameType)
, mReferrerType(aOther.mReferrerType)
, mReferrerURL(aOther.mReferrerURL)
, mMode(aOther.mMode)
, mCredentialsMode(aOther.mCredentialsMode)
, mResponseTainting(aOther.mResponseTainting)
, mRedirectCount(aOther.mRedirectCount)
, mAuthenticationFlag(aOther.mAuthenticationFlag)
, mForceOriginHeader(aOther.mForceOriginHeader)
, mManualRedirect(aOther.mManualRedirect)
, mPreserveContentCodings(aOther.mPreserveContentCodings)
, mSameOriginDataURL(aOther.mSameOriginDataURL)
, mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs)
, mSkipServiceWorker(aOther.mSkipServiceWorker)
, mSynchronous(aOther.mSynchronous)
, mUnsafeRequest(aOther.mUnsafeRequest)
, mUseURLCredentials(aOther.mUseURLCredentials)
{
}
void
GetMethod(nsCString& aMethod) const
{
aMethod.Assign(mMethod);
}
void
SetMethod(const nsACString& aMethod)
{
mMethod.Assign(aMethod);
}
void
GetURL(nsCString& aURL) const
{
aURL.Assign(mURL);
}
bool
ReferrerIsNone() const
{
return mReferrerType == REFERRER_NONE;
}
bool
ReferrerIsURL() const
{
return mReferrerType == REFERRER_URL;
}
bool
ReferrerIsClient() const
{
return mReferrerType == REFERRER_CLIENT;
}
nsCString
ReferrerAsURL() const
{
MOZ_ASSERT(ReferrerIsURL());
return mReferrerURL;
}
void
SetReferrer(const nsACString& aReferrer)
{
// May be removed later.
MOZ_ASSERT(!ReferrerIsNone());
mReferrerType = REFERRER_URL;
mReferrerURL.Assign(aReferrer);
}
bool
IsSynchronous() const
{
return mSynchronous;
}
void
SetMode(RequestMode aMode)
{
mMode = aMode;
}
void
SetCredentialsMode(RequestCredentials aCredentialsMode)
{
mCredentialsMode = aCredentialsMode;
}
nsContentPolicyType
GetContext() const
{
return mContext;
}
Headers*
Headers_()
{
return mHeaders;
}
bool
ForceOriginHeader()
{
return mForceOriginHeader;
}
void
GetOrigin(nsCString& aOrigin) const
{
aOrigin.Assign(mOrigin);
}
void
SetBody(nsIInputStream* aStream)
{
mBodyStream = aStream;
}
// Will return the original stream!
// Use a tee or copy if you don't want to erase the original.
void
GetBody(nsIInputStream** aStream)
{
nsCOMPtr<nsIInputStream> s = mBodyStream;
s.forget(aStream);
}
// The global is used as the client for the new object.
already_AddRefed<InternalRequest>
GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const;
private:
~InternalRequest();
void
SetURL(const nsACString& aURL)
{
mURL.Assign(aURL);
}
nsCString mMethod;
nsCString mURL;
nsRefPtr<Headers> mHeaders;
nsCOMPtr<nsIInputStream> mBodyStream;
// nsContentPolicyType does not cover the complete set defined in the spec,
// but it is a good start.
nsContentPolicyType mContext;
nsCString mOrigin;
ContextFrameType mContextFrameType;
ReferrerType mReferrerType;
// When mReferrerType is REFERRER_URL.
nsCString mReferrerURL;
RequestMode mMode;
RequestCredentials mCredentialsMode;
ResponseTainting mResponseTainting;
uint32_t mRedirectCount;
bool mAuthenticationFlag;
bool mForceOriginHeader;
bool mManualRedirect;
bool mPreserveContentCodings;
bool mSameOriginDataURL;
bool mSandboxedStorageAreaURLs;
bool mSkipServiceWorker;
bool mSynchronous;
bool mUnsafeRequest;
bool mUseURLCredentials;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_InternalRequest_h

Просмотреть файл

@ -5,30 +5,44 @@
#include "Request.h"
#include "nsDOMString.h"
#include "nsISupportsImpl.h"
#include "nsIUnicodeDecoder.h"
#include "nsIURI.h"
#include "nsDOMFile.h"
#include "nsDOMString.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/URL.h"
#include "mozilla/dom/workers/bindings/URL.h"
// dom/workers
#include "File.h"
#include "WorkerPrivate.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner, mHeaders)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
Request::Request(nsISupports* aOwner)
Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest)
: mOwner(aOwner)
, mHeaders(new Headers(aOwner))
, mRequest(aRequest)
, mBodyUsed(false)
{
SetIsDOMBinding();
}
@ -37,82 +51,393 @@ Request::~Request()
{
}
/*static*/ already_AddRefed<Request>
Request::Constructor(const GlobalObject& global,
const RequestOrScalarValueString& aInput,
const RequestInit& aInit, ErrorResult& rv)
already_AddRefed<InternalRequest>
Request::GetInternalRequest()
{
nsRefPtr<Request> request = new Request(global.GetAsSupports());
return request.forget();
nsRefPtr<InternalRequest> r = mRequest;
return r.forget();
}
/*static*/ already_AddRefed<Request>
Request::Constructor(const GlobalObject& aGlobal,
const RequestOrScalarValueString& aInput,
const RequestInit& aInit, ErrorResult& aRv)
{
nsRefPtr<InternalRequest> request;
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (aInput.IsRequest()) {
nsRefPtr<Request> inputReq = &aInput.GetAsRequest();
if (inputReq->BodyUsed()) {
aRv.ThrowTypeError(MSG_REQUEST_BODY_CONSUMED_ERROR);
return nullptr;
}
inputReq->SetBodyUsed();
request = inputReq->GetInternalRequest();
} else {
request = new InternalRequest();
}
request = request->GetRequestConstructorCopy(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RequestMode fallbackMode = RequestMode::EndGuard_;
RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
if (aInput.IsScalarValueString()) {
nsString input;
input.Assign(aInput.GetAsScalarValueString());
nsString requestURL;
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
MOZ_ASSERT(window);
nsCOMPtr<nsIURI> docURI = window->GetDocumentURI();
nsCString spec;
aRv = docURI->GetSpec(spec);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsRefPtr<mozilla::dom::URL> url =
dom::URL::Constructor(aGlobal, input, NS_ConvertUTF8toUTF16(spec), aRv);
if (aRv.Failed()) {
return nullptr;
}
url->Stringify(requestURL, aRv);
if (aRv.Failed()) {
return nullptr;
}
} else {
workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->AssertIsOnWorkerThread();
nsString baseURL = NS_ConvertUTF8toUTF16(worker->GetLocationInfo().mHref);
nsRefPtr<workers::URL> url =
workers::URL::Constructor(aGlobal, input, baseURL, aRv);
if (aRv.Failed()) {
return nullptr;
}
url->Stringify(requestURL, aRv);
if (aRv.Failed()) {
return nullptr;
}
}
request->SetURL(NS_ConvertUTF16toUTF8(requestURL));
fallbackMode = RequestMode::Cors;
fallbackCredentials = RequestCredentials::Omit;
}
RequestMode mode = aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode;
RequestCredentials credentials =
aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value()
: fallbackCredentials;
if (mode != RequestMode::EndGuard_) {
request->SetMode(mode);
}
if (credentials != RequestCredentials::EndGuard_) {
request->SetCredentialsMode(credentials);
}
if (aInit.mMethod.WasPassed()) {
nsCString method = aInit.mMethod.Value();
ToLowerCase(method);
if (!method.EqualsASCII("options") &&
!method.EqualsASCII("get") &&
!method.EqualsASCII("head") &&
!method.EqualsASCII("post") &&
!method.EqualsASCII("put") &&
!method.EqualsASCII("delete")) {
NS_ConvertUTF8toUTF16 label(method);
aRv.ThrowTypeError(MSG_INVALID_REQUEST_METHOD, &label);
return nullptr;
}
ToUpperCase(method);
request->SetMethod(method);
}
nsRefPtr<Request> domRequest = new Request(global, request);
nsRefPtr<Headers> domRequestHeaders = domRequest->Headers_();
nsRefPtr<Headers> headers;
if (aInit.mHeaders.WasPassed()) {
headers = Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv);
if (aRv.Failed()) {
return nullptr;
}
} else {
headers = new Headers(*domRequestHeaders);
}
domRequestHeaders->Clear();
if (domRequest->Mode() == RequestMode::No_cors) {
nsCString method;
domRequest->GetMethod(method);
ToLowerCase(method);
if (!method.EqualsASCII("get") &&
!method.EqualsASCII("head") &&
!method.EqualsASCII("post")) {
NS_ConvertUTF8toUTF16 label(method);
aRv.ThrowTypeError(MSG_INVALID_REQUEST_METHOD, &label);
return nullptr;
}
domRequestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv);
if (aRv.Failed()) {
return nullptr;
}
}
domRequestHeaders->Fill(*headers, aRv);
if (aRv.Failed()) {
return nullptr;
}
if (aInit.mBody.WasPassed()) {
const OwningArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams& bodyInit = aInit.mBody.Value();
nsCOMPtr<nsIInputStream> stream;
nsCString contentType;
aRv = ExtractByteStreamFromBody(bodyInit,
getter_AddRefs(stream), contentType);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
request->SetBody(stream);
if (!contentType.IsVoid() &&
!domRequestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
domRequestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"),
contentType, aRv);
}
if (aRv.Failed()) {
return nullptr;
}
}
// Extract mime type.
nsTArray<nsCString> contentTypeValues;
domRequestHeaders->GetAll(NS_LITERAL_CSTRING("Content-Type"),
contentTypeValues, aRv);
if (aRv.Failed()) {
return nullptr;
}
// HTTP ABNF states Content-Type may have only one value.
// This is from the "parse a header value" of the fetch spec.
if (contentTypeValues.Length() == 1) {
domRequest->mMimeType = contentTypeValues[0];
ToLowerCase(domRequest->mMimeType);
}
return domRequest.forget();
}
already_AddRefed<Request>
Request::Clone() const
{
nsRefPtr<Request> request = new Request(mOwner);
// FIXME(nsm): Bug 1073231. This is incorrect, but the clone method isn't
// well defined yet.
nsRefPtr<Request> request = new Request(mOwner,
new InternalRequest(*mRequest));
return request.forget();
}
namespace {
nsresult
DecodeUTF8(const nsCString& aBuffer, nsString& aDecoded)
{
nsCOMPtr<nsIUnicodeDecoder> decoder =
EncodingUtils::DecoderForEncoding("UTF-8");
if (!decoder) {
return NS_ERROR_FAILURE;
}
int32_t destBufferLen;
nsresult rv =
decoder->GetMaxLength(aBuffer.get(), aBuffer.Length(), &destBufferLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!aDecoded.SetCapacity(destBufferLen, fallible_t())) {
return NS_ERROR_OUT_OF_MEMORY;
}
char16_t* destBuffer = aDecoded.BeginWriting();
int32_t srcLen = (int32_t) aBuffer.Length();
int32_t outLen = destBufferLen;
rv = decoder->Convert(aBuffer.get(), &srcLen, destBuffer, &outLen);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(outLen <= destBufferLen);
aDecoded.SetLength(outLen);
return NS_OK;
}
}
already_AddRefed<Promise>
Request::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
{
nsRefPtr<Promise> promise = Promise::Create(mOwner, aRv);
if (aRv.Failed()) {
return nullptr;
}
if (BodyUsed()) {
aRv.ThrowTypeError(MSG_REQUEST_BODY_CONSUMED_ERROR);
return nullptr;
}
SetBodyUsed();
// While the spec says to do this asynchronously, all the body constructors
// right now only accept bodies whose streams are backed by an in-memory
// buffer that can be read without blocking. So I think this is fine.
nsCOMPtr<nsIInputStream> stream;
mRequest->GetBody(getter_AddRefs(stream));
if (!stream) {
aRv = NS_NewByteInputStream(getter_AddRefs(stream), "", 0,
NS_ASSIGNMENT_COPY);
if (aRv.Failed()) {
return nullptr;
}
}
AutoJSAPI api;
api.Init(mOwner);
JSContext* cx = api.cx();
// We can make this assertion because for now we only support memory backed
// structures for the body argument for a Request.
MOZ_ASSERT(NS_InputStreamIsBuffered(stream));
nsCString buffer;
uint64_t len;
aRv = stream->Available(&len);
if (aRv.Failed()) {
return nullptr;
}
aRv = NS_ReadInputStreamToString(stream, buffer, len);
if (aRv.Failed()) {
return nullptr;
}
buffer.SetLength(len);
switch (aType) {
case CONSUME_ARRAYBUFFER: {
JS::Rooted<JSObject*> arrayBuffer(cx);
arrayBuffer =
ArrayBuffer::Create(cx, buffer.Length(),
reinterpret_cast<const uint8_t*>(buffer.get()));
JS::Rooted<JS::Value> val(cx);
val.setObjectOrNull(arrayBuffer);
promise->MaybeResolve(cx, val);
return promise.forget();
}
case CONSUME_BLOB: {
// XXXnsm it is actually possible to avoid these duplicate allocations
// for the Blob case by having the Blob adopt the stream's memory
// directly, but I've not added a special case for now.
//
// This is similar to nsContentUtils::CreateBlobBuffer, but also deals
// with worker wrapping.
uint32_t blobLen = buffer.Length();
void* blobData = moz_malloc(blobLen);
nsCOMPtr<nsIDOMBlob> blob;
if (blobData) {
memcpy(blobData, buffer.BeginReading(), blobLen);
blob = DOMFile::CreateMemoryFile(blobData, blobLen,
NS_ConvertUTF8toUTF16(mMimeType));
} else {
aRv = NS_ERROR_OUT_OF_MEMORY;
return nullptr;
}
JS::Rooted<JS::Value> jsBlob(cx);
if (NS_IsMainThread()) {
aRv = nsContentUtils::WrapNative(cx, blob, &jsBlob);
if (aRv.Failed()) {
return nullptr;
}
} else {
jsBlob.setObject(*workers::file::CreateBlob(cx, blob));
}
promise->MaybeResolve(cx, jsBlob);
return promise.forget();
}
case CONSUME_JSON: {
nsString decoded;
aRv = DecodeUTF8(buffer, decoded);
if (aRv.Failed()) {
return nullptr;
}
JS::Rooted<JS::Value> json(cx);
if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
JS::Rooted<JS::Value> exn(cx);
if (JS_GetPendingException(cx, &exn)) {
JS_ClearPendingException(cx);
promise->MaybeReject(cx, exn);
}
}
promise->MaybeResolve(cx, json);
return promise.forget();
}
case CONSUME_TEXT: {
nsString decoded;
aRv = DecodeUTF8(buffer, decoded);
if (aRv.Failed()) {
return nullptr;
}
promise->MaybeResolve(decoded);
return promise.forget();
}
}
NS_NOTREACHED("Unexpected consume body type");
// Silence warnings.
return nullptr;
}
already_AddRefed<Promise>
Request::ArrayBuffer(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
MOZ_ASSERT(global);
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return promise.forget();
return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
}
already_AddRefed<Promise>
Request::Blob(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
MOZ_ASSERT(global);
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return promise.forget();
return ConsumeBody(CONSUME_BLOB, aRv);
}
already_AddRefed<Promise>
Request::Json(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
MOZ_ASSERT(global);
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return promise.forget();
return ConsumeBody(CONSUME_JSON, aRv);
}
already_AddRefed<Promise>
Request::Text(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
MOZ_ASSERT(global);
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
return promise.forget();
}
bool
Request::BodyUsed()
{
return false;
return ConsumeBody(CONSUME_TEXT, aRv);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -9,10 +9,12 @@
#include "nsISupportsImpl.h"
#include "nsWrapperCache.h"
#include "mozilla/dom/InternalRequest.h"
// Required here due to certain WebIDL enums/classes being declared in both
// files.
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/UnionTypes.h"
class nsPIDOMWindow;
namespace mozilla {
@ -28,7 +30,7 @@ class Request MOZ_FINAL : public nsISupports
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Request)
public:
Request(nsISupports* aOwner);
Request(nsIGlobalObject* aOwner, InternalRequest* aRequest);
JSObject*
WrapObject(JSContext* aCx)
@ -39,34 +41,40 @@ public:
void
GetUrl(DOMString& aUrl) const
{
aUrl.AsAString() = EmptyString();
aUrl.AsAString() = NS_ConvertUTF8toUTF16(mRequest->mURL);
}
void
GetMethod(nsCString& aMethod) const
{
aMethod = EmptyCString();
aMethod = mRequest->mMethod;
}
RequestMode
Mode() const
{
return RequestMode::Same_origin;
return mRequest->mMode;
}
RequestCredentials
Credentials() const
{
return RequestCredentials::Omit;
return mRequest->mCredentialsMode;
}
void
GetReferrer(DOMString& aReferrer) const
{
aReferrer.AsAString() = EmptyString();
if (mRequest->ReferrerIsNone()) {
aReferrer.AsAString() = EmptyString();
return;
}
// FIXME(nsm): Spec doesn't say what to do if referrer is client.
aReferrer.AsAString() = NS_ConvertUTF8toUTF16(mRequest->mReferrerURL);
}
Headers* Headers_() const { return mHeaders; }
Headers* Headers_() const { return mRequest->Headers_(); }
static already_AddRefed<Request>
Constructor(const GlobalObject& aGlobal, const RequestOrScalarValueString& aInput,
@ -93,12 +101,38 @@ public:
Text(ErrorResult& aRv);
bool
BodyUsed();
BodyUsed() const
{
return mBodyUsed;
}
already_AddRefed<InternalRequest>
GetInternalRequest();
private:
enum ConsumeType
{
CONSUME_ARRAYBUFFER,
CONSUME_BLOB,
// FormData not supported right now,
CONSUME_JSON,
CONSUME_TEXT,
};
~Request();
nsCOMPtr<nsISupports> mOwner;
nsRefPtr<Headers> mHeaders;
already_AddRefed<Promise>
ConsumeBody(ConsumeType aType, ErrorResult& aRv);
void
SetBodyUsed()
{
mBodyUsed = true;
}
nsCOMPtr<nsIGlobalObject> mOwner;
nsRefPtr<InternalRequest> mRequest;
bool mBodyUsed;
nsCString mMimeType;
};
} // namespace dom

Просмотреть файл

@ -5,13 +5,17 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom += [
'Fetch.h',
'Headers.h',
'InternalRequest.h',
'Request.h',
'Response.h',
]
UNIFIED_SOURCES += [
'Fetch.cpp',
'Headers.cpp',
'InternalRequest.cpp',
'Request.cpp',
'Response.cpp',
]

Просмотреть файл

@ -22,7 +22,7 @@
#include "nsString.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "StructuredCloneTags.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "Queue.h"
#include "WorkerFeature.h"

Просмотреть файл

@ -1,5 +1,7 @@
[DEFAULT]
support-files =
worker_interfaces.js
worker_test_request.js
[test_interfaces.html]
[test_request.html]

Просмотреть файл

@ -0,0 +1,48 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug XXXXXX - Test Request object in worker</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
function checkEnabled() {
var worker = new Worker("worker_test_request.js");
worker.onmessage = function(event) {
if (event.data.type == 'finish') {
SimpleTest.finish();
} else if (event.data.type == 'status') {
ok(event.data.status, event.data.msg);
}
}
worker.onerror = function(event) {
ok(false, "Worker had an error: " + event.message + " at " + event.lineno);
SimpleTest.finish();
};
worker.postMessage(true);
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.fetch.enabled", true]
]}, function() {
checkEnabled();
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,215 @@
function ok(a, msg) {
dump("OK: " + !!a + " => " + a + " " + msg + "\n");
postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
}
function is(a, b, msg) {
dump("IS: " + (a===b) + " => " + a + " | " + b + " " + msg + "\n");
postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
}
function testDefaultCtor() {
var req = new Request("");
is(req.method, "GET", "Default Request method is GET");
ok(req.headers instanceof Headers, "Request should have non-null Headers object");
is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
is(req.referrer, "", "Default referrer is `client` which serializes to empty string.");
is(req.mode, "cors", "Request mode for string input is cors");
is(req.credentials, "omit", "Default Request credentials is omit");
var req = new Request(req);
is(req.method, "GET", "Default Request method is GET");
ok(req.headers instanceof Headers, "Request should have non-null Headers object");
is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
is(req.referrer, "", "Default referrer is `client` which serializes to empty string.");
is(req.mode, "cors", "Request mode string input is cors");
is(req.credentials, "omit", "Default Request credentials is omit");
}
function testClone() {
var req = (new Request("./cloned_request.txt", {
method: 'POST',
headers: { "Content-Length": 5 },
body: "Sample body",
mode: "same-origin",
credentials: "same-origin",
})).clone();
ok(req.method === "POST", "Request method is POST");
ok(req.headers instanceof Headers, "Request should have non-null Headers object");
is(req.headers.get('content-length'), "5", "Request content-length should be 5.");
ok(req.url === (new URL("./cloned_request.txt", self.location.href)).href,
"URL should be resolved with entry settings object's API base URL");
ok(req.referrer === "", "Default referrer is `client` which serializes to empty string.");
ok(req.mode === "same-origin", "Request mode is same-origin");
ok(req.credentials === "same-origin", "Default credentials is same-origin");
}
function testUsedRequest() {
// Passing a used request should fail.
var req = new Request("", { body: "This is foo" });
var p1 = req.text().then(function(v) {
try {
var req2 = new Request(req);
ok(false, "Used Request cannot be passed to new Request");
} catch(e) {
ok(true, "Used Request cannot be passed to new Request");
}
});
// Passing a request should set the request as used.
var reqA = new Request("", { body: "This is foo" });
var reqB = new Request(reqA);
is(reqA.bodyUsed, true, "Passing a Request to another Request should set the former as used");
return p1;
}
function testSimpleUrlParse() {
// Just checks that the URL parser is actually being used.
var req = new Request("/file.html");
is(req.url, (new URL("/file.html", self.location.href)).href, "URL parser should be used to resolve Request URL");
}
function testMethod() {
var allowed = ["delete", "get", "head", "options", "post", "put"];
for (var i = 0; i < allowed.length; ++i) {
try {
var r = new Request("", { method: allowed[i] });
ok(true, "Method " + allowed[i] + " should be allowed");
} catch(e) {
ok(false, "Method " + allowed[i] + " should be allowed");
}
}
var forbidden = ["aardvark", "connect", "trace", "track"];
for (var i = 0; i < forbidden.length; ++i) {
try {
var r = new Request("", { method: forbidden[i] });
ok(false, "Method " + forbidden[i] + " should be forbidden");
} catch(e) {
ok(true, "Method " + forbidden[i] + " should be forbidden");
}
}
var allowedNoCors = ["get", "head", "post"];
for (var i = 0; i < allowedNoCors.length; ++i) {
try {
var r = new Request("", { method: allowedNoCors[i], mode: "no-cors" });
ok(true, "Method " + allowedNoCors[i] + " should be allowed in no-cors mode");
} catch(e) {
ok(false, "Method " + allowedNoCors[i] + " should be allowed in no-cors mode");
}
}
var forbiddenNoCors = ["aardvark", "delete", "options", "put"];
for (var i = 0; i < forbiddenNoCors.length; ++i) {
try {
var r = new Request("", { method: forbiddenNoCors[i], mode: "no-cors" });
ok(false, "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode");
} catch(e) {
ok(true, "Method " + forbiddenNoCors[i] + " should be forbidden in no-cors mode");
}
}
}
function testUrlFragment() {
var req = new Request("./request#withfragment");
ok(req.url, (new URL("./request", self.location.href)).href, "request.url should be serialized with exclude fragment flag set");
}
function testBodyUsed() {
var req = new Request("./bodyused", { body: "Sample body" });
is(req.bodyUsed, false, "bodyUsed is initially false.");
return req.text().then((v) => {
is(v, "Sample body", "Body should match");
is(req.bodyUsed, true, "After reading body, bodyUsed should be true.");
}).then((v) => {
return req.blob().then((v) => {
ok(false, "Attempting to read body again should fail.");
}, (e) => {
ok(true, "Attempting to read body again should fail.");
})
});
}
// FIXME(nsm): Bug 1071290: We can't use Blobs as the body yet.
function testBodyCreation() {
var text = "κόσμε";
var req1 = new Request("", { body: text });
var p1 = req1.text().then(function(v) {
ok(typeof v === "string", "Should resolve to string");
is(text, v, "Extracted string should match");
});
var req2 = new Request("", { body: new Uint8Array([72, 101, 108, 108, 111]) });
var p2 = req2.text().then(function(v) {
is("Hello", v, "Extracted string should match");
});
var req2b = new Request("", { body: (new Uint8Array([72, 101, 108, 108, 111])).buffer });
var p2b = req2b.text().then(function(v) {
is("Hello", v, "Extracted string should match");
});
var params = new URLSearchParams();
params.append("item", "Geckos");
params.append("feature", "stickyfeet");
params.append("quantity", "700");
var req3 = new Request("", { body: params });
var p3 = req3.text().then(function(v) {
var extracted = new URLSearchParams(v);
is(extracted.get("item"), "Geckos", "Param should match");
is(extracted.get("feature"), "stickyfeet", "Param should match");
is(extracted.get("quantity"), "700", "Param should match");
});
return Promise.all([p1, p2, p2b, p3]);
}
function testBodyExtraction() {
var text = "κόσμε";
var newReq = function() { return new Request("", { body: text }); }
return newReq().text().then(function(v) {
ok(typeof v === "string", "Should resolve to string");
is(text, v, "Extracted string should match");
}).then(function() {
return newReq().blob().then(function(v) {
ok(v instanceof Blob, "Should resolve to Blob");
var fs = new FileReaderSync();
is(fs.readAsText(v), text, "Decoded Blob should match original");
});
}).then(function() {
return newReq().json().then(function(v) {
ok(false, "Invalid json should reject");
}, function(e) {
ok(true, "Invalid json should reject");
})
}).then(function() {
return newReq().arrayBuffer().then(function(v) {
ok(v instanceof ArrayBuffer, "Should resolve to ArrayBuffer");
var dec = new TextDecoder();
is(dec.decode(new Uint8Array(v)), text, "UTF-8 decoded ArrayBuffer should match original");
});
})
}
onmessage = function() {
var done = function() { postMessage({ type: 'finish' }) }
testDefaultCtor();
testClone();
testSimpleUrlParse();
testUrlFragment();
testMethod();
Promise.resolve()
.then(testBodyCreation)
.then(testBodyUsed)
.then(testBodyExtraction)
.then(testUsedRequest)
// Put more promise based tests here.
.then(done)
.catch(function(e) {
ok(false, "Some Request tests failed " + e);
done();
})
}