1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* Copyright © 2013, Deutsche Telekom, Inc. */
11 if (DEBUG) dump("-*- Nfc DOM: " + s + "\n");
14 const Cc = Components.classes;
15 const Ci = Components.interfaces;
16 const Cu = Components.utils;
18 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
19 Cu.import("resource://gre/modules/Services.jsm");
20 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
22 XPCOMUtils.defineLazyServiceGetter(this,
24 "@mozilla.org/AppsService;1",
27 function NfcCallback(aWindow) {
28 this._window = aWindow;
29 this.initDOMRequestHelper(aWindow, null);
30 this._createPromise();
32 NfcCallback.prototype = {
33 __proto__: DOMRequestIpcHelper.prototype,
39 _createPromise: function _createPromise() {
40 this.promise = this.createPromise((aResolve, aReject) => {
41 this._requestId = btoa(this.getPromiseResolverId({
48 getCallbackId: function getCallbackId() {
49 return this._requestId;
52 notifySuccess: function notifySuccess() {
53 let resolver = this.takePromiseResolver(atob(this._requestId));
55 debug("can not find promise resolver for id: " + this._requestId);
61 notifySuccessWithBoolean: function notifySuccessWithBoolean(aResult) {
62 let resolver = this.takePromiseResolver(atob(this._requestId));
64 debug("can not find promise resolver for id: " + this._requestId);
67 resolver.resolve(aResult);
70 notifySuccessWithNDEFRecords: function notifySuccessWithNDEFRecords(aRecords) {
71 let resolver = this.takePromiseResolver(atob(this._requestId));
73 debug("can not find promise resolver for id: " + this._requestId);
77 let records = new this._window.Array();
78 for (let i = 0; i < aRecords.length; i++) {
79 let record = aRecords[i];
80 records.push(new this._window.MozNDEFRecord({tnf: record.tnf,
83 payload: record.payload}));
85 resolver.resolve(records);
88 notifySuccessWithByteArray: function notifySuccessWithByteArray(aArray) {
89 let resolver = this.takePromiseResolver(atob(this._requestId));
91 debug("can not find promise resolver for id: " + this._requestId);
94 resolver.resolve(Cu.cloneInto(aArray, this._window));
97 notifyError: function notifyError(aErrorMsg) {
98 let resolver = this.takePromiseResolver(atob(this._requestId));
100 debug("can not find promise resolver for id: " + this._requestId +
101 ", errormsg: " + aErrorMsg);
104 resolver.reject(new this._window.Error(aErrorMsg));
107 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
109 Ci.nsINfcRequestCallback]),
112 // Should be mapped to the NFCTagType defined in MozNFCTag.webidl.
118 MIFARE_CLASSIC: "MIFARE-Classic"
122 * Implementation of NFCTag.
124 * @param window global window object.
125 * @param sessionToken session token received from parent process.
126 * @param tagInfo type of nsITagInfo received from parent process.
127 * @parem ndefInfo type of nsITagNDEFInfo received from parent process.
129 function MozNFCTagImpl(window, sessionToken, tagInfo, ndefInfo) {
130 debug("In MozNFCTagImpl Constructor");
131 this._nfcContentHelper = Cc["@mozilla.org/nfc/content-helper;1"]
132 .getService(Ci.nsINfcContentHelper);
133 this._window = window;
134 this.session = sessionToken;
135 this.techList = tagInfo.techList;
136 this.id = Cu.cloneInto(tagInfo.tagId, window);
139 this.type = ndefInfo.tagType;
140 this.maxNDEFSize = ndefInfo.maxNDEFSize;
141 this.isReadOnly = ndefInfo.isReadOnly;
142 this.isFormatable = ndefInfo.isFormatable;
143 this.canBeMadeReadOnly = this.type == TagType.TYPE1 ||
144 this.type == TagType.TYPE2 ||
145 this.type == TagType.MIFARE_CLASSIC;
148 MozNFCTagImpl.prototype = {
149 _nfcContentHelper: null,
158 canBeMadeReadOnly: null,
161 createTech: { "ISO-DEP": (win, tag) => { return new win.MozIsoDepTech(tag); }
165 readNDEF: function readNDEF() {
167 throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
170 let callback = new NfcCallback(this._window);
171 this._nfcContentHelper.readNDEF(this.session, callback);
172 return callback.promise;
175 writeNDEF: function writeNDEF(records) {
177 throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
180 if (this.isReadOnly) {
181 throw new this._window.DOMError("InvalidAccessError", "NFCTag object is read-only");
185 for (let record of records) {
186 ndefLen += record.size;
189 if (ndefLen > this.maxNDEFSize) {
190 throw new this._window.DOMError("NotSupportedError", "Exceed max NDEF size");
193 let callback = new NfcCallback(this._window);
194 this._nfcContentHelper.writeNDEF(records, this.session, callback);
195 return callback.promise;
198 makeReadOnly: function makeReadOnly() {
200 throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
203 if (!this.canBeMadeReadOnly) {
204 throw new this._window.DOMError("InvalidAccessError",
205 "NFCTag object cannot be made read-only");
208 let callback = new NfcCallback(this._window);
209 this._nfcContentHelper.makeReadOnly(this.session, callback);
210 return callback.promise;
213 format: function format() {
215 throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
218 if (!this.isFormatable) {
219 throw new this._window.DOMError("InvalidAccessError",
220 "NFCTag object is not formatable");
223 let callback = new NfcCallback(this._window);
224 this._nfcContentHelper.format(this.session, callback);
225 return callback.promise;
228 selectTech: function selectTech(tech) {
230 throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
233 if (this.techList.indexOf(tech) == -1) {
234 throw new this._window.DOMError("InvalidAccessError",
235 "NFCTag does not contain selected tag technology");
238 if (this.createTech[tech] === undefined) {
239 throw new this._window.DOMError("InvalidAccessError",
240 "Technology is not supported now");
243 return this.createTech[tech](this._window, this._contentObj);
246 transceive: function transceive(tech, cmd) {
248 throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
251 let callback = new NfcCallback(this._window);
252 this._nfcContentHelper.transceive(this.session, tech, cmd, callback);
253 return callback.promise;
256 notifyLost: function notifyLost() {
260 classID: Components.ID("{4e1e2e90-3137-11e3-aa6e-0800200c9a66}"),
261 contractID: "@mozilla.org/nfc/tag;1",
262 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
263 Ci.nsIDOMGlobalPropertyInitializer]),
267 * Implementation of NFCPeer.
269 * @param window global window object.
270 * @param sessionToken session token received from parent process.
272 function MozNFCPeerImpl(aWindow, aSessionToken) {
273 debug("In MozNFCPeerImpl Constructor");
274 this._nfcContentHelper = Cc["@mozilla.org/nfc/content-helper;1"]
275 .getService(Ci.nsINfcContentHelper);
277 this._window = aWindow;
278 this.session = aSessionToken;
280 MozNFCPeerImpl.prototype = {
281 _nfcContentHelper: null,
285 // NFCPeer interface:
286 sendNDEF: function sendNDEF(records) {
288 throw new this._window.DOMError("InvalidStateError", "NFCPeer object is invalid");
291 // Just forward sendNDEF to writeNDEF
292 let callback = new NfcCallback(this._window);
293 this._nfcContentHelper.writeNDEF(records, this.session, callback);
294 return callback.promise;
297 sendFile: function sendFile(blob) {
299 throw new this._window.DOMError("InvalidStateError", "NFCPeer object is invalid");
306 let callback = new NfcCallback(this._window);
307 this._nfcContentHelper.sendFile(Cu.cloneInto(data, this._window),
308 this.session, callback);
309 return callback.promise;
312 notifyLost: function notifyLost() {
316 classID: Components.ID("{c1b2bcf0-35eb-11e3-aa6e-0800200c9a66}"),
317 contractID: "@mozilla.org/nfc/peer;1",
318 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
319 Ci.nsIDOMGlobalPropertyInitializer]),
322 // Should be mapped to the RFState defined in WebIDL.
326 DISCOVERY: "discovery"
330 * Implementation of navigator NFC object.
332 function MozNFCImpl() {
333 debug("In MozNFCImpl Constructor");
335 this._nfcContentHelper = Cc["@mozilla.org/nfc/content-helper;1"]
336 .getService(Ci.nsINfcContentHelper);
338 debug("No NFC support.");
341 this.eventService = Cc["@mozilla.org/eventlistenerservice;1"]
342 .getService(Ci.nsIEventListenerService);
344 MozNFCImpl.prototype = {
345 _nfcContentHelper: null,
348 _innerWindowId: null,
355 init: function init(aWindow) {
356 debug("MozNFCImpl init called");
357 this.window = aWindow;
358 let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
359 .getInterface(Ci.nsIDOMWindowUtils);
360 this._innerWindowId = util.currentInnerWindowID;
362 this.defineEventHandlerGetterSetter("ontagfound");
363 this.defineEventHandlerGetterSetter("ontaglost");
364 this.defineEventHandlerGetterSetter("onpeerready");
365 this.defineEventHandlerGetterSetter("onpeerfound");
366 this.defineEventHandlerGetterSetter("onpeerlost");
368 Services.obs.addObserver(this, "inner-window-destroyed",
369 /* weak-ref */ false);
371 if (this._nfcContentHelper) {
372 this._tabId = this.getTabId(aWindow);
373 this._nfcContentHelper.addEventListener(this, this._tabId);
374 this._rfState = this._nfcContentHelper.queryRFState();
378 getTabId: function getTabId(aWindow) {
380 // For now, we assume app will run in oop mode so we can get
381 // tab id for each app. Fix bug 1116449 if we are going to
382 // support in-process mode.
383 let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
384 .getInterface(Ci.nsIWebNavigation)
385 .QueryInterface(Ci.nsIDocShell);
387 tabId = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
388 .getInterface(Ci.nsITabChild)
391 // Only parent process does not have tab id, so in this case
392 // NfcContentHelper is used by system app. Use -1(tabId) to
393 // indicate its system app.
394 let inParent = Cc["@mozilla.org/xre/app-info;1"]
395 .getService(Ci.nsIXULRuntime)
396 .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
398 tabId = Ci.nsINfcBrowserAPI.SYSTEM_APP_ID;
400 throw Components.Exception("Can't get tab id in child process",
401 Cr.NS_ERROR_UNEXPECTED);
408 // Only apps which have nfc-manager permission can call the following interfaces
409 // 'checkP2PRegistration' , 'notifyUserAcceptedP2P' , 'notifySendFileStatus',
410 // 'startPoll', 'stopPoll', and 'powerOff'.
411 checkP2PRegistration: function checkP2PRegistration(manifestUrl) {
412 // Get the AppID and pass it to ContentHelper
413 let appID = appsService.getAppLocalIdByManifestURL(manifestUrl);
415 let callback = new NfcCallback(this.window);
416 this._nfcContentHelper.checkP2PRegistration(appID, callback);
417 return callback.promise;
420 notifyUserAcceptedP2P: function notifyUserAcceptedP2P(manifestUrl) {
421 let appID = appsService.getAppLocalIdByManifestURL(manifestUrl);
422 // Notify chrome process of user's acknowledgement
423 this._nfcContentHelper.notifyUserAcceptedP2P(appID);
426 notifySendFileStatus: function notifySendFileStatus(status, requestId) {
427 this._nfcContentHelper.notifySendFileStatus(status, requestId);
430 startPoll: function startPoll() {
431 let callback = new NfcCallback(this.window);
432 this._nfcContentHelper.changeRFState(RFState.DISCOVERY, callback);
433 return callback.promise;
436 stopPoll: function stopPoll() {
437 let callback = new NfcCallback(this.window);
438 this._nfcContentHelper.changeRFState(RFState.LISTEN, callback);
439 return callback.promise;
442 powerOff: function powerOff() {
443 let callback = new NfcCallback(this.window);
444 this._nfcContentHelper.changeRFState(RFState.IDLE, callback);
445 return callback.promise;
449 return this._rfState != RFState.IDLE;
452 observe: function observe(subject, topic, data) {
453 if (topic !== "inner-window-destroyed") {
457 let wId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
458 if (wId != this._innerWindowId) {
462 this._nfcContentHelper.removeEventListener(this._tabId);
465 defineEventHandlerGetterSetter: function defineEventHandlerGetterSetter(name) {
466 Object.defineProperty(this, name, {
467 get: function get() {
468 return this.__DOM_IMPL__.getEventHandler(name);
470 set: function set(handler) {
471 this.__DOM_IMPL__.setEventHandler(name, handler);
476 eventListenerWasAdded: function(eventType) {
477 if (eventType !== "peerready") {
481 let appId = this.window.document.nodePrincipal.appId;
482 this._nfcContentHelper.registerTargetForPeerReady(appId);
485 eventListenerWasRemoved: function(eventType) {
486 if (eventType !== "peerready") {
490 let appId = this.window.document.nodePrincipal.appId;
491 this._nfcContentHelper.unregisterTargetForPeerReady(appId);
494 notifyTagFound: function notifyTagFound(sessionToken, tagInfo, ndefInfo, records) {
495 if (!this.handleTagFound(sessionToken, tagInfo, ndefInfo, records)) {
496 this._nfcContentHelper.callDefaultFoundHandler(sessionToken, false, records);
501 * Handles Tag Found event.
503 * returns true if the app could process this event, false otherwise.
505 handleTagFound: function handleTagFound(sessionToken, tagInfo, ndefInfo, records) {
506 if (this.hasDeadWrapper()) {
507 dump("this.window or this.__DOM_IMPL__ is a dead wrapper.");
511 if (!this.eventService.hasListenersFor(this.__DOM_IMPL__, "tagfound")) {
512 debug("ontagfound is not registered.");
516 if (!this.checkPermissions(["nfc"])) {
520 let tagImpl = new MozNFCTagImpl(this.window, sessionToken, tagInfo, ndefInfo);
521 let tag = this.window.MozNFCTag._create(this.window, tagImpl);
523 tagImpl._contentObj = tag;
526 let length = records ? records.length : 0;
527 let ndefRecords = records ? [] : null;
528 for (let i = 0; i < length; i++) {
529 let record = records[i];
530 ndefRecords.push(new this.window.MozNDEFRecord({tnf: record.tnf,
533 payload: record.payload}));
539 "ndefRecords": ndefRecords
542 debug("fire ontagfound " + sessionToken);
543 let tagEvent = new this.window.MozNFCTagEvent("tagfound", eventData);
544 this.__DOM_IMPL__.dispatchEvent(tagEvent);
546 // If defaultPrevented is false, means we need to take the default action
547 // for this event - redirect this event to System app. Before redirecting to
548 // System app, we need revoke the tag object first.
549 if (!tagEvent.defaultPrevented) {
550 this.notifyTagLost(sessionToken);
553 return tagEvent.defaultPrevented;
556 notifyTagLost: function notifyTagLost(sessionToken) {
557 if (!this.handleTagLost(sessionToken)) {
558 this._nfcContentHelper.callDefaultLostHandler(sessionToken, false);
562 handleTagLost: function handleTagLost(sessionToken) {
563 if (this.hasDeadWrapper()) {
564 dump("this.window or this.__DOM_IMPL__ is a dead wrapper.");
568 if (!this.checkPermissions(["nfc"])) {
573 debug("No NFCTag object existing.");
577 this.nfcTag.notifyLost();
580 debug("fire ontaglost " + sessionToken);
581 let event = new this.window.Event("taglost");
582 this.__DOM_IMPL__.dispatchEvent(event);
587 notifyPeerFound: function notifyPeerFound(sessionToken, isPeerReady) {
588 if (!this.handlePeerFound(sessionToken, isPeerReady)) {
589 this._nfcContentHelper.callDefaultFoundHandler(sessionToken, true, null);
594 * Handles Peer Found/Peer Ready event.
596 * returns true if the app could process this event, false otherwise.
598 handlePeerFound: function handlePeerFound(sessionToken, isPeerReady) {
599 if (this.hasDeadWrapper()) {
600 dump("this.window or this.__DOM_IMPL__ is a dead wrapper.");
605 !this.eventService.hasListenersFor(this.__DOM_IMPL__, "peerfound")) {
606 debug("onpeerfound is not registered.");
610 let perm = isPeerReady ? ["nfc-share"] : ["nfc"];
611 if (!this.checkPermissions(perm)) {
615 let peerImpl = new MozNFCPeerImpl(this.window, sessionToken);
616 this.nfcPeer = this.window.MozNFCPeer._create(this.window, peerImpl);
624 eventType = "peerready";
626 eventData.cancelable = true;
627 eventType = "peerfound";
630 debug("fire on" + eventType + " " + sessionToken);
631 let event = new this.window.MozNFCPeerEvent(eventType, eventData);
632 this.__DOM_IMPL__.dispatchEvent(event);
634 // For peerready we don't take the default action.
639 // If defaultPrevented is false, means we need to take the default action
640 // for this event - redirect this event to System app. Before redirecting to
641 // System app, we need revoke the peer object first.
642 if (!event.defaultPrevented) {
643 this.notifyPeerLost(sessionToken);
646 return event.defaultPrevented;
649 notifyPeerLost: function notifyPeerLost(sessionToken) {
650 if (!this.handlePeerLost(sessionToken)) {
651 this._nfcContentHelper.callDefaultLostHandler(sessionToken, true);
655 handlePeerLost: function handlePeerLost(sessionToken) {
656 if (this.hasDeadWrapper()) {
657 dump("this.window or this.__DOM_IMPL__ is a dead wrapper.");
661 if (!this.checkPermissions(["nfc", "nfc-share"])) {
666 debug("No NFCPeer object existing.");
670 this.nfcPeer.notifyLost();
673 debug("fire onpeerlost");
674 let event = new this.window.Event("peerlost");
675 this.__DOM_IMPL__.dispatchEvent(event);
680 notifyRFStateChanged: function notifyRFStateChanged(rfState) {
681 this._rfState = rfState;
684 notifyFocusChanged: function notifyFocusChanged(focus) {
690 debug("losing focus, call taglost.");
691 this.notifyTagLost(this.nfcTag.session);
695 debug("losing focus, call peerlost.");
696 this.notifyPeerLost(this.nfcPeer.session);
700 checkPermissions: function checkPermissions(perms) {
701 let principal = this.window.document.nodePrincipal;
702 for (let perm of perms) {
704 Services.perms.testExactPermissionFromPrincipal(principal, perm);
705 if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) {
708 debug("doesn't have " + perm + " permission.");
715 hasDeadWrapper: function hasDeadWrapper() {
716 return Cu.isDeadWrapper(this.window) || Cu.isDeadWrapper(this.__DOM_IMPL__);
719 classID: Components.ID("{6ff2b290-2573-11e3-8224-0800200c9a66}"),
720 contractID: "@mozilla.org/nfc/manager;1",
721 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
722 Ci.nsIDOMGlobalPropertyInitializer,
723 Ci.nsINfcEventListener,
727 function NFCSendFileWrapper() {
729 NFCSendFileWrapper.prototype = {
730 // nsISystemMessagesWrapper implementation.
731 wrapMessage: function wrapMessage(aMessage, aWindow) {
732 let peerImpl = new MozNFCPeerImpl(aWindow, aMessage.sessionToken);
733 let peer = aWindow.MozNFCPeer._create(aWindow, peerImpl);
735 delete aMessage.sessionToken;
736 aMessage = Cu.cloneInto(aMessage, aWindow);
737 aMessage.peer = peer;
741 classDescription: "NFCSendFileWrapper",
742 classID: Components.ID("{c5063a5c-8cb9-41d2-baf5-56062a2e30e9}"),
743 contractID: "@mozilla.org/dom/system-messages/wrapper/nfc-manager-send-file;1",
744 QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesWrapper])
747 function NFCTechDiscoveredWrapper() {
749 NFCTechDiscoveredWrapper.prototype = {
750 // nsISystemMessagesWrapper implementation.
751 wrapMessage: function wrapMessage(aMessage, aWindow) {
752 aMessage = Cu.cloneInto(aMessage, aWindow);
753 if (aMessage.isP2P) {
754 let peerImpl = new MozNFCPeerImpl(aWindow, aMessage.sessionToken);
755 let peer = aWindow.MozNFCPeer._create(aWindow, peerImpl);
756 aMessage.peer = peer;
759 delete aMessage.isP2P;
760 delete aMessage.sessionToken;
765 classDescription: "NFCTechDiscoveredWrapper",
766 classID: Components.ID("{2e7f9285-3c72-4e1f-b985-141a00a23a75}"),
767 contractID: "@mozilla.org/dom/system-messages/wrapper/nfc-manager-tech-discovered;1",
768 QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesWrapper])
771 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozNFCTagImpl,
772 MozNFCPeerImpl, MozNFCImpl, NFCSendFileWrapper, NFCTechDiscoveredWrapper]);