This commit is contained in:
Ryan VanderMeulen 2014-10-29 16:30:13 -04:00
Родитель c54858f02a 4f43d43505
Коммит a70c03ab03
66 изменённых файлов: 1867 добавлений и 785 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);
}
},

Просмотреть файл

@ -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
}));
}

Просмотреть файл

@ -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
}));
}

Просмотреть файл

@ -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
}),

Просмотреть файл

@ -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"));
},
/**

Просмотреть файл

@ -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

Просмотреть файл

@ -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() {

Просмотреть файл

@ -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() {

Просмотреть файл

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

Просмотреть файл

@ -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();
}

Просмотреть файл

@ -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();
}

Просмотреть файл

@ -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();
}

Просмотреть файл

@ -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]

Просмотреть файл

@ -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
}
},

Просмотреть файл

@ -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')

Просмотреть файл

@ -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;"

Просмотреть файл

@ -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

Просмотреть файл

@ -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;"

Просмотреть файл

@ -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);

Просмотреть файл

@ -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")

Просмотреть файл

@ -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…

Двоичные данные
browser/themes/linux/Toolbar-inverted.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 11 KiB

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
browser/themes/linux/Toolbar.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 14 KiB

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
browser/themes/linux/downloads/download-glow.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 445 B

Просмотреть файл

@ -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 ***/

Просмотреть файл

@ -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)

Двоичные данные
browser/themes/osx/Toolbar-inverted.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 27 KiB

После

Ширина:  |  Высота:  |  Размер: 29 KiB

Двоичные данные
browser/themes/osx/Toolbar-inverted@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 65 KiB

После

Ширина:  |  Высота:  |  Размер: 72 KiB

Двоичные данные
browser/themes/osx/Toolbar-yosemite.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 17 KiB

После

Ширина:  |  Высота:  |  Размер: 18 KiB

Двоичные данные
browser/themes/osx/Toolbar-yosemite@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 39 KiB

После

Ширина:  |  Высота:  |  Размер: 43 KiB

Двоичные данные
browser/themes/osx/Toolbar.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 26 KiB

После

Ширина:  |  Высота:  |  Размер: 28 KiB

Двоичные данные
browser/themes/osx/Toolbar@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 70 KiB

После

Ширина:  |  Высота:  |  Размер: 78 KiB

Двоичные данные
browser/themes/osx/downloads/download-glow.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 676 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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)

Двоичные данные
browser/themes/windows/Toolbar-XP.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 16 KiB

После

Ширина:  |  Высота:  |  Размер: 17 KiB

Двоичные данные
browser/themes/windows/Toolbar-aero.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 15 KiB

После

Ширина:  |  Высота:  |  Размер: 17 KiB

Двоичные данные
browser/themes/windows/Toolbar-inverted.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 11 KiB

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
browser/themes/windows/Toolbar-lunaSilver.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 15 KiB

После

Ширина:  |  Высота:  |  Размер: 17 KiB

Двоичные данные
browser/themes/windows/Toolbar.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 6.0 KiB

После

Ширина:  |  Высота:  |  Размер: 6.5 KiB

Просмотреть файл

@ -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 ***/

Просмотреть файл

@ -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)

Просмотреть файл

@ -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;
}
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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);
});
}

Просмотреть файл

@ -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);
});
}

Просмотреть файл

@ -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>

Просмотреть файл

@ -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);
}
}
}
}

Просмотреть файл

@ -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

Просмотреть файл

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

Просмотреть файл

@ -2539,7 +2539,8 @@ public class BrowserApp extends GeckoApp
@Override
public void openOptionsMenu() {
// Disable menu access (for hardware buttons) when the software menu button is inaccessible.
if (mBrowserToolbar.isEditing()) {
// Note that the software button is always accessible on new tablet.
if (mBrowserToolbar.isEditing() && !NewTabletUI.isEnabled(this)) {
return;
}

Просмотреть файл

@ -37,7 +37,7 @@ import android.util.Log;
class ChromeCast implements GeckoMediaPlayer {
private static final boolean SHOW_DEBUG = false;
static final String MIRROR_RECEIVER_APP_ID = "5F72F863";
static final String MIRROR_RECEIVER_APP_ID = "08FF1091";
private final Context context;
private final RouteInfo route;

Просмотреть файл

@ -188,7 +188,9 @@ public class TabStripItemView extends ThemedLinearLayout
// The surrounding tab strip dividers need to be hidden
// when a tab item enters pressed state.
View parent = (View) getParent();
parent.invalidate();
if (parent != null) {
parent.invalidate();
}
}
void updateFromTab(Tab tab) {

Просмотреть файл

@ -13,6 +13,8 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import java.util.ArrayList;
import java.util.List;
@ -73,9 +75,21 @@ public class TabStripView extends TwoWayView {
final int selected = getPositionForSelectedTab();
if (selected != -1) {
updateSelectedStyle(selected);
ensurePositionIsVisible(selected);
}
}
private void ensurePositionIsVisible(final int position) {
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
smoothScrollToPosition(position);
return true;
}
});
}
private int getCheckedIndex(int childCount) {
final int checkedIndex = getCheckedItemPosition() - getFirstVisiblePosition();
if (checkedIndex < 0 || checkedIndex > childCount - 1) {

Просмотреть файл

@ -10,6 +10,7 @@ import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
/**
@ -20,8 +21,18 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
private static final int FORWARD_ANIMATION_DURATION = 450;
private enum ForwardButtonState {
HIDDEN,
DISPLAYED,
TRANSITIONING,
}
private final int forwardButtonTranslationWidth;
private ForwardButtonState forwardButtonState;
private boolean backButtonWasEnabledOnStartEditing;
public BrowserToolbarNewTablet(final Context context, final AttributeSet attrs) {
super(context, attrs);
@ -32,6 +43,19 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
// so translate it for start of the expansion animation; future
// iterations translate it to this position when hiding and will already be set up.
ViewHelper.setTranslationX(forwardButton, -forwardButtonTranslationWidth);
// TODO: Move this to *TabletBase when old tablet is removed.
// We don't want users clicking the forward button in transitions, but we don't want it to
// look disabled to avoid flickering complications (e.g. disabled in editing mode), so undo
// the work of the super class' constructor.
setButtonEnabled(forwardButton, true);
updateForwardButtonState(ForwardButtonState.HIDDEN);
}
private void updateForwardButtonState(final ForwardButtonState state) {
forwardButtonState = state;
forwardButton.setEnabled(forwardButtonState == ForwardButtonState.DISPLAYED);
}
@Override
@ -52,14 +76,11 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
@Override
protected void animateForwardButton(final ForwardButtonAnimation animation) {
final boolean willShowForward = (animation == ForwardButtonAnimation.SHOW);
// If we're not in the appropriate state to start a particular animation,
// then we must be in the opposite state and do not need to animate.
final float forwardOffset = ViewHelper.getTranslationX(forwardButton);
if ((forwardOffset >= 0 && willShowForward) ||
forwardOffset < 0 && !willShowForward) {
if ((forwardButtonState != ForwardButtonState.HIDDEN && willShowForward) ||
(forwardButtonState != ForwardButtonState.DISPLAYED && !willShowForward)) {
return;
}
updateForwardButtonState(ForwardButtonState.TRANSITIONING);
// We want the forward button to show immediately when switching tabs
final PropertyAnimator forwardAnim =
@ -88,6 +109,7 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
@Override
public void onPropertyAnimationEnd() {
final ForwardButtonState newForwardButtonState;
if (willShowForward) {
// Increase the margins to ensure the text does not run outside the View.
MarginLayoutParams layoutParams =
@ -96,9 +118,14 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
layoutParams.leftMargin = forwardButtonTranslationWidth;
newForwardButtonState = ForwardButtonState.DISPLAYED;
} else {
newForwardButtonState = ForwardButtonState.HIDDEN;
}
urlDisplayLayout.finishForwardAnimation();
updateForwardButtonState(newForwardButtonState);
requestLayout();
}
@ -133,4 +160,38 @@ class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
public void triggerTabsPanelTransition(final PropertyAnimator animator, final boolean areTabsShown) {
// Do nothing.
}
@Override
public void startEditing(final String url, final PropertyAnimator animator) {
// We already know the forward button state - no need to store it here.
backButtonWasEnabledOnStartEditing = backButton.isEnabled();
setButtonEnabled(backButton, false);
setButtonEnabled(forwardButton, false);
super.startEditing(url, animator);
}
@Override
public String commitEdit() {
stopEditingNewTablet();
return super.commitEdit();
}
@Override
public String cancelEdit() {
stopEditingNewTablet();
setButtonEnabled(backButton, backButtonWasEnabledOnStartEditing);
updateForwardButtonState(forwardButtonState);
return super.cancelEdit();
}
private void stopEditingNewTablet() {
// Undo the changes caused by calling setButtonEnabled in startEditing.
// Note that this should be called first so the enabled state of the
// forward button is set to the proper value.
setButtonEnabled(forwardButton, true);
}
}

Просмотреть файл

@ -7,6 +7,7 @@ package org.mozilla.gecko.toolbar;
import java.util.Arrays;
import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
@ -103,10 +104,17 @@ abstract class BrowserToolbarTabletBase extends BrowserToolbar {
setButtonEnabled(backButton, canDoBack(tab));
final boolean isForwardEnabled = canDoForward(tab);
if (forwardButton.isEnabled() != isForwardEnabled) {
// Save the state on the forward button so that we can skip animations
// when there's nothing to change
setButtonEnabled(forwardButton, isForwardEnabled);
if (!NewTabletUI.isEnabled(getContext())) {
if (forwardButton.isEnabled() != isForwardEnabled) {
// Save the state on the forward button so that we can skip animations
// when there's nothing to change
setButtonEnabled(forwardButton, isForwardEnabled);
animateForwardButton(
isForwardEnabled ? ForwardButtonAnimation.SHOW : ForwardButtonAnimation.HIDE);
}
} else {
// I don't know the implications of changing this code on old tablet
// (and no one is going to thoroughly test it) so duplicate the code.
animateForwardButton(
isForwardEnabled ? ForwardButtonAnimation.SHOW : ForwardButtonAnimation.HIDE);
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -109,6 +109,7 @@ this.HawkClient.prototype = {
code: restResponse.status,
errno: restResponse.status
};
errorObj.toString = function() this.code + ": " + this.message;
let retryAfter = restResponse.headers && restResponse.headers["retry-after"];
retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter;
if (retryAfter) {

Просмотреть файл

@ -209,7 +209,12 @@ let MemoryActor = protocol.ActorClass({
* An object of the form:
*
* {
* allocations: [<index into "frames" below> ...],
* allocations: [<index into "frames" below>, ...],
* allocationsTimestamps: [
* <timestamp for allocations[0]>,
* <timestamp for allocations[1]>,
* ...
* ],
* frames: [
* {
* line: <line number for this frame>,
@ -217,7 +222,7 @@ let MemoryActor = protocol.ActorClass({
* source: <filename string for this frame>,
* functionDisplayName: <this frame's inferred function name function or null>,
* parent: <index into "frames">
* }
* },
* ...
* ],
* counts: [
@ -228,6 +233,8 @@ let MemoryActor = protocol.ActorClass({
* ]
* }
*
* The timestamps' unit is microseconds since the epoch.
*
* Subsequent `getAllocations` request within the same recording and
* tab navigation will always place the same stack frames at the same
* indices as previous `getAllocations` requests in the same
@ -255,10 +262,11 @@ let MemoryActor = protocol.ActorClass({
getAllocations: method(expectState("attached", function() {
const allocations = this.dbg.memory.drainAllocationsLog()
const packet = {
allocations: []
allocations: [],
allocationsTimestamps: []
};
for (let { frame: stack } of allocations) {
for (let { frame: stack, timestamp } of allocations) {
if (stack && Cu.isDeadWrapper(stack)) {
continue;
}
@ -275,6 +283,7 @@ let MemoryActor = protocol.ActorClass({
this._countFrame(waived);
packet.allocations.push(this._framesToIndices.get(waived));
packet.allocationsTimestamps.push(timestamp);
}
// Now that we are guaranteed to have a form for every frame, we know the

Просмотреть файл

@ -1581,6 +1581,13 @@ Object.defineProperty(BrowserTabActor.prototype, "docShell", {
Object.defineProperty(BrowserTabActor.prototype, "title", {
get: function() {
// On Fennec, we can check the session store data for zombie tabs
if (this._browser.__SS_restore) {
let sessionStore = this._browser.__SS_data;
// Get the last selected entry
let entry = sessionStore.entries[sessionStore.index - 1];
return entry.title;
}
let title = this.contentDocument.title || this._browser.contentTitle;
// If contentTitle is empty (e.g. on a not-yet-restored tab), but there is a
// tabbrowser (i.e. desktop Firefox, but not Fennec), we can use the label
@ -1597,6 +1604,24 @@ Object.defineProperty(BrowserTabActor.prototype, "title", {
configurable: false
});
Object.defineProperty(BrowserTabActor.prototype, "url", {
get: function() {
// On Fennec, we can check the session store data for zombie tabs
if (this._browser.__SS_restore) {
let sessionStore = this._browser.__SS_data;
// Get the last selected entry
let entry = sessionStore.entries[sessionStore.index - 1];
return entry.url;
}
if (this.webNavigation.currentURI) {
return this.webNavigation.currentURI.spec;
}
return null;
},
enumerable: true,
configurable: true
});
Object.defineProperty(BrowserTabActor.prototype, "browser", {
get: function() {
return this._browser;

Просмотреть файл

@ -79,6 +79,7 @@ skip-if = buildapp == 'mulet'
[test_memory_allocations_02.html]
[test_memory_allocations_03.html]
[test_memory_allocations_04.html]
[test_memory_allocations_05.html]
[test_memory_attach_01.html]
[test_memory_attach_02.html]
[test_memory_census.html]

Просмотреть файл

@ -0,0 +1,87 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1068144 - Test getting the timestamps for allocations.
-->
<head>
<meta charset="utf-8">
<title>Memory monitoring actor test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
<script>
window.onload = function() {
SimpleTest.waitForExplicitFinish();
Task.spawn(function* () {
var { memory, client } = yield startServerAndGetSelectedTabMemory();
yield memory.attach();
var allocs = [];
function allocator() {
allocs.push(new Object);
}
// Using setTimeout results in wildly varying delays that make it hard to
// test our timestamps and results in intermittent failures. Instead, we
// actually spin an empty loop for a whole millisecond.
function actuallyWaitOneWholeMillisecond() {
var start = window.performance.now();
while (window.performance.now() - start < 1.000) ;
}
yield memory.startRecordingAllocations();
allocator();
actuallyWaitOneWholeMillisecond();
allocator();
actuallyWaitOneWholeMillisecond();
allocator();
var response = yield memory.getAllocations();
yield memory.stopRecordingAllocations();
ok(response.allocationsTimestamps, "The response should have timestamps.");
is(response.allocationsTimestamps.length, response.allocations.length,
"There should be a timestamp for every allocation.");
var allocatorIndices = response.allocations
.map(function (a, idx) {
var frame = response.frames[a];
if (frame && frame.functionDisplayName === "allocator") {
return idx;
}
})
.filter(function (idx) {
return idx !== undefined;
});
is(allocatorIndices.length, 3, "Should have our 3 allocations from the `allocator` timeouts.");
var lastTimestamp;
for (var i = 0; i < 3; i++) {
var timestamp = response.allocationsTimestamps[allocatorIndices[i]];
info("timestamp", timestamp);
ok(timestamp, "We should have a timestamp for the `allocator` allocation.");
if (lastTimestamp) {
var delta = timestamp - lastTimestamp;
info("delta since last timestamp", delta);
ok(delta >= 1000 /* 1 ms */,
"The timestamp should be about 1 ms after the last timestamp.");
}
lastTimestamp = timestamp;
}
yield memory.detach();
destroyServerAndFinish(client);
});
};
</script>
</pre>
</body>
</html>