Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(457)

Unified Diff: third_party/WebKit/Source/modules/nfc/NFC.cpp

Issue 1708543002: [webnfc] Implement push() method in blink nfc module. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@onionsoup_webnfc
Patch Set: Move dependency from content_browsertests to layouttest_support_content Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/WebKit/Source/modules/nfc/NFC.h ('k') | third_party/WebKit/Source/modules/nfc/NFCError.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/WebKit/Source/modules/nfc/NFC.cpp
diff --git a/third_party/WebKit/Source/modules/nfc/NFC.cpp b/third_party/WebKit/Source/modules/nfc/NFC.cpp
index ab1afe7641ec86f18eefccadcbbaa5899af69c3b..1c0a78cb5134c766b420b33170d2c627cc2f7c52 100644
--- a/third_party/WebKit/Source/modules/nfc/NFC.cpp
+++ b/third_party/WebKit/Source/modules/nfc/NFC.cpp
@@ -4,18 +4,448 @@
#include "modules/nfc/NFC.h"
+#include "bindings/core/v8/JSONValuesForV8.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
+#include "bindings/core/v8/V8ArrayBuffer.h"
+#include "core/dom/DOMArrayBuffer.h"
#include "core/dom/DOMException.h"
+#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
+#include "core/frame/LocalDOMWindow.h"
+#include "modules/nfc/NFCError.h"
#include "modules/nfc/NFCMessage.h"
#include "modules/nfc/NFCPushOptions.h"
+#include "platform/mojo/MojoHelper.h"
+#include "public/platform/ServiceRegistry.h"
+
+namespace nfc = device::nfc::blink;
+
+namespace {
+const char kJsonMimePrefix[] = "application/";
+const char kJsonMimeType[] = "application/json";
+const char kOpaqueMimeType[] = "application/octet-stream";
+const char kPlainTextMimeType[] = "text/plain";
+const char kPlainTextMimePrefix[] = "text/";
+const char kCharSetUTF8[] = ";charset=UTF-8";
+} // anonymous namespace
+
+// Mojo type converters
+namespace mojo {
+
+using nfc::NFCMessage;
+using nfc::NFCMessagePtr;
+using nfc::NFCRecord;
+using nfc::NFCRecordPtr;
+using nfc::NFCRecordType;
+using nfc::NFCPushOptions;
+using nfc::NFCPushOptionsPtr;
+using nfc::NFCPushTarget;
+
+NFCPushTarget toNFCPushTarget(const WTF::String& target)
+{
+ if (target == "tag")
+ return NFCPushTarget::TAG;
+
+ if (target == "peer")
+ return NFCPushTarget::PEER;
+
+ return NFCPushTarget::ANY;
+}
+
+NFCRecordType toNFCRecordType(const WTF::String& recordType)
+{
+ if (recordType == "empty")
+ return NFCRecordType::EMPTY;
+
+ if (recordType == "text")
+ return NFCRecordType::TEXT;
+
+ if (recordType == "url")
+ return NFCRecordType::URL;
+
+ if (recordType == "json")
+ return NFCRecordType::JSON;
+
+ if (recordType == "opaque")
+ return NFCRecordType::OPAQUE_RECORD;
+
+ NOTREACHED();
+ return NFCRecordType::EMPTY;
+}
+
+// https://w3c.github.io/web-nfc/#creating-web-nfc-message Step 2.1
+// If NFCRecord type is not provided, deduce NFCRecord type from JS data type:
+// String or Number => 'text' record
+// ArrayBuffer => 'opaque' record
+// JSON serializable Object => 'json' record
+NFCRecordType deduceRecordTypeFromDataType(const blink::NFCRecord& record)
+{
+ if (record.hasData()) {
+ v8::Local<v8::Value> value = record.data().v8Value();
+
+ if (value->IsString()
+ || (value->IsNumber() && !std::isnan(value.As<v8::Number>()->Value()))) {
+ return NFCRecordType::TEXT;
+ }
+
+ if (value->IsObject() && !value->IsArrayBuffer()) {
+ return NFCRecordType::JSON;
+ }
+
+ if (value->IsArrayBuffer()) {
+ return NFCRecordType::OPAQUE_RECORD;
+ }
+ }
+
+ return NFCRecordType::EMPTY;
+}
+
+void setMediaType(NFCRecordPtr& recordPtr, const WTF::String& recordMediaType, const WTF::String& defaultMediaType)
+{
+ recordPtr->mediaType = recordMediaType.isEmpty() ? defaultMediaType : recordMediaType;
+}
+
+template <>
+struct TypeConverter<mojo::WTFArray<uint8_t>, WTF::String> {
+ static mojo::WTFArray<uint8_t> Convert(const WTF::String& string)
+ {
+ WTF::CString utf8String = string.utf8();
+ WTF::Vector<uint8_t> array;
+ array.append(utf8String.data(), utf8String.length());
+ return mojo::WTFArray<uint8_t>(std::move(array));
+ }
+};
+
+template <>
+struct TypeConverter<mojo::WTFArray<uint8_t>, blink::DOMArrayBuffer*> {
+ static mojo::WTFArray<uint8_t> Convert(blink::DOMArrayBuffer* buffer)
+ {
+ WTF::Vector<uint8_t> array;
+ array.append(static_cast<uint8_t*>(buffer->data()), buffer->byteLength());
+ return mojo::WTFArray<uint8_t>(std::move(array));
+ }
+};
+
+template <>
+struct TypeConverter<NFCRecordPtr, WTF::String> {
+ static NFCRecordPtr Convert(const WTF::String& string)
+ {
+ NFCRecordPtr record = NFCRecord::New();
+ record->recordType = NFCRecordType::TEXT;
+ record->mediaType = kPlainTextMimeType;
+ record->mediaType.append(kCharSetUTF8);
+ record->data = mojo::WTFArray<uint8_t>::From(string);
+ return record;
+ }
+};
+
+template <>
+struct TypeConverter<NFCRecordPtr, blink::DOMArrayBuffer*> {
+ static NFCRecordPtr Convert(blink::DOMArrayBuffer* buffer)
+ {
+ NFCRecordPtr record = NFCRecord::New();
+ record->recordType = NFCRecordType::OPAQUE_RECORD;
+ record->mediaType = kOpaqueMimeType;
+ record->data = mojo::WTFArray<uint8_t>::From(buffer);
+ return record;
+ }
+};
+
+template <>
+struct TypeConverter<NFCMessagePtr, WTF::String> {
+ static NFCMessagePtr Convert(const WTF::String& string)
+ {
+ NFCMessagePtr message = NFCMessage::New();
+ message->data = mojo::WTFArray<NFCRecordPtr>::New(1);
+ message->data[0] = NFCRecord::From(string);
+ return message;
+ }
+};
+
+template <>
+struct TypeConverter<mojo::WTFArray<uint8_t>, blink::ScriptValue> {
+ static mojo::WTFArray<uint8_t> Convert(const blink::ScriptValue& scriptValue)
+ {
+ v8::Local<v8::Value> value = scriptValue.v8Value();
+
+ if (value->IsNumber())
+ return mojo::WTFArray<uint8_t>::From(WTF::String::number(value.As<v8::Number>()->Value()));
+
+ if (value->IsString()) {
+ blink::V8StringResource<> stringResource = value;
+ if (stringResource.prepare())
+ return mojo::WTFArray<uint8_t>::From<WTF::String>(stringResource);
+ }
+
+ if (value->IsObject() && !value->IsArrayBuffer()) {
+ RefPtr<blink::JSONValue> jsonResult = blink::toJSONValue(scriptValue.context(), value);
+ if (jsonResult && (jsonResult->getType() == blink::JSONValue::TypeObject))
+ return mojo::WTFArray<uint8_t>::From(jsonResult->toJSONString());
+ }
+
+ if (value->IsArrayBuffer())
+ return mojo::WTFArray<uint8_t>::From(blink::V8ArrayBuffer::toImpl(value.As<v8::Object>()));
+
+ return nullptr;
+ }
+};
+
+template <>
+struct TypeConverter<NFCRecordPtr, blink::NFCRecord> {
+ static NFCRecordPtr Convert(const blink::NFCRecord& record)
+ {
+ NFCRecordPtr recordPtr = NFCRecord::New();
+
+ if (record.hasRecordType())
+ recordPtr->recordType = toNFCRecordType(record.recordType());
+ else
+ recordPtr->recordType = 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->recordType == NFCRecordType::EMPTY)
+ return recordPtr;
+
+ switch (recordPtr->recordType) {
+ case NFCRecordType::TEXT:
+ case NFCRecordType::URL:
+ setMediaType(recordPtr, record.mediaType(), kPlainTextMimeType);
+ recordPtr->mediaType.append(kCharSetUTF8);
+ break;
+ case NFCRecordType::JSON:
+ setMediaType(recordPtr, record.mediaType(), kJsonMimeType);
+ break;
+ case NFCRecordType::OPAQUE_RECORD:
+ setMediaType(recordPtr, record.mediaType(), kOpaqueMimeType);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ recordPtr->data = mojo::WTFArray<uint8_t>::From(record.data());
+
+ // If JS object cannot be converted to uint8_t array, return null,
+ // interrupt NFCMessage conversion algorithm and reject promise with
+ // SyntaxError exception.
+ if (recordPtr->data.is_null())
+ return nullptr;
+
+ return recordPtr;
+ }
+};
+
+template <>
+struct TypeConverter<NFCMessagePtr, blink::NFCMessage> {
+ static NFCMessagePtr Convert(const blink::NFCMessage& message)
+ {
+ NFCMessagePtr messagePtr = NFCMessage::New();
+ messagePtr->url = message.url();
+ messagePtr->data.resize(message.data().size());
+ for (size_t i = 0; i < message.data().size(); ++i) {
+ NFCRecordPtr record = NFCRecord::From(message.data()[i]);
+ if (record.is_null())
+ return nullptr;
+
+ messagePtr->data[i] = std::move(record);
+ }
+ messagePtr->data = mojo::WTFArray<NFCRecordPtr>::From(message.data());
+ return messagePtr;
+ }
+};
+
+template <>
+struct TypeConverter<NFCMessagePtr, blink::DOMArrayBuffer*> {
+ static NFCMessagePtr Convert(blink::DOMArrayBuffer* buffer)
+ {
+ NFCMessagePtr message = NFCMessage::New();
+ message->data = mojo::WTFArray<NFCRecordPtr>::New(1);
+ message->data[0] = NFCRecord::From(buffer);
+ return message;
+ }
+};
+
+template <>
+struct TypeConverter<NFCMessagePtr, blink::NFCPushMessage> {
+ static NFCMessagePtr Convert(const blink::NFCPushMessage& message)
+ {
+ if (message.isString())
+ return NFCMessage::From(message.getAsString());
+
+ if (message.isNFCMessage())
+ return NFCMessage::From(message.getAsNFCMessage());
+
+ if (message.isArrayBuffer())
+ return NFCMessage::From(message.getAsArrayBuffer());
+
+ NOTREACHED();
+ return nullptr;
+ }
+};
+
+template <>
+struct TypeConverter<NFCPushOptionsPtr, blink::NFCPushOptions> {
+ static NFCPushOptionsPtr Convert(const blink::NFCPushOptions& pushOptions)
+ {
+ // https://w3c.github.io/web-nfc/#the-nfcpushoptions-dictionary
+ // Default values for NFCPushOptions dictionary are:
+ // target = 'any', timeout = Infinity, ignoreRead = true
+ NFCPushOptionsPtr pushOptionsPtr = NFCPushOptions::New();
+
+ if (pushOptions.hasTarget())
+ pushOptionsPtr->target = toNFCPushTarget(pushOptions.target());
+ else
+ pushOptionsPtr->target = NFCPushTarget::ANY;
+
+ if (pushOptions.hasTimeout())
+ pushOptionsPtr->timeout = pushOptions.timeout();
+ else
+ pushOptionsPtr->timeout = std::numeric_limits<double>::infinity();
+
+ if (pushOptions.hasIgnoreRead())
+ pushOptionsPtr->ignoreRead = pushOptions.ignoreRead();
+ else
+ pushOptionsPtr->ignoreRead = true;
+
+ return pushOptionsPtr;
+ }
+};
+
+} // namespace mojo
namespace blink {
+namespace {
+
+bool isValidTextRecord(const NFCRecord& record)
+{
+ v8::Local<v8::Value> value = record.data().v8Value();
+ if (!value->IsString() && !(value->IsNumber() && !std::isnan(value.As<v8::Number>()->Value())))
+ return false;
+
+ if (record.hasMediaType() && !record.mediaType().startsWith(kPlainTextMimePrefix, TextCaseInsensitive))
+ return false;
+
+ return true;
+}
+
+bool isValidURLRecord(const NFCRecord& record)
+{
+ if (!record.data().v8Value()->IsString())
+ return false;
+
+ blink::V8StringResource<> stringResource = record.data().v8Value();
+ if (!stringResource.prepare())
+ return false;
+
+ return KURL(KURL(), stringResource).isValid();
+}
+
+bool isValidJSONRecord(const NFCRecord& record)
+{
+ v8::Local<v8::Value> value = record.data().v8Value();
+ if (!value->IsObject() || value->IsArrayBuffer())
+ return false;
+
+ if (record.hasMediaType() && !record.mediaType().startsWith(kJsonMimePrefix, TextCaseInsensitive))
+ return false;
+
+ return true;
+}
+
+bool isValidOpaqueRecord(const NFCRecord& record)
+{
+ return record.data().v8Value()->IsArrayBuffer();
+}
+
+bool isValidNFCRecord(const NFCRecord& record)
+{
+ nfc::NFCRecordType type;
+ if (record.hasRecordType()) {
+ type = mojo::toNFCRecordType(record.recordType());
+ } else {
+ type = mojo::deduceRecordTypeFromDataType(record);
+
+ // https://w3c.github.io/web-nfc/#creating-web-nfc-message
+ // If NFCRecord.recordType is not set and record type cannot be deduced
+ // from NFCRecord.data, reject promise with SyntaxError.
+ if (type == nfc::NFCRecordType::EMPTY)
+ return false;
+ }
+
+ // Non-empty records must have data.
+ if (!record.hasData() && (type != nfc::NFCRecordType::EMPTY))
+ return false;
+
+ switch (type) {
+ case nfc::NFCRecordType::TEXT:
+ return isValidTextRecord(record);
+ case nfc::NFCRecordType::URL:
+ return isValidURLRecord(record);
+ case nfc::NFCRecordType::JSON:
+ return isValidJSONRecord(record);
+ case nfc::NFCRecordType::OPAQUE_RECORD:
+ return isValidOpaqueRecord(record);
+ case nfc::NFCRecordType::EMPTY:
+ return !record.hasData() && record.mediaType().isEmpty();
+ }
+
+ NOTREACHED();
+ return false;
+}
+
+DOMException* isValidNFCRecordArray(const HeapVector<NFCRecord>& records)
+{
+ // https://w3c.github.io/web-nfc/#the-push-method
+ // If NFCMessage.data is empty, reject promise with SyntaxError
+ if (records.isEmpty())
+ return DOMException::create(SyntaxError);
+
+ for (const auto& record : records) {
+ if (!isValidNFCRecord(record))
+ return DOMException::create(SyntaxError);
+ }
+
+ return nullptr;
+}
+
+DOMException* isValidNFCPushMessage(const NFCPushMessage& message)
+{
+ if (!message.isNFCMessage() && !message.isString() && !message.isArrayBuffer())
+ return DOMException::create(TypeMismatchError);
+
+ if (message.isNFCMessage()) {
+ if (!message.getAsNFCMessage().hasData())
+ return DOMException::create(TypeMismatchError);
+
+ return isValidNFCRecordArray(message.getAsNFCMessage().data());
+ }
+
+ return nullptr;
+}
+
+bool setURL(const String& origin, nfc::NFCMessagePtr& message)
+{
+ KURL originURL(ParsedURLString, origin);
+
+ if (!message->url.isEmpty() && originURL.canSetPathname()) {
+ originURL.setPath(message->url);
+ }
+
+ message->url = originURL;
+ return originURL.isValid();
+}
+
+} // anonymous namespace
NFC::NFC(LocalFrame* frame)
- : LocalFrameLifecycleObserver(frame)
- , PageLifecycleObserver(frame ? frame->page() : 0)
+ : PageLifecycleObserver(frame->page())
+ , ContextLifecycleObserver(frame->document())
+ , m_client(this)
{
+ ThreadState::current()->registerPreFinalizer(this);
+ frame->serviceRegistry()->connectToRemoteService(mojo::GetProxy(&m_nfc));
+ m_nfc.set_connection_error_handler(createBaseCallback(bind(&NFC::OnConnectionError, WeakPersistentThisPointer<NFC>(this))));
+ m_nfc->SetClient(m_client.CreateInterfacePtrAndBind());
}
NFC* NFC::create(LocalFrame* frame)
@@ -24,16 +454,68 @@ NFC* NFC::create(LocalFrame* frame)
return nfc;
}
-ScriptPromise NFC::push(ScriptState* scriptState, const NFCPushMessage& records, const NFCPushOptions& options)
+NFC::~NFC()
{
- // TODO(shalamov): To be implemented.
- return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError));
+ // |m_nfc| may hold persistent handle to |this| object, therefore, there
+ // should be no more outstanding requests when NFC object is destructed.
+ DCHECK(m_requests.isEmpty());
+}
+
+void NFC::dispose()
+{
+ m_client.Close();
+}
+
+void NFC::contextDestroyed()
+{
+ m_nfc.reset();
+ m_requests.clear();
+}
+
+// https://w3c.github.io/web-nfc/#writing-or-pushing-content
+ScriptPromise NFC::push(ScriptState* scriptState, const NFCPushMessage& pushMessage, const NFCPushOptions& options)
+{
+ String errorMessage;
+ if (!scriptState->getExecutionContext()->isSecureContext(errorMessage))
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(SecurityError, errorMessage));
+
+ DOMException* exception = isValidNFCPushMessage(pushMessage);
+ if (exception)
+ return ScriptPromise::rejectWithDOMException(scriptState, exception);
+
+ if (!m_nfc)
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError));
+
+ nfc::NFCMessagePtr message = nfc::NFCMessage::From(pushMessage);
+ if (!message)
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(SyntaxError));
+
+ if (!setURL(scriptState->getExecutionContext()->getSecurityOrigin()->toString(), message))
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(SyntaxError));
+
+ ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
+ m_requests.add(resolver);
+ auto callback = createBaseCallback(bind<nfc::NFCErrorPtr>(&NFC::OnRequestCompleted, this, resolver));
+ m_nfc->Push(std::move(message), nfc::NFCPushOptions::From(options), callback);
+
+ return resolver->promise();
}
ScriptPromise NFC::cancelPush(ScriptState* scriptState, const String& target)
{
- // TODO(shalamov): To be implemented.
- return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError));
+ String errorMessage;
+ if (!scriptState->getExecutionContext()->isSecureContext(errorMessage))
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(SecurityError, errorMessage));
+
+ if (!m_nfc)
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError));
+
+ ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
+ m_requests.add(resolver);
+ auto callback = createBaseCallback(bind<nfc::NFCErrorPtr>(&NFC::OnRequestCompleted, this, resolver));
+ m_nfc->CancelPush(mojo::toNFCPushTarget(target), callback);
+
+ return resolver->promise();
}
ScriptPromise NFC::watch(ScriptState* scriptState, MessageCallback* callback, const NFCWatchOptions& options)
@@ -54,22 +536,54 @@ ScriptPromise NFC::cancelWatch(ScriptState* scriptState)
return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError));
}
-void NFC::willDetachFrameHost()
-{
- // TODO(shalamov): To be implemented.
-}
-
void NFC::pageVisibilityChanged()
{
- // TODO(shalamov): To be implemented. When visibility is lost,
+ // If service is not initialized, there cannot be any pending NFC activities
+ if (!m_nfc)
+ return;
+
// NFC operations should be suspended.
// https://w3c.github.io/web-nfc/#nfc-suspended
+ if (page()->visibilityState() == PageVisibilityStateVisible)
+ m_nfc->ResumeNFCOperations();
+ else
+ m_nfc->SuspendNFCOperations();
+}
+
+void NFC::OnRequestCompleted(ScriptPromiseResolver* resolver, nfc::NFCErrorPtr error)
+{
+ if (!m_requests.contains(resolver))
+ return;
+
+ m_requests.remove(resolver);
+ if (error.is_null())
+ resolver->resolve();
+ else
+ resolver->reject(NFCError::take(resolver, error->error_type));
+}
+
+void NFC::OnConnectionError()
+{
+ m_nfc.reset();
+
+ // If NFCService is not available or disappears when NFC hardware is
+ // disabled, reject promise with NotSupportedError exception.
+ for (ScriptPromiseResolver* resolver : m_requests)
+ resolver->reject(NFCError::take(resolver, nfc::NFCErrorType::NOT_SUPPORTED));
+
+ m_requests.clear();
+}
+
+void NFC::OnWatch(mojo::WTFArray<uint32_t> ids, nfc::NFCMessagePtr)
+{
+ // TODO(shalamov): Not implemented.
}
DEFINE_TRACE(NFC)
{
- LocalFrameLifecycleObserver::trace(visitor);
PageLifecycleObserver::trace(visitor);
+ ContextLifecycleObserver::trace(visitor);
+ visitor->trace(m_requests);
}
} // namespace blink
« no previous file with comments | « third_party/WebKit/Source/modules/nfc/NFC.h ('k') | third_party/WebKit/Source/modules/nfc/NFCError.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698