Bug 1109456 - Support NFC tag transceive WebAPI. r=smaug, yoshi
authordlee <dlee@mozilla.com>
Fri, 09 Jan 2015 10:42:48 +0800
changeset 248711 0c39bc01f6345d0a1b686c787271737fad499e3e
parent 248710 ccd36ead0046a565ce24326940c1a5dc99d9b89c
child 248712 825d2e29f16c805d40e1247592465666929d4a5e
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, yoshi
bugs1109456
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1109456 - Support NFC tag transceive WebAPI. r=smaug, yoshi --- dom/nfc/NfcContentHelper.js | 33 ++++++++++++++++++++++ dom/nfc/gonk/Nfc.js | 7 ++++- dom/nfc/gonk/NfcGonkMessage.h | 2 ++ dom/nfc/gonk/NfcMessageHandler.cpp | 54 ++++++++++++++++++++++++++++++++++++ dom/nfc/gonk/NfcMessageHandler.h | 4 ++- dom/nfc/gonk/NfcOptions.h | 13 +++++++++ dom/nfc/gonk/NfcService.cpp | 6 ++++ dom/nfc/nsINfcContentHelper.idl | 27 ++++++++++++++++-- dom/nfc/nsNfc.js | 19 +++++++++++++ dom/webidl/MozNFCTag.webidl | 6 ++++ dom/webidl/NfcOptions.webidl | 6 ++++ 11 files changed, 173 insertions(+), 4 deletions(-)
dom/nfc/NfcContentHelper.js
dom/nfc/gonk/Nfc.js
dom/nfc/gonk/NfcGonkMessage.h
dom/nfc/gonk/NfcMessageHandler.cpp
dom/nfc/gonk/NfcMessageHandler.h
dom/nfc/gonk/NfcOptions.h
dom/nfc/gonk/NfcService.cpp
dom/nfc/nsINfcContentHelper.idl
dom/nfc/nsNfc.js
dom/webidl/MozNFCTag.webidl
dom/webidl/NfcOptions.webidl
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -50,16 +50,17 @@ updateDebug();
 const NFCCONTENTHELPER_CID =
   Components.ID("{4d72c120-da5f-11e1-9b23-0800200c9a66}");
 
 const NFC_IPC_MSG_NAMES = [
   "NFC:ReadNDEFResponse",
   "NFC:WriteNDEFResponse",
   "NFC:MakeReadOnlyResponse",
   "NFC:FormatResponse",
+  "NFC:TransceiveResponse",
   "NFC:ConnectResponse",
   "NFC:CloseResponse",
   "NFC:CheckP2PRegistrationResponse",
   "NFC:DOMEvent",
   "NFC:NotifySendFileStatusResponse",
   "NFC:ChangeRFStateResponse"
 ];
 
@@ -169,16 +170,28 @@ NfcContentHelper.prototype = {
     this._requestMap[requestId] = callback;
 
     cpmm.sendAsyncMessage("NFC:Format", {
       requestId: requestId,
       sessionToken: sessionToken
     });
   },
 
+  transceive: function transceive(sessionToken, technology, command, callback) {
+    let requestId = callback.getCallbackId();
+    this._requestMap[requestId] = callback;
+
+    cpmm.sendAsyncMessage("NFC:Transceive", {
+      requestId: requestId,
+      sessionToken: sessionToken,
+      technology: technology,
+      command: command
+    });
+  },
+
   connect: function connect(techType, sessionToken, callback) {
     let requestId = callback.getCallbackId();
     this._requestMap[requestId] = callback;
 
     cpmm.sendAsyncMessage("NFC:Connect", {
       requestId: requestId,
       sessionToken: sessionToken,
       techType: techType
@@ -275,16 +288,19 @@ NfcContentHelper.prototype = {
 
     switch (message.name) {
       case "NFC:ReadNDEFResponse":
         this.handleReadNDEFResponse(result);
         break;
       case "NFC:CheckP2PRegistrationResponse":
         this.handleCheckP2PRegistrationResponse(result);
         break;
+      case "NFC:TransceiveResponse":
+        this.handleTransceiveResponse(result);
+        break;
       case "NFC:ConnectResponse": // Fall through.
       case "NFC:CloseResponse":
       case "NFC:WriteNDEFResponse":
       case "NFC:MakeReadOnlyResponse":
       case "NFC:FormatResponse":
       case "NFC:NotifySendFileStatusResponse":
       case "NFC:ChangeRFStateResponse":
         this.handleGeneralResponse(result);
@@ -389,16 +405,33 @@ NfcContentHelper.prototype = {
       return;
     }
     delete this._requestMap[requestId];
 
     // Privilaged status API. Always fire success to avoid using exposed props.
     // The receiver must check the boolean mapped status code to handle.
     callback.notifySuccessWithBoolean(!result.errorMsg);
   },
+
+  handleTransceiveResponse: function handleTransceiveResponse(result) {
+    let requestId = result.requestId;
+    let callback = this._requestMap[requestId];
+    if (!callback) {
+      debug("not firing message handleTransceiveResponse for id: " + requestId);
+      return;
+    }
+    delete this._requestMap[requestId];
+
+    if (result.errorMsg) {
+      callback.notifyError(result.errorMsg);
+      return;
+    }
+
+    callback.notifySuccessWithByteArray(result.response);
+  },
 };
 
 function TagNDEFInfo(tagType, maxNDEFSize, isReadOnly, isFormatable) {
   this.tagType = tagType;
   this.maxNDEFSize = maxNDEFSize;
   this.isReadOnly = isReadOnly;
   this.isFormatable = isFormatable;
 }
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -60,17 +60,18 @@ const NFC_IPC_MSG_ENTRIES = [
                "NFC:QueryInfo"] },
 
   { permission: "nfc",
     messages: ["NFC:ReadNDEF",
                "NFC:Connect",
                "NFC:Close",
                "NFC:WriteNDEF",
                "NFC:MakeReadOnly",
-               "NFC:Format"] },
+               "NFC:Format",
+               "NFC:Transceive"] },
 
   { permission: "nfc-share",
     messages: ["NFC:SendFile",
                "NFC:RegisterPeerReadyTarget",
                "NFC:UnregisterPeerReadyTarget"] },
 
   { permission: "nfc-manager",
     messages: ["NFC:CheckP2PRegistration",
@@ -513,16 +514,17 @@ Nfc.prototype = {
           gMessageManager.onRFStateChange(this.rfState);
         }
         break;
       case "ConnectResponse": // Fall through.
       case "CloseResponse":
       case "ReadNDEFResponse":
       case "MakeReadOnlyResponse":
       case "FormatResponse":
+      case "TransceiveResponse":
       case "WriteNDEFResponse":
         this.sendNfcResponse(message);
         break;
       default:
         throw new Error("Don't know about this message type: " + message.type);
     }
   },
 
@@ -569,16 +571,19 @@ Nfc.prototype = {
         this.sendToNfcService("writeNDEF", message.data);
         break;
       case "NFC:MakeReadOnly":
         this.sendToNfcService("makeReadOnly", message.data);
         break;
       case "NFC:Format":
         this.sendToNfcService("format", message.data);
         break;
+      case "NFC:Transceive":
+        this.sendToNfcService("transceive", message.data);
+        break;
       case "NFC:Connect":
         this.sendToNfcService("connect", message.data);
         break;
       case "NFC:Close":
         this.sendToNfcService("close", message.data);
         break;
       case "NFC:SendFile":
         // Chrome process is the arbitrator / mediator between
--- a/dom/nfc/gonk/NfcGonkMessage.h
+++ b/dom/nfc/gonk/NfcGonkMessage.h
@@ -13,22 +13,24 @@ namespace mozilla {
 enum NfcRequest {
   ChangeRFStateReq = 0,
   ConnectReq,
   CloseReq,
   ReadNDEFReq,
   WriteNDEFReq,
   MakeReadOnlyReq,
   FormatReq,
+  TransceiveReq,
 };
 
 enum NfcResponse {
   GeneralRsp = 1000,
   ChangeRFStateRsp,
   ReadNDEFRsp,
+  TransceiveRsp
 };
 
 enum NfcNotification {
   Initialized = 2000,
   TechDiscovered,
   TechLost,
   HCIEventTransaction,
 };
--- a/dom/nfc/gonk/NfcMessageHandler.cpp
+++ b/dom/nfc/gonk/NfcMessageHandler.cpp
@@ -17,24 +17,26 @@ using namespace android;
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static const char* kChangeRFStateRequest = "changeRFState";
 static const char* kReadNDEFRequest = "readNDEF";
 static const char* kWriteNDEFRequest = "writeNDEF";
 static const char* kMakeReadOnlyRequest = "makeReadOnly";
 static const char* kFormatRequest = "format";
+static const char* kTransceiveRequest = "transceive";
 static const char* kConnectRequest = "connect";
 static const char* kCloseRequest = "close";
 
 static const char* kChangeRFStateResponse = "ChangeRFStateResponse";
 static const char* kReadNDEFResponse = "ReadNDEFResponse";
 static const char* kWriteNDEFResponse = "WriteNDEFResponse";
 static const char* kMakeReadOnlyResponse = "MakeReadOnlyResponse";
 static const char* kFormatResponse = "FormatResponse";
+static const char* kTransceiveResponse = "TransceiveResponse";
 static const char* kConnectResponse = "ConnectResponse";
 static const char* kCloseResponse = "CloseResponse";
 
 static const char* kInitializedNotification = "InitializedNotification";
 static const char* kTechDiscoveredNotification = "TechDiscoveredNotification";
 static const char* kTechLostNotification = "TechLostNotification";
 static const char* kHCIEventTransactionNotification =
                      "HCIEventTransactionNotification";
@@ -53,16 +55,19 @@ NfcMessageHandler::Marshall(Parcel& aPar
     result = WriteNDEFRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::WriteNDEFReq);
   } else if (!strcmp(type, kMakeReadOnlyRequest)) {
     result = MakeReadOnlyRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::MakeReadOnlyReq);
   } else if (!strcmp(type, kFormatRequest)) {
     result = FormatRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::FormatReq);
+  } else if (!strcmp(type, kTransceiveRequest)) {
+    result = TransceiveRequest(aParcel, aOptions);
+    mPendingReqQueue.AppendElement(NfcRequest::TransceiveReq);
   } else if (!strcmp(type, kConnectRequest)) {
     result = ConnectRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::ConnectReq);
   } else if (!strcmp(type, kCloseRequest)) {
     result = CloseRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::CloseReq);
   } else {
     result = false;
@@ -83,16 +88,19 @@ NfcMessageHandler::Unmarshall(const Parc
       result = GeneralResponse(aParcel, aOptions);
       break;
     case NfcResponse::ChangeRFStateRsp:
       result = ChangeRFStateResponse(aParcel, aOptions);
       break;
     case NfcResponse::ReadNDEFRsp:
       result = ReadNDEFResponse(aParcel, aOptions);
       break;
+    case NfcResponse::TransceiveRsp:
+      result = TransceiveResponse(aParcel, aOptions);
+      break;
     case NfcNotification::Initialized:
       result = InitializeNotification(aParcel, aOptions);
       break;
     case NfcNotification::TechDiscovered:
       result = TechDiscoveredNotification(aParcel, aOptions);
       break;
     case NfcNotification::TechLost:
       result = TechLostNotification(aParcel, aOptions);
@@ -192,16 +200,34 @@ NfcMessageHandler::ReadNDEFResponse(cons
   if (aOptions.mErrorCode == NfcErrorCode::Success) {
     ReadNDEFMessage(aParcel, aOptions);
   }
 
   return true;
 }
 
 bool
+NfcMessageHandler::TransceiveResponse(const Parcel& aParcel, EventOptions& aOptions)
+{
+  aOptions.mType = NS_ConvertUTF8toUTF16(kTransceiveResponse);
+  aOptions.mErrorCode = aParcel.readInt32();
+  aOptions.mSessionId = aParcel.readInt32();
+
+  NS_ENSURE_TRUE(!mRequestIdQueue.IsEmpty(), false);
+  aOptions.mRequestId = mRequestIdQueue[0];
+  mRequestIdQueue.RemoveElementAt(0);
+
+  if (aOptions.mErrorCode == NfcErrorCode::Success) {
+    ReadTransceiveResponse(aParcel, aOptions);
+  }
+
+  return true;
+}
+
+bool
 NfcMessageHandler::WriteNDEFRequest(Parcel& aParcel, const CommandOptions& aOptions)
 {
   aParcel.writeInt32(NfcRequest::WriteNDEFReq);
   aParcel.writeInt32(aOptions.mSessionId);
   aParcel.writeInt32(aOptions.mIsP2P);
   WriteNDEFMessage(aParcel, aOptions);
   mRequestIdQueue.AppendElement(aOptions.mRequestId);
   return true;
@@ -221,16 +247,33 @@ NfcMessageHandler::FormatRequest(Parcel&
 {
   aParcel.writeInt32(NfcRequest::FormatReq);
   aParcel.writeInt32(aOptions.mSessionId);
   mRequestIdQueue.AppendElement(aOptions.mRequestId);
   return true;
 }
 
 bool
+NfcMessageHandler::TransceiveRequest(Parcel& aParcel, const CommandOptions& aOptions)
+{
+  aParcel.writeInt32(NfcRequest::TransceiveReq);
+  aParcel.writeInt32(aOptions.mSessionId);
+  aParcel.writeInt32(aOptions.mTechnology);
+
+  uint32_t length = aOptions.mCommand.Length();
+  aParcel.writeInt32(length);
+
+  void* data = aParcel.writeInplace(length);
+  memcpy(data, aOptions.mCommand.Elements(), length);
+
+  mRequestIdQueue.AppendElement(aOptions.mRequestId);
+  return true;
+}
+
+bool
 NfcMessageHandler::ConnectRequest(Parcel& aParcel, const CommandOptions& aOptions)
 {
   aParcel.writeInt32(NfcRequest::ConnectReq);
   aParcel.writeInt32(aOptions.mSessionId);
   aParcel.writeInt32(aOptions.mTechType);
   mRequestIdQueue.AppendElement(aOptions.mRequestId);
   return true;
 }
@@ -369,8 +412,19 @@ NfcMessageHandler::WriteNDEFMessage(Parc
 
     aParcel.writeInt32(record.mPayload.Length());
     data = aParcel.writeInplace(record.mPayload.Length());
     memcpy(data, record.mPayload.Elements(), record.mPayload.Length());
   }
 
   return true;
 }
+
+bool
+NfcMessageHandler::ReadTransceiveResponse(const Parcel& aParcel, EventOptions& aOptions)
+{
+  uint32_t length = aParcel.readInt32();
+
+  aOptions.mResponse.AppendElements(
+    static_cast<const uint8_t*>(aParcel.readInplace(length)), length);
+
+  return true;
+}
--- a/dom/nfc/gonk/NfcMessageHandler.h
+++ b/dom/nfc/gonk/NfcMessageHandler.h
@@ -27,27 +27,29 @@ private:
   bool GeneralResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool ChangeRFStateRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool ChangeRFStateResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool ReadNDEFRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool ReadNDEFResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool WriteNDEFRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool MakeReadOnlyRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool FormatRequest(android::Parcel& aParcel, const CommandOptions& options);
+  bool TransceiveRequest(android::Parcel& aParcel, const CommandOptions& options);
+  bool TransceiveResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool ConnectRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool CloseRequest(android::Parcel& aParcel, const CommandOptions& options);
 
   bool InitializeNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool TechDiscoveredNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool TechLostNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool HCIEventTransactionNotification(const android::Parcel& aParcel, EventOptions& aOptions);
 
   bool ReadNDEFMessage(const android::Parcel& aParcel, EventOptions& aOptions);
   bool WriteNDEFMessage(android::Parcel& aParcel, const CommandOptions& aOptions);
-
+  bool ReadTransceiveResponse(const android::Parcel& aParcel, EventOptions& aOptions);
 private:
   nsTArray<int32_t> mPendingReqQueue;
   nsTArray<nsString> mRequestIdQueue;
 };
 
 } // namespace mozilla
 
 #endif // NfcMessageHandler_h
--- a/dom/nfc/gonk/NfcOptions.h
+++ b/dom/nfc/gonk/NfcOptions.h
@@ -33,16 +33,26 @@ struct CommandOptions
 
     mRfState = aOther.mRfState.WasPassed() ?
                  static_cast<int32_t>(aOther.mRfState.Value()) :
                  0;
 
     COPY_OPT_FIELD(mTechType, 0)
     COPY_OPT_FIELD(mIsP2P, false)
 
+    mTechnology = aOther.mTechnology.WasPassed() ?
+                    static_cast<int32_t>(aOther.mTechnology.Value()) :
+                    -1;
+
+    if (aOther.mCommand.WasPassed()) {
+      dom::Uint8Array const & currentValue = aOther.mCommand.InternalValue();
+      currentValue.ComputeLengthAndData();
+      mCommand.AppendElements(currentValue.Data(), currentValue.Length());
+    }
+
     if (!aOther.mRecords.WasPassed()) {
       return;
     }
 
     mozilla::dom::Sequence<mozilla::dom::MozNDEFRecordOptions> const & currentValue = aOther.mRecords.InternalValue();
     int count = currentValue.Length();
     for (int32_t i = 0; i < count; i++) {
       NDEFRecordStruct record;
@@ -75,16 +85,18 @@ struct CommandOptions
 
   nsString mType;
   int32_t mSessionId;
   nsString mRequestId;
   int32_t mRfState;
   int32_t mTechType;
   bool mIsP2P;
   nsTArray<NDEFRecordStruct> mRecords;
+  int32_t mTechnology;
+  nsTArray<uint8_t> mCommand;
 };
 
 struct EventOptions
 {
   EventOptions()
     : mType(EmptyString()), mStatus(-1), mErrorCode(-1), mSessionId(-1), mRequestId(EmptyString()),
       mMajorVersion(-1), mMinorVersion(-1), mIsP2P(-1),
       mTagType(-1), mMaxNDEFSize(-1), mIsReadOnly(-1), mIsFormatable(-1), mRfState(-1),
@@ -107,13 +119,14 @@ struct EventOptions
   int32_t mIsReadOnly;
   int32_t mIsFormatable;
   int32_t mRfState;
 
   int32_t mOriginType;
   int32_t mOriginIndex;
   nsTArray<uint8_t> mAid;
   nsTArray<uint8_t> mPayload;
+  nsTArray<uint8_t> mResponse;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/nfc/gonk/NfcService.cpp
+++ b/dom/nfc/gonk/NfcService.cpp
@@ -193,16 +193,22 @@ public:
       event.mAid.Value().Init(Uint8Array::Create(cx, mEvent.mAid.Length(), mEvent.mAid.Elements()));
     }
 
     if (mEvent.mPayload.Length() > 0) {
       event.mPayload.Construct();
       event.mPayload.Value().Init(Uint8Array::Create(cx, mEvent.mPayload.Length(), mEvent.mPayload.Elements()));
     }
 
+    if (mEvent.mResponse.Length() > 0) {
+      event.mResponse.Construct();
+      event.mResponse.Value().Init(
+        Uint8Array::Create(cx, mEvent.mResponse.Length(), mEvent.mResponse.Elements()));
+    }
+
 #undef COPY_FIELD
 #undef COPY_OPT_FIELD
 
     gNfcService->DispatchNfcEvent(event);
     return NS_OK;
   }
 
 private:
