Bug 1207233 - Part 2: Make URLSearchParams nsIXHRSendable as well as clonable, and have XHRs set the correct request content type for them. r=baku

--HG--
extra : rebase_source : de17309ef5704f288f309605dbb7744eadea3bd8
This commit is contained in:
Thomas Wisniewski 2016-07-11 15:03:44 -04:00
Родитель 291ec7772c
Коммит 458f4f42b3
13 изменённых файлов: 458 добавлений и 18 удалений

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

@ -30,6 +30,7 @@
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/SubtleCryptoBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/WebCryptoCommon.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/ipc/BackgroundChild.h"
@ -395,7 +396,7 @@ StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx,
return ReadStructuredCloneImageData(aCx, aReader);
}
if (aTag == SCTAG_DOM_WEBCRYPTO_KEY) {
if (aTag == SCTAG_DOM_WEBCRYPTO_KEY || aTag == SCTAG_DOM_URLSEARCHPARAMS) {
nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
if (!global) {
return nullptr;
@ -404,11 +405,20 @@ StructuredCloneHolder::ReadFullySerializableObjects(JSContext* aCx,
// Prevent the return value from being trashed by a GC during ~nsRefPtr.
JS::Rooted<JSObject*> result(aCx);
{
RefPtr<CryptoKey> key = new CryptoKey(global);
if (!key->ReadStructuredClone(aReader)) {
result = nullptr;
} else {
result = key->WrapObject(aCx, nullptr);
if (aTag == SCTAG_DOM_WEBCRYPTO_KEY) {
RefPtr<CryptoKey> key = new CryptoKey(global);
if (!key->ReadStructuredClone(aReader)) {
result = nullptr;
} else {
result = key->WrapObject(aCx, nullptr);
}
} else if (aTag == SCTAG_DOM_URLSEARCHPARAMS) {
RefPtr<URLSearchParams> usp = new URLSearchParams(global);
if (!usp->ReadStructuredClone(aReader)) {
result = nullptr;
} else {
result = usp->WrapObject(aCx, nullptr);
}
}
}
return result;
@ -504,6 +514,15 @@ StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx,
}
}
// Handle URLSearchParams cloning
{
URLSearchParams* usp = nullptr;
if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, aObj, usp))) {
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_URLSEARCHPARAMS, 0) &&
usp->WriteStructuredClone(aWriter);
}
}
// Handle Key cloning
{
CryptoKey* key = nullptr;

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

@ -54,6 +54,9 @@ enum StructuredCloneTags {
SCTAG_DOM_DIRECTORY,
// This tag is used by both main thread and workers.
SCTAG_DOM_URLSEARCHPARAMS,
SCTAG_DOM_MAX
};

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

@ -8,6 +8,8 @@
#include "mozilla/dom/URLSearchParamsBinding.h"
#include "mozilla/dom/EncodingUtils.h"
#include "nsDOMString.h"
#include "nsIInputStream.h"
#include "nsStringStream.h"
namespace mozilla {
namespace dom {
@ -300,6 +302,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(URLSearchParams)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URLSearchParams)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsIXHRSendable)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
@ -444,5 +447,103 @@ URLSearchParams::GetValueAtIndex(uint32_t aIndex) const
return mParams->GetValueAtIndex(aIndex);
}
// Helper functions for structured cloning
inline bool
ReadString(JSStructuredCloneReader* aReader, nsString& aString)
{
MOZ_ASSERT(aReader);
bool read;
uint32_t nameLength, zero;
read = JS_ReadUint32Pair(aReader, &nameLength, &zero);
if (!read) {
return false;
}
MOZ_ASSERT(zero == 0);
aString.SetLength(nameLength);
size_t charSize = sizeof(nsString::char_type);
read = JS_ReadBytes(aReader, (void*) aString.BeginWriting(),
nameLength * charSize);
if (!read) {
return false;
}
return true;
}
inline bool
WriteString(JSStructuredCloneWriter* aWriter, const nsString& aString)
{
MOZ_ASSERT(aWriter);
size_t charSize = sizeof(nsString::char_type);
return JS_WriteUint32Pair(aWriter, aString.Length(), 0) &&
JS_WriteBytes(aWriter, aString.get(), aString.Length() * charSize);
}
bool
URLParams::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
{
const uint32_t& nParams = mParams.Length();
if (!JS_WriteUint32Pair(aWriter, nParams, 0)) {
return false;
}
for (uint32_t i = 0; i < nParams; ++i) {
if (!WriteString(aWriter, mParams[i].mKey) ||
!WriteString(aWriter, mParams[i].mValue)) {
return false;
}
}
return true;
}
bool
URLParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
{
MOZ_ASSERT(aReader);
DeleteAll();
uint32_t nParams, zero;
nsAutoString key, value;
if (!JS_ReadUint32Pair(aReader, &nParams, &zero)) {
return false;
}
MOZ_ASSERT(zero == 0);
for (uint32_t i = 0; i < nParams; ++i) {
if (!ReadString(aReader, key) || !ReadString(aReader, value)) {
return false;
}
Append(key, value);
}
return true;
}
bool
URLSearchParams::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
{
return mParams->WriteStructuredClone(aWriter);
}
bool
URLSearchParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
{
return mParams->ReadStructuredClone(aReader);
}
NS_IMETHODIMP
URLSearchParams::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset)
{
aContentType.AssignLiteral("application/x-www-form-urlencoded");
aCharset.AssignLiteral("UTF-8");
nsAutoString serialized;
Serialize(serialized);
NS_ConvertUTF16toUTF8 converted(serialized);
*aContentLength = converted.Length();
return NS_NewCStringInputStream(aBody, converted);
}
} // namespace dom
} // namespace mozilla

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

