Commit 314288e9 authored by Yutaka Hirano's avatar Yutaka Hirano Committed by Commit Bot

Fix RequestInit IDL compatibility issues

RequestInit is manually implemented and has some compatibility issues.

 1. undefined value is treated as a real value, e.g.,
    (new Request('/', {method: undefined}).method
    returns "undefined".
 2. Exceptions are silently ignored, e.g.,
    new Request('/', {get method() { throw Error(); }})
    doesn't throw.

This change fixes the issues.

Bug: 775318
Change-Id: I3d5b2b49f47cfdf92ba0707f6af0260653e386d9
Reviewed-on: https://chromium-review.googlesource.com/722543
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Reviewed-by: default avatarAdam Rice <ricea@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#509714}
parent 88d99072
...@@ -560,6 +560,72 @@ test(() => { ...@@ -560,6 +560,72 @@ test(() => {
assert_throws({name: 'TypeError'}, () => { req.clone(); }); assert_throws({name: 'TypeError'}, () => { req.clone(); });
}, 'Used => clone'); }, 'Used => clone');
test(() => {
// We implement RequestInit manually so we need to test the functionality
// here.
function undefined_notpresent(property_name) {
assert_not_equals(property_name, 'referrer', 'property_name');
const request = new Request('/', {referrer: '/'});
let init = {};
init[property_name] = undefined;
assert_equals((new Request(request, init)).referrer, request.url,
property_name);
}
undefined_notpresent('method');
undefined_notpresent('headers');
undefined_notpresent('body');
undefined_notpresent('referrerPolicy');
undefined_notpresent('mode');
undefined_notpresent('credentials');
undefined_notpresent('cache');
undefined_notpresent('redirect');
undefined_notpresent('integrity');
undefined_notpresent('keepalive');
undefined_notpresent('signal');
undefined_notpresent('window');
// |undefined_notpresent| uses referrer for testing, so we need to test
// the property manually.
const request = new Request('/', {referrerPolicy: 'same-origin'});
assert_equals(new Request(request, {referrer: undefined}).referrerPolicy,
'same-origin', 'referrer');
}, 'An undefined member should be treated as not-present');
test(() => {
// We implement RequestInit manually so we need to test the functionality
// here.
const e = Error();
assert_throws(e, () => {
new Request('/', {get method() { throw e; }})}, 'method');
assert_throws(e, () => {
new Request('/', {get headers() { throw e; }})}, 'headers');
assert_throws(e, () => {
new Request('/', {get body() { throw e; }})}, 'body');
assert_throws(e, () => {
new Request('/', {get referrer() { throw e; }})}, 'referrer');
assert_throws(e, () => {
new Request('/', {get referrerPolicy() { throw e; }})}, 'referrerPolicy');
assert_throws(e, () => {
new Request('/', {get mode() { throw e; }})}, 'mode');
assert_throws(e, () => {
new Request('/', {get credentials() { throw e; }})}, 'credentials');
assert_throws(e, () => {
new Request('/', {get cache() { throw e; }})}, 'cache');
assert_throws(e, () => {
new Request('/', {get redirect() { throw e; }})}, 'redirect');
assert_throws(e, () => {
new Request('/', {get integrity() { throw e; }})}, 'integrity');
// Not implemented
// assert_throws(e, () => {
// new Request('/', {get keepalive() { throw e; }})}, 'keepalive');
// assert_throws(e, () => {
// new Request('/', {get signal() { throw e; }})}, 'signal');
// assert_throws(e, () => {
// new Request('/', {get window() { throw e; }})}, 'window');
}, 'Getter exceptions should not be silently ignored');
promise_test(function() { promise_test(function() {
var headers = new Headers; var headers = new Headers;
headers.set('Content-Language', 'ja'); headers.set('Content-Language', 'ja');
......
...@@ -162,16 +162,6 @@ struct DictionaryHelper { ...@@ -162,16 +162,6 @@ struct DictionaryHelper {
template <typename T> template <typename T>
static bool Get(const Dictionary&, const StringView& key, T& value); static bool Get(const Dictionary&, const StringView& key, T& value);
template <typename T> template <typename T>
static bool GetWithUndefinedCheck(const Dictionary& dictionary,
const StringView& key,
T& value) {
v8::Local<v8::Value> v8_value;
if (!dictionary.Get(key, v8_value) || v8_value.IsEmpty() ||
v8_value->IsUndefined())
return false;
return DictionaryHelper::Get(dictionary, key, value);
}
template <typename T>
static bool Get(const Dictionary&, const StringView& key, Nullable<T>& value); static bool Get(const Dictionary&, const StringView& key, Nullable<T>& value);
}; };
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
#include "modules/fetch/RequestInit.h" #include "modules/fetch/RequestInit.h"
#include "bindings/core/v8/Dictionary.h" #include "bindings/core/v8/Dictionary.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/IDLTypes.h"
#include "bindings/core/v8/NativeValueTraitsImpl.h"
#include "bindings/core/v8/V8ArrayBuffer.h" #include "bindings/core/v8/V8ArrayBuffer.h"
#include "bindings/core/v8/V8ArrayBufferView.h" #include "bindings/core/v8/V8ArrayBufferView.h"
#include "bindings/core/v8/V8BindingForCore.h" #include "bindings/core/v8/V8BindingForCore.h"
...@@ -27,49 +30,127 @@ ...@@ -27,49 +30,127 @@
namespace blink { namespace blink {
// TODO(yiyix): Verify if any DictionaryHelper::get should be replaced with struct RequestInit::IDLPassThrough
// DictionaryHelper::getWithUndefinedCheck. : public IDLBaseHelper<v8::Local<v8::Value>> {};
template <>
struct NativeValueTraits<RequestInit::IDLPassThrough>
: public NativeValueTraitsBase<RequestInit::IDLPassThrough> {
static v8::Local<v8::Value> NativeValue(v8::Isolate* isolate,
v8::Local<v8::Value> value,
ExceptionState& exception_state) {
DCHECK(!value.IsEmpty());
return value;
}
};
class RequestInit::GetterHelper {
STACK_ALLOCATED();
WTF_MAKE_NONCOPYABLE(GetterHelper);
public:
// |this| object must not outlive |src| and |exception_state|.
GetterHelper(const Dictionary& src, ExceptionState& exception_state)
: src_(src), exception_state_(exception_state) {}
template <typename IDLType>
WTF::Optional<typename IDLType::ImplType> Get(const StringView& key) {
auto r = src_.Get<IDLType>(key, exception_state_);
are_any_members_set_ = are_any_members_set_ || r.has_value();
return r;
}
bool AreAnyMembersSet() const { return are_any_members_set_; }
private:
const Dictionary& src_;
ExceptionState& exception_state_;
bool are_any_members_set_ = false;
};
RequestInit::RequestInit(ExecutionContext* context, RequestInit::RequestInit(ExecutionContext* context,
const Dictionary& options, const Dictionary& options,
ExceptionState& exception_state) { ExceptionState& exception_state) {
are_any_members_set_ |= DictionaryHelper::Get(options, "method", method_); GetterHelper h(options, exception_state);
// From https://github.com/whatwg/fetch/issues/479: method_ = h.Get<IDLByteString>("method").value_or(String());
// - undefined is the same as "this member has not been passed". if (exception_state.HadException())
// - {} means "the list of headers is empty", so the member has been set. return;
// - null is an invalid value for both sequences and records, but it is not
// the same as undefined: a value has been set, even if invalid, and will auto v8_headers = h.Get<IDLPassThrough>("headers");
// throw a TypeError later when it gets converted to a HeadersInit object. if (exception_state.HadException())
v8::Local<v8::Value> v8_headers; return;
bool is_header_set =
options.Get("headers", v8_headers) && !v8_headers->IsUndefined(); mode_ = h.Get<IDLUSVString>("mode").value_or(String());
are_any_members_set_ |= is_header_set; if (exception_state.HadException())
return;
are_any_members_set_ |= DictionaryHelper::Get(options, "mode", mode_);
if (RuntimeEnabledFeatures::FetchRequestCacheEnabled()) if (RuntimeEnabledFeatures::FetchRequestCacheEnabled()) {
are_any_members_set_ |= DictionaryHelper::Get(options, "cache", cache_); cache_ = h.Get<IDLUSVString>("cache").value_or(String());
if (exception_state.HadException())
are_any_members_set_ |= DictionaryHelper::Get(options, "redirect", redirect_); return;
AtomicString referrer_string; }
bool is_referrer_string_set = DictionaryHelper::GetWithUndefinedCheck(
options, "referrer", referrer_string); redirect_ = h.Get<IDLUSVString>("redirect").value_or(String());
are_any_members_set_ |= is_referrer_string_set; if (exception_state.HadException())
are_any_members_set_ |= return;
DictionaryHelper::Get(options, "integrity", integrity_);
AtomicString referrer_policy_string; auto referrer_string = h.Get<IDLUSVString>("referrer");
bool is_referrer_policy_set = if (exception_state.HadException())
DictionaryHelper::Get(options, "referrerPolicy", referrer_policy_string); return;
are_any_members_set_ |= is_referrer_policy_set;
auto referrer_policy_string = h.Get<IDLUSVString>("referrerPolicy");
v8::Local<v8::Value> v8_body; if (exception_state.HadException())
bool is_body_set = options.Get("body", v8_body); return;
are_any_members_set_ |= is_body_set;
integrity_ = h.Get<IDLString>("integrity").value_or(String());
v8::Local<v8::Value> v8_credential; if (exception_state.HadException())
bool is_credential_set = options.Get("credentials", v8_credential); return;
are_any_members_set_ |= is_credential_set;
auto v8_body = h.Get<IDLPassThrough>("body");
if (are_any_members_set_) { if (exception_state.HadException())
return;
auto v8_credentials = h.Get<IDLPassThrough>("credentials");
if (exception_state.HadException())
return;
are_any_members_set_ = h.AreAnyMembersSet();
SetUpReferrer(referrer_string, referrer_policy_string, exception_state);
if (exception_state.HadException())
return;
v8::Isolate* isolate = ToIsolate(context);
if (v8_headers.has_value()) {
V8ByteStringSequenceSequenceOrByteStringByteStringRecord::ToImpl(
isolate, *v8_headers, headers_, UnionTypeConversionMode::kNotNullable,
exception_state);
if (exception_state.HadException())
return;
}
if (v8_credentials.has_value()) {
SetUpCredentials(context, isolate, *v8_credentials, exception_state);
if (exception_state.HadException())
return;
}
if (v8_body.has_value()) {
SetUpBody(context, isolate, *v8_body, exception_state);
if (exception_state.HadException())
return;
}
}
void RequestInit::SetUpReferrer(
const WTF::Optional<String>& referrer_string,
const WTF::Optional<String>& referrer_policy_string,
ExceptionState& exception_state) {
if (!are_any_members_set_)
return;
// A part of the Request constructor algorithm is performed here. See // A part of the Request constructor algorithm is performed here. See
// the comments in the Request constructor code for the detail. // the comments in the Request constructor code for the detail.
...@@ -77,26 +158,26 @@ RequestInit::RequestInit(ExecutionContext* context, ...@@ -77,26 +158,26 @@ RequestInit::RequestInit(ExecutionContext* context,
// because "about:client" => |clientReferrerString| conversion is done // because "about:client" => |clientReferrerString| conversion is done
// in Request::createRequestWithRequestOrString. // in Request::createRequestWithRequestOrString.
referrer_ = Referrer("about:client", kReferrerPolicyDefault); referrer_ = Referrer("about:client", kReferrerPolicyDefault);
if (is_referrer_string_set) if (referrer_string.has_value())
referrer_.referrer = referrer_string; referrer_.referrer = AtomicString(*referrer_string);
if (is_referrer_policy_set) { if (referrer_policy_string.has_value()) {
if (referrer_policy_string == "") { if (*referrer_policy_string == "") {
referrer_.referrer_policy = kReferrerPolicyDefault; referrer_.referrer_policy = kReferrerPolicyDefault;
} else if (referrer_policy_string == "no-referrer") { } else if (*referrer_policy_string == "no-referrer") {
referrer_.referrer_policy = kReferrerPolicyNever; referrer_.referrer_policy = kReferrerPolicyNever;
} else if (referrer_policy_string == "no-referrer-when-downgrade") { } else if (*referrer_policy_string == "no-referrer-when-downgrade") {
referrer_.referrer_policy = kReferrerPolicyNoReferrerWhenDowngrade; referrer_.referrer_policy = kReferrerPolicyNoReferrerWhenDowngrade;
} else if (referrer_policy_string == "origin") { } else if (*referrer_policy_string == "origin") {
referrer_.referrer_policy = kReferrerPolicyOrigin; referrer_.referrer_policy = kReferrerPolicyOrigin;
} else if (referrer_policy_string == "origin-when-cross-origin") { } else if (*referrer_policy_string == "origin-when-cross-origin") {
referrer_.referrer_policy = kReferrerPolicyOriginWhenCrossOrigin; referrer_.referrer_policy = kReferrerPolicyOriginWhenCrossOrigin;
} else if (referrer_policy_string == "same-origin") { } else if (*referrer_policy_string == "same-origin") {
referrer_.referrer_policy = kReferrerPolicySameOrigin; referrer_.referrer_policy = kReferrerPolicySameOrigin;
} else if (referrer_policy_string == "strict-origin") { } else if (*referrer_policy_string == "strict-origin") {
referrer_.referrer_policy = kReferrerPolicyStrictOrigin; referrer_.referrer_policy = kReferrerPolicyStrictOrigin;
} else if (referrer_policy_string == "unsafe-url") { } else if (*referrer_policy_string == "unsafe-url") {
referrer_.referrer_policy = kReferrerPolicyAlways; referrer_.referrer_policy = kReferrerPolicyAlways;
} else if (referrer_policy_string == "strict-origin-when-cross-origin") { } else if (*referrer_policy_string == "strict-origin-when-cross-origin") {
referrer_.referrer_policy = referrer_.referrer_policy =
kReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin; kReferrerPolicyNoReferrerWhenDowngradeOriginWhenCrossOrigin;
} else { } else {
...@@ -104,20 +185,13 @@ RequestInit::RequestInit(ExecutionContext* context, ...@@ -104,20 +185,13 @@ RequestInit::RequestInit(ExecutionContext* context,
return; return;
} }
} }
} }
v8::Isolate* isolate = ToIsolate(context);
if (is_header_set) {
V8ByteStringSequenceSequenceOrByteStringByteStringRecord::ToImpl(
isolate, v8_headers, headers_, UnionTypeConversionMode::kNotNullable,
exception_state);
if (exception_state.HadException())
return;
}
if (is_credential_set) { void RequestInit::SetUpCredentials(ExecutionContext* context,
if (V8PasswordCredential::hasInstance(v8_credential, isolate)) { v8::Isolate* isolate,
v8::Local<v8::Value> v8_credentials,
ExceptionState& exception_state) {
if (V8PasswordCredential::hasInstance(v8_credentials, isolate)) {
Deprecation::CountDeprecation(context, Deprecation::CountDeprecation(context,
WebFeature::kCredentialManagerCustomFetch); WebFeature::kCredentialManagerCustomFetch);
// TODO(mkwst): According to the spec, we'd serialize this once we touch // TODO(mkwst): According to the spec, we'd serialize this once we touch
...@@ -129,16 +203,21 @@ RequestInit::RequestInit(ExecutionContext* context, ...@@ -129,16 +203,21 @@ RequestInit::RequestInit(ExecutionContext* context,
// behavior with this option, except that the `Content-Type` header will // behavior with this option, except that the `Content-Type` header will
// be set early. That seems reasonable. // be set early. That seems reasonable.
PasswordCredential* credential = PasswordCredential* credential =
V8PasswordCredential::ToImpl(v8_credential.As<v8::Object>()); V8PasswordCredential::ToImpl(v8_credentials.As<v8::Object>());
attached_credential_ = credential->EncodeFormData(content_type_); attached_credential_ = credential->EncodeFormData(content_type_);
credentials_ = "password"; credentials_ = "password";
} else if (v8_credential->IsString()) { } else if (v8_credentials->IsString()) {
credentials_ = ToUSVString(isolate, v8_credential, exception_state); credentials_ = ToUSVString(isolate, v8_credentials, exception_state);
} if (exception_state.HadException())
return;
} }
}
if (attached_credential_.get() || !is_body_set || v8_body->IsUndefined() || void RequestInit::SetUpBody(ExecutionContext* context,
v8_body->IsNull()) v8::Isolate* isolate,
v8::Local<v8::Value> v8_body,
ExceptionState& exception_state) {
if (attached_credential_ || v8_body->IsNull())
return; return;
if (v8_body->IsArrayBuffer()) { if (v8_body->IsArrayBuffer()) {
......
...@@ -5,11 +5,13 @@ ...@@ -5,11 +5,13 @@
#ifndef RequestInit_h #ifndef RequestInit_h
#define RequestInit_h #define RequestInit_h
#include "bindings/core/v8/NativeValueTraits.h"
#include "bindings/modules/v8/byte_string_sequence_sequence_or_byte_string_byte_string_record.h" #include "bindings/modules/v8/byte_string_sequence_sequence_or_byte_string_byte_string_record.h"
#include "modules/fetch/Headers.h" #include "modules/fetch/Headers.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "platform/network/EncodedFormData.h" #include "platform/network/EncodedFormData.h"
#include "platform/weborigin/Referrer.h" #include "platform/weborigin/Referrer.h"
#include "platform/wtf/Optional.h"
#include "platform/wtf/RefPtr.h" #include "platform/wtf/RefPtr.h"
#include "platform/wtf/text/WTFString.h" #include "platform/wtf/text/WTFString.h"
...@@ -41,6 +43,24 @@ class RequestInit { ...@@ -41,6 +43,24 @@ class RequestInit {
bool AreAnyMembersSet() const { return are_any_members_set_; } bool AreAnyMembersSet() const { return are_any_members_set_; }
private: private:
// These are defined here to avoid JUMBO ambiguity.
class GetterHelper;
struct IDLPassThrough;
friend struct NativeValueTraits<IDLPassThrough>;
friend struct NativeValueTraitsBase<IDLPassThrough>;
void SetUpReferrer(const WTF::Optional<String>& referrer_string,
const WTF::Optional<String>& referrer_policy_string,
ExceptionState&);
void SetUpCredentials(ExecutionContext*,
v8::Isolate*,
v8::Local<v8::Value> v8_credentials,
ExceptionState&);
void SetUpBody(ExecutionContext*,
v8::Isolate*,
v8::Local<v8::Value> v8_body,
ExceptionState&);
String method_; String method_;
HeadersInit headers_; HeadersInit headers_;
String content_type_; String content_type_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment