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

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: Rebased to master. Added back finalization to NFC class. Created 4 years, 8 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
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..1fff0a1772da51eb030a4b6839fe5a0d3e9d2952 100644
--- a/third_party/WebKit/Source/modules/nfc/NFC.cpp
+++ b/third_party/WebKit/Source/modules/nfc/NFC.cpp
@@ -4,17 +4,454 @@
#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/ExceptionCode.h"
+#include "core/frame/LocalDOMWindow.h"
+#include "modules/nfc/NFCError.h"
#include "modules/nfc/NFCMessage.h"
#include "modules/nfc/NFCPushOptions.h"
+#include "platform/MojoHelper.h"
+#include "public/platform/ServiceRegistry.h"
+
+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/";
+} // anonymous namespace
+
+// Mojo type converters
+namespace mojo {
+
+using device::wtf::NFCMessage;
+using device::wtf::NFCMessagePtr;
+using device::wtf::NFCRecord;
+using device::wtf::NFCRecordPtr;
+using device::wtf::NFCRecordType;
+using device::wtf::NFCPushOptions;
+using device::wtf::NFCPushOptionsPtr;
+using device::wtf::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;
+
+ ASSERT_NOT_REACHED();
+ return NFCRecordType::EMPTY;
+}
+
+// https://w3c.github.io/web-nfc/#creating-web-nfc-message Step 2.1
+// If NFCRecord type is not provided, deduct NFCRecord type from JS data type:
+// String or Number => 'text' record
+// ArrayBuffer => 'opaque' record
+// JSON serializable Object => 'json' record
+NFCRecordType deductRecordTypeFromDataType(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 blink::NFCRecord& record, const WTF::String& mediaType)
+{
+ recordPtr->mediaType = record.hasMediaType() ? record.mediaType() : mediaType;
+}
+
+template <>
+struct TypeConverter<mojo::WTFArray<uint8_t>, WTF::String> {
+ static mojo::WTFArray<uint8_t> Convert(const WTF::String& string)
+ {
+ mojo::WTFArray<uint8_t> array(string.sizeInBytes());
+ const uint8_t* buffer;
+ if (string.is8Bit())
+ buffer = reinterpret_cast<const uint8_t*>(string.characters8());
+ else
+ buffer = reinterpret_cast<const uint8_t*>(string.characters16());
+ memcpy(&array.front(), buffer, string.sizeInBytes());
+ return array;
+ }
+};
+
+template <>
+struct TypeConverter<mojo::WTFArray<uint8_t>, blink::DOMArrayBuffer*> {
+ static mojo::WTFArray<uint8_t> Convert(blink::DOMArrayBuffer* buffer)
+ {
+ mojo::WTFArray<uint8_t> array(buffer->byteLength());
+ memcpy(&array.front(), buffer->data(), buffer->byteLength());
+ return 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->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 = deductRecordTypeFromDataType(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, kPlainTextMimeType);
+ break;
+ case NFCRecordType::JSON:
+ setMediaType(recordPtr, record, kJsonMimeType);
+ break;
+ case NFCRecordType::OPAQUE_RECORD:
+ setMediaType(recordPtr, record, kOpaqueMimeType);
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ 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());
+
+ ASSERT_NOT_REACHED();
+ return nullptr;
+ }
+};
+
+template <>
+struct TypeConverter<NFCPushOptionsPtr, blink::NFCPushOptions> {
+ static NFCPushOptionsPtr Convert(const blink::NFCPushOptions& pushOptions)
+ {
+ 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;
kenneth.christiansen 2016/04/14 14:29:27 link to spec saying the default is true?
shalamov 2016/04/15 07:56:39 Done.
+
+ 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;
+
+ return isValidTextRecord(record);
+}
+
+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)
+{
+ device::wtf::NFCRecordType type;
+ if (record.hasRecordType()) {
+ type = mojo::toNFCRecordType(record.recordType());
+ } else {
+ type = mojo::deductRecordTypeFromDataType(record);
+
+ // https://w3c.github.io/web-nfc/#creating-web-nfc-message
+ // If NFCRecord.recordType is not set and record type cannot be deducted
+ // from NFCRecord.data, reject promise with SyntaxError.
+ if (type == device::wtf::NFCRecordType::EMPTY)
+ return false;
+ }
+
+ // Non-empty records must have data.
+ if (!record.hasData() && (type != device::wtf::NFCRecordType::EMPTY))
+ return false;
+
+ switch (type) {
+ case device::wtf::NFCRecordType::TEXT:
+ return isValidTextRecord(record);
+ case device::wtf::NFCRecordType::URL:
+ return isValidURLRecord(record);
+ case device::wtf::NFCRecordType::JSON:
+ return isValidJSONRecord(record);
+ case device::wtf::NFCRecordType::OPAQUE_RECORD:
+ return isValidOpaqueRecord(record);
+ case device::wtf::NFCRecordType::EMPTY:
+ return !record.hasData() && record.mediaType().isEmpty();
+ }
+
+ ASSERT_NOT_REACHED();
+ 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, device::wtf::NFCMessagePtr& message)
+{
+ KURL originURL(ParsedURLString, origin);
+
+ if (!message->url.isEmpty() && originURL.canSetPathname()) {
+ originURL.setPath(message->url);
+ message->url = originURL;
+ }
+
+ return originURL.isValid();
+}
+
+} // anonymous namespace
+
+using NFCMojoCallback = mojo::Callback<void(device::wtf::NFCErrorPtr)>;
+class NFCCallback final : public NFCMojoCallback::Runnable {
+public:
+ using NFCCallbackPromiseAdapter = CallbackPromiseAdapter<void, NFCError>;
+ NFCCallback(ScriptPromiseResolver* resolver)
+ : m_promiseAdapter(adoptPtr(new NFCCallbackPromiseAdapter(resolver))) { }
+
+ void Run(device::wtf::NFCErrorPtr error)
+ {
+ if (error.is_null())
+ m_promiseAdapter->onSuccess();
+ else
+ m_promiseAdapter->onError(error->error_type);
+ }
+
+private:
+ OwnPtr<NFCCallbackPromiseAdapter> m_promiseAdapter;
+};
+
NFC::NFC(LocalFrame* frame)
- : LocalFrameLifecycleObserver(frame)
- , PageLifecycleObserver(frame ? frame->page() : 0)
+ : PageLifecycleObserver(frame ? frame->page() : 0),
+ m_client(this)
{
}
@@ -24,16 +461,50 @@ NFC* NFC::create(LocalFrame* frame)
return nfc;
}
-ScriptPromise NFC::push(ScriptState* scriptState, const NFCPushMessage& records, const NFCPushOptions& options)
+NFC::~NFC() = default;
+
+// https://w3c.github.io/web-nfc/#writing-or-pushing-content
+ScriptPromise NFC::push(ScriptState* scriptState, const NFCPushMessage& pushMessage, const NFCPushOptions& options)
{
- // 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));
+ }
+
+ DOMException* exception = isValidNFCPushMessage(pushMessage);
+ if (exception)
+ return ScriptPromise::rejectWithDOMException(scriptState, exception);
+
+ if (!Initialize(scriptState->domWindow()->frame()->serviceRegistry()))
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError));
+
+ device::wtf::NFCMessagePtr message = device::wtf::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_nfc->Push(std::move(message), device::wtf::NFCPushOptions::From(options), device::wtf::NFC::PushCallback(new NFCCallback(resolver)));
+
+ 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 (!Initialize(scriptState->domWindow()->frame()->serviceRegistry()))
+ return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError));
+
+ ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
+ m_nfc->CancelPush(mojo::toNFCPushTarget(target), device::wtf::NFC::CancelPushCallback(new NFCCallback(resolver)));
+
+ return resolver->promise();
}
ScriptPromise NFC::watch(ScriptState* scriptState, MessageCallback* callback, const NFCWatchOptions& options)
@@ -54,21 +525,48 @@ 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::OnWatch(uint32_t id, device::wtf::NFCMessagePtr message)
+{
+}
+
+bool NFC::Initialize(ServiceRegistry* registry)
+{
+ DCHECK(registry);
+
+ if (m_nfc)
kenneth.christiansen 2016/04/14 14:29:27 do you intent to initialize this multiple times? o
shalamov 2016/04/15 07:56:39 I want to lazy initialize mojo service only when o
+ return true;
+
+ registry->connectToRemoteService(mojo::GetProxy(&m_nfc));
+
+ if (m_nfc) {
+ m_nfc.set_connection_error_handler(sameThreadBindForMojo(&NFC::OnError, this));
+ m_nfc->SetClient(m_client.CreateInterfacePtrAndBind());
+ }
+
+ return m_nfc;
+}
+
+void NFC::OnError()
+{
+ // todo(shalamov): clear map that contains nfc watch callbacks
}
DEFINE_TRACE(NFC)
{
- LocalFrameLifecycleObserver::trace(visitor);
PageLifecycleObserver::trace(visitor);
}

Powered by Google App Engine
This is Rietveld 408576698