Commit 59d962b4 authored by Leon Han's avatar Leon Han Committed by Commit Bot

Reland "[webnfc] Refactor/Refine impl for "Creating Web NFC message" algorithm"

This is a reland of 581b273e

Original change's description:
> [webnfc] Refactor/Refine impl for "Creating Web NFC message" algorithm
>
> This CL implements strictly the "Creating Web NFC message" algorithm
> described at http://w3c.github.io/web-nfc/#creating-web-nfc-message.
>
> 1.
> Changes the Web IDL definition for NDEFRecordData from
> "typedef (DOMString or unrestricted double or ArrayBuffer or Dictionary) NDEFRecordData;"
> to
> "typedef any NDEFRecordData;"
>
> 2.
> Adds verification of NDEFRecordInit when creating NDEFRecord, i.e. makes
> NDEFRecord ctor throw exceptions in case of invalid NDEFRecordInit.
> Also, the same thing for NDEFMessage and NFCReadingEvent.
>
> 3.
> Changes code flow for Step 10.9 of "9.10.1 The push() method" described
> at https://w3c.github.io/web-nfc/#the-push-method.
> "
> Let output be the notation for the NDEF message to be created by UA, as
> the result of passing message to create Web NFC message. If this throws
> an exception, reject p with that exception and abort these steps.
> "
> Previously,
>   NDEFMessageSource (NDEFMessageInit) -Validity check->
>   -Mojo TypeConverter-> Mojom NDEFMessagePtr
> Now,
>   Remove "Validity check" and "Mojo TypeConverter" mentioned above,
>   instead, we reuse the process of creating NDEFMessage from
>   NDEFMessageSource (NDEFMessageInit), then the next step converting
>   NDEFMessage to Mojom NDEFMessagePtr is quite an easy job.
>
> BUG=520391
>
> Change-Id: I628981e556f89ffdd0f883da0cfa99b20a6f8300
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1767719
> Commit-Queue: Leon Han <leon.han@intel.com>
> Reviewed-by: Daniel Cheng <dcheng@chromium.org>
> Reviewed-by: Reilly Grant <reillyg@chromium.org>
> Reviewed-by: Rijubrata Bhaumik <rijubrata.bhaumik@intel.com>
> Cr-Commit-Position: refs/heads/master@{#693539}

Bug: 520391,923974
TBR: dcheng@,reillyg@
Change-Id: I608e28638c0909c9d4f675774514cd990f9878c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1787557
Commit-Queue: Leon Han <leon.han@intel.com>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#693638}
parent 98b2e29f
......@@ -8,7 +8,6 @@ import android.net.Uri;
import android.os.Build;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Log;
import org.chromium.device.mojom.NdefMessage;
import org.chromium.device.mojom.NdefRecord;
import org.chromium.device.mojom.NdefRecordType;
......@@ -29,8 +28,6 @@ public final class NdefMessageUtils {
private static final String TEXT_MIME = "text/plain";
private static final String JSON_MIME = "application/json";
private static final String OCTET_STREAM_MIME = "application/octet-stream";
private static final String CHARSET_UTF8 = ";charset=UTF-8";
private static final String CHARSET_UTF16 = ";charset=UTF-16";
/**
* Converts mojo NdefMessage to android.nfc.NdefMessage
......@@ -85,37 +82,21 @@ public final class NdefMessageUtils {
return webNdefMessage;
}
/**
* Returns charset of mojo NdefRecord. Only applicable for URL and TEXT records.
* If charset cannot be determined, UTF-8 charset is used by default.
*/
private static String getCharset(NdefRecord record) {
if (record.mediaType.endsWith(CHARSET_UTF8)) return "UTF-8";
// When 16bit WTF::String data is converted to bytearray, it is in LE byte order, without
// BOM. By default, Android interprets UTF-16 charset without BOM as UTF-16BE, thus, use
// UTF-16LE as encoding for text data.
if (record.mediaType.endsWith(CHARSET_UTF16)) return "UTF-16LE";
Log.w(TAG, "Unknown charset, defaulting to UTF-8.");
return "UTF-8";
}
/**
* Converts mojo NdefRecord to android.nfc.NdefRecord
* |record.data| should always be treated as "UTF-8" encoding bytes, this is guaranteed by the
* sender (Blink).
*/
private static android.nfc.NdefRecord toNdefRecord(NdefRecord record)
throws InvalidNdefMessageException, IllegalArgumentException,
UnsupportedEncodingException {
switch (record.recordType) {
case NdefRecordType.URL:
return android.nfc.NdefRecord.createUri(
new String(record.data, getCharset(record)));
return android.nfc.NdefRecord.createUri(new String(record.data, "UTF-8"));
case NdefRecordType.TEXT:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return android.nfc.NdefRecord.createTextRecord(
"en-US", new String(record.data, getCharset(record)));
"en-US", new String(record.data, "UTF-8"));
} else {
return android.nfc.NdefRecord.createMime(TEXT_MIME, record.data);
}
......
......@@ -88,8 +88,6 @@ bindings_modules_generated_union_type_files = [
"$bindings_modules_v8_output_dir/string_or_canvas_gradient_or_canvas_pattern.h",
"$bindings_modules_v8_output_dir/string_or_string_sequence_or_constrain_dom_string_parameters.cc",
"$bindings_modules_v8_output_dir/string_or_string_sequence_or_constrain_dom_string_parameters.h",
"$bindings_modules_v8_output_dir/string_or_unrestricted_double_or_array_buffer_or_dictionary.cc",
"$bindings_modules_v8_output_dir/string_or_unrestricted_double_or_array_buffer_or_dictionary.h",
"$bindings_modules_v8_output_dir/string_or_unsigned_long.cc",
"$bindings_modules_v8_output_dir/string_or_unsigned_long.h",
"$bindings_modules_v8_output_dir/unsigned_long_or_unsigned_long_sequence.cc",
......
......@@ -5,23 +5,57 @@
#include "third_party/blink/renderer/modules/nfc/ndef_message.h"
#include "services/device/public/mojom/nfc.mojom-blink.h"
#include "third_party/blink/renderer/bindings/modules/v8/string_or_array_buffer_or_ndef_message_init.h"
#include "third_party/blink/renderer/modules/nfc/ndef_message_init.h"
#include "third_party/blink/renderer/modules/nfc/ndef_record.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
namespace blink {
// static
NDEFMessage* NDEFMessage::Create(const NDEFMessageInit* init) {
return MakeGarbageCollected<NDEFMessage>(init);
NDEFMessage* NDEFMessage::Create(const NDEFMessageInit* init,
ExceptionState& exception_state) {
NDEFMessage* message = MakeGarbageCollected<NDEFMessage>();
message->url_ = init->url();
if (init->hasRecords()) {
for (const NDEFRecordInit* record_init : init->records()) {
NDEFRecord* record = NDEFRecord::Create(record_init, exception_state);
if (exception_state.HadException())
return nullptr;
DCHECK(record);
message->records_.push_back(record);
}
}
return message;
}
NDEFMessage::NDEFMessage(const NDEFMessageInit* init) : url_(init->url()) {
if (init->hasRecords()) {
for (const NDEFRecordInit* record_init : init->records())
records_.push_back(NDEFRecord::Create(record_init));
// static
NDEFMessage* NDEFMessage::Create(const NDEFMessageSource& source,
ExceptionState& exception_state) {
if (source.IsString()) {
NDEFMessage* message = MakeGarbageCollected<NDEFMessage>();
message->records_.push_back(
MakeGarbageCollected<NDEFRecord>(source.GetAsString()));
return message;
}
if (source.IsArrayBuffer()) {
NDEFMessage* message = MakeGarbageCollected<NDEFMessage>();
message->records_.push_back(
MakeGarbageCollected<NDEFRecord>(source.GetAsArrayBuffer()));
return message;
}
if (source.IsNDEFMessageInit()) {
return Create(source.GetAsNDEFMessageInit(), exception_state);
}
NOTREACHED();
return nullptr;
}
NDEFMessage::NDEFMessage() = default;
NDEFMessage::NDEFMessage(const device::mojom::blink::NDEFMessage& message)
: url_(message.url) {
for (wtf_size_t i = 0; i < message.data.size(); ++i) {
......
......@@ -13,17 +13,22 @@
namespace blink {
class ExceptionState;
class NDEFMessageInit;
class NDEFRecord;
class StringOrArrayBufferOrNDEFMessageInit;
using NDEFMessageSource = StringOrArrayBufferOrNDEFMessageInit;
class MODULES_EXPORT NDEFMessage final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
static NDEFMessage* Create(const NDEFMessageInit*);
static NDEFMessage* Create(const NDEFMessageInit*, ExceptionState&);
static NDEFMessage* Create(const NDEFMessageSource&, ExceptionState&);
NDEFMessage(const NDEFMessageInit*);
NDEFMessage(const device::mojom::blink::NDEFMessage&);
NDEFMessage();
explicit NDEFMessage(const device::mojom::blink::NDEFMessage&);
const String& url() const;
const HeapVector<Member<NDEFRecord>>& records() const;
......
......@@ -8,6 +8,7 @@
RuntimeEnabled=WebNFC,
SecureContext,
Constructor(NDEFMessageInit messageInit),
RaisesException=Constructor,
Exposed=Window
] interface NDEFMessage {
readonly attribute USVString url;
......
......@@ -24,22 +24,32 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
static NDEFRecord* Create(const NDEFRecordInit*);
static NDEFRecord* Create(const NDEFRecordInit*, ExceptionState&);
NDEFRecord(const NDEFRecordInit*);
NDEFRecord(const device::mojom::blink::NDEFRecordPtr&);
// Construct a "text" record from a string.
explicit NDEFRecord(const String&);
// Construct a "opaque" record from an array buffer.
explicit NDEFRecord(DOMArrayBuffer*);
NDEFRecord(const String&, const String&, WTF::Vector<uint8_t>);
explicit NDEFRecord(const device::mojom::blink::NDEFRecordPtr&);
const String& recordType() const;
const String& mediaType() const;
String toText() const;
DOMArrayBuffer* toArrayBuffer() const;
ScriptValue toJSON(ScriptState*, ExceptionState& exception_state) const;
ScriptValue toJSON(ScriptState*, ExceptionState&) const;
const WTF::Vector<uint8_t>& data() const;
void Trace(blink::Visitor*) override;
private:
String record_type_;
String media_type_;
// Holds the NDEFRecord.[[PayloadData]] bytes defined at
// https://w3c.github.io/web-nfc/#the-ndefrecord-interface.
WTF::Vector<uint8_t> data_;
};
......
......@@ -8,6 +8,7 @@
RuntimeEnabled=WebNFC,
SecureContext,
Constructor(NDEFRecordInit recordInit),
RaisesException=Constructor,
Exposed=Window
] interface NDEFRecord {
readonly attribute NDEFRecordType recordType;
......
......@@ -6,7 +6,7 @@
enum NDEFRecordType { "empty", "text", "url", "json", "opaque" };
typedef (DOMString or unrestricted double or ArrayBuffer or Dictionary) NDEFRecordData;
typedef any NDEFRecordData;
dictionary NDEFRecordInit {
NDEFRecordType recordType;
......
......@@ -9,6 +9,7 @@ namespace blink {
const char kNfcJsonMimePostfix[] = "+json";
const char kNfcJsonMimePrefix[] = "application/";
const char kNfcJsonMimeType[] = "application/json";
const char kNfcJsonTextMimeType[] = "text/json";
const char kNfcOpaqueMimeType[] = "application/octet-stream";
const char kNfcPlainTextMimeType[] = "text/plain";
const char kNfcPlainTextMimePrefix[] = "text/";
......@@ -21,15 +22,17 @@ const char kNfcNotSupported[] =
"No NFC adapter or cannot establish connection.";
const char kNfcNotReadable[] = "NFC is not enabled.";
const char kNfcTextRecordTypeError[] =
"The data for 'text' NDEFRecords must be of String or UnrestrctedDouble.";
"The data for 'text' NDEFRecords must be of String.";
const char kNfcSetIdError[] = "Cannot set WebNFC Id.";
const char kNfcTextRecordMediaTypeError[] =
"Invalid media type for 'text' record.";
const char kNfcUrlRecordTypeError[] =
"The data for 'url' NDEFRecord must be of String type.";
const char kNfcUrlRecordParseError[] = "Cannot parse data for 'url' record.";
const char kNfcJsonRecordTypeError[] =
"The data for 'json' NDEFRecord must be of Object type.";
const char kNfcJsonRecordNoDataError[] =
"The data for 'json' NDEFRecord is missing.";
const char kNfcJsonRecordStringifyError[] =
"Cannot stringify data for 'json' record.";
const char kNfcJsonRecordMediaTypeError[] =
"Invalid media type for 'json' record.";
const char kNfcOpaqueRecordTypeError[] =
......
......@@ -5,18 +5,13 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_NFC_NFC_CONSTANTS_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_NFC_NFC_CONSTANTS_H_
#include "third_party/blink/renderer/bindings/modules/v8/string_or_array_buffer_or_ndef_message_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/string_or_unrestricted_double_or_array_buffer_or_dictionary.h"
namespace blink {
using NDEFMessageSource = blink::StringOrArrayBufferOrNDEFMessageInit;
using NDEFRecordData =
blink::StringOrUnrestrictedDoubleOrArrayBufferOrDictionary;
extern const char kNfcJsonMimePostfix[];
extern const char kNfcJsonMimePrefix[];
extern const char kNfcJsonMimeType[];
extern const char kNfcJsonTextMimeType[];
extern const char kNfcOpaqueMimeType[];
extern const char kNfcPlainTextMimeType[];
extern const char kNfcPlainTextMimePrefix[];
......@@ -32,7 +27,8 @@ extern const char kNfcSetIdError[];
extern const char kNfcTextRecordMediaTypeError[];
extern const char kNfcUrlRecordTypeError[];
extern const char kNfcUrlRecordParseError[];
extern const char kNfcJsonRecordTypeError[];
extern const char kNfcJsonRecordNoDataError[];
extern const char kNfcJsonRecordStringifyError[];
extern const char kNfcJsonRecordMediaTypeError[];
extern const char kNfcOpaqueRecordTypeError[];
extern const char kNfcRecordTypeError[];
......
......@@ -6,14 +6,27 @@
#include "third_party/blink/renderer/modules/nfc/ndef_message.h"
#include "third_party/blink/renderer/modules/nfc/nfc_reading_event_init.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
namespace blink {
// static
NFCReadingEvent* NFCReadingEvent::Create(const AtomicString& event_type,
const NFCReadingEventInit* init,
ExceptionState& exception_state) {
NDEFMessage* message = NDEFMessage::Create(init->message(), exception_state);
if (exception_state.HadException())
return nullptr;
DCHECK(message);
return MakeGarbageCollected<NFCReadingEvent>(event_type, init, message);
}
NFCReadingEvent::NFCReadingEvent(const AtomicString& event_type,
const NFCReadingEventInit* initializer)
: Event(event_type, initializer),
serial_number_(initializer->serialNumber()),
message_(NDEFMessage::Create(initializer->message())) {}
const NFCReadingEventInit* init,
NDEFMessage* message)
: Event(event_type, init),
serial_number_(init->serialNumber()),
message_(message) {}
NFCReadingEvent::NFCReadingEvent(const AtomicString& event_type,
const String& serial_number,
......
......@@ -11,6 +11,7 @@
namespace blink {
class ExceptionState;
class NDEFMessage;
class NFCReadingEventInit;
......@@ -18,12 +19,13 @@ class NFCReadingEvent final : public Event {
DEFINE_WRAPPERTYPEINFO();
public:
static NFCReadingEvent* Create(const AtomicString& event_type,
const NFCReadingEventInit* initializer) {
return MakeGarbageCollected<NFCReadingEvent>(event_type, initializer);
}
static NFCReadingEvent* Create(const AtomicString&,
const NFCReadingEventInit*,
ExceptionState&);
NFCReadingEvent(const AtomicString&, const NFCReadingEventInit*);
NFCReadingEvent(const AtomicString&,
const NFCReadingEventInit*,
NDEFMessage*);
NFCReadingEvent(const AtomicString&, const String&, NDEFMessage*);
~NFCReadingEvent() override;
......
......@@ -9,6 +9,7 @@
RuntimeEnabled=WebNFC,
SecureContext,
Constructor(DOMString type, NFCReadingEventInit eventInitDict),
RaisesException=Constructor,
Exposed=Window
] interface NFCReadingEvent : Event {
readonly attribute DOMString serialNumber;
......
......@@ -8,213 +8,45 @@
#include <utility>
#include "services/device/public/mojom/nfc.mojom-blink.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/modules/nfc/ndef_message.h"
#include "third_party/blink/renderer/modules/nfc/ndef_record.h"
#include "third_party/blink/renderer/modules/nfc/nfc_push_options.h"
#include "third_party/blink/renderer/modules/nfc/nfc_scan_options.h"
#include "third_party/blink/renderer/modules/nfc/nfc_utils.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
using device::mojom::blink::NDEFCompatibility;
using device::mojom::blink::NDEFMessage;
using device::mojom::blink::NDEFMessagePtr;
using device::mojom::blink::NDEFRecord;
using device::mojom::blink::NDEFRecordPtr;
using device::mojom::blink::NDEFRecordType;
using device::mojom::blink::NDEFRecordTypeFilter;
using device::mojom::blink::NFCPushOptions;
using device::mojom::blink::NFCPushOptionsPtr;
using device::mojom::blink::NFCPushTarget;
using device::mojom::blink::NFCScanOptions;
using device::mojom::blink::NFCScanOptionsPtr;
using WTF::String;
namespace {
void SetMediaType(NDEFRecordPtr& recordPtr,
const String& recordMediaType,
const String& defaultMediaType) {
recordPtr->media_type =
recordMediaType.IsEmpty() ? defaultMediaType : recordMediaType;
}
} // anonymous namespace
// Mojo type converters
namespace mojo {
Vector<uint8_t> TypeConverter<Vector<uint8_t>, String>::Convert(
const String& string) {
StringUTF8Adaptor utf8_string(string);
Vector<uint8_t> array;
array.Append(utf8_string.data(), utf8_string.size());
return array;
}
Vector<uint8_t> TypeConverter<Vector<uint8_t>, blink::DOMArrayBuffer*>::Convert(
blink::DOMArrayBuffer* buffer) {
Vector<uint8_t> array;
array.Append(static_cast<uint8_t*>(buffer->Data()), buffer->ByteLength());
return array;
}
NDEFRecordPtr TypeConverter<NDEFRecordPtr, String>::Convert(
const String& string) {
NDEFRecordPtr record = NDEFRecord::New();
record->record_type = NDEFRecordType::TEXT;
record->media_type =
StringView(blink::kNfcPlainTextMimeType) + blink::kNfcCharSetUTF8;
record->data = mojo::ConvertTo<Vector<uint8_t>>(string);
return record;
}
NDEFRecordPtr TypeConverter<NDEFRecordPtr, blink::DOMArrayBuffer*>::Convert(
blink::DOMArrayBuffer* buffer) {
NDEFRecordPtr record = NDEFRecord::New();
record->record_type = NDEFRecordType::OPAQUE_RECORD;
record->media_type = blink::kNfcOpaqueMimeType;
record->data = mojo::ConvertTo<Vector<uint8_t>>(buffer);
return record;
}
NDEFMessagePtr TypeConverter<NDEFMessagePtr, String>::Convert(
const String& string) {
NDEFMessagePtr message = NDEFMessage::New();
message->data.push_back(NDEFRecord::From(string));
return message;
}
base::Optional<Vector<uint8_t>>
TypeConverter<base::Optional<Vector<uint8_t>>, blink::NDEFRecordData>::Convert(
const blink::NDEFRecordData& value) {
if (value.IsUnrestrictedDouble()) {
return mojo::ConvertTo<Vector<uint8_t>>(
String::Number(value.GetAsUnrestrictedDouble()));
}
if (value.IsString()) {
return mojo::ConvertTo<Vector<uint8_t>>(value.GetAsString());
}
// TODO(https://crbug.com/520391): Remove this duplicate code for stringifying
// json, by reusing the same code in NDEFRecord ctor. i.e. Make users of this
// type converter:
// 1. first construct a NDEFRecord from the given NDEFRecordInit, the
// NDEFRecord ctor would stringify json.
// 2. then convert the NDEFRecord into an mojom::NDEFRecordPtr, which should
// be just simple.
if (value.IsDictionary()) {
v8::Local<v8::String> jsonString;
blink::Dictionary dictionary = value.GetAsDictionary();
v8::Isolate* isolate = dictionary.GetIsolate();
v8::TryCatch try_catch(isolate);
// https://w3c.github.io/web-nfc/#mapping-json-to-ndef
// If serialization throws, reject promise with a "SyntaxError" exception.
if (!v8::JSON::Stringify(dictionary.V8Context(), dictionary.V8Value())
.ToLocal(&jsonString) ||
try_catch.HasCaught()) {
return base::nullopt;
}
String string =
blink::ToBlinkString<String>(jsonString, blink::kDoNotExternalize);
return mojo::ConvertTo<Vector<uint8_t>>(string);
}
if (value.IsArrayBuffer()) {
return mojo::ConvertTo<Vector<uint8_t>>(value.GetAsArrayBuffer());
}
return base::nullopt;
NDEFRecordPtr TypeConverter<NDEFRecordPtr, ::blink::NDEFRecord*>::Convert(
const ::blink::NDEFRecord* record) {
return NDEFRecord::New(blink::StringToNDEFRecordType(record->recordType()),
record->mediaType(), record->data());
}
NDEFRecordPtr TypeConverter<NDEFRecordPtr, blink::NDEFRecordInit*>::Convert(
const blink::NDEFRecordInit* record) {
NDEFRecordPtr recordPtr = NDEFRecord::New();
if (record->hasRecordType()) {
recordPtr->record_type =
blink::StringToNDEFRecordType(record->recordType());
} else {
recordPtr->record_type = blink::DeduceRecordTypeFromDataType(record);
}
// If record type is "empty", no need to set media type or data.
// https://w3c.github.io/web-nfc/#creating-web-nfc-message
if (recordPtr->record_type == NDEFRecordType::EMPTY)
return recordPtr;
switch (recordPtr->record_type) {
case NDEFRecordType::TEXT:
case NDEFRecordType::URL:
SetMediaType(recordPtr, record->mediaType(),
blink::kNfcPlainTextMimeType);
recordPtr->media_type = recordPtr->media_type + blink::kNfcCharSetUTF8;
break;
case NDEFRecordType::JSON:
SetMediaType(recordPtr, record->mediaType(), blink::kNfcJsonMimeType);
break;
case NDEFRecordType::OPAQUE_RECORD:
SetMediaType(recordPtr, record->mediaType(), blink::kNfcOpaqueMimeType);
break;
default:
NOTREACHED();
break;
}
auto recordData =
mojo::ConvertTo<base::Optional<Vector<uint8_t>>>(record->data());
// If JS object cannot be converted to uint8_t array, return null,
// interrupt NDEFMessage conversion algorithm and reject promise with
// SyntaxError exception.
if (!recordData)
return nullptr;
recordPtr->data = recordData.value();
return recordPtr;
}
NDEFMessagePtr TypeConverter<NDEFMessagePtr, blink::NDEFMessageInit*>::Convert(
const blink::NDEFMessageInit* message) {
NDEFMessagePtr TypeConverter<NDEFMessagePtr, ::blink::NDEFMessage*>::Convert(
const ::blink::NDEFMessage* message) {
NDEFMessagePtr messagePtr = NDEFMessage::New();
messagePtr->url = message->url();
messagePtr->data.resize(message->records().size());
for (wtf_size_t i = 0; i < message->records().size(); ++i) {
NDEFRecordPtr record = NDEFRecord::From(message->records()[i].Get());
if (record.is_null())
return nullptr;
DCHECK(record);
messagePtr->data[i] = std::move(record);
}
return messagePtr;
}
NDEFMessagePtr TypeConverter<NDEFMessagePtr, blink::DOMArrayBuffer*>::Convert(
blink::DOMArrayBuffer* buffer) {
NDEFMessagePtr message = NDEFMessage::New();
message->data.push_back(NDEFRecord::From(buffer));
return message;
}
NDEFMessagePtr TypeConverter<NDEFMessagePtr, blink::NDEFMessageSource>::Convert(
const blink::NDEFMessageSource& message) {
if (message.IsString())
return NDEFMessage::From(message.GetAsString());
if (message.IsNDEFMessageInit())
return NDEFMessage::From(message.GetAsNDEFMessageInit());
if (message.IsArrayBuffer())
return NDEFMessage::From(message.GetAsArrayBuffer());
NOTREACHED();
return nullptr;
}
NFCPushOptionsPtr
TypeConverter<NFCPushOptionsPtr, const blink::NFCPushOptions*>::Convert(
const blink::NFCPushOptions* pushOptions) {
......
......@@ -7,103 +7,46 @@
#include "mojo/public/cpp/bindings/type_converter.h"
#include "services/device/public/mojom/nfc.mojom-blink-forward.h"
#include "third_party/blink/renderer/modules/nfc/nfc_constants.h"
#include "third_party/blink/renderer/platform/wtf/forward.h"
namespace blink {
class DOMArrayBuffer;
class NDEFRecordInit;
class NDEFMessageInit;
class NDEFRecord;
class NDEFMessage;
class NFCScanOptions;
class NFCPushOptions;
} // namespace blink
// Mojo type converters
namespace mojo {
template <>
struct TypeConverter<Vector<uint8_t>, WTF::String> {
static Vector<uint8_t> Convert(const WTF::String& string);
};
template <>
struct TypeConverter<Vector<uint8_t>, blink::DOMArrayBuffer*> {
static Vector<uint8_t> Convert(blink::DOMArrayBuffer* buffer);
};
template <>
struct TypeConverter<device::mojom::blink::NDEFRecordPtr, WTF::String> {
static device::mojom::blink::NDEFRecordPtr Convert(const WTF::String& string);
};
template <>
struct TypeConverter<device::mojom::blink::NDEFRecordPtr,
blink::DOMArrayBuffer*> {
::blink::NDEFRecord*> {
static device::mojom::blink::NDEFRecordPtr Convert(
blink::DOMArrayBuffer* buffer);
};
template <>
struct TypeConverter<device::mojom::blink::NDEFMessagePtr, WTF::String> {
static device::mojom::blink::NDEFMessagePtr Convert(
const WTF::String& string);
};
template <>
struct TypeConverter<base::Optional<Vector<uint8_t>>, blink::NDEFRecordData> {
static base::Optional<Vector<uint8_t>> Convert(
const blink::NDEFRecordData& value);
};
template <>
struct TypeConverter<device::mojom::blink::NDEFRecordPtr,
blink::NDEFRecordInit*> {
static device::mojom::blink::NDEFRecordPtr Convert(
const blink::NDEFRecordInit* record);
};
template <>
struct TypeConverter<device::mojom::blink::NDEFMessagePtr,
blink::NDEFMessageInit*> {
static device::mojom::blink::NDEFMessagePtr Convert(
const blink::NDEFMessageInit* message);
};
template <>
struct TypeConverter<device::mojom::blink::NDEFMessagePtr,
blink::DOMArrayBuffer*> {
static device::mojom::blink::NDEFMessagePtr Convert(
blink::DOMArrayBuffer* buffer);
const ::blink::NDEFRecord* record);
};
// TODO(https://crbug.com/520391): Add a creator function like
// static NDEFMessage* NDEFMessage::Create(const NDEFMessageSource&);
// which constructs NDEFMessage with
// - validity check of the input NDEFMessageSource, and
// - data serialization of each record (duplicated with code of this type
// converter),
// after that we can convert the NDEFMessage easily to a
// device::mojom::blink::NDEFMessagePtr. This enables us to remove this type
// converter and a bunch of sub converters it depends on.
template <>
struct TypeConverter<device::mojom::blink::NDEFMessagePtr,
blink::NDEFMessageSource> {
::blink::NDEFMessage*> {
static device::mojom::blink::NDEFMessagePtr Convert(
const blink::NDEFMessageSource& message);
const ::blink::NDEFMessage* message);
};
template <>
struct TypeConverter<device::mojom::blink::NFCPushOptionsPtr,
const blink::NFCPushOptions*> {
const ::blink::NFCPushOptions*> {
static device::mojom::blink::NFCPushOptionsPtr Convert(
const blink::NFCPushOptions* pushOptions);
const ::blink::NFCPushOptions* pushOptions);
};
template <>
struct TypeConverter<device::mojom::blink::NFCScanOptionsPtr,
const blink::NFCScanOptions*> {
const ::blink::NFCScanOptions*> {
static device::mojom::blink::NFCScanOptionsPtr Convert(
const blink::NFCScanOptions* scanOptions);
const ::blink::NFCScanOptions* scanOptions);
};
} // namespace mojo
......
......@@ -20,193 +20,6 @@ using device::mojom::blink::NFCPushTarget;
namespace blink {
ScriptPromise RejectIfInvalidTextRecord(ScriptState* script_state,
const NDEFRecordInit* record) {
const NDEFRecordData& value = record->data();
if (!value.IsString() && !(value.IsUnrestrictedDouble() &&
!std::isnan(value.GetAsUnrestrictedDouble()))) {
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcTextRecordTypeError));
}
if (record->hasMediaType() &&
!record->mediaType().StartsWithIgnoringASCIICase(
kNfcPlainTextMimePrefix)) {
return ScriptPromise::RejectWithDOMException(
script_state,
MakeGarbageCollected<DOMException>(DOMExceptionCode::kSyntaxError,
kNfcTextRecordMediaTypeError));
}
return ScriptPromise();
}
ScriptPromise RejectIfInvalidURLRecord(ScriptState* script_state,
const NDEFRecordInit* record) {
if (!record->data().IsString()) {
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcUrlRecordTypeError));
}
if (!KURL(NullURL(), record->data().GetAsString()).IsValid()) {
return ScriptPromise::RejectWithDOMException(
script_state,
MakeGarbageCollected<DOMException>(DOMExceptionCode::kSyntaxError,
kNfcUrlRecordParseError));
}
return ScriptPromise();
}
ScriptPromise RejectIfInvalidJSONRecord(ScriptState* script_state,
const NDEFRecordInit* record) {
if (!record->data().IsDictionary()) {
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcJsonRecordTypeError));
}
// If JSON record has media type, it must be equal to "application/json" or
// start with "application/" and end with "+json".
if (record->hasMediaType() &&
(record->mediaType() != kNfcJsonMimeType &&
!(record->mediaType().StartsWithIgnoringASCIICase(kNfcJsonMimePrefix) &&
record->mediaType().EndsWithIgnoringASCIICase(kNfcJsonMimePostfix)))) {
return ScriptPromise::RejectWithDOMException(
script_state,
MakeGarbageCollected<DOMException>(DOMExceptionCode::kSyntaxError,
kNfcJsonRecordMediaTypeError));
}
return ScriptPromise();
}
ScriptPromise RejectIfInvalidOpaqueRecord(ScriptState* script_state,
const NDEFRecordInit* record) {
if (!record->data().IsArrayBuffer()) {
return ScriptPromise::Reject(
script_state,
V8ThrowException::CreateTypeError(script_state->GetIsolate(),
kNfcOpaqueRecordTypeError));
}
return ScriptPromise();
}
ScriptPromise RejectIfInvalidNDEFRecord(ScriptState* script_state,
const NDEFRecordInit* record) {
NDEFRecordType type;
if (record->hasRecordType()) {
type = StringToNDEFRecordType(record->recordType());
} else {
type = DeduceRecordTypeFromDataType(record);
// https://w3c.github.io/web-nfc/#creating-web-nfc-message
// If NDEFRecord.recordType is not set and record type cannot be deduced
// from NDEFRecord.data, reject promise with TypeError.
if (type == NDEFRecordType::EMPTY) {
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcRecordTypeError));
}
}
// Non-empty records must have data.
if (!record->hasData() && (type != NDEFRecordType::EMPTY)) {
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcRecordDataError));
}
switch (type) {
case NDEFRecordType::TEXT:
return RejectIfInvalidTextRecord(script_state, record);
case NDEFRecordType::URL:
return RejectIfInvalidURLRecord(script_state, record);
case NDEFRecordType::JSON:
return RejectIfInvalidJSONRecord(script_state, record);
case NDEFRecordType::OPAQUE_RECORD:
return RejectIfInvalidOpaqueRecord(script_state, record);
case NDEFRecordType::EMPTY:
return ScriptPromise();
}
NOTREACHED();
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcRecordError));
}
ScriptPromise RejectIfInvalidNDEFRecordArray(
ScriptState* script_state,
const HeapVector<Member<NDEFRecordInit>>& records) {
for (const auto& record : records) {
ScriptPromise isValidRecord =
RejectIfInvalidNDEFRecord(script_state, record);
if (!isValidRecord.IsEmpty())
return isValidRecord;
}
return ScriptPromise();
}
ScriptPromise RejectIfInvalidNDEFMessageSource(
ScriptState* script_state,
const NDEFMessageSource& push_message) {
// If NDEFMessageSource of invalid type, reject promise with TypeError
if (!push_message.IsNDEFMessageInit() && !push_message.IsString() &&
!push_message.IsArrayBuffer()) {
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcMsgTypeError));
}
if (push_message.IsNDEFMessageInit()) {
// https://w3c.github.io/web-nfc/#the-push-method
// If NDEFMessage.records is empty, reject promise with TypeError
const NDEFMessageInit* message = push_message.GetAsNDEFMessageInit();
if (!message->hasRecords() || message->records().IsEmpty()) {
return ScriptPromise::Reject(
script_state, V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcEmptyMsg));
}
return RejectIfInvalidNDEFRecordArray(script_state, message->records());
}
return ScriptPromise();
}
// https://w3c.github.io/web-nfc/#creating-web-nfc-message Step 2.1
// If NDEFRecord type is not provided, deduce NDEFRecord type from JS data type:
// String or Number => 'text' record
// ArrayBuffer => 'opaque' record
// Dictionary, JSON serializable Object => 'json' record
NDEFRecordType DeduceRecordTypeFromDataType(
const blink::NDEFRecordInit* record) {
if (record->hasData()) {
const blink::NDEFRecordData& value = record->data();
if (value.IsString() || (value.IsUnrestrictedDouble() &&
!std::isnan(value.GetAsUnrestrictedDouble()))) {
return NDEFRecordType::TEXT;
}
if (value.IsDictionary()) {
return NDEFRecordType::JSON;
}
if (value.IsArrayBuffer()) {
return NDEFRecordType::OPAQUE_RECORD;
}
}
return NDEFRecordType::EMPTY;
}
size_t GetNDEFMessageSize(const device::mojom::blink::NDEFMessagePtr& message) {
size_t message_size = message->url.CharactersSizeInBytes();
for (wtf_size_t i = 0; i < message->data.size(); ++i) {
......
......@@ -16,32 +16,6 @@ namespace blink {
class DOMException;
ScriptPromise RejectIfInvalidTextRecord(ScriptState* script_state,
const NDEFRecordInit* record);
ScriptPromise RejectIfInvalidURLRecord(ScriptState* script_state,
const NDEFRecordInit* record);
ScriptPromise RejectIfInvalidJSONRecord(ScriptState* script_state,
const NDEFRecordInit* record);
ScriptPromise RejectIfInvalidOpaqueRecord(ScriptState* script_state,
const NDEFRecordInit* record);
ScriptPromise RejectIfInvalidNDEFRecord(ScriptState* script_state,
const NDEFRecordInit* record);
ScriptPromise RejectIfInvalidNDEFRecordArray(
ScriptState* script_state,
const HeapVector<Member<NDEFRecordInit>>& records);
ScriptPromise RejectIfInvalidNDEFMessageSource(
ScriptState* script_state,
const NDEFMessageSource& push_message);
device::mojom::blink::NDEFRecordType DeduceRecordTypeFromDataType(
const blink::NDEFRecordInit* record);
size_t GetNDEFMessageSize(const device::mojom::blink::NDEFMessagePtr& message);
bool SetNDEFMessageURL(const String& origin,
......
......@@ -8,6 +8,7 @@
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/modules/v8/string_or_array_buffer_or_ndef_message_init.h"
#include "third_party/blink/renderer/core/dom/abort_signal.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/modules/nfc/nfc_push_options.h"
......@@ -35,7 +36,8 @@ void NFCWriter::Trace(blink::Visitor* visitor) {
// https://w3c.github.io/web-nfc/#the-push-method
ScriptPromise NFCWriter::push(ScriptState* script_state,
const NDEFMessageSource& push_message,
const NFCPushOptions* options) {
const NFCPushOptions* options,
ExceptionState& exception_state) {
ExecutionContext* execution_context = GetExecutionContext();
// https://w3c.github.io/web-nfc/#security-policies
// WebNFC API must be only accessible from top level browsing context.
......@@ -54,11 +56,6 @@ ScriptPromise NFCWriter::push(ScriptState* script_state,
DOMExceptionCode::kAbortError, kNfcCancelled));
}
ScriptPromise is_valid_message =
RejectIfInvalidNDEFMessageSource(script_state, push_message);
if (!is_valid_message.IsEmpty())
return is_valid_message;
// 9. If timeout value is NaN or negative, reject promise with "TypeError"
// and abort these steps.
if (options->hasTimeout() &&
......@@ -68,13 +65,24 @@ ScriptPromise NFCWriter::push(ScriptState* script_state,
script_state->GetIsolate(), kNfcInvalidPushTimeout));
}
auto message = device::mojom::blink::NDEFMessage::From(push_message);
if (!message) {
return ScriptPromise::RejectWithDOMException(
script_state, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kSyntaxError, kNfcMsgConvertError));
// Step 10.8: Run "create Web NFC message", if this throws an exception,
// reject p with that exception and abort these steps.
NDEFMessage* ndef_message =
NDEFMessage::Create(push_message, exception_state);
if (exception_state.HadException()) {
return ScriptPromise();
}
// If NDEFMessage.records is empty, reject promise with TypeError
if (ndef_message->records().size() == 0) {
return ScriptPromise::Reject(script_state,
V8ThrowException::CreateTypeError(
script_state->GetIsolate(), kNfcEmptyMsg));
}
auto message = device::mojom::blink::NDEFMessage::From(ndef_message);
DCHECK(message);
if (!SetNDEFMessageURL(execution_context->GetSecurityOrigin()->ToString(),
message)) {
return ScriptPromise::RejectWithDOMException(
......
......@@ -15,8 +15,12 @@
namespace blink {
class NFCPushOptions;
class ExceptionState;
class ExecutionContext;
class ScriptPromise;
class StringOrArrayBufferOrNDEFMessageInit;
using NDEFMessageSource = StringOrArrayBufferOrNDEFMessageInit;
class NFCWriter : public ScriptWrappable, public ContextClient {
DEFINE_WRAPPERTYPEINFO();
......@@ -33,7 +37,8 @@ class NFCWriter : public ScriptWrappable, public ContextClient {
// Pushes NDEFMessageSource asynchronously to NFC tag / peer.
ScriptPromise push(ScriptState*,
const NDEFMessageSource&,
const NFCPushOptions*);
const NFCPushOptions*,
ExceptionState&);
// Called by NFCProxy for notification about connection error.
void OnMojoConnectionError();
......
......@@ -15,5 +15,5 @@ typedef (DOMString or ArrayBuffer or NDEFMessageInit) NDEFMessageSource;
ConstructorCallWith=ExecutionContext,
Exposed=Window
] interface NFCWriter {
[CallWith=ScriptState] Promise<void> push (NDEFMessageSource message, optional NFCPushOptions options);
[CallWith=ScriptState, RaisesException] Promise<void> push (NDEFMessageSource message, optional NFCPushOptions options);
};
......@@ -12,12 +12,8 @@
}, 'NDEFRecord constructor without init dict');
test(() => {
const record = new NDEFRecord(null);
assert_equals(record.recordType.length, 0, 'empty recordType');
assert_equals(record.mediaType.length, 0, 'empty mediaType');
assert_equals(record.toText(), null, 'toText() returns null');
assert_equals(record.toArrayBuffer(), null, 'toArrayBuffer() returns null');
assert_equals(record.toJSON(), null, 'toJSON() returns null');
assert_throws(new TypeError, () => new NDEFRecord(null),
"The record has neither type nor data.");
}, 'NDEFRecord constructor with null init dict');
test(() => {
......
......@@ -23,19 +23,15 @@ const invalid_type_messages =
// NDEFRecord must have data.
createMessage([createTextRecord()]),
// NDEFRecord.data for 'text' record must be number or string.
// NDEFRecord.data for 'text' record must be a string.
createMessage([createTextRecord(test_buffer_data)]),
createMessage([createTextRecord(test_json_data)]),
createMessage([createTextRecord(test_number_data)]),
// https://w3c.github.io/web-nfc/#dfn-map-a-json-object-to-ndef
// NDEFRecord must have data.
createMessage([createJsonRecord()]),
// NDEFRecord.data for 'json' record must be object.
createMessage([createJsonRecord(test_buffer_data)]),
createMessage([createJsonRecord(test_number_data)]),
createMessage([createJsonRecord(test_text_data)]),
// https://w3c.github.io/web-nfc/#dfn-map-a-url-to-ndef
// NDEFRecord must have data.
createMessage([createUrlRecord()]),
......@@ -59,16 +55,16 @@ const invalid_syntax_messages =
[
// NDEFRecord.mediaType for 'text' record must be 'text/*'.
createMessage([createRecord('text', 'application/json',
test_number_data)]),
test_text_data)]),
// Data for 'url' record, must be a valid URL.
createMessage([createUrlRecord('Invalid URL:// Data')]),
// NDEFRecord.mediaType for 'json' record must be 'application/json' or
// starts with 'application/' and ends with '+json'.
// A JSON MIME type is any MIME type whose subtype ends in "+json" or
// whose essence is "application/json" or "text/json".
createMessage([createRecord('json', 'image/png', test_json_data)]),
createMessage([createRecord('json', 'application/x+y', test_json_data)]),
createMessage([createRecord('json', 'custom/app+json', test_json_data)]),
createMessage([createRecord('json', 'custom/app+jsonx', test_json_data)]),
];
const invalid_signals = [
......@@ -195,8 +191,8 @@ promise_test(async t => {
const writer = new NFCWriter();
const message = createMessage([createRecord('json','application/json',
{ get x(){ return this; } })]);
await promise_rejects(t, 'SyntaxError', writer.push(message));
}, "Reject promise with SyntaxError if 'json' record cannot be serialized.");
await promise_rejects(t, new TypeError(), writer.push(message));
}, "Reject promise with exceptions thrown from serializing the 'json' record data.");
promise_test(async t => {
const writer = new NFCWriter();
......@@ -251,8 +247,8 @@ nfc_test(async (t, mockNFC) => {
const writer = new NFCWriter();
let message = createMessage([createTextRecord(test_text_data),
createJsonRecord(test_json_data),
createJsonRecord(test_number_data),
createOpaqueRecord(test_buffer_data),
createTextRecord(test_number_data),
createUrlRecord(test_url_data)],
test_message_origin);
await writer.push(message);
......
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