Merge m-c to inbound. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-10-29 16:51:39 -04:00
commit bcc3ab5ec1
161 changed files with 7289 additions and 1079 deletions

View File

@ -1042,3 +1042,6 @@ pref("dom.mapped_arraybuffer.enabled", true);
// UDPSocket API
pref("dom.udpsocket.enabled", true);
// Enable TV Manager API
pref("dom.tv.enabled", true);

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>

View File

@ -1,9 +1,8 @@
[
{
"size": 120750384,
"digest": "0e0a0b0dcca020e3283ce8deb33d0eed48fab16ef2fd919120bd7b5abba00713210be17f466d11bf77cca3c9e3b663805be61774476cc669f0a75736d901edfd",
"size": 91247216,
"digest": "2b4be549f98695488ea7288d9e7f8ac0fa45112bedefa485a6e016c4af73fa21bb6b3992beda516f268417207c5deb57afad3959d3b1fbd07d5269b3a6be6a27",
"algorithm": "sha512",
"filename": "backup-flame.tar.xz",
"comment": "v188-1"
"filename": "backup-flame.tar.xz"
}
]

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "ab5fb977047100ac985402e022d24da1dea68f24",
"revision": "4830af61b34d751c57da15abfc2efcda571b12c0",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="28be739bcdcbc9eb91c0bdbff1f7d3eab717969b"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a9a847920b51b79c4ad4ad339f0a005777a6228c"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="0dfc1996eb583c8b507a82bf6b8319624bba23ea"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

View File

@ -197,6 +197,7 @@
@BINPATH@/components/dom_telephony.xpt
@BINPATH@/components/dom_threads.xpt
@BINPATH@/components/dom_traversal.xpt
@BINPATH@/components/dom_tv.xpt
@BINPATH@/components/dom_views.xpt
@BINPATH@/components/dom_voicemail.xpt
#ifdef MOZ_WEBSPEECH

View File

@ -1764,8 +1764,10 @@ pref("media.gmp-gmpopenh264.provider.enabled", true);
pref("browser.apps.URL", "https://marketplace.firefox.com/discovery/");
#ifdef NIGHTLY_BUILD
pref("browser.polaris.enabled", false);
pref("privacy.trackingprotection.ui.enabled", false);
#endif
// Temporary pref to allow printing in e10s windows on some platforms.
#ifdef UNIX_BUT_NOT_MAC

View File

