mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 13:55:43 +00:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
7c8bee6a68
@ -316,7 +316,7 @@ skip-if = e10s
|
||||
run-if = datareporting
|
||||
[browser_devedition.js]
|
||||
[browser_devices_get_user_media.js]
|
||||
skip-if = buildapp == 'mulet' || (os == "linux" && debug) || e10s # linux: bug 976544; e10s: Bug 973001 - appears user media notifications only happen in the child and don't make their way to the parent?
|
||||
skip-if = buildapp == 'mulet' || os == "linux" || e10s # linux: bug 976544 & bug 1060315; e10s: Bug 973001 - appears user media notifications only happen in the child and don't make their way to the parent?
|
||||
[browser_devices_get_user_media_about_urls.js]
|
||||
skip-if = e10s # Bug 973001 - appears user media notifications only happen in the child and don't make their way to the parent?
|
||||
[browser_discovery.js]
|
||||
|
@ -57,4 +57,5 @@ skip-if = e10s # Bug 1069162 - lots of orange
|
||||
skip-if = e10s # Bug 915547 (social providers don't install)
|
||||
[browser_social_window.js]
|
||||
[browser_social_workercrash.js]
|
||||
skip-if = !crashreporter
|
||||
#skip-if = !crashreporter
|
||||
skip-if = true # Bug 1060813 - frequent leaks on all platforms
|
||||
|
@ -201,9 +201,9 @@
|
||||
<vbox id="PanelUI-panic-explanations">
|
||||
<label id="PanelUI-panic-actionlist-main-label">&panicButton.view.mainActionDesc;</label>
|
||||
|
||||
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
|
||||
<label id="PanelUI-panic-actionlist-cookies" class="PanelUI-panic-actionlist">&panicButton.view.deleteCookies;</label>
|
||||
<label id="PanelUI-panic-actionlist-history" class="PanelUI-panic-actionlist">&panicButton.view.deleteHistory;</label>
|
||||
<label id="PanelUI-panic-actionlist-windows" class="PanelUI-panic-actionlist">&panicButton.view.deleteTabsAndWindows;</label>
|
||||
<label id="PanelUI-panic-actionlist-newwindow" class="PanelUI-panic-actionlist">&panicButton.view.openNewWindow;</label>
|
||||
|
||||
<label id="PanelUI-panic-warning">&panicButton.view.undoWarning;</label>
|
||||
|
@ -179,8 +179,13 @@ CallProgressSocket.prototype = {
|
||||
* and register with the Loop server.
|
||||
*/
|
||||
let LoopCallsInternal = {
|
||||
callsData: {inUse: false},
|
||||
_mocks: {webSocket: undefined},
|
||||
callsData: {
|
||||
inUse: false,
|
||||
},
|
||||
|
||||
mocks: {
|
||||
webSocket: undefined,
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback from MozLoopPushHandler - A push notification has been received from
|
||||
@ -308,7 +313,9 @@ let LoopCallsInternal = {
|
||||
callData.progressURL,
|
||||
callData.callId,
|
||||
callData.websocketToken);
|
||||
callProgress._websocket = this._mocks.webSocket;
|
||||
if (this.mocks.webSocket) {
|
||||
callProgress._websocket = this.mocks.webSocket;
|
||||
}
|
||||
// This instance of CallProgressSocket should stay alive until the underlying
|
||||
// websocket is closed since it is passed to the websocket as the nsIWebSocketListener.
|
||||
callProgress.connect(() => {callProgress.sendBusy();});
|
||||
|
@ -109,7 +109,6 @@ function getJSONPref(aName) {
|
||||
// or the registration was successful. This is null if a registration attempt was
|
||||
// unsuccessful.
|
||||
let gRegisteredDeferred = null;
|
||||
let gPushHandler = null;
|
||||
let gHawkClient = null;
|
||||
let gLocalizedStrings = null;
|
||||
let gInitializeTimer = null;
|
||||
@ -126,7 +125,12 @@ let gErrors = new Map();
|
||||
* and register with the Loop server.
|
||||
*/
|
||||
let MozLoopServiceInternal = {
|
||||
_mocks: {webSocket: undefined},
|
||||
mocks: {
|
||||
pushHandler: undefined,
|
||||
webSocket: undefined,
|
||||
},
|
||||
|
||||
get pushHandler() this.mocks.pushHandler || MozLoopPushHandler,
|
||||
|
||||
// The uri of the Loop server.
|
||||
get loopServerUri() Services.prefs.getCharPref("loop.server"),
|
||||
@ -307,21 +311,14 @@ let MozLoopServiceInternal = {
|
||||
* Starts registration of Loop with the push server, and then will register
|
||||
* with the Loop server. It will return early if already registered.
|
||||
*
|
||||
* @param {Object} mockPushHandler Optional, test-only mock push handler. Used
|
||||
* to allow mocking of the MozLoopPushHandler.
|
||||
* @param {Object} mockWebSocket Optional, test-only mock webSocket. To be passed
|
||||
* through to MozLoopPushHandler.
|
||||
* @returns {Promise} a promise that is resolved with no params on completion, or
|
||||
* rejected with an error code or string.
|
||||
*/
|
||||
promiseRegisteredWithServers: function(mockPushHandler, mockWebSocket) {
|
||||
promiseRegisteredWithServers: function() {
|
||||
if (gRegisteredDeferred) {
|
||||
return gRegisteredDeferred.promise;
|
||||
}
|
||||
|
||||
this._mocks.webSocket = mockWebSocket;
|
||||
this._mocks.pushHandler = mockPushHandler;
|
||||
|
||||
// Wrap push notification registration call-back in a Promise.
|
||||
let registerForNotification = function(channelID, onNotification) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -332,7 +329,7 @@ let MozLoopServiceInternal = {
|
||||
resolve(pushUrl);
|
||||
}
|
||||
};
|
||||
gPushHandler.register(channelID, onRegistered, onNotification);
|
||||
MozLoopServiceInternal.pushHandler.register(channelID, onRegistered, onNotification);
|
||||
});
|
||||
};
|
||||
|
||||
@ -341,27 +338,28 @@ let MozLoopServiceInternal = {
|
||||
// it back to null on error.
|
||||
let result = gRegisteredDeferred.promise;
|
||||
|
||||
gPushHandler = mockPushHandler || MozLoopPushHandler;
|
||||
let options = mockWebSocket ? {mockWebSocket: mockWebSocket} : {};
|
||||
gPushHandler.initialize(options);
|
||||
let options = this.mocks.webSocket ? { mockWebSocket: this.mocks.webSocket } : {};
|
||||
this.pushHandler.initialize(options);
|
||||
|
||||
let callsRegGuest = registerForNotification(MozLoopService.channelIDs.callsGuest,
|
||||
LoopCalls.onNotification);
|
||||
|
||||
|
||||
let roomsRegGuest = registerForNotification(MozLoopService.channelIDs.roomsGuest,
|
||||
roomsPushNotification);
|
||||
|
||||
|
||||
let callsRegFxA = registerForNotification(MozLoopService.channelIDs.callsFxA,
|
||||
LoopCalls.onNotification);
|
||||
|
||||
|
||||
let roomsRegFxA = registerForNotification(MozLoopService.channelIDs.roomsFxA,
|
||||
roomsPushNotification);
|
||||
|
||||
Promise.all([callsRegGuest, roomsRegGuest, callsRegFxA, roomsRegFxA])
|
||||
.then((pushUrls) => {
|
||||
return this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST,
|
||||
{calls: pushUrls[0], rooms: pushUrls[1]}) })
|
||||
.then(() => {
|
||||
return this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST,{
|
||||
calls: pushUrls[0],
|
||||
rooms: pushUrls[1],
|
||||
});
|
||||
}).then(() => {
|
||||
// storeSessionToken could have rejected and nulled the promise if the token was malformed.
|
||||
if (!gRegisteredDeferred) {
|
||||
return;
|
||||
@ -873,13 +871,13 @@ let MozLoopServiceInternal = {
|
||||
};
|
||||
Object.freeze(MozLoopServiceInternal);
|
||||
|
||||
let gInitializeTimerFunc = (deferredInitialization, mockPushHandler, mockWebSocket) => {
|
||||
let gInitializeTimerFunc = (deferredInitialization) => {
|
||||
// Kick off the push notification service into registering after a timeout.
|
||||
// This ensures we're not doing too much straight after the browser's finished
|
||||
// starting up.
|
||||
gInitializeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
gInitializeTimer.initWithCallback(Task.async(function* initializationCallback() {
|
||||
yield MozLoopService.register(mockPushHandler, mockWebSocket).then(Task.async(function*() {
|
||||
yield MozLoopService.register().then(Task.async(function*() {
|
||||
if (!MozLoopServiceInternal.fxAOAuthTokenData) {
|
||||
log.debug("MozLoopService: Initialized without an already logged-in account");
|
||||
deferredInitialization.resolve("initialized to guest status");
|
||||
@ -890,8 +888,8 @@ let gInitializeTimerFunc = (deferredInitialization, mockPushHandler, mockWebSock
|
||||
let registeredPromise =
|
||||
MozLoopServiceInternal.registerWithLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, {
|
||||
calls: gPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
|
||||
rooms: gPushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA]
|
||||
calls: MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
|
||||
rooms: MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA]
|
||||
});
|
||||
registeredPromise.then(() => {
|
||||
deferredInitialization.resolve("initialized to logged-in status");
|
||||
@ -936,7 +934,7 @@ this.MozLoopService = {
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
initialize: Task.async(function*(mockPushHandler, mockWebSocket) {
|
||||
initialize: Task.async(function*() {
|
||||
// Do this here, rather than immediately after definition, so that we can
|
||||
// stub out API functions for unit testing
|
||||
Object.freeze(this);
|
||||
@ -962,7 +960,7 @@ this.MozLoopService = {
|
||||
}
|
||||
|
||||
let deferredInitialization = Promise.defer();
|
||||
gInitializeTimerFunc(deferredInitialization, mockPushHandler, mockWebSocket);
|
||||
gInitializeTimerFunc(deferredInitialization);
|
||||
|
||||
return deferredInitialization.promise.catch(error => {
|
||||
if (typeof(error) == "object") {
|
||||
@ -1088,12 +1086,10 @@ this.MozLoopService = {
|
||||
* Starts registration of Loop with the push server, and then will register
|
||||
* with the Loop server. It will return early if already registered.
|
||||
*
|
||||
* @param {Object} mockPushHandler Optional, test-only mock push handler. Used
|
||||
* to allow mocking of the MozLoopPushHandler.
|
||||
* @returns {Promise} a promise that is resolved with no params on completion, or
|
||||
* rejected with an error code or string.
|
||||
*/
|
||||
register: function(mockPushHandler, mockWebSocket) {
|
||||
register: function() {
|
||||
log.debug("registering");
|
||||
// Don't do anything if loop is not enabled.
|
||||
if (!Services.prefs.getBoolPref("loop.enabled")) {
|
||||
@ -1104,7 +1100,7 @@ this.MozLoopService = {
|
||||
throw new Error("Loop is disabled by the soft-start mechanism");
|
||||
}
|
||||
|
||||
return MozLoopServiceInternal.promiseRegisteredWithServers(mockPushHandler, mockWebSocket);
|
||||
return MozLoopServiceInternal.promiseRegisteredWithServers();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1301,8 +1297,8 @@ this.MozLoopService = {
|
||||
return tokenData;
|
||||
}).then(tokenData => {
|
||||
return gRegisteredDeferred.promise.then(Task.async(function*() {
|
||||
let callsUrl = gPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
|
||||
roomsUrl = gPushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
|
||||
let callsUrl = MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA],
|
||||
roomsUrl = MozLoopServiceInternal.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
|
||||
if (callsUrl && roomsUrl) {
|
||||
yield MozLoopServiceInternal.registerWithLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, {calls: callsUrl, rooms: roomsUrl});
|
||||
@ -1348,23 +1344,19 @@ this.MozLoopService = {
|
||||
*/
|
||||
logOutFromFxA: Task.async(function*() {
|
||||
log.debug("logOutFromFxA");
|
||||
let callsPushUrl, roomsPushUrl;
|
||||
if (gPushHandler) {
|
||||
callsPushUrl = gPushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA];
|
||||
roomsPushUrl = gPushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
|
||||
}
|
||||
let pushHandler = MozLoopServiceInternal.pushHandler;
|
||||
let callsPushUrl = pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA];
|
||||
let roomsPushUrl = pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
|
||||
try {
|
||||
if (callsPushUrl) {
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, callsPushUrl);
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA, callsPushUrl);
|
||||
}
|
||||
if (roomsPushUrl) {
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(
|
||||
LOOP_SESSION_TYPE.FXA, roomsPushUrl);
|
||||
yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA, roomsPushUrl);
|
||||
}
|
||||
}
|
||||
catch (error) {throw error}
|
||||
finally {
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
MozLoopServiceInternal.clearSessionToken(LOOP_SESSION_TYPE.FXA);
|
||||
}
|
||||
|
||||
|
@ -539,11 +539,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
_onRoomListChanged: function() {
|
||||
var storeState = this.props.store.getStoreState();
|
||||
this.setState({
|
||||
error: storeState.error,
|
||||
rooms: storeState.rooms
|
||||
});
|
||||
this.setState(this.props.store.getStoreState());
|
||||
},
|
||||
|
||||
_getListHeading: function() {
|
||||
|
@ -539,11 +539,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
_onRoomListChanged: function() {
|
||||
var storeState = this.props.store.getStoreState();
|
||||
this.setState({
|
||||
error: storeState.error,
|
||||
rooms: storeState.rooms
|
||||
});
|
||||
this.setState(this.props.store.getStoreState());
|
||||
},
|
||||
|
||||
_getListHeading: function() {
|
||||
|
@ -434,21 +434,27 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.feedback label {
|
||||
.feedback-category-label {
|
||||
display: block;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.feedback form input[type="radio"] {
|
||||
.feedback-category-radio {
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.feedback form button[type="submit"],
|
||||
.feedback form input[type="text"] {
|
||||
.feedback > form > .btn-success,
|
||||
.feedback-description {
|
||||
width: 100%;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.feedback > form > .btn-success {
|
||||
padding-top: .5em;
|
||||
padding-bottom: .5em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.feedback .info {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
|
@ -134,6 +134,22 @@ loop.shared.actions = (function() {
|
||||
GetAllRooms: Action.define("getAllRooms", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* An error occured while trying to fetch the room list.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*/
|
||||
GetAllRoomsError: Action.define("getAllRoomsError", {
|
||||
error: String
|
||||
}),
|
||||
|
||||
/**
|
||||
* Updates room list.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*/
|
||||
UpdateRoomList: Action.define("updateRoomList", {
|
||||
roomList: Array
|
||||
}),
|
||||
|
||||
/**
|
||||
* Primes localRoomStore with roomLocalId, which triggers the EmptyRoomView
|
||||
* to do any necessary setup.
|
||||
|
@ -97,7 +97,58 @@ loop.shared.mixins = (function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Audio mixin. Allows playing a single audio file and ensuring it
|
||||
* is stopped when the component is unmounted.
|
||||
*/
|
||||
var AudioMixin = {
|
||||
audio: null,
|
||||
|
||||
_isLoopDesktop: function() {
|
||||
return typeof rootObject.navigator.mozLoop === "object";
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts playing an audio file, stopping any audio that is already in progress.
|
||||
*
|
||||
* @param {String} filename The filename to play (excluding the extension).
|
||||
*/
|
||||
play: function(filename, options) {
|
||||
if (this._isLoopDesktop()) {
|
||||
// XXX: We need navigator.mozLoop.playSound(name), see Bug 1089585.
|
||||
return;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.loop = options.loop || false;
|
||||
|
||||
this._ensureAudioStopped();
|
||||
this.audio = new Audio('shared/sounds/' + filename + ".ogg");
|
||||
this.audio.loop = options.loop;
|
||||
this.audio.play();
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures audio is stopped playing, and removes the object from memory.
|
||||
*/
|
||||
_ensureAudioStopped: function() {
|
||||
if (this.audio) {
|
||||
this.audio.pause();
|
||||
this.audio.removeAttribute("src");
|
||||
delete this.audio;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures audio is stopped when the component is unmounted.
|
||||
*/
|
||||
componentWillUnmount: function() {
|
||||
this._ensureAudioStopped();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
AudioMixin: AudioMixin,
|
||||
setRootObject: setRootObject,
|
||||
DropdownMenuMixin: DropdownMenuMixin,
|
||||
DocumentVisibilityMixin: DocumentVisibilityMixin
|
||||
|
@ -10,49 +10,25 @@ loop.store = loop.store || {};
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Shared actions.
|
||||
* @type {Object}
|
||||
*/
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
||||
/**
|
||||
* Room validation schema. See validate.js.
|
||||
* @type {Object}
|
||||
*/
|
||||
var roomSchema = {
|
||||
roomToken: String,
|
||||
roomUrl: String,
|
||||
roomName: String,
|
||||
maxSize: Number,
|
||||
currSize: Number,
|
||||
ctime: Number
|
||||
roomToken: String,
|
||||
roomUrl: String,
|
||||
roomName: String,
|
||||
maxSize: Number,
|
||||
participants: Array,
|
||||
ctime: Number
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporary sample raw room list data.
|
||||
* XXX Should be removed when we plug the real mozLoop API for rooms.
|
||||
* See bug 1074664.
|
||||
* @type {Array}
|
||||
*/
|
||||
var temporaryRawRoomList = [{
|
||||
roomToken: "_nxD4V4FflQ",
|
||||
roomUrl: "http://sample/_nxD4V4FflQ",
|
||||
roomName: "First Room Name",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
ctime: 1405517546
|
||||
}, {
|
||||
roomToken: "QzBbvGmIZWU",
|
||||
roomUrl: "http://sample/QzBbvGmIZWU",
|
||||
roomName: "Second Room Name",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
ctime: 1405517418
|
||||
}, {
|
||||
roomToken: "3jKS_Els9IU",
|
||||
roomUrl: "http://sample/3jKS_Els9IU",
|
||||
roomName: "Third Room Name",
|
||||
maxSize: 3,
|
||||
clientMaxSize: 2,
|
||||
currSize: 1,
|
||||
ctime: 1405518241
|
||||
}];
|
||||
|
||||
/**
|
||||
* Room type. Basically acts as a typed object constructor.
|
||||
*
|
||||
@ -95,7 +71,9 @@ loop.store = loop.store || {};
|
||||
|
||||
this.dispatcher.register(this, [
|
||||
"getAllRooms",
|
||||
"openRoom"
|
||||
"getAllRoomsError",
|
||||
"openRoom",
|
||||
"updateRoomList"
|
||||
]);
|
||||
}
|
||||
|
||||
@ -119,21 +97,6 @@ loop.store = loop.store || {};
|
||||
this.trigger("change");
|
||||
},
|
||||
|
||||
/**
|
||||
* Proxy to navigator.mozLoop.rooms.getAll.
|
||||
* XXX Could probably be removed when bug 1074664 lands.
|
||||
*
|
||||
* @param {Function} cb Callback(error, roomList)
|
||||
*/
|
||||
_fetchRoomList: function(cb) {
|
||||
// Faking this.mozLoop.rooms until it's available; bug 1074664.
|
||||
if (!this.mozLoop.hasOwnProperty("rooms")) {
|
||||
cb(null, temporaryRawRoomList);
|
||||
return;
|
||||
}
|
||||
this.mozLoop.rooms.getAll(cb);
|
||||
},
|
||||
|
||||
/**
|
||||
* Maps and sorts the raw room list received from the mozLoop API.
|
||||
*
|
||||
@ -158,13 +121,37 @@ loop.store = loop.store || {};
|
||||
* Gather the list of all available rooms from the MozLoop API.
|
||||
*/
|
||||
getAllRooms: function() {
|
||||
this._fetchRoomList(function(err, rawRoomList) {
|
||||
this.setStoreState({
|
||||
error: err,
|
||||
rooms: this._processRawRoomList(rawRoomList)
|
||||
});
|
||||
this.mozLoop.rooms.getAll(function(err, rawRoomList) {
|
||||
var action;
|
||||
if (err) {
|
||||
action = new sharedActions.GetAllRoomsError({error: err});
|
||||
} else {
|
||||
action = new sharedActions.UpdateRoomList({roomList: rawRoomList});
|
||||
}
|
||||
this.dispatcher.dispatch(action);
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates current error state in case getAllRooms failed.
|
||||
*
|
||||
* @param {sharedActions.UpdateRoomListError} actionData The action data.
|
||||
*/
|
||||
getAllRoomsError: function(actionData) {
|
||||
this.setStoreState({error: actionData.error});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates current room list.
|
||||
*
|
||||
* @param {sharedActions.UpdateRoomList} actionData The action data.
|
||||
*/
|
||||
updateRoomList: function(actionData) {
|
||||
this.setStoreState({
|
||||
error: undefined,
|
||||
rooms: this._processRawRoomList(actionData.roomList)
|
||||
});
|
||||
},
|
||||
}, Backbone.Events);
|
||||
|
||||
loop.store.RoomListStore = RoomListStore;
|
||||
|
@ -135,7 +135,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
* Conversation view.
|
||||
*/
|
||||
var ConversationView = React.createClass({displayName: 'ConversationView',
|
||||
mixins: [Backbone.Events],
|
||||
mixins: [Backbone.Events, sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
@ -183,7 +183,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
componentDidMount: function() {
|
||||
if (this.props.initiate) {
|
||||
this.listenTo(this.props.model, "session:connected",
|
||||
this.startPublishing);
|
||||
this._onSessionConnected);
|
||||
this.listenTo(this.props.model, "session:stream-created",
|
||||
this._streamCreated);
|
||||
this.listenTo(this.props.model, ["session:peer-hungup",
|
||||
@ -225,6 +225,11 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
this.props.model.endSession();
|
||||
},
|
||||
|
||||
_onSessionConnected: function(event) {
|
||||
this.startPublishing(event);
|
||||
this.play("connected");
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribes and attaches each created stream to a DOM element.
|
||||
*
|
||||
@ -397,8 +402,9 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
var categories = this._getCategories();
|
||||
return Object.keys(categories).map(function(category, key) {
|
||||
return (
|
||||
React.DOM.label({key: key},
|
||||
React.DOM.label({key: key, className: "feedback-category-label"},
|
||||
React.DOM.input({type: "radio", ref: "category", name: "category",
|
||||
className: "feedback-category-radio",
|
||||
value: category,
|
||||
onChange: this.handleCategoryChange,
|
||||
checked: this.state.category === category}),
|
||||
@ -466,6 +472,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
this._getCategoryFields(),
|
||||
React.DOM.p(null,
|
||||
React.DOM.input({type: "text", ref: "description", name: "description",
|
||||
className: "feedback-description",
|
||||
onChange: this.handleDescriptionFieldChange,
|
||||
onFocus: this.handleDescriptionFieldFocus,
|
||||
value: descriptionDisplayValue,
|
||||
|
@ -135,7 +135,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
* Conversation view.
|
||||
*/
|
||||
var ConversationView = React.createClass({
|
||||
mixins: [Backbone.Events],
|
||||
mixins: [Backbone.Events, sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
sdk: React.PropTypes.object.isRequired,
|
||||
@ -183,7 +183,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
componentDidMount: function() {
|
||||
if (this.props.initiate) {
|
||||
this.listenTo(this.props.model, "session:connected",
|
||||
this.startPublishing);
|
||||
this._onSessionConnected);
|
||||
this.listenTo(this.props.model, "session:stream-created",
|
||||
this._streamCreated);
|
||||
this.listenTo(this.props.model, ["session:peer-hungup",
|
||||
@ -225,6 +225,11 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
this.props.model.endSession();
|
||||
},
|
||||
|
||||
_onSessionConnected: function(event) {
|
||||
this.startPublishing(event);
|
||||
this.play("connected");
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribes and attaches each created stream to a DOM element.
|
||||
*
|
||||
@ -397,8 +402,9 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
var categories = this._getCategories();
|
||||
return Object.keys(categories).map(function(category, key) {
|
||||
return (
|
||||
<label key={key}>
|
||||
<label key={key} className="feedback-category-label">
|
||||
<input type="radio" ref="category" name="category"
|
||||
className="feedback-category-radio"
|
||||
value={category}
|
||||
onChange={this.handleCategoryChange}
|
||||
checked={this.state.category === category} />
|
||||
@ -466,6 +472,7 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
{this._getCategoryFields()}
|
||||
<p>
|
||||
<input type="text" ref="description" name="description"
|
||||
className="feedback-description"
|
||||
onChange={this.handleDescriptionFieldChange}
|
||||
onFocus={this.handleDescriptionFieldFocus}
|
||||
value={descriptionDisplayValue}
|
||||
|
BIN
browser/components/loop/content/shared/sounds/connected.ogg
Normal file
BIN
browser/components/loop/content/shared/sounds/connected.ogg
Normal file
Binary file not shown.
BIN
browser/components/loop/content/shared/sounds/connecting.ogg
Normal file
BIN
browser/components/loop/content/shared/sounds/connecting.ogg
Normal file
Binary file not shown.
BIN
browser/components/loop/content/shared/sounds/ringing.ogg
Normal file
BIN
browser/components/loop/content/shared/sounds/ringing.ogg
Normal file
Binary file not shown.
BIN
browser/components/loop/content/shared/sounds/terminated.ogg
Normal file
BIN
browser/components/loop/content/shared/sounds/terminated.ogg
Normal file
Binary file not shown.
@ -188,6 +188,7 @@ p.standalone-btn-label {
|
||||
|
||||
.btn-pending-cancel-group > .btn-cancel {
|
||||
flex: 2 1 auto;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
|
@ -262,9 +262,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
|
||||
var PendingConversationView = React.createClass({displayName: 'PendingConversationView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
callState: this.props.callState || "connecting"
|
||||
callState: "connecting"
|
||||
};
|
||||
},
|
||||
|
||||
@ -274,11 +277,13 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("connecting", {loop: true});
|
||||
this.props.websocket.listenTo(this.props.websocket, "progress:alerting",
|
||||
this._handleRingingProgress);
|
||||
},
|
||||
|
||||
_handleRingingProgress: function() {
|
||||
this.play("ringing", {loop: true});
|
||||
this.setState({callState: "ringing"});
|
||||
},
|
||||
|
||||
@ -518,6 +523,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
* Ended conversation view.
|
||||
*/
|
||||
var EndedConversationView = React.createClass({displayName: 'EndedConversationView',
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
@ -526,6 +533,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
onAfterFeedbackReceived: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
{clientShortname: mozL10n.get("clientShortname2"),
|
||||
|
@ -262,9 +262,12 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
|
||||
var PendingConversationView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
callState: this.props.callState || "connecting"
|
||||
callState: "connecting"
|
||||
};
|
||||
},
|
||||
|
||||
@ -274,11 +277,13 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("connecting", {loop: true});
|
||||
this.props.websocket.listenTo(this.props.websocket, "progress:alerting",
|
||||
this._handleRingingProgress);
|
||||
},
|
||||
|
||||
_handleRingingProgress: function() {
|
||||
this.play("ringing", {loop: true});
|
||||
this.setState({callState: "ringing"});
|
||||
},
|
||||
|
||||
@ -518,6 +523,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
* Ended conversation view.
|
||||
*/
|
||||
var EndedConversationView = React.createClass({
|
||||
mixins: [sharedMixins.AudioMixin],
|
||||
|
||||
propTypes: {
|
||||
conversation: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
@ -526,6 +533,10 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
onAfterFeedbackReceived: React.PropTypes.func.isRequired
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.play("terminated");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
document.title = mozL10n.get("standalone_title_with_status",
|
||||
{clientShortname: mozL10n.get("clientShortname2"),
|
||||
|
@ -49,6 +49,11 @@ describe("loop.panel", function() {
|
||||
callback(null, []);
|
||||
},
|
||||
on: sandbox.stub()
|
||||
},
|
||||
rooms: {
|
||||
getAll: function(callback) {
|
||||
callback(null, []);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -40,11 +40,13 @@ function* checkFxA401() {
|
||||
add_task(function* setup() {
|
||||
Services.prefs.setCharPref("loop.server", BASE_URL);
|
||||
Services.prefs.setCharPref("services.push.serverURL", "ws://localhost/");
|
||||
MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
|
||||
registerCleanupFunction(function* () {
|
||||
info("cleanup time");
|
||||
yield promiseDeletedOAuthParams(BASE_URL);
|
||||
Services.prefs.clearUserPref("loop.server");
|
||||
Services.prefs.clearUserPref("services.push.serverURL");
|
||||
MozLoopServiceInternal.mocks.pushHandler = undefined;
|
||||
yield resetFxA();
|
||||
Services.prefs.clearUserPref(MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.GUEST));
|
||||
});
|
||||
@ -251,7 +253,7 @@ add_task(function* basicAuthorizationAndRegistration() {
|
||||
mockPushHandler.registrationPushURL = "https://localhost/pushUrl/guest";
|
||||
// Notification observed due to the error being cleared upon successful registration.
|
||||
let statusChangedPromise = promiseObserverNotified("loop-status-changed");
|
||||
yield MozLoopService.register(mockPushHandler);
|
||||
yield MozLoopService.register();
|
||||
yield statusChangedPromise;
|
||||
|
||||
// Normally the same pushUrl would be registered but we change it in the test
|
||||
@ -316,7 +318,7 @@ add_task(function* loginWithParams401() {
|
||||
test_error: "params_401",
|
||||
};
|
||||
yield promiseOAuthParamsSetup(BASE_URL, params);
|
||||
yield MozLoopService.register(mockPushHandler);
|
||||
yield MozLoopService.register();
|
||||
|
||||
let loginPromise = MozLoopService.logInToFxA();
|
||||
yield loginPromise.then(tokenData => {
|
||||
|
@ -50,14 +50,14 @@ describe("loop.store.RoomListStore", function () {
|
||||
roomUrl: "http://sample/_nxD4V4FflQ",
|
||||
roomName: "First Room Name",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
participants: [],
|
||||
ctime: 1405517546
|
||||
}, {
|
||||
roomToken: "QzBbvGmIZWU",
|
||||
roomUrl: "http://sample/QzBbvGmIZWU",
|
||||
roomName: "Second Room Name",
|
||||
maxSize: 2,
|
||||
currSize: 0,
|
||||
participants: [],
|
||||
ctime: 1405517418
|
||||
}, {
|
||||
roomToken: "3jKS_Els9IU",
|
||||
@ -65,7 +65,7 @@ describe("loop.store.RoomListStore", function () {
|
||||
roomName: "Third Room Name",
|
||||
maxSize: 3,
|
||||
clientMaxSize: 2,
|
||||
currSize: 1,
|
||||
participants: [],
|
||||
ctime: 1405518241
|
||||
}];
|
||||
|
||||
@ -93,7 +93,7 @@ describe("loop.store.RoomListStore", function () {
|
||||
|
||||
it("should fetch the room list from the mozLoop API", function(done) {
|
||||
store.once("change", function() {
|
||||
expect(store.getStoreState().error).to.be.a.null;
|
||||
expect(store.getStoreState().error).to.be.a.undefined;
|
||||
expect(store.getStoreState().rooms).to.have.length.of(3);
|
||||
done();
|
||||
});
|
||||
@ -104,7 +104,7 @@ describe("loop.store.RoomListStore", function () {
|
||||
it("should order the room list using ctime desc", function(done) {
|
||||
store.once("change", function() {
|
||||
var storeState = store.getStoreState();
|
||||
expect(storeState.error).to.be.a.null;
|
||||
expect(storeState.error).to.be.a.undefined;
|
||||
expect(storeState.rooms[0].ctime).eql(1405518241);
|
||||
expect(storeState.rooms[1].ctime).eql(1405517546);
|
||||
expect(storeState.rooms[2].ctime).eql(1405517418);
|
||||
|
@ -161,13 +161,20 @@ describe("loop.shared.views", function() {
|
||||
});
|
||||
|
||||
describe("ConversationView", function() {
|
||||
var fakeSDK, fakeSessionData, fakeSession, fakePublisher, model;
|
||||
var fakeSDK, fakeSessionData, fakeSession, fakePublisher, model, fakeAudio;
|
||||
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(sharedViews.ConversationView(props));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
fakeAudio = {
|
||||
play: sinon.spy(),
|
||||
pause: sinon.spy(),
|
||||
removeAttribute: sinon.spy()
|
||||
};
|
||||
sandbox.stub(window, "Audio").returns(fakeAudio);
|
||||
|
||||
fakeSessionData = {
|
||||
sessionId: "sessionId",
|
||||
sessionToken: "sessionToken",
|
||||
@ -350,46 +357,69 @@ describe("loop.shared.views", function() {
|
||||
});
|
||||
|
||||
describe("Model events", function() {
|
||||
it("should start streaming on session:connected", function() {
|
||||
model.trigger("session:connected");
|
||||
|
||||
sinon.assert.calledOnce(fakeSDK.initPublisher);
|
||||
});
|
||||
describe("for standalone", function() {
|
||||
|
||||
it("should publish remote stream on session:stream-created",
|
||||
function() {
|
||||
var s1 = {connection: {connectionId: 42}};
|
||||
|
||||
model.trigger("session:stream-created", {stream: s1});
|
||||
|
||||
sinon.assert.calledOnce(fakeSession.subscribe);
|
||||
sinon.assert.calledWith(fakeSession.subscribe, s1);
|
||||
beforeEach(function() {
|
||||
// In standalone, navigator.mozLoop does not exists
|
||||
if (navigator.hasOwnProperty("mozLoop"))
|
||||
sandbox.stub(navigator, "mozLoop", undefined);
|
||||
});
|
||||
|
||||
it("should unpublish local stream on session:ended", function() {
|
||||
comp.startPublishing();
|
||||
it("should play a connected sound, once, on session:connected",
|
||||
function() {
|
||||
model.trigger("session:connected");
|
||||
|
||||
model.trigger("session:ended");
|
||||
|
||||
sinon.assert.calledOnce(fakeSession.unpublish);
|
||||
sinon.assert.calledOnce(window.Audio);
|
||||
sinon.assert.calledWithExactly(
|
||||
window.Audio, "shared/sounds/connected.ogg");
|
||||
expect(fakeAudio.loop).to.not.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should unpublish local stream on session:peer-hungup", function() {
|
||||
comp.startPublishing();
|
||||
describe("for both (standalone and desktop)", function() {
|
||||
it("should start streaming on session:connected", function() {
|
||||
model.trigger("session:connected");
|
||||
|
||||
model.trigger("session:peer-hungup");
|
||||
sinon.assert.calledOnce(fakeSDK.initPublisher);
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(fakeSession.unpublish);
|
||||
});
|
||||
it("should publish remote stream on session:stream-created",
|
||||
function() {
|
||||
var s1 = {connection: {connectionId: 42}};
|
||||
|
||||
it("should unpublish local stream on session:network-disconnected",
|
||||
function() {
|
||||
model.trigger("session:stream-created", {stream: s1});
|
||||
|
||||
sinon.assert.calledOnce(fakeSession.subscribe);
|
||||
sinon.assert.calledWith(fakeSession.subscribe, s1);
|
||||
});
|
||||
|
||||
it("should unpublish local stream on session:ended", function() {
|
||||
comp.startPublishing();
|
||||
|
||||
model.trigger("session:network-disconnected");
|
||||
model.trigger("session:ended");
|
||||
|
||||
sinon.assert.calledOnce(fakeSession.unpublish);
|
||||
});
|
||||
|
||||
it("should unpublish local stream on session:peer-hungup", function() {
|
||||
comp.startPublishing();
|
||||
|
||||
model.trigger("session:peer-hungup");
|
||||
|
||||
sinon.assert.calledOnce(fakeSession.unpublish);
|
||||
});
|
||||
|
||||
it("should unpublish local stream on session:network-disconnected",
|
||||
function() {
|
||||
comp.startPublishing();
|
||||
|
||||
model.trigger("session:network-disconnected");
|
||||
|
||||
sinon.assert.calledOnce(fakeSession.unpublish);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Publisher events", function() {
|
||||
@ -526,7 +556,7 @@ describe("loop.shared.views", function() {
|
||||
fillSadFeedbackForm(comp, "confusing");
|
||||
|
||||
expect(comp.getDOMNode()
|
||||
.querySelector("form input[type='text']").value).eql("");
|
||||
.querySelector(".feedback-description").value).eql("");
|
||||
});
|
||||
|
||||
it("should enable the form submit button once a predefined category is " +
|
||||
|
@ -575,7 +575,7 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
describe("PendingConversationView", function() {
|
||||
var view, websocket;
|
||||
var view, websocket, fakeAudio;
|
||||
|
||||
beforeEach(function() {
|
||||
websocket = new loop.CallConnectionWebSocket({
|
||||
@ -585,6 +585,12 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
sinon.stub(websocket, "cancel");
|
||||
fakeAudio = {
|
||||
play: sinon.spy(),
|
||||
pause: sinon.spy(),
|
||||
removeAttribute: sinon.spy()
|
||||
};
|
||||
sandbox.stub(window, "Audio").returns(fakeAudio);
|
||||
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.PendingConversationView({
|
||||
@ -593,6 +599,16 @@ describe("loop.webapp", function() {
|
||||
);
|
||||
});
|
||||
|
||||
describe("#componentDidMount", function() {
|
||||
|
||||
it("should play a looped connecting sound", function() {
|
||||
sinon.assert.calledOnce(window.Audio);
|
||||
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/connecting.ogg");
|
||||
expect(fakeAudio.loop).to.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("#_cancelOutgoingCall", function() {
|
||||
it("should inform the websocket to cancel the setup", function() {
|
||||
var button = view.getDOMNode().querySelector(".btn-cancel");
|
||||
@ -609,6 +625,13 @@ describe("loop.webapp", function() {
|
||||
|
||||
expect(view.state.callState).to.be.equal("ringing");
|
||||
});
|
||||
|
||||
it("should play a looped ringing sound", function() {
|
||||
websocket.trigger("progress:alerting");
|
||||
|
||||
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/ringing.ogg");
|
||||
expect(fakeAudio.loop).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -843,9 +866,16 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
describe("EndedConversationView", function() {
|
||||
var view, conversation;
|
||||
var view, conversation, fakeAudio;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeAudio = {
|
||||
play: sinon.spy(),
|
||||
pause: sinon.spy(),
|
||||
removeAttribute: sinon.spy()
|
||||
};
|
||||
sandbox.stub(window, "Audio").returns(fakeAudio);
|
||||
|
||||
conversation = new sharedModels.ConversationModel({}, {
|
||||
sdk: {}
|
||||
});
|
||||
@ -866,6 +896,17 @@ describe("loop.webapp", function() {
|
||||
it("should render a FeedbackView", function() {
|
||||
TestUtils.findRenderedComponentWithType(view, sharedViews.FeedbackView);
|
||||
});
|
||||
|
||||
describe("#componentDidMount", function() {
|
||||
|
||||
it("should play a terminating sound, once", function() {
|
||||
sinon.assert.calledOnce(window.Audio);
|
||||
sinon.assert.calledWithExactly(window.Audio, "shared/sounds/terminated.ogg");
|
||||
expect(fakeAudio.loop).to.not.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("PromoteFirefoxView", function() {
|
||||
|
@ -40,8 +40,11 @@ function setupFakeLoopServer() {
|
||||
Services.prefs.setCharPref("loop.server",
|
||||
"http://localhost:" + loopServer.identity.primaryPort);
|
||||
|
||||
MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
|
||||
|
||||
do_register_cleanup(function() {
|
||||
loopServer.stop(function() {});
|
||||
MozLoopServiceInternal.mocks.pushHandler = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -19,17 +19,12 @@ let msgHandler = function(msg) {
|
||||
msg.reason === "busy") {
|
||||
actionReceived = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mockWebSocket = new MockWebSocketChannel({defaultMsgHandler: msgHandler});
|
||||
LoopCallsInternal._mocks.webSocket = mockWebSocket;
|
||||
|
||||
Services.io.offline = false;
|
||||
};
|
||||
|
||||
add_test(function test_busy_2guest_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
@ -52,7 +47,7 @@ add_test(function test_busy_2guest_calls() {
|
||||
add_test(function test_busy_1fxa_1guest_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
@ -76,7 +71,7 @@ add_test(function test_busy_1fxa_1guest_calls() {
|
||||
add_test(function test_busy_2fxa_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
@ -99,7 +94,7 @@ add_test(function test_busy_2fxa_calls() {
|
||||
add_test(function test_busy_1guest_1fxa_calls() {
|
||||
actionReceived = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler, mockWebSocket).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
let opened = 0;
|
||||
Chat.open = function() {
|
||||
opened++;
|
||||
@ -120,8 +115,7 @@ add_test(function test_busy_1guest_1fxa_calls() {
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
// Setup fake login state so we get FxA requests.
|
||||
@ -129,6 +123,11 @@ function run_test()
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"};
|
||||
MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
|
||||
|
||||
let mockWebSocket = new MockWebSocketChannel({defaultMsgHandler: msgHandler});
|
||||
LoopCallsInternal.mocks.webSocket = mockWebSocket;
|
||||
|
||||
Services.io.offline = false;
|
||||
|
||||
// For each notification received from the PushServer, MozLoopService will first query
|
||||
// for any pending calls on the FxA hawk session and then again using the guest session.
|
||||
// A pair of response objects in the callsResponses array will be consumed for each
|
||||
@ -178,7 +177,7 @@ function run_test()
|
||||
// clear test pref
|
||||
Services.prefs.clearUserPref("loop.seenToS");
|
||||
|
||||
LoopCallsInternal._mocks.webSocket = undefined;
|
||||
LoopCallsInternal.mocks.webSocket = undefined;
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
|
@ -31,7 +31,7 @@ add_test(function test_set_do_not_disturb() {
|
||||
add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
|
||||
MozLoopService.doNotDisturb = false;
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
let opened = false;
|
||||
Chat.open = function() {
|
||||
opened = true;
|
||||
@ -64,8 +64,7 @@ add_test(function test_do_not_disturb_enabled_shouldnt_open_chat_window() {
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
loopServer.registerPathHandler("/registration", (request, response) => {
|
||||
|
@ -10,7 +10,7 @@ let openChatOrig = Chat.open;
|
||||
add_test(function test_openChatWindow_on_notification() {
|
||||
Services.prefs.setCharPref("loop.seenToS", "unseen");
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
let opened = false;
|
||||
Chat.open = function() {
|
||||
opened = true;
|
||||
@ -32,8 +32,7 @@ add_test(function test_openChatWindow_on_notification() {
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
loopServer.registerPathHandler("/registration", (request, response) => {
|
||||
|
@ -17,7 +17,7 @@ Cu.import("resource://services-common/utils.js");
|
||||
add_test(function test_register_websocket_success_loop_server_fail() {
|
||||
mockPushHandler.registrationResult = "404";
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
do_throw("should not succeed when loop server registration fails");
|
||||
}, (err) => {
|
||||
// 404 is an expected failure indicated by the lack of route being set
|
||||
@ -49,15 +49,14 @@ add_test(function test_register_success() {
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
run_next_test();
|
||||
}, err => {
|
||||
do_throw("shouldn't error on a successful request");
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
do_register_cleanup(function() {
|
||||
|
@ -26,7 +26,7 @@ add_task(function test_initialize_with_expired_urls_and_no_auth_token() {
|
||||
Services.prefs.setIntPref(LOOP_URL_EXPIRY_PREF, nowSeconds - 2);
|
||||
Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
|
||||
|
||||
yield MozLoopService.initialize(mockPushHandler).then((msg) => {
|
||||
yield MozLoopService.initialize().then((msg) => {
|
||||
Assert.equal(msg, "registration not needed", "Initialize should not register when the " +
|
||||
"URLs are expired and there are no auth tokens");
|
||||
}, (error) => {
|
||||
@ -42,7 +42,7 @@ add_task(function test_initialize_with_urls_and_no_auth_token() {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
});
|
||||
|
||||
yield MozLoopService.initialize(mockPushHandler).then((msg) => {
|
||||
yield MozLoopService.initialize().then((msg) => {
|
||||
Assert.equal(msg, "initialized to guest status", "Initialize should register as a " +
|
||||
"guest when no auth tokens but expired URLs");
|
||||
}, (error) => {
|
||||
@ -66,12 +66,11 @@ add_task(function test_initialize_with_invalid_fxa_token() {
|
||||
}));
|
||||
});
|
||||
|
||||
yield MozLoopService.initialize(mockPushHandler).then(() => {
|
||||
yield MozLoopService.initialize().then(() => {
|
||||
Assert.ok(false, "Initializing with an invalid token should reject the promise");
|
||||
},
|
||||
(error) => {
|
||||
let pushHandler = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).gPushHandler;
|
||||
Assert.equal(pushHandler.pushUrl, kEndPointUrl, "Push URL should match");
|
||||
Assert.equal(MozLoopServiceInternal.pushHandler.pushUrl, kEndPointUrl, "Push URL should match");
|
||||
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
|
||||
"FXA pref should be cleared if token was invalid");
|
||||
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
|
||||
@ -86,7 +85,7 @@ add_task(function test_initialize_with_fxa_token() {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
});
|
||||
|
||||
yield MozLoopService.initialize(mockPushHandler).then(() => {
|
||||
yield MozLoopService.initialize().then(() => {
|
||||
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), FAKE_FXA_TOKEN_DATA,
|
||||
"FXA pref should still be set after initialization");
|
||||
Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), FAKE_FXA_PROFILE,
|
||||
@ -98,9 +97,11 @@ function run_test() {
|
||||
setupFakeLoopServer();
|
||||
// Note, this is just used to speed up the test.
|
||||
Services.prefs.setIntPref(LOOP_INITIAL_DELAY_PREF, 0);
|
||||
MozLoopServiceInternal.mocks.pushHandler = mockPushHandler;
|
||||
mockPushHandler.pushUrl = kEndPointUrl;
|
||||
|
||||
do_register_cleanup(function() {
|
||||
MozLoopServiceInternal.mocks.pushHandler = undefined;
|
||||
Services.prefs.clearUserPref(LOOP_INITIAL_DELAY_PREF);
|
||||
Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
|
||||
Services.prefs.clearUserPref(LOOP_FXA_PROFILE_PREF);
|
||||
|
@ -31,7 +31,7 @@ add_test(function test_registration_invalid_token() {
|
||||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
// Due to the way the time stamp checking code works in hawkclient, we expect a couple
|
||||
// of authorization requests before we reset the token.
|
||||
Assert.equal(authorizationAttempts, 2);
|
||||
@ -43,8 +43,7 @@ add_test(function test_registration_invalid_token() {
|
||||
});
|
||||
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
do_register_cleanup(function() {
|
||||
|
@ -16,7 +16,7 @@ add_test(function test_registration_returns_hawk_session_token() {
|
||||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
var hawkSessionPref;
|
||||
try {
|
||||
hawkSessionPref = Services.prefs.getCharPref("loop.hawk-session-token");
|
||||
@ -31,8 +31,7 @@ add_test(function test_registration_returns_hawk_session_token() {
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
do_register_cleanup(function() {
|
||||
|
@ -24,7 +24,7 @@ add_test(function test_registration_uses_hawk_session_token() {
|
||||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
run_next_test();
|
||||
}, err => {
|
||||
do_throw("shouldn't error on a succesful request");
|
||||
@ -32,8 +32,7 @@ add_test(function test_registration_uses_hawk_session_token() {
|
||||
});
|
||||
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
do_register_cleanup(function() {
|
||||
|
@ -16,7 +16,7 @@ add_test(function test_registration_handles_bogus_hawk_token() {
|
||||
response.finish();
|
||||
});
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
do_throw("should not succeed with a bogus token");
|
||||
}, err => {
|
||||
|
||||
@ -36,8 +36,7 @@ add_test(function test_registration_handles_bogus_hawk_token() {
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
|
||||
do_register_cleanup(function() {
|
||||
@ -45,5 +44,4 @@ function run_test()
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ add_test(function test_getAllRooms() {
|
||||
returnRoomDetails(response, "Third Room Name");
|
||||
});
|
||||
|
||||
MozLoopService.register(mockPushHandler).then(() => {
|
||||
MozLoopService.register().then(() => {
|
||||
|
||||
LoopRooms.getAll((error, rooms) => {
|
||||
do_check_false(error);
|
||||
@ -113,8 +113,7 @@ add_test(function test_getAllRooms() {
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
setupFakeLoopServer();
|
||||
mockPushHandler.registrationPushURL = kEndPointUrl;
|
||||
|
||||
|
@ -2,6 +2,47 @@
|
||||
* 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/. */
|
||||
|
||||
// Sample from https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms
|
||||
var fakeRooms = [
|
||||
{
|
||||
"roomToken": "_nxD4V4FflQ",
|
||||
"roomName": "First Room Name",
|
||||
"roomUrl": "http://localhost:3000/rooms/_nxD4V4FflQ",
|
||||
"roomOwner": "Alexis",
|
||||
"maxSize": 2,
|
||||
"creationTime": 1405517546,
|
||||
"ctime": 1405517546,
|
||||
"expiresAt": 1405534180,
|
||||
"participants": []
|
||||
},
|
||||
{
|
||||
"roomToken": "QzBbvGmIZWU",
|
||||
"roomName": "Second Room Name",
|
||||
"roomUrl": "http://localhost:3000/rooms/QzBbvGmIZWU",
|
||||
"roomOwner": "Alexis",
|
||||
"maxSize": 2,
|
||||
"creationTime": 1405517546,
|
||||
"ctime": 1405517546,
|
||||
"expiresAt": 1405534180,
|
||||
"participants": []
|
||||
},
|
||||
{
|
||||
"roomToken": "3jKS_Els9IU",
|
||||
"roomName": "UX Discussion",
|
||||
"roomUrl": "http://localhost:3000/rooms/3jKS_Els9IU",
|
||||
"roomOwner": "Alexis",
|
||||
"maxSize": 2,
|
||||
"clientMaxSize": 2,
|
||||
"creationTime": 1405517546,
|
||||
"ctime": 1405517818,
|
||||
"expiresAt": 1405534180,
|
||||
"participants": [
|
||||
{ "displayName": "Alexis", "account": "alexis@example.com", "roomConnectionId": "2a1787a6-4a73-43b5-ae3e-906ec1e763cb" },
|
||||
{ "displayName": "Adam", "roomConnectionId": "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Faking the mozLoop object which doesn't exist in regular web pages.
|
||||
* @type {Object}
|
||||
@ -22,5 +63,10 @@ navigator.mozLoop = {
|
||||
},
|
||||
on: function() {}
|
||||
},
|
||||
rooms: {
|
||||
getAll: function(callback) {
|
||||
callback(null, fakeRooms);
|
||||
}
|
||||
},
|
||||
fxAEnabled: true
|
||||
};
|
||||
|
@ -59,7 +59,7 @@
|
||||
var dispatcher = new loop.Dispatcher();
|
||||
var roomListStore = new loop.store.RoomListStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: {}
|
||||
mozLoop: navigator.mozLoop
|
||||
});
|
||||
|
||||
// Local mocks
|
||||
|
@ -59,7 +59,7 @@
|
||||
var dispatcher = new loop.Dispatcher();
|
||||
var roomListStore = new loop.store.RoomListStore({
|
||||
dispatcher: dispatcher,
|
||||
mozLoop: {}
|
||||
mozLoop: navigator.mozLoop
|
||||
});
|
||||
|
||||
// Local mocks
|
||||
|
@ -97,6 +97,19 @@ var gMainPane = {
|
||||
e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
let uAppData = OS.Constants.Path.userApplicationDataDir;
|
||||
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
|
||||
|
||||
setEventListener("separateProfileMode", "command", gMainPane.separateProfileModeChange);
|
||||
let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
|
||||
setEventListener("getStarted", "click", gMainPane.onGetStarted);
|
||||
|
||||
OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
|
||||
() => separateProfileModeCheckbox.checked = true);
|
||||
#endif
|
||||
|
||||
// Notify observers that the UI is now ready
|
||||
Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService)
|
||||
@ -154,6 +167,66 @@ var gMainPane = {
|
||||
},
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
separateProfileModeChange: function ()
|
||||
{
|
||||
function quitApp() {
|
||||
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile);
|
||||
}
|
||||
function revertCheckbox(error) {
|
||||
separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
|
||||
if (error) {
|
||||
Cu.reportError("Failed to toggle separate profile mode: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
const Cc = Components.classes, Ci = Components.interfaces;
|
||||
let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
|
||||
let brandName = document.getElementById("bundleBrand").getString("brandShortName");
|
||||
let bundle = document.getElementById("bundlePreferences");
|
||||
let msg = bundle.getFormattedString(separateProfileModeCheckbox.checked ?
|
||||
"featureEnableRequiresRestart" : "featureDisableRequiresRestart",
|
||||
[brandName]);
|
||||
let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
|
||||
let shouldProceed = Services.prompt.confirm(window, title, msg)
|
||||
if (shouldProceed) {
|
||||
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Ci.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
|
||||
"restart");
|
||||
shouldProceed = !cancelQuit.data;
|
||||
|
||||
if (shouldProceed) {
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
let uAppData = OS.Constants.Path.userApplicationDataDir;
|
||||
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
|
||||
|
||||
if (separateProfileModeCheckbox.checked) {
|
||||
OS.File.remove(ignoreSeparateProfile).then(quitApp, revertCheckbox);
|
||||
} else {
|
||||
OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(quitApp, revertCheckbox);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Revert the checkbox in case we didn't quit
|
||||
revertCheckbox();
|
||||
},
|
||||
|
||||
onGetStarted: function (aEvent) {
|
||||
const Cc = Components.classes, Ci = Components.interfaces;
|
||||
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Ci.nsIWindowMediator);
|
||||
let win = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
if (win) {
|
||||
let accountsTab = win.gBrowser.addTab("about:accounts?action=migrateToDevEdition");
|
||||
win.gBrowser.selectedTab = accountsTab;
|
||||
}
|
||||
},
|
||||
#endif
|
||||
|
||||
// HOME PAGE
|
||||
|
||||
/*
|
||||
|
@ -117,6 +117,17 @@
|
||||
hidden="true">
|
||||
<caption><label>&startup.label;</label></caption>
|
||||
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
<vbox id="separateProfileBox">
|
||||
<checkbox id="separateProfileMode"
|
||||
label="&separateProfileMode.label;"/>
|
||||
<hbox align="center" class="indent">
|
||||
<label id="useFirefoxSync">&useFirefoxSync.label;</label>
|
||||
<label id="getStarted" class="text-link">&getStarted.label;</label>
|
||||
</hbox>
|
||||
</vbox>
|
||||
#endif
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<checkbox id="e10sAutoStart"
|
||||
label="Enable E10S (multi-process)"/>
|
||||
@ -126,13 +137,13 @@
|
||||
<vbox id="defaultBrowserBox">
|
||||
<hbox align="center">
|
||||
<checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
|
||||
label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault2.accesskey;"/>
|
||||
label="&alwaysCheckDefault2.label;" accesskey="&alwaysCheckDefault2.accesskey;"/>
|
||||
</hbox>
|
||||
<deck id="setDefaultPane">
|
||||
<hbox align="center" class="indent">
|
||||
<label id="isNotDefaultLabel" flex="1">&isNotDefault.label;</label>
|
||||
<button id="setDefaultButton"
|
||||
label="&setAsMyDefaultBrowser.label;" accesskey="&setAsMyDefaultBrowser.accesskey;"
|
||||
label="&setAsMyDefaultBrowser2.label;" accesskey="&setAsMyDefaultBrowser2.accesskey;"
|
||||
preference="pref.general.disable_button.default_browser"/>
|
||||
</hbox>
|
||||
<hbox align="center" class="indent">
|
||||
|
@ -95,6 +95,8 @@ function gotoPref(aCategory) {
|
||||
categories.selectedItem = item;
|
||||
window.history.replaceState(category, document.title);
|
||||
search(category, "data-category");
|
||||
let mainContent = document.querySelector(".main-content");
|
||||
mainContent.scrollTop = 0;
|
||||
}
|
||||
|
||||
function search(aQuery, aAttribute) {
|
||||
|
@ -7,8 +7,9 @@ support-files =
|
||||
[browser_advanced_update.js]
|
||||
[browser_bug410900.js]
|
||||
[browser_bug731866.js]
|
||||
[browser_bug1020245_openPreferences_to_paneContent.js]
|
||||
[browser_bug795764_cachedisabled.js]
|
||||
[browser_bug1018066_resetScrollPosition.js]
|
||||
[browser_bug1020245_openPreferences_to_paneContent.js]
|
||||
[browser_connection.js]
|
||||
[browser_connection_bug388287.js]
|
||||
[browser_healthreport.js]
|
||||
|
@ -0,0 +1,27 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Services.prefs.setBoolPref("browser.preferences.inContent", true);
|
||||
|
||||
let originalWindowHeight;
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("browser.preferences.inContent");
|
||||
window.resizeTo(window.outerWidth, originalWindowHeight);
|
||||
while (gBrowser.tabs[1])
|
||||
gBrowser.removeTab(gBrowser.tabs[1]);
|
||||
});
|
||||
|
||||
add_task(function() {
|
||||
originalWindowHeight = window.outerHeight;
|
||||
window.resizeTo(window.outerWidth, 300);
|
||||
let prefs = yield openPreferencesViaOpenPreferencesAPI("paneApplications", undefined, {leaveOpen: true});
|
||||
is(prefs.selectedPane, "paneApplications", "Applications pane was selected");
|
||||
let mainContent = gBrowser.contentDocument.querySelector(".main-content");
|
||||
mainContent.scrollTop = 50;
|
||||
is(mainContent.scrollTop, 50, "main-content should be scrolled 50 pixels");
|
||||
|
||||
gBrowser.contentWindow.gotoPref("paneGeneral");
|
||||
is(mainContent.scrollTop, 0,
|
||||
"Switching to a different category should reset the scroll position");
|
||||
});
|
||||
|
@ -25,28 +25,6 @@ add_task(function() {
|
||||
is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
|
||||
});
|
||||
|
||||
function openPreferencesViaOpenPreferencesAPI(aPane, aAdvancedTab) {
|
||||
let deferred = Promise.defer();
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:blank");
|
||||
openPreferences(aPane, aAdvancedTab ? {advancedTab: aAdvancedTab} : undefined);
|
||||
let newTabBrowser = gBrowser.selectedBrowser;
|
||||
|
||||
newTabBrowser.addEventListener("Initialized", function PrefInit() {
|
||||
newTabBrowser.removeEventListener("Initialized", PrefInit, true);
|
||||
newTabBrowser.contentWindow.addEventListener("load", function prefLoad() {
|
||||
newTabBrowser.contentWindow.removeEventListener("load", prefLoad);
|
||||
let win = gBrowser.contentWindow;
|
||||
let selectedPane = win.history.state;
|
||||
let doc = win.document;
|
||||
let selectedAdvancedTab = aAdvancedTab && doc.getElementById("advancedPrefs").selectedTab.id;
|
||||
gBrowser.removeCurrentTab();
|
||||
deferred.resolve({selectedPane: selectedPane, selectedAdvancedTab: selectedAdvancedTab});
|
||||
});
|
||||
}, true);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function openPreferencesViaHash(aPane) {
|
||||
let deferred = Promise.defer();
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:preferences" + (aPane ? "#" + aPane : ""));
|
||||
|
@ -117,3 +117,25 @@ function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) {
|
||||
return eventDeferred.promise.then(cleanup, cleanup);
|
||||
}
|
||||
|
||||
function openPreferencesViaOpenPreferencesAPI(aPane, aAdvancedTab, aOptions) {
|
||||
let deferred = Promise.defer();
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:blank");
|
||||
openPreferences(aPane, aAdvancedTab ? {advancedTab: aAdvancedTab} : undefined);
|
||||
let newTabBrowser = gBrowser.selectedBrowser;
|
||||
|
||||
newTabBrowser.addEventListener("Initialized", function PrefInit() {
|
||||
newTabBrowser.removeEventListener("Initialized", PrefInit, true);
|
||||
newTabBrowser.contentWindow.addEventListener("load", function prefLoad() {
|
||||
newTabBrowser.contentWindow.removeEventListener("load", prefLoad);
|
||||
let win = gBrowser.contentWindow;
|
||||
let selectedPane = win.history.state;
|
||||
let doc = win.document;
|
||||
let selectedAdvancedTab = aAdvancedTab && doc.getElementById("advancedPrefs").selectedTab.id;
|
||||
if (!aOptions || !aOptions.leaveOpen)
|
||||
gBrowser.removeCurrentTab();
|
||||
deferred.resolve({selectedPane: selectedPane, selectedAdvancedTab: selectedAdvancedTab});
|
||||
});
|
||||
}, true);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -60,12 +60,87 @@ var gMainPane = {
|
||||
|
||||
this.updateBrowserStartupLastSession();
|
||||
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
|
||||
let listener = gMainPane.separateProfileModeChange.bind(gMainPane);
|
||||
separateProfileModeCheckbox.addEventListener("command", listener);
|
||||
|
||||
let getStartedLink = document.getElementById("getStarted");
|
||||
let syncListener = gMainPane.onGetStarted.bind(gMainPane);
|
||||
getStartedLink.addEventListener("click", syncListener);
|
||||
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
let uAppData = OS.Constants.Path.userApplicationDataDir;
|
||||
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
|
||||
|
||||
OS.File.stat(ignoreSeparateProfile).then(() => separateProfileModeCheckbox.checked = false,
|
||||
() => separateProfileModeCheckbox.checked = true);
|
||||
#endif
|
||||
|
||||
// Notify observers that the UI is now ready
|
||||
Components.classes["@mozilla.org/observer-service;1"]
|
||||
.getService(Components.interfaces.nsIObserverService)
|
||||
.notifyObservers(window, "main-pane-loaded", null);
|
||||
},
|
||||
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
separateProfileModeChange: function ()
|
||||
{
|
||||
function quitApp() {
|
||||
Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile);
|
||||
}
|
||||
function revertCheckbox(error) {
|
||||
separateProfileModeCheckbox.checked = !separateProfileModeCheckbox.checked;
|
||||
if (error) {
|
||||
Cu.reportError("Failed to toggle separate profile mode: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
let separateProfileModeCheckbox = document.getElementById("separateProfileMode");
|
||||
let brandName = document.getElementById("bundleBrand").getString("brandShortName");
|
||||
let bundle = document.getElementById("bundlePreferences");
|
||||
let msg = bundle.getFormattedString(separateProfileModeCheckbox.checked ?
|
||||
"featureEnableRequiresRestart" : "featureDisableRequiresRestart",
|
||||
[brandName]);
|
||||
let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
|
||||
let shouldProceed = Services.prompt.confirm(window, title, msg)
|
||||
if (shouldProceed) {
|
||||
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Ci.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
|
||||
"restart");
|
||||
shouldProceed = !cancelQuit.data;
|
||||
|
||||
if (shouldProceed) {
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
let uAppData = OS.Constants.Path.userApplicationDataDir;
|
||||
let ignoreSeparateProfile = OS.Path.join(uAppData, "ignore-dev-edition-profile");
|
||||
|
||||
if (separateProfileModeCheckbox.checked) {
|
||||
OS.File.remove(ignoreSeparateProfile).then(quitApp, revertCheckbox);
|
||||
} else {
|
||||
OS.File.writeAtomic(ignoreSeparateProfile, new Uint8Array()).then(quitApp, revertCheckbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Revert the checkbox in case we didn't quit
|
||||
revertCheckbox();
|
||||
},
|
||||
|
||||
onGetStarted: function (aEvent) {
|
||||
const Cc = Components.classes, Ci = Components.interfaces;
|
||||
let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
|
||||
.getService(Ci.nsIWindowMediator);
|
||||
let win = wm.getMostRecentWindow("navigator:browser");
|
||||
|
||||
if (win) {
|
||||
let accountsTab = win.gBrowser.addTab("about:accounts?action=migrateToDevEdition");
|
||||
win.gBrowser.selectedTab = accountsTab;
|
||||
}
|
||||
},
|
||||
#endif
|
||||
|
||||
// HOME PAGE
|
||||
|
||||
/*
|
||||
|
@ -77,17 +77,28 @@
|
||||
<groupbox id="startupGroup">
|
||||
<caption label="&startup.label;"/>
|
||||
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
<vbox id="separateProfileBox">
|
||||
<checkbox id="separateProfileMode"
|
||||
label="&separateProfileMode.label;"/>
|
||||
<hbox align="center" class="indent">
|
||||
<label id="useFirefoxSync">&useFirefoxSync.label;</label>
|
||||
<label id="getStarted" class="text-link">&getStarted.label;</label>
|
||||
</hbox>
|
||||
</vbox>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SHELL_SERVICE
|
||||
<vbox id="defaultBrowserBox">
|
||||
<hbox align="center">
|
||||
<checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
|
||||
label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault2.accesskey;"/>
|
||||
label="&alwaysCheckDefault2.label;" accesskey="&alwaysCheckDefault2.accesskey;"/>
|
||||
</hbox>
|
||||
<deck id="setDefaultPane">
|
||||
<hbox align="center" class="indent">
|
||||
<label id="isNotDefaultLabel" flex="1">&isNotDefault.label;</label>
|
||||
<button id="setDefaultButton"
|
||||
label="&setAsMyDefaultBrowser.label;" accesskey="&setAsMyDefaultBrowser.accesskey;"
|
||||
label="&setAsMyDefaultBrowser2.label;" accesskey="&setAsMyDefaultBrowser2.accesskey;"
|
||||
oncommand="gMainPane.setDefaultBrowser();"
|
||||
preference="pref.general.disable_button.default_browser"/>
|
||||
</hbox>
|
||||
|
@ -594,7 +594,7 @@ let SessionStoreInternal = {
|
||||
// manager message, so the target will be a <xul:browser>.
|
||||
var browser = aMessage.target;
|
||||
var win = browser.ownerDocument.defaultView;
|
||||
let tab = this._getTabForBrowser(browser);
|
||||
let tab = win.gBrowser.getTabForBrowser(browser);
|
||||
if (!tab) {
|
||||
// Ignore messages from <browser> elements that are not tabs.
|
||||
return;
|
||||
@ -2971,24 +2971,6 @@ let SessionStoreInternal = {
|
||||
return window;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tab for the given browser. This should be marginally better
|
||||
* than using tabbrowser's getTabForContentWindow. This assumes the browser
|
||||
* is the linkedBrowser of a tab, not a dangling browser.
|
||||
*
|
||||
* @param aBrowser
|
||||
* The browser from which to get the tab.
|
||||
*/
|
||||
_getTabForBrowser: function ssi_getTabForBrowser(aBrowser) {
|
||||
let window = aBrowser.ownerDocument.defaultView;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
let tab = window.gBrowser.tabs[i];
|
||||
if (tab.linkedBrowser == aBrowser)
|
||||
return tab;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether or not to resume session, if not recovering from a crash.
|
||||
* @returns bool
|
||||
|
@ -31,10 +31,13 @@
|
||||
<!ENTITY alwaysAsk.label "Always ask me where to save files">
|
||||
<!ENTITY alwaysAsk.accesskey "A">
|
||||
|
||||
<!ENTITY alwaysCheckDefault.label "Always check to see if &brandShortName; is the default browser on startup">
|
||||
<!ENTITY alwaysCheckDefault2.label "Always check if &brandShortName; is your default browser">
|
||||
<!ENTITY alwaysCheckDefault2.accesskey "w">
|
||||
<!ENTITY setAsMyDefaultBrowser.label "Make &brandShortName; My Default Browser">
|
||||
<!ENTITY setAsMyDefaultBrowser.accesskey "D">
|
||||
<!ENTITY setAsMyDefaultBrowser2.label "Make Default">
|
||||
<!ENTITY setAsMyDefaultBrowser2.accesskey "D">
|
||||
<!ENTITY isDefault.label "&brandShortName; is currently your default browser">
|
||||
<!ENTITY isNotDefault.label "&brandShortName; is not your default browser">
|
||||
|
||||
<!ENTITY separateProfileMode.label "Allow &brandShortName; and Firefox to run at the same time">
|
||||
<!ENTITY useFirefoxSync.label "Tip: This uses separate profiles. Use Sync to share data between them.">
|
||||
<!ENTITY getStarted.label "Start using Sync…">
|
||||
|
@ -55,6 +55,11 @@ label.small {
|
||||
}
|
||||
|
||||
/* General Pane */
|
||||
#useFirefoxSync,
|
||||
#getStarted {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#isNotDefaultLabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -169,6 +169,11 @@ caption {
|
||||
|
||||
/* General Pane */
|
||||
|
||||
#useFirefoxSync,
|
||||
#getStarted {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#isNotDefaultLabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -110,6 +110,15 @@ treecol {
|
||||
|
||||
/* General Pane */
|
||||
|
||||
#useFirefoxSync {
|
||||
font-size: 90%;
|
||||
-moz-margin-end: 8px !important;
|
||||
}
|
||||
|
||||
#getStarted {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#isNotDefaultLabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -55,6 +55,11 @@ label.small {
|
||||
|
||||
/* General Pane */
|
||||
|
||||
#useFirefoxSync,
|
||||
#getStarted {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#isNotDefaultLabel {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -625,7 +625,7 @@ class ReftestOptions(OptionParser):
|
||||
# Certain paths do not make sense when we're cross compiling Fennec. This
|
||||
# logic is cribbed from the example in
|
||||
# python/mozbuild/mozbuild/mach_commands.py.
|
||||
defaults['appname'] = build_obj.get_binary_path() if \
|
||||
defaults['app'] = build_obj.get_binary_path() if \
|
||||
build_obj and build_obj.substs['MOZ_BUILD_APP'] != 'mobile/android' else None
|
||||
|
||||
self.add_option("--extra-profile-file",
|
||||
|
@ -104,6 +104,7 @@ skip-if = android_version == "10"
|
||||
[testJNI]
|
||||
# [testMozPay] # see bug 945675
|
||||
[testNetworkManager]
|
||||
[testOfflinePage]
|
||||
[testOrderedBroadcast]
|
||||
[testOSLocale]
|
||||
[testResourceSubstitutions]
|
||||
|
11
mobile/android/base/tests/testOfflinePage.java
Normal file
11
mobile/android/base/tests/testOfflinePage.java
Normal file
@ -0,0 +1,11 @@
|
||||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko.tests;
|
||||
|
||||
public class testOfflinePage extends JavascriptTest {
|
||||
public testOfflinePage() {
|
||||
super("testOfflinePage.js");
|
||||
}
|
||||
}
|
107
mobile/android/base/tests/testOfflinePage.js
Normal file
107
mobile/android/base/tests/testOfflinePage.js
Normal file
@ -0,0 +1,107 @@
|
||||
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
|
||||
function ok(passed, text) {
|
||||
do_report_result(passed, text, Components.stack.caller, false);
|
||||
}
|
||||
|
||||
function is(lhs, rhs, text) {
|
||||
do_report_result(lhs === rhs, text, Components.stack.caller, false);
|
||||
}
|
||||
|
||||
// The chrome window
|
||||
let chromeWin;
|
||||
|
||||
// Track the <browser> where the tests are happening
|
||||
let browser;
|
||||
|
||||
// The proxy setting
|
||||
let proxyPrefValue;
|
||||
|
||||
const kUniqueURI = Services.io.newURI("http://mochi.test:8888/tests/robocop/video_controls.html", null, null);
|
||||
|
||||
add_test(function setup_browser() {
|
||||
// Tests always connect to localhost, and per bug 87717, localhost is now
|
||||
// reachable in offline mode. To avoid this, disable any proxy.
|
||||
proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
|
||||
Services.prefs.setIntPref("network.proxy.type", 0);
|
||||
|
||||
// Clear network cache.
|
||||
Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService).clear();
|
||||
|
||||
chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let BrowserApp = chromeWin.BrowserApp;
|
||||
|
||||
do_register_cleanup(function cleanup() {
|
||||
BrowserApp.closeTab(BrowserApp.getTabForBrowser(browser));
|
||||
Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
|
||||
Services.io.offline = false;
|
||||
});
|
||||
|
||||
do_test_pending();
|
||||
|
||||
// Add a new tab with a blank page so we can better control the real page load and the offline state
|
||||
browser = BrowserApp.addTab("about:blank", { selected: true, parentId: BrowserApp.selectedTab.id }).browser;
|
||||
|
||||
// Go offline, expecting the error page.
|
||||
Services.io.offline = true;
|
||||
|
||||
// Load our test web page
|
||||
browser.addEventListener("DOMContentLoaded", errorListener, true);
|
||||
browser.loadURI(kUniqueURI.spec, null, null)
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// listen to loading the neterror page. (offline mode)
|
||||
function errorListener() {
|
||||
if (browser.contentWindow.location == "about:blank") {
|
||||
do_print("got about:blank, which is expected once, so return");
|
||||
return;
|
||||
}
|
||||
|
||||
browser.removeEventListener("DOMContentLoaded", errorListener, true);
|
||||
ok(Services.io.offline, "Services.io.offline is true.");
|
||||
|
||||
// This is an error page.
|
||||
is(browser.contentDocument.documentURI.substring(0, 27), "about:neterror?e=netOffline", "Document URI is the error page.");
|
||||
|
||||
// But location bar should show the original request.
|
||||
is(browser.contentWindow.location.href, kUniqueURI.spec, "Docshell URI is the original URI.");
|
||||
|
||||
Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
|
||||
|
||||
// Now press the "Try Again" button, with offline mode off.
|
||||
Services.io.offline = false;
|
||||
|
||||
browser.addEventListener("DOMContentLoaded", reloadListener, true);
|
||||
|
||||
ok(browser.contentDocument.getElementById("errorTryAgain"), "The error page has got a #errorTryAgain element");
|
||||
browser.contentDocument.getElementById("errorTryAgain").click();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// listen to reload of neterror.
|
||||
function reloadListener() {
|
||||
browser.removeEventListener("DOMContentLoaded", reloadListener, true);
|
||||
|
||||
ok(!Services.io.offline, "Services.io.offline is false.");
|
||||
|
||||
// This is not an error page.
|
||||
is(browser.contentDocument.documentURI, kUniqueURI.spec, "Document URI is not the offline-error page, but the original URI.");
|
||||
|
||||
do_test_finished();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
run_next_test();
|
@ -159,6 +159,38 @@ let Bookmarks = Object.freeze({
|
||||
}
|
||||
|
||||
let item = yield insertBookmark(insertInfo, parent);
|
||||
|
||||
// Notify onItemAdded to listeners.
|
||||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
// We need the itemId to notify, though once the switch to guids is
|
||||
// complete we may stop using it.
|
||||
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
|
||||
let itemId = yield PlacesUtils.promiseItemId(item.guid);
|
||||
notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
|
||||
item.type, uri, item.title || null,
|
||||
toPRTime(item.dateAdded), item.guid,
|
||||
item.parentGuid ]);
|
||||
|
||||
// If a keyword is defined, notify onItemChanged for it.
|
||||
if (item.keyword) {
|
||||
notify(observers, "onItemChanged", [ itemId, "keyword", false,
|
||||
item.keyword,
|
||||
toPRTime(item.lastModified),
|
||||
item.type, parent._id, item.guid,
|
||||
item.parentGuid ]);
|
||||
}
|
||||
|
||||
// If it's a tag, notify OnItemChanged to all bookmarks for this URL.
|
||||
let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
|
||||
if (isTagging) {
|
||||
for (let entry of (yield fetchBookmarksByURL(item))) {
|
||||
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
|
||||
toPRTime(entry.lastModified),
|
||||
entry.type, entry._parentId,
|
||||
entry.guid, entry.parentGuid ]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non-enumerable properties.
|
||||
return Object.assign({}, item);
|
||||
}.bind(this));
|
||||
@ -275,6 +307,59 @@ let Bookmarks = Object.freeze({
|
||||
updateFrecency(db, [updatedItem.url]).then(null, Cu.reportError);
|
||||
}
|
||||
|
||||
// Notify onItemChanged to listeners.
|
||||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
// For lastModified, we only care about the original input, since we
|
||||
// should not notify implciit lastModified changes.
|
||||
if (info.hasOwnProperty("lastModified") &&
|
||||
updateInfo.hasOwnProperty("lastModified") &&
|
||||
item.lastModified != updatedItem.lastModified) {
|
||||
notify(observers, "onItemChanged", [ updatedItem._id, "lastModified",
|
||||
false,
|
||||
`${toPRTime(updatedItem.lastModified)}`,
|
||||
toPRTime(updatedItem.lastModified),
|
||||
updatedItem.type,
|
||||
updatedItem._parentId,
|
||||
updatedItem.guid,
|
||||
updatedItem.parentGuid ]);
|
||||
}
|
||||
if (updateInfo.hasOwnProperty("title")) {
|
||||
notify(observers, "onItemChanged", [ updatedItem._id, "title",
|
||||
false, updatedItem.title,
|
||||
toPRTime(updatedItem.lastModified),
|
||||
updatedItem.type,
|
||||
updatedItem._parentId,
|
||||
updatedItem.guid,
|
||||
updatedItem.parentGuid ]);
|
||||
}
|
||||
if (updateInfo.hasOwnProperty("url")) {
|
||||
notify(observers, "onItemChanged", [ updatedItem._id, "uri",
|
||||
false, updatedItem.url.href,
|
||||
toPRTime(updatedItem.lastModified),
|
||||
updatedItem.type,
|
||||
updatedItem._parentId,
|
||||
updatedItem.guid,
|
||||
updatedItem.parentGuid ]);
|
||||
}
|
||||
if (updateInfo.hasOwnProperty("keyword")) {
|
||||
notify(observers, "onItemChanged", [ updatedItem._id, "keyword",
|
||||
false, updatedItem.keyword,
|
||||
toPRTime(updatedItem.lastModified),
|
||||
updatedItem.type,
|
||||
updatedItem._parentId,
|
||||
updatedItem.guid,
|
||||
updatedItem.parentGuid ]);
|
||||
}
|
||||
// If the item was move, notify onItemMoved.
|
||||
if (item.parentGuid != updatedItem.parentGuid ||
|
||||
item.index != updatedItem.index) {
|
||||
notify(observers, "onItemMoved", [ updatedItem._id, item._parentId,
|
||||
item.index, updatedItem._parentId,
|
||||
updatedItem.index, updatedItem.type,
|
||||
updatedItem.guid, item.parentGuid,
|
||||
updatedItem.newParentGuid ]);
|
||||
}
|
||||
|
||||
// Remove non-enumerable properties.
|
||||
return Object.assign({}, updatedItem);
|
||||
}.bind(this));
|
||||
@ -313,8 +398,25 @@ let Bookmarks = Object.freeze({
|
||||
if (!item._parentId || item._parentId == PlacesUtils.placesRootId)
|
||||
throw new Error("It's not possible to remove Places root folders.");
|
||||
|
||||
let isUntagging = item._grandParentId == PlacesUtils.bookmarks.tagsFolderId;
|
||||
item = yield removeBookmark(item, isUntagging);
|
||||
item = yield removeBookmark(item);
|
||||
|
||||
// Notify onItemRemoved to listeners.
|
||||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
|
||||
notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
|
||||
item.type, uri, item.guid,
|
||||
item.parentGuid ]);
|
||||
|
||||
let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
|
||||
if (isUntagging) {
|
||||
for (let entry of (yield fetchBookmarksByURL(item))) {
|
||||
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
|
||||
toPRTime(entry.lastModified),
|
||||
entry.type, entry._parentId,
|
||||
entry.guid, entry.parentGuid ]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non-enumerable properties.
|
||||
return Object.assign({}, item);
|
||||
});
|
||||
@ -335,15 +437,16 @@ let Bookmarks = Object.freeze({
|
||||
let rows = yield db.executeCached(
|
||||
`WITH RECURSIVE
|
||||
descendants(did) AS (
|
||||
SELECT folder_id FROM moz_bookmarks_roots
|
||||
WHERE root_name IN ("toolbar", "menu", "unfiled")
|
||||
SELECT id FROM moz_bookmarks
|
||||
WHERE parent IN (SELECT folder_id FROM moz_bookmarks_roots
|
||||
WHERE root_name IN ("toolbar", "menu", "unfiled"))
|
||||
UNION ALL
|
||||
SELECT id FROM moz_bookmarks
|
||||
JOIN descendants ON parent = did
|
||||
)
|
||||
SELECT b.id AS _id, b.parent AS _parentId, b.position AS 'index',
|
||||
b.type, url, b.guid, p.guid AS parentGuid, b.dateAdded,
|
||||
b.lastModified, b.title, NULL AS _grandParentId,
|
||||
b.lastModified, b.title, p.parent AS _grandParentId,
|
||||
NULL AS _childCount, NULL AS keyword
|
||||
FROM moz_bookmarks b
|
||||
JOIN moz_bookmarks p ON p.id = b.parent
|
||||
@ -355,8 +458,9 @@ let Bookmarks = Object.freeze({
|
||||
yield db.executeCached(
|
||||
`WITH RECURSIVE
|
||||
descendants(did) AS (
|
||||
SELECT folder_id FROM moz_bookmarks_roots
|
||||
WHERE root_name IN ("toolbar", "menu", "unfiled")
|
||||
SELECT id FROM moz_bookmarks
|
||||
WHERE parent IN (SELECT folder_id FROM moz_bookmarks_roots
|
||||
WHERE root_name IN ("toolbar", "menu", "unfiled"))
|
||||
UNION ALL
|
||||
SELECT id FROM moz_bookmarks
|
||||
JOIN descendants ON parent = did
|
||||
@ -380,9 +484,28 @@ let Bookmarks = Object.freeze({
|
||||
let urls = [for (item of items) if (item.url) item.url];
|
||||
updateFrecency(db, urls).then(null, Cu.reportError);
|
||||
|
||||
// TODO: send notifications
|
||||
// Send onItemRemoved notifications to listeners.
|
||||
// TODO (Bug 1087580): this should send a single clear bookmarks
|
||||
// notification rather than notifying for each bookmark.
|
||||
|
||||
// Notify listeners in reverse order to serve children before parents.
|
||||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
for (let item of items.reverse()) {
|
||||
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
|
||||
notify(observers, "onItemRemoved", [ item._id, item._parentId,
|
||||
item.index, item.type, uri,
|
||||
item.guid, item.parentGuid ]);
|
||||
|
||||
let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
|
||||
if (isUntagging) {
|
||||
for (let entry of (yield fetchBookmarksByURL(item))) {
|
||||
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
|
||||
toPRTime(entry.lastModified),
|
||||
entry.type, entry._parentId,
|
||||
entry.guid, entry.parentGuid ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
@ -582,6 +705,24 @@ let Bookmarks = Object.freeze({
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Globals.
|
||||
|
||||
/**
|
||||
* Sends a bookmarks notification through the given observers.
|
||||
*
|
||||
* @param observers
|
||||
* array of nsINavBookmarkObserver objects.
|
||||
* @param notification
|
||||
* the notification name.
|
||||
* @param args
|
||||
* array of arguments to pass to the notification.
|
||||
*/
|
||||
function notify(observers, notification, args) {
|
||||
for (let observer of observers) {
|
||||
try {
|
||||
observer[notification](...args);
|
||||
} catch (ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DBConnPromised",
|
||||
() => new Promise((resolve, reject) => {
|
||||
Sqlite.wrapStorageConnection({ connection: PlacesUtils.history.DBConnection } )
|
||||
@ -706,9 +847,6 @@ function* updateBookmark(info, item, newParent) {
|
||||
function* insertBookmark(item, parent) {
|
||||
let db = yield DBConnPromised;
|
||||
|
||||
let isTaggingURL = item.hasOwnProperty("url") &&
|
||||
parent._parentId == PlacesUtils.bookmarks.tagsFolderId;
|
||||
|
||||
// If a guid was not provided, generate one, so we won't need to fetch the
|
||||
// bookmark just after having created it.
|
||||
if (!item.hasOwnProperty("guid"))
|
||||
@ -752,19 +890,12 @@ function* insertBookmark(item, parent) {
|
||||
});
|
||||
|
||||
// If not a tag recalculate frecency...
|
||||
if (item.type == Bookmarks.TYPE_BOOKMARK && !isTaggingURL) {
|
||||
let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
|
||||
if (item.type == Bookmarks.TYPE_BOOKMARK && !isTagging) {
|
||||
// ...though we don't wait for the calculation.
|
||||
updateFrecency(db, [item.url]).then(null, Cu.reportError);
|
||||
}
|
||||
|
||||
// Notify onItemAdded
|
||||
// TODO
|
||||
|
||||
// If it's a tag notify OnItemChanged to all bookmarks for this URL.
|
||||
if (isTaggingURL) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// Don't return an empty title to the caller.
|
||||
if (item.hasOwnProperty("title") && item.title === null)
|
||||
delete item.title;
|
||||
@ -858,14 +989,17 @@ function* fetchBookmarksByKeyword(info) {
|
||||
function* removeBookmark(item) {
|
||||
let db = yield DBConnPromised;
|
||||
|
||||
|
||||
let isUntagging = item._grandParentId == PlacesUtils.bookmarks.tagsFolderId;
|
||||
// Remove annotations first. If this is a tag, we can avoid paying that cost.
|
||||
if (!isUntagging) {
|
||||
PlacesUtils.annotations.removeItemAnnotations(item._id);
|
||||
}
|
||||
let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
|
||||
|
||||
yield db.executeTransaction(function* transaction() {
|
||||
// Remove annotations first. If it's a tag, we can avoid paying that cost.
|
||||
if (!isUntagging) {
|
||||
// We don't go through the annotations service for this cause otherwise
|
||||
// we'd get a pointless onItemChanged notification and it would also
|
||||
// set lastModified to an unexpected value.
|
||||
yield removeAnnotationsForItem(db, item._id);
|
||||
}
|
||||
|
||||
// Remove the bookmark from the database.
|
||||
yield db.executeCached(
|
||||
`DELETE FROM moz_bookmarks WHERE guid = :guid`, { guid: item.guid });
|
||||
@ -889,14 +1023,6 @@ function* removeBookmark(item) {
|
||||
updateFrecency(db, [item.url]).then(null, Cu.reportError);
|
||||
}
|
||||
|
||||
// Notify onItemRemoved.
|
||||
// TODO
|
||||
|
||||
// If we are untagging a bookmark, notify OnItemChanged.
|
||||
if (isUntagging) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@ -953,6 +1079,15 @@ function removeSameValueProperties(dest, src) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an URL object to an nsIURI.
|
||||
*
|
||||
* @param url
|
||||
* the URL object to convert.
|
||||
* @return nsIURI for the given URL.
|
||||
*/
|
||||
function toURI(url) NetUtil.newURI(url.href);
|
||||
|
||||
/**
|
||||
* Reverse a host based on the moz_places algorithm, that is reverse the host
|
||||
* string and add a trialing period. For example "google.com" becomes
|
||||
@ -1203,6 +1338,27 @@ let removeOrphanAnnotations = Task.async(function* (db) {
|
||||
`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Removes annotations for a given item.
|
||||
*
|
||||
* @param db
|
||||
* the Sqlite.jsm connection handle.
|
||||
* @param itemId
|
||||
* internal id of the item for which to remove annotations.
|
||||
*/
|
||||
let removeAnnotationsForItem = Task.async(function* (db, itemId) {
|
||||
yield db.executeCached(
|
||||
`DELETE FROM moz_items_annos
|
||||
WHERE item_id = :id
|
||||
`, { id: itemId });
|
||||
yield db.executeCached(
|
||||
`DELETE FROM moz_anno_attributes
|
||||
WHERE id IN (SELECT n.id from moz_anno_attributes n
|
||||
LEFT JOIN moz_items_annos a ON a.anno_attribute_id = n.id
|
||||
WHERE a.id ISNULL)
|
||||
`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates lastModified for all the ancestors of a given folder GUID.
|
||||
*
|
||||
|
@ -223,7 +223,7 @@ interface nsINavBookmarkObserver : nsISupports
|
||||
* folders. A URI in history can be contained in one or more such folders.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(4C309044-B6DA-4511-AF57-E8940DB00045)]
|
||||
[scriptable, uuid(b0f9a80a-d7f0-4421-8513-444125f0d828)]
|
||||
interface nsINavBookmarksService : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -544,6 +544,12 @@ interface nsINavBookmarksService : nsISupports
|
||||
*/
|
||||
void removeObserver(in nsINavBookmarkObserver observer);
|
||||
|
||||
/**
|
||||
* Gets an array of registered nsINavBookmarkObserver objects.
|
||||
*/
|
||||
void getObservers([optional] out unsigned long count,
|
||||
[retval, array, size_is(count)] out nsINavBookmarkObserver observers);
|
||||
|
||||
/**
|
||||
* Runs the passed callback inside of a database transaction.
|
||||
* Use this when a lot of things are about to change, for example
|
||||
|
@ -2670,6 +2670,47 @@ nsNavBookmarks::RemoveObserver(nsINavBookmarkObserver* aObserver)
|
||||
return mObservers.RemoveWeakElement(aObserver);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavBookmarks::GetObservers(uint32_t* _count,
|
||||
nsINavBookmarkObserver*** _observers)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(_count);
|
||||
NS_ENSURE_ARG_POINTER(_observers);
|
||||
|
||||
*_count = 0;
|
||||
*_observers = nullptr;
|
||||
|
||||
if (!mCanNotify)
|
||||
return NS_OK;
|
||||
|
||||
nsCOMArray<nsINavBookmarkObserver> observers;
|
||||
|
||||
// First add the category cache observers.
|
||||
mCacheObservers.GetEntries(observers);
|
||||
|
||||
// Then add the other observers.
|
||||
for (uint32_t i = 0; i < mObservers.Length(); ++i) {
|
||||
const nsCOMPtr<nsINavBookmarkObserver> &observer = mObservers.ElementAt(i);
|
||||
// Skip nullified weak observers.
|
||||
if (observer)
|
||||
observers.AppendElement(observer);
|
||||
}
|
||||
|
||||
if (observers.Count() == 0)
|
||||
return NS_OK;
|
||||
|
||||
*_observers = static_cast<nsINavBookmarkObserver**>
|
||||
(nsMemory::Alloc(observers.Count() * sizeof(nsINavBookmarkObserver*)));
|
||||
NS_ENSURE_TRUE(*_observers, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
*_count = observers.Count();
|
||||
for (uint32_t i = 0; i < *_count; ++i) {
|
||||
NS_ADDREF((*_observers)[i] = observers[i]);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsNavBookmarks::NotifyItemVisited(const ItemVisitData& aData)
|
||||
{
|
||||
|
@ -78,6 +78,22 @@ add_task(function* test_eraseEverything() {
|
||||
Assert.equal(rows.length, 0);
|
||||
});
|
||||
|
||||
add_task(function* test_eraseEverything_roots() {
|
||||
yield PlacesUtils.bookmarks.eraseEverything();
|
||||
|
||||
// Ensure the roots have not been removed.
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
Assert.ok(yield PlacesUtils.bookmarks.fetch(unfiledGuid));
|
||||
let toolbarGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.toolbarFolderId);
|
||||
Assert.ok(yield PlacesUtils.bookmarks.fetch(toolbarGuid));
|
||||
let menuGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.bookmarksMenuFolderId);
|
||||
Assert.ok(yield PlacesUtils.bookmarks.fetch(menuGuid));
|
||||
let tagsGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.tagsFolderId);
|
||||
Assert.ok(yield PlacesUtils.bookmarks.fetch(tagsGuid));
|
||||
let rootGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.placesRootId);
|
||||
Assert.ok(yield PlacesUtils.bookmarks.fetch(rootGuid));
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
@ -0,0 +1,364 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
add_task(function* insert_separator_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let observer = expectNotifications();
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
|
||||
parentGuid: unfiledGuid});
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
observer.check([ { name: "onItemAdded",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type,
|
||||
null, null, bm.dateAdded,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* insert_folder_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let observer = expectNotifications();
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: unfiledGuid,
|
||||
title: "a folder" });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
observer.check([ { name: "onItemAdded",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type,
|
||||
null, bm.title, bm.dateAdded,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* insert_folder_notitle_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let observer = expectNotifications();
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: unfiledGuid });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
observer.check([ { name: "onItemAdded",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type,
|
||||
null, null, bm.dateAdded,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* insert_bookmark_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let observer = expectNotifications();
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://example.com/"),
|
||||
title: "a bookmark" });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
observer.check([ { name: "onItemAdded",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type,
|
||||
bm.url, bm.title, bm.dateAdded,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* insert_bookmark_notitle_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let observer = expectNotifications();
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://example.com/") });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
observer.check([ { name: "onItemAdded",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type,
|
||||
bm.url, null, bm.dateAdded,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* insert_bookmark_keyword_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let observer = expectNotifications();
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://example.com/"),
|
||||
keyword: "kw" });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
observer.check([ { name: "onItemAdded",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type,
|
||||
bm.url, null, bm.dateAdded,
|
||||
bm.guid, bm.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ itemId, "keyword", false, bm.keyword,
|
||||
bm.lastModified, bm.type, parentId,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* insert_bookmark_tag_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://tag.example.com/") });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
let tagsGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.tagsFolderId);
|
||||
let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: tagsGuid,
|
||||
title: "tag" });
|
||||
let observer = expectNotifications();
|
||||
let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: tagFolder.guid,
|
||||
url: new URL("http://tag.example.com/") });
|
||||
let tagId = yield PlacesUtils.promiseItemId(tag.guid);
|
||||
let tagParentId = yield PlacesUtils.promiseItemId(tag.parentGuid);
|
||||
|
||||
observer.check([ { name: "onItemAdded",
|
||||
arguments: [ tagId, tagParentId, tag.index, tag.type,
|
||||
tag.url, null, tag.dateAdded,
|
||||
tag.guid, tag.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ tagId, "tags", false, "",
|
||||
tag.lastModified, tag.type, tagParentId,
|
||||
tag.guid, tag.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ itemId, "tags", false, "",
|
||||
bm.lastModified, bm.type, parentId,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* update_bookmark_lastModified() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://lastmod.example.com/") });
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
|
||||
lastModified: new Date() });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ itemId, "lastModified", false,
|
||||
`${bm.lastModified * 1000}`, bm.lastModified,
|
||||
bm.type, parentId, bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* update_bookmark_title() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://title.example.com/") });
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
|
||||
title: "new title" });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ itemId, "title", false, bm.title,
|
||||
bm.lastModified, bm.type, parentId, bm.guid,
|
||||
bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* update_bookmark_uri() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://url.example.com/") });
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
|
||||
url: "http://mozilla.org/" });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ itemId, "uri", false, bm.url.href,
|
||||
bm.lastModified, bm.type, parentId, bm.guid,
|
||||
bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* update_bookmark_keyword() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://keyword.example.com/") });
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
|
||||
keyword: "kw" });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ itemId, "keyword", false, bm.keyword,
|
||||
bm.lastModified, bm.type, parentId, bm.guid,
|
||||
bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* remove_bookmark() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://remove.example.com/") });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.remove(bm.guid);
|
||||
// TODO (Bug 653910): onItemAnnotationRemoved notified even if there were no
|
||||
// annotations.
|
||||
observer.check([ { name: "onItemRemoved",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type, bm.url,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* remove_folder() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: unfiledGuid });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
let observer = expectNotifications();
|
||||
bm = yield PlacesUtils.bookmarks.remove(bm.guid);
|
||||
observer.check([ { name: "onItemRemoved",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type, null,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* remove_bookmark_tag_notification() {
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: unfiledGuid,
|
||||
url: new URL("http://untag.example.com/") });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
let tagsGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.tagsFolderId);
|
||||
let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: tagsGuid,
|
||||
title: "tag" });
|
||||
let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: tagFolder.guid,
|
||||
url: new URL("http://untag.example.com/") });
|
||||
let tagId = yield PlacesUtils.promiseItemId(tag.guid);
|
||||
let tagParentId = yield PlacesUtils.promiseItemId(tag.parentGuid);
|
||||
|
||||
let observer = expectNotifications();
|
||||
let removed = yield PlacesUtils.bookmarks.remove(tag.guid);
|
||||
|
||||
observer.check([ { name: "onItemRemoved",
|
||||
arguments: [ tagId, tagParentId, tag.index, tag.type,
|
||||
tag.url, tag.guid, tag.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ itemId, "tags", false, "",
|
||||
bm.lastModified, bm.type, parentId,
|
||||
bm.guid, bm.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
add_task(function* eraseEverything_notification() {
|
||||
// Let's start from a clean situation.
|
||||
yield PlacesUtils.bookmarks.eraseEverything();
|
||||
|
||||
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
|
||||
let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: unfiledGuid });
|
||||
let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
|
||||
let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
|
||||
|
||||
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: folder1.guid,
|
||||
url: new URL("http://example.com/") });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
|
||||
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
|
||||
|
||||
let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: unfiledGuid });
|
||||
let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
|
||||
let folder2ParentId = yield PlacesUtils.promiseItemId(folder2.parentGuid);
|
||||
|
||||
let toolbarGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.toolbarFolderId);
|
||||
let toolbarBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: toolbarGuid,
|
||||
url: new URL("http://example.com/") });
|
||||
let toolbarBmId = yield PlacesUtils.promiseItemId(toolbarBm.guid);
|
||||
let toolbarBmParentId = yield PlacesUtils.promiseItemId(toolbarBm.parentGuid);
|
||||
|
||||
let menuGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.bookmarksMenuFolderId);
|
||||
let menuBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: menuGuid,
|
||||
url: new URL("http://example.com/") });
|
||||
let menuBmId = yield PlacesUtils.promiseItemId(menuBm.guid);
|
||||
let menuBmParentId = yield PlacesUtils.promiseItemId(menuBm.parentGuid);
|
||||
|
||||
let observer = expectNotifications();
|
||||
let removed = yield PlacesUtils.bookmarks.eraseEverything();
|
||||
|
||||
observer.check([ { name: "onItemRemoved",
|
||||
arguments: [ menuBmId, menuBmParentId,
|
||||
menuBm.index, menuBm.type,
|
||||
menuBm.url, menuBm.guid,
|
||||
menuBm.parentGuid ] },
|
||||
{ name: "onItemRemoved",
|
||||
arguments: [ toolbarBmId, toolbarBmParentId,
|
||||
toolbarBm.index, toolbarBm.type,
|
||||
toolbarBm.url, toolbarBm.guid,
|
||||
toolbarBm.parentGuid ] },
|
||||
{ name: "onItemRemoved",
|
||||
arguments: [ folder2Id, folder2ParentId, folder2.index,
|
||||
folder2.type, null, folder2.guid,
|
||||
folder2.parentGuid ] },
|
||||
{ name: "onItemRemoved",
|
||||
arguments: [ itemId, parentId, bm.index, bm.type,
|
||||
bm.url, bm.guid, bm.parentGuid ] },
|
||||
{ name: "onItemRemoved",
|
||||
arguments: [ folder1Id, folder1ParentId, folder1.index,
|
||||
folder1.type, null, folder1.guid,
|
||||
folder1.parentGuid ] }
|
||||
]);
|
||||
});
|
||||
|
||||
function expectNotifications() {
|
||||
let notifications = [];
|
||||
let observer = new Proxy(NavBookmarkObserver, {
|
||||
get(target, name) {
|
||||
if (name == "check") {
|
||||
PlacesUtils.bookmarks.removeObserver(observer);
|
||||
return expectedNotifications =>
|
||||
Assert.deepEqual(notifications, expectedNotifications);
|
||||
}
|
||||
|
||||
if (name.startsWith("onItem")) {
|
||||
return () => {
|
||||
let args = Array.from(arguments, arg => {
|
||||
if (arg && arg instanceof Ci.nsIURI)
|
||||
return new URL(arg.spec);
|
||||
if (arg && typeof(arg) == "number" && arg >= Date.now() * 1000)
|
||||
return new Date(parseInt(arg/1000));
|
||||
return arg;
|
||||
});
|
||||
notifications.push({ name: name, arguments: args });
|
||||
}
|
||||
}
|
||||
|
||||
if (name in target)
|
||||
return target[name];
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
PlacesUtils.bookmarks.addObserver(observer, false);
|
||||
return observer;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
@ -32,6 +32,7 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
|
||||
[test_bookmarks_eraseEverything.js]
|
||||
[test_bookmarks_fetch.js]
|
||||
[test_bookmarks_insert.js]
|
||||
[test_bookmarks_notifications.js]
|
||||
[test_bookmarks_remove.js]
|
||||
[test_bookmarks_update.js]
|
||||
[test_changeBookmarkURI.js]
|
||||
|
@ -1,49 +1,57 @@
|
||||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
// Get services.
|
||||
let bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
||||
getService(Ci.nsINavBookmarksService);
|
||||
let os = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
|
||||
let gDummyCreated = false;
|
||||
let gDummyAdded = false;
|
||||
|
||||
let observer = {
|
||||
observe: function(subject, topic, data) {
|
||||
if (topic == "dummy-observer-created")
|
||||
gDummyCreated = true;
|
||||
else if (topic == "dummy-observer-item-added")
|
||||
gDummyAdded = true;
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
])
|
||||
};
|
||||
|
||||
function verify() {
|
||||
do_check_true(gDummyCreated);
|
||||
do_check_true(gDummyAdded);
|
||||
do_test_finished();
|
||||
function run_test() {
|
||||
run_next_test()
|
||||
}
|
||||
|
||||
// main
|
||||
function run_test() {
|
||||
add_task(function* test_observers() {
|
||||
do_load_manifest("nsDummyObserver.manifest");
|
||||
|
||||
os.addObserver(observer, "dummy-observer-created", true);
|
||||
os.addObserver(observer, "dummy-observer-item-added", true);
|
||||
let dummyCreated = false;
|
||||
let dummyReceivedOnItemAdded = false;
|
||||
|
||||
Services.obs.addObserver(function created() {
|
||||
Services.obs.removeObserver(created, "dummy-observer-created");
|
||||
dummyCreated = true;
|
||||
}, "dummy-observer-created", false);
|
||||
Services.obs.addObserver(function added() {
|
||||
Services.obs.removeObserver(added, "dummy-observer-item-added");
|
||||
dummyReceivedOnItemAdded = true;
|
||||
}, "dummy-observer-item-added", false);
|
||||
|
||||
let initialObservers = PlacesUtils.bookmarks.getObservers();
|
||||
|
||||
// Add a common observer, it should be invoked after the category observer.
|
||||
let notificationsPromised = new Promise((resolve, reject) => {
|
||||
PlacesUtils.bookmarks.addObserver( {
|
||||
__proto__: NavBookmarkObserver.prototype,
|
||||
onItemAdded() {
|
||||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
Assert.equal(observers.length, initialObservers.length + 1);
|
||||
|
||||
// Check the common observer is the last one.
|
||||
for (let i = 0; i < initialObservers.length; ++i) {
|
||||
Assert.equal(initialObservers[i], observers[i]);
|
||||
}
|
||||
|
||||
PlacesUtils.bookmarks.removeObserver(this);
|
||||
observers = PlacesUtils.bookmarks.getObservers();
|
||||
Assert.equal(observers.length, initialObservers.length);
|
||||
|
||||
// Check the category observer has been invoked before this one.
|
||||
Assert.ok(dummyCreated);
|
||||
Assert.ok(dummyReceivedOnItemAdded);
|
||||
resolve();
|
||||
}
|
||||
}, false);
|
||||
});
|
||||
|
||||
// Add a bookmark
|
||||
bs.insertBookmark(bs.unfiledBookmarksFolder, uri("http://typed.mozilla.org"),
|
||||
bs.DEFAULT_INDEX, "bookmark");
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
uri("http://typed.mozilla.org"),
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"bookmark");
|
||||
|
||||
do_test_pending();
|
||||
do_timeout(1000, verify);
|
||||
}
|
||||
yield notificationsPromised;
|
||||
});
|
||||
|
@ -8,40 +8,29 @@
|
||||
|
||||
let Cr = Components.results;
|
||||
|
||||
/**
|
||||
* Print some debug message to the console. All arguments will be printed,
|
||||
* separated by spaces.
|
||||
*
|
||||
* @param [arg0, arg1, arg2, ...]
|
||||
* Any number of arguments to print out
|
||||
* @usage _("Hello World") -> prints "Hello World"
|
||||
* @usage _(1, 2, 3) -> prints "1 2 3"
|
||||
*/
|
||||
let _ = function(some, debug, text, to) print(Array.slice(arguments).join(" "));
|
||||
|
||||
_("Make an array of services to test, each specifying a class id, interface,",
|
||||
"and an array of function names that don't throw when passed nulls");
|
||||
// Make an array of services to test, each specifying a class id, interface
|
||||
// and an array of function names that don't throw when passed nulls
|
||||
let testServices = [
|
||||
["browser/nav-history-service;1", "nsINavHistoryService",
|
||||
["queryStringToQueries", "removePagesByTimeframe", "removePagesFromHost",
|
||||
"removeVisitsByTimeframe"]],
|
||||
["browser/nav-bookmarks-service;1","nsINavBookmarksService",
|
||||
["createFolder"]],
|
||||
["createFolder", "getObservers"]],
|
||||
["browser/livemark-service;2","mozIAsyncLivemarks", ["reloadLivemarks"]],
|
||||
["browser/annotation-service;1","nsIAnnotationService", []],
|
||||
["browser/favicon-service;1","nsIFaviconService", []],
|
||||
["browser/tagging-service;1","nsITaggingService", []],
|
||||
];
|
||||
_(testServices.join("\n"));
|
||||
do_print(testServices.join("\n"));
|
||||
|
||||
function run_test()
|
||||
{
|
||||
testServices.forEach(function([cid, iface, nothrow]) {
|
||||
_("Running test with", cid, iface, nothrow);
|
||||
for (let [cid, iface, nothrow] of testServices) {
|
||||
do_print(`Running test with ${cid} ${iface} ${nothrow}`);
|
||||
let s = Cc["@mozilla.org/" + cid].getService(Ci[iface]);
|
||||
|
||||
let okName = function(name) {
|
||||
_("Checking if function is okay to test:", name);
|
||||
do_print(`Checking if function is okay to test: ${name}`);
|
||||
let func = s[name];
|
||||
|
||||
let mesg = "";
|
||||
@ -52,60 +41,58 @@ function run_test()
|
||||
else if (name == "QueryInterface")
|
||||
mesg = "Ignore QI!";
|
||||
|
||||
if (mesg == "")
|
||||
return true;
|
||||
if (mesg) {
|
||||
do_print(`${mesg} Skipping: ${name}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
_(mesg, "Skipping:", name);
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
_("Generating an array of functions to test service:", s);
|
||||
[i for (i in s) if (okName(i))].sort().forEach(function(n) {
|
||||
_();
|
||||
_("Testing " + iface + " function with null args:", n);
|
||||
do_print(`Generating an array of functions to test service: ${s}`);
|
||||
for (let n of [i for (i in s) if (okName(i))].sort()) {
|
||||
do_print(`\nTesting ${iface} function with null args: ${n}`);
|
||||
|
||||
let func = s[n];
|
||||
let num = func.length;
|
||||
_("Generating array of nulls for #args:", num);
|
||||
let args = [];
|
||||
for (let i = num; --i >= 0; )
|
||||
args.push(null);
|
||||
do_print(`Generating array of nulls for #args: ${num}`);
|
||||
let args = Array(num).fill(null);
|
||||
|
||||
let tryAgain = true;
|
||||
while (tryAgain == true) {
|
||||
try {
|
||||
_("Calling with args:", JSON.stringify(args));
|
||||
do_print(`Calling with args: ${JSON.stringify(args)}`);
|
||||
func.apply(s, args);
|
||||
|
||||
_("The function didn't throw! Is it one of the nothrow?", nothrow);
|
||||
do_check_neq(nothrow.indexOf(n), -1);
|
||||
do_print(`The function did not throw! Is it one of the nothrow? ${nothrow}`);
|
||||
Assert.notEqual(nothrow.indexOf(n), -1);
|
||||
|
||||
_("Must have been an expected nothrow, so no need to try again");
|
||||
do_print("Must have been an expected nothrow, so no need to try again");
|
||||
tryAgain = false;
|
||||
}
|
||||
catch(ex if ex.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
|
||||
_("Caught an expected exception:", ex.name);
|
||||
do_print(`Caught an expected exception: ${ex.name}`);
|
||||
|
||||
_("Moving on to the next test..");
|
||||
do_print("Moving on to the next test..");
|
||||
tryAgain = false;
|
||||
}
|
||||
catch(ex if ex.result == Cr.NS_ERROR_XPC_NEED_OUT_OBJECT) {
|
||||
let pos = Number(ex.message.match(/object arg (\d+)/)[1]);
|
||||
_("Function call expects an out object at", pos);
|
||||
do_print(`Function call expects an out object at ${pos}`);
|
||||
args[pos] = {};
|
||||
}
|
||||
catch(ex if ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED) {
|
||||
_("Method not implemented exception:", ex.name);
|
||||
do_print(`Method not implemented exception: ${ex.name}`);
|
||||
|
||||
_("Moving on to the next test..");
|
||||
do_print("Moving on to the next test..");
|
||||
tryAgain = false;
|
||||
}
|
||||
catch(ex) {
|
||||
_("Caught some unexpected exception.. dumping");
|
||||
_([[i, ex[i]] for (i in ex)].join("\n"));
|
||||
do_check_true(false);
|
||||
do_print("Caught some unexpected exception.. dumping");
|
||||
do_print([[i, ex[i]] for (i in ex)].join("\n"));
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,7 +143,8 @@ nsAppStartup::nsAppStartup() :
|
||||
mInterrupted(false),
|
||||
mIsSafeModeNecessary(false),
|
||||
mStartupCrashTrackingEnded(false),
|
||||
mRestartTouchEnvironment(false)
|
||||
mRestartTouchEnvironment(false),
|
||||
mRestartNotSameProfile(false)
|
||||
{ }
|
||||
|
||||
|
||||
@ -287,6 +288,8 @@ nsAppStartup::Run(void)
|
||||
retval = NS_SUCCESS_RESTART_METRO_APP;
|
||||
} else if (mRestart) {
|
||||
retval = NS_SUCCESS_RESTART_APP;
|
||||
} else if (mRestartNotSameProfile) {
|
||||
retval = NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE;
|
||||
}
|
||||
|
||||
return retval;
|
||||
@ -386,7 +389,12 @@ nsAppStartup::Quit(uint32_t aMode)
|
||||
gRestartMode = (aMode & 0xF0);
|
||||
}
|
||||
|
||||
if (mRestart || mRestartTouchEnvironment) {
|
||||
if (!mRestartNotSameProfile) {
|
||||
mRestartNotSameProfile = (aMode & eRestartNotSameProfile) != 0;
|
||||
gRestartMode = (aMode & 0xF0);
|
||||
}
|
||||
|
||||
if (mRestart || mRestartTouchEnvironment || mRestartNotSameProfile) {
|
||||
// Mark the next startup as a restart.
|
||||
PR_SetEnv("MOZ_APP_RESTART=1");
|
||||
|
||||
@ -456,7 +464,7 @@ nsAppStartup::Quit(uint32_t aMode)
|
||||
NS_NAMED_LITERAL_STRING(shutdownStr, "shutdown");
|
||||
NS_NAMED_LITERAL_STRING(restartStr, "restart");
|
||||
obsService->NotifyObservers(nullptr, "quit-application",
|
||||
(mRestart || mRestartTouchEnvironment) ?
|
||||
(mRestart || mRestartTouchEnvironment || mRestartNotSameProfile) ?
|
||||
restartStr.get() : shutdownStr.get());
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,8 @@ private:
|
||||
bool mInterrupted; // Was startup interrupted by an interactive prompt?
|
||||
bool mIsSafeModeNecessary; // Whether safe mode is necessary
|
||||
bool mStartupCrashTrackingEnded; // Whether startup crash tracking has already ended
|
||||
bool mRestartTouchEnvironment; // Quit (eRestartTouchEnvironment)
|
||||
bool mRestartTouchEnvironment; // Quit (eRestartTouchEnvironment)
|
||||
bool mRestartNotSameProfile; // Quit(eRestartNotSameProfile)
|
||||
|
||||
#if defined(XP_WIN)
|
||||
//Interaction with OS-provided profiling probes
|
||||
|
@ -36,6 +36,11 @@ interface nsIAppStartup : nsISupports
|
||||
* This return code indicates that the application should be
|
||||
* restarted in metro because quit was called with the
|
||||
* eRestartTouchEnviroment flag.
|
||||
|
||||
* @returnCode NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE
|
||||
* This return code indicates that the application should be
|
||||
* restarted without necessarily using the same profile because
|
||||
* quit was called with the eRestartNotSameProfile flag.
|
||||
*/
|
||||
void run();
|
||||
|
||||
@ -134,6 +139,13 @@ interface nsIAppStartup : nsISupports
|
||||
*/
|
||||
const uint32_t eRestartTouchEnvironment = 0x80;
|
||||
|
||||
/**
|
||||
* Restart the application after quitting. The application will be
|
||||
* restarted with an empty command line and the normal profile selection
|
||||
* process will take place on startup.
|
||||
*/
|
||||
const uint32_t eRestartNotSameProfile = 0x100;
|
||||
|
||||
/**
|
||||
* Exit the event loop, and shut down the app.
|
||||
*
|
||||
|
@ -102,6 +102,7 @@ function acceptDialog()
|
||||
gDialogParams.objects.insertElementAt(profileLock.nsIProfileLock, 0, false);
|
||||
|
||||
gProfileService.selectedProfile = selectedProfile.profile;
|
||||
gProfileService.defaultProfile = selectedProfile.profile;
|
||||
updateStartupPrefs();
|
||||
|
||||
gDialogParams.SetInt(0, 1);
|
||||
|
@ -29,6 +29,12 @@ interface nsIToolkitProfileService : nsISupports
|
||||
* browser if no other profile is specified at runtime). This is the profile
|
||||
* marked with Default=1 in profiles.ini and is usually the same as
|
||||
* selectedProfile, except on Developer Edition.
|
||||
*
|
||||
* Developer Edition uses a profile named "dev-edition-default" as the
|
||||
* default profile (which it creates if it doesn't exist), unless a special
|
||||
* empty file named "ignore-dev-edition-profile" is present next to
|
||||
* profiles.ini. In that case Developer Edition behaves the same as any
|
||||
* other build of Firefox.
|
||||
*/
|
||||
attribute nsIToolkitProfile defaultProfile;
|
||||
|
||||
|
@ -424,6 +424,22 @@ nsToolkitProfileService::Init()
|
||||
|
||||
nsToolkitProfile* currentProfile = nullptr;
|
||||
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
nsCOMPtr<nsIFile> ignoreSeparateProfile;
|
||||
rv = mAppData->Clone(getter_AddRefs(ignoreSeparateProfile));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = ignoreSeparateProfile->AppendNative(NS_LITERAL_CSTRING("ignore-dev-edition-profile"));
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
bool shouldIgnoreSeparateProfile;
|
||||
rv = ignoreSeparateProfile->Exists(&shouldIgnoreSeparateProfile);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
#endif
|
||||
|
||||
unsigned int c = 0;
|
||||
bool foundAuroraDefault = false;
|
||||
for (c = 0; true; ++c) {
|
||||
@ -485,8 +501,9 @@ nsToolkitProfileService::Init()
|
||||
this->SetDefaultProfile(currentProfile);
|
||||
}
|
||||
#ifdef MOZ_DEV_EDITION
|
||||
// Use the dev-edition-default profile if this is an Aurora build.
|
||||
if (name.EqualsLiteral("dev-edition-default")) {
|
||||
// Use the dev-edition-default profile if this is an Aurora build and
|
||||
// ignore-dev-edition-profile is not present.
|
||||
if (name.EqualsLiteral("dev-edition-default") && !shouldIgnoreSeparateProfile) {
|
||||
mChosen = currentProfile;
|
||||
foundAuroraDefault = true;
|
||||
}
|
||||
@ -498,7 +515,7 @@ nsToolkitProfileService::Init()
|
||||
// on webapprt.
|
||||
bool isFirefox = strcmp(gAppData->ID,
|
||||
"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") == 0;
|
||||
if (!foundAuroraDefault && isFirefox) {
|
||||
if (!foundAuroraDefault && isFirefox && !shouldIgnoreSeparateProfile) {
|
||||
// If a single profile exists, it may not be already marked as default.
|
||||
// Do it now to avoid problems when we create the dev-edition-default profile.
|
||||
if (!mChosen && mFirst && !mFirst->mNext)
|
||||
|
@ -4176,7 +4176,9 @@ XREMain::XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
|
||||
|
||||
// Check for an application initiated restart. This is one that
|
||||
// corresponds to nsIAppStartup.quit(eRestart)
|
||||
if (rv == NS_SUCCESS_RESTART_APP || rv == NS_SUCCESS_RESTART_METRO_APP) {
|
||||
if (rv == NS_SUCCESS_RESTART_APP
|
||||
|| rv == NS_SUCCESS_RESTART_METRO_APP
|
||||
|| rv == NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE) {
|
||||
appInitiatedRestart = true;
|
||||
|
||||
// We have an application restart don't do any shutdown checks here
|
||||
@ -4209,10 +4211,12 @@ XREMain::XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
|
||||
if (appInitiatedRestart) {
|
||||
RestoreStateForAppInitiatedRestart();
|
||||
|
||||
// Ensure that these environment variables are set:
|
||||
SaveFileToEnvIfUnset("XRE_PROFILE_PATH", mProfD);
|
||||
SaveFileToEnvIfUnset("XRE_PROFILE_LOCAL_PATH", mProfLD);
|
||||
SaveWordToEnvIfUnset("XRE_PROFILE_NAME", mProfileName);
|
||||
if (rv != NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE) {
|
||||
// Ensure that these environment variables are set:
|
||||
SaveFileToEnvIfUnset("XRE_PROFILE_PATH", mProfD);
|
||||
SaveFileToEnvIfUnset("XRE_PROFILE_LOCAL_PATH", mProfLD);
|
||||
SaveWordToEnvIfUnset("XRE_PROFILE_NAME", mProfileName);
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
MOZ_gdk_display_close(mGdkDisplay);
|
||||
|
@ -906,6 +906,7 @@
|
||||
* case in which nsIAppStartup::Quit was called with the eRestart flag. */
|
||||
ERROR(NS_SUCCESS_RESTART_APP, SUCCESS(1)),
|
||||
ERROR(NS_SUCCESS_RESTART_METRO_APP, SUCCESS(2)),
|
||||
ERROR(NS_SUCCESS_RESTART_APP_NOT_SAME_PROFILE, SUCCESS(3)),
|
||||
ERROR(NS_SUCCESS_UNORM_NOTFOUND, SUCCESS(17)),
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user