This commit is contained in:
Ryan VanderMeulen 2015-04-28 15:20:07 -04:00
Родитель 4f8a713ad8 b17bce13e6
Коммит 0d944297b7
50 изменённых файлов: 1014 добавлений и 250 удалений

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

@ -638,6 +638,37 @@ let LoopRoomsInternal = {
}, callback);
},
/**
* Forwards connection status to the server.
*
* @param {String} roomToken The room token.
* @param {String} sessionToken The session token for the
* session that has been
* joined.
* @param {sharedActions.SdkStatus} status The connection status.
* @param {Function} callback Optional. Function that will be invoked once
* the operation finished. The first argument
* passed will be an `Error` object or `null`.
*/
sendConnectionStatus: function(roomToken, sessionToken, status, callback) {
if (!callback) {
callback = function(error) {
if (error) {
MozLoopService.log.error(error);
}
};
}
this._postToRoom(roomToken, {
action: "status",
event: status.event,
state: status.state,
connections: status.connections,
sendStreams: status.sendStreams,
recvStreams: status.recvStreams,
sessionToken: sessionToken
}, callback);
},
/**
* Renames a room.
*
@ -780,6 +811,10 @@ this.LoopRooms = {
return LoopRoomsInternal.leave(roomToken, sessionToken, callback);
},
sendConnectionStatus: function(roomToken, sessionToken, status, callback) {
return LoopRoomsInternal.sendConnectionStatus(roomToken, sessionToken, status, callback);
},
rename: function(roomToken, newRoomName, callback) {
return LoopRoomsInternal.rename(roomToken, newRoomName, callback);
},

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

@ -432,6 +432,8 @@ loop.panel = (function(_, mozL10n) {
});
var RoomEntryContextItem = React.createClass({displayName: "RoomEntryContextItem",
mixins: [loop.shared.mixins.WindowCloseMixin],
propTypes: {
mozLoop: React.PropTypes.object.isRequired,
roomUrls: React.PropTypes.object
@ -441,6 +443,7 @@ loop.panel = (function(_, mozL10n) {
event.stopPropagation();
event.preventDefault();
this.props.mozLoop.openURL(event.currentTarget.href);
this.closeWindow();
},
render: function() {
@ -700,9 +703,17 @@ loop.panel = (function(_, mozL10n) {
},
render: function() {
var hostname;
try {
hostname = new URL(this.state.url).hostname;
} catch (ex) {
// Empty catch - if there's an error, then we won't show the context.
}
var contextClasses = React.addons.classSet({
context: true,
hide: !this.state.url ||
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConverations.enabled")
});
@ -716,7 +727,7 @@ loop.panel = (function(_, mozL10n) {
),
React.createElement("img", {className: "context-preview", src: this.state.previewImage}),
React.createElement("span", {className: "context-description"}, this.state.description),
React.createElement("span", {className: "context-url"}, this.state.url)
React.createElement("span", {className: "context-url"}, hostname)
),
React.createElement("button", {className: "btn btn-info new-room-button",
onClick: this.handleCreateButtonClick,

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

@ -432,6 +432,8 @@ loop.panel = (function(_, mozL10n) {
});
var RoomEntryContextItem = React.createClass({
mixins: [loop.shared.mixins.WindowCloseMixin],
propTypes: {
mozLoop: React.PropTypes.object.isRequired,
roomUrls: React.PropTypes.object
@ -441,6 +443,7 @@ loop.panel = (function(_, mozL10n) {
event.stopPropagation();
event.preventDefault();
this.props.mozLoop.openURL(event.currentTarget.href);
this.closeWindow();
},
render: function() {
@ -700,9 +703,17 @@ loop.panel = (function(_, mozL10n) {
},
render: function() {
var hostname;
try {
hostname = new URL(this.state.url).hostname;
} catch (ex) {
// Empty catch - if there's an error, then we won't show the context.
}
var contextClasses = React.addons.classSet({
context: true,
hide: !this.state.url ||
hide: !hostname ||
!this.props.mozLoop.getLoopPref("contextInConverations.enabled")
});
@ -716,7 +727,7 @@ loop.panel = (function(_, mozL10n) {
</label>
<img className="context-preview" src={this.state.previewImage}/>
<span className="context-description">{this.state.description}</span>
<span className="context-url">{this.state.url}</span>
<span className="context-url">{hostname}</span>
</div>
<button className="btn btn-info new-room-button"
onClick={this.handleCreateButtonClick}

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

@ -499,6 +499,18 @@ loop.shared.actions = (function() {
*/
SendFeedbackError: Action.define("sendFeedbackError", {
error: Error
}),
/**
* Used to inform of the current session, publisher and connection
* status.
*/
ConnectionStatus: Action.define("connectionStatus", {
event: String,
state: String,
connections: Number,
sendStreams: Number,
recvStreams: Number
})
};
})();

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