--- a/dom/nfc/nsINfcContentHelper.idl
+++ b/dom/nfc/nsINfcContentHelper.idl
@@ -81,31 +81,33 @@ interface nsINfcEventListener : nsISuppo
    * Callback function used to notify RF state change.
    *
    * @param rfState
    *        RF state received from parent process
    */
   void notifyRFStateChange(in DOMString rfState);
 };
 
-[scriptable, uuid(a8ef3590-d853-4766-b54a-a4547da4dde4)]
+[scriptable, uuid(6c913015-9658-46a9-88d9-6ecfda2bd020)]
 interface nsINfcRequestCallback : nsISupports
 {
   DOMString getCallbackId();
 
   void notifySuccess();
 
   void notifySuccessWithBoolean(in boolean result);
 
   void notifySuccessWithNDEFRecords(in nsIVariant records);
 
+  void notifySuccessWithByteArray(in nsIVariant array);
+
   void notifyError(in DOMString errorMsg);
 };
 
-[scriptable, uuid(c5fdf956-735e-45d3-aa25-3a871bd3e2f8)]
+[scriptable, uuid(0f8aae32-9920-491e-a197-8995941d54df)]
 interface nsINfcContentHelper : nsISupports
 {
   void init(in nsIDOMWindow window);
 
   /**
    * Read current NDEF data on the tag.
    *
    * @param sessionToken
@@ -153,16 +155,37 @@ interface nsINfcContentHelper : nsISuppo
    *
    * @param callback
    *        Called when request is finished
    */
   void format(in DOMString sessionToken,
               in nsINfcRequestCallback callback);
 
   /**
+   * Send raw command to the tag and receive the response.
+   *
+   * @param sessionToken
+   *        Current token
+   *
+   * @param technology
+   *        Tag technology
+   *
+   * @param command
+   *        Command to send
+   *
+   * @param callback
+   *        Called when request is finished
+   *
+   */
+  void transceive(in DOMString sessionToken,
+                  in DOMString technology,
+                  in nsIVariant command,
+                  in nsINfcRequestCallback callback);
+
+  /**
    * Enable I/O operations to the tag
    *
    * @param techType
    *        Interface to a technology in a Tag
    *
    * @param sessionToken
    *        Current token
    *
--- a/dom/nfc/nsNfc.js
+++ b/dom/nfc/nsNfc.js
@@ -69,16 +69,25 @@ NfcCallback.prototype = {
     let resolver = this.takePromiseResolver(atob(this._requestId));
     if (!resolver) {
       debug("can not find promise resolver for id: " + this._requestId);
       return;
     }
     resolver.resolve(aRecords);
   },
 
+  notifySuccessWithByteArray: function notifySuccessWithByteArray(aArray) {
+    let resolver = this.takePromiseResolver(atob(this._requestId));
+    if (!resolver) {
+      debug("can not find promise resolver for id: " + this._requestId);
+      return;
+    }
+    resolver.resolve(aArray);
+  },
+
   notifyError: function notifyError(aErrorMsg) {
     let resolver = this.takePromiseResolver(atob(this._requestId));
     if (!resolver) {
       debug("can not find promise resolver for id: " + this._requestId +
            ", errormsg: " + aErrorMsg);
       return;
     }
     resolver.reject(aErrorMsg);
@@ -187,16 +196,26 @@ MozNFCTagImpl.prototype = {
                                       "NFCTag object is not formatable");
     }
 
     let callback = new NfcCallback(this._window);
     this._nfcContentHelper.format(this.session, callback);
     return callback.promise;
   },
 
+  transceive: function transceive(tech, cmd) {
+    if (this.isLost) {
+      throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
+    }
+
+    let callback = new NfcCallback(this._window);
+    this._nfcContentHelper.transceive(this.session, tech, cmd, callback);
+    return callback.promise;
+  },
+
   notifyLost: function notifyLost() {
     this.isLost = true;
   },
 
   classID: Components.ID("{4e1e2e90-3137-11e3-aa6e-0800200c9a66}"),
   contractID: "@mozilla.org/nfc/tag;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
--- a/dom/webidl/MozNFCTag.webidl
+++ b/dom/webidl/MozNFCTag.webidl
@@ -104,9 +104,15 @@ interface MozNFCTag {
 
 // Mozilla Only
 partial interface MozNFCTag {
   [ChromeOnly]
   attribute DOMString session;
 
   [ChromeOnly]
   void notifyLost();
+
+  /**
+   * Send raw command to tag and receive the response.
+   */
+  [ChromeOnly, Throws]
+  Promise<Uint8Array> transceive(NFCTechType tech, Uint8Array command);
 };
--- a/dom/webidl/NfcOptions.webidl
+++ b/dom/webidl/NfcOptions.webidl
@@ -16,16 +16,19 @@ dictionary NfcCommandOptions
   DOMString requestId = "";
 
   RFState rfState;
 
   long techType;
 
   boolean isP2P;
   sequence<MozNDEFRecordOptions> records;
+
+  NFCTechType technology;
+  Uint8Array command;
 };
 
 dictionary NfcEventOptions
 {
   DOMString type = "";
 
   long status;
   NfcErrorMessage errorMsg;
@@ -46,9 +49,12 @@ dictionary NfcEventOptions
   boolean isFormatable;
 
   RFState rfState;
 
   // HCI Event Transaction fields
   DOMString origin;
   Uint8Array aid;
   Uint8Array payload;
+
+  // Tag transceive response data
+  Uint8Array response;
 };