From 9f90a20a2185c0340253354ba0e85505d353ccc2 Mon Sep 17 00:00:00 2001 From: Yoshi Huang Date: Thu, 11 Dec 2014 17:42:35 +0800 Subject: [PATCH] Bug 1082443 - B2G NFC: event fallback to System app if the foreground app cannot handle it. r=smaug, dimi --- dom/nfc/NfcContentHelper.js | 14 ++++++ dom/nfc/gonk/Nfc.js | 18 +++++--- dom/nfc/nsINfcContentHelper.idl | 17 +++++++- dom/nfc/nsNfc.js | 76 ++++++++++++++++++++++++++++----- dom/webidl/MozNFC.webidl | 19 ++++++++- 5 files changed, 125 insertions(+), 19 deletions(-) diff --git a/dom/nfc/NfcContentHelper.js b/dom/nfc/NfcContentHelper.js index 976ea44d29e3..269cdabaf6f8 100644 --- a/dom/nfc/NfcContentHelper.js +++ b/dom/nfc/NfcContentHelper.js @@ -147,6 +147,10 @@ NfcContentHelper.prototype = { }, encodeNDEFRecords: function encodeNDEFRecords(records) { + if (!Array.isArray(records)) { + return null; + } + let encodedRecords = []; for (let i = 0; i < records.length; i++) { let record = records[i]; @@ -278,6 +282,16 @@ NfcContentHelper.prototype = { 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") { diff --git a/dom/nfc/gonk/Nfc.js b/dom/nfc/gonk/Nfc.js index fa95b262b9a9..27f6f87eda4a 100644 --- a/dom/nfc/gonk/Nfc.js +++ b/dom/nfc/gonk/Nfc.js @@ -57,7 +57,8 @@ const NFC_CID = const NFC_IPC_MSG_ENTRIES = [ { permission: null, messages: ["NFC:AddEventListener", - "NFC:QueryInfo"] }, + "NFC:QueryInfo", + "NFC:CallDefaultFoundHandler"] }, { permission: "nfc", messages: ["NFC:ReadNDEF", @@ -227,6 +228,13 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () { 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]; @@ -317,6 +325,9 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () { } this.nfc.sendNfcResponse(message.data); return null; + case "NFC:CallDefaultFoundHandler": + this.callDefaultFoundHandler(message.data); + return null; default: return this.nfc.receiveMessage(message); } @@ -502,11 +513,6 @@ Nfc.prototype = { } 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"; diff --git a/dom/nfc/nsINfcContentHelper.idl b/dom/nfc/nsINfcContentHelper.idl index c1ce95fb26a0..cd42489c5ef8 100644 --- a/dom/nfc/nsINfcContentHelper.idl +++ b/dom/nfc/nsINfcContentHelper.idl @@ -111,7 +111,7 @@ interface nsINfcBrowserAPI : nsISupports 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); @@ -278,4 +278,19 @@ interface nsINfcContentHelper : nsISupports */ 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); }; diff --git a/dom/nfc/nsNfc.js b/dom/nfc/nsNfc.js index f2aec7b2a476..049f49f67e68 100644 --- a/dom/nfc/nsNfc.js +++ b/dom/nfc/nsNfc.js @@ -431,18 +431,29 @@ MozNFCImpl.prototype = { }, 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", @@ -465,6 +476,7 @@ MozNFCImpl.prototype = { } let eventData = { + "cancelable": true, "tag": tag, "ndefRecords": ndefRecords }; @@ -472,6 +484,15 @@ MozNFCImpl.prototype = { 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) { @@ -504,20 +525,31 @@ MozNFCImpl.prototype = { }, 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", @@ -525,12 +557,36 @@ MozNFCImpl.prototype = { 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"; - debug("fire on" + type + " " + sessionToken); - let event = new this._window.MozNFCPeerEvent(type, eventData); + let eventType; + let eventData = { + "peer": this.nfcPeer + }; + + if (isPeerReady) { + eventType = "peerready"; + } else { + eventData.cancelable = true; + eventType = "peerfound"; + } + + 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) { diff --git a/dom/webidl/MozNFC.webidl b/dom/webidl/MozNFC.webidl index 1b4ed3e712ed..c34cb5cc4e5d 100644 --- a/dom/webidl/MozNFC.webidl +++ b/dom/webidl/MozNFC.webidl @@ -90,7 +90,13 @@ interface MozNFC : EventTarget { 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; @@ -101,7 +107,16 @@ interface MozNFC : EventTarget { 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;