@ -156,7 +156,8 @@ loop.store.ActiveRoomStore = (function() {
"videoDimensionsChanged",
"startScreenShare",
"endScreenShare",
"updateSocialShareInfo"
"updateSocialShareInfo",
"connectionStatus"
]);
},
@ -639,6 +640,17 @@ loop.store.ActiveRoomStore = (function() {
this.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
},
/**
* Handles an SDK status update, forwarding it to the server.
*
* @param {sharedActions.ConnectionStatus} actionData
*/
connectionStatus: function(actionData) {
this._mozLoop.rooms.sendConnectionStatus(this.getStoreState("roomToken"),
this.getStoreState("sessionToken"),
actionData);
},
/**
* Handles the window being unloaded. Ensures the room is left.
*/

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

@ -17,39 +17,47 @@ loop.OTSdkDriver = (function() {
* actions, and instruct the SDK what to do as a result of actions.
*/
var OTSdkDriver = function(options) {
if (!options.dispatcher) {
throw new Error("Missing option dispatcher");
}
if (!options.sdk) {
throw new Error("Missing option sdk");
if (!options.dispatcher) {
throw new Error("Missing option dispatcher");
}
if (!options.sdk) {
throw new Error("Missing option sdk");
}
this.dispatcher = options.dispatcher;
this.sdk = options.sdk;
this._isDesktop = !!options.isDesktop;
if (this._isDesktop) {
if (!options.mozLoop) {
throw new Error("Missing option mozLoop");
}
this.mozLoop = options.mozLoop;
}
this.dispatcher = options.dispatcher;
this.sdk = options.sdk;
this.connections = {};
this._isDesktop = !!options.isDesktop;
// Metrics object to keep track of the number of connections we have
// and the amount of streams.
this._metrics = {
connections: 0,
sendStreams: 0,
recvStreams: 0
};
if (this._isDesktop) {
if (!options.mozLoop) {
throw new Error("Missing option mozLoop");
}
this.mozLoop = options.mozLoop;
}
this.dispatcher.register(this, [
"setupStreamElements",
"setMute"
]);
this.connections = {};
this.dispatcher.register(this, [
"setupStreamElements",
"setMute"
]);
// Set loop.debug.twoWayMediaTelemetry to true in the browser
// by changing the hidden pref loop.debug.twoWayMediaTelemetry using
// about:config, or use
//
// localStorage.setItem("debug.twoWayMediaTelemetry", true);
this._debugTwoWayMediaTelemetry =
loop.shared.utils.getBoolPreference("debug.twoWayMediaTelemetry");
// Set loop.debug.twoWayMediaTelemetry to true in the browser
// by changing the hidden pref loop.debug.twoWayMediaTelemetry using
// about:config, or use
//
// localStorage.setItem("debug.twoWayMediaTelemetry", true);
this._debugTwoWayMediaTelemetry =
loop.shared.utils.getBoolPreference("debug.twoWayMediaTelemetry");
/**
* XXX This is a workaround for desktop machines that do not have a
@ -104,6 +112,7 @@ loop.OTSdkDriver = (function() {
this.publisher = this.sdk.initPublisher(this.getLocalElement(),
this._getCopyPublisherConfig());
this.publisher.on("streamCreated", this._onLocalStreamCreated.bind(this));
this.publisher.on("streamDestroyed", this._onLocalStreamDestroyed.bind(this));
this.publisher.on("accessAllowed", this._onPublishComplete.bind(this));
this.publisher.on("accessDenied", this._onPublishDenied.bind(this));
this.publisher.on("accessDialogOpened",
@ -164,6 +173,7 @@ loop.OTSdkDriver = (function() {
config);
this.screenshare.on("accessAllowed", this._onScreenShareGranted.bind(this));
this.screenshare.on("accessDenied", this._onScreenShareDenied.bind(this));
this.screenshare.on("streamCreated", this._onScreenShareStreamCreated.bind(this));
this._noteSharingState(options.videoSource, true);
},
@ -193,8 +203,10 @@ loop.OTSdkDriver = (function() {
return false;
}
this._notifyMetricsEvent("Publisher.streamDestroyed");
this.session.unpublish(this.screenshare);
this.screenshare.off("accessAllowed accessDenied");
this.screenshare.off("accessAllowed accessDenied streamCreated");
this.screenshare.destroy();
delete this.screenshare;
this._noteSharingState(this._windowId ? "browser" : "window", false);
@ -223,18 +235,18 @@ loop.OTSdkDriver = (function() {
this._sendTwoWayMediaTelemetry = !!sessionData.sendTwoWayMediaTelemetry;
this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_UNINITIALIZED);
this.session.on("connectionCreated", this._onConnectionCreated.bind(this));
this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
this.session.on("streamDestroyed", this._onRemoteStreamDestroyed.bind(this));
this.session.on("connectionDestroyed",
this._onConnectionDestroyed.bind(this));
this.session.on("sessionDisconnected",
this._onSessionDisconnected.bind(this));
this.session.on("connectionCreated", this._onConnectionCreated.bind(this));
this.session.on("connectionDestroyed",
this._onConnectionDestroyed.bind(this));
this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
this.session.on("streamDestroyed", this._onRemoteStreamDestroyed.bind(this));
this.session.on("streamPropertyChanged", this._onStreamPropertyChanged.bind(this));
// This starts the actual session connection.
this.session.connect(sessionData.apiKey, sessionData.sessionToken,
this._onConnectionComplete.bind(this));
this._onSessionConnectionCompleted.bind(this));
},
/**
@ -244,10 +256,11 @@ loop.OTSdkDriver = (function() {
this.endScreenShare();
if (this.session) {
this.session.off("streamCreated streamDestroyed connectionDestroyed " +
"sessionDisconnected streamPropertyChanged");
this.session.off("sessionDisconnected streamCreated streamDestroyed connectionCreated connectionDestroyed streamPropertyChanged");
this.session.disconnect();
delete this.session;
this._notifyMetricsEvent("Session.connectionDestroyed", "local");
}
if (this.publisher) {
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
@ -302,7 +315,7 @@ loop.OTSdkDriver = (function() {
*
* @param {Error} error An OT error object, null if there was no error.
*/
_onConnectionComplete: function(error) {
_onSessionConnectionCompleted: function(error) {
if (error) {
console.error("Failed to complete connection", error);
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
@ -327,7 +340,11 @@ loop.OTSdkDriver = (function() {
if (connection && (connection.id in this.connections)) {
delete this.connections[connection.id];
}
this._notifyMetricsEvent("Session.connectionDestroyed", "peer");
this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now());
this.dispatcher.dispatch(new sharedActions.RemotePeerDisconnected({
peerHungup: event.reason === "clientDisconnected"
}));
@ -370,12 +387,95 @@ loop.OTSdkDriver = (function() {
_onConnectionCreated: function(event) {
var connection = event.connection;
if (this.session.connection.id === connection.id) {
// This is the connection event for our connection.
this._notifyMetricsEvent("Session.connectionCreated", "local");
return;
}
this.connections[connection.id] = connection;
this._notifyMetricsEvent("Session.connectionCreated", "peer");
this.dispatcher.dispatch(new sharedActions.RemotePeerConnected());
},
/**
* Works out the current connection state based on the streams being
* sent or received.
*/
_getConnectionState: function() {
if (this._metrics.sendStreams) {
return this._metrics.recvStreams ? "sendrecv" : "sending";
}
if (this._metrics.recvStreams) {
return "receiving";
}
return "starting";
},
/**
* Notifies of a metrics related event for tracking call setup purposes.
* See https://wiki.mozilla.org/Loop/Architecture/Rooms#Updating_Session_State
*
* @param {String} eventName The name of the event for the update.
* @param {String} clientType Used for connection created/destoryed. Indicates
* if it is for the "peer" or the "local" client.
*/
_notifyMetricsEvent: function(eventName, clientType) {
if (!eventName) {
return;
}
var state;
// We intentionally don't bounds check these, in case there's an error
// somewhere, if there is, we'll see it in the server metrics and are more
// likely to investigate.
switch (eventName) {
case "Session.connectionCreated":
this._metrics.connections++;
if (clientType === "local") {
state = "waiting";
}
break;
case "Session.connectionDestroyed":
this._metrics.connections--;
if (clientType === "local") {
// Don't log this, as the server doesn't accept it after
// the room has been left.
return;
} else if (!this._metrics.connections) {
state = "waiting";
}
break;
case "Publisher.streamCreated":
this._metrics.sendStreams++;
break;
case "Publisher.streamDestroyed":
this._metrics.sendStreams--;
break;
case "Session.streamCreated":
this._metrics.recvStreams++;
break;
case "Session.streamDestroyed":
this._metrics.recvStreams--;
break;
default:
console.error("Unexpected event name", eventName);
return;
}
if (!state) {
state = this._getConnectionState();
}
this.dispatcher.dispatch(new sharedActions.ConnectionStatus({
event: eventName,
state: state,
connections: this._metrics.connections,
sendStreams: this._metrics.sendStreams,
recvStreams: this._metrics.recvStreams
}));
},
/**
* Handles when a remote screen share is created, subscribing to
* the stream, and notifying the stores that a share is being
@ -407,6 +507,8 @@ loop.OTSdkDriver = (function() {
* https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
*/
_onRemoteStreamCreated: function(event) {
this._notifyMetricsEvent("Session.streamCreated");
if (event.stream[STREAM_PROPERTIES.HAS_VIDEO]) {
this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({
isLocal: false,
@ -439,6 +541,8 @@ loop.OTSdkDriver = (function() {
* https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
*/
_onLocalStreamCreated: function(event) {
this._notifyMetricsEvent("Publisher.streamCreated");
if (event.stream[STREAM_PROPERTIES.HAS_VIDEO]) {
this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({
isLocal: true,
@ -506,6 +610,8 @@ loop.OTSdkDriver = (function() {
* https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
*/
_onRemoteStreamDestroyed: function(event) {
this._notifyMetricsEvent("Session.streamDestroyed");
if (event.stream.videoType !== "screen") {
return;
}
@ -517,6 +623,13 @@ loop.OTSdkDriver = (function() {
}));
},
/**
* Handles the event when the remote stream is destroyed.
*/
_onLocalStreamDestroyed: function() {
this._notifyMetricsEvent("Publisher.streamDestroyed");
},
/**
* Called from the sdk when the media access dialog is opened.
* Prevents the default action, to prevent the SDK's "allow access"
@ -631,6 +744,13 @@ loop.OTSdkDriver = (function() {
}));
},
/**
* Called when a screenshare stream is published.
*/
_onScreenShareStreamCreated: function() {
this._notifyMetricsEvent("Publisher.streamCreated");
},
/*
* XXX all of the bi-directional media connection telemetry stuff in this
* file, (much, but not all, of it is below) should be hoisted into its

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

@ -222,6 +222,29 @@ loop.StandaloneMozLoop = (function(mozL10n) {
}, null, false, callback);
},
/**
* Forwards connection status to the server.
*
* @param {String} roomToken The room token.
* @param {String} sessionToken The session token for the session that has been
* joined.
* @param {sharedActions.SdkStatus} status The connection status.
*/
sendConnectionStatus: function(roomToken, sessionToken, status) {
this._postToRoom(roomToken, sessionToken, {
action: "status",
event: status.event,
state: status.state,
connections: status.connections,
sendStreams: status.sendStreams,
recvStreams: status.recvStreams
}, null, true, function(error) {
if (error) {
console.error(error);
}
});
},
// Dummy functions to reflect those in the desktop mozLoop.rooms that we
// don't currently use.
on: function() {},

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

@ -280,15 +280,28 @@ loop.standaloneRoomViews = (function(mozL10n) {
roomInfoFailure: React.PropTypes.string
},
getInitialState: function() {
return {
failureLogged: false
}
},
_logFailure: function(message) {
if (!this.state.failureLogged) {
console.error(mozL10n.get(message));
this.state.failureLogged = true;
}
},
render: function() {
// For failures, we currently just log the messages - UX doesn't want them
// displayed on primary UI at the moment.
if (this.props.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
return (React.createElement("h2", {className: "room-info-failure"},
mozL10n.get("room_information_failure_unsupported_browser")
));
this._logFailure("room_information_failure_unsupported_browser");
return null;
} else if (this.props.roomInfoFailure) {
return (React.createElement("h2", {className: "room-info-failure"},
mozL10n.get("room_information_failure_not_available")
));
this._logFailure("room_information_failure_not_available");
return null;
}
// We only support one item in the context Urls array for now.

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

@ -280,15 +280,28 @@ loop.standaloneRoomViews = (function(mozL10n) {
roomInfoFailure: React.PropTypes.string
},
getInitialState: function() {
return {
failureLogged: false
}
},
_logFailure: function(message) {
if (!this.state.failureLogged) {
console.error(mozL10n.get(message));
this.state.failureLogged = true;
}
},
render: function() {
// For failures, we currently just log the messages - UX doesn't want them
// displayed on primary UI at the moment.
if (this.props.roomInfoFailure === ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED) {
return (<h2 className="room-info-failure">
{mozL10n.get("room_information_failure_unsupported_browser")}
</h2>);
this._logFailure("room_information_failure_unsupported_browser");
return null;
} else if (this.props.roomInfoFailure) {
return (<h2 className="room-info-failure">
{mozL10n.get("room_information_failure_not_available")}
</h2>);
this._logFailure("room_information_failure_not_available");
return null;
}
// We only support one item in the context Urls array for now.

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

@ -614,6 +614,20 @@ describe("loop.panel", function() {
sinon.assert.calledOnce(fakeMozLoop.openURL);
sinon.assert.calledWithExactly(fakeMozLoop.openURL, "http://invalid/");
});
it("should call close the panel after opening a url", function() {
roomData.decryptedContext.urls = [{
description: "invalid entry",
location: "http://invalid/",
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
}];
roomEntry = mountEntryForContext();
TestUtils.Simulate.click(roomEntry.getDOMNode().querySelector("a"));
sinon.assert.calledOnce(fakeWindow.close);
});
});
describe("Room Entry click", function() {
@ -833,6 +847,24 @@ describe("loop.panel", function() {
var contextInfo = view.getDOMNode().querySelector(".context");
expect(contextInfo.classList.contains("hide")).to.equal(true);
});
it("should show only the hostname of the url", function() {
fakeMozLoop.getSelectedTabMetadata = function (callback) {
callback({
url: "https://www.example.com:1234",
description: "fake description",
previews: [""]
});
};
var view = createTestComponent();
// Simulate being visible
view.onDocumentVisible();
var contextHostname = view.getDOMNode().querySelector(".context-url");
expect(contextHostname.textContent).eql("www.example.com");
});
});
describe('loop.panel.ToSView', function() {

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

@ -32,7 +32,8 @@ describe("loop.store.ActiveRoomStore", function () {
refreshMembership: sinon.stub(),
leave: sinon.stub(),
on: sinon.stub(),
off: sinon.stub()
off: sinon.stub(),
sendConnectionStatus: sinon.stub()
},
setScreenShareState: sinon.stub(),
getActiveTabWindowId: sandbox.stub().callsArgWith(0, null, 42),
@ -1121,6 +1122,29 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("#connectionStatus", function() {
it("should call rooms.sendConnectionStatus on mozLoop", function() {
store.setStoreState({
roomToken: "fakeToken",
sessionToken: "9876543210"
});
var data = new sharedActions.ConnectionStatus({
event: "Publisher.streamCreated",
state: "sendrecv",
connections: 2,
recvStreams: 1,
sendStreams: 2
});
store.connectionStatus(data);
sinon.assert.calledOnce(fakeMozLoop.rooms.sendConnectionStatus);
sinon.assert.calledWith(fakeMozLoop.rooms.sendConnectionStatus,
"fakeToken", "9876543210", data);
});
});
describe("#windowUnload", function() {
beforeEach(function() {
store.setStoreState({

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

@ -35,6 +35,9 @@ describe("loop.OTSdkDriver", function () {
};
dispatcher = new loop.Dispatcher();
sandbox.stub(dispatcher, "dispatch");
session = _.extend({
connect: sinon.stub(),
disconnect: sinon.stub(),
@ -108,7 +111,7 @@ describe("loop.OTSdkDriver", function () {
describe("#setupStreamElements", function() {
it("should call initPublisher", function() {
dispatcher.dispatch(new sharedActions.SetupStreamElements({
driver.setupStreamElements(new sharedActions.SetupStreamElements({
getLocalElementFunc: function() {return fakeLocalElement;},
getRemoteElementFunc: function() {return fakeRemoteElement;},
publisherConfig: publisherConfig
@ -154,7 +157,7 @@ describe("loop.OTSdkDriver", function () {
beforeEach(function() {
sdk.initPublisher.returns(publisher);
dispatcher.dispatch(new sharedActions.SetupStreamElements({
driver.setupStreamElements(new sharedActions.SetupStreamElements({
getLocalElementFunc: function() {return fakeLocalElement;},
getRemoteElementFunc: function() {return fakeRemoteElement;},
publisherConfig: publisherConfig
@ -162,7 +165,7 @@ describe("loop.OTSdkDriver", function () {
});
it("should publishAudio with the correct enabled value", function() {
dispatcher.dispatch(new sharedActions.SetMute({
driver.setMute(new sharedActions.SetMute({
type: "audio",
enabled: false
}));
@ -172,7 +175,7 @@ describe("loop.OTSdkDriver", function () {
});
it("should publishVideo with the correct enabled value", function() {
dispatcher.dispatch(new sharedActions.SetMute({
driver.setMute(new sharedActions.SetMute({
type: "video",
enabled: true
}));
@ -186,7 +189,6 @@ describe("loop.OTSdkDriver", function () {
var fakeElement;
beforeEach(function() {
sandbox.stub(dispatcher, "dispatch");
sandbox.stub(driver, "_noteSharingState");
fakeElement = {
@ -240,7 +242,6 @@ describe("loop.OTSdkDriver", function () {
driver.getScreenShareElementFunc = function() {
return fakeScreenElement;
};
sandbox.stub(dispatcher, "dispatch");
driver.startScreenShare(options);
});
@ -263,7 +264,6 @@ describe("loop.OTSdkDriver", function () {
beforeEach(function() {
driver.getScreenShareElementFunc = function() {};
sandbox.stub(dispatcher, "dispatch");
sandbox.stub(driver, "_noteSharingState");
});
@ -327,6 +327,32 @@ describe("loop.OTSdkDriver", function () {
sinon.assert.calledWithExactly(driver._noteSharingState, "browser", false);
});
it("should dispatch a ConnectionStatus action", function() {
driver.startScreenShare({
videoSource: "browser",
constraints: {
browserWindow: 42
}
});
driver.session = session;
driver._metrics.connections = 2;
driver._metrics.recvStreams = 1;
driver._metrics.sendStreams = 2;
driver.endScreenShare(new sharedActions.EndScreenShare());
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Publisher.streamDestroyed",
state: "sendrecv",
connections: 2,
sendStreams: 1,
recvStreams: 1
}));
});
});
describe("#connectSession", function() {
@ -365,7 +391,6 @@ describe("loop.OTSdkDriver", function () {
it("should dispatch connectionFailure if connecting failed", function() {
session.connect.callsArgWith(2, new Error("Failure"));
sandbox.stub(dispatcher, "dispatch");
driver.connectSession(sessionData);
@ -578,14 +603,12 @@ describe("loop.OTSdkDriver", function () {
beforeEach(function() {
driver.connectSession(sessionData);
dispatcher.dispatch(new sharedActions.SetupStreamElements({
driver.setupStreamElements(new sharedActions.SetupStreamElements({
getLocalElementFunc: function() {return fakeLocalElement;},
getScreenShareElementFunc: function() {return fakeScreenElement;},
getRemoteElementFunc: function() {return fakeRemoteElement;},
publisherConfig: publisherConfig
}));
sandbox.stub(dispatcher, "dispatch");
});
describe("connectionDestroyed", function() {
@ -595,7 +618,7 @@ describe("loop.OTSdkDriver", function () {
reason: "clientDisconnected"
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "remotePeerDisconnected"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
@ -608,13 +631,30 @@ describe("loop.OTSdkDriver", function () {
reason: "networkDisconnected"
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "remotePeerDisconnected"));
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("peerHungup", false));
});
it("should dispatch a ConnectionStatus action", function() {
driver._metrics.connections = 1;
session.trigger("connectionDestroyed", {
reason: "clientDisconnected"
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Session.connectionDestroyed",
state: "waiting",
connections: 0,
sendStreams: 0,
recvStreams: 0
}));
});
it("should call _noteConnectionLengthIfNeeded with connection duration", function() {
driver.session = session;
@ -681,16 +721,20 @@ describe("loop.OTSdkDriver", function () {
});
describe("streamCreated (publisher/local)", function() {
it("should dispatch a VideoDimensionsChanged action", function() {
var fakeStream = {
var fakeStream;
beforeEach(function() {
fakeStream = {
hasVideo: true,
videoType: "camera",
videoDimensions: {width: 1, height: 2}
};
});
it("should dispatch a VideoDimensionsChanged action", function() {
publisher.trigger("streamCreated", {stream: fakeStream});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.VideoDimensionsChanged({
isLocal: true,
@ -698,6 +742,23 @@ describe("loop.OTSdkDriver", function () {
dimensions: {width: 1, height: 2}
}));
});
it("should dispatch a ConnectionStatus action", function() {
driver._metrics.recvStreams = 1;
driver._metrics.connections = 2;
publisher.trigger("streamCreated", {stream: fakeStream});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Publisher.streamCreated",
state: "sendrecv",
connections: 2,
recvStreams: 1,
sendStreams: 1
}));
});
});
describe("streamCreated (session/remote)", function() {
@ -714,7 +775,7 @@ describe("loop.OTSdkDriver", function () {
it("should dispatch a VideoDimensionsChanged action", function() {
session.trigger("streamCreated", {stream: fakeStream});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.VideoDimensionsChanged({
isLocal: false,
@ -723,6 +784,22 @@ describe("loop.OTSdkDriver", function () {
}));
});
it("should dispatch a ConnectionStatus action", function() {
driver._metrics.connections = 1;
session.trigger("streamCreated", {stream: fakeStream});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Session.streamCreated",
state: "receiving",
connections: 1,
recvStreams: 1,
sendStreams: 0
}));
});
it("should subscribe to a camera stream", function() {
session.trigger("streamCreated", {stream: fakeStream});
@ -747,9 +824,9 @@ describe("loop.OTSdkDriver", function () {
session.trigger("streamCreated", {stream: fakeStream});
// Called twice due to the VideoDimensionsChanged above.
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "mediaConnected"));
new sharedActions.MediaConnected({}));
});
it("should store the start time when both streams are up and" +
@ -803,13 +880,33 @@ describe("loop.OTSdkDriver", function () {
session.trigger("streamCreated", {stream: fakeStream});
// Called twice due to the VideoDimensionsChanged above.
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithMatch(dispatcher.dispatch,
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ReceivingScreenShare({receiving: true}));
});
});
describe("streamDestroyed", function() {
describe("streamDestroyed (publisher/local)", function() {
it("should dispatch a ConnectionStatus action", function() {
driver._metrics.sendStreams = 1;
driver._metrics.recvStreams = 1;
driver._metrics.connections = 2;
publisher.trigger("streamDestroyed");
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Publisher.streamDestroyed",
state: "receiving",
connections: 2,
recvStreams: 1,
sendStreams: 0
}));
});
});
describe("streamDestroyed (session/remote)", function() {
var fakeStream;
beforeEach(function() {
@ -821,19 +918,38 @@ describe("loop.OTSdkDriver", function () {
it("should dispatch a ReceivingScreenShare action", function() {
session.trigger("streamDestroyed", {stream: fakeStream});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ReceivingScreenShare({
receiving: false
}));
});
it("should dispatch a ConnectionStatus action", function() {
driver._metrics.connections = 2;
driver._metrics.sendStreams = 1;
driver._metrics.recvStreams = 1;
session.trigger("streamDestroyed", {stream: fakeStream});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Session.streamDestroyed",
state: "sending",
connections: 2,
recvStreams: 0,
sendStreams: 1
}));
});
it("should not dispatch an action if the videoType is camera", function() {
fakeStream.videoType = "camera";
session.trigger("streamDestroyed", {stream: fakeStream});
sinon.assert.notCalled(dispatcher.dispatch);
sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "receivingScreenShare"));
});
});
@ -888,25 +1004,75 @@ describe("loop.OTSdkDriver", function () {
connection: {id: "remoteUser"}
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.RemotePeerConnected());
it("should store the connection details for a remote user", function() {
expect(driver.connections).to.include.keys("remoteUser");
});
});
it("should not dispatch an action if this is for a local user",
it("should store the connection details for a remote user", function() {
session.trigger("connectionCreated", {
connection: {id: "remoteUser"}
});
expect(driver.connections).to.include.keys("remoteUser");
});
it("should dispatch a ConnectionStatus action for a remote user", function() {
driver._metrics.connections = 1;
driver._metrics.sendStreams = 1;
session.trigger("connectionCreated", {
connection: {id: "remoteUser"}
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Session.connectionCreated",
state: "sending",
connections: 2,
recvStreams: 0,
sendStreams: 1
}));
});
it("should not dispatch an RemotePeerConnected action if this is for a local user",
function() {
session.trigger("connectionCreated", {
connection: {id: "localUser"}
});
sinon.assert.notCalled(dispatcher.dispatch);
it("should not store the connection details for a local user", function() {
expect(driver.connections).to.not.include.keys("localUser");
});
sinon.assert.neverCalledWithMatch(dispatcher.dispatch,
sinon.match.hasOwn("name", "remotePeerConnected"));
});
it("should not store the connection details for a local user", function() {
session.trigger("connectionCreated", {
connection: {id: "localUser"}
});
expect(driver.connections).to.not.include.keys("localUser");
});
it("should dispatch a ConnectionStatus action for a remote user", function() {
driver._metrics.connections = 0;
driver._metrics.sendStreams = 0;
session.trigger("connectionCreated", {
connection: {id: "localUser"}
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Session.connectionCreated",
state: "waiting",
connections: 1,
recvStreams: 0,
sendStreams: 0
}));
});
});
describe("accessAllowed", function() {
@ -989,8 +1155,6 @@ describe("loop.OTSdkDriver", function () {
driver.startScreenShare({
videoSource: "window"
});
sandbox.stub(dispatcher, "dispatch");
});
describe("accessAllowed", function() {
@ -1022,5 +1186,25 @@ describe("loop.OTSdkDriver", function () {
}));
});
});
describe("streamCreated", function() {
it("should dispatch a ConnectionStatus action", function() {
driver._metrics.connections = 2;
driver._metrics.recvStreams = 1;
driver._metrics.sendStreams = 1;
publisher.trigger("streamCreated", fakeEvent);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionStatus({
event: "Publisher.streamCreated",
state: "sendrecv",
connections: 2,
recvStreams: 1,
sendStreams: 2
}));
});
});
});
});

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

@ -63,13 +63,14 @@ describe("loop.standaloneRoomViews", function() {
expect(view.getDOMNode().textContent).eql("Mike's room");
});
it("should display an unsupported browser message if crypto is unsupported", function() {
it("should log an unsupported browser message if crypto is unsupported", function() {
var view = mountTestComponent({
roomName: "Mark's room",
roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED
});
expect(view.getDOMNode().textContent).match(/unsupported/);
sinon.assert.called(console.error);
sinon.assert.calledWithMatch(console.error, sinon.match("unsupported"));
});
it("should display a general error message for any other failure", function() {
@ -78,7 +79,8 @@ describe("loop.standaloneRoomViews", function() {
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA
});
expect(view.getDOMNode().textContent).match(/not_available/);
sinon.assert.called(console.error);
sinon.assert.calledWithMatch(console.error, sinon.match("not_available"));
});
it("should display context information if a url is supplied", function() {

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

@ -577,6 +577,26 @@ add_task(function* test_leaveRoom() {
Assert.equal(leaveData.sessionToken, "fakeLeaveSessionToken");
});
// Test if sending connection status works as expected.
add_task(function* test_sendConnectionStatus() {
let roomToken = "_nxD4V4FflQ";
let extraData = {
event: "Session.connectionDestroyed",
state: "test",
connections: 1,
sendStreams: 2,
recvStreams: 3
};
let statusData = yield LoopRooms.promise("sendConnectionStatus", roomToken,
"fakeStatusSessionToken", extraData
);
Assert.equal(statusData.sessionToken, "fakeStatusSessionToken");
extraData.action = "status";
extraData.sessionToken = "fakeStatusSessionToken";
Assert.deepEqual(statusData, extraData);
});
// Test if renaming a room works as expected.
add_task(function* test_renameRoom() {
Services.prefs.setBoolPref(kContextEnabledPref, true);

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

@ -234,6 +234,7 @@ var BookmarkPropertiesPanel = {
}
else { // edit
this._node = dialogInfo.node;
this._title = this._node.title;
switch (dialogInfo.type) {
case "bookmark":
this._itemType = BOOKMARK_ITEM;

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

@ -529,7 +529,7 @@ let gEditItemOverlay = {
let itemId = this._paneInfo.itemId;
let description = this._element("descriptionField").value;
if (description != PlacesUIUtils.getItemDescription(this._itemId)) {
if (description != PlacesUIUtils.getItemDescription(this._paneInfo.itemId)) {
let annotation =
{ name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
if (!PlacesUIUtils.useAsyncTransactions) {
@ -562,7 +562,7 @@ let gEditItemOverlay = {
if (!PlacesUIUtils.useAsyncTransactions) {
let itemId = this._paneInfo.itemId;
let txn = new PlacesEditBookmarkURITransaction(this._itemId, newURI);
let txn = new PlacesEditBookmarkURITransaction(this._paneInfo.itemId, newURI);
PlacesUtils.transactionManager.doTransaction(txn);
return;
}
@ -677,7 +677,7 @@ let gEditItemOverlay = {
if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
// reset the selection back to where it was and expand the tree
// (this menu-item is hidden when the tree is already visible
let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId);
let item = this._getFolderMenuItem(containerId);
this._folderMenuList.selectedItem = item;
// XXXmano HACK: setTimeout 100, otherwise focus goes back to the
@ -697,7 +697,7 @@ let gEditItemOverlay = {
}.bind(this));
}
else {
let txn = new PlacesMoveItemTransaction(this._itemId,
let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId,
containerId,
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.transactionManager.doTransaction(txn);

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

@ -598,6 +598,30 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
});
},
/**
* Copy the raw request headers from the currently selected item.
*/
copyRequestHeaders: function() {
let selected = this.selectedItem.attachment;
let rawHeaders = selected.requestHeaders.rawHeaders.trim();
if (Services.appinfo.OS !== "WINNT") {
rawHeaders = rawHeaders.replace(/\r/g, "");
}
clipboardHelper.copyString(rawHeaders, document);
},
/**
* Copy the raw response headers from the currently selected item.
*/
copyResponseHeaders: function() {
let selected = this.selectedItem.attachment;
let rawHeaders = selected.responseHeaders.rawHeaders.trim();
if (Services.appinfo.OS !== "WINNT") {
rawHeaders = rawHeaders.replace(/\r/g, "");
}
clipboardHelper.copyString(rawHeaders, document);
},
/**
* Copy image as data uri.
*/
@ -1809,6 +1833,12 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
let copyAsCurlElement = $("#request-menu-context-copy-as-curl");
copyAsCurlElement.hidden = !selectedItem || !selectedItem.attachment.responseContent;
let copyRequestHeadersElement = $("#request-menu-context-copy-request-headers");
copyRequestHeadersElement.hidden = !selectedItem || !selectedItem.attachment.requestHeaders;
let copyResponseHeadersElement = $("#response-menu-context-copy-response-headers");
copyResponseHeadersElement.hidden = !selectedItem || !selectedItem.attachment.responseHeaders;
let copyResponse = $("#request-menu-context-copy-response");
copyResponse.hidden = !selectedItem ||
!selectedItem.attachment.responseContent ||

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

@ -34,6 +34,13 @@
<menuitem id="request-menu-context-copy-as-curl"
label="&netmonitorUI.context.copyAsCurl;"
oncommand="NetMonitorView.RequestsMenu.copyAsCurl();"/>
<menuitem id="request-menu-context-copy-request-headers"
label="&netmonitorUI.context.copyRequestHeaders;"
accesskey="&netmonitorUI.context.copyRequestHeaders.accesskey;"
oncommand="NetMonitorView.RequestsMenu.copyRequestHeaders();"/>
<menuitem id="response-menu-context-copy-response-headers"
label="&netmonitorUI.context.copyResponseHeaders;"
oncommand="NetMonitorView.RequestsMenu.copyResponseHeaders();"/>
<menuitem id="request-menu-context-copy-response"
label="&netmonitorUI.context.copyResponse;"
accesskey="&netmonitorUI.context.copyResponse.accesskey;"/>

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

@ -54,6 +54,7 @@ skip-if= buildapp == 'mulet'
[browser_net_copy_image_as_data_uri.js]
[browser_net_copy_url.js]
[browser_net_copy_response.js]
[browser_net_copy_headers.js]
[browser_net_copy_as_curl.js]
skip-if = e10s # Bug 1091596
[browser_net_cyrillic-01.js]

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

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if copying a request's request/response headers works.
*/
add_task(function*() {
let [ aTab, aDebuggee, aMonitor ] = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
let { NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
aDebuggee.location.reload();
yield waitForNetworkEvents(aMonitor, 1);
let requestItem = RequestsMenu.getItemAtIndex(0);
RequestsMenu.selectedItem = requestItem;
let clipboard = null;
const EXPECTED_REQUEST_HEADERS = [
requestItem.attachment.method + " " + SIMPLE_URL + " " + requestItem.attachment.httpVersion,
"Host: example.com",
"User-Agent: " + navigator.userAgent + "",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: " + navigator.languages.join(",") + ";q=0.5",
"Accept-Encoding: gzip, deflate",
"Connection: keep-alive",
"Pragma: no-cache",
"Cache-Control: no-cache"
].join("\n");
RequestsMenu.copyRequestHeaders();
clipboard = SpecialPowers.getClipboardData("text/unicode");
// Sometimes, a "Cookie" header is left over from other tests. Remove it:
clipboard = clipboard.replace(/Cookie: [^\n]+\n/, "");
is(clipboard, EXPECTED_REQUEST_HEADERS, "Clipboard contains the currently selected item's request headers.");
const EXPECTED_RESPONSE_HEADERS = [
requestItem.attachment.httpVersion + " " + requestItem.attachment.status + " " + requestItem.attachment.statusText,
"Last-Modified: Sun, 3 May 2015 11:11:11 GMT",
"Content-Type: text/html",
"Content-Length: 465",
"Connection: close",
"Server: httpd.js",
"Date: Sun, 3 May 2015 11:11:11 GMT"
].join("\n");
RequestsMenu.copyResponseHeaders();
clipboard = SpecialPowers.getClipboardData("text/unicode");
// Fake the "Last-Modified" and "Date" headers because they will vary:
clipboard = clipboard
.replace(/Last-Modified: [^\n]+ GMT/, "Last-Modified: Sun, 3 May 2015 11:11:11 GMT")
.replace(/Date: [^\n]+ GMT/, "Date: Sun, 3 May 2015 11:11:11 GMT");
is(clipboard, EXPECTED_RESPONSE_HEADERS, "Clipboard contains the currently selected item's response headers.");
teardown(aMonitor).then(finish);
});

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

@ -33,7 +33,7 @@
background-position: -48px center;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#eyedropper-button {
background-image: url("chrome://browser/skin/devtools/command-eyedropper@2x.png");
}

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

@ -42,7 +42,7 @@
position: relative;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.breakpoint {
background-image: url("chrome://browser/skin/devtools/editor-breakpoint@2x.png");
}
@ -56,7 +56,7 @@
background-image: url("chrome://browser/skin/devtools/editor-debug-location.png");
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.debugLocation {
background-image: url("chrome://browser/skin/devtools/editor-debug-location@2x.png");
}
@ -68,7 +68,7 @@
url("chrome://browser/skin/devtools/editor-breakpoint.png");
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.breakpoint.debugLocation {
background-image:
url("chrome://browser/skin/devtools/editor-debug-location@2x.png"),

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

@ -260,16 +260,16 @@
- on the context menu that copies the selected request's url -->
<!ENTITY netmonitorUI.context.copyUrl "Copy URL">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyUrl.accesskey): This is the access key
- for the Copy URL menu item displayed in the context menu for a request -->
<!ENTITY netmonitorUI.context.copyUrl.accesskey "C">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyAsCurl): This is the label displayed
- on the context menu that copies the selected request as a cURL command.
- The capitalization is part of the official name and should be used throughout all languages.
- http://en.wikipedia.org/wiki/CURL -->
<!ENTITY netmonitorUI.context.copyAsCurl "Copy as cURL">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyUrl.accesskey): This is the access key
- for the Copy URL menu item displayed in the context menu for a request -->
<!ENTITY netmonitorUI.context.copyUrl.accesskey "C">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyImageAsDataUri): This is the label displayed
- on the context menu that copies the selected image as data uri -->
<!ENTITY netmonitorUI.context.copyImageAsDataUri "Copy Image as Data URI">
@ -282,10 +282,22 @@
- on the context menu that copies the selected response as a string -->
<!ENTITY netmonitorUI.context.copyResponse "Copy Response">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyImageAsDataUri.accesskey): This is the access key
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyRespose.accesskey): This is the access key
- for the Copy Response menu item displayed in the context menu for a request -->
<!ENTITY netmonitorUI.context.copyResponse.accesskey "R">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyRequestHeaders): This is the label displayed
- on the context menu that copies the selected item's request headers -->
<!ENTITY netmonitorUI.context.copyRequestHeaders "Copy Request Headers">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyRequestHeaders.accesskey): This is the access key
- for the Copy Request Headers menu item displayed in the context menu for a request -->
<!ENTITY netmonitorUI.context.copyRequestHeaders.accesskey "H">
<!-- LOCALIZATION NOTE (netmonitorUI.context.copyResponseHeaders): This is the label displayed
- on the context menu that copies the selected item's response headers -->
<!ENTITY netmonitorUI.context.copyResponseHeaders "Copy Response Headers">
<!-- LOCALIZATION NOTE (netmonitorUI.summary.editAndResend): This is the label displayed
- on the button in the headers tab that opens a form to edit and resend the currently
displayed request -->

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

@ -84,7 +84,7 @@ body {
background-image: url("debugger-play.png");
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#element-picker::before {
background-image: url("chrome://browser/skin/devtools/command-pick@2x.png");
background-size: 64px;
@ -156,7 +156,7 @@ body {
background-image: url(rewind.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.timeline .toggle::before {
background-image: url(debugger-pause@2x.png);
}

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

@ -145,7 +145,7 @@
list-style-image: url(debugger-step-out.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#resume {
list-style-image: url(debugger-play@2x.png);
-moz-image-region: rect(0px,64px,32px,32px);
@ -250,7 +250,7 @@
background-size: 12px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.selected .call-item-gutter {
background-image: url("editor-debug-location@2x.png");
}

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

@ -82,7 +82,7 @@
-moz-image-region: rect(0px, 64px, 16px, 48px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#developer-toolbar-toolbox-button {
list-style-image: url("chrome://browser/skin/devtools/toggle-tools@2x.png");
-moz-image-region: rect(0px, 32px, 32px, 0px);
@ -111,7 +111,7 @@
opacity: 0.6;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#developer-toolbar-closebutton {
list-style-image: url("chrome://browser/skin/devtools/close@2x.png");
}
@ -199,7 +199,7 @@ html|*#gcli-output-frame {
background-position: -16px center;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.gclitoolbar-input-node::before {
background-image: url("chrome://browser/skin/devtools/commandline-icon@2x.png");
}

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

@ -61,7 +61,7 @@ body {
background-size: 5px 8px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.property-value, .other-property-value {
background-image: url(arrow-e@2x.png);
}

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

@ -326,7 +326,7 @@ div.CodeMirror span.eval-text {
background-position: -42px 0;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-twisty, .theme-checkbox {
background-image: url("chrome://browser/skin/devtools/controls@2x.png");
}
@ -364,7 +364,7 @@ div.CodeMirror span.eval-text {
margin-left: -4px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-tooltip-panel .panel-arrow[side="top"],
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-dark@2x.png");

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

@ -66,7 +66,7 @@
list-style-image: url(debugger-blackbox.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#black-box {
list-style-image: url(debugger-blackbox@2x.png);
}
@ -76,7 +76,7 @@
list-style-image: url(debugger-prettyprint.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#pretty-print {
list-style-image: url(debugger-prettyprint@2x.png);
}
@ -86,7 +86,7 @@
list-style-image: url(debugger-toggleBreakpoints.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#toggle-breakpoints {
list-style-image: url(debugger-toggleBreakpoints@2x.png);
}
@ -100,7 +100,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#sources-toolbar .devtools-toolbarbutton:not([label]) {
-moz-image-region: rect(0px,32px,32px,0px);
}
@ -134,7 +134,7 @@
-moz-margin-end: 5px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#black-boxed-message-button > .button-box > .button-icon {
background-image: url(debugger-blackbox@2x.png);
}
@ -222,7 +222,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#trace {
list-style-image: url(tracer-icon@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);
@ -324,7 +324,7 @@
margin: 2px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.dbg-expression-arrow {
background-image: url(commandline-icon@2x.png);
}
@ -560,7 +560,7 @@
list-style-image: url(debugger-play.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#resume {
list-style-image: url(debugger-pause@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);
@ -592,7 +592,7 @@
list-style-image: url(debugger-step-out.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#step-over {
list-style-image: url(debugger-step-over@2x.png);
}
@ -622,7 +622,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#instruments-pane-toggle {
list-style-image: url(debugger-collapse@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);

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

@ -60,7 +60,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#inspector-pane-toggle {
list-style-image: url(debugger-collapse@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);

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

@ -335,7 +335,7 @@ div.CodeMirror span.eval-text {
background-position: -14px 0;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-twisty, .theme-checkbox {
background-image: url("chrome://browser/skin/devtools/controls@2x.png");
}
@ -373,7 +373,7 @@ div.CodeMirror span.eval-text {
margin-left: -4px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-tooltip-panel .panel-arrow[side="top"],
.theme-tooltip-panel .panel-arrow[side="bottom"] {
list-style-image: url("chrome://browser/skin/devtools/tooltip/arrow-vertical-light@2x.png");

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

@ -470,7 +470,7 @@ label.requests-menu-status-code {
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#details-pane-toggle {
list-style-image: url("chrome://browser/skin/devtools/debugger-collapse@2x.png");
-moz-image-region: rect(0px,32px,32px,0px);
@ -603,7 +603,7 @@ label.requests-menu-status-code {
height: 12px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.security-warning-icon {
background-image: url(alerticon-warning@2x.png);
}

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

@ -281,7 +281,7 @@
background-image: url(magnifying-glass-light.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-dark .call-tree-zoom {
background-image: url(magnifying-glass@2x.png);
}
@ -610,7 +610,7 @@
background-position: -16px -16px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#jit-optimizations-view .opt-icon::before {
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
}

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

@ -190,7 +190,7 @@
background-image: url(newtab.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-dark #profile-newtab-button {
background-image: url(newtab-inverted@2x.png);
}
@ -367,7 +367,7 @@
background-image: url(magnifying-glass-light.png);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-dark .call-tree-zoom {
background-image: url(magnifying-glass@2x.png);
}

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

@ -155,7 +155,7 @@
list-style-image: url("chrome://browser/skin/devtools/responsiveui-rotate.png");
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.devtools-responsiveui-close {
list-style-image: url("chrome://browser/skin/devtools/close@2x.png");
}
@ -174,7 +174,7 @@
-moz-image-region: rect(0px,32px,16px,16px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.devtools-responsiveui-touch {
list-style-image: url("chrome://browser/skin/devtools/responsiveui-touch@2x.png");
-moz-image-region: rect(0px,32px,32px,0px);
@ -189,7 +189,7 @@
list-style-image: url("chrome://browser/skin/devtools/responsiveui-screenshot.png");
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.devtools-responsiveui-screenshot {
list-style-image: url("chrome://browser/skin/devtools/responsiveui-screenshot@2x.png");
}
@ -321,7 +321,7 @@
border-bottom-left-radius: 12px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.devtools-responsiveui-resizebarV {
background-image: url("chrome://browser/skin/devtools/responsive-vertical-resizer@2x.png");
}

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

@ -107,7 +107,7 @@
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.ruleview-warning {
background-image: url(alerticon-warning@2x.png);
}
@ -194,7 +194,7 @@
background-size: 1em;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.ruleview-bezierswatch {
background: url("chrome://browser/skin/devtools/cubic-bezier-swatch@2x.png");
background-size: 1em;

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

@ -60,7 +60,7 @@
border: 0;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.side-menu-widget-item-checkbox .checkbox-check {
background-image: url(itemToggle@2x.png);
}

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

@ -115,7 +115,7 @@
height: 40px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.stylesheet-enabled {
background-image: url(itemToggle@2x.png);
}

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

@ -316,7 +316,7 @@
background-repeat: no-repeat;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.devtools-button::before {
background-size: 32px;
}
@ -437,7 +437,7 @@
-moz-image-region: rect(0, 32px, 16px, 16px);
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.theme-dark .devtools-searchinput {
background-image: url(magnifying-glass@2x.png);
}
@ -769,7 +769,7 @@
background-image: url("chrome://browser/skin/devtools/command-rulers.png");
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#command-button-paintflashing > image {
background-image: url("chrome://browser/skin/devtools/command-paintflashing@2x.png");
}

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

@ -187,7 +187,7 @@ text {
-moz-box-flex: 1;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
#inspector-pane-toggle {
list-style-image: url(debugger-collapse@2x.png);
-moz-image-region: rect(0px,32px,32px,0px);

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

@ -52,7 +52,7 @@ a {
display: inline-block;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.message > .icon::before {
background-image: url(chrome://browser/skin/devtools/webconsole@2x.png);
}
@ -361,7 +361,7 @@ a {
background-size: 16px 16px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.jsterm-input-node {
background-image: -moz-image-rect(url('chrome://browser/skin/devtools/commandline-icon@2x.png'), 0, 64, 32, 32);
}

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

@ -107,7 +107,7 @@
padding: 0;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.scrollbutton-up > .toolbarbutton-icon,
.scrollbutton-down > .toolbarbutton-icon {
background-image: url("breadcrumbs-scrollbutton@2x.png");
@ -637,7 +637,7 @@
height: 16px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.variable-or-property-non-writable-icon {
background-image: url("chrome://browser/skin/devtools/vview-lock@2x.png");
}
@ -737,7 +737,7 @@
height: 16px;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.variables-view-delete {
background-image: url("chrome://browser/skin/devtools/vview-delete@2x.png");
}
@ -763,7 +763,7 @@
cursor: pointer;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.variables-view-edit {
background-image: url("chrome://browser/skin/devtools/vview-edit@2x.png");
}
@ -789,7 +789,7 @@
cursor: pointer;
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.variables-view-open-inspector {
background-image: url("chrome://browser/skin/devtools/vview-open-inspector@2x.png");
}
@ -1437,7 +1437,7 @@
}
}
@media (min-resolution: 2dppx) {
@media (min-resolution: 1.25dppx) {
.tree-widget-item:before {
background-image: url("chrome://browser/skin/devtools/controls@2x.png");
}

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

@ -918,13 +918,30 @@ public class BrowserApp extends GeckoApp
@Override
public void run() {
if (TabQueueHelper.shouldOpenTabQueueUrls(BrowserApp.this)) {
TabQueueHelper.openQueuedUrls(BrowserApp.this, mProfile, TabQueueHelper.FILE_NAME, false);
openQueuedTabs();
}
}
});
}
}
private void openQueuedTabs() {
ThreadUtils.assertNotOnUiThread();
int queuedTabCount = TabQueueHelper.getTabQueueLength(BrowserApp.this);
TabQueueHelper.openQueuedUrls(BrowserApp.this, mProfile, TabQueueHelper.FILE_NAME, false);
// If there's more than one tab then also show the tabs panel.
if (queuedTabCount > 1) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
showNormalTabs();
}
});
}
}
@Override
public void onResume() {
super.onResume();
@ -3423,18 +3440,7 @@ public class BrowserApp extends GeckoApp
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
int queuedTabCount = TabQueueHelper.getTabQueueLength(BrowserApp.this);
TabQueueHelper.openQueuedUrls(BrowserApp.this, mProfile, TabQueueHelper.FILE_NAME, false);
// If there's more than one tab then also show the tabs panel.
if (queuedTabCount > 1) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
showNormalTabs();
}
});
}
openQueuedTabs();
}
});
}

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

@ -783,7 +783,7 @@ class KeyedHistogram {
public:
KeyedHistogram(const nsACString &name, const nsACString &expiration,
uint32_t histogramType, uint32_t min, uint32_t max,
uint32_t bucketCount);
uint32_t bucketCount, uint32_t dataset);
nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession);
Histogram* GetHistogram(const nsCString& name, bool subsession);
uint32_t GetHistogramType() const { return mHistogramType; }
@ -821,6 +821,7 @@ private:
const uint32_t mMin;
const uint32_t mMax;
const uint32_t mBucketCount;
const uint32_t mDataset;
};
// Hardcoded probes
@ -874,22 +875,41 @@ IsValidHistogramName(const nsACString& name)
}
bool
IsInDataset(const TelemetryHistogram& h, uint32_t dataset)
IsInDataset(uint32_t dataset, uint32_t containingDataset)
{
if (h.dataset == dataset) {
if (dataset == containingDataset) {
return true;
}
// The "optin on release channel" dataset is a superset of the
// "optout on release channel one".
if (dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN
&& h.dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT) {
if (containingDataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN
&& dataset == nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT) {
return true;
}
return false;
}
bool
CanRecordDataset(uint32_t dataset)
{
// If we are extended telemetry is enabled, we are allowed to record regardless of
// the dataset.
if (TelemetryImpl::CanRecordExtended()) {
return true;
}
// If base telemetry data is enabled and we're trying to record base telemetry, allow it.
if (TelemetryImpl::CanRecordBase() &&
IsInDataset(dataset, nsITelemetry::DATASET_RELEASE_CHANNEL_OPTOUT)) {
return true;
}
// We're not recording extended telemetry or this is not the base dataset. Bail out.
return false;
}
nsresult
CheckHistogramArguments(uint32_t histogramType, uint32_t min, uint32_t max,
uint32_t bucketCount, bool haveOptArgs)
@ -1079,8 +1099,13 @@ GetSubsessionHistogram(Histogram& existing)
#endif
nsresult
HistogramAdd(Histogram& histogram, int32_t value)
HistogramAdd(Histogram& histogram, int32_t value, uint32_t dataset)
{
// Check if we are allowed to record the data.
if (!CanRecordDataset(dataset)) {
return NS_OK;
}
histogram.Add(value);
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
if (Histogram* subsession = GetSubsessionHistogram(histogram)) {
@ -1091,6 +1116,26 @@ HistogramAdd(Histogram& histogram, int32_t value)
return NS_OK;
}
nsresult
HistogramAdd(Histogram& histogram, int32_t value)
{
uint32_t dataset = nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN;
// We only really care about the dataset of the histogram if we are not recording
// extended telemetry. Otherwise, we always record histogram data.
if (!TelemetryImpl::CanRecordExtended()) {
Telemetry::ID id;
nsresult rv = TelemetryImpl::GetHistogramEnumId(histogram.histogram_name().c_str(), &id);
if (NS_FAILED(rv)) {
// If we can't look up the dataset, it might be because the histogram was added
// at runtime. Since we're not recording extended telemetry, bail out.
return NS_OK;
}
dataset = gHistograms[id].dataset;
}
return HistogramAdd(histogram, value, dataset);
}
bool
FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h)
{
@ -1210,7 +1255,7 @@ JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
}
}
if (TelemetryImpl::CanRecordExtended()) {
if (TelemetryImpl::CanRecordBase()) {
HistogramAdd(*h, value);
}
@ -1866,7 +1911,7 @@ mFailedLockCount(0)
const nsDependentCString id(h.id());
const nsDependentCString expiration(h.expiration());
mKeyedHistograms.Put(id, new KeyedHistogram(id, expiration, h.histogramType,
h.min, h.max, h.bucketCount));
h.min, h.max, h.bucketCount, h.dataset));
}
}
@ -1913,7 +1958,8 @@ TelemetryImpl::NewKeyedHistogram(const nsACString &name, const nsACString &expir
}
KeyedHistogram* keyed = new KeyedHistogram(name, expiration, histogramType,
min, max, bucketCount);
min, max, bucketCount,
nsITelemetry::DATASET_RELEASE_CHANNEL_OPTIN);
if (MOZ_UNLIKELY(!mKeyedHistograms.Put(name, keyed, fallible))) {
delete keyed;
return NS_ERROR_OUT_OF_MEMORY;
@ -3076,7 +3122,7 @@ GetRegisteredHistogramIds(bool keyed, uint32_t dataset, uint32_t *aCount,
for (size_t i = 0; i < ArrayLength(gHistograms); ++i) {
const TelemetryHistogram& h = gHistograms[i];
if (IsExpired(h.expiration()) || h.keyed != keyed ||
!IsInDataset(h, dataset)) {
!IsInDataset(h.dataset, dataset)) {
continue;
}
@ -3622,22 +3668,22 @@ namespace Telemetry {
void
Accumulate(ID aHistogram, uint32_t aSample)
{
if (!TelemetryImpl::CanRecordExtended()) {
if (!TelemetryImpl::CanRecordBase()) {
return;
}
Histogram *h;
nsresult rv = GetHistogramByEnumId(aHistogram, &h);
if (NS_SUCCEEDED(rv))
HistogramAdd(*h, aSample);
if (NS_SUCCEEDED(rv)) {
HistogramAdd(*h, aSample, gHistograms[aHistogram].dataset);
}
}
void
Accumulate(ID aID, const nsCString& aKey, uint32_t aSample)
{
if (!TelemetryImpl::CanRecordExtended()) {
if (!TelemetryImpl::CanRecordBase()) {
return;
}
const TelemetryHistogram& th = gHistograms[aID];
KeyedHistogram* keyed = TelemetryImpl::GetKeyedHistogramById(nsDependentCString(th.id()));
MOZ_ASSERT(keyed);
@ -3647,7 +3693,7 @@ Accumulate(ID aID, const nsCString& aKey, uint32_t aSample)
void
Accumulate(const char* name, uint32_t sample)
{
if (!TelemetryImpl::CanRecordExtended()) {
if (!TelemetryImpl::CanRecordBase()) {
return;
}
ID id;
@ -3659,7 +3705,7 @@ Accumulate(const char* name, uint32_t sample)
Histogram *h;
rv = GetHistogramByEnumId(id, &h);
if (NS_SUCCEEDED(rv)) {
HistogramAdd(*h, sample);
HistogramAdd(*h, sample, gHistograms[id].dataset);
}
}
@ -4066,7 +4112,7 @@ XRE_TelemetryAccumulate(int aID, uint32_t aSample)
KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expiration,
uint32_t histogramType, uint32_t min, uint32_t max,
uint32_t bucketCount)
uint32_t bucketCount, uint32_t dataset)
: mHistogramMap()
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
, mSubsessionMap()
@ -4077,6 +4123,7 @@ KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expirat
, mMin(min)
, mMax(max)
, mBucketCount(bucketCount)
, mDataset(dataset)
{
}
@ -4140,14 +4187,7 @@ nsresult
KeyedHistogram::GetDataset(uint32_t* dataset) const
{
MOZ_ASSERT(dataset);
Telemetry::ID id;
nsresult rv = TelemetryImpl::GetHistogramEnumId(mName.get(), &id);
if (NS_FAILED(rv)) {
return rv;
}
*dataset = gHistograms[id].dataset;
*dataset = mDataset;
return NS_OK;
}
@ -4162,7 +4202,7 @@ KeyedHistogram::ClearHistogramEnumerator(KeyedHistogramEntry* entry, void*)
nsresult
KeyedHistogram::Add(const nsCString& key, uint32_t sample)
{
if (!TelemetryImpl::CanRecordExtended()) {
if (!CanRecordDataset(mDataset)) {
return NS_OK;
}

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

@ -360,6 +360,60 @@ function test_privateMode() {
do_check_neq(uneval(orig), uneval(h.snapshot()));
}
// Check that telemetry records only when it is suppose to.
function test_histogramRecording() {
// Check that no histogram is recorded if both base and extended recording are off.
Telemetry.canRecordBase = false;
Telemetry.canRecordExtended = false;
let h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTOUT");
h.clear();
let orig = h.snapshot();
h.add(1);
Assert.equal(orig.sum, h.snapshot().sum);
// Check that only base histograms are recorded.
Telemetry.canRecordBase = true;
h.add(1);
Assert.equal(orig.sum + 1, h.snapshot().sum,
"Histogram value should have incremented by 1 due to recording.");
// Extended histograms should not be recorded.
h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTIN");
orig = h.snapshot();
h.add(1);
Assert.equal(orig.sum, h.snapshot().sum,
"Histograms should be equal after recording.");
// Runtime created histograms should not be recorded.
h = Telemetry.newHistogram("test::runtime_created_boolean", "never", Telemetry.HISTOGRAM_BOOLEAN);
orig = h.snapshot();
h.add(1);
Assert.equal(orig.sum, h.snapshot().sum,
"Histograms should be equal after recording.");
// Check that extended histograms are recorded when required.
Telemetry.canRecordExtended = true;
h.add(1);
Assert.equal(orig.sum + 1, h.snapshot().sum,
"Runtime histogram value should have incremented by 1 due to recording.");
h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTIN");
orig = h.snapshot();
h.add(1);
Assert.equal(orig.sum + 1, h.snapshot().sum,
"Histogram value should have incremented by 1 due to recording.");
// Check that base histograms are still being recorded.
h = Telemetry.getHistogramById("TELEMETRY_TEST_RELEASE_OPTOUT");
h.clear();
orig = h.snapshot();
h.add(1);
Assert.equal(orig.sum + 1, h.snapshot().sum,
"Histogram value should have incremented by 1 due to recording.");
}
// Check that histograms that aren't flagged as needing extended stats
// don't record extended stats.
function test_extended_stats() {
@ -528,6 +582,56 @@ function test_keyed_flag_histogram()
Assert.deepEqual(h.snapshot(), {});
}
function test_keyed_histogram_recording() {
// Check that no histogram is recorded if both base and extended recording are off.
Telemetry.canRecordBase = false;
Telemetry.canRecordExtended = false;
const TEST_KEY = "record_foo";
let h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT");
h.clear();
h.add(TEST_KEY, 1);
Assert.equal(h.snapshot(TEST_KEY).sum, 0);
// Check that only base histograms are recorded.
Telemetry.canRecordBase = true;
h.add(TEST_KEY, 1);
Assert.equal(h.snapshot(TEST_KEY).sum, 1,
"The keyed histogram should record the correct value.");
// Extended set keyed histograms should not be recorded.
h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTIN");
h.clear();
h.add(TEST_KEY, 1);
Assert.equal(h.snapshot(TEST_KEY).sum, 0,
"The keyed histograms should not record any data.");
// Runtime created histograms should not be recorded.
h = Telemetry.newKeyedHistogram("test::runtime_keyed_boolean", "never", Telemetry.HISTOGRAM_BOOLEAN);
h.add(TEST_KEY, 1);
Assert.equal(h.snapshot(TEST_KEY).sum, 0,
"The keyed histogram should not record any data.");
// Check that extended histograms are recorded when required.
Telemetry.canRecordExtended = true;
h.add(TEST_KEY, 1);
Assert.equal(h.snapshot(TEST_KEY).sum, 1,
"The runtime keyed histogram should record the correct value.");
h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTIN");
h.clear();
h.add(TEST_KEY, 1);
Assert.equal(h.snapshot(TEST_KEY).sum, 1,
"The keyed histogram should record the correct value.");
// Check that base histograms are still being recorded.
h = Telemetry.getKeyedHistogramById("TELEMETRY_TEST_KEYED_RELEASE_OPTOUT");
h.clear();
h.add(TEST_KEY, 1);
Assert.equal(h.snapshot(TEST_KEY).sum, 1);
}
function test_keyed_histogram() {
// Check that invalid names get rejected.
@ -554,6 +658,7 @@ function test_keyed_histogram() {
test_keyed_boolean_histogram();
test_keyed_count_histogram();
test_keyed_flag_histogram();
test_keyed_histogram_recording();
}
function test_datasets()
@ -770,6 +875,7 @@ function run_test()
test_histogramFrom();
test_getSlowSQL();
test_privateMode();
test_histogramRecording();
test_addons();
test_extended_stats();
test_expired_histogram();

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

@ -180,15 +180,18 @@ exports.items = [
throw new Error(l10n.lookup("screenshotSelectorChromeConflict"));
}
let capture;
if (!args.chrome) {
// Re-execute the command on the server
const command = context.typed.replace(/^screenshot/, "screenshot_server");
return context.updateExec(command).then(output => {
capture = context.updateExec(command).then(output => {
return output.error ? Promise.reject(output.data) : output.data;
});
} else {
capture = captureScreenshot(args, context.environment.chromeDocument);
}
return processScreenshot(args, context.environment.chromeDocument);
return capture.then(saveScreenshot.bind(null, args, context));
},
},
{
@ -199,53 +202,45 @@ exports.items = [
returnType: "imageSummary",
params: [ filenameParam, standardParams ],
exec: function(args, context) {
return processScreenshot(args, context.environment.document);
return captureScreenshot(args, context.environment.document);
},
}
];
/**
* This function simply handles the --delay argument before calling
* processScreenshotNow
* createScreenshotData
*/
function processScreenshot(args, document) {
function captureScreenshot(args, document) {
if (args.delay > 0) {
return new Promise((resolve, reject) => {
document.defaultView.setTimeout(() => {
processScreenshotNow(args, document).then(resolve, reject);
createScreenshotData(document, args).then(resolve, reject);
}, args.delay * 1000);
});
}
else {
return processScreenshotNow(args, document);
return createScreenshotData(document, args);
}
}
/**
* There are several possible destinations for the screenshot, SKIP is used
* in processScreenshotNow() whenever one of them is not used
* in saveScreenshot() whenever one of them is not used
*/
const SKIP = Promise.resolve();
/**
* This is just like exec, except the 'delay' has been handled already so
* this is where we do that actual work of process the screenshot
* Save the captured screenshot to one of several destinations.
*/
function processScreenshotNow(args, document) {
const reply = createScreenshotData(document, args);
const loadContext = document.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsILoadContext);
function saveScreenshot(args, context, reply) {
const fileNeeded = args.filename != FILENAME_DEFAULT_VALUE ||
(!args.imgur && !args.clipboard);
return Promise.all([
args.clipboard ? saveToClipboard(loadContext, reply) : SKIP,
args.imgur ? uploadToImgur(reply) : SKIP,
fileNeeded ? saveToFile(loadContext, reply) : SKIP,
args.clipboard ? saveToClipboard(context, reply) : SKIP,
args.imgur ? uploadToImgur(reply) : SKIP,
fileNeeded ? saveToFile(context, reply) : SKIP,
]).then(() => reply);
}
@ -300,13 +295,13 @@ function createScreenshotData(document, args) {
window.scrollTo(currentX, currentY);
}
return {
return Promise.resolve({
destinations: [],
data: data,
height: height,
width: width,
filename: getFilename(args.filename),
};
});
}
/**
@ -339,8 +334,12 @@ function getFilename(defaultName) {
* be treated exactly like imgur / file processing, but it's really sync
* for now.
*/
function saveToClipboard(loadContext, reply) {
function saveToClipboard(context, reply) {
try {
const loadContext = context.environment.chromeWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsILoadContext);
const io = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
const channel = io.newChannel2(reply.data, null, null,
@ -422,37 +421,21 @@ function uploadToImgur(reply) {
* Save the screenshot data to disk, returning a promise which
* is resolved on completion
*/
function saveToFile(loadContext, reply) {
function saveToFile(context, reply) {
return Task.spawn(function*() {
try {
let document = context.environment.chromeDocument;
let window = context.environment.chromeWindow;
let filename = reply.filename;
// Check there is a .png extension to filename
if (!filename.match(/.png$/i)) {
filename += ".png";
}
// If the filename is relative, tack it onto the download directory
if (!filename.match(/[\\\/]/)) {
const preferredDir = yield Downloads.getPreferredDownloadsDirectory();
filename = OS.Path.join(preferredDir, filename);
reply.filename = filename;
}
const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(filename);
const ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
const Persist = Ci.nsIWebBrowserPersist;
const persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
.createInstance(Persist);
persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
// TODO: UTF8? For an image?
const source = ioService.newURI(reply.data, "UTF8", null);
persist.saveURI(source, null, null, 0, null, null, file, loadContext);
window.saveURL(reply.data, filename, null,
true /* aShouldBypassCache */, true /* aSkipPrompt */,
document.documentURIObject, document);
reply.destinations.push(l10n.lookup("screenshotSavedToFile") + " \"" + filename + "\"");
}

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

@ -100,7 +100,9 @@ const GcliActor = ActorClass({
request: {
customProps: Arg(0, "nullable:array:string")
},
response: RetVal("json")
response: {
value: RetVal("array:json")
}
}),
/**

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

@ -45,7 +45,7 @@ this.AUSTLMY = {
CHK_SHOWPROMPT_PREF: 3,
// Incompatible add-on check disabled by preference (background download)
CHK_ADDON_PREF_DISABLED: 4,
// Incompatible add-on checke not performed due to same app version as the
// Incompatible add-on check not performed due to same app version as the
// update's app version (background download)
CHK_ADDON_SAME_APP_VER: 5,
// Incompatible add-ons found and all of them have updates (background download)

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

@ -648,12 +648,11 @@ function getCanStageUpdates() {
return false;
}
if (AppConstants.platform == "win" && isServiceInstalled() &&
shouldUseService()) {
// No need to perform directory write checks, the maintenance service will
// be able to write to all directories.
LOG("getCanStageUpdates - able to stage updates because we'll use the service");
LOG("getCanStageUpdates - able to stage updates using the service");
return true;
}
@ -661,7 +660,7 @@ function getCanStageUpdates() {
// files into place.
if (AppConstants.platform == "gonk") {
LOG("getCanStageUpdates - able to stage updates because this is gonk");
return true;
return true;
}
if (!hasUpdateMutex()) {

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

@ -376,7 +376,8 @@ function run_test_pt11() {
function check_test_pt11() {
let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
Assert.ok(!bestUpdate);
Assert.ok(!bestUpdate,
"there should be no update available");
run_test_pt12();
}
@ -392,7 +393,8 @@ function run_test_pt12() {
function check_test_pt12() {
let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
Assert.ok(!!bestUpdate);
Assert.ok(!!bestUpdate,
"there should be one update available");
Assert.equal(bestUpdate.displayVersion, "version 1.0",
"the update displayVersion attribute" + MSG_SHOULD_EQUAL);