| 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 | 
|  |