Bug 1073231 Implement Request and Response Clone() methods. r=nsm r=baku

This commit is contained in:
Ben Kelly 2015-02-19 20:24:24 -05:00
parent b880944866
commit 3869f5c2b9
15 changed files with 230 additions and 60 deletions

View File

@ -62,7 +62,7 @@ MSG_DEF(MSG_INVALID_HEADER_SEQUENCE, 0, JSEXN_TYPEERR, "Headers require name/val
MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, JSEXN_TYPEERR, "Permission denied to pass cross-origin object as {0}.") MSG_DEF(MSG_PERMISSION_DENIED_TO_PASS_ARG, 1, JSEXN_TYPEERR, "Permission denied to pass cross-origin object as {0}.")
MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, JSEXN_TYPEERR, "Missing required {0}.") MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, JSEXN_TYPEERR, "Missing required {0}.")
MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, JSEXN_TYPEERR, "Invalid request method {0}.") MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, JSEXN_TYPEERR, "Invalid request method {0}.")
MSG_DEF(MSG_REQUEST_BODY_CONSUMED_ERROR, 0, JSEXN_TYPEERR, "Request body has already been consumed.") MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 0, JSEXN_TYPEERR, "Body has already been consumed.")
MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, JSEXN_TYPEERR, "Response statusText may not contain newline or carriage return.") MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, JSEXN_TYPEERR, "Response statusText may not contain newline or carriage return.")
MSG_DEF(MSG_FETCH_FAILED, 0, JSEXN_TYPEERR, "NetworkError when attempting to fetch resource.") MSG_DEF(MSG_FETCH_FAILED, 0, JSEXN_TYPEERR, "NetworkError when attempting to fetch resource.")
MSG_DEF(MSG_NO_BODY_ALLOWED_FOR_GET_AND_HEAD, 0, JSEXN_TYPEERR, "HEAD or GET Request cannot have a body.") MSG_DEF(MSG_NO_BODY_ALLOWED_FOR_GET_AND_HEAD, 0, JSEXN_TYPEERR, "HEAD or GET Request cannot have a body.")

View File

@ -1164,7 +1164,7 @@ FetchBody<Derived>::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
{ {
mConsumeType = aType; mConsumeType = aType;
if (BodyUsed()) { if (BodyUsed()) {
aRv.ThrowTypeError(MSG_REQUEST_BODY_CONSUMED_ERROR); aRv.ThrowTypeError(MSG_FETCH_BODY_CONSUMED_ERROR);
return nullptr; return nullptr;
} }

View File

@ -99,7 +99,7 @@ template <class Derived>
class FetchBody { class FetchBody {
public: public:
bool bool
BodyUsed() { return mBodyUsed; } BodyUsed() const { return mBodyUsed; }
already_AddRefed<Promise> already_AddRefed<Promise>
ArrayBuffer(ErrorResult& aRv) ArrayBuffer(ErrorResult& aRv)

View File

@ -7,6 +7,7 @@
#include "nsIContentPolicy.h" #include "nsIContentPolicy.h"
#include "nsIDocument.h" #include "nsIDocument.h"
#include "nsStreamUtils.h"
#include "mozilla/ErrorResult.h" #include "mozilla/ErrorResult.h"
#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ScriptSettings.h"
@ -43,6 +44,53 @@ InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult
return copy.forget(); return copy.forget();
} }
already_AddRefed<InternalRequest>
InternalRequest::Clone()
{
nsRefPtr<InternalRequest> clone = new InternalRequest(*this);
if (!mBodyStream) {
return clone.forget();
}
nsCOMPtr<nsIInputStream> clonedBody;
nsCOMPtr<nsIInputStream> replacementBody;
nsresult rv = NS_CloneInputStream(mBodyStream, getter_AddRefs(clonedBody),
getter_AddRefs(replacementBody));
if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; }
clone->mBodyStream.swap(clonedBody);
if (replacementBody) {
mBodyStream.swap(replacementBody);
}
return clone.forget();
}
InternalRequest::InternalRequest(const InternalRequest& aOther)
: mMethod(aOther.mMethod)
, mURL(aOther.mURL)
, mHeaders(new InternalHeaders(*aOther.mHeaders))
, mContentPolicyType(aOther.mContentPolicyType)
, mReferrer(aOther.mReferrer)
, mMode(aOther.mMode)
, mCredentialsMode(aOther.mCredentialsMode)
, mResponseTainting(aOther.mResponseTainting)
, mCacheMode(aOther.mCacheMode)
, mAuthenticationFlag(aOther.mAuthenticationFlag)
, mForceOriginHeader(aOther.mForceOriginHeader)
, mPreserveContentCodings(aOther.mPreserveContentCodings)
, mSameOriginDataURL(aOther.mSameOriginDataURL)
, mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs)
, mSkipServiceWorker(aOther.mSkipServiceWorker)
, mSynchronous(aOther.mSynchronous)
, mUnsafeRequest(aOther.mUnsafeRequest)
, mUseURLCredentials(aOther.mUseURLCredentials)
{
// NOTE: does not copy body stream... use the fallible Clone() for that
}
InternalRequest::~InternalRequest() InternalRequest::~InternalRequest()
{ {
} }