@ -250,6 +250,8 @@ let LoopCallsInternal = {
respData.calls.forEach((callData) => {
if (!this.callsData.inUse) {
callData.sessionType = sessionType;
// XXX Bug 1090209 will transiton into a better window id.
callData.windowId = callData.callId;
this._startCall(callData, "incoming");
} else {
this._returnBusy(callData);
@ -277,7 +279,7 @@ let LoopCallsInternal = {
null,
// No title, let the page set that, to avoid flickering.
"",
"about:loopconversation#" + conversationType + "/" + callData.callId);
"about:loopconversation#" + conversationType + "/" + callData.windowId);
},
/**
@ -295,7 +297,7 @@ let LoopCallsInternal = {
contact: contact,
callType: callType,
// XXX Really we shouldn't be using random numbers, bug 1090209 will fix this.
callId: Math.floor((Math.random() * 100000000))
windowId: Math.floor((Math.random() * 100000000))
};
this._startCall(callData, "outgoing");
@ -339,17 +341,17 @@ this.LoopCalls = {
},
/**
* Returns the callData for a specific loopCallId
* Returns the callData for a specific conversation window id.
*
* The data was retrieved from the LoopServer via a GET/calls/<version> request
* triggered by an incoming message from the LoopPushServer.
*
* @param {int} loopCallId
* @param {Number} conversationWindowId
* @return {callData} The callData or undefined if error.
*/
getCallData: function(loopCallId) {
getCallData: function(conversationWindowId) {
if (LoopCallsInternal.callsData.data &&
LoopCallsInternal.callsData.data.callId == loopCallId) {
LoopCallsInternal.callsData.data.windowId == conversationWindowId) {
return LoopCallsInternal.callsData.data;
} else {
return undefined;
@ -357,15 +359,15 @@ this.LoopCalls = {
},
/**
* Releases the callData for a specific loopCallId
* Releases the callData for a specific conversation window id.
*
* The result of this call will be a free call session slot.
*
* @param {int} loopCallId
* @param {Number} conversationWindowId
*/
releaseCallData: function(loopCallId) {
releaseCallData: function(conversationWindowId) {
if (LoopCallsInternal.callsData.data &&
LoopCallsInternal.callsData.data.callId == loopCallId) {
LoopCallsInternal.callsData.data.windowId == conversationWindowId) {
LoopCallsInternal.callsData.data = undefined;
LoopCallsInternal.callsData.inUse = false;
}

View File

@ -5,339 +5,222 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService",
"resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LOOP_SESSION_TYPE",
"resource:///modules/loop/MozLoopService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
"resource:///modules/loop/MozLoopPushHandler.jsm");
const {MozLoopService, LOOP_SESSION_TYPE} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
return new EventEmitter();
});
this.EXPORTED_SYMBOLS = ["LoopRooms", "roomsPushNotification"];
let gRoomsListFetched = false;
let gRooms = new Map();
let gCallbacks = new Map();
/**
* Callback used to indicate changes to rooms data on the LoopServer.
*
* @param {Object} version Version number assigned to this change set.
* @param {Object} channelID Notification channel identifier.
*
*/
const roomsPushNotification = function(version, channelID) {
return LoopRoomsInternal.onNotification(version, channelID);
};
let LoopRoomsInternal = {
getAll: function(callback) {
Task.spawn(function*() {
yield MozLoopService.register();
if (gRoomsListFetched) {
callback(null, [...gRooms.values()]);
return;
}
// Fetch the rooms from the server.
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
let rooms = yield this.requestRoomList(sessionType);
// Add each room to our in-memory Map using a locally unique
// identifier.
for (let room of rooms) {
let id = MozLoopService.generateLocalID();
room.localRoomId = id;
// Next, request the detailed information for each room.
// If the request fails the room data will not be added to the map.
try {
let details = yield this.requestRoomDetails(room.roomToken, sessionType);
for (let attr in details) {
room[attr] = details[attr]
}
delete room.currSize; //This attribute will be eliminated in the next revision.
gRooms.set(id, room);
}
catch (error) {MozLoopService.log.warn(
"failed GETing room details for roomToken = " + room.roomToken + ": ", error)}
}
callback(null, [...gRooms.values()]);
return;
}.bind(this)).catch((error) => {MozLoopService.log.error("getAll error:", error);
callback(error)});
return;
},
getRoomData: function(localRoomId, callback) {
if (gRooms.has(localRoomId)) {
callback(null, gRooms.get(localRoomId));
} else {
callback(new Error("Room data not found or not fetched yet for room with ID " + localRoomId));
}
return;
},
/**
* Request list of all rooms associated with this account.
*
* @param {String} sessionType Indicates which hawkRequest endpoint to use.
*
* @returns {Promise} room list
*/
requestRoomList: function(sessionType) {
return MozLoopService.hawkRequest(sessionType, "/rooms", "GET")
.then(response => {
let roomsList = JSON.parse(response.body);
if (!Array.isArray(roomsList)) {
// Force a reject in the returned promise.
// To be caught by the caller using the returned Promise.
throw new Error("Missing array of rooms in response.");
}
return roomsList;
});
},
/**
* Request information about a specific room from the server.
*
* @param {Object} token Room identifier returned from the LoopServer.
* @param {String} sessionType Indicates which hawkRequest endpoint to use.
*
* @returns {Promise} room details
*/
requestRoomDetails: function(token, sessionType) {
return MozLoopService.hawkRequest(sessionType, "/rooms/" + token, "GET")
.then(response => JSON.parse(response.body));
},
/**
* Callback used to indicate changes to rooms data on the LoopServer.
*
* @param {Object} version Version number assigned to this change set.
* @param {Object} channelID Notification channel identifier.
*
*/
onNotification: function(version, channelID) {
return;
},
createRoom: function(props, callback) {
// Always create a basic room record and launch the window, attaching
// the localRoomId. Later errors will be returned via the registered callback.
let localRoomId = MozLoopService.generateLocalID((id) => {gRooms.has(id)})
let room = {localRoomId : localRoomId};
for (let prop in props) {
room[prop] = props[prop]
}
gRooms.set(localRoomId, room);
this.addCallback(localRoomId, "RoomCreated", callback);
MozLoopService.openChatWindow(null, "", "about:loopconversation#room/" + localRoomId);
if (!"roomName" in props ||
!"expiresIn" in props ||
!"roomOwner" in props ||
!"maxSize" in props) {
this.postCallback(localRoomId, "RoomCreated",
new Error("missing required room create property"));
return localRoomId;
}
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
MozLoopService.hawkRequest(sessionType, "/rooms", "POST", props).then(
(response) => {
let data = JSON.parse(response.body);
for (let attr in data) {
room[attr] = data[attr]
}
delete room.expiresIn; //Do not keep this value - it is a request to the server
this.postCallback(localRoomId, "RoomCreated", null, room);
},
(error) => {
this.postCallback(localRoomId, "RoomCreated", error);
});
return localRoomId;
},
/**
* Send an update to the callbacks registered for a specific localRoomId
* for a callback type.
*
* The result set is always saved. Then each
* callback function that has been registered when this function is
* called will be called with the result set. Any new callback that
* is regsitered via addCallback will receive a copy of the last
* saved result set when registered. This allows the posting operation
* to complete before the callback is registered in an asynchronous
* operation.
*
* Callbacsk must be of the form:
* function (error, success) {...}
*
* @param {String} localRoomId Local room identifier.
* @param {String} callbackName callback type
* @param {?Error} error result or null.
* @param {?Object} success result if error argument is null.
*/
postCallback: function(localRoomId, callbackName, error, success) {
let roomCallbacks = gCallbacks.get(localRoomId);
if (!roomCallbacks) {
// No callbacks have been registered or results posted for this room.
// Initialize a record for this room and callbackName, saving the
// result set.
gCallbacks.set(localRoomId, new Map([[
callbackName,
{ callbackList: [], result: { error: error, success: success } }]]));
return;
}
let namedCallback = roomCallbacks.get(callbackName);
// A callback of this name has not been registered.
if (!namedCallback) {
roomCallbacks.set(
callbackName,
{callbackList: [], result: {error: error, success: success}});
return;
}
// Record the latest result set.
namedCallback.result = {error: error, success: success};
// Call each registerd callback passing the new result posted.
namedCallback.callbackList.forEach((callback) => {
callback(error, success);
});
},
addCallback: function(localRoomId, callbackName, callback) {
let roomCallbacks = gCallbacks.get(localRoomId);
if (!roomCallbacks) {
// No callbacks have been registered or results posted for this room.
// Initialize a record for this room and callbackName.
gCallbacks.set(localRoomId, new Map([[
callbackName,
{callbackList: [callback]}]]));
return;
}
let namedCallback = roomCallbacks.get(callbackName);
// A callback of this name has not been registered.
if (!namedCallback) {
roomCallbacks.set(
callbackName,
{callbackList: [callback]});
return;
}
// Add this callback if not already in the array
if (namedCallback.callbackList.indexOf(callback) >= 0) {
return;
}
namedCallback.callbackList.push(callback);
// If a result has been posted for this callback
// send it using this new callback function.
let result = namedCallback.result;
if (result) {
callback(result.error, result.success);
}
},
deleteCallback: function(localRoomId, callbackName, callback) {
let roomCallbacks = gCallbacks.get(localRoomId);
if (!roomCallbacks) {
return;
}
let namedCallback = roomCallbacks.get(callbackName);
if (!namedCallback) {
return;
}
let i = namedCallback.callbackList.indexOf(callback);
if (i >= 0) {
namedCallback.callbackList.splice(i, 1);
}
return;
},
return LoopRoomsInternal.onNotification(version, channelID);
};
Object.freeze(LoopRoomsInternal);
// Since the LoopRoomsInternal.rooms map as defined below is a local cache of
// room objects that are retrieved from the server, this is list may become out
// of date. The Push server may notify us of this event, which will set the global
// 'dirty' flag to TRUE.
let gDirty = true;
/**
* The LoopRooms class.
* Extend a `target` object with the properties defined in `source`.
*
* @param {Object} target The target object to receive properties defined in `source`
* @param {Object} source The source object to copy properties from
*/
const extend = function(target, source) {
for (let key of Object.getOwnPropertyNames(source)) {
target[key] = source[key];
}
return target;
};
/**
* The Rooms class.
*
* Each method that is a member of this class requires the last argument to be a
* callback Function. MozLoopAPI will cause things to break if this invariant is
* violated. You'll notice this as well in the documentation for each method.
*/
this.LoopRooms = {
let LoopRoomsInternal = {
rooms: new Map(),
/**
* Fetch a list of rooms that the currently registered user is a member of.
*
* @param {String} [version] If set, we will fetch a list of changed rooms since
* `version`. Optional.
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`. The second argument will
* be the list of rooms, if it was fetched successfully.
*/
getAll: function(version = null, callback) {
if (!callback) {
callback = version;
version = null;
}
Task.spawn(function* () {
yield MozLoopService.register();
if (!gDirty) {
callback(null, [...this.rooms.values()]);
return;
}
// Fetch the rooms from the server.
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
let url = "/rooms" + (version ? "?version=" + encodeURIComponent(version) : "");
let response = yield MozLoopService.hawkRequest(sessionType, url, "GET");
let roomsList = JSON.parse(response.body);
if (!Array.isArray(roomsList)) {
throw new Error("Missing array of rooms in response.");
}
// Next, request the detailed information for each room. If the request
// fails the room data will not be added to the map.
for (let room of roomsList) {
this.rooms.set(room.roomToken, room);
yield LoopRooms.promise("get", room.roomToken);
}
// Set the 'dirty' flag back to FALSE, since the list is as fresh as can be now.
gDirty = false;
callback(null, [...this.rooms.values()]);
}.bind(this)).catch(error => {
callback(error);
});
},
/**
* Request information about a specific room from the server. It will be
* returned from the cache if it's already in it.
*
* @param {String} roomToken Room identifier
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`. The second argument will
* be the list of rooms, if it was fetched successfully.
*/
get: function(roomToken, callback) {
let room = this.rooms.has(roomToken) ? this.rooms.get(roomToken) : {};
// Check if we need to make a request to the server to collect more room data.
if (!room || gDirty || !("participants" in room)) {
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
MozLoopService.hawkRequest(sessionType, "/rooms/" + encodeURIComponent(roomToken), "GET")
.then(response => {
let eventName = ("roomToken" in room) ? "add" : "update";
extend(room, JSON.parse(response.body));
// Remove the `currSize` for posterity.
if ("currSize" in room) {
delete room.currSize;
}
this.rooms.set(roomToken, room);
eventEmitter.emit(eventName, room);
callback(null, room);
}, err => callback(err)).catch(err => callback(err));
} else {
callback(null, room);
}
},
/**
* Create a room.
*
* @param {Object} room Properties to be sent to the LoopServer
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`. The second argument will
* be the list of rooms, if it was fetched successfully.
* be the room, if it was created successfully.
*/
getAll: function(callback) {
return LoopRoomsInternal.getAll(callback);
create: function(room, callback) {
if (!("roomName" in room) || !("expiresIn" in room) ||
!("roomOwner" in room) || !("maxSize" in room)) {
callback(new Error("Missing required property to create a room"));
return;
}
let sessionType = MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
MozLoopService.hawkRequest(sessionType, "/rooms", "POST", room)
.then(response => {
let data = JSON.parse(response.body);
extend(room, data);
// Do not keep this value - it is a request to the server.
delete room.expiresIn;
this.rooms.set(room.roomToken, room);
eventEmitter.emit("add", room);
callback(null, room);
}, error => callback(error)).catch(error => callback(error));
},
/**
* Return the current stored version of the data for the indicated room.
* Callback used to indicate changes to rooms data on the LoopServer.
*
* @param {String} localRoomId Local room identifier
* @param {Function} callback Function that will be invoked once the operation
* finished. The first argument passed will be an
* `Error` object or `null`. The second argument will
* be the list of rooms, if it was fetched successfully.
* @param {String} version Version number assigned to this change set.
* @param {String} channelID Notification channel identifier.
*/
getRoomData: function(localRoomId, callback) {
return LoopRoomsInternal.getRoomData(localRoomId, callback);
},
/**
* Create a room. Will both open a chat window for the new room
* and perform an exchange with the LoopServer to create the room.
* for a callback type. Callback must be of the form:
* function (error, success) {...}
*
* @param {Object} room properties to be sent to the LoopServer
* @param {Function} callback Must be of the form: function (error, success) {...}
*
* @returns {String} localRoomId assigned to this new room.
*/
createRoom: function(roomProps, callback) {
return LoopRoomsInternal.createRoom(roomProps, callback);
},
/**
* Register a callback of a specified type with a localRoomId.
*
* @param {String} localRoomId Local room identifier.
* @param {String} callbackName callback type
* @param {Function} callback Must be of the form: function (error, success) {...}
*/
addCallback: function(localRoomId, callbackName, callback) {
return LoopRoomsInternal.addCallback(localRoomId, callbackName, callback);
},
/**
* Un-register and delete a callback of a specified type for a localRoomId.
*
* @param {String} localRoomId Local room identifier.
* @param {String} callbackName callback type
* @param {Function} callback Previously passed to addCallback().
*/
deleteCallback: function(localRoomId, callbackName, callback) {
return LoopRoomsInternal.deleteCallback(localRoomId, callbackName, callback);
onNotification: function(version, channelID) {
gDirty = true;
this.getAll(version, () => {});
},
};
Object.freeze(LoopRooms);
Object.freeze(LoopRoomsInternal);
/**
* Public Loop Rooms API.
*
* LoopRooms implements the EventEmitter interface by exposing three methods -
* `on`, `once` and `off` - to subscribe to events.
* At this point the following events may be subscribed to:
* - 'add': A new room object was successfully added to the data store.
* - 'remove': A room was successfully removed from the data store.
* - 'update': A room object was successfully updated with changed
* properties in the data store.
*
* See the internal code for the API documentation.
*/
this.LoopRooms = {
getAll: function(version, callback) {
return LoopRoomsInternal.getAll(version, callback);
},
get: function(roomToken, callback) {
return LoopRoomsInternal.get(roomToken, callback);
},
create: function(options, callback) {
return LoopRoomsInternal.create(options, callback);
},
promise: function(method, ...params) {
return new Promise((resolve, reject) => {
this[method](...params, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
},
on: (...params) => eventEmitter.on(...params),
once: (...params) => eventEmitter.once(...params),
off: (...params) => eventEmitter.off(...params)
};
Object.freeze(this.LoopRooms);

View File

@ -75,6 +75,14 @@ const cloneValueInto = function(value, targetWindow) {
return value;
}
// Strip Function properties, since they can not be cloned across boundaries
// like this.
for (let prop of Object.getOwnPropertyNames(value)) {
if (typeof value[prop] == "function") {
delete value[prop];
}
}
// Inspect for an error this way, because the Error object is special.
if (value.constructor.name == "Error") {
return cloneErrorObject(value, targetWindow);
@ -176,8 +184,10 @@ function injectLoopAPI(targetWindow) {
}
// We have to clone the error property since it may be an Error object.
if (error.hasOwnProperty("toString")) {
delete error.toString;
}
errors[type] = Cu.cloneInto(error, targetWindow);
}
return Cu.cloneInto(errors, targetWindow);
},
@ -196,34 +206,34 @@ function injectLoopAPI(targetWindow) {
},
/**
* Returns the callData for a specific callDataId
* Returns the callData for a specific conversation window id.
*
* The data was retrieved from the LoopServer via a GET/calls/<version> request
* triggered by an incoming message from the LoopPushServer.
*
* @param {int} loopCallId
* @param {Number} conversationWindowId
* @returns {callData} The callData or undefined if error.
*/
getCallData: {
enumerable: true,
writable: true,
value: function(loopCallId) {
return Cu.cloneInto(LoopCalls.getCallData(loopCallId), targetWindow);
value: function(conversationWindowId) {
return Cu.cloneInto(LoopCalls.getCallData(conversationWindowId), targetWindow);
}
},
/**
* Releases the callData for a specific loopCallId
* Releases the callData for a specific conversation window id.
*
* The result of this call will be a free call session slot.
*
* @param {int} loopCallId
* @param {Number} conversationWindowId
*/
releaseCallData: {
enumerable: true,
writable: true,
value: function(loopCallId) {
LoopCalls.releaseCallData(loopCallId);
value: function(conversationWindowId) {
LoopCalls.releaseCallData(conversationWindowId);
}
},

View File

@ -352,7 +352,7 @@ loop.conversation = (function(mozL10n) {
setupIncomingCall: function() {
navigator.mozLoop.startAlerting();
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("callId"));
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("windowId"));
if (!callData) {
// XXX Not the ideal response, but bug 1047410 will be replacing
// this by better "call failed" UI.
@ -374,7 +374,7 @@ loop.conversation = (function(mozL10n) {
* Moves the call to the end state
*/
endCall: function() {
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
this.setState({callStatus: "end"});
},
@ -475,7 +475,7 @@ loop.conversation = (function(mozL10n) {
*/
_declineCall: function() {
this._websocket.decline();
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
this._websocket.close();
// Having a timeout here lets the logging for the websocket complete and be
// displayed on the console if both are on.
@ -618,10 +618,10 @@ loop.conversation = (function(mozL10n) {
{sdk: window.OT} // Model dependencies
);
// Obtain the callId and pass it through
// Obtain the windowId and pass it through
var helper = new loop.shared.utils.Helper();
var locationHash = helper.locationData().hash;
var callId;
var windowId;
var outgoing;
var localRoomStore;
@ -633,7 +633,7 @@ loop.conversation = (function(mozL10n) {
var hash = locationHash.match(/#incoming\/(.*)/);
if (hash) {
callId = hash[1];
windowId = hash[1];
outgoing = false;
} else if (hash = locationHash.match(/#room\/(.*)/)) {
localRoomStore = new loop.store.LocalRoomStore({
@ -643,16 +643,16 @@ loop.conversation = (function(mozL10n) {
} else {
hash = locationHash.match(/#outgoing\/(.*)/);
if (hash) {
callId = hash[1];
windowId = hash[1];
outgoing = true;
}
}
conversation.set({callId: callId});
conversation.set({windowId: windowId});
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
navigator.mozLoop.releaseCallData(callId);
navigator.mozLoop.releaseCallData(windowId);
});
React.renderComponent(AppControllerView({
@ -671,7 +671,7 @@ loop.conversation = (function(mozL10n) {
}
dispatcher.dispatch(new loop.shared.actions.GatherCallData({
callId: callId,
windowId: windowId,
outgoing: outgoing
}));
}

View File

@ -352,7 +352,7 @@ loop.conversation = (function(mozL10n) {
setupIncomingCall: function() {
navigator.mozLoop.startAlerting();
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("callId"));
var callData = navigator.mozLoop.getCallData(this.props.conversation.get("windowId"));
if (!callData) {
// XXX Not the ideal response, but bug 1047410 will be replacing
// this by better "call failed" UI.
@ -374,7 +374,7 @@ loop.conversation = (function(mozL10n) {
* Moves the call to the end state
*/
endCall: function() {
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
this.setState({callStatus: "end"});
},
@ -475,7 +475,7 @@ loop.conversation = (function(mozL10n) {
*/
_declineCall: function() {
this._websocket.decline();
navigator.mozLoop.releaseCallData(this.props.conversation.get("callId"));
navigator.mozLoop.releaseCallData(this.props.conversation.get("windowId"));
this._websocket.close();
// Having a timeout here lets the logging for the websocket complete and be
// displayed on the console if both are on.
@ -618,10 +618,10 @@ loop.conversation = (function(mozL10n) {
{sdk: window.OT} // Model dependencies
);
// Obtain the callId and pass it through
// Obtain the windowId and pass it through
var helper = new loop.shared.utils.Helper();
var locationHash = helper.locationData().hash;
var callId;
var windowId;
var outgoing;
var localRoomStore;
@ -633,7 +633,7 @@ loop.conversation = (function(mozL10n) {
var hash = locationHash.match(/#incoming\/(.*)/);
if (hash) {
callId = hash[1];
windowId = hash[1];
outgoing = false;
} else if (hash = locationHash.match(/#room\/(.*)/)) {
localRoomStore = new loop.store.LocalRoomStore({
@ -643,16 +643,16 @@ loop.conversation = (function(mozL10n) {
} else {
hash = locationHash.match(/#outgoing\/(.*)/);
if (hash) {
callId = hash[1];
windowId = hash[1];
outgoing = true;
}
}
conversation.set({callId: callId});
conversation.set({windowId: windowId});
window.addEventListener("unload", function(event) {
// Handle direct close of dialog box via [x] control.
navigator.mozLoop.releaseCallData(callId);
navigator.mozLoop.releaseCallData(windowId);
});
React.renderComponent(<AppControllerView
@ -671,7 +671,7 @@ loop.conversation = (function(mozL10n) {
}
dispatcher.dispatch(new loop.shared.actions.GatherCallData({
callId: callId,
windowId: windowId,
outgoing: outgoing
}));
}

View File

@ -42,7 +42,7 @@ loop.shared.actions = (function() {
*/
GatherCallData: Action.define("gatherCallData", {
// Specify the callId for an incoming call.
callId: [String, null],
windowId: [String, null],
outgoing: Boolean
}),

View File

@ -55,6 +55,8 @@ loop.store.ConversationStore = (function() {
var ConversationStore = Backbone.Model.extend({
defaults: {
// The id of the window. Currently used for getting the window id.
windowId: undefined,
// The current state of the call
callState: CALL_STATES.INIT,
// The reason if a call was terminated
@ -200,7 +202,7 @@ loop.store.ConversationStore = (function() {
return;
}
var callData = navigator.mozLoop.getCallData(actionData.callId);
var callData = navigator.mozLoop.getCallData(actionData.windowId);
if (!callData) {
console.error("Failed to get the call data");
this.set({callState: CALL_STATES.TERMINATED});
@ -210,7 +212,7 @@ loop.store.ConversationStore = (function() {
this.set({
contact: callData.contact,
outgoing: actionData.outgoing,
callId: actionData.callId,
windowId: actionData.windowId,
callType: callData.callType,
callState: CALL_STATES.GATHER
});
@ -407,11 +409,7 @@ loop.store.ConversationStore = (function() {
delete this._websocket;
}
// XXX: The internal callId is different from
// this.get("callId"), see bug 1084228 for more info.
var locationHash = new loop.shared.utils.Helper().locationData().hash;
var callId = locationHash.match(/\#outgoing\/(.*)/)[1];
navigator.mozLoop.releaseCallData(callId);
navigator.mozLoop.releaseCallData(this.get("windowId"));
},
/**

View File

@ -22,6 +22,7 @@ loop.shared.models = (function(l10n) {
sessionToken: undefined, // OT session token
sessionType: undefined, // Hawk session type
apiKey: undefined, // OT api key
windowId: undefined, // The window id
callId: undefined, // The callId on the server
progressURL: undefined, // The websocket url to use for progress
websocketToken: undefined, // The token to use for websocket auth, this is

View File

@ -158,7 +158,7 @@ describe("loop.conversation", function() {
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.GatherCallData({
callId: "42",
windowId: "42",
outgoing: false
}));
});
@ -175,7 +175,7 @@ describe("loop.conversation", function() {
sinon.assert.calledOnce(loop.Dispatcher.prototype.dispatch);
sinon.assert.calledWithExactly(loop.Dispatcher.prototype.dispatch,
new loop.shared.actions.GatherCallData({
callId: "24",
windowId: "24",
outgoing: true
}));
});
@ -276,7 +276,7 @@ describe("loop.conversation", function() {
conversation = new loop.shared.models.ConversationModel({}, {
sdk: {}
});
conversation.set({callId: 42});
conversation.set({windowId: 42});
sandbox.stub(conversation, "setOutgoingSessionData");
});
@ -547,8 +547,10 @@ describe("loop.conversation", function() {
decline: sinon.stub(),
close: sinon.stub()
};
conversation.set({
windowId: "8699"
});
conversation.setIncomingSessionData({
callId: 8699,
websocketToken: 123
});
});
@ -571,7 +573,7 @@ describe("loop.conversation", function() {
icView.decline();
sinon.assert.calledOnce(navigator.mozLoop.releaseCallData);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, 8699);
sinon.assert.calledWithExactly(navigator.mozLoop.releaseCallData, "8699");
});
});
@ -610,7 +612,7 @@ describe("loop.conversation", function() {
sinon.assert.calledTwice(conversation.get);
sinon.assert.calledWithExactly(conversation.get, "callToken");
sinon.assert.calledWithExactly(conversation.get, "callId");
sinon.assert.calledWithExactly(conversation.get, "windowId");
});
it("should trigger error handling in case of error", function() {

View File

@ -125,11 +125,7 @@ describe("loop.store.ConversationStore", function () {
describe("#connectionFailure", function() {
beforeEach(function() {
store._websocket = fakeWebsocket;
sandbox.stub(loop.shared.utils.Helper.prototype, "locationData")
.returns({
hash: "#outgoing/42",
pathname: ""
});
store.set({windowId: "42"});
});
it("should disconnect the session", function() {
@ -246,7 +242,7 @@ describe("loop.store.ConversationStore", function () {
it("should set the state to 'gather'", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
callId: "76543218",
windowId: "76543218",
outgoing: true
}));
@ -256,18 +252,18 @@ describe("loop.store.ConversationStore", function () {
it("should save the basic call information", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
callId: "123456",
windowId: "123456",
outgoing: true
}));
expect(store.get("callId")).eql("123456");
expect(store.get("windowId")).eql("123456");
expect(store.get("outgoing")).eql(true);
});
it("should save the basic information from the mozLoop api", function() {
dispatcher.dispatch(
new sharedActions.GatherCallData({
callId: "123456",
windowId: "123456",
outgoing: true
}));
@ -280,7 +276,7 @@ describe("loop.store.ConversationStore", function () {
beforeEach(function() {
outgoingCallData = {
callId: "123456",
windowId: "123456",
outgoing: true
};
});
@ -499,11 +495,7 @@ describe("loop.store.ConversationStore", function () {
close: wsCloseSpy
};
store.set({callState: CALL_STATES.ONGOING});
sandbox.stub(loop.shared.utils.Helper.prototype, "locationData")
.returns({
hash: "#outgoing/42",
pathname: ""
});
store.set({windowId: "42"});
});
it("should disconnect the session", function() {
@ -549,11 +541,7 @@ describe("loop.store.ConversationStore", function () {
close: wsCloseSpy
};
store.set({callState: CALL_STATES.ONGOING});
sandbox.stub(loop.shared.utils.Helper.prototype, "locationData")
.returns({
hash: "#outgoing/42",
pathname: ""
});
store.set({windowId: "42"});
});
it("should disconnect the session", function() {
@ -587,11 +575,7 @@ describe("loop.store.ConversationStore", function () {
store._websocket = fakeWebsocket;
store.set({callState: CALL_STATES.CONNECTING});
sandbox.stub(loop.shared.utils.Helper.prototype, "locationData")
.returns({
hash: "#outgoing/42",
pathname: ""
});
store.set({windowId: "42"});
});
it("should disconnect the session", function() {

View File

@ -45,7 +45,7 @@ describe("loop.Dispatcher", function () {
beforeEach(function() {
gatherAction = new sharedActions.GatherCallData({
callId: "42",
windowId: "42",
outgoing: false
});

View File

@ -0,0 +1,175 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Cu.import("resource://services-common/utils.js");
Cu.import("resource:///modules/loop/LoopRooms.jsm");
const kRooms = new Map([
["_nxD4V4FflQ", {
roomToken: "_nxD4V4FflQ",
roomName: "First Room Name",
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
maxSize: 2,
currSize: 0,
ctime: 1405517546
}],
["QzBbvGmIZWU", {
roomToken: "QzBbvGmIZWU",
roomName: "Second Room Name",
roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU",
maxSize: 2,
currSize: 0,
ctime: 140551741
}],
["3jKS_Els9IU", {
roomToken: "3jKS_Els9IU",
roomName: "Third Room Name",
roomUrl: "http://localhost:3000/rooms/3jKS_Els9IU",
maxSize: 3,
clientMaxSize: 2,
currSize: 1,
ctime: 1405518241
}]
]);
let roomDetail = {
roomName: "First Room Name",
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
roomOwner: "Alexis",
maxSize: 2,
clientMaxSize: 2,
creationTime: 1405517546,
expiresAt: 1405534180,
participants: [{
displayName: "Alexis",
account: "alexis@example.com",
roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
}, {
displayName: "Adam",
roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
}]
};
const kCreateRoomProps = {
roomName: "UX Discussion",
expiresIn: 5,
roomOwner: "Alexis",
maxSize: 2
};
const kCreateRoomData = {
roomToken: "_nxD4V4FflQ",
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
expiresAt: 1405534180
};
add_task(function* setup_server() {
loopServer.registerPathHandler("/registration", (req, res) => {
res.setStatusLine(null, 200, "OK");
res.processAsync();
res.finish();
});
loopServer.registerPathHandler("/rooms", (req, res) => {
res.setStatusLine(null, 200, "OK");
if (req.method == "POST") {
Assert.ok(req.bodyInputStream, "POST request should have a payload");
let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
let data = JSON.parse(body);
Assert.deepEqual(data, kCreateRoomProps);
res.write(JSON.stringify(kCreateRoomData));
} else {
res.write(JSON.stringify([...kRooms.values()]));
}
res.processAsync();
res.finish();
});
function returnRoomDetails(res, roomName) {
roomDetail.roomName = roomName;
res.setStatusLine(null, 200, "OK");
res.write(JSON.stringify(roomDetail));
res.processAsync();
res.finish();
}
// Add a request handler for each room in the list.
[...kRooms.values()].forEach(function(room) {
loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => {
returnRoomDetails(res, room.roomName);
});
});
loopServer.registerPathHandler("/rooms/error401", (req, res) => {
res.setStatusLine(null, 401, "Not Found");
res.processAsync();
res.finish();
});
loopServer.registerPathHandler("/rooms/errorMalformed", (req, res) => {
res.setStatusLine(null, 200, "OK");
res.write("{\"some\": \"Syntax Error!\"}}}}}}");
res.processAsync();
res.finish();
});
});
const normalizeRoom = function(room) {
delete room.currSize;
if (!("participants" in room)) {
let name = room.roomName;
for (let key of Object.getOwnPropertyNames(roomDetail)) {
room[key] = roomDetail[key];
}
room.roomName = name;
}
return room;
};
const compareRooms = function(room1, room2) {
Assert.deepEqual(normalizeRoom(room1), normalizeRoom(room2));
};
add_task(function* test_getAllRooms() {
yield MozLoopService.register(mockPushHandler);
let rooms = yield LoopRooms.promise("getAll");
Assert.equal(rooms.length, 3);
for (let room of rooms) {
compareRooms(kRooms.get(room.roomToken), room);
}
});
add_task(function* test_getRoom() {
yield MozLoopService.register(mockPushHandler);
let roomToken = "_nxD4V4FflQ";
let room = yield LoopRooms.promise("get", roomToken);
Assert.deepEqual(room, kRooms.get(roomToken));
});
add_task(function* test_errorStates() {
yield Assert.rejects(LoopRooms.promise("get", "error401"), /Not Found/, "Fetching a non-existent room should fail");
yield Assert.rejects(LoopRooms.promise("get", "errorMalformed"), /SyntaxError/, "Wrong message format should reject");
});
add_task(function* test_createRoom() {
let eventCalled = false;
LoopRooms.once("add", (e, room) => {
compareRooms(room, kCreateRoomProps);
eventCalled = true;
});
let room = yield LoopRooms.promise("create", kCreateRoomProps);
compareRooms(room, kCreateRoomProps);
Assert.ok(eventCalled, "Event should have fired");
});
function run_test() {
setupFakeLoopServer();
run_next_test();
}

View File

@ -1,103 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Cu.import("resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
let hasTheseProps = function(a, b) {
for (let prop in a) {
if (a[prop] != b[prop]) {
return false;
}
}
return true;
}
let openChatOrig = Chat.open;
add_test(function test_openRoomsWindow() {
let roomProps = {roomName: "UX Discussion",
expiresIn: 5,
roomOwner: "Alexis",
maxSize: 2}
let roomData = {roomToken: "_nxD4V4FflQ",
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
expiresAt: 1405534180}
loopServer.registerPathHandler("/rooms", (request, response) => {
if (!request.bodyInputStream) {
do_throw("empty request body");
}
let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
let data = JSON.parse(body);
do_check_true(hasTheseProps(roomProps, data));
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify(roomData));
response.processAsync();
response.finish();
});
MozLoopService.register(mockPushHandler).then(() => {
let opened = false;
let created = false;
let urlPieces = [];
Chat.open = function(contentWindow, origin, title, url) {
urlPieces = url.split('/');
do_check_eq(urlPieces[0], "about:loopconversation#room");
opened = true;
};
let returnedID = LoopRooms.createRoom(roomProps, (error, data) => {
do_check_false(error);
do_check_true(data);
do_check_true(hasTheseProps(roomData, data));
do_check_eq(data.localRoomId, urlPieces[1]);
created = true;
});
waitForCondition(function() created && opened).then(() => {
do_check_true(opened, "should open a chat window");
do_check_eq(returnedID, urlPieces[1]);
// Verify that a delayed callback, when attached,
// received the same data.
LoopRooms.addCallback(
urlPieces[1], "RoomCreated",
(error, data) => {
do_check_false(error);
do_check_true(data);
do_check_true(hasTheseProps(roomData, data));
do_check_eq(data.localRoomId, urlPieces[1]);
});
run_next_test();
}, () => {
do_throw("should have opened a chat window");
});
});
});
function run_test()
{
setupFakeLoopServer();
mockPushHandler.registrationPushURL = kEndPointUrl;
loopServer.registerPathHandler("/registration", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.processAsync();
response.finish();
});
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
});
run_next_test();
}

View File

@ -1,132 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
Cu.import("resource://services-common/utils.js");
XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
let hasTheseProps = function(a, b) {
for (let prop in a) {
if (a[prop] != b[prop]) {
do_print("hasTheseProps fail: prop = " + prop);
return false;
}
}
return true;
}
let openChatOrig = Chat.open;
add_test(function test_getAllRooms() {
let roomList = [
{ roomToken: "_nxD4V4FflQ",
roomName: "First Room Name",
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
maxSize: 2,
currSize: 0,
ctime: 1405517546 },
{ roomToken: "QzBbvGmIZWU",
roomName: "Second Room Name",
roomUrl: "http://localhost:3000/rooms/QzBbvGmIZWU",
maxSize: 2,
currSize: 0,
ctime: 140551741 },
{ roomToken: "3jKS_Els9IU",
roomName: "Third Room Name",
roomUrl: "http://localhost:3000/rooms/3jKS_Els9IU",
maxSize: 3,
clientMaxSize: 2,
currSize: 1,
ctime: 1405518241 }
]
let roomDetail = {
roomName: "First Room Name",
roomUrl: "http://localhost:3000/rooms/_nxD4V4FflQ",
roomOwner: "Alexis",
maxSize: 2,
clientMaxSize: 2,
creationTime: 1405517546,
expiresAt: 1405534180,
participants: [
{ displayName: "Alexis", account: "alexis@example.com", roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" },
{ displayName: "Adam", roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" }
]
}
loopServer.registerPathHandler("/rooms", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify(roomList));
response.processAsync();
response.finish();
});
let returnRoomDetails = function(response, roomName) {
roomDetail.roomName = roomName;
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify(roomDetail));
response.processAsync();
response.finish();
}
loopServer.registerPathHandler("/rooms/_nxD4V4FflQ", (request, response) => {
returnRoomDetails(response, "First Room Name");
});
loopServer.registerPathHandler("/rooms/QzBbvGmIZWU", (request, response) => {
returnRoomDetails(response, "Second Room Name");
});
loopServer.registerPathHandler("/rooms/3jKS_Els9IU", (request, response) => {
returnRoomDetails(response, "Third Room Name");
});
MozLoopService.register().then(() => {
LoopRooms.getAll((error, rooms) => {
do_check_false(error);
do_check_true(rooms);
do_check_eq(rooms.length, 3);
do_check_eq(rooms[0].roomName, "First Room Name");
do_check_eq(rooms[1].roomName, "Second Room Name");
do_check_eq(rooms[2].roomName, "Third Room Name");
let room = rooms[0];
do_check_true(room.localRoomId);
do_check_false(room.currSize);
delete roomList[0].currSize;
do_check_true(hasTheseProps(roomList[0], room));
delete roomDetail.roomName;
delete room.participants;
delete roomDetail.participants;
do_check_true(hasTheseProps(roomDetail, room));
LoopRooms.getRoomData(room.localRoomId, (error, roomData) => {
do_check_false(error);
do_check_true(hasTheseProps(room, roomData));
run_next_test();
});
});
});
});
function run_test() {
setupFakeLoopServer();
mockPushHandler.registrationPushURL = kEndPointUrl;
loopServer.registerPathHandler("/registration", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.processAsync();
response.finish();
});
do_register_cleanup(function() {
// Revert original Chat.open implementation
Chat.open = openChatOrig;
});
run_next_test();
}

View File

@ -6,6 +6,7 @@ skip-if = toolkit == 'gonk'
[test_loopapi_hawk_request.js]
[test_looppush_initialize.js]
[test_looprooms.js]
[test_loopservice_directcall.js]
[test_loopservice_dnd.js]
[test_loopservice_expiry.js]
@ -21,5 +22,3 @@ skip-if = toolkit == 'gonk'
[test_loopservice_token_send.js]
[test_loopservice_token_validation.js]
[test_loopservice_busy.js]
[test_rooms_getdata.js]
[test_rooms_create.js]

View File

@ -401,6 +401,7 @@ BrowserGlue.prototype = {
Services.obs.removeObserver(this, "browser-search-service");
this._syncSearchEngines();
break;
#ifdef NIGHTLY_BUILD
case "nsPref:changed":
if (data == POLARIS_ENABLED) {
let enabled = Services.prefs.getBoolPref(POLARIS_ENABLED);
@ -408,6 +409,7 @@ BrowserGlue.prototype = {
Services.prefs.setBoolPref("privacy.trackingprotection.enabled", enabled);
Services.prefs.setBoolPref("privacy.trackingprotection.ui.enabled", enabled);
}
#endif
}
},

View File

@ -14,6 +14,24 @@ var gPrivacyPane = {
*/
_shouldPromptForRestart: true,
#ifdef NIGHTLY_BUILD
/**
* Show the Tracking Protection UI depending on the
* privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link
*/
_initTrackingProtection: function () {
if (!Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled")) {
return;
}
let link = document.getElementById("trackingProtectionLearnMore");
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection";
link.setAttribute("href", url);
document.getElementById("trackingprotectionbox").hidden = false;
},
#endif
/**
* Sets up the UI for the number of days of history to keep, and updates the
* label of the "Clear Now..." button.
@ -31,6 +49,9 @@ var gPrivacyPane = {
this.updateHistoryModePane();
this.updatePrivacyMicroControls();
this.initAutoStartPrivateBrowsingReverter();
#ifdef NIGHTLY_BUILD
this._initTrackingProtection();
#endif
setEventListener("browser.urlbar.default.behavior", "change",
document.getElementById('browser.urlbar.autocomplete.enabled')

View File

@ -13,6 +13,9 @@
<preference id="privacy.donottrackheader.enabled"
name="privacy.donottrackheader.enabled"
type="bool"/>
<preference id="privacy.trackingprotection.enabled"
name="privacy.trackingprotection.enabled"
type="bool"/>
<!-- XXX button prefs -->
<preference id="pref.privacy.disable_button.cookie_exceptions"
@ -71,6 +74,19 @@
<!-- Tracking -->
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
<caption><label>&tracking.label;</label></caption>
<vbox id="trackingprotectionbox" hidden="true">
<hbox align="center">
<checkbox id="trackingProtection"
preference="privacy.trackingprotection.enabled"
accesskey="&trackingProtection.accesskey;"
label="&trackingProtection.label;" />
<image id="trackingProtectionImage" src="chrome://browser/skin/bad-content-blocked-16.png"/>
</hbox>
<label id="trackingProtectionLearnMore"
class="text-link"
value="&trackingProtectionLearnMore.label;"/>
<separator/>
</vbox>
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"

View File

@ -17,6 +17,24 @@ var gPrivacyPane = {
*/
_shouldPromptForRestart: true,
#ifdef NIGHTLY_BUILD
/**
* Show the Tracking Protection UI depending on the
* privacy.trackingprotection.ui.enabled pref, and linkify its Learn More link
*/
_initTrackingProtection: function () {
if (!Services.prefs.getBoolPref("privacy.trackingprotection.ui.enabled")) {
return;
}
let link = document.getElementById("trackingProtectionLearnMore");
let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "tracking-protection";
link.setAttribute("href", url);
document.getElementById("trackingprotectionbox").hidden = false;
},
#endif
/**
* Sets up the UI for the number of days of history to keep, and updates the
* label of the "Clear Now..." button.
@ -28,6 +46,9 @@ var gPrivacyPane = {
this.updateHistoryModePane();
this.updatePrivacyMicroControls();
this.initAutoStartPrivateBrowsingReverter();
#ifdef NIGHTLY_BUILD
this._initTrackingProtection();
#endif
},
// HISTORY MODE

View File

@ -26,6 +26,9 @@
<preference id="privacy.donottrackheader.enabled"
name="privacy.donottrackheader.enabled"
type="bool"/>
<preference id="privacy.trackingprotection.enabled"
name="privacy.trackingprotection.enabled"
type="bool"/>
<!-- XXX button prefs -->
<preference id="pref.privacy.disable_button.cookie_exceptions"
@ -81,6 +84,19 @@
<!-- Tracking -->
<groupbox id="trackingGroup" align="start">
<caption label="&tracking.label;"/>
<vbox id="trackingprotectionbox" hidden="true">
<hbox align="center">
<checkbox id="trackingProtection"
preference="privacy.trackingprotection.enabled"
accesskey="&trackingProtection.accesskey;"
label="&trackingProtection.label;" />
<image id="trackingProtectionImage" src="chrome://browser/skin/bad-content-blocked-16.png"/>
</hbox>
<label id="trackingProtectionLearnMore"
class="text-link"
value="&trackingProtectionLearnMore.label;"/>
<separator/>
</vbox>
<checkbox id="privacyDoNotTrackCheckbox"
label="&dntTrackingNotOkay.label2;"
accesskey="&dntTrackingNotOkay.accesskey;"

View File

@ -23,12 +23,24 @@ function* testPrefs(test) {
}
}
function isNightly() {
return Services.appinfo.version.contains("a1");
}
add_task(function* test_default_values() {
if (!isNightly()) {
ok(true, "Skipping test, not Nightly")
return;
}
Assert.ok(!Services.prefs.getBoolPref(POLARIS_ENABLED), POLARIS_ENABLED + " is disabled by default.");
Assert.ok(!Services.prefs.getBoolPref(PREF_TPUI), PREF_TPUI + "is disabled by default.");
});
add_task(function* test_changing_pref_changes_tracking() {
if (!isNightly()) {
ok(true, "Skipping test, not Nightly")
return;
}
function* testPref(pref) {
Services.prefs.setBoolPref(POLARIS_ENABLED, true);
yield assertPref(pref, true);
@ -41,6 +53,10 @@ add_task(function* test_changing_pref_changes_tracking() {
});
add_task(function* test_prefs_can_be_changed_individually() {
if (!isNightly()) {
ok(true, "Skipping test, not Nightly")
return;
}
function* testPref(pref) {
Services.prefs.setBoolPref(POLARIS_ENABLED, true);
yield assertPref(pref, true);

View File

@ -44,8 +44,14 @@ const TIMELINE_BLUEPRINT = {
stroke: "hsl(39,82%,49%)",
label: L10N.getStr("timeline.label.paint")
},
"ConsoleTime": {
"DOMEvent": {
group: 3,
fill: "hsl(219,82%,69%)",
stroke: "hsl(219,82%,69%)",
label: L10N.getStr("timeline.label.domevent")
},
"ConsoleTime": {
group: 4,
fill: "hsl(0,0%,80%)",
stroke: "hsl(0,0%,60%)",
label: L10N.getStr("timeline.label.consoleTime")

View File

@ -231,6 +231,7 @@
@BINPATH@/components/dom_stylesheets.xpt
@BINPATH@/components/dom_telephony.xpt
@BINPATH@/components/dom_traversal.xpt
@BINPATH@/components/dom_tv.xpt
@BINPATH@/components/dom_voicemail.xpt
#ifdef MOZ_WEBSPEECH
@BINPATH@/components/dom_webspeechrecognition.xpt

View File

@ -17,6 +17,12 @@ setDefaultBrowserNotNow.accesskey = N
setDefaultBrowserNever.label = Don't ask me again
setDefaultBrowserNever.accesskey = D
# LOCALIZATION NOTE (setDefaultBrowserTitle, setDefaultBrowserMessage, setDefaultBrowserDontAsk):
# These strings are used as an alternative to the ones above, in a modal dialog.
# %S will be replaced by brandShortName
setDefaultBrowserTitle=Default Browser
setDefaultBrowserMessage=%S is not currently set as your default browser. Would you like to make it your default browser?
setDefaultBrowserDontAsk=Always perform this check when starting %S.
desktopBackgroundLeafNameWin=Desktop Background.bmp
DesktopBackgroundDownloading=Saving Picture…

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

View File

@ -32,7 +32,11 @@ toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention]) >
}
#downloads-button[cui-areatype="toolbar"][attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
}
toolbar[brighttext] #downloads-button[cui-areatype="toolbar"][attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
}
#downloads-button[cui-areatype="menu-panel"][attention] {
@ -54,7 +58,11 @@ toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloa
}
#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
}
toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
}
/*** Download notifications ***/

View File

@ -108,7 +108,6 @@ browser.jar:
skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png)
skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -34,7 +34,11 @@ toolbar[brighttext] #downloads-indicator-icon {
}
#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
}
toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
}
#downloads-button[cui-areatype="menu-panel"][attention] {
@ -56,7 +60,11 @@ toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloa
}
#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
}
toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
}
@media (min-resolution: 2dppx) {
@ -79,7 +87,11 @@ toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloa
}
#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: url("chrome://browser/skin/downloads/download-glow@2x.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
}
toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
}
#downloads-button[cui-areatype="menu-panel"][attention] {
@ -87,7 +99,11 @@ toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloa
}
#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: url("chrome://browser/skin/downloads/download-glow@2x.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
}
toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
}
}

View File

@ -182,8 +182,6 @@ browser.jar:
skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
skin/classic/browser/downloads/buttons@2x.png (downloads/buttons@2x.png)
skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png)
skin/classic/browser/downloads/download-glow@2x.png (downloads/download-glow@2x.png)
skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
skin/classic/browser/downloads/download-glow-menuPanel@2x.png (downloads/download-glow-menuPanel@2x.png)
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -32,7 +32,11 @@ toolbar[brighttext] #downloads-button:not([attention]) > #downloads-indicator-an
}
#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
}
toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
}
#downloads-button[cui-areatype="menu-panel"][attention] {
@ -44,10 +48,6 @@ toolbar[brighttext] #downloads-button:not([attention]) > #downloads-indicator-an
@media (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) {
%endif
#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
background-image: url("chrome://browser/skin/downloads/download-glow-XPVista7.png");
}
#downloads-button[cui-areatype="menu-panel"][attention] {
list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel-XPVista7.png");
}
@ -69,7 +69,11 @@ toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloa
}
#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: url("chrome://browser/skin/downloads/download-glow.png");
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
}
toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
}
/*** Download notifications ***/

View File

@ -132,7 +132,6 @@ browser.jar:
* skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
skin/classic/browser/downloads/download-glow-XPVista7.png (downloads/download-glow-XPVista7.png)
skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png (downloads/download-glow-menuPanel-XPVista7.png)
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
@ -564,8 +563,6 @@ browser.jar:
* skin/classic/aero/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay-aero.css)
skin/classic/aero/browser/downloads/buttons.png (downloads/buttons-aero.png)
skin/classic/aero/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
skin/classic/aero/browser/downloads/download-glow.png (downloads/download-glow.png)
skin/classic/aero/browser/downloads/download-glow-XPVista7.png (downloads/download-glow-XPVista7.png)
skin/classic/aero/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
skin/classic/aero/browser/downloads/download-glow-menuPanel-XPVista7.png (downloads/download-glow-menuPanel-XPVista7.png)
skin/classic/aero/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)

View File

@ -2885,6 +2885,11 @@ nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
bool hasSeenEnd = false;
// DOM events can be nested, so we must take care when searching
// for the matching end. It doesn't hurt to apply this logic to
// all event types.
uint32_t markerDepth = 0;
// The assumption is that the devtools timeline flushes markers frequently
// enough for the amount of markers to always be small enough that the
// nested for loop isn't going to be a performance problem.
@ -2898,24 +2903,32 @@ nsDocShell::PopProfileTimelineMarkers(JSContext* aCx,
hasSeenPaintedLayer = true;
}
bool isSameMarkerType = strcmp(startMarkerName, endMarkerName) == 0;
if (strcmp(startMarkerName, endMarkerName) != 0) {
continue;
}
bool isPaint = strcmp(startMarkerName, "Paint") == 0;
// Pair start and end markers.
if (endPayload->GetMetaData() == TRACING_INTERVAL_END && isSameMarkerType) {
// But ignore paint start/end if no layer has been painted.
if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
mozilla::dom::ProfileTimelineMarker marker;
marker.mName = NS_ConvertUTF8toUTF16(startMarkerName);
marker.mStart = mProfileTimelineMarkers[i]->mTime;
marker.mEnd = mProfileTimelineMarkers[j]->mTime;
profileTimelineMarkers.AppendElement(marker);
if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
++markerDepth;
} else if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
if (markerDepth > 0) {
--markerDepth;
} else {
// But ignore paint start/end if no layer has been painted.
if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
mozilla::dom::ProfileTimelineMarker marker;
marker.mName = NS_ConvertUTF8toUTF16(startMarkerName);
marker.mStart = mProfileTimelineMarkers[i]->mTime;
marker.mEnd = mProfileTimelineMarkers[j]->mTime;
profileTimelineMarkers.AppendElement(marker);
}
// We want the start to be dropped either way.
hasSeenEnd = true;
break;
}
// We want the start to be dropped either way.
hasSeenEnd = true;
break;
}
}

View File

@ -35,6 +35,7 @@ support-files =
file_bug1046022.html
print_postdata.sjs
test-form_sjis.html
timelineMarkers-04.html
[browser_bug134911.js]
skip-if = e10s # Bug ?????? - BrowserSetForcedCharacterSet() in browser.js references docShell
@ -98,3 +99,7 @@ skip-if = e10s
[browser_timelineMarkers-01.js]
[browser_timelineMarkers-02.js]
skip-if = e10s
[browser_timelineMarkers-03.js]
skip-if = e10s
[browser_timelineMarkers-04.js]
skip-if = e10s

View File

@ -0,0 +1,127 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the docShell profile timeline API returns the right
// markers for DOM events.
let TESTS = [{
desc: "Event dispatch with single handler",
setup: function() {
content.document.body.addEventListener("dog",
function(e) { console.log("hi"); },
true);
content.document.body.dispatchEvent(new Event("dog"));
},
check: function(markers) {
is(markers.length, 1, "Got 1 marker");
}
}, {
desc: "Event dispatch with a second handler",
setup: function() {
content.document.body.addEventListener("dog",
function(e) { console.log("hi"); },
false);
content.document.body.dispatchEvent(new Event("dog"));
},
check: function(markers) {
is(markers.length, 2, "Got 2 markers");
}
}, {
desc: "Event dispatch on a new document",
setup: function() {
let doc = content.document.implementation.createHTMLDocument("doc");
let p = doc.createElement("p");
p.innerHTML = "inside";
doc.body.appendChild(p);
p.addEventListener("zebra", function(e) {console.log("hi");});
p.dispatchEvent(new Event("zebra"));
},
check: function(markers) {
is(markers.length, 1, "Got 1 marker");
}
}, {
desc: "Event dispatch on window",
setup: function() {
let doc = content.window.addEventListener("aardvark", function(e) {
console.log("I like ants!");
});
content.window.dispatchEvent(new Event("aardvark"));
},
check: function(markers) {
is(markers.length, 1, "Got 1 marker");
}
}];
let test = Task.async(function*() {
waitForExplicitFinish();
yield openUrl("data:text/html;charset=utf-8,Test page");
let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
info("Start recording");
docShell.recordProfileTimelineMarkers = true;
for (let {desc, setup, check} of TESTS) {
info("Running test: " + desc);
info("Flushing the previous markers if any");
docShell.popProfileTimelineMarkers();
info("Running the test setup function");
let onMarkers = waitForMarkers(docShell);
setup();
info("Waiting for new markers on the docShell");
let markers = yield onMarkers;
info("Running the test check function");
check(markers.filter(m => m.name == "DOMEvent"));
}
info("Stop recording");
docShell.recordProfileTimelineMarkers = false;
gBrowser.removeCurrentTab();
finish();
});
function openUrl(url) {
return new Promise(function(resolve, reject) {
window.focus();
let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url);
let linkedBrowser = tab.linkedBrowser;
linkedBrowser.addEventListener("load", function onload() {
linkedBrowser.removeEventListener("load", onload, true);
resolve(tab);
}, true);
});
}
function waitForMarkers(docshell) {
return new Promise(function(resolve, reject) {
let waitIterationCount = 0;
let maxWaitIterationCount = 10; // Wait for 2sec maximum
let interval = setInterval(() => {
let markers = docshell.popProfileTimelineMarkers();
if (markers.length > 0) {
clearInterval(interval);
resolve(markers);
}
if (waitIterationCount > maxWaitIterationCount) {
clearInterval(interval);
resolve([]);
}
waitIterationCount++;
}, 200);
});
}

View File

@ -0,0 +1,94 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that the docShell profile timeline API returns the right
// markers for XMLHttpRequest events.
let TESTS = [{
desc: "Event dispatch from XMLHttpRequest",
setup: function() {
content.dispatchEvent(new Event("dog"));
},
check: function(markers) {
// One subtlety here is that we have five events: the event we
// inject in "setup", plus the four state transition events. The
// first state transition is reported synchronously and so should
// show up as a nested marker.
is(markers.length, 5, "Got 5 markers");
}
}];
let test = Task.async(function*() {
waitForExplicitFinish();
const testDir = "http://mochi.test:8888/browser/docshell/test/browser/";
const testName = "timelineMarkers-04.html";
yield openUrl(testDir + testName);
let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
info("Start recording");
docShell.recordProfileTimelineMarkers = true;
for (let {desc, setup, check} of TESTS) {
info("Running test: " + desc);
info("Flushing the previous markers if any");
docShell.popProfileTimelineMarkers();
info("Running the test setup function");
let onMarkers = waitForMarkers(docShell);
setup();
info("Waiting for new markers on the docShell");
let markers = yield onMarkers;
info("Running the test check function");
check(markers.filter(m => m.name == "DOMEvent"));
}
info("Stop recording");
docShell.recordProfileTimelineMarkers = false;
gBrowser.removeCurrentTab();
finish();
});
function openUrl(url) {
return new Promise(function(resolve, reject) {
window.focus();
let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url);
let linkedBrowser = tab.linkedBrowser;
linkedBrowser.addEventListener("load", function onload() {
linkedBrowser.removeEventListener("load", onload, true);
resolve(tab);
}, true);
});
}
function waitForMarkers(docshell) {
return new Promise(function(resolve, reject) {
let waitIterationCount = 0;
let maxWaitIterationCount = 10; // Wait for 2sec maximum
let interval = setInterval(() => {
let markers = docshell.popProfileTimelineMarkers();
if (markers.length > 0) {
clearInterval(interval);
resolve(markers);
}
if (waitIterationCount > maxWaitIterationCount) {
clearInterval(interval);
resolve([]);
}
waitIterationCount++;
}, 200);
});
}

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"></meta>
<title>markers test</title>
</head>
<body>
<p>Test page</p>
<script>
function do_xhr() {
const theURL = "timelineMarkers-04.html";
xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
// Nothing.
};
xhr.open("get", theURL, true);
xhr.send();
}
window.addEventListener("dog", do_xhr, true);
</script>
</body>
</html>

View File

@ -493,6 +493,11 @@ this.PermissionsTable = { geolocation: {
trusted: DENY_ACTION,
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},
"tv": {
app: DENY_ACTION,
privileged: DENY_ACTION,
certified: ALLOW_ACTION
}
};

View File

@ -39,6 +39,7 @@
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/Telephony.h"
#include "mozilla/dom/Voicemail.h"
#include "mozilla/dom/TVManager.h"
#include "mozilla/Hal.h"
#include "nsISiteSpecificUserAgent.h"
#include "mozilla/ClearOnShutdown.h"
@ -173,6 +174,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileMessageManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTelephony)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVoicemail)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTVManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
#ifdef MOZ_B2G_RIL
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileConnections)
@ -256,6 +258,10 @@ Navigator::Invalidate()
mVoicemail = nullptr;
}
if (mTVManager) {
mTVManager = nullptr;
}
if (mConnection) {
mConnection->Shutdown();
mConnection = nullptr;
@ -1582,6 +1588,19 @@ Navigator::GetMozTelephony(ErrorResult& aRv)
return mTelephony;
}
TVManager*
Navigator::GetTv()
{
if (!mTVManager) {
if (!mWindow) {
return nullptr;
}
mTVManager = TVManager::Create(mWindow);
}
return mTVManager;
}
#ifdef MOZ_B2G
already_AddRefed<Promise>
Navigator::GetMobileIdAssertion(const MobileIdOptions& aOptions,
@ -2326,6 +2345,37 @@ Navigator::HasMobileIdSupport(JSContext* aCx, JSObject* aGlobal)
}
#endif
/* static */
bool
Navigator::HasTVSupport(JSContext* aCx, JSObject* aGlobal)
{
JS::Rooted<JSObject*> global(aCx, aGlobal);
nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(global);
if (!win) {
return false;
}
// Just for testing, we can enable TV for any kind of app.
if (Preferences::GetBool("dom.testing.tv_enabled_for_hosted_apps", false)) {
return true;
}
nsIDocument* doc = win->GetExtantDoc();
if (!doc || !doc->NodePrincipal()) {
return false;
}
nsIPrincipal* principal = doc->NodePrincipal();
uint16_t status;
if (NS_FAILED(principal->GetAppStatus(&status))) {
return false;
}
// Only support TV Manager API for certified apps for now.
return status == nsIPrincipal::APP_STATUS_CERTIFIED;
}
/* static */
already_AddRefed<nsPIDOMWindow>
Navigator::GetWindowFromGlobal(JSObject* aGlobal)

View File

@ -93,6 +93,7 @@ class PowerManager;
class CellBroadcast;
class Telephony;
class Voicemail;
class TVManager;
namespace time {
class TimeManager;
@ -223,6 +224,7 @@ public:
MobileMessageManager* GetMozMobileMessage();
Telephony* GetMozTelephony(ErrorResult& aRv);
Voicemail* GetMozVoicemail(ErrorResult& aRv);
TVManager* GetTv();
network::Connection* GetConnection(ErrorResult& aRv);
nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
MediaDevices* GetMediaDevices(ErrorResult& aRv);
@ -305,6 +307,8 @@ public:
static bool HasMobileIdSupport(JSContext* aCx, JSObject* aGlobal);
#endif
static bool HasTVSupport(JSContext* aCx, JSObject* aGlobal);
nsPIDOMWindow* GetParentObject() const
{
return GetWindow();
@ -334,6 +338,7 @@ private:
nsRefPtr<MobileMessageManager> mMobileMessageManager;
nsRefPtr<Telephony> mTelephony;
nsRefPtr<Voicemail> mVoicemail;
nsRefPtr<TVManager> mTVManager;
nsRefPtr<network::Connection> mConnection;
#ifdef MOZ_B2G_RIL
nsRefPtr<MobileConnectionArray> mMobileConnections;

View File

@ -4491,13 +4491,19 @@ nsContentUtils::AppendNodeTextContent(nsINode* aNode, bool aDeep,
}
bool
nsContentUtils::HasNonEmptyTextContent(nsINode* aNode)
nsContentUtils::HasNonEmptyTextContent(nsINode* aNode,
TextContentDiscoverMode aDiscoverMode)
{
for (nsIContent* child = aNode->GetFirstChild();
child;
child = child->GetNextSibling()) {
if (child->IsNodeOfType(nsINode::eTEXT) &&
child->TextLength() > 0) {
return true;
}
if (aDiscoverMode == eRecurseIntoChildren &&
HasNonEmptyTextContent(child, aDiscoverMode)) {
return true;
}
}

View File

@ -1240,12 +1240,19 @@ public:
nsAString& aResult, const mozilla::fallible_t&);
/**
* Utility method that checks if a given node has any non-empty
* children.
* NOTE! This method does not descend recursivly into elements.
* Though it would be easy to make it so if needed
* Utility method that checks if a given node has any non-empty children. This
* method does not descend recursively into children by default.
*
* @param aDiscoverMode Set to eRecurseIntoChildren to descend recursively
* into children.
*/
static bool HasNonEmptyTextContent(nsINode* aNode);
enum TextContentDiscoverMode MOZ_ENUM_TYPE(uint8_t) {
eRecurseIntoChildren, eDontRecurseIntoChildren
};
static bool HasNonEmptyTextContent(
nsINode* aNode,
TextContentDiscoverMode aDiscoverMode = eDontRecurseIntoChildren);
/**
* Delete strings allocated for nsContentList matches

View File

@ -707,6 +707,8 @@ GK_ATOM(onconnected, "onconnected")
GK_ATOM(onconnecting, "onconnecting")
GK_ATOM(oncontextmenu, "oncontextmenu")
GK_ATOM(oncopy, "oncopy")
GK_ATOM(oncurrentchannelchanged, "oncurrentchannelchanged")
GK_ATOM(oncurrentsourcechanged, "oncurrentsourcechanged")
GK_ATOM(oncut, "oncut")
GK_ATOM(ondatachange, "ondatachange")
GK_ATOM(ondataerror, "ondataerror")
@ -746,6 +748,7 @@ GK_ATOM(ondragleave, "ondragleave")
GK_ATOM(ondragover, "ondragover")
GK_ATOM(ondragstart, "ondragstart")
GK_ATOM(ondrop, "ondrop")
GK_ATOM(oneitbroadcasted, "oneitbroadcasted")
GK_ATOM(onenabled, "onenabled")
GK_ATOM(onenterpincodereq, "onenterpincodereq")
GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
@ -848,6 +851,7 @@ GK_ATOM(onreset, "onreset")
GK_ATOM(onresuming, "onresuming")
GK_ATOM(onresize, "onresize")
GK_ATOM(onrtchange, "onrtchange")
GK_ATOM(onscanningstatechanged, "onscanningstatechanged")
GK_ATOM(onscostatuschanged, "onscostatuschanged")
GK_ATOM(onscroll, "onscroll")
GK_ATOM(onselect, "onselect")

View File

@ -202,6 +202,7 @@
#include "prrng.h"
#include "nsSandboxFlags.h"
#include "TimeChangeObserver.h"
#include "TouchCaret.h"
#include "mozilla/dom/AudioContext.h"
#include "mozilla/dom/BlobBinding.h"
#include "mozilla/dom/BrowserElementDictionariesBinding.h"
@ -9357,6 +9358,24 @@ nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, in
SelectionChangeEventInit init;
init.mBubbles = true;
if (aSel) {
bool isTouchCaretVisible = false;
bool isCollapsed = aSel->Collapsed();
nsIPresShell *shell = mDoc->GetShell();
if (shell) {
nsRefPtr<TouchCaret> touchCaret = shell->GetTouchCaret();
if (touchCaret) {
isTouchCaretVisible = touchCaret->GetVisibility();
}
}
// Dispatch selection change events when touch caret is visible even if selection
// is collapsed because it could be the shortcut mode, otherwise ignore this
// UpdateCommands
if (isCollapsed && !isTouchCaretVisible) {
return NS_OK;
}
Selection* selection = static_cast<Selection*>(aSel);
int32_t rangeCount = selection->GetRangeCount();
nsLayoutUtils::RectAccumulator accumulator;

View File

@ -9,6 +9,7 @@
#include "mozilla/AddonPathService.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#ifdef MOZ_B2G
@ -26,6 +27,7 @@
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsDocShell.h"
#include "nsDOMCID.h"
#include "nsError.h"
#include "nsGkAtoms.h"
@ -977,6 +979,47 @@ EventListenerManager::HandleEventSubType(Listener* aListener,
return result;
}
nsIDocShell*
EventListenerManager::GetDocShellForTarget()
{
nsCOMPtr<nsINode> node(do_QueryInterface(mTarget));
nsIDocument* doc = nullptr;
nsIDocShell* docShell = nullptr;
if (node) {
doc = node->OwnerDoc();
if (!doc->GetDocShell()) {
bool ignore;
nsCOMPtr<nsPIDOMWindow> window =
do_QueryInterface(doc->GetScriptHandlingObject(ignore));
if (window) {
doc = window->GetExtantDoc();
}
}
} else {
nsCOMPtr<nsPIDOMWindow> window = GetTargetAsInnerWindow();
if (window) {
doc = window->GetExtantDoc();
}
}
if (!doc) {
nsCOMPtr<DOMEventTargetHelper> helper(do_QueryInterface(mTarget));
if (helper) {
nsPIDOMWindow* window = helper->GetOwner();
if (window) {
doc = window->GetExtantDoc();
}
}
}
if (doc) {
docShell = doc->GetDocShell();
}
return docShell;
}
/**
* Causes a check for event listeners and processing by them if they exist.
* @param an event listener
@ -1028,10 +1071,28 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext,
}
}
// Maybe add a marker to the docshell's timeline, but only
// bother with all the logic if some docshell is recording.
nsCOMPtr<nsIDocShell> docShell;
if (mIsMainThreadELM &&
nsDocShell::gProfileTimelineRecordingsCount > 0 &&
listener->mListenerType != Listener::eNativeListener) {
docShell = GetDocShellForTarget();
if (docShell) {
nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
ds->AddProfileTimelineMarker("DOMEvent", TRACING_INTERVAL_START);
}
}
if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent,
aCurrentTarget))) {
aEvent->mFlags.mExceptionHasBeenRisen = true;
}
if (docShell) {
nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
ds->AddProfileTimelineMarker("DOMEvent", TRACING_INTERVAL_END);
}
}
}
}

View File

@ -16,6 +16,7 @@
#include "nsIDOMEventListener.h"
#include "nsTObserverArray.h"
class nsIDocShell;
class nsIDOMEvent;
class nsIEventListenerInfo;
class nsIScriptContext;
@ -420,6 +421,8 @@ protected:
nsIDOMEvent* aDOMEvent,
dom::EventTarget* aCurrentTarget);
nsIDocShell* GetDocShellForTarget();
/**
* Compile the "inline" event listener for aListener. The
* body of the listener can be provided in aBody; if this is null we

View File

@ -130,6 +130,7 @@ include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/docshell/base',
'/dom/base',
'/dom/html',
'/dom/settings',

View File

@ -297,6 +297,10 @@ const kEventConstructors = {
return new MozNFCPeerEvent(aName, aProps);
},
},
MozNFCTagEvent: { create: function (aName, aProps) {
return new MozNFCTagEvent(aName, aProps);
},
},
MozOtaStatusEvent: { create: function (aName, aProps) {
return new MozOtaStatusEvent(aName, aProps);
},
@ -468,6 +472,38 @@ const kEventConstructors = {
return new TransitionEvent(aName, aProps);
},
},
TVCurrentChannelChangedEvent: { create: function (aName, aProps) {
return new TVCurrentChannelChangedEvent(aName, aProps);
},
},
TVCurrentProgramChangedEvent: { create: function (aName, aProps) {
return new TVCurrentProgramChangedEvent(aName, aProps);
},
},
TVCurrentSourceChangedEvent: { create: function (aName, aProps) {
return new TVCurrentSourceChangedEvent(aName, aProps);
},
},
TVEITBroadcastedEvent: { create: function (aName, aProps) {
return new TVEITBroadcastedEvent(aName, aProps);
},
},
TVParentalControlChangedEvent: { create: function (aName, aProps) {
return new TVParentalControlChangedEvent(aName, aProps);
},
},
TVProtectionStateChangedEvent: { create: function (aName, aProps) {
return new TVProtectionStateChangedEvent(aName, aProps);
},
},
TVScanningStateChangedEvent: { create: function (aName, aProps) {
return new TVScanningStateChangedEvent(aName, aProps);
},
},
TVTunerChangedEvent: { create: function (aName, aProps) {
return new TVTunerChangedEvent(aName, aProps);
},
},
UIEvent: { create: function (aName, aProps) {
return new UIEvent(aName, aProps);
},

View File

@ -93,6 +93,7 @@ DIRS += [
'promise',
'smil',
'telephony',
'tv',
'voicemail',
'inputmethod',
'webidl',

View File

@ -28,6 +28,8 @@ GetNetUtilsLibHandle()
return sNetUtilsLib;
}
mozilla::Mutex NetUtils::sIfcMutex("NetUtils::sIfcMutex");
// static
void*
NetUtils::GetSharedLibrary()
@ -62,21 +64,20 @@ DEFINE_DLFUNC(ifc_remove_default_route, int32_t, const char*)
DEFINE_DLFUNC(dhcp_stop, int32_t, const char*)
NetUtils::NetUtils()
: mIfcMutex("NetUtils::mIfcMutex")
{
}
int32_t NetUtils::do_ifc_enable(const char *ifname)
{
USE_DLFUNC(ifc_enable)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_enable(ifname);
}
int32_t NetUtils::do_ifc_disable(const char *ifname)
{
USE_DLFUNC(ifc_disable)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_disable(ifname);
}
@ -88,7 +89,7 @@ int32_t NetUtils::do_ifc_configure(const char *ifname,
in_addr_t dns2)
{
USE_DLFUNC(ifc_configure)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
int32_t ret = ifc_configure(ifname, address, prefixLength, gateway, dns1, dns2);
return ret;
}
@ -97,7 +98,7 @@ int32_t NetUtils::do_ifc_reset_connections(const char *ifname,
const int32_t resetMask)
{
USE_DLFUNC(ifc_reset_connections)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_reset_connections(ifname, resetMask);
}
@ -105,7 +106,7 @@ int32_t NetUtils::do_ifc_set_default_route(const char *ifname,
in_addr_t gateway)
{
USE_DLFUNC(ifc_set_default_route)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_set_default_route(ifname, gateway);
}
@ -115,7 +116,7 @@ int32_t NetUtils::do_ifc_add_route(const char *ifname,
const char *gateway)
{
USE_DLFUNC(ifc_add_route)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_add_route(ifname, dst, prefixLength, gateway);
}
@ -125,21 +126,21 @@ int32_t NetUtils::do_ifc_remove_route(const char *ifname,
const char *gateway)
{
USE_DLFUNC(ifc_remove_route)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_remove_route(ifname, dst, prefixLength, gateway);
}
int32_t NetUtils::do_ifc_remove_host_routes(const char *ifname)
{
USE_DLFUNC(ifc_remove_host_routes)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_remove_host_routes(ifname);
}
int32_t NetUtils::do_ifc_remove_default_route(const char *ifname)
{
USE_DLFUNC(ifc_remove_default_route)
mozilla::MutexAutoLock lock(mIfcMutex);
mozilla::MutexAutoLock lock(sIfcMutex);
return ifc_remove_default_route(ifname);
}

View File

@ -61,7 +61,7 @@ public:
static int32_t SdkVersion();
private:
mozilla::Mutex mIfcMutex;
static mozilla::Mutex sIfcMutex;
};
// Defines a function type with the right arguments and return type.

View File

@ -408,12 +408,19 @@ NfcContentHelper.prototype = {
break;
case "NFC:DOMEvent":
switch (result.event) {
case NFC.NFC_PEER_EVENT_READY:
case NFC.PEER_EVENT_READY:
this.eventTarget.notifyPeerReady(result.sessionToken);
break;
case NFC.NFC_PEER_EVENT_LOST:
case NFC.PEER_EVENT_LOST:
this.eventTarget.notifyPeerLost(result.sessionToken);
break;
case NFC.TAG_EVENT_FOUND:
let event = new NfcTagEvent(result.techList);
this.eventTarget.notifyTagFound(result.sessionToken, event, result.records);
break;
case NFC.TAG_EVENT_LOST:
this.eventTarget.notifyTagLost(result.sessionToken);
break;
}
break;
}
@ -462,6 +469,15 @@ NfcContentHelper.prototype = {
},
};
function NfcTagEvent(techList) {
this.techList = techList;
}
NfcTagEvent.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsINfcTagEvent]),
techList: null
};
if (NFC_ENABLED) {
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NfcContentHelper]);
}

View File

@ -247,10 +247,25 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
// Remember the target that receives onpeerready.
this.currentPeer = target;
this.notifyDOMEvent(target, {event: NFC.NFC_PEER_EVENT_READY,
this.notifyDOMEvent(target, {event: NFC.PEER_EVENT_READY,
sessionToken: sessionToken});
},
onTagFound: function onTagFound(message) {
message.event = NFC.TAG_EVENT_FOUND;
for (let target of this.eventTargets) {
this.notifyDOMEvent(target, message);
}
delete message.event;
},
onTagLost: function onTagLost(sessionToken) {
for (let target of this.eventTargets) {
this.notifyDOMEvent(target, {event: NFC.TAG_EVENT_LOST,
sessionToken: sessionToken});
}
},
onPeerLost: function onPeerLost(sessionToken) {
if (!this.currentPeer) {
// The target is already killed.
@ -259,7 +274,7 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
// For peerlost, the message is delievered to the target which
// onpeerready has been called before.
this.notifyDOMEvent(this.currentPeer, {event: NFC.NFC_PEER_EVENT_LOST,
this.notifyDOMEvent(this.currentPeer, {event: NFC.PEER_EVENT_LOST,
sessionToken: sessionToken});
this.currentPeer = null;
},
@ -526,8 +541,13 @@ Nfc.prototype = {
SessionHelper.registerSession(message.sessionId, message.techList);
// Do not expose the actual session to the content
let sessionId = message.sessionId;
delete message.sessionId;
if (!SessionHelper.isP2PSession(sessionId)) {
gMessageManager.onTagFound(message);
}
gSystemMessenger.broadcastMessage("nfc-manager-tech-discovered", message);
break;
case "TechLostNotification":
@ -537,6 +557,8 @@ Nfc.prototype = {
message.sessionToken = SessionHelper.getToken(message.sessionId);
if (SessionHelper.isP2PSession(message.sessionId)) {
gMessageManager.onPeerLost(message.sessionToken);
} else {
gMessageManager.onTagLost(message.sessionToken);
}
SessionHelper.unregisterSession(message.sessionId);

View File

@ -86,8 +86,10 @@ this.TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
this.SETTING_NFC_DEBUG = "nfc.debugging.enabled";
this.NFC_PEER_EVENT_READY = 0x01;
this.NFC_PEER_EVENT_LOST = 0x02;
this.PEER_EVENT_READY = 0x01;
this.PEER_EVENT_LOST = 0x02;
this.TAG_EVENT_FOUND = 0x03;
this.TAG_EVENT_LOST = 0x04;
// Allow this file to be imported via Components.utils.import().
this.EXPORTED_SYMBOLS = Object.keys(this);

View File

@ -7,24 +7,52 @@
interface nsIVariant;
[scriptable, uuid(e81cc1ac-6f0b-4581-a9fb-7ee47ed0158e)]
[scriptable, uuid(44eb02f4-6eba-4c12-8ed1-c142513776c9)]
interface nsINfcTagEvent : nsISupports
{
readonly attribute nsIVariant techList;
};
[scriptable, uuid(8d77d653-f76a-4a21-ae3f-c4cc6074d8ec)]
interface nsINfcDOMEventTarget : nsISupports
{
/**
* Callback function used to notify tagfound.
*
* @param sessionToken
* SessionToken received from parent process
* @param event
* nsINfcTagFoundEvent received from parent process.
* @param ndefRecords
* NDEF records pre-read during tag-discovered.
*/
void notifyTagFound(in DOMString sessionToken,
in nsINfcTagEvent event,
in nsIVariant ndefRecords);
/**
* Callback function used to notify taglost.
*
* @param sessionToken
* SessionToken received from parent process
*/
void notifyTagLost(in DOMString sessionToken);
/**
* Callback function used to notify peerready.
*
* @param sessionToken
* SessionToken received from Chrome process
* SessionToken received from parent process
*/
void notifyPeerReady(in DOMString sessionToken);
void notifyPeerReady(in DOMString sessionToken);
/**
* Callback function used to notify peerlost.
*
* @param sessionToken
* SessionToken received from Chrome process
* SessionToken received from parent process
*/
void notifyPeerLost(in DOMString sessionToken);
void notifyPeerLost(in DOMString sessionToken);
};
[scriptable, uuid(cb9c934d-a7fa-422b-bcc1-4ac39741e6ec)]
@ -44,23 +72,23 @@ interface nsINfcContentHelper : nsISupports
nsIDOMDOMRequest connect(in nsIDOMWindow window, in unsigned long techType, in DOMString sessionToken);
nsIDOMDOMRequest close(in nsIDOMWindow window, in DOMString sessionToken);
/**
* Initiate Send file operation
*
* @param window
* Current window
*
* @param blob
* Raw data of the file to be sent. This object represents a file-like
* (nsIDOMFile) object of immutable, raw data. The blob data needs
* to be 'object wrapped' before calling this interface.
*
* @param sessionToken
* Current token
*
* Returns DOMRequest, if initiation of send file operation is successful
* then 'onsuccess' is called else 'onerror'
*/
/**
* Initiate send file operation.
*
* @param window
* Current window
*
* @param blob
* Raw data of the file to be sent. This object represents a file-like
* (nsIDOMFile) object of immutable, raw data. The blob data needs
* to be 'object wrapped' before calling this interface.
*
* @param sessionToken
* Current token
*
* Returns DOMRequest, if initiation of send file operation is successful
* then 'onsuccess' is called else 'onerror'
*/
nsIDOMDOMRequest sendFile(in nsIDOMWindow window,
in jsval blob,
in DOMString sessionToken);
@ -72,66 +100,66 @@ interface nsINfcContentHelper : nsISupports
*/
void registerEventTarget(in nsINfcDOMEventTarget target);
/**
* Register the given application id with Chrome process
*
* @param window
* Current window
*
* @param appId
* Application ID to be registered
*/
/**
* Register the given application id with parent process
*
* @param window
* Current window
*
* @param appId
* Application ID to be registered
*/
void registerTargetForPeerReady(in nsIDOMWindow window,
in unsigned long appId);
/**
* Unregister the given application id with Chrome process
*
* @param window
* Current window
*
* @param appId
* Application ID to be registered
*/
/**
* Unregister the given application id with parent process
*
* @param window
* Current window
*
* @param appId
* Application ID to be registered
*/
void unregisterTargetForPeerReady(in nsIDOMWindow window,
in unsigned long appId);
/**
* Checks if the given application's id is a registered peer target (with the Chrome process)
*
* @param window
* Current window
*
* @param appId
* Application ID to be updated with Chrome process
*
* Returns DOMRequest, if appId is registered then 'onsuccess' is called else 'onerror'
*/
/**
* Checks if the given application's id is a registered peer target (with the parent process)
*
* @param window
* Current window
*
* @param appId
* Application ID to be updated with parent process
*
* Returns DOMRequest, if appId is registered then 'onsuccess' is called else 'onerror'
*/
nsIDOMDOMRequest checkP2PRegistration(in nsIDOMWindow window, in unsigned long appId);
/**
* Notify the Chrome process that user has accepted to share nfc message on P2P UI
*
* @param window
* Current window
*
* @param appId
* Application ID that is capable of handling NFC_EVENT_PEER_READY event
*/
/**
* Notify the parent process that user has accepted to share nfc message on P2P UI
*
* @param window
* Current window
*
* @param appId
* Application ID that is capable of handling NFC_EVENT_PEER_READY event
*/
void notifyUserAcceptedP2P(in nsIDOMWindow window, in unsigned long appId);
/**
* Notify the status of sendFile operation to Chrome process
*
* @param window
* Current window
*
* @param status
* Status of sendFile operation
*
* @param requestId
* Request ID of SendFile DOM Request
*/
/**
* Notify the status of sendFile operation to parent process
*
* @param window
* Current window
*
* @param status
* Status of sendFile operation
*
* @param requestId
* Request ID of SendFile DOM Request
*/
void notifySendFileStatus(in nsIDOMWindow window,
in octet status,
in DOMString requestId);

View File

@ -125,6 +125,11 @@ mozNfc.prototype = {
init: function init(aWindow) {
debug("mozNfc init called");
this._window = aWindow;
this.defineEventHandlerGetterSetter("ontagfound");
this.defineEventHandlerGetterSetter("ontaglost");
this.defineEventHandlerGetterSetter("onpeerready");
this.defineEventHandlerGetterSetter("onpeerlost");
if (this._nfcContentHelper) {
this._nfcContentHelper.init(aWindow);
}
@ -162,14 +167,6 @@ mozNfc.prototype = {
return this._nfcContentHelper.powerOff(this._window);
},
getNFCTag: function getNFCTag(sessionToken) {
let obj = new MozNFCTag(this._window, sessionToken);
if (this._nfcContentHelper.checkSessionToken(sessionToken)) {
return this._window.MozNFCTag._create(this._window, obj);
}
return null;
},
getNFCPeer: function getNFCPeer(sessionToken) {
if (!sessionToken || !this._nfcContentHelper.checkSessionToken(sessionToken)) {
return null;
@ -184,22 +181,15 @@ mozNfc.prototype = {
return this.nfcObject.contentObject;
},
// get/set onpeerready
get onpeerready() {
return this.__DOM_IMPL__.getEventHandler("onpeerready");
},
set onpeerready(handler) {
this.__DOM_IMPL__.setEventHandler("onpeerready", handler);
},
// get/set onpeerlost
get onpeerlost() {
return this.__DOM_IMPL__.getEventHandler("onpeerlost");
},
set onpeerlost(handler) {
this.__DOM_IMPL__.setEventHandler("onpeerlost", handler);
defineEventHandlerGetterSetter: function defineEventHandlerGetterSetter(name) {
Object.defineProperty(this, name, {
get: function get() {
return this.__DOM_IMPL__.getEventHandler(name);
},
set: function set(handler) {
this.__DOM_IMPL__.setEventHandler(name, handler);
}
});
},
eventListenerWasAdded: function(eventType) {
@ -220,6 +210,54 @@ mozNfc.prototype = {
this._nfcContentHelper.unregisterTargetForPeerReady(this._window, appId);
},
notifyTagFound: function notifyTagFound(sessionToken, event, records) {
if (this.hasDeadWrapper()) {
dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
return;
}
if (!this.checkPermissions(["nfc-read", "nfc-write"])) {
return;
}
let tag = new MozNFCTag(this._window, sessionToken);
let tagContentObj = this._window.MozNFCTag._create(this._window, tag);
let length = records ? records.length : 0;
let ndefRecords = records ? [] : null;
for (let i = 0; i < length; i++) {
let record = records[i];
ndefRecords.push(new this._window.MozNDEFRecord({tnf: record.tnf,
type: record.type,
id: record.id,
payload: record.payload}));
}
let eventData = {
"tag": tagContentObj,
"ndefRecords": ndefRecords
};
debug("fire ontagfound " + sessionToken);
let tagEvent = new this._window.MozNFCTagEvent("tagfound", eventData);
this.__DOM_IMPL__.dispatchEvent(tagEvent);
},
notifyTagLost: function notifyTagLost(sessionToken) {
if (this.hasDeadWrapper()) {
dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
return;
}
if (!this.checkPermissions(["nfc-read", "nfc-write"])) {
return;
}
debug("fire ontaglost " + sessionToken);
let event = new this._window.Event("taglost");
this.__DOM_IMPL__.dispatchEvent(event);
},
notifyPeerReady: function notifyPeerReady(sessionToken) {
if (this.hasDeadWrapper()) {
dump("this._window or this.__DOM_IMPL__ is a dead wrapper.");
@ -259,6 +297,21 @@ mozNfc.prototype = {
this.__DOM_IMPL__.dispatchEvent(event);
},
checkPermissions: function checkPermissions(perms) {
let principal = this._window.document.nodePrincipal;
for (let perm of perms) {
let permValue =
Services.perms.testExactPermissionFromPrincipal(principal, perm);
if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) {
return true;
} else {
debug("doesn't have " + perm + " permission.");
}
}
return false;
},
hasDeadWrapper: function hasDeadWrapper() {
return Cu.isDeadWrapper(this._window) || Cu.isDeadWrapper(this.__DOM_IMPL__);
},

View File

@ -191,18 +191,6 @@ function testPeerInvalidToken() {
runNextTest();
}
/**
* Added for completeness in Bug 1042651,
* TODO: remove once Bug 963531 lands
*/
function testTagInvalidToken() {
log("testTagInvalidToken");
let tag = nfc.getNFCTag("fakeSessionToken");
is(tag, null, "NFCTag should be null on wrong session token");
runNextTest();
}
let tests = [
testPeerReady,
testGetNFCPeer,
@ -210,8 +198,7 @@ let tests = [
testPeerLostShouldBeCalled,
testPeerLostShouldNotBeCalled,
testPeerShouldThrow,
testPeerInvalidToken,
testTagInvalidToken
testPeerInvalidToken
];
SpecialPowers.pushPermissions(

View File

@ -55,7 +55,12 @@ function SettingsLock(aSettingsManager) {
"Settings:Clear:OK", "Settings:Clear:KO",
"Settings:Set:OK", "Settings:Set:KO",
"Settings:Finalize:OK", "Settings:Finalize:KO"]);
this.sendMessage("Settings:CreateLock", {lockID: this._id, isServiceLock: false});
let createLockPayload = {
lockID: this._id,
isServiceLock: false,
windowID: this._settingsManager.innerWindowID
};
this.sendMessage("Settings:CreateLock", createLockPayload);
Services.tm.currentThread.dispatch(this._closeHelper.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
// We only want to file closeHelper once per set of receiveMessage calls.
@ -255,7 +260,6 @@ function SettingsManager() {
this._callbacks = null;
this._isRegistered = false;
this._locks = [];
this._principal = null;
}
SettingsManager.prototype = {
@ -376,17 +380,18 @@ SettingsManager.prototype = {
if (DEBUG) debug("SettingsManager init");
mrm.registerStrongReporter(this);
cpmm.addMessageListener("Settings:Change:Return:OK", this);
this._window = aWindow;
this._principal = this._window.document.nodePrincipal;
Services.obs.addObserver(this, "dom-window-destroyed", false);
Services.obs.addObserver(this, "inner-window-destroyed", false);
let util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
this.innerWindowID = util.currentInnerWindowID;
this._window = aWindow;
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "dom-window-destroyed") {
let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
if (window == this._window) {
if (DEBUG) debug("Topic: " + aTopic);
if (aTopic === "inner-window-destroyed") {
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
if (wId === this.innerWindowID) {
if (DEBUG) debug("Received: inner-window-destroyed for valid innerWindowID=" + wId + ", cleanup.");
this.cleanup();
}
}
@ -417,21 +422,11 @@ SettingsManager.prototype = {
},
cleanup: function() {
Services.obs.removeObserver(this, "dom-window-destroyed");
Services.obs.removeObserver(this, "inner-window-destroyed");
cpmm.removeMessageListener("Settings:Change:Return:OK", this);
mrm.unregisterStrongReporter(this);
// At this point, the window is dying, so there's nothing left
// that we could do with our lock. Go ahead and run finalize on
// it to make sure changes are commited.
for (let i = 0; i < this._locks.length; ++i) {
if (DEBUG) debug("Lock alive at destroy, finalizing: " + this._locks[i]);
cpmm.sendAsyncMessage("Settings:Finalize",
{lockID: this._locks[i]},
undefined,
this._principal);
}
this.innerWindowID = null;
this._window = null;
this._innerWindowID = null;
},
classID: Components.ID("{c40b1c70-00fb-11e2-a21f-0800200c9a66}"),

View File

@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PermissionsTable.jsm");
const kXpcomShutdownObserverTopic = "xpcom-shutdown";
const kInnerWindowDestroyed = "inner-window-destroyed";
const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kSettingsReadSuffix = "-read";
const kSettingsWriteSuffix = "-write";
@ -71,12 +72,14 @@ let SettingsPermissions = {
};
function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock) {
function SettingsLockInfo(aDB, aMsgMgr, aLockID, aIsServiceLock, aWindowID) {
return {
// ID Shared with the object on the child side
lockID: aLockID,
// Is this a content lock or a settings service lock?
isServiceLock: aIsServiceLock,
// Which inner window ID
windowID: aWindowID,
// Tasks to be run once the lock is at the head of the queue
tasks: [],
// This is set to true once a transaction is ready to run, but is not at the
@ -171,6 +174,7 @@ let SettingsRequestManager = {
ppmm.addMessageListener(msgName, this);
}).bind(this));
Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
Services.obs.addObserver(this, kInnerWindowDestroyed, false);
},
_serializePreservingBinaries: function _serializePreservingBinaries(aObject) {
@ -648,7 +652,7 @@ let SettingsRequestManager = {
},
observe: function(aSubject, aTopic, aData) {
if (DEBUG) debug("observe");
if (DEBUG) debug("observe: " + aTopic);
switch (aTopic) {
case kXpcomShutdownObserverTopic:
this.messages.forEach((function(msgName) {
@ -657,6 +661,12 @@ let SettingsRequestManager = {
Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
ppmm = null;
break;
case kInnerWindowDestroyed:
let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
this.forceFinalizeChildLocksNonOOP(wId);
break;
default:
if (DEBUG) debug("Wrong observer topic: " + aTopic);
break;
@ -745,42 +755,59 @@ let SettingsRequestManager = {
}
},
removeMessageManager: function(aMsgMgr, aPrincipal) {
if (DEBUG) debug("Removing message manager");
hasLockFinalizeTask: function(lock) {
// Go in reverse order because finalize should be the last one
for (let task_index = lock.tasks.length; task_index >= 0; task_index--) {
if (lock.tasks[task_index]
&& lock.tasks[task_index].operation === "finalize") {
return true;
}
}
return false;
},
enqueueForceFinalize: function(lock, principal) {
if (!this.hasLockFinalizeTask(lock)) {
if (DEBUG) debug("Alive lock has pending tasks: " + lock.lockID);
this.queueTask("finalize", {lockID: lock.lockID}, principal).then(
function() {
if (DEBUG) debug("Alive lock " + lockId + " succeeded to force-finalize");
},
function(error) {
if (DEBUG) debug("Alive lock " + lockId + " failed to force-finalize due to error: " + error);
}
);
}
},
forceFinalizeChildLocksNonOOP: function(windowId) {
if (DEBUG) debug("Forcing finalize on child locks, non OOP");
for (let lockId of Object.keys(this.lockInfo)) {
let lock = this.lockInfo[lockId];
if (lock.windowID === windowId) {
let principal = this.mmPrincipals.get(lock._mm);
this.enqueueForceFinalize(lock, principal);
}
}
},
forceFinalizeChildLocksOOP: function(aMsgMgr, aPrincipal) {
if (DEBUG) debug("Forcing finalize on child locks, OOP");
let msgMgrPrincipal = this.mmPrincipals.get(aMsgMgr);
this.removeObserver(aMsgMgr);
let lockIDs = Object.keys(this.lockInfo);
for (let i in lockIDs) {
let lockId = lockIDs[i];
for (let lockId of Object.keys(this.lockInfo)) {
let lock = this.lockInfo[lockId];
if (lock._mm === aMsgMgr && msgMgrPrincipal === aPrincipal) {
let is_finalizing = false;
let task_index;
// Go in reverse order because finalize should be the last one
for (task_index = lock.tasks.length; task_index >= 0; task_index--) {
if (lock.tasks[task_index]
&& lock.tasks[task_index].operation === "finalize") {
is_finalizing = true;
break;
}
}
if (!is_finalizing) {
this.queueTask("finalize", {lockID: lockId}, aPrincipal).then(
function() {
if (DEBUG) debug("Lock " + lockId + " with dead message manager finalized");
},
function(error) {
if (DEBUG) debug("Lock " + lockId + " with dead message manager NOT FINALIZED due to error: " + error);
}
);
}
this.enqueueForceFinalize(lock, aPrincipal);
}
}
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage " + aMessage.name);
if (DEBUG) debug("receiveMessage " + aMessage.name + ": " + JSON.stringify(aMessage.data));
let msg = aMessage.data;
let mm = aMessage.target;
@ -832,7 +859,7 @@ let SettingsRequestManager = {
switch (aMessage.name) {
case "child-process-shutdown":
if (DEBUG) debug("Child process shutdown received.");
this.removeMessageManager(mm, aMessage.principal);
this.forceFinalizeChildLocksOOP(mm, aMessage.principal);
break;
case "Settings:RegisterForMessages":
if (!SettingsPermissions.hasSomeReadPermission(aMessage.principal)) {
@ -847,7 +874,7 @@ let SettingsRequestManager = {
this.removeObserver(mm);
break;
case "Settings:CreateLock":
if (DEBUG) debug("Received CreateLock for " + msg.lockID + " from " + aMessage.principal.origin);
if (DEBUG) debug("Received CreateLock for " + msg.lockID + " from " + aMessage.principal.origin + " window: " + msg.windowID);
// If we try to create a lock ID that collides with one
// already in the system, consider it a security violation and
// kill.
@ -857,7 +884,11 @@ let SettingsRequestManager = {
return;
}
this.settingsLockQueue.push(msg.lockID);
this.lockInfo[msg.lockID] = SettingsLockInfo(this.settingsDB, mm, msg.lockID, msg.isServiceLock);
this.lockInfo[msg.lockID] = SettingsLockInfo(this.settingsDB,
mm,
msg.lockID,
msg.isServiceLock,
msg.windowID);
break;
case "Settings:Get":
if (DEBUG) debug("Received getRequest from " + msg.lockID);

View File

@ -109,6 +109,11 @@ typedef Tuple3<NetdCommand*, CommandChain*, CommandCallback> QueueData;
} \
} while (0);
#define WARN_IF_FAILED(rv) do { \
if (SUCCESS != rv) { \
WARN("Error (%d) occurred in %s (%s:%d)", rv, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
} \
} while (0);
static NetworkUtils* gNetworkUtils;
static nsTArray<QueueData> gCommandQueue;
@ -1388,7 +1393,7 @@ CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
// Remove IPv4's default route.
RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(GET_CHAR(mOldIfname)));
// Remove IPv6's default route.
RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL));
WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL));
}
uint32_t length = aOptions.mGateways.Length();
@ -1444,9 +1449,9 @@ CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
return EAFNOSUPPORT;
}
RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
type == AF_INET ? "0.0.0.0" : "::",
0, autoGateway.get()));
WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mIfname),
type == AF_INET ? "0.0.0.0" : "::",
0, autoGateway.get()));
}
return SUCCESS;
@ -1543,7 +1548,7 @@ CommandResult NetworkUtils::removeNetworkRoute(NetworkParams& aOptions)
}
// Remove default route.
RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL));
WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), "::", 0, NULL));
// Remove subnet route.
RETURN_IF_FAILED(mNetUtils->do_ifc_remove_route(autoIfname.get(), subnetStr, prefixLength, NULL));

View File

@ -533,6 +533,10 @@ this.ICC_EF_MWIS = 0x6fca;
this.ICC_EF_CFIS = 0x6fcb;
this.ICC_EF_SPDI = 0x6fcd;
// CPHS files to be supported
this.ICC_EF_CPHS_INFO = 0x6f16; // CPHS Information
this.ICC_EF_CPHS_MBN = 0x6f17; // Mailbox Numbers
// CSIM files
this.ICC_EF_CSIM_IMSI_M = 0x6f22;
this.ICC_EF_CSIM_CDMAHOME = 0x6f28;
@ -1309,6 +1313,18 @@ this.GECKO_ICC_SERVICES = {
ENHANCED_PHONEBOOK: 6,
SPN: 17,
SDN: 18
},
// @see B.3.1.1 CPHS Information in CPHS Phase 2:
// Indicates which of the CPHS 'optional' data-fields are present in the SIM card:
// EF_CPHS_CSP, EF_CPHS_SST, EF_CPHS_MBN, EF_CPHS_ONSF, EF_CPHS_INFO_NUM
// Note: Mandatory EFs are: (B.3.1 Enhanced SIM Requirements)
// EF_CPHS_CFF, EF_CPHS_VMI, EF_CPHS_ONS, EF_CPHS_INFO
cphs: {
CSP: 1,
SST: 2,
MBN: 3,
ONSF: 4,
INFO_NUM: 5
}
};

View File

@ -12150,6 +12150,8 @@ ICCFileHelperObject.prototype = {
case ICC_EF_OPL:
case ICC_EF_PNN:
case ICC_EF_GID1:
case ICC_EF_CPHS_INFO:
case ICC_EF_CPHS_MBN:
return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
default:
return null;
@ -12176,6 +12178,12 @@ ICCFileHelperObject.prototype = {
case ICC_EF_PNN:
case ICC_EF_SMS:
case ICC_EF_GID1:
// CPHS spec was provided in 1997 based on SIM requirement, there is no
// detailed info about how these ICC_EF_CPHS_XXX are allocated in USIM.
// What we can do now is to follow what has been done in AOSP to have file
// path equal to MF_SIM/DF_GSM.
case ICC_EF_CPHS_INFO:
case ICC_EF_CPHS_MBN:
return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
default:
// The file ids in USIM phone book entries are decided by the
@ -13101,7 +13109,18 @@ SimRecordHelperObject.prototype = {
fetchSimRecords: function() {
this.context.RIL.getIMSI();
this.readAD();
this.readSST();
// CPHS was widely introduced in Europe during GSM(2G) era to provide easier
// access to carrier's core service like voicemail, call forwarding, manual
// PLMN selection, and etc.
// Addition EF like EF_CPHS_MBN, EF_CPHS_CPHS_CFF, EF_CPHS_VWI, etc are
// introduced to support these feature.
// In USIM, the replancement of these EFs are provided. (EF_MBDN, EF_MWIS, ...)
// However, some carriers in Europe still rely on these EFs.
this.readCphsInfo(() => this.readSST(),
(aErrorMsg) => {
this.context.debug("Failed to read CPHS_INFO: " + aErrorMsg);
this.readSST();
});
},
/**
@ -13432,6 +13451,13 @@ SimRecordHelperObject.prototype = {
this.readMBDN();
} else {
if (DEBUG) this.context.debug("MDN: MDN service is not available");
if (ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
// read CPHS_MBN in advance if MBDN is not available.
this.readCphsMBN();
} else {
if (DEBUG) this.context.debug("CPHS_MBN: CPHS_MBN service is not available");
}
}
if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
@ -13504,6 +13530,15 @@ SimRecordHelperObject.prototype = {
let RIL = this.context.RIL;
let contact =
this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
if ((!contact ||
((!contact.alphaId || contact.alphaId == "") &&
(!contact.number || contact.number == ""))) &&
this.context.ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
// read CPHS_MBN in advance if MBDN is invalid or empty.
this.readCphsMBN();
return;
}
if (!contact ||
(RIL.iccInfoPrivate.mbdn !== undefined &&
RIL.iccInfoPrivate.mbdn === contact.number)) {
@ -14081,6 +14116,117 @@ SimRecordHelperObject.prototype = {
callback: callback.bind(this)
});
},
/**
* Read CPHS Phase & Service Table from CPHS Info.
*
* @See B.3.1.1 CPHS Information in CPHS Phase 2.
*
* @param onsuccess Callback to be called when success.
* @param onerror Callback to be called when error.
*/
readCphsInfo: function(onsuccess, onerror) {
function callback() {
try {
let Buf = this.context.Buf;
let RIL = this.context.RIL;
let strLen = Buf.readInt32();
// Each octet is encoded into two chars.
let octetLen = strLen / 2;
let cphsInfo = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
Buf.readStringDelimiter(strLen);
if (DEBUG) {
let str = "";
for (let i = 0; i < cphsInfo.length; i++) {
str += cphsInfo[i] + ", ";
}
this.context.debug("CPHS INFO: " + str);
}
/**
* CPHS INFORMATION
*
* Byte 1: CPHS Phase
* 01 phase 1
* 02 phase 2
* etc.
*
* Byte 2: CPHS Service Table
* +----+----+----+----+----+----+----+----+
* | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
* +----+----+----+----+----+----+----+----+
* | ONSF | MBN | SST | CSP |
* | Phase 2 | ALL | Phase 1 | All |
* +----+----+----+----+----+----+----+----+
*
* Byte 3: CPHS Service Table continued
* +----+----+----+----+----+----+----+----+
* | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
* +----+----+----+----+----+----+----+----+
* | RFU | RFU | RFU | INFO_NUM|
* | | | | Phase 2 |
* +----+----+----+----+----+----+----+----+
*/
let cphsPhase = cphsInfo[0];
if (cphsPhase == 1) {
// Clear 'Phase 2 only' services.
cphsInfo[1] &= 0x3F;
// We don't know whether Byte 3 is available in CPHS phase 1 or not.
// Add boundary check before accessing it.
if (cphsInfo.length > 2) {
cphsInfo[2] = 0x00;
}
} else if (cphsPhase == 2) {
// Clear 'Phase 1 only' services.
cphsInfo[1] &= 0xF3;
} else {
throw new Error("Unknown CPHS phase: " + cphsPhase);
}
RIL.iccInfoPrivate.cphsSt = cphsInfo.subarray(1);
onsuccess();
} catch(e) {
onerror(e.toString());
}
}
this.context.ICCIOHelper.loadTransparentEF({
fileId: ICC_EF_CPHS_INFO,
callback: callback.bind(this),
onerror: onerror
});
},
/**
* Read CPHS MBN. (Mailbox Numbers)
*
* @See B.4.2.2 Voice Message Retrieval and Indicator Clearing
*/
readCphsMBN: function() {
function callback(options) {
let RIL = this.context.RIL;
let contact =
this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
if (!contact ||
(RIL.iccInfoPrivate.mbdn !== undefined &&
RIL.iccInfoPrivate.mbdn === contact.number)) {
return;
}
RIL.iccInfoPrivate.mbdn = contact.number;
if (DEBUG) {
this.context.debug("CPHS_MDN, alphaId=" + contact.alphaId +
" number=" + contact.number);
}
contact.rilMessageType = "iccmbdn";
RIL.sendChromeMessage(contact);
}
this.context.ICCIOHelper.loadLinearFixedEF({
fileId: ICC_EF_CPHS_MBN,
callback: callback.bind(this)
});
}
};
function RuimRecordHelperObject(aContext) {
@ -14662,8 +14808,8 @@ ICCUtilsHelperObject.prototype = {
*
* b1 = 0, service not allocated.
* 1, service allocated.
* b2 = 0, service not activatd.
* 1, service allocated.
* b2 = 0, service not activated.
* 1, service activated.
*
* @see 3GPP TS 51.011 10.3.7.
*/
@ -14689,8 +14835,6 @@ ICCUtilsHelperObject.prototype = {
*
* b1 = 0, service not avaiable.
* 1, service available.
* b2 = 0, service not avaiable.
* 1, service available.
*
* @see 3GPP TS 31.102 4.2.8.
*/
@ -14708,6 +14852,49 @@ ICCUtilsHelperObject.prototype = {
((serviceTable[index] & bitmask) !== 0);
},
/**
* Get whether specificed CPHS service is available.
*
* @param geckoService
* Service name like "MDN", etc.
*
* @return true if the service is enabled, false otherwise.
*/
isCphsServiceAvailable: function(geckoService) {
let RIL = this.context.RIL;
let serviceTable = RIL.iccInfoPrivate.cphsSt;
if (!(serviceTable instanceof Uint8Array)) {
return false;
}
/**
* Service id is valid in 1..N, and 2 bits are used to code each service.
*
* +----+-- --+----+----+
* | b8 | ... | b2 | b1 |
* +----+-- --+----+----+
*
* b1 = 0, service not allocated.
* 1, service allocated.
* b2 = 0, service not activated.
* 1, service activated.
*
* @See B.3.1.1 CPHS Information in CPHS Phase 2.
*/
let cphsService = GECKO_ICC_SERVICES.cphs[geckoService];
if (!cphsService) {
return false;
}
cphsService -= 1;
let index = Math.floor(cphsService / 4);
let bitmask = 2 << ((cphsService % 4) << 1);
return (index < serviceTable.length) &&
((serviceTable[index] & bitmask) !== 0);
},
/**
* Check if the string is of GSM default 7-bit coded alphabets with bit 8
* set to 0.

View File

@ -296,3 +296,31 @@ add_test(function test_get_network_name_from_icc() {
run_next_test();
});
/**
* Verify ICCUtilsHelper.isCphsServiceAvailable.
*/
add_test(function test_is_cphs_service_available() {
let worker = newUint8Worker();
let context = worker.ContextPool._contexts[0];
let ICCUtilsHelper = context.ICCUtilsHelper;
let RIL = context.RIL;
RIL.iccInfoPrivate.cphsSt = Uint8Array(2);
function test_table(cphsSt, geckoService) {
RIL.iccInfoPrivate.cphsSt.set(cphsSt);
for (let service in GECKO_ICC_SERVICES.cphs) {
do_check_eq(ICCUtilsHelper.isCphsServiceAvailable(service),
geckoService == service);
}
}
test_table([0x03, 0x00], "CSP");
test_table([0x0C, 0x00], "SST");
test_table([0x30, 0x00], "MBN");
test_table([0xC0, 0x00], "ONSF");
test_table([0x00, 0x03], "INFO_NUM");
run_next_test();
});

View File

@ -160,14 +160,14 @@ add_test(function test_reading_optional_efs() {
/**
* Verify fetchSimRecords.
*/
add_test(function test_fetch_sim_recodes() {
add_test(function test_fetch_sim_records() {
let worker = newWorker();
let context = worker.ContextPool._contexts[0];
let RIL = context.RIL;
let iccRecord = context.ICCRecordHelper;
let simRecord = context.SimRecordHelper;
function testFetchSimRecordes(expectCalled) {
function testFetchSimRecordes(expectCalled, expectCphsSuccess) {
let ifCalled = [];
RIL.getIMSI = function() {
@ -178,6 +178,15 @@ add_test(function test_fetch_sim_recodes() {
ifCalled.push("readAD");
};
simRecord.readCphsInfo = function(onsuccess, onerror) {
ifCalled.push("readCphsInfo");
if (expectCphsSuccess) {
onsuccess();
} else {
onerror();
}
};
simRecord.readSST = function() {
ifCalled.push("readSST");
};
@ -192,8 +201,9 @@ add_test(function test_fetch_sim_recodes() {
}
}
let expectCalled = ["getIMSI", "readAD", "readSST"];
testFetchSimRecordes(expectCalled);
let expectCalled = ["getIMSI", "readAD", "readCphsInfo", "readSST"];
testFetchSimRecordes(expectCalled, true);
testFetchSimRecordes(expectCalled, false);
run_next_test();
});
@ -782,7 +792,7 @@ add_test(function test_reading_img_length_error() {
let ril = context.RIL;
let buf = context.Buf;
let io = context.ICCIOHelper;
let test_data = [
{/* Offset length not enough, should be 4. */
img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x04, 0x00, 0x06],
@ -1292,3 +1302,204 @@ add_test(function test_reading_img_color() {
}
run_next_test();
});
/**
* Verify SimRecordHelper.readCphsInfo
*/
add_test(function test_read_cphs_info() {
let worker = newUint8Worker();
let context = worker.ContextPool._contexts[0];
let RIL = context.RIL;
let pduHelper = context.GsmPDUHelper;
let recordHelper = context.SimRecordHelper;
let buf = context.Buf;
let io = context.ICCIOHelper;
let cphsPDU = Uint8Array(3);
io.loadTransparentEF = function(options) {
if (cphsPDU) {
// Write data size
buf.writeInt32(cphsPDU.length * 2);
// Write CPHS INFO
for (let i = 0; i < cphsPDU.length; i++) {
pduHelper.writeHexOctet(cphsPDU[i]);
}
// Write string delimiter
buf.writeStringDelimiter(cphsPDU.length * 2);
if (options.callback) {
options.callback(options);
}
} else {
do_print("cphsPDU[] is not set.");
}
};
function do_test(cphsInfo, cphsSt) {
let onsuccess = false;
let onerror = false;
delete RIL.iccInfoPrivate.cphsSt;
cphsPDU.set(cphsInfo);
recordHelper.readCphsInfo(() => { onsuccess = true; },
() => { onerror = true; });
do_check_true((cphsSt) ? onsuccess : onerror);
do_check_false((cphsSt) ? onerror : onsuccess);
if (cphsSt) {
do_check_eq(RIL.iccInfoPrivate.cphsSt.length, cphsSt.length);
for (let i = 0; i < cphsSt.length; i++) {
do_check_eq(RIL.iccInfoPrivate.cphsSt[i], cphsSt[i]);
}
} else {
do_check_eq(RIL.iccInfoPrivate.cphsSt, cphsSt);
}
}
do_test([
0x01, // Phase 1
0xFF, // All available & activated
0x03 // All available & activated
],
[
0x3F, // All services except ONSF(bit 8-7) are available and activated.
0x00 // INFO_NUM shall not be available & activated.
]);
do_test([
0x02, // Phase 2
0xFF, // All available & activated
0x03 // All available & activated
],
[
0xF3, // All services except ONSF are available and activated.
0x03 // INFO_NUM shall not be available & activated.
]);
do_test([
0x03, // Phase 3
0xFF, // All available & activated
0x03 // All available & activated
],
undefined); // RIL.iccInfoPrivate.cphsSt shall be remained as 'undefined'.
run_next_test();
});
/**
* Verify SimRecordHelper.readMBDN/SimRecordHelper.readCphsMBN
*/
add_test(function test_read_voicemail_number() {
let worker = newUint8Worker();
let context = worker.ContextPool._contexts[0];
let RIL = context.RIL;
let pduHelper = context.GsmPDUHelper;
let recordHelper = context.SimRecordHelper;
let buf = context.Buf;
let io = context.ICCIOHelper;
let postedMessage;
worker.postMessage = function(message) {
postedMessage = message;
};
io.loadLinearFixedEF = function(options) {
let mbnData = [
0x56, 0x6F, 0x69, 0x63, 0x65, 0x6D, 0x61, 0x69,
0x6C, 0xFF, // Alpha Identifier: Voicemail
0x03, // Length of BCD number: 3
0x80, // TOA: Unknown
0x11, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, // Dialing Number: 111
0xFF, // Capability/Configuration Record Identifier
0xFF // Extension Record Identifier
];
// Write data size
buf.writeInt32(mbnData.length * 2);
// Write MBN
for (let i = 0; i < mbnData.length; i++) {
pduHelper.writeHexOctet(mbnData[i]);
}
// Write string delimiter
buf.writeStringDelimiter(mbnData.length * 2);
options.recordSize = mbnData.length;
if (options.callback) {
options.callback(options);
}
};
function do_test(funcName, msgCount) {
postedMessage = null;
delete RIL.iccInfoPrivate.mbdn;
recordHelper[funcName]();
do_check_eq("iccmbdn", postedMessage.rilMessageType);
do_check_eq("Voicemail", postedMessage.alphaId);
do_check_eq("111", postedMessage.number);
}
do_test("readMBDN");
do_test("readCphsMBN");
run_next_test();
});
/**
* Verify the recovery from SimRecordHelper.readCphsMBN() if MBDN is not valid
* or is empty after SimRecordHelper.readMBDN().
*/
add_test(function test_read_mbdn_recovered_from_cphs_mbn() {
let worker = newUint8Worker();
let context = worker.ContextPool._contexts[0];
let RIL = context.RIL;
let pduHelper = context.GsmPDUHelper;
let recordHelper = context.SimRecordHelper;
let iccUtilsHelper = context.ICCUtilsHelper;
let buf = context.Buf;
let io = context.ICCIOHelper;
io.loadLinearFixedEF = function(options) {
let mbnData = [
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
];
// Write data size
buf.writeInt32(mbnData.length * 2);
// Write MBN
for (let i = 0; i < mbnData.length; i++) {
pduHelper.writeHexOctet(mbnData[i]);
}
// Write string delimiter
buf.writeStringDelimiter(mbnData.length * 2);
options.recordSize = mbnData.length;
if (options.callback) {
options.callback(options);
}
};
iccUtilsHelper.isCphsServiceAvailable = function(geckoService) {
return geckoService == "MBN";
};
let isRecovered = false;
recordHelper.readCphsMBN = function(onComplete) {
isRecovered = true;
};
recordHelper.readMBDN();
do_check_eq(RIL.iccInfoPrivate.mbdn, undefined);
do_check_true(isRecovered);
run_next_test();
});

View File

@ -1233,6 +1233,24 @@ var interfaceNamesInGlobalScope =
{name: "TreeSelection", xbl: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
"TreeWalker",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVChannel", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVCurrentChannelChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVCurrentSourceChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVEITBroadcastedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVManager", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVProgram", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVScanningStateChangedEvent", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVSource", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "TVTuner", b2g: true, pref: "dom.tv.enabled", permission: "tv"},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "UDPMessageEvent", pref: "dom.udpsocket.enabled", permission: "udp-socket"},
// IMPORTANT: Do not change this list without review from a DOM peer!

196
dom/tv/FakeTVService.cpp Normal file
View File

@ -0,0 +1,196 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TVServiceRunnables.h"
#include "nsCOMPtr.h"
#include "nsIMutableArray.h"
#include "nsServiceManagerUtils.h"
#include "FakeTVService.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS(FakeTVService, nsITVService)
/* virtual */ NS_IMETHODIMP
FakeTVService::GetSourceListener(nsITVSourceListener** aSourceListener)
{
*aSourceListener = mSourceListener;
NS_ADDREF(*aSourceListener);
return NS_OK;
}
/* virtual */ NS_IMETHODIMP
FakeTVService::SetSourceListener(nsITVSourceListener* aSourceListener)
{
mSourceListener = aSourceListener;
return NS_OK;
}
/* virtual */ NS_IMETHODIMP
FakeTVService::GetTuners(nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIMutableArray> tunerDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!tunerDataList) {
return NS_ERROR_OUT_OF_MEMORY;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, tunerDataList);
return NS_DispatchToCurrentThread(runnable);
}
/* virtual */ NS_IMETHODIMP
FakeTVService::SetSource(const nsAString& aTunerId,
const nsAString& aSourceType,
nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, nullptr);
return NS_DispatchToCurrentThread(runnable);
}
/* virtual */ NS_IMETHODIMP
FakeTVService::StartScanningChannels(const nsAString& aTunerId,
const nsAString& aSourceType,
nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, nullptr);
return NS_DispatchToCurrentThread(runnable);
}
/* virtual */ NS_IMETHODIMP
FakeTVService::StopScanningChannels(const nsAString& aTunerId,
const nsAString& aSourceType,
nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, nullptr);
return NS_DispatchToCurrentThread(runnable);
}
/* virtual */ NS_IMETHODIMP
FakeTVService::ClearScannedChannelsCache()
{
// TODO Implement in follow-up patches.
return NS_OK;
}
/* virtual */ NS_IMETHODIMP
FakeTVService::SetChannel(const nsAString& aTunerId,
const nsAString& aSourceType,
const nsAString& aChannelNumber,
nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIMutableArray> channelDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!channelDataList) {
return NS_ERROR_OUT_OF_MEMORY;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, channelDataList);
return NS_DispatchToCurrentThread(runnable);
}
/* virtual */ NS_IMETHODIMP
FakeTVService::GetChannels(const nsAString& aTunerId,
const nsAString& aSourceType,
nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIMutableArray> channelDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!channelDataList) {
return NS_ERROR_OUT_OF_MEMORY;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, channelDataList);
return NS_DispatchToCurrentThread(runnable);
}
/* virtual */ NS_IMETHODIMP
FakeTVService::GetPrograms(const nsAString& aTunerId,
const nsAString& aSourceType,
const nsAString& aChannelNumber,
uint64_t startTime,
uint64_t endTime,
nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIMutableArray> programDataList = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!programDataList) {
return NS_ERROR_OUT_OF_MEMORY;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, programDataList);
return NS_DispatchToCurrentThread(runnable);
}
/* virtual */ NS_IMETHODIMP
FakeTVService::GetOverlayId(const nsAString& aTunerId,
nsITVServiceCallback* aCallback)
{
if (!aCallback) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIMutableArray> overlayIds = do_CreateInstance(NS_ARRAY_CONTRACTID);
if (!overlayIds) {
return NS_ERROR_OUT_OF_MEMORY;
}
// TODO Implement in follow-up patches.
nsCOMPtr<nsIRunnable> runnable =
new TVServiceNotifyRunnable(aCallback, overlayIds);
return NS_DispatchToCurrentThread(runnable);
}
} // namespace dom
} // namespace mozilla

40
dom/tv/FakeTVService.h Normal file
View File

@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_FakeTVService_h
#define mozilla_dom_FakeTVService_h
#include "nsCOMPtr.h"
#include "nsITVService.h"
#define FAKE_TV_SERVICE_CONTRACTID \
"@mozilla.org/tv/faketvservice;1"
#define FAKE_TV_SERVICE_CID \
{ 0x60fb3c53, 0x017f, 0x4340, { 0x91, 0x1b, 0xd5, 0x5c, 0x31, 0x28, 0x88, 0xb6 } }
namespace mozilla {
namespace dom {
class FakeTVService MOZ_FINAL : public nsITVService
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSITVSERVICE
FakeTVService() {}
// TODO More members might be added in follow-up patches to help testing.
private:
~FakeTVService() {}
nsCOMPtr<nsITVSourceListener> mSourceListener;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_FakeTVService_h

213
dom/tv/TVChannel.cpp Normal file
View File

@ -0,0 +1,213 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TVServiceCallbacks.h"
#include "mozilla/dom/TVServiceFactory.h"
#include "mozilla/dom/TVSource.h"
#include "mozilla/dom/TVTuner.h"
#include "mozilla/dom/TVUtils.h"
#include "nsITVService.h"
#include "nsServiceManagerUtils.h"
#include "TVChannel.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_INHERITED(TVChannel, DOMEventTargetHelper,
mTVService, mSource)
NS_IMPL_ADDREF_INHERITED(TVChannel, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(TVChannel, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TVChannel)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
TVChannel::TVChannel(nsPIDOMWindow* aWindow,
TVSource* aSource)
: DOMEventTargetHelper(aWindow)
, mSource(aSource)
{
MOZ_ASSERT(mSource);
}
TVChannel::~TVChannel()
{
}
/* static */ already_AddRefed<TVChannel>
TVChannel::Create(nsPIDOMWindow* aWindow,
TVSource* aSource,
nsITVChannelData* aData)
{
nsRefPtr<TVChannel> channel = new TVChannel(aWindow, aSource);
return (channel->Init(aData)) ? channel.forget() : nullptr;
}
bool
TVChannel::Init(nsITVChannelData* aData)
{
NS_ENSURE_TRUE(aData, false);
nsString channelType;
aData->GetType(channelType);
mType = ToTVChannelType(channelType);
if (NS_WARN_IF(mType == TVChannelType::EndGuard_)) {
return false;
}
aData->GetNetworkId(mNetworkId);
aData->GetTransportStreamId(mTransportStreamId);
aData->GetServiceId(mServiceId);
aData->GetName(mName);
aData->GetNumber(mNumber);
aData->GetIsEmergency(&mIsEmergency);
aData->GetIsFree(&mIsFree);
mTVService = TVServiceFactory::AutoCreateTVService();
NS_ENSURE_TRUE(mTVService, false);
return true;
}
/* virtual */ JSObject*
TVChannel::WrapObject(JSContext* aCx)
{
return TVChannelBinding::Wrap(aCx, this);
}
nsresult
TVChannel::DispatchTVEvent(nsIDOMEvent* aEvent)
{
return DispatchTrustedEvent(aEvent);
}
already_AddRefed<Promise>
TVChannel::GetPrograms(const TVGetProgramsOptions& aOptions, ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
MOZ_ASSERT(global);
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsRefPtr<TVTuner> tuner = mSource->Tuner();
nsString tunerId;
tuner->GetId(tunerId);
uint64_t startTime = aOptions.mStartTime.WasPassed() ?
aOptions.mStartTime.Value() :
PR_Now();
uint64_t endTime = aOptions.mDuration.WasPassed() ?
(startTime + aOptions.mDuration.Value()) :
std::numeric_limits<uint64_t>::max();
nsCOMPtr<nsITVServiceCallback> callback =
new TVServiceProgramGetterCallback(this, promise, false);
nsresult rv =
mTVService->GetPrograms(tunerId,
ToTVSourceTypeStr(mSource->Type()),
mNumber,
startTime,
endTime,
callback);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
return promise.forget();
}
void
TVChannel::GetNetworkId(nsAString& aNetworkId) const
{
aNetworkId = mNetworkId;
}
void
TVChannel::GetTransportStreamId(nsAString& aTransportStreamId) const
{
aTransportStreamId = mTransportStreamId;
}
void
TVChannel::GetServiceId(nsAString& aServiceId) const
{
aServiceId = mServiceId;
}
already_AddRefed<TVSource>
TVChannel::Source() const
{
nsRefPtr<TVSource> source = mSource;
return source.forget();
}
TVChannelType
TVChannel::Type() const
{
return mType;
}
void
TVChannel::GetName(nsAString& aName) const
{
aName = mName;
}
void
TVChannel::GetNumber(nsAString& aNumber) const
{
aNumber = mNumber;
}
bool
TVChannel::IsEmergency() const
{
return mIsEmergency;
}
bool
TVChannel::IsFree() const
{
return mIsFree;
}
already_AddRefed<Promise>
TVChannel::GetCurrentProgram(ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
MOZ_ASSERT(global);
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
nsRefPtr<TVTuner> tuner = mSource->Tuner();
nsString tunerId;
tuner->GetId(tunerId);
// Get only one program from now on.
nsCOMPtr<nsITVServiceCallback> callback =
new TVServiceProgramGetterCallback(this, promise, true);
nsresult rv =
mTVService->GetPrograms(tunerId,
ToTVSourceTypeStr(mSource->Type()),
mNumber,
PR_Now(),
std::numeric_limits<uint64_t>::max(),
callback);
if (NS_WARN_IF(NS_FAILED(rv))) {
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
return promise.forget();
}
} // namespace dom
} // namespace mozilla

88
dom/tv/TVChannel.h Normal file
View File

@ -0,0 +1,88 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_TVChannel_h
#define mozilla_dom_TVChannel_h
#include "mozilla/DOMEventTargetHelper.h"
// Include TVChannelBinding.h since enum TVChannelType can't be forward declared.
#include "mozilla/dom/TVChannelBinding.h"
class nsITVChannelData;
class nsITVService;
namespace mozilla {
namespace dom {
class Promise;
class TVProgram;
class TVSource;
class TVChannel MOZ_FINAL : public DOMEventTargetHelper
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(TVChannel, DOMEventTargetHelper)
static already_AddRefed<TVChannel> Create(nsPIDOMWindow* aWindow,
TVSource* aSource,
nsITVChannelData* aData);
// WebIDL (internal functions)
virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
nsresult DispatchTVEvent(nsIDOMEvent* aEvent);
// WebIDL (public APIs)
already_AddRefed<Promise> GetPrograms(const TVGetProgramsOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise> GetCurrentProgram(ErrorResult& aRv);
void GetNetworkId(nsAString& aNetworkId) const;
void GetTransportStreamId(nsAString& aTransportStreamId) const;
void GetServiceId(nsAString& aServiceId) const;
already_AddRefed<TVSource> Source() const;
TVChannelType Type() const;
void GetName(nsAString& aName) const;
void GetNumber(nsAString& aNumber) const;
bool IsEmergency() const;
bool IsFree() const;
private:
TVChannel(nsPIDOMWindow* aWindow,
TVSource* aSource);
~TVChannel();
bool Init(nsITVChannelData* aData);
nsCOMPtr<nsITVService> mTVService;
nsRefPtr<TVSource> mSource;
nsString mNetworkId;
nsString mTransportStreamId;
nsString mServiceId;
TVChannelType mType;
nsString mNumber;
nsString mName;
bool mIsEmergency;
bool mIsFree;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_TVChannel_h

105
dom/tv/TVListeners.cpp Normal file
View File

@ -0,0 +1,105 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/TVSource.h"
#include "mozilla/dom/TVTuner.h"
#include "mozilla/dom/TVUtils.h"
#include "TVListeners.h"
namespace mozilla {
namespace dom {
NS_IMPL_ISUPPORTS(TVSourceListener, nsITVSourceListener)
void
TVSourceListener::RegisterSource(TVSource* aSource)
{
nsString tunerId;
nsRefPtr<TVTuner> tuner = aSource->Tuner();
tuner->GetId(tunerId);
nsRefPtrHashtable<nsStringHashKey, TVSource>* tunerSources = nullptr;
if (!mSources.Get(tunerId, &tunerSources)) {
tunerSources = new nsRefPtrHashtable<nsStringHashKey, TVSource>();
mSources.Put(tunerId, tunerSources);
}
nsString sourceType = ToTVSourceTypeStr(aSource->Type());
tunerSources->Put(sourceType, aSource);
}
void
TVSourceListener::UnregisterSource(TVSource* aSource)
{
nsString tunerId;
nsRefPtr<TVTuner> tuner = aSource->Tuner();
tuner->GetId(tunerId);
nsRefPtrHashtable<nsStringHashKey, TVSource>* tunerSources = nullptr;
if (!mSources.Get(tunerId, &tunerSources)) {
return;
}
nsString sourceType = ToTVSourceTypeStr(aSource->Type());
tunerSources->Remove(sourceType);
}
/* virtual */ NS_IMETHODIMP
TVSourceListener::NotifyChannelScanned(const nsAString& aTunerId,
const nsAString& aSourceType,
nsITVChannelData* aChannelData)
{
nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
source->NotifyChannelScanned(aChannelData);
return NS_OK;
}
/* virtual */ NS_IMETHODIMP
TVSourceListener::NotifyChannelScanComplete(const nsAString& aTunerId,
const nsAString& aSourceType)
{
nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
source->NotifyChannelScanComplete();
return NS_OK;
}
/* virtual */ NS_IMETHODIMP
TVSourceListener::NotifyChannelScanStopped(const nsAString& aTunerId,
const nsAString& aSourceType)
{
nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
source->NotifyChannelScanStopped();
return NS_OK;
}
/* virtual */ NS_IMETHODIMP
TVSourceListener::NotifyEITBroadcasted(const nsAString& aTunerId,
const nsAString& aSourceType,
nsITVChannelData* aChannelData,
nsITVProgramData** aProgramDataList,
const uint32_t aCount)
{
nsRefPtr<TVSource> source = GetSource(aTunerId, aSourceType);
source->NotifyEITBroadcasted(aChannelData, aProgramDataList, aCount);
return NS_OK;
}
already_AddRefed<TVSource>
TVSourceListener::GetSource(const nsAString& aTunerId,
const nsAString& aSourceType)
{
nsRefPtrHashtable<nsStringHashKey, TVSource>* tunerSources = nullptr;
if (!mSources.Get(aTunerId, &tunerSources)) {
return nullptr;
}
nsRefPtr<TVSource> source;
tunerSources->Get(aSourceType, getter_AddRefs(source));
return source.forget();
}
} // namespace dom
} // namespace mozilla

Some files were not shown because too many files have changed in this diff Show More