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 @@ ...@@ -5,13 +5,14 @@
package org.chromium.device.nfc; package org.chromium.device.nfc;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.device.mojom.NdefMessage; import org.chromium.device.mojom.NdefMessage;
import org.chromium.device.mojom.NdefRecord; import org.chromium.device.mojom.NdefRecord;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -102,8 +103,8 @@ public final class NdefMessageUtils { ...@@ -102,8 +103,8 @@ public final class NdefMessageUtils {
/** /**
* Converts mojo NdefRecord to android.nfc.NdefRecord * Converts mojo NdefRecord to android.nfc.NdefRecord
* |record.data| should always be treated as "UTF-8" encoding bytes, this is guaranteed by the * |record.data| can safely be treated as "UTF-8" encoding bytes for non text records, this is
* sender (Blink). * guaranteed by the sender (Blink).
*/ */
private static android.nfc.NdefRecord toNdefRecord(NdefRecord record) private static android.nfc.NdefRecord toNdefRecord(NdefRecord record)
throws InvalidNdefMessageException, IllegalArgumentException, throws InvalidNdefMessageException, IllegalArgumentException,
...@@ -112,12 +113,9 @@ public final class NdefMessageUtils { ...@@ -112,12 +113,9 @@ public final class NdefMessageUtils {
case RECORD_TYPE_URL: case RECORD_TYPE_URL:
return android.nfc.NdefRecord.createUri(new String(record.data, "UTF-8")); return android.nfc.NdefRecord.createUri(new String(record.data, "UTF-8"));
case RECORD_TYPE_TEXT: case RECORD_TYPE_TEXT:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { byte[] payload = createPayloadForTextRecord(record);
return android.nfc.NdefRecord.createTextRecord( return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_WELL_KNOWN,
"en-US", new String(record.data, "UTF-8")); android.nfc.NdefRecord.RTD_TEXT, null, payload);
} else {
return android.nfc.NdefRecord.createMime(TEXT_MIME, record.data);
}
case RECORD_TYPE_JSON: case RECORD_TYPE_JSON:
case RECORD_TYPE_OPAQUE: case RECORD_TYPE_OPAQUE:
return android.nfc.NdefRecord.createMime(record.mediaType, record.data); return android.nfc.NdefRecord.createMime(record.mediaType, record.data);
...@@ -306,4 +304,19 @@ public final class NdefMessageUtils { ...@@ -306,4 +304,19 @@ public final class NdefMessageUtils {
return new PairOfDomainAndType(domain, type); 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 @@ ...@@ -4,6 +4,7 @@
package org.chromium.device.nfc; package org.chromium.device.nfc;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
...@@ -60,6 +61,9 @@ import org.chromium.testing.local.LocalRobolectricTestRunner; ...@@ -60,6 +61,9 @@ import org.chromium.testing.local.LocalRobolectricTestRunner;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; 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. * Unit tests for NfcImpl and NdefMessageUtils classes.
...@@ -99,6 +103,7 @@ public class NFCTest { ...@@ -99,6 +103,7 @@ public class NFCTest {
private static final String JSON_MIME = "application/json"; private static final String JSON_MIME = "application/json";
private static final String OCTET_STREAM_MIME = "application/octet-stream"; private static final String OCTET_STREAM_MIME = "application/octet-stream";
private static final String ENCODING_UTF8 = "utf-8"; 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"; private static final String LANG_EN_US = "en-US";
/** /**
...@@ -239,19 +244,41 @@ public class NFCTest { ...@@ -239,19 +244,41 @@ public class NFCTest {
assertNull(urlMojoNdefMessage.data[0].lang); assertNull(urlMojoNdefMessage.data[0].lang);
assertEquals(TEST_URL, new String(urlMojoNdefMessage.data[0].data)); assertEquals(TEST_URL, new String(urlMojoNdefMessage.data[0].data));
// Test TEXT record conversion. // Test TEXT record conversion for UTF-8 content.
android.nfc.NdefMessage textNdefMessage = new android.nfc.NdefMessage( android.nfc.NdefMessage utf8TextNdefMessage = new android.nfc.NdefMessage(
android.nfc.NdefRecord.createTextRecord(LANG_EN_US, TEST_TEXT)); android.nfc.NdefRecord.createTextRecord(LANG_EN_US, TEST_TEXT));
NdefMessage textMojoNdefMessage = NdefMessageUtils.toNdefMessage(textNdefMessage); NdefMessage utf8TextMojoNdefMessage = NdefMessageUtils.toNdefMessage(utf8TextNdefMessage);
assertNull(textMojoNdefMessage.url); assertNull(utf8TextMojoNdefMessage.url);
assertEquals(1, textMojoNdefMessage.data.length); assertEquals(1, utf8TextMojoNdefMessage.data.length);
assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, textMojoNdefMessage.data[0].recordType); assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, utf8TextMojoNdefMessage.data[0].recordType);
assertEquals(TEXT_MIME, textMojoNdefMessage.data[0].mediaType); assertEquals(TEXT_MIME, utf8TextMojoNdefMessage.data[0].mediaType);
assertEquals(true, textMojoNdefMessage.data[0].id.isEmpty()); assertEquals(true, utf8TextMojoNdefMessage.data[0].id.isEmpty());
// TODO(beaufort.francois): Find a way to test UTF-16 encoding record conversion. assertEquals(ENCODING_UTF8, utf8TextMojoNdefMessage.data[0].encoding);
assertEquals(ENCODING_UTF8, textMojoNdefMessage.data[0].encoding); assertEquals(LANG_EN_US, utf8TextMojoNdefMessage.data[0].lang);
assertEquals(LANG_EN_US, textMojoNdefMessage.data[0].lang); assertEquals(TEST_TEXT, new String(utf8TextMojoNdefMessage.data[0].data, "UTF-8"));
assertEquals(TEST_TEXT, new String(textMojoNdefMessage.data[0].data));
// 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. // Test MIME record conversion.
android.nfc.NdefMessage mimeNdefMessage = android.nfc.NdefMessage mimeNdefMessage =
...@@ -265,7 +292,7 @@ public class NFCTest { ...@@ -265,7 +292,7 @@ public class NFCTest {
assertEquals(true, mimeMojoNdefMessage.data[0].id.isEmpty()); assertEquals(true, mimeMojoNdefMessage.data[0].id.isEmpty());
assertNull(mimeMojoNdefMessage.data[0].encoding); assertNull(mimeMojoNdefMessage.data[0].encoding);
assertNull(mimeMojoNdefMessage.data[0].lang); 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. // Test JSON record conversion.
android.nfc.NdefMessage jsonNdefMessage = android.nfc.NdefMessage jsonNdefMessage =
...@@ -348,21 +375,62 @@ public class NFCTest { ...@@ -348,21 +375,62 @@ public class NFCTest {
new String(urlNdefMessage.getRecords()[1].getType()) new String(urlNdefMessage.getRecords()[1].getType())
.compareToIgnoreCase(AUTHOR_RECORD_DOMAIN + ":" + AUTHOR_RECORD_TYPE)); .compareToIgnoreCase(AUTHOR_RECORD_DOMAIN + ":" + AUTHOR_RECORD_TYPE));
// Test TEXT record conversion. // Test TEXT record conversion for UTF-8 content.
NdefRecord textMojoNdefRecord = new NdefRecord(); NdefRecord utf8TextMojoNdefRecord = new NdefRecord();
textMojoNdefRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT; utf8TextMojoNdefRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT;
textMojoNdefRecord.mediaType = TEXT_MIME; utf8TextMojoNdefRecord.mediaType = TEXT_MIME;
textMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT); utf8TextMojoNdefRecord.encoding = ENCODING_UTF8;
NdefMessage textMojoNdefMessage = createMojoNdefMessage(TEST_URL, textMojoNdefRecord); utf8TextMojoNdefRecord.lang = LANG_EN_US;
android.nfc.NdefMessage textNdefMessage = utf8TextMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
NdefMessageUtils.toNdefMessage(textMojoNdefMessage); NdefMessage utf8TextMojoNdefMessage =
assertEquals(2, textNdefMessage.getRecords().length); createMojoNdefMessage(TEST_URL, utf8TextMojoNdefRecord);
short tnf = textNdefMessage.getRecords()[0].getTnf(); android.nfc.NdefMessage utf8TextNdefMessage =
boolean isWellKnownOrMime = tnf == android.nfc.NdefRecord.TNF_WELL_KNOWN NdefMessageUtils.toNdefMessage(utf8TextMojoNdefMessage);
|| tnf == android.nfc.NdefRecord.TNF_MIME_MEDIA; assertEquals(2, utf8TextNdefMessage.getRecords().length);
assertEquals(true, isWellKnownOrMime); assertEquals(android.nfc.NdefRecord.TNF_WELL_KNOWN,
assertEquals( utf8TextNdefMessage.getRecords()[0].getTnf());
android.nfc.NdefRecord.TNF_EXTERNAL_TYPE, textNdefMessage.getRecords()[1].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. // Test MIME record conversion.
NdefRecord mimeMojoNdefRecord = new NdefRecord(); NdefRecord mimeMojoNdefRecord = new NdefRecord();
...@@ -1209,6 +1277,8 @@ public class NFCTest { ...@@ -1209,6 +1277,8 @@ public class NFCTest {
NdefRecord nfcRecord = new NdefRecord(); NdefRecord nfcRecord = new NdefRecord();
nfcRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT; nfcRecord.recordType = NdefMessageUtils.RECORD_TYPE_TEXT;
nfcRecord.mediaType = TEXT_MIME; nfcRecord.mediaType = TEXT_MIME;
nfcRecord.encoding = ENCODING_UTF8;
nfcRecord.lang = LANG_EN_US;
nfcRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT); nfcRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
message.data[0] = nfcRecord; message.data[0] = nfcRecord;
return message; return message;
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "services/device/public/mojom/nfc.mojom-blink.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/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_message_init.h"
#include "third_party/blink/renderer/modules/nfc/ndef_record.h" #include "third_party/blink/renderer/modules/nfc/ndef_record.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
...@@ -13,13 +14,15 @@ ...@@ -13,13 +14,15 @@
namespace blink { namespace blink {
// static // static
NDEFMessage* NDEFMessage::Create(const NDEFMessageInit* init, NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context,
const NDEFMessageInit* init,
ExceptionState& exception_state) { ExceptionState& exception_state) {
NDEFMessage* message = MakeGarbageCollected<NDEFMessage>(); NDEFMessage* message = MakeGarbageCollected<NDEFMessage>();
message->url_ = init->url(); message->url_ = init->url();
if (init->hasRecords()) { if (init->hasRecords()) {
for (const NDEFRecordInit* record_init : init->records()) { 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()) if (exception_state.HadException())
return nullptr; return nullptr;
DCHECK(record); DCHECK(record);
...@@ -30,12 +33,13 @@ NDEFMessage* NDEFMessage::Create(const NDEFMessageInit* init, ...@@ -30,12 +33,13 @@ NDEFMessage* NDEFMessage::Create(const NDEFMessageInit* init,
} }
// static // static
NDEFMessage* NDEFMessage::Create(const NDEFMessageSource& source, NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context,
const NDEFMessageSource& source,
ExceptionState& exception_state) { ExceptionState& exception_state) {
if (source.IsString()) { if (source.IsString()) {
NDEFMessage* message = MakeGarbageCollected<NDEFMessage>(); NDEFMessage* message = MakeGarbageCollected<NDEFMessage>();
message->records_.push_back( message->records_.push_back(MakeGarbageCollected<NDEFRecord>(
MakeGarbageCollected<NDEFRecord>(source.GetAsString())); execution_context, source.GetAsString()));
return message; return message;
} }
...@@ -47,7 +51,8 @@ NDEFMessage* NDEFMessage::Create(const NDEFMessageSource& source, ...@@ -47,7 +51,8 @@ NDEFMessage* NDEFMessage::Create(const NDEFMessageSource& source,
} }
if (source.IsNDEFMessageInit()) { if (source.IsNDEFMessageInit()) {
return Create(source.GetAsNDEFMessageInit(), exception_state); return Create(execution_context, source.GetAsNDEFMessageInit(),
exception_state);
} }
NOTREACHED(); NOTREACHED();
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
namespace blink { namespace blink {
class ExceptionState; class ExceptionState;
class ExecutionContext;
class NDEFMessageInit; class NDEFMessageInit;
class NDEFRecord; class NDEFRecord;
class StringOrArrayBufferOrNDEFMessageInit; class StringOrArrayBufferOrNDEFMessageInit;
...@@ -24,8 +25,12 @@ class MODULES_EXPORT NDEFMessage final : public ScriptWrappable { ...@@ -24,8 +25,12 @@ class MODULES_EXPORT NDEFMessage final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
static NDEFMessage* Create(const NDEFMessageInit*, ExceptionState&); static NDEFMessage* Create(const ExecutionContext*,
static NDEFMessage* Create(const NDEFMessageSource&, ExceptionState&); const NDEFMessageInit*,
ExceptionState&);
static NDEFMessage* Create(const ExecutionContext*,
const NDEFMessageSource&,
ExceptionState&);
NDEFMessage(); NDEFMessage();
explicit NDEFMessage(const device::mojom::blink::NDEFMessage&); explicit NDEFMessage(const device::mojom::blink::NDEFMessage&);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
RuntimeEnabled=WebNFC, RuntimeEnabled=WebNFC,
SecureContext, SecureContext,
Constructor(NDEFMessageInit messageInit), Constructor(NDEFMessageInit messageInit),
ConstructorCallWith=ExecutionContext,
RaisesException=Constructor, RaisesException=Constructor,
Exposed=Window Exposed=Window
] interface NDEFMessage { ] interface NDEFMessage {
......
...@@ -14,7 +14,8 @@ namespace blink { ...@@ -14,7 +14,8 @@ namespace blink {
NDEFReadingEvent* NDEFReadingEvent::Create(const AtomicString& event_type, NDEFReadingEvent* NDEFReadingEvent::Create(const AtomicString& event_type,
const NDEFReadingEventInit* init, const NDEFReadingEventInit* init,
ExceptionState& exception_state) { ExceptionState& exception_state) {
NDEFMessage* message = NDEFMessage::Create(init->message(), exception_state); NDEFMessage* message =
NDEFMessage::Create(nullptr, init->message(), exception_state);
if (exception_state.HadException()) if (exception_state.HadException())
return nullptr; return nullptr;
DCHECK(message); DCHECK(message);
......
...@@ -6,7 +6,11 @@ ...@@ -6,7 +6,11 @@
#include "services/device/public/mojom/nfc.mojom-blink.h" #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.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/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/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/ndef_record_init.h"
#include "third_party/blink/renderer/modules/nfc/nfc_utils.h" #include "third_party/blink/renderer/modules/nfc/nfc_utils.h"
...@@ -68,13 +72,32 @@ String ValidateCustomRecordType(const String& input) { ...@@ -68,13 +72,32 @@ String ValidateCustomRecordType(const String& input) {
return domain + ':' + right; 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, const ScriptValue& data,
ExceptionState& exception_state) { ExceptionState& exception_state) {
// https://w3c.github.io/web-nfc/#mapping-string-to-ndef // 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( 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; return nullptr;
} }
...@@ -93,9 +116,51 @@ static NDEFRecord* CreateTextRecord(const String& media_type, ...@@ -93,9 +116,51 @@ static NDEFRecord* CreateTextRecord(const String& media_type,
return nullptr; return nullptr;
} }
String text = ToCoreString(data.V8Value().As<v8::String>()); // Set language to lang if it exists, or the document element's lang
return MakeGarbageCollected<NDEFRecord>("text", mime_type, // attribute, or 'en'.
GetUTF8DataFromString(text)); 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>());
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, static NDEFRecord* CreateUrlRecord(const String& media_type,
...@@ -205,7 +270,8 @@ static NDEFRecord* CreateExternalRecord(const String& custom_type, ...@@ -205,7 +270,8 @@ static NDEFRecord* CreateExternalRecord(const String& custom_type,
} // namespace } // namespace
// static // static
NDEFRecord* NDEFRecord::Create(const NDEFRecordInit* init, NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context,
const NDEFRecordInit* init,
ExceptionState& exception_state) { ExceptionState& exception_state) {
// https://w3c.github.io/web-nfc/#creating-web-nfc-message // https://w3c.github.io/web-nfc/#creating-web-nfc-message
String record_type; String record_type;
...@@ -232,7 +298,9 @@ NDEFRecord* NDEFRecord::Create(const NDEFRecordInit* init, ...@@ -232,7 +298,9 @@ NDEFRecord* NDEFRecord::Create(const NDEFRecordInit* init,
return MakeGarbageCollected<NDEFRecord>(record_type, String(), return MakeGarbageCollected<NDEFRecord>(record_type, String(),
WTF::Vector<uint8_t>()); WTF::Vector<uint8_t>());
} else if (record_type == "text") { } 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") { } else if (record_type == "url") {
return CreateUrlRecord(init->mediaType(), init->data(), exception_state); return CreateUrlRecord(init->mediaType(), init->data(), exception_state);
} else if (record_type == "json") { } else if (record_type == "json") {
...@@ -260,9 +328,23 @@ NDEFRecord::NDEFRecord(const String& record_type, ...@@ -260,9 +328,23 @@ NDEFRecord::NDEFRecord(const String& record_type,
media_type_(media_type), media_type_(media_type),
payload_data_(std::move(data)) {} 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"), : record_type_("text"),
media_type_("text/plain;charset=UTF-8"), media_type_("text/plain;charset=UTF-8"),
encoding_("utf-8"),
lang_(getDocumentLanguage(execution_context)),
payload_data_(GetUTF8DataFromString(text)) {} payload_data_(GetUTF8DataFromString(text)) {}
NDEFRecord::NDEFRecord(DOMArrayBuffer* array_buffer) NDEFRecord::NDEFRecord(DOMArrayBuffer* array_buffer)
......
...@@ -18,6 +18,7 @@ namespace blink { ...@@ -18,6 +18,7 @@ namespace blink {
class DOMArrayBuffer; class DOMArrayBuffer;
class DOMDataView; class DOMDataView;
class ExceptionState; class ExceptionState;
class ExecutionContext;
class NDEFRecordInit; class NDEFRecordInit;
class ScriptState; class ScriptState;
...@@ -25,15 +26,22 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable { ...@@ -25,15 +26,22 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
static NDEFRecord* Create(const NDEFRecordInit*, ExceptionState&); static NDEFRecord* Create(const ExecutionContext*,
const NDEFRecordInit*,
ExceptionState&);
// Construct a "text" record from a string. // 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. // Construct a "opaque" record from an array buffer.
explicit NDEFRecord(DOMArrayBuffer*); explicit NDEFRecord(DOMArrayBuffer*);
NDEFRecord(const String&, const String&, WTF::Vector<uint8_t>); 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&); explicit NDEFRecord(const device::mojom::blink::NDEFRecord&);
const String& recordType() const; const String& recordType() const;
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
RuntimeEnabled=WebNFC, RuntimeEnabled=WebNFC,
SecureContext, SecureContext,
Constructor(NDEFRecordInit recordInit), Constructor(NDEFRecordInit recordInit),
ConstructorCallWith=ExecutionContext,
RaisesException=Constructor, RaisesException=Constructor,
Exposed=Window Exposed=Window
] interface NDEFRecord { ] interface NDEFRecord {
......
...@@ -11,5 +11,9 @@ typedef DOMString NDEFRecordType; ...@@ -11,5 +11,9 @@ typedef DOMString NDEFRecordType;
dictionary NDEFRecordInit { dictionary NDEFRecordInit {
NDEFRecordType recordType; NDEFRecordType recordType;
USVString mediaType; USVString mediaType;
USVString encoding;
USVString lang;
any data; any data;
}; };
...@@ -72,7 +72,7 @@ ScriptPromise NDEFWriter::push(ScriptState* script_state, ...@@ -72,7 +72,7 @@ ScriptPromise NDEFWriter::push(ScriptState* script_state,
// Step 10.8: Run "create Web NFC message", if this throws an exception, // Step 10.8: Run "create Web NFC message", if this throws an exception,
// reject p with that exception and abort these steps. // reject p with that exception and abort these steps.
NDEFMessage* ndef_message = NDEFMessage* ndef_message =
NDEFMessage::Create(push_message, exception_state); NDEFMessage::Create(execution_context, push_message, exception_state);
if (exception_state.HadException()) { if (exception_state.HadException()) {
return ScriptPromise(); return ScriptPromise();
} }
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
assert_equals(message.records.length, 1, 'one text record'); assert_equals(message.records.length, 1, 'one text record');
assert_equals(message.records[0].recordType, 'text', 'messageType'); assert_equals(message.records[0].recordType, 'text', 'messageType');
assert_equals(message.records[0].mediaType, 'text/plain', 'mediaType'); 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, assert_true(message.records[0].data instanceof DataView,
'data returns a DataView'); 'data returns a DataView');
const decoder = new TextDecoder(); const decoder = new TextDecoder();
......
...@@ -20,12 +20,70 @@ ...@@ -20,12 +20,70 @@
const record = new NDEFRecord(createTextRecord(test_text_data)); const record = new NDEFRecord(createTextRecord(test_text_data));
assert_equals(record.recordType, 'text', 'recordType'); assert_equals(record.recordType, 'text', 'recordType');
assert_equals(record.mediaType, 'text/plain', 'mediaType'); assert_equals(record.mediaType, 'text/plain', 'mediaType');
assert_equals(record.encoding, 'utf-8', 'encoding');
assert_equals(record.lang, 'en', 'lang');
const decoder = new TextDecoder(); const decoder = new TextDecoder();
assert_equals(decoder.decode(record.data), test_text_data, assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary'); 'data has the same content with the original dictionary');
assert_equals(record.text(), test_text_data, }, 'NDEFRecord constructor with text record type and string data');
'text() has the same content with the original dictionary');
}, 'NDEFRecord constructor with text record type'); 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(() => { test(() => {
const record = new NDEFRecord(createUrlRecord(test_url_data)); const record = new NDEFRecord(createUrlRecord(test_url_data));
......
...@@ -27,11 +27,18 @@ const invalid_type_messages = ...@@ -27,11 +27,18 @@ const invalid_type_messages =
// NDEFRecord must have data. // NDEFRecord must have data.
createMessage([createTextRecord()]), createMessage([createTextRecord()]),
// NDEFRecord.data for 'text' record must be a string. // NDEFRecord.data for 'text' record must be either a string,
createMessage([createTextRecord(test_buffer_data)]), // an arrayBuffer, or an arrayBufferView.
createMessage([createTextRecord(test_json_data)]), createMessage([createTextRecord(test_json_data)]),
createMessage([createTextRecord(test_number_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 // https://w3c.github.io/web-nfc/#dfn-map-a-json-object-to-ndef
// NDEFRecord must have data. // NDEFRecord must have data.
createMessage([createJsonRecord()]), createMessage([createJsonRecord()]),
......
...@@ -84,19 +84,23 @@ function createMessage(records) { ...@@ -84,19 +84,23 @@ function createMessage(records) {
} }
} }
function createRecord(recordType, mediaType, data) { function createRecord(recordType, mediaType, data, encoding, lang) {
let record = {}; let record = {};
if (recordType !== undefined) if (recordType !== undefined)
record.recordType = recordType; record.recordType = recordType;
if (mediaType !== undefined) if (mediaType !== undefined)
record.mediaType = mediaType; record.mediaType = mediaType;
if (encoding !== undefined)
record.encoding = encoding;
if (lang !== undefined)
record.lang = lang;
if (data !== undefined) if (data !== undefined)
record.data = data; record.data = data;
return record; return record;
} }
function createTextRecord(text) { function createTextRecord(data, encoding, lang) {
return createRecord('text', 'text/plain', text); return createRecord('text', 'text/plain', data, encoding, lang);
} }
function createJsonRecord(json) { 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