View File

@ -67,28 +67,7 @@ public:
{ {
} }
explicit InternalRequest(const InternalRequest& aOther) already_AddRefed<InternalRequest> Clone();
: mMethod(aOther.mMethod)
, mURL(aOther.mURL)
, mHeaders(aOther.mHeaders)
, mBodyStream(aOther.mBodyStream)
, mContentPolicyType(aOther.mContentPolicyType)
, mReferrer(aOther.mReferrer)
, mMode(aOther.mMode)
, mCredentialsMode(aOther.mCredentialsMode)
, mResponseTainting(aOther.mResponseTainting)
, mCacheMode(aOther.mCacheMode)
, mAuthenticationFlag(aOther.mAuthenticationFlag)
, mForceOriginHeader(aOther.mForceOriginHeader)
, mPreserveContentCodings(aOther.mPreserveContentCodings)
, mSameOriginDataURL(aOther.mSameOriginDataURL)
, mSandboxedStorageAreaURLs(aOther.mSandboxedStorageAreaURLs)
, mSkipServiceWorker(aOther.mSkipServiceWorker)
, mSynchronous(aOther.mSynchronous)
, mUnsafeRequest(aOther.mUnsafeRequest)
, mUseURLCredentials(aOther.mUseURLCredentials)
{
}
void void
GetMethod(nsCString& aMethod) const GetMethod(nsCString& aMethod) const
@ -293,6 +272,9 @@ public:
GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const; GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const;
private: private:
// Does not copy mBodyStream. Use fallible Clone() for complete copy.
explicit InternalRequest(const InternalRequest& aOther);
~InternalRequest(); ~InternalRequest();
nsCString mMethod; nsCString mMethod;

View File

