mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-01 05:43:46 +00:00
f544144e4d
--HG-- extra : rebase_source : 4aee7bf49a45f778d70ce7354c94fd454384cb93
487 lines
16 KiB
JavaScript
487 lines
16 KiB
JavaScript
/* Copyright 2012 Mozilla Foundation and Mozilla contributors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/* Copyright © 2013, Deutsche Telekom, Inc. */
|
|
|
|
"use strict";
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
let NFC = {};
|
|
Cu.import("resource://gre/modules/nfc_consts.js", NFC);
|
|
|
|
// set to true in nfc_consts.js to see debug messages
|
|
let DEBUG = NFC.DEBUG_NFC;
|
|
|
|
let debug;
|
|
if (DEBUG) {
|
|
debug = function (s) {
|
|
dump("-*- Nfc: " + s + "\n");
|
|
};
|
|
} else {
|
|
debug = function (s) {};
|
|
}
|
|
|
|
const NFC_CONTRACTID = "@mozilla.org/nfc;1";
|
|
const NFC_CID =
|
|
Components.ID("{2ff24790-5e74-11e1-b86c-0800200c9a66}");
|
|
|
|
const NFC_IPC_MSG_NAMES = [
|
|
"NFC:SetSessionToken",
|
|
"NFC:ReadNDEF",
|
|
"NFC:WriteNDEF",
|
|
"NFC:GetDetailsNDEF",
|
|
"NFC:MakeReadOnlyNDEF",
|
|
"NFC:Connect",
|
|
"NFC:Close"
|
|
];
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
|
"@mozilla.org/parentprocessmessagemanager;1",
|
|
"nsIMessageBroadcaster");
|
|
XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
|
|
"@mozilla.org/system-message-internal;1",
|
|
"nsISystemMessagesInternal");
|
|
XPCOMUtils.defineLazyServiceGetter(this, "gSystemWorkerManager",
|
|
"@mozilla.org/telephony/system-worker-manager;1",
|
|
"nsISystemWorkerManager");
|
|
XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
|
|
"@mozilla.org/settingsService;1",
|
|
"nsISettingsService");
|
|
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
|
|
"@mozilla.org/uuid-generator;1",
|
|
"nsIUUIDGenerator");
|
|
XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
|
|
return {
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
|
|
Ci.nsIObserver]),
|
|
|
|
nfc: null,
|
|
|
|
// Manage message targets in terms of sessionToken. Only the authorized and
|
|
// registered contents can receive related messages.
|
|
targetsBySessionTokens: {},
|
|
sessionTokens: [],
|
|
|
|
init: function init(nfc) {
|
|
this.nfc = nfc;
|
|
|
|
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
|
this._registerMessageListeners();
|
|
},
|
|
|
|
_shutdown: function _shutdown() {
|
|
this.nfc = null;
|
|
|
|
Services.obs.removeObserver(this, "xpcom-shutdown");
|
|
this._unregisterMessageListeners();
|
|
},
|
|
|
|
_registerMessageListeners: function _registerMessageListeners() {
|
|
ppmm.addMessageListener("child-process-shutdown", this);
|
|
for (let msgname of NFC_IPC_MSG_NAMES) {
|
|
ppmm.addMessageListener(msgname, this);
|
|
}
|
|
},
|
|
|
|
_unregisterMessageListeners: function _unregisterMessageListeners() {
|
|
ppmm.removeMessageListener("child-process-shutdown", this);
|
|
for (let msgname of NFC_IPC_MSG_NAMES) {
|
|
ppmm.removeMessageListener(msgname, this);
|
|
}
|
|
ppmm = null;
|
|
},
|
|
|
|
_registerMessageTarget: function _registerMessageTarget(sessionToken, target) {
|
|
let targets = this.targetsBySessionTokens[sessionToken];
|
|
if (!targets) {
|
|
targets = this.targetsBySessionTokens[sessionToken] = [];
|
|
let list = this.sessionTokens;
|
|
if (list.indexOf(sessionToken) == -1) {
|
|
list.push(sessionToken);
|
|
}
|
|
}
|
|
|
|
if (targets.indexOf(target) != -1) {
|
|
debug("Already registered this target!");
|
|
return;
|
|
}
|
|
|
|
targets.push(target);
|
|
debug("Registered :" + sessionToken + " target: " + target);
|
|
},
|
|
|
|
_unregisterMessageTarget: function _unregisterMessageTarget(sessionToken, target) {
|
|
if (sessionToken == null) {
|
|
// Unregister the target for every sessionToken when no sessionToken is specified.
|
|
for (let session of this.sessionTokens) {
|
|
this._unregisterMessageTarget(session, target);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Unregister the target for a specified sessionToken.
|
|
let targets = this.targetsBySessionTokens[sessionToken];
|
|
if (!targets) {
|
|
return;
|
|
}
|
|
|
|
if (target == null) {
|
|
debug("Unregistered all targets for the " + sessionToken + " targets: " + targets);
|
|
targets = [];
|
|
let list = this.sessionTokens;
|
|
if (sessionToken !== null) {
|
|
let index = list.indexOf(sessionToken);
|
|
if (index > -1) {
|
|
list.splice(index, 1);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
let index = targets.indexOf(target);
|
|
if (index != -1) {
|
|
targets.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
_sendTargetMessage: function _sendTargetMessage(sessionToken, message, options) {
|
|
let targets = this.targetsBySessionTokens[sessionToken];
|
|
if (!targets) {
|
|
return;
|
|
}
|
|
|
|
for (let target of targets) {
|
|
target.sendAsyncMessage(message, options);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* nsIMessageListener interface methods.
|
|
*/
|
|
|
|
receiveMessage: function receiveMessage(msg) {
|
|
debug("Received '" + msg.name + "' message from content process");
|
|
if (msg.name == "child-process-shutdown") {
|
|
// By the time we receive child-process-shutdown, the child process has
|
|
// already forgotten its permissions so we need to unregister the target
|
|
// for every permission.
|
|
this._unregisterMessageTarget(null, msg.target);
|
|
return null;
|
|
}
|
|
|
|
if (NFC_IPC_MSG_NAMES.indexOf(msg.name) != -1) {
|
|
if (!msg.target.assertPermission("nfc-read")) {
|
|
debug("Nfc message " + msg.name +
|
|
" from a content process with no 'nfc-read' privileges.");
|
|
return null;
|
|
}
|
|
} else {
|
|
debug("Ignoring unknown message type: " + msg.name);
|
|
return null;
|
|
}
|
|
|
|
switch (msg.name) {
|
|
case "NFC:SetSessionToken":
|
|
this._registerMessageTarget(this.nfc.sessionTokenMap[this.nfc._currentSessionId], msg.target);
|
|
debug("Registering target for this SessionToken : " +
|
|
this.nfc.sessionTokenMap[this.nfc._currentSessionId]);
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* nsIObserver interface methods.
|
|
*/
|
|
|
|
observe: function observe(subject, topic, data) {
|
|
switch (topic) {
|
|
case "xpcom-shutdown":
|
|
this._shutdown();
|
|
break;
|
|
}
|
|
},
|
|
|
|
sendNfcResponseMessage: function sendNfcResponseMessage(message, data) {
|
|
this._sendTargetMessage(this.nfc.sessionTokenMap[this.nfc._currentSessionId], message, data);
|
|
},
|
|
};
|
|
});
|
|
|
|
function Nfc() {
|
|
debug("Starting Worker");
|
|
this.worker = new ChromeWorker("resource://gre/modules/nfc_worker.js");
|
|
this.worker.onerror = this.onerror.bind(this);
|
|
this.worker.onmessage = this.onmessage.bind(this);
|
|
|
|
for each (let msgname in NFC_IPC_MSG_NAMES) {
|
|
ppmm.addMessageListener(msgname, this);
|
|
}
|
|
|
|
Services.obs.addObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED, false);
|
|
Services.obs.addObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN, false);
|
|
|
|
gMessageManager.init(this);
|
|
let lock = gSettingsService.createLock();
|
|
lock.get(NFC.SETTING_NFC_POWER_LEVEL, this);
|
|
lock.get(NFC.SETTING_NFC_ENABLED, this);
|
|
// Maps sessionId (that are generated from nfcd) with a unique guid : 'SessionToken'
|
|
this.sessionTokenMap = {};
|
|
|
|
gSystemWorkerManager.registerNfcWorker(this.worker);
|
|
}
|
|
|
|
Nfc.prototype = {
|
|
|
|
classID: NFC_CID,
|
|
classInfo: XPCOMUtils.generateCI({classID: NFC_CID,
|
|
classDescription: "Nfc",
|
|
interfaces: [Ci.nsIWorkerHolder]}),
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
|
|
Ci.nsIObserver,
|
|
Ci.nsISettingsServiceCallback]),
|
|
|
|
_currentSessionId: null,
|
|
_enabled: false,
|
|
|
|
onerror: function onerror(event) {
|
|
debug("Got an error: " + event.filename + ":" +
|
|
event.lineno + ": " + event.message + "\n");
|
|
event.preventDefault();
|
|
},
|
|
|
|
/**
|
|
* Send arbitrary message to worker.
|
|
*
|
|
* @param nfcMessageType
|
|
* A text message type.
|
|
* @param message [optional]
|
|
* An optional message object to send.
|
|
*/
|
|
sendToWorker: function sendToWorker(nfcMessageType, message) {
|
|
message = message || {};
|
|
message.type = nfcMessageType;
|
|
this.worker.postMessage(message);
|
|
},
|
|
|
|
/**
|
|
* Send Error response to content.
|
|
*
|
|
* @param message
|
|
* An nsIMessageListener's message parameter.
|
|
*/
|
|
sendNfcErrorResponse: function sendNfcErrorResponse(message) {
|
|
if (!message.target) {
|
|
return;
|
|
}
|
|
|
|
let nfcMsgType = message.name + "Response";
|
|
message.target.sendAsyncMessage(nfcMsgType, {
|
|
sessionId: message.json.sessionToken,
|
|
requestId: message.json.requestId,
|
|
status: NFC.GECKO_NFC_ERROR_GENERIC_FAILURE
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Process the incoming message from the NFC worker
|
|
*/
|
|
onmessage: function onmessage(event) {
|
|
let message = event.data;
|
|
debug("Received message from NFC worker: " + JSON.stringify(message));
|
|
|
|
switch (message.type) {
|
|
case "techDiscovered":
|
|
this._currentSessionId = message.sessionId;
|
|
// Check if the session token already exists. If exists, continue to use the same one.
|
|
// If not, generate a new token.
|
|
if (!this.sessionTokenMap[this._currentSessionId]) {
|
|
this.sessionTokenMap[this._currentSessionId] = UUIDGenerator.generateUUID().toString();
|
|
}
|
|
// Update the upper layers with a session token (alias)
|
|
message.sessionToken = this.sessionTokenMap[this._currentSessionId];
|
|
// Do not expose the actual session to the content
|
|
delete message.sessionId;
|
|
gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
|
|
break;
|
|
case "techLost":
|
|
gMessageManager._unregisterMessageTarget(this.sessionTokenMap[this._currentSessionId], null);
|
|
// Update the upper layers with a session token (alias)
|
|
message.sessionToken = this.sessionTokenMap[this._currentSessionId];
|
|
// Do not expose the actual session to the content
|
|
delete message.sessionId;
|
|
gSystemMessenger.broadcastMessage("nfc-manager-tech-lost", message);
|
|
delete this.sessionTokenMap[this._currentSessionId];
|
|
this._currentSessionId = null;
|
|
break;
|
|
case "ConfigResponse":
|
|
gSystemMessenger.broadcastMessage("nfc-powerlevel-change", message);
|
|
break;
|
|
case "ConnectResponse": // Fall through.
|
|
case "CloseResponse":
|
|
case "GetDetailsNDEFResponse":
|
|
case "ReadNDEFResponse":
|
|
case "MakeReadOnlyNDEFResponse":
|
|
case "WriteNDEFResponse":
|
|
message.sessionToken = this.sessionTokenMap[this._currentSessionId];
|
|
// Do not expose the actual session to the content
|
|
delete message.sessionId;
|
|
gMessageManager.sendNfcResponseMessage("NFC:" + message.type, message);
|
|
break;
|
|
default:
|
|
throw new Error("Don't know about this message type: " + message.type);
|
|
}
|
|
},
|
|
|
|
// nsINfcWorker
|
|
worker: null,
|
|
|
|
powerLevel: NFC.NFC_POWER_LEVEL_DISABLED,
|
|
|
|
sessionTokenMap: null,
|
|
|
|
/**
|
|
* Process a message from the content process.
|
|
*/
|
|
receiveMessage: function receiveMessage(message) {
|
|
debug("Received '" + JSON.stringify(message) + "' message from content process");
|
|
|
|
if (!this._enabled) {
|
|
debug("NFC is not enabled.");
|
|
this.sendNfcErrorResponse(message);
|
|
return null;
|
|
}
|
|
|
|
// Enforce bare minimums for NFC permissions
|
|
switch (message.name) {
|
|
case "NFC:Connect": // Fall through
|
|
case "NFC:Close":
|
|
case "NFC:GetDetailsNDEF":
|
|
case "NFC:ReadNDEF":
|
|
if (!message.target.assertPermission("nfc-read")) {
|
|
debug("NFC message " + message.name +
|
|
" from a content process with no 'nfc-read' privileges.");
|
|
this.sendNfcErrorResponse(message);
|
|
return null;
|
|
}
|
|
break;
|
|
case "NFC:WriteNDEF": // Fall through
|
|
case "NFC:MakeReadOnlyNDEF":
|
|
if (!message.target.assertPermission("nfc-write")) {
|
|
debug("NFC message " + message.name +
|
|
" from a content process with no 'nfc-write' privileges.");
|
|
this.sendNfcErrorResponse(message);
|
|
return null;
|
|
}
|
|
break;
|
|
case "NFC:SetSessionToken":
|
|
//Do nothing here. No need to process this message further
|
|
return null;
|
|
}
|
|
|
|
// Sanity check on sessionId
|
|
if (message.json.sessionToken !== this.sessionTokenMap[this._currentSessionId]) {
|
|
debug("Invalid Session Token: " + message.json.sessionToken +
|
|
" Expected Session Token: " + this.sessionTokenMap[this._currentSessionId]);
|
|
this.sendNfcErrorResponse(message);
|
|
return null;
|
|
}
|
|
|
|
// Update the current sessionId before sending to the worker
|
|
message.sessionId = this._currentSessionId;
|
|
|
|
switch (message.name) {
|
|
case "NFC:GetDetailsNDEF":
|
|
this.sendToWorker("getDetailsNDEF", message.json);
|
|
break;
|
|
case "NFC:ReadNDEF":
|
|
this.sendToWorker("readNDEF", message.json);
|
|
break;
|
|
case "NFC:WriteNDEF":
|
|
this.sendToWorker("writeNDEF", message.json);
|
|
break;
|
|
case "NFC:MakeReadOnlyNDEF":
|
|
this.sendToWorker("makeReadOnlyNDEF", message.json);
|
|
break;
|
|
case "NFC:Connect":
|
|
this.sendToWorker("connect", message.json);
|
|
break;
|
|
case "NFC:Close":
|
|
this.sendToWorker("close", message.json);
|
|
break;
|
|
default:
|
|
debug("UnSupported : Message Name " + message.name);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* nsISettingsServiceCallback
|
|
*/
|
|
|
|
handle: function handle(aName, aResult) {
|
|
switch(aName) {
|
|
case NFC.SETTING_NFC_ENABLED:
|
|
debug("'nfc.enabled' is now " + aResult);
|
|
this._enabled = aResult;
|
|
// General power setting
|
|
let powerLevel = this._enabled ? NFC.NFC_POWER_LEVEL_ENABLED :
|
|
NFC.NFC_POWER_LEVEL_DISABLED;
|
|
// Only if the value changes, set the power config and persist
|
|
if (powerLevel !== this.powerLevel) {
|
|
debug("New Power Level " + powerLevel);
|
|
this.setConfig({powerLevel: powerLevel});
|
|
this.powerLevel = powerLevel;
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* nsIObserver
|
|
*/
|
|
|
|
observe: function observe(subject, topic, data) {
|
|
switch (topic) {
|
|
case NFC.TOPIC_XPCOM_SHUTDOWN:
|
|
for each (let msgname in NFC_IPC_MSG_NAMES) {
|
|
ppmm.removeMessageListener(msgname, this);
|
|
}
|
|
ppmm = null;
|
|
Services.obs.removeObserver(this, NFC.TOPIC_XPCOM_SHUTDOWN);
|
|
break;
|
|
case NFC.TOPIC_MOZSETTINGS_CHANGED:
|
|
let setting = JSON.parse(data);
|
|
if (setting) {
|
|
let setting = JSON.parse(data);
|
|
this.handle(setting.key, setting.value);
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
setConfig: function setConfig(prop) {
|
|
this.sendToWorker("config", prop);
|
|
}
|
|
};
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Nfc]);
|