@ -7,12 +7,14 @@
#ifndef mozilla_dom_URLSearchParams_h
#define mozilla_dom_URLSearchParams_h
#include "js/StructuredClone.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/ErrorResult.h"
#include "nsCycleCollectionParticipant.h"
#include "nsWrapperCache.h"
#include "nsISupports.h"
#include "nsIUnicodeDecoder.h"
#include "nsIXMLHttpRequest.h"
namespace mozilla {
namespace dom {
@ -108,6 +110,12 @@ public:
return mParams[aIndex].mValue;
}
bool
ReadStructuredClone(JSStructuredCloneReader* aReader);
bool
WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
private:
void DecodeString(const nsACString& aInput, nsAString& aOutput);
void ConvertString(const nsACString& aInput, nsAString& aOutput);
@ -122,17 +130,19 @@ private:
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
};
class URLSearchParams final : public nsISupports,
class URLSearchParams final : public nsIXHRSendable,
public nsWrapperCache
{
~URLSearchParams();
public:
NS_DECL_NSIXHRSENDABLE
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(URLSearchParams)
URLSearchParams(nsISupports* aParent,
URLSearchParamsObserver* aObserver);
URLSearchParamsObserver* aObserver=nullptr);
URLSearchParams(nsISupports* aParent,
const URLSearchParams& aOther);
@ -189,6 +199,12 @@ public:
return true;
}
bool
ReadStructuredClone(JSStructuredCloneReader* aReader);
bool
WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
private:
void AppendInternal(const nsAString& aName, const nsAString& aValue);

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

