Commit d80e1fc8 authored by Francois Beaufort's avatar Francois Beaufort Committed by Commit Bot

[WebNFC] Support writing NDEFRecord attributes "encoding" and "lang".

This CL adds support for writing the new NDEFRecord encoding and lang
attributes as specified in https://github.com/w3c/web-nfc/pull/381

It also makes sure ArrayBuffer and ArrayBufferView are valid types for
NDEFRecord data when used in a text record.

Bug: 520391
Change-Id: Ic159ee77eae5d2bcf20857bf533e41cef80db5cb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1876390
Commit-Queue: François Beaufort <beaufort.francois@gmail.com>
Reviewed-by: default avatarRijubrata Bhaumik <rijubrata.bhaumik@intel.com>
Cr-Commit-Position: refs/heads/master@{#710823}
parent 410a08a8
......@@ -5,13 +5,14 @@
package org.chromium.device.nfc;
import android.net.Uri;
import android.os.Build;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.device.mojom.NdefMessage;
import org.chromium.device.mojom.NdefRecord;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
......@@ -102,8 +103,8 @@ public final class NdefMessageUtils {
/**
* 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).
* |record.data| can safely be treated as "UTF-8" encoding bytes for non text records, this is
* guaranteed by the sender (Blink).
*/
private static android.nfc.NdefRecord toNdefRecord(NdefRecord record)
throws InvalidNdefMessageException, IllegalArgumentException,
......@@ -112,12 +113,9 @@ public final class NdefMessageUtils {
case RECORD_TYPE_URL:
return android.nfc.NdefRecord.createUri(new String(record.data, "UTF-8"));
case RECORD_TYPE_TEXT:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return android.nfc.NdefRecord.createTextRecord(
"en-US", new String(record.data, "UTF-8"));
} else {
return android.nfc.NdefRecord.createMime(TEXT_MIME, record.data);
}
byte[] payload = createPayloadForTextRecord(record);
return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_WELL_KNOWN,
android.nfc.NdefRecord.RTD_TEXT, null, payload);
case RECORD_TYPE_JSON:
case RECORD_TYPE_OPAQUE:
return android.nfc.NdefRecord.createMime(record.mediaType, record.data);
......@@ -306,4 +304,19 @@ public final class NdefMessageUtils {
return new PairOfDomainAndType(domain, type);
}
private static byte[] createPayloadForTextRecord(NdefRecord record)
throws UnsupportedEncodingException {
byte[] languageCodeBytes = record.lang.getBytes(StandardCharsets.US_ASCII);
ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + record.data.length);
// Lang length is always less than 64 as it is guaranteed by Blink.
byte status = (byte) languageCodeBytes.length;
if (!record.encoding.equals(ENCODING_UTF8)) {
status |= (byte) (1 << 7);
}
buffer.put(status);
buffer.put(languageCodeBytes);
buffer.put(record.data);
return buffer.array();
}
}
......@@ -4,6 +4,7 @@
package org.chromium.device.nfc;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
......@@ -60,6 +61,9 @@ import org.chromium.testing.local.LocalRobolectricTestRunner;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Unit tests for NfcImpl and NdefMessageUtils classes.
......@@ -99,6 +103,7 @@ public class NFCTest {
private static final String JSON_MIME = "application/json";
private static final String OCTET_STREAM_MIME = "application/octet-stream";
private static final String ENCODING_UTF8 = "utf-8";
private static final String ENCODING_UTF16 = "utf-16";
private static final String LANG_EN_US = "en-US";
/**
......@@ -239,19 +244,41 @@ public class NFCTest {
assertNull(urlMojoNdefMessage.data[0].lang);
assertEquals(TEST_URL, new String(urlMojoNdefMessage.data[0].data));
// Test TEXT record conversion.
android.nfc.NdefMessage textNdefMessage = new android.nfc.NdefMessage(
// Test TEXT record conversion for UTF-8 content.
android.nfc.NdefMessage utf8TextNdefMessage = new android.nfc.NdefMessage(
android.nfc.NdefRecord.createTextRecord(LANG_EN_US, TEST_TEXT));
NdefMessage textMojoNdefMessage = NdefMessageUtils.toNdefMessage(textNdefMessage);
assertNull(textMojoNdefMessage.url);
assertEquals(1, textMojoNdefMessage.data.length);
assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, textMojoNdefMessage.data[0].recordType);
assertEquals(TEXT_MIME, textMojoNdefMessage.data[0].mediaType);
assertEquals(true, textMojoNdefMessage.data[0].id.isEmpty());
// TODO(beaufort.francois): Find a way to test UTF-16 encoding record conversion.
assertEquals(ENCODING_UTF8, textMojoNdefMessage.data[0].encoding);
assertEquals(LANG_EN_US, textMojoNdefMessage.data[0].lang);
assertEquals(TEST_TEXT, new String(textMojoNdefMessage.data[0].data));
NdefMessage utf8TextMojoNdefMessage = NdefMessageUtils.toNdefMessage(utf8TextNdefMessage);
assertNull(utf8TextMojoNdefMessage.url);
assertEquals(1, utf8TextMojoNdefMessage.data.length);
assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, utf8TextMojoNdefMessage.data[0].recordType);
assertEquals(TEXT_MIME, utf8TextMojoNdefMessage.data[0].mediaType);
assertEquals(true, utf8TextMojoNdefMessage.data[0].id.isEmpty());
assertEquals(ENCODING_UTF8, utf8TextMojoNdefMessage.data[0].encoding);
assertEquals(LANG_EN_US, utf8TextMojoNdefMessage.data[0].lang);
assertEquals(TEST_TEXT, new String(utf8TextMojoNdefMessage.data[0].data, "UTF-8"));
// Test TEXT record conversion for UTF-16 content.
byte[] textBytes = TEST_TEXT.getBytes(StandardCharsets.UTF_16BE);
byte[] languageCodeBytes = LANG_EN_US.getBytes(StandardCharsets.US_ASCII);
ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + textBytes.length);
byte status = (byte) languageCodeBytes.length;
status |= (byte) (1 << 7);
buffer.put(status);
buffer.put(languageCodeBytes);
buffer.put(textBytes);
android.nfc.NdefMessage utf16TextNdefMessage = new android.nfc.NdefMessage(
new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_WELL_KNOWN,
android.nfc.NdefRecord.RTD_TEXT, null, buffer.array()));
NdefMessage utf16TextMojoNdefMessage = NdefMessageUtils.toNdefMessage(utf16TextNdefMessage);
assertNull(utf16TextMojoNdefMessage.url);
assertEquals(1, utf16TextMojoNdefMessage.data.length);
assertEquals(
NdefMessageUtils.RECORD_TYPE_TEXT, utf16TextMojoNdefMessage.data[0].recordType);
assertEquals(TEXT_MIME, utf16TextMojoNdefMessage.data[0].mediaType);
assertEquals(true, utf16TextMojoNdefMessage.data[0].id.isEmpty());
assertEquals(ENCODING_UTF16, utf16TextMojoNdefMessage.data[0].encoding);
assertEquals(LANG_EN_US, utf16TextMojoNdefMessage.data[0].lang);
assertEquals(TEST_TEXT, new String(utf16TextMojoNdefMessage.data[0].data, "UTF-16"));
// Test MIME record conversion.
android.nfc.NdefMessage mimeNdefMessage =
......@@ -265,7 +292,7 @@ public class NFCTest {
assertEquals(true, mimeMojoNdefMessage.data[0].id.isEmpty());
assertNull(mimeMojoNdefMessage.data[0].encoding);
assertNull(mimeMojoNdefMessage.data[0].lang);
assertEquals(TEST_TEXT, new String(textMojoNdefMessage.data[0].data));
assertEquals(TEST_TEXT, new String(mimeMojoNdefMessage.data[0].data));
// Test JSON record conversion.
android.nfc.NdefMessage jsonNdefMessage =
......@@ -348,21 +375,62 @@ public class NFCTest {
new String(urlNdefMessage.getRecords()[1].getType())
.compareToIgnoreCase(AUTHOR_RECORD_DOMAIN + ":" + AUTHOR_RECORD_TYPE));
// Test TEXT record conversion.
NdefRecord textMojoNdefRecord = new NdefRecord();
textMojoNdefRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT;
textMojoNdefRecord.mediaType = TEXT_MIME;
textMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
NdefMessage textMojoNdefMessage = createMojoNdefMessage(TEST_URL, textMojoNdefRecord);
android.nfc.NdefMessage textNdefMessage =
NdefMessageUtils.toNdefMessage(textMojoNdefMessage);
assertEquals(2, textNdefMessage.getRecords().length);
short tnf = textNdefMessage.getRecords()[0].getTnf();
boolean isWellKnownOrMime = tnf == android.nfc.NdefRecord.TNF_WELL_KNOWN
|| tnf == android.nfc.NdefRecord.TNF_MIME_MEDIA;
assertEquals(true, isWellKnownOrMime);
assertEquals(
android.nfc.NdefRecord.TNF_EXTERNAL_TYPE, textNdefMessage.getRecords()[1].getTnf());
// Test TEXT record conversion for UTF-8 content.
NdefRecord utf8TextMojoNdefRecord = new NdefRecord();
utf8TextMojoNdefRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT;
utf8TextMojoNdefRecord.mediaType = TEXT_MIME;
utf8TextMojoNdefRecord.encoding = ENCODING_UTF8;
utf8TextMojoNdefRecord.lang = LANG_EN_US;
utf8TextMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
NdefMessage utf8TextMojoNdefMessage =
createMojoNdefMessage(TEST_URL, utf8TextMojoNdefRecord);
android.nfc.NdefMessage utf8TextNdefMessage =
NdefMessageUtils.toNdefMessage(utf8TextMojoNdefMessage);
assertEquals(2, utf8TextNdefMessage.getRecords().length);
assertEquals(android.nfc.NdefRecord.TNF_WELL_KNOWN,
utf8TextNdefMessage.getRecords()[0].getTnf());
{
byte[] languageCodeBytes = LANG_EN_US.getBytes(StandardCharsets.US_ASCII);
ByteBuffer expectedPayload = ByteBuffer.allocate(
1 + languageCodeBytes.length + utf8TextMojoNdefRecord.data.length);
byte status = (byte) languageCodeBytes.length;
expectedPayload.put(status);
expectedPayload.put(languageCodeBytes);
expectedPayload.put(utf8TextMojoNdefRecord.data);
assertArrayEquals(
expectedPayload.array(), utf8TextNdefMessage.getRecords()[0].getPayload());
}
assertEquals(android.nfc.NdefRecord.TNF_EXTERNAL_TYPE,
utf8TextNdefMessage.getRecords()[1].getTnf());
// Test TEXT record conversion for UTF-16 content.
NdefRecord utf16TextMojoNdefRecord = new NdefRecord();
utf16TextMojoNdefRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT;
utf16TextMojoNdefRecord.mediaType = TEXT_MIME;
utf16TextMojoNdefRecord.encoding = ENCODING_UTF16;
utf16TextMojoNdefRecord.lang = LANG_EN_US;
utf16TextMojoNdefRecord.data = TEST_TEXT.getBytes(Charset.forName("UTF-16"));
NdefMessage utf16TextMojoNdefMessage =
createMojoNdefMessage(TEST_URL, utf16TextMojoNdefRecord);
android.nfc.NdefMessage utf16TextNdefMessage =
NdefMessageUtils.toNdefMessage(utf16TextMojoNdefMessage);
assertEquals(2, utf16TextNdefMessage.getRecords().length);
assertEquals(android.nfc.NdefRecord.TNF_WELL_KNOWN,
utf16TextNdefMessage.getRecords()[0].getTnf());
{
byte[] languageCodeBytes = LANG_EN_US.getBytes(StandardCharsets.US_ASCII);
ByteBuffer expectedPayload = ByteBuffer.allocate(
1 + languageCodeBytes.length + utf16TextMojoNdefRecord.data.length);
byte status = (byte) languageCodeBytes.length;
status |= (byte) (1 << 7);
expectedPayload.put(status);
expectedPayload.put(languageCodeBytes);
expectedPayload.put(utf16TextMojoNdefRecord.data);
assertArrayEquals(
expectedPayload.array(), utf16TextNdefMessage.getRecords()[0].getPayload());
}
assertEquals(android.nfc.NdefRecord.TNF_EXTERNAL_TYPE,
utf16TextNdefMessage.getRecords()[1].getTnf());
// Test MIME record conversion.
NdefRecord mimeMojoNdefRecord = new NdefRecord();
......@@ -1209,6 +1277,8 @@ public class NFCTest {
NdefRecord nfcRecord = new NdefRecord();
nfcRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT;
nfcRecord.mediaType = TEXT_MIME;
nfcRecord.encoding = ENCODING_UTF8;
nfcRecord.lang = LANG_EN_US;
nfcRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
message.data[0] = nfcRecord;
return message;
......
......@@ -6,6 +6,7 @@
#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/core/execution_context/execution_context.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"
......@@ -13,13 +14,15 @@
namespace blink {
// static
NDEFMessage* NDEFMessage::Create(const NDEFMessageInit* init,
NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context,
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);
NDEFRecord* record =
NDEFRecord::Create(execution_context, record_init, exception_state);
if (exception_state.HadException())
return nullptr;
DCHECK(record);
......@@ -30,12 +33,13 @@ NDEFMessage* NDEFMessage::Create(const NDEFMessageInit* init,
}
// static
NDEFMessage* NDEFMessage::Create(const NDEFMessageSource& source,
NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context,
const NDEFMessageSource& source,
ExceptionState& exception_state) {
if (source.IsString()) {
NDEFMessage* message = MakeGarbageCollected<NDEFMessage>();
message->records_.push_back(
MakeGarbageCollected<NDEFRecord>(source.GetAsString()));
message->records_.push_back(MakeGarbageCollected<NDEFRecord>(
execution_context, source.GetAsString()));
return message;
}
......@@ -47,7 +51,8 @@ NDEFMessage* NDEFMessage::Create(const NDEFMessageSource& source,
}
if (source.IsNDEFMessageInit()) {
return Create(source.GetAsNDEFMessageInit(), exception_state);
return Create(execution_context, source.GetAsNDEFMessageInit(),
exception_state);
}
NOTREACHED();
......
......@@ -14,6 +14,7 @@
namespace blink {
class ExceptionState;
class ExecutionContext;
class NDEFMessageInit;
class NDEFRecord;
class StringOrArrayBufferOrNDEFMessageInit;
......@@ -24,8 +25,12 @@ class MODULES_EXPORT NDEFMessage final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
static NDEFMessage* Create(const NDEFMessageInit*, ExceptionState&);
static NDEFMessage* Create(const NDEFMessageSource&, ExceptionState&);
static NDEFMessage* Create(const ExecutionContext*,
const NDEFMessageInit*,
ExceptionState&);
static NDEFMessage* Create(const ExecutionContext*,
const NDEFMessageSource&,
ExceptionState&);
NDEFMessage();
explicit NDEFMessage(const device::mojom::blink::NDEFMessage&);
......
......@@ -8,6 +8,7 @@
RuntimeEnabled=WebNFC,
SecureContext,
Constructor(NDEFMessageInit messageInit),
ConstructorCallWith=ExecutionContext,
RaisesException=Constructor,
Exposed=Window
] interface NDEFMessage {
......
......@@ -14,7 +14,8 @@ namespace blink {
NDEFReadingEvent* NDEFReadingEvent::Create(const AtomicString& event_type,
const NDEFReadingEventInit* init,
ExceptionState& exception_state) {
NDEFMessage* message = NDEFMessage::Create(init->message(), exception_state);
NDEFMessage* message =
NDEFMessage::Create(nullptr, init->message(), exception_state);
if (exception_state.HadException())
return nullptr;
DCHECK(message);
......
......@@ -6,7 +6,11 @@
#include "services/device/public/mojom/nfc.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer_view.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_data_view.h"
#include "third_party/blink/renderer/modules/nfc/ndef_record_init.h"
#include "third_party/blink/renderer/modules/nfc/nfc_utils.h"
......@@ -68,13 +72,32 @@ String ValidateCustomRecordType(const String& input) {
return domain + ':' + right;
}
static NDEFRecord* CreateTextRecord(const String& media_type,
String getDocumentLanguage(const ExecutionContext* execution_context) {
DCHECK(execution_context);
String document_language;
Element* document_element =
To<Document>(execution_context)->documentElement();
if (document_element) {
document_language = document_element->getAttribute(html_names::kLangAttr);
}
if (document_language.IsEmpty()) {
document_language = "en";
}
return document_language;
}
static NDEFRecord* CreateTextRecord(const ExecutionContext* execution_context,
const String& media_type,
const String& encoding,
const String& lang,
const ScriptValue& data,
ExceptionState& exception_state) {
// https://w3c.github.io/web-nfc/#mapping-string-to-ndef
if (data.IsEmpty() || !data.V8Value()->IsString()) {
if (data.IsEmpty() ||
!(data.V8Value()->IsString() || data.V8Value()->IsArrayBuffer() ||
data.V8Value()->IsArrayBufferView())) {
exception_state.ThrowTypeError(
"The data for 'text' NDEFRecords must be a String.");
"The data for 'text' NDEFRecords must be a String or a BufferSource.");
return nullptr;
}
......@@ -93,9 +116,51 @@ static NDEFRecord* CreateTextRecord(const String& media_type,
return nullptr;
}
// Set language to lang if it exists, or the document element's lang
// attribute, or 'en'.
String language = lang;
if (execution_context && language.IsEmpty()) {
language = getDocumentLanguage(execution_context);
}
// Bits 0 to 5 define the length of the language tag
// https://w3c.github.io/web-nfc/#text-record
if (language.length() > 63) {
exception_state.ThrowTypeError("Lang length cannot be stored in 6 bit.");
return nullptr;
}
String encoding_label = !encoding.IsEmpty() ? encoding : "utf-8";
if (encoding_label != "utf-8" && encoding_label != "utf-16" &&
encoding_label != "utf-16be" && encoding_label != "utf-16le") {
exception_state.ThrowTypeError(
"Encoding must be either \"utf-8\", \"utf-16\", \"utf-16be\", or "
"\"utf-16le\".");
return nullptr;
}
WTF::Vector<uint8_t> bytes;
if (data.V8Value()->IsString()) {
String text = ToCoreString(data.V8Value().As<v8::String>());
return MakeGarbageCollected<NDEFRecord>("text", mime_type,
GetUTF8DataFromString(text));
StringUTF8Adaptor utf8_string(text);
bytes.Append(utf8_string.data(), utf8_string.size());
} else if (data.V8Value()->IsArrayBuffer()) {
DOMArrayBuffer* array_buffer =
V8ArrayBuffer::ToImpl(data.V8Value().As<v8::Object>());
bytes.Append(static_cast<uint8_t*>(array_buffer->Data()),
array_buffer->ByteLength());
} else if (data.V8Value()->IsArrayBufferView()) {
DOMArrayBufferView* array_buffer_view =
V8ArrayBufferView::ToImpl(data.V8Value().As<v8::Object>());
bytes.Append(
static_cast<uint8_t*>(array_buffer_view->View()->BaseAddress()),
array_buffer_view->View()->ByteLength());
} else {
DCHECK(false);
}
return MakeGarbageCollected<NDEFRecord>("text", mime_type, encoding_label,
language, std::move(bytes));
}
static NDEFRecord* CreateUrlRecord(const String& media_type,
......@@ -205,7 +270,8 @@ static NDEFRecord* CreateExternalRecord(const String& custom_type,
} // namespace
// static
NDEFRecord* NDEFRecord::Create(const NDEFRecordInit* init,
NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context,
const NDEFRecordInit* init,
ExceptionState& exception_state) {
// https://w3c.github.io/web-nfc/#creating-web-nfc-message
String record_type;
......@@ -232,7 +298,9 @@ NDEFRecord* NDEFRecord::Create(const NDEFRecordInit* init,
return MakeGarbageCollected<NDEFRecord>(record_type, String(),
WTF::Vector<uint8_t>());
} else if (record_type == "text") {
return CreateTextRecord(init->mediaType(), init->data(), exception_state);
return CreateTextRecord(execution_context, init->mediaType(),
init->encoding(), init->lang(), init->data(),
exception_state);
} else if (record_type == "url") {
return CreateUrlRecord(init->mediaType(), init->data(), exception_state);
} else if (record_type == "json") {
......@@ -260,9 +328,23 @@ NDEFRecord::NDEFRecord(const String& record_type,
media_type_(media_type),
payload_data_(std::move(data)) {}
NDEFRecord::NDEFRecord(const String& text)
NDEFRecord::NDEFRecord(const String& record_type,
const String& media_type,
const String& encoding,
const String& lang,
WTF::Vector<uint8_t> data)
: record_type_(record_type),
media_type_(media_type),
encoding_(encoding),
lang_(lang),
payload_data_(std::move(data)) {}
NDEFRecord::NDEFRecord(const ExecutionContext* execution_context,
const String& text)
: record_type_("text"),
media_type_("text/plain;charset=UTF-8"),
encoding_("utf-8"),
lang_(getDocumentLanguage(execution_context)),
payload_data_(GetUTF8DataFromString(text)) {}
NDEFRecord::NDEFRecord(DOMArrayBuffer* array_buffer)
......
......@@ -18,6 +18,7 @@ namespace blink {
class DOMArrayBuffer;
class DOMDataView;
class ExceptionState;
class ExecutionContext;
class NDEFRecordInit;
class ScriptState;
......@@ -25,15 +26,22 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
static NDEFRecord* Create(const NDEFRecordInit*, ExceptionState&);
static NDEFRecord* Create(const ExecutionContext*,
const NDEFRecordInit*,
ExceptionState&);
// Construct a "text" record from a string.
explicit NDEFRecord(const String&);
explicit NDEFRecord(const ExecutionContext*, const String&);
// Construct a "opaque" record from an array buffer.
explicit NDEFRecord(DOMArrayBuffer*);
NDEFRecord(const String&, const String&, WTF::Vector<uint8_t>);
NDEFRecord(const String&,
const String&,
const String&,
const String&,
WTF::Vector<uint8_t>);
explicit NDEFRecord(const device::mojom::blink::NDEFRecord&);
const String& recordType() const;
......
......@@ -8,6 +8,7 @@
RuntimeEnabled=WebNFC,
SecureContext,
Constructor(NDEFRecordInit recordInit),
ConstructorCallWith=ExecutionContext,
RaisesException=Constructor,
Exposed=Window
] interface NDEFRecord {
......
......@@ -11,5 +11,9 @@ typedef DOMString NDEFRecordType;
dictionary NDEFRecordInit {
NDEFRecordType recordType;
USVString mediaType;
USVString encoding;
USVString lang;
any data;
};
......@@ -72,7 +72,7 @@ ScriptPromise NDEFWriter::push(ScriptState* script_state,
// 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);
NDEFMessage::Create(execution_context, push_message, exception_state);
if (exception_state.HadException()) {
return ScriptPromise();
}
......
......@@ -23,6 +23,8 @@
assert_equals(message.records.length, 1, 'one text record');
assert_equals(message.records[0].recordType, 'text', 'messageType');
assert_equals(message.records[0].mediaType, 'text/plain', 'mediaType');
assert_equals(message.records[0].encoding, 'utf-8', 'encoding');
assert_equals(message.records[0].lang, 'en', 'lang');
assert_true(message.records[0].data instanceof DataView,
'data returns a DataView');
const decoder = new TextDecoder();
......
......@@ -20,12 +20,70 @@
const record = new NDEFRecord(createTextRecord(test_text_data));
assert_equals(record.recordType, 'text', 'recordType');
assert_equals(record.mediaType, 'text/plain', 'mediaType');
assert_equals(record.encoding, 'utf-8', 'encoding');
assert_equals(record.lang, 'en', 'lang');
const decoder = new TextDecoder();
assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary');
assert_equals(record.text(), test_text_data,
'text() has the same content with the original dictionary');
}, 'NDEFRecord constructor with text record type');
}, 'NDEFRecord constructor with text record type and string data');
test(() => {
const encoder = new TextEncoder();
const uint8Array = encoder.encode(test_text_data);
const record = new NDEFRecord(createTextRecord(uint8Array.buffer));
assert_equals(record.recordType, 'text', 'recordType');
assert_equals(record.mediaType, 'text/plain', 'mediaType');
assert_equals(record.encoding, 'utf-8', 'encoding');
assert_equals(record.lang, 'en', 'lang');
const decoder = new TextDecoder();
assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary');
}, 'NDEFRecord constructor with text record type and arrayBuffer data');
test(() => {
const encoder = new TextEncoder();
const uint8Array = encoder.encode(test_text_data);
const record = new NDEFRecord(createTextRecord(uint8Array));
assert_equals(record.recordType, 'text', 'recordType');
assert_equals(record.mediaType, 'text/plain', 'mediaType');
assert_equals(record.encoding, 'utf-8', 'encoding');
assert_equals(record.lang, 'en', 'lang');
const decoder = new TextDecoder();
assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary');
}, 'NDEFRecord constructor with text record type and arrayBufferView data');
test(() => {
const encodings = ['utf-8', 'utf-16', 'utf-16be', 'utf-16le'];
for (const encoding of encodings) {
const lang = 'fr';
const record = new NDEFRecord(createTextRecord(test_text_data, encoding, lang));
assert_equals(record.recordType, 'text', 'recordType');
assert_equals(record.mediaType, 'text/plain', 'mediaType');
assert_equals(record.encoding, encoding, 'encoding');
assert_equals(record.lang, lang, 'lang');
const decoder = new TextDecoder();
assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary');
}
}, 'NDEFRecord constructor with text record type, encoding, and lang');
test(t => {
const previous_lang = document.querySelector('html').getAttribute('lang');
const test_lang = 'fr';
document.querySelector('html').setAttribute('lang', test_lang);
t.add_cleanup(() => {
document.querySelector('html').setAttribute('lang', previous_lang);
});
const record = new NDEFRecord(createTextRecord(test_text_data));
assert_equals(record.recordType, 'text', 'recordType');
assert_equals(record.mediaType, 'text/plain', 'mediaType');
assert_equals(record.encoding, 'utf-8', 'encoding');
assert_equals(record.lang, test_lang, 'lang');
const decoder = new TextDecoder();
assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary');
}, 'NDEFRecord constructor with text record type and custom document language');
test(() => {
const record = new NDEFRecord(createUrlRecord(test_url_data));
......
......@@ -27,11 +27,18 @@ const invalid_type_messages =
// NDEFRecord must have data.
createMessage([createTextRecord()]),
// NDEFRecord.data for 'text' record must be a string.
createMessage([createTextRecord(test_buffer_data)]),
// NDEFRecord.data for 'text' record must be either a string,
// an arrayBuffer, or an arrayBufferView.
createMessage([createTextRecord(test_json_data)]),
createMessage([createTextRecord(test_number_data)]),
// NDEFRecord.encoding for 'text' record must be either "utf-8",
// "utf-16", "utf-16le" or "utf-16be".
createMessage([createTextRecord(test_text_data, "chinese")]),
// NDEFRecord.lang length for 'text' record must be lower than 64.
createMessage([createTextRecord(test_text_data, undefined /* encoding */, [...Array(64)].map(_ => 'a'))]),
// https://w3c.github.io/web-nfc/#dfn-map-a-json-object-to-ndef
// NDEFRecord must have data.
createMessage([createJsonRecord()]),
......
......@@ -84,19 +84,23 @@ function createMessage(records) {
}
}
function createRecord(recordType, mediaType, data) {
function createRecord(recordType, mediaType, data, encoding, lang) {
let record = {};
if (recordType !== undefined)
record.recordType = recordType;
if (mediaType !== undefined)
record.mediaType = mediaType;
if (encoding !== undefined)
record.encoding = encoding;
if (lang !== undefined)
record.lang = lang;
if (data !== undefined)
record.data = data;
return record;
}
function createTextRecord(text) {
return createRecord('text', 'text/plain', text);
function createTextRecord(data, encoding, lang) {
return createRecord('text', 'text/plain', data, encoding, lang);
}
function createJsonRecord(json) {
......
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