@ -8,6 +8,7 @@
#include "nsIDOMFile.h" #include "nsIDOMFile.h"
#include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/InternalHeaders.h"
#include "nsStreamUtils.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -22,7 +23,7 @@ InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusTe
} }
// Headers are not copied since BasicResponse and CORSResponse both need custom // Headers are not copied since BasicResponse and CORSResponse both need custom
// header handling. // header handling. Body is not copied as it cannot be shared directly.
InternalResponse::InternalResponse(const InternalResponse& aOther) InternalResponse::InternalResponse(const InternalResponse& aOther)
: mType(aOther.mType) : mType(aOther.mType)
, mTerminationReason(aOther.mTerminationReason) , mTerminationReason(aOther.mTerminationReason)
@ -30,11 +31,35 @@ InternalResponse::InternalResponse(const InternalResponse& aOther)
, mFinalURL(aOther.mFinalURL) , mFinalURL(aOther.mFinalURL)
, mStatus(aOther.mStatus) , mStatus(aOther.mStatus)
, mStatusText(aOther.mStatusText) , mStatusText(aOther.mStatusText)
, mBody(aOther.mBody)
, mContentType(aOther.mContentType) , mContentType(aOther.mContentType)
{ {
} }
already_AddRefed<InternalResponse>
InternalResponse::Clone()
{
nsRefPtr<InternalResponse> clone = new InternalResponse(*this);
clone->mHeaders = new InternalHeaders(*mHeaders);
if (!mBody) {
return clone.forget();
}
nsCOMPtr<nsIInputStream> clonedBody;
nsCOMPtr<nsIInputStream> replacementBody;
nsresult rv = NS_CloneInputStream(mBody, getter_AddRefs(clonedBody),
getter_AddRefs(replacementBody));
if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; }
clone->mBody.swap(clonedBody);
if (replacementBody) {
mBody.swap(replacementBody);
}
return clone.forget();
}
// static // static
already_AddRefed<InternalResponse> already_AddRefed<InternalResponse>
InternalResponse::BasicResponse(InternalResponse* aInner) InternalResponse::BasicResponse(InternalResponse* aInner)
@ -43,6 +68,7 @@ InternalResponse::BasicResponse(InternalResponse* aInner)
nsRefPtr<InternalResponse> basic = new InternalResponse(*aInner); nsRefPtr<InternalResponse> basic = new InternalResponse(*aInner);
basic->mType = ResponseType::Basic; basic->mType = ResponseType::Basic;
basic->mHeaders = InternalHeaders::BasicHeaders(aInner->mHeaders); basic->mHeaders = InternalHeaders::BasicHeaders(aInner->mHeaders);
basic->mBody.swap(aInner->mBody);
return basic.forget(); return basic.forget();
} }
@ -54,6 +80,7 @@ InternalResponse::CORSResponse(InternalResponse* aInner)
nsRefPtr<InternalResponse> cors = new InternalResponse(*aInner); nsRefPtr<InternalResponse> cors = new InternalResponse(*aInner);
cors->mType = ResponseType::Cors; cors->mType = ResponseType::Cors;
cors->mHeaders = InternalHeaders::CORSHeaders(aInner->mHeaders); cors->mHeaders = InternalHeaders::CORSHeaders(aInner->mHeaders);
cors->mBody.swap(aInner->mBody);
return cors.forget(); return cors.forget();
} }

View File

@ -25,6 +25,8 @@ public:
InternalResponse(uint16_t aStatus, const nsACString& aStatusText); InternalResponse(uint16_t aStatus, const nsACString& aStatusText);
already_AddRefed<InternalResponse> Clone();
static already_AddRefed<InternalResponse> static already_AddRefed<InternalResponse>
NetworkError() NetworkError()
{ {
@ -125,8 +127,8 @@ private:
~InternalResponse() ~InternalResponse()
{ } { }
// Used to create filtered responses. // Used to create filtered and cloned responses.
// Does not copy headers. // Does not copy headers or body stream.
explicit InternalResponse(const InternalResponse& aOther); explicit InternalResponse(const InternalResponse& aOther);
ResponseType mType; ResponseType mType;

View File

@ -63,7 +63,7 @@ Request::Constructor(const GlobalObject& aGlobal,
inputReq->GetBody(getter_AddRefs(body)); inputReq->GetBody(getter_AddRefs(body));
if (body) { if (body) {
if (inputReq->BodyUsed()) { if (inputReq->BodyUsed()) {
aRv.ThrowTypeError(MSG_REQUEST_BODY_CONSUMED_ERROR); aRv.ThrowTypeError(MSG_FETCH_BODY_CONSUMED_ERROR);
return nullptr; return nullptr;
} else { } else {
inputReq->SetBodyUsed(); inputReq->SetBodyUsed();
@ -255,12 +255,20 @@ Request::Constructor(const GlobalObject& aGlobal,
} }
already_AddRefed<Request> already_AddRefed<Request>
Request::Clone() const Request::Clone(ErrorResult& aRv) const
{ {
// FIXME(nsm): Bug 1073231. This is incorrect, but the clone method isn't if (BodyUsed()) {
// well defined yet. aRv.ThrowTypeError(MSG_FETCH_BODY_CONSUMED_ERROR);
nsRefPtr<Request> request = new Request(mOwner, return nullptr;
new InternalRequest(*mRequest)); }
nsRefPtr<InternalRequest> ir = mRequest->Clone();
if (!ir) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<Request> request = new Request(mOwner, ir);
return request.forget(); return request.forget();
} }

View File

@ -115,7 +115,7 @@ public:
} }
already_AddRefed<Request> already_AddRefed<Request>
Clone() const; Clone(ErrorResult& aRv) const;
already_AddRefed<InternalRequest> already_AddRefed<InternalRequest>
GetInternalRequest(); GetInternalRequest();

View File

@ -190,19 +190,23 @@ Response::Constructor(const GlobalObject& aGlobal,
return r.forget(); return r.forget();
} }
// FIXME(nsm): Bug 1073231: This is currently unspecced!
already_AddRefed<Response> already_AddRefed<Response>
Response::Clone() Response::Clone(ErrorResult& aRv) const
{ {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mOwner); if (BodyUsed()) {
nsRefPtr<Response> response = new Response(global, mInternalResponse); aRv.ThrowTypeError(MSG_FETCH_BODY_CONSUMED_ERROR);
return nullptr;
}
nsRefPtr<InternalResponse> ir = mInternalResponse->Clone();
nsRefPtr<Response> response = new Response(mOwner, ir);
return response.forget(); return response.forget();
} }
void void
Response::SetBody(nsIInputStream* aBody) Response::SetBody(nsIInputStream* aBody)
{ {
// FIXME(nsm): Do we flip bodyUsed here? MOZ_ASSERT(!BodyUsed());
mInternalResponse->SetBody(aBody); mInternalResponse->SetBody(aBody);
} }

View File

@ -112,7 +112,7 @@ public:
} }
already_AddRefed<Response> already_AddRefed<Response>
Clone(); Clone(ErrorResult& aRv) const;
void void
SetBody(nsIInputStream* aBody); SetBody(nsIInputStream* aBody);

