Bug 1082443 - B2G NFC: event fallback to System app if the foreground app cannot handle it. r=smaug, dimi
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -142,16 +142,20 @@ NfcContentHelper.prototype = {
}
},
queryRFState: function queryRFState() {
return this._rfState;
},
encodeNDEFRecords: function encodeNDEFRecords(records) {
+ if (!Array.isArray(records)) {
+ return null;
+ }
+
let encodedRecords = [];
for (let i = 0; i < records.length; i++) {
let record = records[i];
encodedRecords.push({
tnf: record.tnf,
type: record.type || undefined,
id: record.id || undefined,
payload: record.payload || undefined,
@@ -273,16 +277,26 @@ NfcContentHelper.prototype = {
let requestId = callback.getCallbackId();
this._requestMap[requestId] = callback;
cpmm.sendAsyncMessage("NFC:ChangeRFState",
{requestId: requestId,
rfState: rfState});
},
+ callDefaultFoundHandler: function callDefaultFoundHandler(sessionToken,
+ isP2P,
+ records) {
+ let encodedRecords = this.encodeNDEFRecords(records);
+ cpmm.sendAsyncMessage("NFC:CallDefaultFoundHandler",
+ {sessionToken: sessionToken,
+ isP2P: isP2P,
+ records: encodedRecords});
+ },
+
// nsIObserver
observe: function observe(subject, topic, data) {
if (topic == "xpcom-shutdown") {
this.destroyDOMRequestHelper();
Services.obs.removeObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED);
Services.obs.removeObserver(this, "xpcom-shutdown");
cpmm = null;
} else if (topic == NFC.TOPIC_MOZSETTINGS_CHANGED) {
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -52,17 +52,18 @@ updateDebug();
const NFC_CONTRACTID = "@mozilla.org/nfc;1";
const NFC_CID =
Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");
const NFC_IPC_MSG_ENTRIES = [
{ permission: null,
messages: ["NFC:AddEventListener",
- "NFC:QueryInfo"] },
+ "NFC:QueryInfo",
+ "NFC:CallDefaultFoundHandler"] },
{ permission: "nfc",
messages: ["NFC:ReadNDEF",
"NFC:WriteNDEF",
"NFC:MakeReadOnly",
"NFC:Format",
"NFC:Transceive"] },
@@ -222,16 +223,23 @@ XPCOMUtils.defineLazyGetter(this, "gMess
debug("Peer already lost or " + appId + " is not a registered PeerReadytarget");
return;
}
this.notifyDOMEvent(target, {event: NFC.PEER_EVENT_READY,
sessionToken: sessionToken});
},
+ callDefaultFoundHandler: function callDefaultFoundHandler(message) {
+ let sysMsg = new NfcTechDiscoveredSysMsg(message.sessionToken,
+ message.isP2P,
+ message.records || null);
+ gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", sysMsg);
+ },
+
onTagFound: function onTagFound(message) {
let target = this.eventListeners[this.focusApp] ||
this.eventListeners[NFC.SYSTEM_APP_ID];
message.event = NFC.TAG_EVENT_FOUND;
this.notifyDOMEvent(target, message);
@@ -312,16 +320,19 @@ XPCOMUtils.defineLazyGetter(this, "gMess
// to appropriate content process.
message.data.type = "NotifySendFileStatusResponse";
if (message.data.status) {
message.data.errorMsg =
this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_SEND_FILE_FAILED);
}
this.nfc.sendNfcResponse(message.data);
return null;
+ case "NFC:CallDefaultFoundHandler":
+ this.callDefaultFoundHandler(message.data);
+ return null;
default:
return this.nfc.receiveMessage(message);
}
},
/**
* nsIObserver interface methods.
*/
@@ -497,21 +508,16 @@ Nfc.prototype = {
if (message.records) {
// TODO: Bug 1082493.
} else {
gMessageManager.onPeerEvent(NFC.PEER_EVENT_FOUND, message.sessionToken);
}
} else {
gMessageManager.onTagFound(message);
}
-
- let sysMsg = new NfcTechDiscoveredSysMsg(message.sessionToken,
- message.isP2P,
- message.records || null);
- gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", sysMsg);
break;
case "TechLostNotification":
message.type = "techLost";
// Update the upper layers with a session token (alias)
message.sessionToken = SessionHelper.getToken(message.sessionId);
if (SessionHelper.isP2PSession(message.sessionId)) {
gMessageManager.onPeerEvent(NFC.PEER_EVENT_LOST, message.sessionToken);
--- a/dom/nfc/nsINfcContentHelper.idl
+++ b/dom/nfc/nsINfcContentHelper.idl
@@ -106,17 +106,17 @@ interface nsINfcRequestCallback : nsISup
interface nsINfcBrowserAPI : nsISupports
{
const int32_t SYSTEM_APP_ID = -1;
void setFocusApp(in uint64_t tabId,
in boolean isFocus);
};
-[scriptable, uuid(b5194ae8-d5d5-482f-a73f-dd0d755a1972)]
+[scriptable, uuid(b35f4bf5-e1b8-45f4-b5d3-2ae9b6d5871e)]
interface nsINfcContentHelper : nsISupports
{
void init(in nsIDOMWindow window);
/**
* Read current NDEF data on the tag.
*
* @param sessionToken
@@ -273,9 +273,24 @@ interface nsINfcContentHelper : nsISuppo
*
* @param rfState. Possible values are 'idle', 'listen' and 'discovery'.
*
* @param callback
* Called when request is finished
*/
void changeRFState(in DOMString rfState,
in nsINfcRequestCallback callback);
+
+ /**
+ * Notify parent process to call the default tagfound or peerfound event
+ * handler.
+ *
+ * @param sessionToken
+ * Session token of this event.
+ * @param isP2P
+ * Is this a P2P Session.
+ * @param records
+ * NDEF Records.
+ */
+ void callDefaultFoundHandler(in DOMString sessionToken,
+ in boolean isP2P,
+ in nsIVariant records);
};
--- a/dom/nfc/nsNfc.js
+++ b/dom/nfc/nsNfc.js
@@ -426,28 +426,39 @@ MozNFCImpl.prototype = {
return;
}
let appId = this._window.document.nodePrincipal.appId;
this._nfcContentHelper.unregisterTargetForPeerReady(appId);
},
notifyTagFound: function notifyTagFound(sessionToken, tagInfo, ndefInfo, records) {
+ if (!this.handleTagFound(sessionToken, tagInfo, ndefInfo, records)) {
+ this._nfcContentHelper.callDefaultFoundHandler(sessionToken, false, records);
+ };
+ },
+
+ /**
+ * Handles Tag Found event.
+ *
+ * returns true if the app could process this event, false otherwise.
+ */
+ handleTagFound: function handleTagFound(sessionToken, tagInfo, ndefInfo, records) {
if (this.hasDeadWrapper()) {
dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
- return;
+ return false;
}
if (!this.eventService.hasListenersFor(this.__DOM_IMPL__, "tagfound")) {
debug("ontagfound is not registered.");
- return;
+ return false;
}
if (!this.checkPermissions(["nfc"])) {
- return;
+ return false;
}
this.eventService.addSystemEventListener(this._window, "visibilitychange",
this, /* useCapture */false);
let tagImpl = new MozNFCTagImpl(this._window, sessionToken, tagInfo, ndefInfo);
let tag = this._window.MozNFCTag._create(this._window, tagImpl);
@@ -460,23 +471,33 @@ MozNFCImpl.prototype = {
let record = records[i];
ndefRecords.push(new this._window.MozNDEFRecord({tnf: record.tnf,
type: record.type,
id: record.id,
payload: record.payload}));
}
let eventData = {
+ "cancelable": true,
"tag": tag,
"ndefRecords": ndefRecords
};
debug("fire ontagfound " + sessionToken);
let tagEvent = new this._window.MozNFCTagEvent("tagfound", eventData);
this.__DOM_IMPL__.dispatchEvent(tagEvent);
+
+ // If defaultPrevented is false, means we need to take the default action
+ // for this event - redirect this event to System app. Before redirecting to
+ // System app, we need revoke the tag object first.
+ if (!tagEvent.defaultPrevented) {
+ this.notifyTagLost(sessionToken);
+ }
+
+ return tagEvent.defaultPrevented;
},
notifyTagLost: function notifyTagLost(sessionToken) {
if (this.hasDeadWrapper()) {
dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
return;
}
@@ -499,43 +520,78 @@ MozNFCImpl.prototype = {
this.nfcTag = null;
debug("fire ontaglost " + sessionToken);
let event = new this._window.Event("taglost");
this.__DOM_IMPL__.dispatchEvent(event);
},
notifyPeerFound: function notifyPeerFound(sessionToken, isPeerReady) {
+ if (!this.handlePeerFound(sessionToken, isPeerReady)) {
+ this._nfcContentHelper.callDefaultFoundHandler(sessionToken, true, null);
+ }
+ },
+
+ /**
+ * Handles Peer Found/Peer Ready event.
+ *
+ * returns true if the app could process this event, false otherwise.
+ */
+ handlePeerFound: function handlePeerFound(sessionToken, isPeerReady) {
if (this.hasDeadWrapper()) {
dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
- return;
+ return false;
}
if (!isPeerReady &&
!this.eventService.hasListenersFor(this.__DOM_IMPL__, "peerfound")) {
debug("onpeerfound is not registered.");
- return;
+ return false;
}
let perm = isPeerReady ? ["nfc-share"] : ["nfc"];
if (!this.checkPermissions(perm)) {
- return;
+ return false;
}
this.eventService.addSystemEventListener(this._window, "visibilitychange",
this, /* useCapture */false);
let peerImpl = new MozNFCPeerImpl(this._window, sessionToken);
this.nfcPeer = this._window.MozNFCPeer._create(this._window, peerImpl);
- let eventData = { "peer": this.nfcPeer };
- let type = (isPeerReady) ? "peerready" : "peerfound";
+
+ let eventType;
+ let eventData = {
+ "peer": this.nfcPeer
+ };
+
+ if (isPeerReady) {
+ eventType = "peerready";
+ } else {
+ eventData.cancelable = true;
+ eventType = "peerfound";
+ }
- debug("fire on" + type + " " + sessionToken);
- let event = new this._window.MozNFCPeerEvent(type, eventData);
+ debug("fire on" + eventType + " " + sessionToken);
+ let event = new this._window.MozNFCPeerEvent(eventType, eventData);
this.__DOM_IMPL__.dispatchEvent(event);
+
+ // For peerready we don't take the default action.
+ if (isPeerReady) {
+ return true;
+ }
+
+ // If defaultPrevented is false, means we need to take the default action
+ // for this event - redirect this event to System app. Before redirecting to
+ // System app, we need revoke the peer object first.
+ if (!event.defaultPrevented) {
+ this.notifyPeerLost(sessionToken);
+ }
+
+ return event.defaultPrevented;
},
notifyPeerLost: function notifyPeerLost(sessionToken) {
if (this.hasDeadWrapper()) {
dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
return;
}
--- a/dom/webidl/MozNFC.webidl
+++ b/dom/webidl/MozNFC.webidl
@@ -85,28 +85,43 @@ interface MozNFC : EventTarget {
* This event will be fired when another NFCPeer is detected, and user confirms
* to share data to the NFCPeer object by calling mozNFC.notifyUserAcceptedP2P.
* The event will be type of NFCPeerEvent.
*/
[CheckPermissions="nfc-share", AvailableIn=CertifiedApps]
attribute EventHandler onpeerready;
/**
- * This event will be fired when a NFCPeer is detected.
+ * This event will be fired when a NFCPeer is detected. The application has to
+ * be running on the foreground (decided by System app) to receive this event.
+ *
+ * The default action of this event is to dispatch the event in System app
+ * again, and System app will run the default UX behavior (like vibration).
+ * So if the application would like to cancel the event, the application
+ * should call event.preventDefault() or return false in this event handler.
*/
attribute EventHandler onpeerfound;
/**
* This event will be fired when NFCPeer, earlier detected in onpeerready
* or onpeerfound, moves out of range.
*/
attribute EventHandler onpeerlost;
/**
- * Ths event will be fired when a NFCTag is detected.
+ * This event will be fired when a NFCTag is detected. The application has to
+ * be running on the foreground (decided by System app) to receive this event.
+ *
+ * The default action of this event is to dispatch the event in System app
+ * again, and System app will run the default UX behavior (like vibration) and
+ * launch MozActivity to handle the content of the tag. (For example, System
+ * app will launch Browser if the tag contains URL). So if the application
+ * would like to cancel the event, i.e. in the above example, the application
+ * would process the URL by itself without launching Browser, the application
+ * should call event.preventDefault() or return false in this event handler.
*/
attribute EventHandler ontagfound;
/**
* This event will be fired if the tag detected in ontagfound has been removed.
*/
attribute EventHandler ontaglost;
};