@ -102,6 +102,8 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget {
void send(FormData data);
[Throws]
void send(InputStream data);
[Throws]
void send(URLSearchParams data);
[Throws]
void abort();

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

@ -19,6 +19,7 @@ namespace dom {
class Blob;
class FormData;
class URLSearchParams;
class XMLHttpRequestUpload;
class XMLHttpRequest : public XMLHttpRequestEventTarget
@ -88,6 +89,9 @@ public:
virtual void
Send(JSContext* aCx, Blob& aBlob, ErrorResult& aRv) = 0;
virtual void
Send(JSContext* aCx, URLSearchParams& aURLSearchParams, ErrorResult& aRv) = 0;
virtual void
Send(JSContext* aCx, nsIDocument& aDoc, ErrorResult& aRv) = 0;

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

@ -15,6 +15,7 @@
#include "mozilla/dom/File.h"
#include "mozilla/dom/FetchUtil.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/LoadInfo.h"
@ -2217,6 +2218,15 @@ GetRequestBodyInternal(nsIInputStream* aStream, nsIInputStream** aResult,
return NS_OK;
}
static nsresult
GetRequestBodyInternal(URLSearchParams* aURLSearchParams,
nsIInputStream** aResult, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset)
{
return aURLSearchParams->GetSendInfo(aResult, aContentLength,
aContentType, aCharset);
}
static nsresult
GetRequestBodyInternal(nsIXHRSendable* aSendable, nsIInputStream** aResult,
uint64_t* aContentLength, nsACString& aContentType,
@ -2397,6 +2407,12 @@ XMLHttpRequestMainThread::GetRequestBody(nsIVariant* aVariant,
return GetRequestBodyInternal(value.mFormData, aResult, aContentLength,
aContentType, aCharset);
}
case XMLHttpRequestMainThread::RequestBody::eURLSearchParams:
{
MOZ_ASSERT(value.mURLSearchParams);
return GetRequestBodyInternal(value.mURLSearchParams, aResult,
aContentLength, aContentType, aCharset);
}
case XMLHttpRequestMainThread::RequestBody::eInputStream:
{
return GetRequestBodyInternal(value.mStream, aResult, aContentLength,

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

@ -56,6 +56,7 @@ namespace dom {
class Blob;
class BlobSet;
class FormData;
class URLSearchParams;
class XMLHttpRequestUpload;
// A helper for building up an ArrayBuffer object's data
@ -263,6 +264,11 @@ private:
{
mValue.mBlob = &aBlob;
}
explicit RequestBody(mozilla::dom::URLSearchParams& aURLSearchParams) :
mType(eURLSearchParams)
{
mValue.mURLSearchParams = &aURLSearchParams;
}
explicit RequestBody(nsIDocument* aDocument) : mType(eDocument)
{
mValue.mDocument = aDocument;
@ -288,7 +294,8 @@ private:
eDocument,
eDOMString,
eFormData,
eInputStream
eInputStream,
eURLSearchParams
};
union Value {
const ArrayBuffer* mArrayBuffer;
@ -298,6 +305,7 @@ private:
const nsAString* mString;
FormData* mFormData;
nsIInputStream* mStream;
URLSearchParams* mURLSearchParams;
};
Type GetType() const
@ -368,6 +376,12 @@ public:
aRv = Send(RequestBody(aBlob));
}
virtual void Send(JSContext* /*aCx*/, URLSearchParams& aURLSearchParams,
ErrorResult& aRv) override
{
aRv = Send(RequestBody(aURLSearchParams));
}
virtual void
Send(JSContext* /*aCx*/, nsIDocument& aDoc, ErrorResult& aRv) override
{

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

@ -21,6 +21,7 @@
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/ProgressEvent.h"
#include "mozilla/dom/StructuredCloneHolder.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/Telemetry.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
@ -2166,6 +2167,39 @@ XMLHttpRequestWorker::Send(JSContext* aCx, FormData& aBody, ErrorResult& aRv)
SendInternal(sendRunnable, aRv);
}
void
XMLHttpRequestWorker::Send(JSContext* aCx, URLSearchParams& aBody,
ErrorResult& aRv)
{
mWorkerPrivate->AssertIsOnWorkerThread();
if (mCanceled) {
aRv.ThrowUncatchableException();
return;
}
if (!mProxy) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
JS::Rooted<JS::Value> value(aCx);
if (!GetOrCreateDOMReflector(aCx, &aBody, &value)) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
RefPtr<SendRunnable> sendRunnable =
new SendRunnable(mWorkerPrivate, mProxy, EmptyString());
sendRunnable->Write(aCx, value, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
SendInternal(sendRunnable, aRv);
}
void
XMLHttpRequestWorker::Send(JSContext* aCx, const ArrayBuffer& aBody,
ErrorResult& aRv)

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

@ -175,6 +175,9 @@ public:
virtual void
Send(JSContext* aCx, const ArrayBufferView& aBody, ErrorResult& aRv) override;
virtual void
Send(JSContext* aCx, URLSearchParams& aBody, ErrorResult& aRv) override;
virtual void
Send(JSContext* aCx, nsIDocument& aDoc, ErrorResult& aRv) override
{

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

@ -0,0 +1,10 @@
[setrequestheader-content-type.htm]
type: testharness
[ReadableStream request respects setRequestHeader("")]
expected: FAIL
[ReadableStream request with under type sends no Content-Type without setRequestHeader() call]
expected: FAIL
[ReadableStream request keeps setRequestHeader() Content-Type and charset]
expected: FAIL

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

@ -9,19 +9,229 @@
<body>
<div id="log"></div>
<script>
function request(value) {
function request(inputGenerator, headersToSend, expectedType, title) {
test(function() {
try {
var toSend = inputGenerator();
} catch(e) {
assert_unreached("Skipping test as could not create a " + inputGenerator.name.replace("_", "") + "; ");
}
var client = new XMLHttpRequest()
client.open("POST", "resources/inspect-headers.py?filter_name=Content-Type", false)
client.setRequestHeader('Content-Type', value)
client.send("")
assert_equals(client.responseText, 'content-type: '+ String(value ? value.trim() : value).toLowerCase()+'\n' )
}, document.title + " (" + value + ")")
for(header in headersToSend) {
if (headersToSend.hasOwnProperty(header)) {
client.setRequestHeader(header, headersToSend[header]);
}
}
client.send(toSend)
var responseType = client.responseText.replace("\n", "").replace("; ", ";").toLowerCase(); // don't care about case or space after semicolon for charset
if (expectedType === undefined || expectedType === null) {
assert_equals(responseType, "");
} else if (expectedType instanceof RegExp) {
if (!expectedType.ignoreCase) expectedType = new RegExp(expectedType, "i"); // always ignore case; the regex itself will have to remember to handle the optional space after the semicolon for charset
assert_regexp_match(responseType, expectedType);
} else {
expectedType = "content-type: " + String(expectedType ? expectedType.trim().replace("; ", ";") : expectedType).toLowerCase()
assert_equals(responseType, expectedType);
}
}, title)
}
request("")
request(" ")
request(null)
request(undefined)
request(
function _String() { return ""; },
{"Content-Type": ""},
"",
'setRequestHeader("") sends a blank string'
)
request(
function _String() { return ""; },
{"Content-Type": " "},
" ",
'setRequestHeader(" ") sends the string " "'
)
request(
function _String() { return ""; },
{"Content-Type": null},
"null",
'setRequestHeader(null) sends the string "null"'
)
request(
function _String() { return ""; },
{"Content-Type": undefined},
"undefined",
'setRequestHeader(undefined) sends the string "undefined"'
)
request(
function _String() { return "test"; },
{},
"text/plain;charset=UTF-8",
'String request has correct default Content-Type of "text/plain;charset=UTF-8"'
)
request(
function _String() { return "test()"; },
{"Content-Type": "text/javascript;charset=ASCII"},
"text/javascript;charset=UTF-8",
"String request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8"
)
request(
function _XMLDocument() { return new DOMParser().parseFromString("<xml/>", "application/xml"); },
{"Content-Type": ""},
"",
'XML Document request respects setRequestHeader("")'
)
request(
function _XMLDocument() { return new DOMParser().parseFromString("<xml/>", "application/xml"); },
{},
"application/xml;charset=UTF-8",
'XML Document request has correct default Content-Type of "application/xml;charset=UTF-8"'
)
request(
function _XMLDocument() { return new DOMParser().parseFromString("<xml/>", "application/xml"); },
{"Content-Type": "application/xhtml+xml;charset=ASCII"},
"application/xhtml+xml;charset=UTF-8",
"XML Document request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8"
)
request(
function _HTMLDocument() { return new DOMParser().parseFromString("<html></html>", "text/html"); },
{"Content-Type": ""},
"",
'HTML Document request respects setRequestHeader("")'
)
request(
function _HTMLDocument() { return new DOMParser().parseFromString("<html></html>", "text/html"); },
{},
"text/html;charset=UTF-8",
'HTML Document request has correct default Content-Type of "text/html;charset=UTF-8"'
)
request(
function _HTMLDocument() { return new DOMParser().parseFromString("<html></html>", "text/html"); },
{"Content-Type": "text/html+junk;charset=ASCII"},
"text/html+junk;charset=UTF-8",
"HTML Document request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8"
)
request(
function _Blob() { return new Blob(["test"]); },
{"Content-Type": ""},
"",
'Blob request respects setRequestHeader("") to be specified'
)
request(
function _Blob() { return new Blob(["test"]); },
{},
undefined,
"Blob request with unset type sends no Content-Type without setRequestHeader() call"
)
request(
function _Blob() { return new Blob(["test"]); },
{"Content-Type": "application/xml;charset=ASCII"},
"application/xml;charset=ASCII",
"Blob request with unset type keeps setRequestHeader() Content-Type and charset"
)
request(
function _Blob() { return new Blob(["<xml/>"], {type : "application/xml;charset=ASCII"}); },
{},
"application/xml;charset=ASCII",
"Blob request with set type uses that it for Content-Type unless setRequestHeader()"
)
request(
function _Blob() { return new Blob(["<xml/>"], {type : "application/xml;charset=UTF8"}); },
{"Content-Type": "application/xml+junk;charset=ASCII"},
"application/xml+junk;charset=ASCII",
"Blob request with set type keeps setRequestHeader() Content-Type and charset"
)
request(
function _ArrayBuffer() { return new ArrayBuffer(10); },
{"Content-Type": ""},
"",
'ArrayBuffer request respects setRequestHeader("")'
)
request(
function _ArrayBuffer() { return new ArrayBuffer(10); },
{},
undefined,
"ArrayBuffer request sends no Content-Type without setRequestHeader() call"
)
request(
function _ArrayBuffer() { return new ArrayBuffer(10); },
{"Content-Type": "application/xml;charset=ASCII"},
"application/xml;charset=ASCII",
"ArrayBuffer request keeps setRequestHeader() Content-Type and charset"
)
request(
function _Uint8Array() { return new Uint8Array(new ArrayBuffer(10)); },
{"Content-Type": ""},
"",
'ArrayBufferView request respects setRequestHeader("")'
)
request(
function _Uint8Array() { return new Uint8Array(new ArrayBuffer(10)); },
{},
undefined,
"ArrayBufferView request sends no Content-Type without setRequestHeader() call"
)
request(
function _Uint8Array() { return new Uint8Array(new ArrayBuffer(10)); },
{"Content-Type": "application/xml;charset=ASCII"},
"application/xml;charset=ASCII",
"ArrayBufferView request keeps setRequestHeader() Content-Type and charset"
)
request(
function _FormData() { return new FormData(); },
{"Content-Type": ""},
"",
'FormData request respects setRequestHeader("")'
)
request(
function _FormData() { return new FormData(); },
{},
/multipart\/form-data;boundary=(.*)/,
'FormData request has correct default Content-Type of "multipart\/form-data;boundary=_"'
)
request(
function _FormData() { return new FormData(); },
{"Content-Type": "application/xml;charset=ASCII"},
"application/xml;charset=ASCII",
"FormData request keeps setRequestHeader() Content-Type and charset"
)
request(
function _URLSearchParams() { return new URLSearchParams("q=testQ&topic=testTopic") },
{"Content-Type": ""},
"",
'URLSearchParams respects setRequestHeader("")'
)
request(
function _URLSearchParams() { return new URLSearchParams("q=testQ&topic=testTopic") },
{},
"application/x-www-form-urlencoded;charset=UTF-8",
'URLSearchParams request has correct default Content-Type of "application/x-www-form-urlencoded;charset=UTF-8"'
)
request(
function _URLSearchParams() { return new URLSearchParams("q=testQ&topic=testTopic") },
{"Content-Type": "application/xml;charset=ASCII"},
"application/xml;charset=UTF-8",
"URLSearchParams request keeps setRequestHeader() Content-Type, with charset adjusted to UTF-8"
// the default Content-Type for URLSearchParams has a charset specified (utf-8) in
// https://fetch.spec.whatwg.org/#bodyinit, so the user's must be changed to match it
// as per https://xhr.spec.whatwg.org/#the-send%28%29-method step 4.
)
request(
function _ReadableStream() { return new ReadableStream() },
{"Content-Type": ""},
"",
'ReadableStream request respects setRequestHeader("")'
)
request(
function _ReadableStream() { return new ReadableStream() },
{},
undefined,
"ReadableStream request with under type sends no Content-Type without setRequestHeader() call"
)
request(
function _ReadableStream() { return new ReadableStream() },
{"Content-Type": "application/xml;charset=ASCII"},
"application/xml;charset=ASCII",
"ReadableStream request keeps setRequestHeader() Content-Type and charset"
)
</script>
</body>
</html>

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

@ -215,6 +215,14 @@ URLSearchParams::NotifyObserver()
NS_NOTREACHED("Unexpected call to URLSearchParams::NotifyObserver");
}
NS_IMETHODIMP
URLSearchParams::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
nsACString& aContentType, nsACString& aCharset)
{
NS_NOTREACHED("Unexpected call to URLSearchParams::GetSendInfo");
return NS_OK;
}
} // namespace dom
} // namespace mozilla