View File

@ -23,7 +23,8 @@ interface Request {
readonly attribute RequestCredentials credentials; readonly attribute RequestCredentials credentials;
readonly attribute RequestCache cache; readonly attribute RequestCache cache;
[NewObject] Request clone(); [Throws,
NewObject] Request clone();
// Bug 1124638 - Allow chrome callers to set the context. // Bug 1124638 - Allow chrome callers to set the context.
[ChromeOnly] [ChromeOnly]

View File

@ -25,7 +25,8 @@ interface Response {
readonly attribute ByteString statusText; readonly attribute ByteString statusText;
[SameObject] readonly attribute Headers headers; [SameObject] readonly attribute Headers headers;
[NewObject] Response clone(); [Throws,
NewObject] Response clone();
}; };
Response implements Body; Response implements Body;

View File

@ -27,21 +27,70 @@ function testDefaultCtor() {
} }
function testClone() { function testClone() {
var req = (new Request("./cloned_request.txt", { var orig = new Request("./cloned_request.txt", {
method: 'POST', method: 'POST',
headers: { "Content-Length": 5 }, headers: { "Content-Length": 5 },
body: "Sample body", body: "Sample body",
mode: "same-origin", mode: "same-origin",
credentials: "same-origin", credentials: "same-origin",
})).clone(); });
ok(req.method === "POST", "Request method is POST"); var clone = orig.clone();
ok(req.headers instanceof Headers, "Request should have non-null Headers object"); ok(clone.method === "POST", "Request method is POST");
is(req.headers.get('content-length'), "5", "Request content-length should be 5."); ok(clone.headers instanceof Headers, "Request should have non-null Headers object");
ok(req.url === (new URL("./cloned_request.txt", self.location.href)).href,
is(clone.headers.get('content-length'), "5", "Response content-length should be 5.");
orig.headers.set('content-length', 6);
is(clone.headers.get('content-length'), "5", "Request content-length should be 5.");
ok(clone.url === (new URL("./cloned_request.txt", self.location.href)).href,
"URL should be resolved with entry settings object's API base URL"); "URL should be resolved with entry settings object's API base URL");
ok(req.referrer === "about:client", "Default referrer is `client` which serializes to about:client."); ok(clone.referrer === "about:client", "Default referrer is `client` which serializes to about:client.");
ok(req.mode === "same-origin", "Request mode is same-origin"); ok(clone.mode === "same-origin", "Request mode is same-origin");
ok(req.credentials === "same-origin", "Default credentials is same-origin"); ok(clone.credentials === "same-origin", "Default credentials is same-origin");
ok(!orig.bodyUsed, "Original body is not consumed.");
ok(!clone.bodyUsed, "Clone body is not consumed.");
var origBody = null;
var clone2 = null;
return orig.text().then(function (body) {
origBody = body;
is(origBody, "Sample body", "Original body string matches");
ok(orig.bodyUsed, "Original body is consumed.");
ok(!clone.bodyUsed, "Clone body is not consumed.");
try {
orig.clone()
ok(false, "Cannot clone Request whose body is already consumed");
} catch (e) {
is(e.name, "TypeError", "clone() of consumed body should throw TypeError");
}
clone2 = clone.clone();
return clone.text();
}).then(function (body) {
is(body, origBody, "Clone body matches original body.");
ok(clone.bodyUsed, "Clone body is consumed.");
try {
clone.clone()
ok(false, "Cannot clone Request whose body is already consumed");
} catch (e) {
is(e.name, "TypeError", "clone() of consumed body should throw TypeError");
}
return clone2.text();
}).then(function (body) {
is(body, origBody, "Clone body matches original body.");
ok(clone2.bodyUsed, "Clone body is consumed.");
try {
clone2.clone()
ok(false, "Cannot clone Request whose body is already consumed");
} catch (e) {
is(e.name, "TypeError", "clone() of consumed body should throw TypeError");
}
});
} }
function testUsedRequest() { function testUsedRequest() {
@ -266,7 +315,6 @@ onmessage = function() {
var done = function() { postMessage({ type: 'finish' }) } var done = function() { postMessage({ type: 'finish' }) }
testDefaultCtor(); testDefaultCtor();
testClone();
testSimpleUrlParse(); testSimpleUrlParse();
testUrlFragment(); testUrlFragment();
testMethod(); testMethod();
@ -278,6 +326,7 @@ onmessage = function() {
.then(testBodyUsed) .then(testBodyUsed)
.then(testBodyExtraction) .then(testBodyExtraction)
.then(testUsedRequest) .then(testUsedRequest)
.then(testClone())
// Put more promise based tests here. // Put more promise based tests here.
.then(done) .then(done)
.catch(function(e) { .catch(function(e) {

View File

@ -18,15 +18,63 @@ function testDefaultCtor() {
} }
function testClone() { function testClone() {
var res = (new Response("This is a body", { var orig = new Response("This is a body", {
status: 404, status: 404,
statusText: "Not Found", statusText: "Not Found",
headers: { "Content-Length": 5 }, headers: { "Content-Length": 5 },
})).clone(); });
is(res.status, 404, "Response status is 404"); var clone = orig.clone();
is(res.statusText, "Not Found", "Response statusText is POST"); is(clone.status, 404, "Response status is 404");
ok(res.headers instanceof Headers, "Response should have non-null Headers object"); is(clone.statusText, "Not Found", "Response statusText is POST");
is(res.headers.get('content-length'), "5", "Response content-length should be 5."); ok(clone.headers instanceof Headers, "Response should have non-null Headers object");
is(clone.headers.get('content-length'), "5", "Response content-length should be 5.");
orig.headers.set('content-length', 6);
is(clone.headers.get('content-length'), "5", "Response content-length should be 5.");
ok(!orig.bodyUsed, "Original body is not consumed.");
ok(!clone.bodyUsed, "Clone body is not consumed.");
var origBody = null;
var clone2 = null;
return orig.text().then(function (body) {
origBody = body;
is(origBody, "This is a body", "Original body string matches");
ok(orig.bodyUsed, "Original body is consumed.");
ok(!clone.bodyUsed, "Clone body is not consumed.");
try {
orig.clone()
ok(false, "Cannot clone Response whose body is already consumed");
} catch (e) {
is(e.name, "TypeError", "clone() of consumed body should throw TypeError");
}
clone2 = clone.clone();
return clone.text();
}).then(function (body) {
is(body, origBody, "Clone body matches original body.");
ok(clone.bodyUsed, "Clone body is consumed.");
try {
clone.clone()
ok(false, "Cannot clone Response whose body is already consumed");
} catch (e) {
is(e.name, "TypeError", "clone() of consumed body should throw TypeError");
}
return clone2.text();
}).then(function (body) {
is(body, origBody, "Clone body matches original body.");
ok(clone2.bodyUsed, "Clone body is consumed.");
try {
clone2.clone()
ok(false, "Cannot clone Response whose body is already consumed");
} catch (e) {
is(e.name, "TypeError", "clone() of consumed body should throw TypeError");
}
});
} }
function testRedirect() { function testRedirect() {