Merge fx-team to central, a=merge

This commit is contained in:
Wes Kocher 2015-09-29 15:56:09 -07:00
commit bfea98b549
71 changed files with 2762 additions and 100 deletions

View File

@ -1,3 +1,7 @@
user_pref("devtools.debugger.prompt-connection", false);
user_pref("devtools.debugger.forbid-certified-apps", false);
user_pref("devtools.apps.forbidden-permissions", "");
// Required for Mulet in order to run the debugger server from the command line
user_pref("devtools.debugger.remote-enabled", true);
user_pref("devtools.chrome.enabled", true);

View File

@ -1344,7 +1344,7 @@ pref("devtools.toolbox.sidebar.width", 500);
pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.previousHost", "side");
pref("devtools.toolbox.selectedTool", "webconsole");
pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage", "rulers"]');
pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage", "rulers", "measure"]');
pref("devtools.toolbox.sideEnabled", true);
pref("devtools.toolbox.zoomValue", "1");
pref("devtools.toolbox.splitconsoleEnabled", false);
@ -1361,6 +1361,7 @@ pref("devtools.command-button-responsive.enabled", true);
pref("devtools.command-button-eyedropper.enabled", false);
pref("devtools.command-button-screenshot.enabled", false);
pref("devtools.command-button-rulers.enabled", false);
pref("devtools.command-button-measure.enabled", false);
// Inspector preferences
// Enable the Inspector

View File

@ -37,6 +37,7 @@ loop.store.ActiveRoomStore = (function() {
var sharedActions = loop.shared.actions;
var crypto = loop.crypto;
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
@ -107,6 +108,7 @@ loop.store.ActiveRoomStore = (function() {
*/
_statesToResetOnLeave: [
"audioMuted",
"chatMessageExchanged",
"localSrcMediaElement",
"localVideoDimensions",
"mediaConnected",
@ -155,7 +157,10 @@ loop.store.ActiveRoomStore = (function() {
// Social API state.
socialShareProviders: null,
// True if media has been connected both-ways.
mediaConnected: false
mediaConnected: false,
// True if a chat message was sent or received during a session.
// Read more at https://wiki.mozilla.org/Loop/Session.
chatMessageExchanged: false
};
},
@ -236,7 +241,7 @@ loop.store.ActiveRoomStore = (function() {
this._registeredActions = true;
this.dispatcher.register(this, [
var actions = [
"roomFailure",
"retryAfterRoomFailure",
"updateRoomInfo",
@ -263,7 +268,14 @@ loop.store.ActiveRoomStore = (function() {
"updateSocialShareInfo",
"connectionStatus",
"mediaConnected"
]);
];
// Register actions that are only used on Desktop.
if (this._isDesktop) {
// 'receivedTextChatMessage' and 'sendTextChatMessage' actions are only
// registered for Telemetry. Once measured, they're unregistered.
actions.push("receivedTextChatMessage", "sendTextChatMessage");
}
this.dispatcher.register(this, actions);
this._onUpdateListener = this._handleRoomUpdate.bind(this);
this._onDeleteListener = this._handleRoomDelete.bind(this);
@ -611,12 +623,18 @@ loop.store.ActiveRoomStore = (function() {
// previously. We should add better user feedback here.
console.error("Firefox didn't handle room it said it could.");
} else {
this.dispatcher.dispatch(new sharedActions.JoinedRoom({
apiKey: "",
sessionToken: "",
sessionId: "",
expires: 0
}));
if (e.detail.message.alreadyOpen) {
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
}));
} else {
this.dispatcher.dispatch(new sharedActions.JoinedRoom({
apiKey: "",
sessionToken: "",
sessionId: "",
expires: 0
}));
}
}
}
@ -1052,6 +1070,15 @@ loop.store.ActiveRoomStore = (function() {
* will skip the leave message.
*/
_leaveRoom: function(nextState, failedJoinRequest) {
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
// If the user agent is handling the room, all we need to do is advance
// to the next state.
this.setStoreState({
roomState: nextState
});
return;
}
if (loop.standaloneMedia) {
loop.standaloneMedia.multiplexGum.reset();
}
@ -1123,6 +1150,50 @@ loop.store.ActiveRoomStore = (function() {
nextState[storeProp] = this.getStoreState()[storeProp];
nextState[storeProp][actionData.videoType] = actionData.dimensions;
this.setStoreState(nextState);
},
/**
* Handles chat messages received and/ or about to send. If this is the first
* chat message for the current session, register a count with telemetry.
* It will unhook the listeners when the telemetry criteria have been
* fulfilled to make sure we remain lean.
* Note: the 'receivedTextChatMessage' and 'sendTextChatMessage' actions are
* only registered on Desktop.
*
* @param {sharedActions.ReceivedTextChatMessage|SendTextChatMessage} actionData
*/
_handleTextChatMessage: function(actionData) {
if (!this._isDesktop || this.getStoreState().chatMessageExchanged ||
actionData.contentType !== CHAT_CONTENT_TYPES.TEXT) {
return;
}
this.setStoreState({ chatMessageExchanged: true });
// There's no need to listen to these actions anymore.
this.dispatcher.unregister(this, [
"receivedTextChatMessage",
"sendTextChatMessage"
]);
// Ping telemetry of this session with successful message(s) exchange.
this._mozLoop.telemetryAddValue("LOOP_ROOM_SESSION_WITHCHAT", 1);
},
/**
* Handles received text chat messages. For telemetry purposes only.
*
* @param {sharedActions.ReceivedTextChatMessage} actionData
*/
receivedTextChatMessage: function(actionData) {
this._handleTextChatMessage(actionData);
},
/**
* Handles sending of a chat message. For telemetry purposes only.
*
* @param {sharedActions.SendTextChatMessage} actionData
*/
sendTextChatMessage: function(actionData) {
this._handleTextChatMessage(actionData);
}
});

View File

@ -38,6 +38,28 @@ loop.Dispatcher = (function() {
}.bind(this));
},
/**
* Unregister a store from receiving notifications of specific actions.
*
* @param {Object} store The store object to unregister
* @param {Array} eventTypes An array of action names
*/
unregister: function(store, eventTypes) {
eventTypes.forEach(function(type) {
if (!this._eventData.hasOwnProperty(type)) {
return;
}
var idx = this._eventData[type].indexOf(store);
if (idx === -1) {
return;
}
this._eventData[type].splice(idx, 1);
if (!this._eventData[type].length) {
delete this._eventData[type];
}
}.bind(this));
},
/**
* Dispatches an action to all registered stores.
*/

View File

@ -16,11 +16,7 @@ loop.store.TextChatStore = (function() {
SPECIAL: "special"
};
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES = {
CONTEXT: "chat-context",
TEXT: "chat-text",
ROOM_NAME: "room-name"
};
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
/**
* A store to handle text chats. The store has a message list that may

View File

@ -12,7 +12,7 @@ loop.shared.views.chat = (function(mozL10n) {
var sharedMixins = loop.shared.mixins;
var sharedViews = loop.shared.views;
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
/**
* Renders an individual entry for the text chat entries view.

View File

@ -12,7 +12,7 @@ loop.shared.views.chat = (function(mozL10n) {
var sharedMixins = loop.shared.mixins;
var sharedViews = loop.shared.views;
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
/**
* Renders an individual entry for the text chat entries view.

View File

@ -75,6 +75,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
var FAILURE_DETAILS = {
MEDIA_DENIED: "reason-media-denied",
NO_MEDIA: "reason-no-media",
ROOM_ALREADY_OPEN: "reason-room-already-open",
UNABLE_TO_PUBLISH_MEDIA: "unable-to-publish-media",
USER_UNAVAILABLE: "reason-user-unavailable",
COULD_NOT_CONNECT: "reason-could-not-connect",
@ -110,6 +111,12 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
ACTIVE: "ss-active"
};
var CHAT_CONTENT_TYPES = {
CONTEXT: "chat-context",
TEXT: "chat-text",
ROOM_NAME: "room-name"
};
/**
* Format a given date into an l10n-friendly string.
*
@ -775,6 +782,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
this.utils = {
CALL_TYPES: CALL_TYPES,
CHAT_CONTENT_TYPES: CHAT_CONTENT_TYPES,
FAILURE_DETAILS: FAILURE_DETAILS,
REST_ERRNOS: REST_ERRNOS,
WEBSOCKET_REASONS: WEBSOCKET_REASONS,

View File

@ -961,9 +961,10 @@ var LoopRoomsInternal = {
return;
}
let sendResponse = response => {
let sendResponse = (response, alreadyOpen) => {
gLinkClickerChannel.send({
response: response
response: response,
alreadyOpen: alreadyOpen
}, sendingContext);
};
@ -971,16 +972,22 @@ var LoopRoomsInternal = {
switch (message.command) {
case "checkWillOpenRoom":
sendResponse(hasRoom);
sendResponse(hasRoom, false);
break;
case "openRoom":
if (hasRoom) {
this.open(message.roomToken);
if (MozLoopService.isChatWindowOpen(message.roomToken)) {
sendResponse(hasRoom, true);
} else {
this.open(message.roomToken);
sendResponse(hasRoom, false);
}
} else {
sendResponse(hasRoom, false);
}
sendResponse(hasRoom);
break;
default:
sendResponse(false);
sendResponse(false, false);
break;
}
}

View File

@ -203,7 +203,8 @@ var MozLoopServiceInternal = {
pushURLs: new Map(),
mocks: {
pushHandler: undefined
pushHandler: undefined,
isChatWindowOpen: undefined
},
/**
@ -871,6 +872,22 @@ var MozLoopServiceInternal = {
return "about:loopconversation#" + chatWindowId;
},
/**
* Determines if a chat window is already open for a given window id.
*
* @param {String} chatWindowId The window id.
* @return {Boolean} True if the window is opened.
*/
isChatWindowOpen: function(chatWindowId) {
if (this.mocks.isChatWindowOpen !== undefined) {
return this.mocks.isChatWindowOpen;
}
let chatUrl = this.getChatURL(chatWindowId);
return [...Chat.chatboxes].some(chatbox => chatbox.src == chatUrl);
},
/**
* Opens the chat window
*
@ -1377,6 +1394,16 @@ this.MozLoopService = {
return MozLoopServiceInternal.openChatWindow(conversationWindowData);
},
/**
* Determines if a chat window is already open for a given window id.
*
* @param {String} chatWindowId The window id.
* @return {Boolean} True if the window is opened.
*/
isChatWindowOpen: function(chatWindowId) {
return MozLoopServiceInternal.isChatWindowOpen(chatWindowId);
},
/**
* @see MozLoopServiceInternal.promiseRegisteredWithServers
*/

View File

@ -184,6 +184,14 @@ html[dir="rtl"] .rooms-footer .footer-logo {
margin: 2rem auto;
}
.handle-user-agent-view > .info-panel > .failure {
color: red;
font-weight: bold;
/* Add padding to match the height of the button. */
padding: 1.15rem 0;
margin: 0;
}
.handle-user-agent-view > .info-panel > button {
width: 80%;
height: 4rem;

View File

@ -125,12 +125,19 @@ loop.store.StandaloneMetricsStore = (function() {
* @param {sharedActions.ConnectionFailure} actionData
*/
connectionFailure: function(actionData) {
if (actionData.reason === FAILURE_DETAILS.MEDIA_DENIED) {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"Media denied");
} else if (actionData.reason === FAILURE_DETAILS.NO_MEDIA) {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"No media");
switch(actionData.reason) {
case FAILURE_DETAILS.MEDIA_DENIED:
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"Media denied");
break;
case FAILURE_DETAILS.NO_MEDIA:
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"No media");
break;
case FAILURE_DETAILS.ROOM_ALREADY_OPEN:
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"Room already open");
break;
}
},

View File

@ -75,7 +75,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
},
render: function() {
_renderJoinButton: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label");
@ -86,6 +86,22 @@ loop.standaloneRoomViews = (function(mozL10n) {
disabled: this.state.roomState === ROOM_STATES.JOINED
});
return (
React.createElement("button", {
className: buttonClasses,
onClick: this.handleJoinButton},
buttonMessage
)
);
},
_renderFailureText: function() {
return (
React.createElement("p", {className: "failure"}, mozL10n.get("rooms_already_joined") )
);
},
render: function() {
// The extra scroller div here is for providing a scroll view for shorter
// screens, as the common.css specifies overflow:hidden for the body which
// we need in some places.
@ -96,11 +112,11 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("p", {className: "loop-logo-text", title: mozL10n.get("clientShortname2") }),
React.createElement("p", {className: "roomName"}, this.state.roomName),
React.createElement("p", {className: "loop-logo"}),
React.createElement("button", {
className: buttonClasses,
onClick: this.handleJoinButton},
buttonMessage
)
this.state.failureReason ?
this._renderFailureText() :
this._renderJoinButton()
),
React.createElement(ToSView, {
dispatcher: this.props.dispatcher}),

View File

@ -75,7 +75,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
},
render: function() {
_renderJoinButton: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label");
@ -86,6 +86,22 @@ loop.standaloneRoomViews = (function(mozL10n) {
disabled: this.state.roomState === ROOM_STATES.JOINED
});
return (
<button
className={buttonClasses}
onClick={this.handleJoinButton}>
{buttonMessage}
</button>
);
},
_renderFailureText: function() {
return (
<p className="failure">{ mozL10n.get("rooms_already_joined") }</p>
);
},
render: function() {
// The extra scroller div here is for providing a scroll view for shorter
// screens, as the common.css specifies overflow:hidden for the body which
// we need in some places.
@ -96,11 +112,11 @@ loop.standaloneRoomViews = (function(mozL10n) {
<p className="loop-logo-text" title={ mozL10n.get("clientShortname2") }></p>
<p className="roomName">{ this.state.roomName }</p>
<p className="loop-logo" />
<button
className={buttonClasses}
onClick={this.handleJoinButton}>
{buttonMessage}
</button>
{
this.state.failureReason ?
this._renderFailureText() :
this._renderJoinButton()
}
</div>
<ToSView
dispatcher={this.props.dispatcher} />

View File

@ -69,6 +69,7 @@ rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
rooms_room_joined_label=Someone has joined the conversation!
rooms_room_join_label=Join the conversation
rooms_room_joined_own_conversation_label=Enjoy your conversation
rooms_already_joined=You're already in this conversation.
rooms_display_name_guest=Guest
rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid.
rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again.

View File

@ -89,7 +89,7 @@ class Test1BrowserCall(MarionetteTestCase):
self.marionette.set_context("content")
def local_start_a_conversation(self):
button = self.marionette.find_element(By.CSS_SELECTOR, ".new-room-view .btn-info")
button = self.wait_for_element_displayed(By.CSS_SELECTOR, ".new-room-view .btn-info")
self.wait_for_element_enabled(button, 120)

View File

@ -28,7 +28,9 @@ function BackChannel(uri) {
this.channel.listen((id, data) => {
if (this.pendingResolve) {
this.pendingResolve(data);
let resolve = this.pendingResolve;
this.pendingResolve = null;
resolve(data);
return;
}
@ -54,7 +56,9 @@ var gBadBackChannel;
function promiseNewChannelResponse(uri, channel, hash) {
let waitForChannelPromise = new Promise((resolve, reject) => {
if (channel.receivedData) {
resolve(channel.receivedData);
let data = channel.receivedData;
channel.receivedData = null;
resolve(data);
return;
}
@ -113,12 +117,16 @@ add_task(function* test_loopRooms_webchannel_checkWillOpenRoom() {
});
add_task(function* test_loopRooms_webchannel_openRoom() {
let openedUrl;
let openedUrl;
Chat.open = function(contentWindow, origin, title, url) {
openedUrl = url;
};
MozLoopServiceInternal.mocks.isChatWindowOpen = false;
registerCleanupFunction(() => {
Chat.open = openChatOrig;
MozLoopServiceInternal.mocks.isChatWindowOpen = undefined;
});
// Test when the room doesn't exist
@ -128,9 +136,13 @@ add_task(function* test_loopRooms_webchannel_openRoom() {
Assert.ok(!openedUrl, "should not open a chat window");
Assert.equal(got.message.response, false, "should have got a response of false");
Assert.equal(got.message.alreadyOpen, false, "should not indicate that its already open");
// Now add a room & check it.
LoopRooms._setRoomsCache(fakeRoomList);
registerCleanupFunction(() => {
LoopRooms._setRoomsCache();
});
got = yield promiseNewChannelResponse(TEST_URI_GOOD, gGoodBackChannel, "openRoom");
@ -144,4 +156,13 @@ add_task(function* test_loopRooms_webchannel_openRoom() {
Assert.equal(windowData.roomToken, ROOM_TOKEN, "window data should have the roomToken");
Assert.equal(got.message.response, true, "should have got a response of true");
Assert.equal(got.message.alreadyOpen, false, "should not indicate that its already open");
// Simulate a window already being open.
MozLoopServiceInternal.mocks.isChatWindowOpen = true;
got = yield promiseNewChannelResponse(TEST_URI_GOOD, gGoodBackChannel, "openRoom");
Assert.equal(got.message.response, true, "should have got a response of true");
Assert.equal(got.message.alreadyOpen, true, "should indicate the room is already open");
});

View File

@ -172,7 +172,21 @@ add_task(function* test_mozLoop_telemetryAdd_roomContextClick() {
let snapshot;
for (let i = 1; i < 4; ++i) {
gMozLoopAPI.telemetryAddValue("LOOP_ROOM_CONTEXT_CLICK", 1);
gMozLoopAPI.telemetryAddValue(histogramId, 1);
snapshot = histogram.snapshot();
Assert.strictEqual(snapshot.counts[0], i);
}
});
add_task(function* test_mozLoop_telemetryAdd_roomSessionWithChat() {
let histogramId = "LOOP_ROOM_SESSION_WITHCHAT";
let histogram = Services.telemetry.getHistogramById(histogramId);
histogram.clear();
let snapshot;
for (let i = 1; i < 4; ++i) {
gMozLoopAPI.telemetryAddValue(histogramId, 1);
snapshot = histogram.snapshot();
Assert.strictEqual(snapshot.counts[0], i);
}

View File

@ -9,6 +9,7 @@ describe("loop.store.ActiveRoomStore", function () {
var sharedUtils = loop.shared.utils;
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
var ROOM_STATES = loop.store.ROOM_STATES;
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
@ -39,7 +40,8 @@ describe("loop.store.ActiveRoomStore", function () {
},
setScreenShareState: sinon.stub(),
getActiveTabWindowId: sandbox.stub().callsArgWith(0, null, 42),
getSocialShareProviders: sinon.stub().returns([])
getSocialShareProviders: sinon.stub().returns([]),
telemetryAddValue: sinon.stub()
};
fakeSdkDriver = {
@ -871,7 +873,7 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("Firefox Handles Room", function() {
describe("User Agent Handles Room", function() {
var channelListener;
beforeEach(function() {
@ -949,7 +951,8 @@ describe("loop.store.ActiveRoomStore", function () {
detail: {
id: "loop-link-clicker",
message: {
response: true
response: true,
alreadyOpen: false
}
}
});
@ -963,6 +966,28 @@ describe("loop.store.ActiveRoomStore", function () {
expires: 0
}));
});
it("should dispatch a ConnectionFailure action if the room was already opened", function() {
// Start the join.
store.joinRoom();
// Pretend Firefox calls back.
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: true,
alreadyOpen: true
}
}
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
}));
});
});
});
@ -1255,6 +1280,29 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
});
it("should set the state to `FAILED` if the user agent is handling the room", function() {
store.setStoreState({
standalone: true,
userAgentHandlesRoom: true
});
store.connectionFailure(connectionFailureAction);
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
});
it("should not do any other cleanup if the user agent is handling the room", function() {
store.setStoreState({
standalone: true,
userAgentHandlesRoom: true
});
store.connectionFailure(connectionFailureAction);
sinon.assert.notCalled(fakeMultiplexGum.reset);
sinon.assert.notCalled(fakeSdkDriver.disconnectSession);
});
});
describe("#setMute", function() {
@ -1842,7 +1890,8 @@ describe("loop.store.ActiveRoomStore", function () {
receivingScreenShare: true,
remoteVideoDimensions: { y: 10 },
screenSharingState: true,
videoMuted: true
videoMuted: true,
chatMessageExchanged: false
});
store.leaveRoom();
@ -1853,6 +1902,7 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store._storeState.remoteVideoDimensions).eql({});
expect(store._storeState.screenSharingState).eql(SCREEN_SHARE_STATES.INACTIVE);
expect(store._storeState.videoMuted).eql(false);
expect(store._storeState.chatMessageExchanged).eql(false);
});
it("should not reset the room context", function() {
@ -1886,6 +1936,73 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("#_handleTextChatMessage", function() {
beforeEach(function() {
store._isDesktop = true;
store.setupWindowData(new sharedActions.SetupWindowData({
windowId: "42",
type: "room",
roomToken: "fakeToken"
}));
});
function assertWeDidNothing() {
expect(dispatcher._eventData.receivedTextChatMessage.length).eql(1);
expect(dispatcher._eventData.sendTextChatMessage.length).eql(1);
expect(store.getStoreState().chatMessageExchanged).eql(false);
sinon.assert.notCalled(fakeMozLoop.telemetryAddValue);
}
it("should not do anything for the link clicker side", function() {
store._isDesktop = false;
store._handleTextChatMessage(new sharedActions.SendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
sentTimestamp: "1970-01-01T00:00:00.000Z"
}));
assertWeDidNothing();
});
it("should not do anything when a chat message has arrived before", function() {
store.setStoreState({ chatMessageExchanged: true });
store._handleTextChatMessage(new sharedActions.ReceivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
receivedTimestamp: "1970-01-01T00:00:00.000Z"
}));
sinon.assert.notCalled(fakeMozLoop.telemetryAddValue);
});
it("should not do anything for non-chat messages", function() {
store._handleTextChatMessage(new sharedActions.SendTextChatMessage({
contentType: CHAT_CONTENT_TYPES.CONTEXT,
message: "Hello!",
sentTimestamp: "1970-01-01T00:00:00.000Z"
}));
assertWeDidNothing();
});
it("should ping telemetry when a chat message arrived or is to be sent", function() {
store._handleTextChatMessage(new sharedActions.ReceivedTextChatMessage({
contentType: CHAT_CONTENT_TYPES.TEXT,
message: "Hello!",
receivedTimestamp: "1970-01-01T00:00:00.000Z"
}));
sinon.assert.calledOnce(fakeMozLoop.telemetryAddValue);
sinon.assert.calledWithExactly(fakeMozLoop.telemetryAddValue,
"LOOP_ROOM_SESSION_WITHCHAT", 1);
expect(store.getStoreState().chatMessageExchanged).eql(true);
expect(dispatcher._eventData.hasOwnProperty("receivedTextChatMessage")).eql(false);
expect(dispatcher._eventData.hasOwnProperty("sendTextChatMessage")).eql(false);
});
});
describe("Events", function() {
describe("update:{roomToken}", function() {
beforeEach(function() {

View File

@ -23,6 +23,8 @@ describe("loop.Dispatcher", function () {
dispatcher.register(object, ["getWindowData"]);
// XXXmikedeboer: Consider changing these tests to not access private
// properties anymore (`_eventData`).
expect(dispatcher._eventData.getWindowData[0]).eql(object);
});
@ -38,6 +40,31 @@ describe("loop.Dispatcher", function () {
});
});
describe("#unregister", function() {
it("should unregister a store against an action name", function() {
var object = { fake: true };
dispatcher.register(object, ["getWindowData"]);
dispatcher.unregister(object, ["getWindowData"]);
expect(dispatcher._eventData.hasOwnProperty("getWindowData")).eql(false);
});
it("should unregister multiple stores against an action name", function() {
var object1 = { fake: true };
var object2 = { fake2: true };
dispatcher.register(object1, ["getWindowData"]);
dispatcher.register(object2, ["getWindowData"]);
dispatcher.unregister(object1, ["getWindowData"]);
expect(dispatcher._eventData.getWindowData.length).eql(1);
dispatcher.unregister(object2, ["getWindowData"]);
expect(dispatcher._eventData.hasOwnProperty("getWindowData")).eql(false);
});
});
describe("#dispatch", function() {
var getDataStore1, getDataStore2, cancelStore1, connectStore1;
var getDataAction, cancelAction, connectAction, resolveCancelStore1;

View File

@ -9,7 +9,7 @@ describe("loop.OTSdkDriver", function () {
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
var STREAM_PROPERTIES = loop.shared.utils.STREAM_PROPERTIES;
var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
var sandbox;
var dispatcher, driver, mozLoop, publisher, sdk, session, sessionData, subscriber;

View File

@ -7,7 +7,7 @@ describe("loop.store.TextChatStore", function () {
var expect = chai.expect;
var sharedActions = loop.shared.actions;
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
var dispatcher, fakeSdkDriver, sandbox, store;

View File

@ -9,7 +9,7 @@ describe("loop.shared.views.TextChatView", function () {
var sharedViews = loop.shared.views;
var TestUtils = React.addons.TestUtils;
var CHAT_MESSAGE_TYPES = loop.store.CHAT_MESSAGE_TYPES;
var CHAT_CONTENT_TYPES = loop.store.CHAT_CONTENT_TYPES;
var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
var fixtures = document.querySelector("#fixtures");
var dispatcher, fakeSdkDriver, sandbox, store, fakeClock;

View File

@ -77,6 +77,17 @@ describe("loop.store.StandaloneMetricsStore", function() {
"No media");
});
it("should log an event on connection failure if the room was already open", function() {
store.connectionFailure(new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
}));
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"Room already open");
});
it("should log an event on GotMediaPermission", function() {
store.gotMediaPermission();

View File

@ -183,6 +183,28 @@ describe("loop.standaloneRoomViews", function() {
expect(button.classList.contains("disabled")).eql(true);
});
it("should not display a join button if there is a failure reason", function() {
activeRoomStore.setStoreState({
failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button).eql(null);
});
it("should display a room already joined message if opening failed", function() {
activeRoomStore.setStoreState({
failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
});
view = mountTestComponent();
var text = view.getDOMNode().querySelector(".failure");
expect(text.textContent).eql("rooms_already_joined");
});
});
describe("StandaloneRoomHeader", function() {

View File

@ -98,7 +98,7 @@
sendTextChatMessage: function(actionData) {
dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: actionData.message,
receivedTimestamp: actionData.sentTimestamp
}));
@ -409,40 +409,40 @@
textChatStore.setStoreState({textChatEnabled: true});
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Rheet!",
sentTimestamp: "2015-06-23T22:21:45.590Z"
}));
dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Hello",
receivedTimestamp: "2015-06-23T23:24:45.590Z"
}));
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Nowforareallylongwordwithoutspacesorpunctuationwhichshouldcause" +
"linewrappingissuesifthecssiswrong",
sentTimestamp: "2015-06-23T22:23:45.590Z"
}));
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Check out this menu from DNA Pizza:" +
" http://example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/" +
"%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%",
sentTimestamp: "2015-06-23T22:23:45.590Z"
}));
dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "That avocado monkey-brains pie sounds tasty!",
receivedTimestamp: "2015-06-23T22:25:45.590Z"
}));
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "What time should we meet?",
sentTimestamp: "2015-06-23T22:27:45.590Z"
}));
dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "8:00 PM",
receivedTimestamp: "2015-06-23T22:27:45.590Z"
}));

View File

@ -98,7 +98,7 @@
sendTextChatMessage: function(actionData) {
dispatcher.dispatch(new loop.shared.actions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: actionData.message,
receivedTimestamp: actionData.sentTimestamp
}));
@ -409,40 +409,40 @@
textChatStore.setStoreState({textChatEnabled: true});
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Rheet!",
sentTimestamp: "2015-06-23T22:21:45.590Z"
}));
dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Hello",
receivedTimestamp: "2015-06-23T23:24:45.590Z"
}));
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Nowforareallylongwordwithoutspacesorpunctuationwhichshouldcause" +
"linewrappingissuesifthecssiswrong",
sentTimestamp: "2015-06-23T22:23:45.590Z"
}));
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "Check out this menu from DNA Pizza:" +
" http://example.com/DNA/pizza/menu/lots-of-different-kinds-of-pizza/" +
"%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%8D%E0%B8%88%E0%B8%A1%E0%B8%A3%E0%",
sentTimestamp: "2015-06-23T22:23:45.590Z"
}));
dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "That avocado monkey-brains pie sounds tasty!",
receivedTimestamp: "2015-06-23T22:25:45.590Z"
}));
dispatcher.dispatch(new sharedActions.SendTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "What time should we meet?",
sentTimestamp: "2015-06-23T22:27:45.590Z"
}));
dispatcher.dispatch(new sharedActions.ReceivedTextChatMessage({
contentType: loop.store.CHAT_CONTENT_TYPES.TEXT,
contentType: loop.shared.utils.CHAT_CONTENT_TYPES.TEXT,
message: "8:00 PM",
receivedTimestamp: "2015-06-23T22:27:45.590Z"
}));

View File

@ -7,16 +7,21 @@ support-files =
[browser_advanced_update.js]
[browser_basic_rebuild_fonts_test.js]
[browser_bug410900.js]
[browser_bug705422.js]
[browser_bug731866.js]
[browser_bug795764_cachedisabled.js]
[browser_bug1018066_resetScrollPosition.js]
[browser_bug1020245_openPreferences_to_paneContent.js]
[browser_change_app_handler.js]
skip-if = os != "win" # This test tests the windows-specific app selection dialog, so can't run on non-Windows
[browser_chunk_permissions.js]
[browser_connection.js]
[browser_connection_bug388287.js]
[browser_cookies_exceptions.js]
skip-if = os == "linux" # See bug 1209521 for re-enabling on Linux
[browser_healthreport.js]
skip-if = !healthreport || (os == 'linux' && debug)
[browser_permissions.js]
[browser_proxy_backup.js]
[browser_privacypane_1.js]
[browser_privacypane_3.js]

View File

@ -0,0 +1,144 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
waitForExplicitFinish();
// Allow all cookies, then actually set up the test
SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, initTest);
}
function initTest() {
const searchTerm = "example";
const dummyTerm = "elpmaxe";
var cm = Components.classes["@mozilla.org/cookiemanager;1"]
.getService(Components.interfaces.nsICookieManager);
// delete all cookies (might be left over from other tests)
cm.removeAll();
// data for cookies
var vals = [[searchTerm+".com", dummyTerm, dummyTerm], // match
[searchTerm+".org", dummyTerm, dummyTerm], // match
[dummyTerm+".com", searchTerm, dummyTerm], // match
[dummyTerm+".edu", searchTerm+dummyTerm, dummyTerm],// match
[dummyTerm+".net", dummyTerm, searchTerm], // match
[dummyTerm+".org", dummyTerm, searchTerm+dummyTerm],// match
[dummyTerm+".int", dummyTerm, dummyTerm]]; // no match
// matches must correspond to above data
const matches = 6;
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var cookieSvc = Components.classes["@mozilla.org/cookieService;1"]
.getService(Components.interfaces.nsICookieService);
var v;
// inject cookies
for (v in vals) {
let [host, name, value] = vals[v];
var cookieUri = ios.newURI("http://"+host, null, null);
cookieSvc.setCookieString(cookieUri, null, name+"="+value+";", null);
}
// open cookie manager
var cmd = window.openDialog("chrome://browser/content/preferences/cookies.xul",
"Browser:Cookies", "", {});
// when it has loaded, run actual tests
cmd.addEventListener("load", function() {executeSoon(function() {runTest(cmd, searchTerm, vals.length, matches);});}, false);
}
function isDisabled(win, expectation) {
var disabled = win.document.getElementById("removeAllCookies").disabled;
is(disabled, expectation, "Remove all cookies button has correct state: "+(expectation?"disabled":"enabled"));
}
function runTest(win, searchTerm, cookies, matches) {
var cm = Components.classes["@mozilla.org/cookiemanager;1"]
.getService(Components.interfaces.nsICookieManager);
// number of cookies should match injected cookies
var cnt = 0,
enumerator = cm.enumerator;
while (enumerator.hasMoreElements()) {
cnt++;
enumerator.getNext();
}
is(cnt, cookies, "Number of cookies match injected cookies");
// "delete all cookies" should be enabled
isDisabled(win, false);
// filter cookies and count matches
win.gCookiesWindow.setFilter(searchTerm);
is(win.gCookiesWindow._view.rowCount, matches, "Correct number of cookies shown after filter is applied");
// "delete all cookies" should be enabled
isDisabled(win, false);
// select first cookie and delete
var tree = win.document.getElementById("cookiesList");
var deleteButton = win.document.getElementById("removeSelectedCookies");
var rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "cell");
EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {}, win);
EventUtils.synthesizeMouseAtCenter(deleteButton, {}, win);
// count cookies should be matches-1
is(win.gCookiesWindow._view.rowCount, matches-1, "Deleted selected cookie");
// select two adjacent cells and delete
EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {}, win);
var eventObj = {};
if (navigator.platform.indexOf("Mac") >= 0)
eventObj.metaKey = true;
else
eventObj.ctrlKey = true;
rect = tree.treeBoxObject.getCoordsForCellItem(1, tree.columns[0], "cell");
EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, eventObj, win);
EventUtils.synthesizeMouseAtCenter(deleteButton, {}, win);
// count cookies should be matches-3
is(win.gCookiesWindow._view.rowCount, matches-3, "Deleted selected two adjacent cookies");
// "delete all cookies" should be enabled
isDisabled(win, false);
// delete all cookies and count
var deleteAllButton = win.document.getElementById("removeAllCookies");
EventUtils.synthesizeMouseAtCenter(deleteAllButton, {}, win);
is(win.gCookiesWindow._view.rowCount, 0, "Deleted all matching cookies");
// "delete all cookies" should be disabled
isDisabled(win, true);
// clear filter and count should be cookies-matches
win.gCookiesWindow.setFilter("");
is(win.gCookiesWindow._view.rowCount, cookies-matches, "Unmatched cookies remain");
// "delete all cookies" should be enabled
isDisabled(win, false);
// delete all cookies and count should be 0
EventUtils.synthesizeMouseAtCenter(deleteAllButton, {}, win);
is(win.gCookiesWindow._view.rowCount, 0, "Deleted all cookies");
// check that datastore is also at 0
var cnt = 0,
enumerator = cm.enumerator;
while (enumerator.hasMoreElements()) {
cnt++;
enumerator.getNext();
}
is(cnt, 0, "Zero cookies remain");
// "delete all cookies" should be disabled
isDisabled(win, true);
// clean up
win.close();
finish();
}

View File

@ -0,0 +1,140 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
const ABOUT_PERMISSIONS_SPEC = "about:permissions";
const TEST_URI_1 = NetUtil.newURI("http://mozilla.com/");
const TEST_URI_2 = NetUtil.newURI("http://mozilla.org/");
const TEST_URI_3 = NetUtil.newURI("http://wikipedia.org/");
// values from DefaultPermissions object
const PERM_UNKNOWN = 0;
const PERM_ALLOW = 1;
const PERM_DENY = 2;
// used to set permissions on test sites
const TEST_PERMS = {
"password": PERM_ALLOW,
"cookie": PERM_ALLOW,
"geo": PERM_UNKNOWN,
"indexedDB": PERM_UNKNOWN,
"popup": PERM_DENY
};
function test() {
waitForExplicitFinish();
registerCleanupFunction(cleanUp);
setup(function() {
runNextTest();
});
}
function setup(aCallback) {
// add test history visit
PlacesTestUtils.addVisits(TEST_URI_1).then(() => {
// set permissions ourselves to avoid problems with different defaults
// from test harness configuration
for (let type in TEST_PERMS) {
if (type == "password") {
Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true);
} else {
// set permissions on a site without history visits to test enumerateServices
Services.perms.add(TEST_URI_2, type, TEST_PERMS[type]);
}
}
Services.perms.add(TEST_URI_3, "popup", TEST_PERMS["popup"]);
aCallback();
});
}
function cleanUp() {
for (let type in TEST_PERMS) {
if (type != "password") {
Services.perms.remove(TEST_URI_1, type);
Services.perms.remove(TEST_URI_2, type);
Services.perms.remove(TEST_URI_3, type);
}
}
}
function runNextTest() {
if (gTestIndex == tests.length) {
PlacesTestUtils.clearHistory().then(finish);
return;
}
let nextTest = tests[gTestIndex++];
info(nextTest.desc);
function preinit_observer() {
Services.obs.removeObserver(preinit_observer, "browser-permissions-preinit");
nextTest.preInit();
}
Services.obs.addObserver(preinit_observer, "browser-permissions-preinit", false);
function init_observer() {
Services.obs.removeObserver(init_observer, "browser-permissions-initialized");
nextTest.run();
}
Services.obs.addObserver(init_observer, "browser-permissions-initialized", false);
// open about:permissions
let tab = gBrowser.selectedTab = gBrowser.addTab("about:permissions");
registerCleanupFunction(function() {
gBrowser.removeTab(tab);
});
}
var gSitesList;
var gTestIndex = 0;
var tests = [
// 'preInit' occurs after opening about:permissions, before sites-list is populated
// 'run' occurs after sites-list is populated
{
desc: "test filtering before sites-list is fully constructed.",
preInit: function() {
let sitesFilter = gBrowser.contentDocument.getElementById("sites-filter");
sitesFilter.value = TEST_URI_2.host;
sitesFilter.doCommand();
},
run: function() {
let testSite1 = getSiteItem(TEST_URI_1.prePath);
ok(testSite1.collapsed, "test site 1 is collapsed after early filtering");
let testSite2 = getSiteItem(TEST_URI_2.prePath);
ok(!testSite2.collapsed, "test site 2 is not collapsed after early filtering");
let testSite3 = getSiteItem(TEST_URI_3.prePath);
ok(testSite3.collapsed, "test site 3 is collapsed after early filtering");
runNextTest();
}
},
{
desc: "test removing from sites-list before it is fully constructed.",
preInit: function() {
ForgetAboutSite.removeDataFromDomain(TEST_URI_2.host);
},
run: function() {
let testSite1 = getSiteItem(TEST_URI_1.prePath);
ok(testSite1, "test site 1 was not removed from sites list");
let testSite2 = getSiteItem(TEST_URI_2.prePath);
ok(!testSite2, "test site 2 was pre-removed from sites list");
let testSite3 = getSiteItem(TEST_URI_3.prePath);
ok(testSite3, "test site 3 was not removed from sites list");
runNextTest();
}
}
];
function getSiteItem(aPrePath) {
return gBrowser.contentDocument.
querySelector(".site[value='" + aPrePath + "']");
}

View File

@ -0,0 +1,304 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
waitForExplicitFinish();
testRunner.runTests();
}
var testRunner = {
tests:
[
{
test: function(params) {
params.url.value = "test.com";
params.btnAllow.doCommand();
is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission text should be set correctly");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://test.com", data: "added",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "test.com";
params.btnBlock.doCommand();
is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
"permission should change to deny in UI");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://test.com", data: "changed",
capability: Ci.nsIPermissionManager.DENY_ACTION }],
},
{
test: function(params) {
params.url.value = "test.com";
params.btnAllow.doCommand();
is(params.tree.view.getCellText(0, params.nameCol), "http://test.com",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission should revert back to allow");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://test.com", data: "changed",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "test.com";
params.btnRemove.doCommand();
is(params.tree.view.rowCount, 0, "exception should be removed");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://test.com", data: "deleted" }],
},
{
expectPermObservancesDuringTestFunction: true,
test: function(params) {
let uri = params.ioService.newURI("http://test.com", null, null);
params.pm.add(uri, "popup", Ci.nsIPermissionManager.DENY_ACTION);
is(params.tree.view.rowCount, 0, "adding unrelated permission should not change display");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "popup", origin: "http://test.com", data: "added",
capability: Ci.nsIPermissionManager.DENY_ACTION }],
cleanUp: function(params) {
let uri = params.ioService.newURI("http://test.com", null, null);
params.pm.remove(uri, "popup");
},
},
{
test: function(params) {
params.url.value = "https://test.com:12345";
params.btnAllow.doCommand();
is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission text should be set correctly");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "https://test.com:12345", data: "added",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "https://test.com:12345";
params.btnBlock.doCommand();
is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
"permission should change to deny in UI");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "https://test.com:12345", data: "changed",
capability: Ci.nsIPermissionManager.DENY_ACTION }],
},
{
test: function(params) {
params.url.value = "https://test.com:12345";
params.btnAllow.doCommand();
is(params.tree.view.getCellText(0, params.nameCol), "https://test.com:12345",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission should revert back to allow");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "https://test.com:12345", data: "changed",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "https://test.com:12345";
params.btnRemove.doCommand();
is(params.tree.view.rowCount, 0, "exception should be removed");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "https://test.com:12345", data: "deleted" }],
},
{
test: function(params) {
params.url.value = "localhost:12345";
params.btnAllow.doCommand();
is(params.tree.view.rowCount, 1, "added exception shows up in treeview");
is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission text should be set correctly");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://localhost:12345", data: "added",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "localhost:12345";
params.btnBlock.doCommand();
is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.denyText,
"permission should change to deny in UI");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://localhost:12345", data: "changed",
capability: Ci.nsIPermissionManager.DENY_ACTION }],
},
{
test: function(params) {
params.url.value = "localhost:12345";
params.btnAllow.doCommand();
is(params.tree.view.getCellText(0, params.nameCol), "http://localhost:12345",
"origin name should be set correctly");
is(params.tree.view.getCellText(0, params.statusCol), params.allowText,
"permission should revert back to allow");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://localhost:12345", data: "changed",
capability: Ci.nsIPermissionManager.ALLOW_ACTION }],
},
{
test: function(params) {
params.url.value = "localhost:12345";
params.btnRemove.doCommand();
is(params.tree.view.rowCount, 0, "exception should be removed");
params.btnApplyChanges.doCommand();
},
observances: [{ type: "cookie", origin: "http://localhost:12345", data: "deleted" }],
},
],
_currentTest: -1,
runTests: function() {
this._currentTest++;
info("Running test #" + (this._currentTest + 1) + "\n");
let that = this;
let p = this.runCurrentTest(this._currentTest + 1);
p.then(function() {
if (that._currentTest == that.tests.length - 1) {
finish();
}
else {
that.runTests();
}
});
},
runCurrentTest: function(testNumber) {
return new Promise(function(resolve, reject) {
let helperFunctions = {
windowLoad: function(win) {
let doc = win.document;
let params = {
doc,
tree: doc.getElementById("permissionsTree"),
nameCol: doc.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(0),
statusCol: doc.getElementById("permissionsTree").treeBoxObject.columns.getColumnAt(1),
url: doc.getElementById("url"),
btnAllow: doc.getElementById("btnAllow"),
btnBlock: doc.getElementById("btnBlock"),
btnApplyChanges: doc.getElementById("btnApplyChanges"),
btnRemove: doc.getElementById("removePermission"),
pm: Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager),
ioService: Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService),
allowText: win.gPermissionManager._getCapabilityString(
Ci.nsIPermissionManager.ALLOW_ACTION),
denyText: win.gPermissionManager._getCapabilityString(
Ci.nsIPermissionManager.DENY_ACTION),
allow: Ci.nsIPermissionManager.ALLOW_ACTION,
deny: Ci.nsIPermissionManager.DENY_ACTION,
};
let permObserver = {
observe: function(aSubject, aTopic, aData) {
if (aTopic != "perm-changed")
return;
if (testRunner.tests[testRunner._currentTest].observances.length == 0) {
// Should fail here as we are not expecting a notification, but we don't.
// See bug 1063410.
return;
}
let permission = aSubject.QueryInterface(Ci.nsIPermission);
let expected = testRunner.tests[testRunner._currentTest].observances.shift();
is(aData, expected.data, "type of message should be the same");
for each (let prop in ["type", "capability"]) {
if (expected[prop])
is(permission[prop], expected[prop],
"property: \"" + prop + "\" should be equal");
}
if (expected.origin) {
is(permission.principal.origin, expected.origin,
"property: \"origin\" should be equal");
}
os.removeObserver(permObserver, "perm-changed");
let test = testRunner.tests[testRunner._currentTest];
if (!test.expectPermObservancesDuringTestFunction) {
if (test.cleanUp) {
test.cleanUp(params);
}
gBrowser.removeCurrentTab();
resolve();
}
},
};
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.addObserver(permObserver, "perm-changed", false);
if (testRunner._currentTest == 0) {
is(params.tree.view.rowCount, 0, "no cookie exceptions");
}
try {
let test = testRunner.tests[testRunner._currentTest];
test.test(params);
if (test.expectPermObservancesDuringTestFunction) {
if (test.cleanUp) {
test.cleanUp(params);
}
gBrowser.removeCurrentTab();
resolve();
}
} catch (ex) {
ok(false, "exception while running test #" +
testNumber + ": " + ex);
}
},
};
openPreferencesViaOpenPreferencesAPI("panePrivacy", null, {leaveOpen: true}).then(function() {
let doc = gBrowser.contentDocument;
let historyMode = doc.getElementById("historyMode");
historyMode.value = "custom";
historyMode.doCommand();
doc.getElementById("cookieExceptions").doCommand();
let subDialogURL = "chrome://browser/content/preferences/permissions.xul";
promiseLoadSubDialog(subDialogURL).then(function(win) {
helperFunctions.windowLoad(win);
});
});
});
},
};

View File

@ -0,0 +1,334 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
const ABOUT_PERMISSIONS_SPEC = "about:permissions";
const TEST_URI_1 = NetUtil.newURI("http://mozilla.com/");
const TEST_URI_2 = NetUtil.newURI("http://mozilla.org/");
const TEST_PRINCIPAL_1 =
Services.scriptSecurityManager.createCodebasePrincipal(TEST_URI_1, {});
const TEST_PRINCIPAL_2 =
Services.scriptSecurityManager.createCodebasePrincipal(TEST_URI_2, {});
// values from DefaultPermissions object
const PERM_UNKNOWN = 0;
const PERM_ALLOW = 1;
const PERM_DENY = 2;
// cookie specific permissions
const PERM_FIRST_PARTY_ONLY = 9;
// used to set permissions on test sites
const TEST_PERMS = {
"password": PERM_ALLOW,
"cookie": PERM_ALLOW,
"geo": PERM_UNKNOWN,
"push": PERM_DENY,
"indexedDB": PERM_UNKNOWN,
"popup": PERM_DENY,
"camera": PERM_UNKNOWN,
"microphone": PERM_UNKNOWN
};
const NO_GLOBAL_ALLOW = [
"geo",
"indexedDB",
];
// number of managed permissions in the interface
const TEST_PERMS_COUNT = 8;
function test() {
waitForExplicitFinish();
registerCleanupFunction(cleanUp);
// add test history visit
PlacesTestUtils.addVisits(TEST_URI_1).then(() => {
// set permissions ourselves to avoid problems with different defaults
// from test harness configuration
for (let type in TEST_PERMS) {
if (type == "password") {
Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true);
} else {
// set permissions on a site without history visits to test enumerateServices
Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, type, TEST_PERMS[type]);
}
}
// open about:permissions
gBrowser.selectedTab = gBrowser.addTab("about:permissions");
});
function observer() {
Services.obs.removeObserver(observer, "browser-permissions-initialized");
runNextTest();
}
Services.obs.addObserver(observer, "browser-permissions-initialized", false);
}
function cleanUp() {
for (let type in TEST_PERMS) {
if (type != "password") {
Services.perms.removeFromPrincipal(TEST_PRINCIPAL_1, type);
Services.perms.removeFromPrincipal(TEST_PRINCIPAL_2, type);
}
}
gBrowser.removeTab(gBrowser.selectedTab);
}
function runNextTest() {
if (gTestIndex == tests.length) {
PlacesTestUtils.clearHistory().then(finish);
return;
}
let nextTest = tests[gTestIndex++];
info("[" + nextTest.name + "] running test");
nextTest();
}
var gSitesList;
var gHeaderDeck;
var gSiteLabel;
var gTestIndex = 0;
var tests = [
function test_page_load() {
is(gBrowser.currentURI.spec, ABOUT_PERMISSIONS_SPEC, "about:permissions loaded");
gSitesList = gBrowser.contentDocument.getElementById("sites-list");
ok(gSitesList, "got sites list");
gHeaderDeck = gBrowser.contentDocument.getElementById("header-deck");
ok(gHeaderDeck, "got header deck");
gSiteLabel = gBrowser.contentDocument.getElementById("site-label");
ok(gSiteLabel, "got site label");
runNextTest();
},
function test_sites_list() {
is(gSitesList.firstChild.id, "all-sites-item",
"all sites is the first item in the sites list");
ok(getSiteItem(TEST_URI_1.prePath), "site item from places db exists");
ok(getSiteItem(TEST_URI_2.prePath), "site item from enumerating services exists");
runNextTest();
},
function test_filter_sites_list() {
// set filter to test host
let sitesFilter = gBrowser.contentDocument.getElementById("sites-filter");
sitesFilter.value = TEST_URI_1.host;
sitesFilter.doCommand();
// make sure correct sites are collapsed/showing
let testSite1 = getSiteItem(TEST_URI_1.prePath);
ok(!testSite1.collapsed, "test site 1 is not collapsed");
let testSite2 = getSiteItem(TEST_URI_2.prePath);
ok(testSite2.collapsed, "test site 2 is collapsed");
// clear filter
sitesFilter.value = "";
sitesFilter.doCommand();
runNextTest();
},
function test_all_sites() {
// "All Sites" item should be selected when the page is first loaded
is(gSitesList.selectedItem, gBrowser.contentDocument.getElementById("all-sites-item"),
"all sites item is selected");
let defaultsHeader = gBrowser.contentDocument.getElementById("defaults-header");
is(defaultsHeader, gHeaderDeck.selectedPanel,
"correct header shown for all sites");
ok(gBrowser.contentDocument.getElementById("passwords-count").hidden,
"passwords count is hidden");
ok(gBrowser.contentDocument.getElementById("cookies-count").hidden,
"cookies count is hidden");
// Test to make sure "Allow" items hidden for certain permission types
NO_GLOBAL_ALLOW.forEach(function(aType) {
let menuitem = gBrowser.contentDocument.getElementById(aType + "-" + PERM_ALLOW);
ok(menuitem.hidden, aType + " allow menuitem hidden for all sites");
});
runNextTest();
},
function test_all_sites_permission() {
// apply the old default of allowing all cookies
Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
// there should be no user-set pref for cookie behavior
is(Services.prefs.getIntPref("network.cookie.cookieBehavior"), PERM_UNKNOWN,
"network.cookie.cookieBehavior is expected default");
// the default behavior is to allow cookies
let cookieMenulist = getPermissionMenulist("cookie");
is(cookieMenulist.value, PERM_ALLOW,
"menulist correctly shows that cookies are allowed");
// set the pref to block cookies
Services.prefs.setIntPref("network.cookie.cookieBehavior", PERM_DENY);
// check to make sure this change is reflected in the UI
is(cookieMenulist.value, PERM_DENY, "menulist correctly shows that cookies are blocked");
// clear the pref
Services.prefs.clearUserPref("network.cookie.cookieBehavior");
runNextTest();
},
function test_manage_all_passwords() {
// make sure "Manage All Passwords..." button opens the correct dialog
addWindowListener("chrome://passwordmgr/content/passwordManager.xul", runNextTest);
gBrowser.contentDocument.getElementById("passwords-manage-all-button").doCommand();
},
function test_manage_all_cookies() {
// make sure "Manage All Cookies..." button opens the correct dialog
addWindowListener("chrome://browser/content/preferences/cookies.xul", runNextTest);
gBrowser.contentDocument.getElementById("cookies-manage-all-button").doCommand();
},
function test_select_site() {
// select the site that has the permissions we set at the beginning of the test
let testSiteItem = getSiteItem(TEST_URI_2.prePath);
gSitesList.selectedItem = testSiteItem;
let siteHeader = gBrowser.contentDocument.getElementById("site-header");
is(siteHeader, gHeaderDeck.selectedPanel,
"correct header shown for a specific site");
is(gSiteLabel.value, TEST_URI_2.prePath, "header updated for selected site");
ok(!gBrowser.contentDocument.getElementById("passwords-count").hidden,
"passwords count is not hidden");
ok(!gBrowser.contentDocument.getElementById("cookies-count").hidden,
"cookies count is not hidden");
// Test to make sure "Allow" items are *not* hidden for certain permission types
NO_GLOBAL_ALLOW.forEach(function(aType) {
let menuitem = gBrowser.contentDocument.getElementById(aType + "-" + PERM_ALLOW);
ok(!menuitem.hidden, aType + " allow menuitem not hidden for single site");
});
runNextTest();
},
function test_permissions() {
let menulists = gBrowser.contentDocument.getElementsByClassName("pref-menulist");
is(menulists.length, TEST_PERMS_COUNT, "got expected number of managed permissions");
for (let i = 0; i < menulists.length; i++) {
let permissionMenulist = menulists.item(i);
let permissionType = permissionMenulist.getAttribute("type");
// permissions should reflect what we set at the beginning of the test
is(permissionMenulist.value, TEST_PERMS[permissionType],
"got expected value for " + permissionType + " permission");
}
runNextTest();
},
function test_permission_change() {
let geoMenulist = getPermissionMenulist("geo");
is(geoMenulist.value, PERM_UNKNOWN, "menulist correctly shows that geolocation permission is unspecified");
// change a permission programatically
Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, "geo", PERM_DENY);
// check to make sure this change is reflected in the UI
is(geoMenulist.value, PERM_DENY, "menulist shows that geolocation is blocked");
// change a permisssion in the UI
let geoAllowItem = gBrowser.contentDocument.getElementById("geo-" + PERM_ALLOW);
geoMenulist.selectedItem = geoAllowItem;
geoMenulist.doCommand();
// check to make sure this change is reflected in the permission manager
is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, "geo"), PERM_ALLOW,
"permission manager shows that geolocation is allowed");
// change a site-specific cookie permission, just for fun
let cookieMenuList = getPermissionMenulist("cookie");
let cookieItem = gBrowser.contentDocument.getElementById("cookie-" + PERM_FIRST_PARTY_ONLY);
cookieMenuList.selectedItem = cookieItem;
cookieMenuList.doCommand();
is(cookieMenuList.value, PERM_FIRST_PARTY_ONLY, "menulist correctly shows that " +
"first party only cookies are allowed");
is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, "cookie"),
PERM_FIRST_PARTY_ONLY, "permission manager shows that first party cookies " +
"are allowed");
runNextTest();
},
function test_forget_site() {
// click "Forget About This Site" button
gBrowser.contentDocument.getElementById("forget-site-button").doCommand();
PlacesTestUtils.clearHistory().then(() => {
is(gSiteLabel.value, "", "site label cleared");
let allSitesItem = gBrowser.contentDocument.getElementById("all-sites-item");
is(gSitesList.selectedItem, allSitesItem,
"all sites item selected after forgetting selected site");
// check to make sure site is gone from sites list
let testSiteItem = getSiteItem(TEST_URI_2.prePath);
ok(!testSiteItem, "site removed from sites list");
// check to make sure we forgot all permissions corresponding to site
for (let type in TEST_PERMS) {
if (type == "password") {
ok(Services.logins.getLoginSavingEnabled(TEST_URI_2.prePath),
"password saving should be enabled by default");
} else {
is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, type), PERM_UNKNOWN,
type + " permission should not be set for test site 2");
}
}
runNextTest();
});
}
];
function getPermissionMenulist(aType) {
return gBrowser.contentDocument.getElementById(aType + "-menulist");
}
function getSiteItem(aHost) {
return gBrowser.contentDocument.
querySelector(".site[value='" + aHost + "']");
}
function addWindowListener(aURL, aCallback) {
Services.wm.addListener({
onOpenWindow: function(aXULWindow) {
info("window opened, waiting for focus");
Services.wm.removeListener(this);
var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
waitForFocus(function() {
is(domwindow.document.location.href, aURL, "should have seen the right window open");
domwindow.close();
aCallback();
}, domwindow);
},
onCloseWindow: function(aXULWindow) { },
onWindowTitleChange: function(aXULWindow, aNewTitle) { }
});
}

View File

@ -297,7 +297,6 @@ if test -n "$MOZ_NATIVE_DEVICES" ; then
MOZ_ANDROID_AAR(play-services-base, 7.8.0, google, com/google/android/gms)
MOZ_ANDROID_AAR(play-services-cast, 7.8.0, google, com/google/android/gms)
MOZ_ANDROID_AAR(appcompat-v7, 22.2.1, android, com/android/support)
MOZ_ANDROID_AAR(mediarouter-v7, 22.2.1, android, com/android/support, REQUIRED_INTERNAL_IMPL)
fi
@ -398,8 +397,9 @@ case "$target" in
AC_SUBST(ANDROID_SDK)
AC_SUBST(ANDROID_TOOLS)
MOZ_ANDROID_AAR(support-v4, 22.2.1, android, com/android/support, REQUIRED_INTERNAL_IMPL)
MOZ_ANDROID_AAR(appcompat-v7, 22.2.1, android, com/android/support)
MOZ_ANDROID_AAR(recyclerview-v7, 22.2.1, android, com/android/support)
MOZ_ANDROID_AAR(support-v4, 22.2.1, android, com/android/support, REQUIRED_INTERNAL_IMPL)
ANDROID_SUPPORT_ANNOTATIONS_JAR="$ANDROID_SDK_ROOT/extras/android/m2repository/com/android/support/support-annotations/22.2.1/support-annotations-22.2.1.jar"
AC_MSG_CHECKING([for support-annotations JAR])

View File

@ -64,6 +64,7 @@ support-files =
support-files =
browser_cmd_jsb_script.jsi
[browser_cmd_listen.js]
[browser_cmd_measure.js]
[browser_cmd_media.js]
support-files =
browser_cmd_media.html

View File

@ -0,0 +1,53 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the highlight command, ensure no invalid arguments are given
const TEST_PAGE = "data:text/html;charset=utf-8,foo";
function test() {
return Task.spawn(spawnTest).then(finish, helpers.handleError);
}
function* spawnTest() {
let options = yield helpers.openTab(TEST_PAGE);
yield helpers.openToolbar(options);
yield helpers.audit(options, [
{
setup: "measure",
check: {
input: "measure",
markup: "VVVVVVV",
status: "VALID"
}
},
{
setup: "measure on",
check: {
input: "measure on",
markup: "VVVVVVVVEE",
status: "ERROR"
},
exec: {
output: "Error: Too many arguments"
}
},
{
setup: "measure --visible",
check: {
input: "measure --visible",
markup: "VVVVVVVVEEEEEEEEE",
status: "ERROR"
},
exec: {
output: "Error: Too many arguments"
}
}
]);
yield helpers.closeToolbar(options);
yield helpers.closeTab(options);
}

View File

@ -92,7 +92,8 @@ const ToolboxButtons = exports.ToolboxButtons = [
{ id: "command-button-scratchpad" },
{ id: "command-button-eyedropper" },
{ id: "command-button-screenshot" },
{ id: "command-button-rulers"}
{ id: "command-button-rulers" },
{ id: "command-button-measure" }
];
/**
@ -512,6 +513,11 @@ Toolbox.prototype = {
let toggleKey = this.doc.getElementById("toolbox-toggle-host-key");
toggleKey.addEventListener("command", this.switchToPreviousHost.bind(this), true);
if (Services.prefs.prefHasUserValue("devtools.loader.srcdir")) {
let reloadKey = this.doc.getElementById("tools-reload-key");
reloadKey.addEventListener("command", this.reload.bind(this), true);
}
// Split console uses keypress instead of command so the event can be
// cancelled with stopPropagation on the keypress, and not preventDefault.
this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
@ -1612,6 +1618,11 @@ Toolbox.prototype = {
return newHost;
},
reload: function () {
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
devtools.reload(true);
},
/**
* Switch to the last used host for the toolbox UI.
* This is determined by the devtools.toolbox.previousHost pref.

View File

@ -95,6 +95,10 @@
key="&toolboxToggle.key;"
oncommand="void(0);"
modifiers="accel shift"/>
<key id="tools-reload-key"
key="&toolboxReload.key;"
oncommand="void(0);"
modifiers="accel alt"/>
</keyset>
<popupset>

View File

@ -63,6 +63,8 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
[browser_inspector_highlighter-keybinding_02.js]
[browser_inspector_highlighter-keybinding_03.js]
[browser_inspector_highlighter-keybinding_04.js]
[browser_inspector_highlighter-measure_01.js]
[browser_inspector_highlighter-measure_02.js]
[browser_inspector_highlighter-options.js]
[browser_inspector_highlighter-rect_01.js]
[browser_inspector_highlighter-rect_02.js]

View File

@ -0,0 +1,88 @@
/* 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 TEST_URL = `data:text/html;charset=utf-8,
<div style='
position:absolute;
left: 0;
top: 0;
width: 40000px;
height: 8000px'>
</div>`;
const PREFIX = "measuring-tool-highlighter-";
const HIGHLIGHTER_TYPE = "MeasuringToolHighlighter";
const X = 32;
const Y = 20;
add_task(function*() {
let helper = yield openInspectorForURL(TEST_URL)
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
let { finalize } = helper;
helper.prefix = PREFIX;
yield isHiddenByDefault(helper);
yield areLabelsHiddenByDefaultWhenShows(helper);
yield areLabelsProperlyDisplayedWhenMouseMoved(helper);
yield finalize();
});
function* isHiddenByDefault({isElementHidden}) {
info("Checking the highlighter is hidden by default");
let hidden = yield isElementHidden("elements");
ok(hidden, "highlighter's root is hidden by default");
hidden = yield isElementHidden("label-size");
ok(hidden, "highlighter's label size is hidden by default");
hidden = yield isElementHidden("label-position");
ok(hidden, "highlighter's label position is hidden by default");
}
function* areLabelsHiddenByDefaultWhenShows({isElementHidden, show}) {
info("Checking the highlighter is displayed when asked");
yield show();
let hidden = yield isElementHidden("elements");
is(hidden, false, "highlighter is visible after show");
hidden = yield isElementHidden("label-size");
ok(hidden, "label's size still hidden");
hidden = yield isElementHidden("label-position");
ok(hidden, "label's position still hidden");
}
function* areLabelsProperlyDisplayedWhenMouseMoved({isElementHidden,
synthesizeMouse, getElementTextContent}) {
info("Checking labels are properly displayed when mouse moved");
yield synthesizeMouse({
selector: ":root",
options: {type: "mousemove"},
x: X,
y: Y
});
let hidden = yield isElementHidden("label-position");
is(hidden, false, "label's position is displayed after the mouse is moved");
hidden = yield isElementHidden("label-size");
ok(hidden, "label's size still hidden");
let text = yield getElementTextContent("label-position");
let [x, y] = text.replace(/ /g, "").split(/\n/);
is(+x, X, "label's position shows the proper X coord");
is(+y, Y, "label's position shows the proper Y coord");
}

View File

@ -0,0 +1,130 @@
/* 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 TEST_URL = `data:text/html;charset=utf-8,
<div style='
position:absolute;
left: 0;
top: 0;
width: 40000px;
height: 8000px'>
</div>`;
const PREFIX = "measuring-tool-highlighter-";
const HIGHLIGHTER_TYPE = "MeasuringToolHighlighter";
const SIDES = ["top", "right", "bottom", "left"];
const X = 32;
const Y = 20;
const WIDTH = 160;
const HEIGHT = 100;
const HYPOTENUSE = Math.hypot(WIDTH, HEIGHT).toFixed(2);
add_task(function*() {
let helper = yield openInspectorForURL(TEST_URL)
.then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
let { show, finalize } = helper;
helper.prefix = PREFIX;
yield show();
yield hasNoLabelsWhenStarts(helper);
yield hasSizeLabelWhenMoved(helper);
yield hasCorrectSizeLabelValue(helper);
yield hasSizeLabelAndGuidesWhenStops(helper);
yield hasCorrectSizeLabelValue(helper);
yield finalize();
});
function* hasNoLabelsWhenStarts({isElementHidden, synthesizeMouse}) {
info("Checking highlighter has no labels when we start to select");
yield synthesizeMouse({
selector: ":root",
options: {type: "mousedown"},
x: X,
y: Y
});
let hidden = yield isElementHidden("label-size");
ok(hidden, "label's size still hidden");
hidden = yield isElementHidden("label-position");
ok(hidden, "label's position still hidden");
info("Checking highlighter has no guides when we start to select");
let guidesHidden = true;
for (let side of SIDES) {
guidesHidden = guidesHidden && (yield isElementHidden("guide-" + side));
}
ok(guidesHidden, "guides are hidden during dragging");
}
function* hasSizeLabelWhenMoved({isElementHidden, synthesizeMouse}) {
info("Checking highlighter has size label when we select the area");
yield synthesizeMouse({
selector: ":root",
options: {type: "mousemove"},
x: X + WIDTH,
y: Y + HEIGHT
});
let hidden = yield isElementHidden("label-size");
is(hidden, false, "label's size is visible during selection");
hidden = yield isElementHidden("label-position");
ok(hidden, "label's position still hidden");
info("Checking highlighter has no guides when we select the area");
let guidesHidden = true;
for (let side of SIDES) {
guidesHidden = guidesHidden && (yield isElementHidden("guide-" + side));
}
ok(guidesHidden, "guides are hidden during selection");
}
function* hasSizeLabelAndGuidesWhenStops({isElementHidden, synthesizeMouse}) {
info("Checking highlighter has size label and guides when we stop");
yield synthesizeMouse({
selector: ":root",
options: {type: "mouseup"},
x: X + WIDTH,
y: Y + HEIGHT
});
let hidden = yield isElementHidden("label-size");
is(hidden, false, "label's size is visible when the selection is done");
hidden = yield isElementHidden("label-position");
ok(hidden, "label's position still hidden");
let guidesVisible = true;
for (let side of SIDES) {
guidesVisible = guidesVisible && !(yield isElementHidden("guide-" + side));
}
ok(guidesVisible, "guides are visible when the selection is done");
}
function* hasCorrectSizeLabelValue({getElementTextContent}) {
let text = yield getElementTextContent("label-size");
let [width, height, hypot] = text.match(/\d.*px/g);
is(parseFloat(width), WIDTH, "width on label's size is correct");
is(parseFloat(height), HEIGHT, "height on label's size is correct");
is(parseFloat(hypot), HYPOTENUSE, "hypotenuse on label's size is correct");
}

View File

@ -480,3 +480,57 @@ function dispatchCommandEvent(node) {
false, false, null);
node.dispatchEvent(commandEvent);
}
/**
* Encapsulate some common operations for highlighter's tests, to have
* the tests cleaner, without exposing directly `inspector`, `highlighter`, and
* `testActor` if not needed.
*
* @param {String}
* The highlighter's type
* @return
* A generator function that takes an object with `inspector` and `testActor`
* properties. (see `openInspector`)
*/
const getHighlighterHelperFor = (type) => Task.async(
function*({inspector, testActor}) {
let front = inspector.inspector;
let highlighter = yield front.getHighlighterByType(type);
let prefix = "";
return {
set prefix(value) {
prefix = value;
},
show: function*(selector = ":root") {
let node = yield getNodeFront(selector, inspector);
yield highlighter.show(node);
},
isElementHidden: function*(id) {
return (yield testActor.getHighlighterNodeAttribute(
prefix + id, "hidden", highlighter)) === "true";
},
getElementTextContent: function*(id) {
return yield testActor.getHighlighterNodeTextContent(
prefix + id, highlighter);
},
getElementAttribute: function*(id, name) {
return yield testActor.getHighlighterNodeAttribute(
prefix + id, name, highlighter);
},
synthesizeMouse: function*(options) {
yield testActor.synthesizeMouse(options);
},
finalize: function*() {
yield highlighter.finalize();
}
};
}
);

View File

@ -205,6 +205,8 @@ devtools.jar:
skin/themes/images/command-eyedropper@2x.png (themes/images/command-eyedropper@2x.png)
skin/themes/images/command-rulers.png (themes/images/command-rulers.png)
skin/themes/images/command-rulers@2x.png (themes/images/command-rulers@2x.png)
skin/themes/images/command-measure.png (themes/images/command-measure.png)
skin/themes/images/command-measure@2x.png (themes/images/command-measure@2x.png)
skin/themes/markup-view.css (themes/markup-view.css)
skin/themes/images/editor-error.png (themes/images/editor-error.png)
skin/themes/images/editor-breakpoint.png (themes/images/editor-breakpoint.png)

View File

@ -70,6 +70,9 @@ var CommandUtils = {
* Utility to ensure that things are loaded in the correct order
*/
createRequisition: function(target, options) {
if (!gcliInit) {
return promise.reject("Unable to load gcli");
}
return gcliInit.getSystem(target).then(system => {
var Requisition = require("gcli/cli").Requisition;
return new Requisition(system, options);

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

View File

@ -768,6 +768,10 @@
background-image: url("chrome://devtools/skin/themes/images/command-rulers.png");
}
#command-button-measure > image {
background-image: url("chrome://devtools/skin/themes/images/command-measure.png");
}
@media (min-resolution: 1.1dppx) {
#command-button-paintflashing > image {
background-image: url("chrome://devtools/skin/themes/images/command-paintflashing@2x.png");
@ -808,6 +812,10 @@
#command-button-rulers > image {
background-image: url("chrome://devtools/skin/themes/images/command-rulers@2x.png");
}
#command-button-measure > image {
background-image: url("chrome://devtools/skin/themes/images/command-measure@2x.png");
}
}
/* Tabs */

View File

@ -273,3 +273,62 @@
transform: rotate(-90deg);
text-anchor: end;
}
/* Measuring Tool highlighter */
:-moz-native-anonymous .measuring-tool-highlighter-root {
position: absolute;
top: 0;
left: 0;
pointer-events: auto;
cursor: crosshair;
}
:-moz-native-anonymous .measuring-tool-highlighter-root path {
shape-rendering: crispEdges;
fill: rgba(135, 206, 235, 0.6);
stroke: #08c;
pointer-events: none;
}
:-moz-native-anonymous .dragging path {
fill: rgba(135, 206, 235, 0.6);
stroke: #08c;
opacity: 0.45;
}
:-moz-native-anonymous .measuring-tool-highlighter-label-size,
:-moz-native-anonymous .measuring-tool-highlighter-label-position {
position: absolute;
top: 0;
left: 0;
display: inline-block;
border-radius: 4px;
padding: 4px;
white-space: pre-line;
font: message-box;
font-size: 10px;
pointer-events: none;
-moz-user-select: none;
box-sizing: border-box;
}
:-moz-native-anonymous .measuring-tool-highlighter-label-position {
color: #fff;
background: hsla(214, 13%, 24%, 0.8);
}
:-moz-native-anonymous .measuring-tool-highlighter-label-size {
color: hsl(216, 33%, 97%);
background: hsl(214, 13%, 24%);
line-height: 1.5em;
}
:-moz-native-anonymous .measuring-tool-highlighter-guide-top,
:-moz-native-anonymous .measuring-tool-highlighter-guide-right,
:-moz-native-anonymous .measuring-tool-highlighter-guide-bottom,
:-moz-native-anonymous .measuring-tool-highlighter-guide-left {
stroke: #08c;
stroke-dasharray: 5 3;
shape-rendering: crispEdges;
}

View File

@ -703,3 +703,7 @@ exports.GeometryEditorHighlighter = GeometryEditorHighlighter;
const { RulersHighlighter } = require("./highlighters/rulers");
register(RulersHighlighter);
exports.RulersHighlighter = RulersHighlighter;
const { MeasuringToolHighlighter } = require("./highlighters/measuring-tool");
register(MeasuringToolHighlighter);
exports.MeasuringToolHighlighter = MeasuringToolHighlighter;

View File

@ -0,0 +1,562 @@
/* 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 events = require("sdk/event/core");
const { getCurrentZoom,
setIgnoreLayoutChanges } = require("devtools/shared/layout/utils");
const {
CanvasFrameAnonymousContentHelper,
createSVGNode, createNode } = require("./utils/markup");
// Hard coded value about the size of measuring tool label, in order to
// position and flip it when is needed.
const LABEL_SIZE_MARGIN = 8;
const LABEL_SIZE_WIDTH = 80;
const LABEL_SIZE_HEIGHT = 52;
const LABEL_POS_MARGIN = 4;
const LABEL_POS_WIDTH = 40;
const LABEL_POS_HEIGHT = 34;
const SIDES = ["top", "right", "bottom", "left"];
/**
* The MeasuringToolHighlighter is used to measure distances in a content page.
* It allows users to click and drag with their mouse to draw an area whose
* dimensions will be displayed in a tooltip next to it.
* This allows users to measure distances between elements on a page.
*/
function MeasuringToolHighlighter(highlighterEnv) {
this.env = highlighterEnv;
this.markup = new CanvasFrameAnonymousContentHelper(highlighterEnv,
this._buildMarkup.bind(this));
this.coords = {
x: 0,
y: 0
};
let { pageListenerTarget } = highlighterEnv;
pageListenerTarget.addEventListener("mousedown", this);
pageListenerTarget.addEventListener("mousemove", this);
pageListenerTarget.addEventListener("mouseleave", this);
pageListenerTarget.addEventListener("scroll", this);
pageListenerTarget.addEventListener("pagehide", this);
}
MeasuringToolHighlighter.prototype = {
typeName: "MeasuringToolHighlighter",
ID_CLASS_PREFIX: "measuring-tool-highlighter-",
_buildMarkup() {
let prefix = this.ID_CLASS_PREFIX;
let { window } = this.env;
let container = createNode(window, {
attributes: {"class": "highlighter-container"}
});
let root = createNode(window, {
parent: container,
attributes: {
"id": "root",
"class": "root",
},
prefix
});
let svg = createSVGNode(window, {
nodeType: "svg",
parent: root,
attributes: {
id: "elements",
"class": "elements",
width: "100%",
height: "100%",
hidden: "true"
},
prefix
});
createNode(window, {
nodeType: "label",
attributes: {
id: "label-size",
"class": "label-size",
"hidden": "true"
},
parent: root,
prefix
});
createNode(window, {
nodeType: "label",
attributes: {
id: "label-position",
"class": "label-position",
"hidden": "true"
},
parent: root,
prefix
});
// Creating a <g> element in order to group all the paths below, that
// together represent the measuring tool; so that would be easier move them
// around
let g = createSVGNode(window, {
nodeType: "g",
attributes: {
id: "tool",
},
parent: svg,
prefix
});
createSVGNode(window, {
nodeType: "path",
attributes: {
id: "box-path"
},
parent: g,
prefix
});
createSVGNode(window, {
nodeType: "path",
attributes: {
id: "diagonal-path"
},
parent: g,
prefix
});
for (let side of SIDES) {
createSVGNode(window, {
nodeType: "line",
parent: svg,
attributes: {
"class": `guide-${side}`,
id: `guide-${side}`,
hidden: "true"
},
prefix
});
}
return container;
},
_update() {
let { window } = this.env;
setIgnoreLayoutChanges(true);
let zoom = getCurrentZoom(window);
let { documentElement } = window.document;
let width = Math.max(documentElement.clientWidth,
documentElement.scrollWidth,
documentElement.offsetWidth);
let height = Math.max(documentElement.clientHeight,
documentElement.scrollHeight,
documentElement.offsetHeight);
let { body } = window.document;
// get the size of the content document despite the compatMode
if (body) {
width = Math.max(width, body.scrollWidth, body.offsetWidth);
height = Math.max(height, body.scrollHeight, body.offsetHeight);
}
let { coords } = this;
let isZoomChanged = zoom !== coords.zoom;
if (isZoomChanged) {
coords.zoom = zoom;
this.updateLabel();
}
let isDocumentSizeChanged = width !== coords.documentWidth ||
height !== coords.documentHeight;
if (isDocumentSizeChanged) {
coords.documentWidth = width;
coords.documentHeight = height;
}
// If either the document's size or the zoom is changed since the last
// repaint, we update the tool's size as well.
if (isZoomChanged || isDocumentSizeChanged) {
this.updateViewport();
}
setIgnoreLayoutChanges(false, documentElement);
this._rafID = window.requestAnimationFrame(() => this._update());
},
_cancelUpdate() {
if (this._rafID) {
this.env.window.cancelAnimationFrame(this._rafID);
this._rafID = 0;
}
},
destroy() {
this.hide();
this._cancelUpdate();
let { pageListenerTarget } = this.env;
pageListenerTarget.removeEventListener("mousedown", this);
pageListenerTarget.removeEventListener("mousemove", this);
pageListenerTarget.removeEventListener("mouseup", this);
pageListenerTarget.removeEventListener("scroll", this);
pageListenerTarget.removeEventListener("pagehide", this);
this.markup.destroy();
events.emit(this, "destroy");
},
show() {
setIgnoreLayoutChanges(true);
this.getElement("elements").removeAttribute("hidden");
this._update();
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
},
hide() {
setIgnoreLayoutChanges(true);
this.hideLabel("size");
this.hideLabel("position");
this.getElement("elements").setAttribute("hidden", "true");
this._cancelUpdate();
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
},
getElement(id) {
return this.markup.getElement(this.ID_CLASS_PREFIX + id);
},
setSize(w, h) {
this.setCoords(undefined, undefined, w, h);
},
setCoords(x, y, w, h) {
let { coords } = this;
if (typeof x !== "undefined") {
coords.x = x;
}
if (typeof y !== "undefined") {
coords.y = y;
}
if (typeof w !== "undefined") {
coords.w = w;
}
if (typeof h !== "undefined") {
coords.h = h;
}
setIgnoreLayoutChanges(true);
if (this._isDragging) {
this.updatePaths();
}
this.updateLabel();
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
},
updatePaths() {
let { x, y, w, h } = this.coords;
let dir = `M0 0 L${w} 0 L${w} ${h} L0 ${h}z`;
// Adding correction to the line path, otherwise some pixels are drawn
// outside the main rectangle area.
let x1 = w > 0 ? 0.5 : 0;
let y1 = w < 0 && h < 0 ? -0.5 : 0;
let w1 = w + (h < 0 && w < 0 ? 0.5 : 0);
let h1 = h + (h > 0 && w > 0 ? -0.5 : 0);
let linedir = `M${x1} ${y1} L${w1} ${h1}`;
this.getElement("box-path").setAttribute("d", dir);
this.getElement("diagonal-path").setAttribute("d", linedir);
this.getElement("tool").setAttribute("transform", `translate(${x},${y})`);
},
updateLabel(type) {
type = type || this._isDragging ? "size" : "position";
let isSizeLabel = type === "size";
let label = this.getElement(`label-${type}`);
let origin = "top left";
let { innerWidth, innerHeight, scrollX, scrollY } = this.env.window;
let { x, y, w, h, zoom } = this.coords;
let scale = 1 / zoom;
w = w || 0;
h = h || 0;
x = (x || 0) + w;
y = (y || 0) + h;
let labelMargin, labelHeight, labelWidth;
if (isSizeLabel) {
labelMargin = LABEL_SIZE_MARGIN;
labelWidth = LABEL_SIZE_WIDTH;
labelHeight = LABEL_SIZE_HEIGHT;
let d = Math.hypot(w, h).toFixed(2);
label.setTextContent(`W: ${Math.abs(w)} px
H: ${Math.abs(h)} px
: ${d}px`);
} else {
labelMargin = LABEL_POS_MARGIN;
labelWidth = LABEL_POS_WIDTH;
labelHeight = LABEL_POS_HEIGHT;
label.setTextContent(`${x}
${y}`);
}
// Size used to position properly the label
let labelBoxWidth = (labelWidth + labelMargin) * scale;
let labelBoxHeight = (labelHeight + labelMargin) * scale;
let isGoingLeft = w < scrollX;
let isSizeGoingLeft = isSizeLabel && isGoingLeft;
let isExceedingLeftMargin = x - labelBoxWidth < scrollX;
let isExceedingRightMargin = x + labelBoxWidth > innerWidth + scrollX;
let isExceedingTopMargin = y - labelBoxHeight < scrollY;
let isExceedingBottomMargin = y + labelBoxHeight > innerHeight + scrollY;
if ((isSizeGoingLeft && !isExceedingLeftMargin) || isExceedingRightMargin) {
x -= labelBoxWidth;
origin = "top right";
} else {
x += labelMargin * scale;
}
if (isSizeLabel) {
y += isExceedingTopMargin ? labelMargin * scale : -labelBoxHeight;
} else {
y += isExceedingBottomMargin ? -labelBoxHeight : labelMargin * scale;
}
label.setAttribute("style", `
width: ${labelWidth}px;
height: ${labelHeight}px;
transform-origin: ${origin};
transform: translate(${x}px,${y}px) scale(${scale})
`);
if (!isSizeLabel) {
let labelSize = this.getElement("label-size");
let style = labelSize.getAttribute("style");
if (style) {
labelSize.setAttribute("style",
style.replace(/scale[^)]+\)/, `scale(${scale})`));
}
}
},
updateViewport() {
let { scrollX, scrollY, devicePixelRatio } = this.env.window;
let { documentWidth, documentHeight, zoom } = this.coords;
// Because `devicePixelRatio` is affected by zoom (see bug 809788),
// in order to get the "real" device pixel ratio, we need divide by `zoom`
let pixelRatio = devicePixelRatio / zoom;
// The "real" device pixel ratio is used to calculate the max stroke
// width we can actually assign: on retina, for instance, it would be 0.5,
// where on non high dpi monitor would be 1.
let minWidth = 1 / pixelRatio;
let strokeWidth = Math.min(minWidth, minWidth / zoom);
this.getElement("root").setAttribute("style",
`stroke-width:${strokeWidth};
width:${documentWidth}px;
height:${documentHeight}px;
transform: translate(${-scrollX}px,${-scrollY}px)`);
},
updateGuides() {
let { x, y, w, h } = this.coords;
let guide = this.getElement("guide-top");
guide.setAttribute("x1", "0");
guide.setAttribute("y1", y);
guide.setAttribute("x2", "100%");
guide.setAttribute("y2", y);
guide = this.getElement("guide-right");
guide.setAttribute("x1", x + w);
guide.setAttribute("y1", 0);
guide.setAttribute("x2", x + w);
guide.setAttribute("y2", "100%");
guide = this.getElement("guide-bottom");
guide.setAttribute("x1", "0");
guide.setAttribute("y1", y + h);
guide.setAttribute("x2", "100%");
guide.setAttribute("y2", y + h);
guide = this.getElement("guide-left");
guide.setAttribute("x1", x);
guide.setAttribute("y1", 0);
guide.setAttribute("x2", x);
guide.setAttribute("y2", "100%");
},
showLabel(type) {
setIgnoreLayoutChanges(true);
this.getElement(`label-${type}`).removeAttribute("hidden");
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
},
hideLabel(type) {
setIgnoreLayoutChanges(true);
this.getElement(`label-${type}`).setAttribute("hidden", "true");
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
},
showGuides() {
let prefix = this.ID_CLASS_PREFIX + "guide-";
for (let side of SIDES) {
this.markup.removeAttributeForElement(`${prefix + side}`, "hidden");
}
},
hideGuides() {
let prefix = this.ID_CLASS_PREFIX + "guide-";
for (let side of SIDES) {
this.markup.setAttributeForElement(`${prefix + side}`, "hidden", "true");
}
},
handleEvent(event) {
let scrollX, scrollY, innerWidth, innerHeight;
let x, y;
let { pageListenerTarget } = this.env;
switch (event.type) {
case "mousedown":
if (event.button) {
return;
}
this._isDragging = true;
let { window } = this.env;
({ scrollX, scrollY } = window);
x = event.clientX + scrollX;
y = event.clientY + scrollY;
pageListenerTarget.addEventListener("mouseup", this);
setIgnoreLayoutChanges(true);
this.getElement("tool").setAttribute("class", "dragging");
this.hideLabel("size");
this.hideLabel("position");
this.hideGuides();
this.setCoords(x, y, 0, 0);
setIgnoreLayoutChanges(false, window.document.documentElement);
break;
case "mouseup":
this._isDragging = false;
pageListenerTarget.removeEventListener("mouseup", this);
setIgnoreLayoutChanges(true);
this.getElement("tool").removeAttribute("class", "");
// Shows the guides only if an actual area is selected
if (this.coords.w !== 0 && this.coords.h !== 0) {
this.updateGuides();
this.showGuides();
}
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
break;
case "mousemove":
({ scrollX, scrollY, innerWidth, innerHeight } = this.env.window);
x = event.clientX + scrollX;
y = event.clientY + scrollY;
let { coords } = this;
x = Math.min(innerWidth + scrollX - 1, Math.max(0 + scrollX, x));
y = Math.min(innerHeight + scrollY, Math.max(1 + scrollY, y));
this.setSize(x - coords.x, y - coords.y);
let type = this._isDragging ? "size" : "position";
this.showLabel(type);
break;
case "mouseleave":
if (!this._isDragging) {
this.hideLabel("position");
}
break;
case "scroll":
setIgnoreLayoutChanges(true);
this.updateViewport();
setIgnoreLayoutChanges(false, this.env.window.document.documentElement);
break;
case "pagehide":
this.destroy();
break;
}
}
};
exports.MeasuringToolHighlighter = MeasuringToolHighlighter;

View File

@ -13,6 +13,7 @@ DevToolsModules(
'box-model.js',
'css-transform.js',
'geometry-editor.js',
'measuring-tool.js',
'rect.js',
'rulers.js',
'selector.js',

View File

@ -842,8 +842,9 @@ var NodeFront = protocol.FrontClass(NodeActor, {
break;
}
}
// This is a new attribute.
if (!found) {
// This is a new attribute. The null check is because of Bug 1192270,
// in the case of a newly added then removed attribute
if (!found && change.newValue !== null) {
this.attributes.push({
name: change.attributeName,
namespace: change.attributeNamespace,

View File

@ -126,13 +126,19 @@ function testQueuedMutations() {
attrNode.removeAttribute("data-newattr3");
attrNode.setAttribute("data-newattr3", "3");
// This shouldn't be added in the attribute set, since it's a new
// attribute that's been added and removed.
attrNode.setAttribute("data-newattr4", "4");
attrNode.removeAttribute("data-newattr4");
gWalker.once("mutations", mutations => {
is(mutations.length, 3, "Only one mutation each is sent for multiple queued attribute changes");
is(mutations.length, 4, "Only one mutation each is sent for multiple queued attribute changes");
is(attrFront.attributes.length, 3, "Should have id, data-newattr2, and data-newattr3.");
is(attrFront.getAttribute("data-newattr2"), "1000", "Node front should still have the correct value");
is(attrFront.getAttribute("data-newattr3"), "3", "Node front should still have the correct value");
ok(!attrFront.hasAttribute("data-newattr"), "Attribute value should be removed.");
ok(!attrFront.hasAttribute("data-newattr4"), "Attribute value should be removed.");
runNextTest();
})

View File

@ -423,7 +423,7 @@ DevToolsLoader.prototype = {
/**
* Reload the current provider.
*/
reload: function() {
reload: function(showToolbox) {
var events = this.require("sdk/system/events");
events.emit("startupcache-invalidate", {});
events.emit("devtools-unloaded", {});
@ -433,6 +433,14 @@ DevToolsLoader.prototype = {
delete this._mainid;
this._chooseProvider();
this.main("devtools/client/main");
// Reopen the toolbox automatically if requested
if (showToolbox) {
let { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
let target = this.TargetFactory.forTab(gBrowser.selectedTab);
const { gDevTools } = this.require("resource:///modules/devtools/client/framework/gDevTools.jsm");
gDevTools.showToolbox(target);
}
},
/**

View File

@ -65,6 +65,7 @@ exports.devtoolsModules = [
"devtools/shared/gcli/commands/inject",
"devtools/shared/gcli/commands/jsb",
"devtools/shared/gcli/commands/listen",
"devtools/shared/gcli/commands/measure",
"devtools/shared/gcli/commands/media",
"devtools/shared/gcli/commands/pagemod",
"devtools/shared/gcli/commands/paintflashing",

View File

@ -0,0 +1,112 @@
/* 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/. */
/* globals getOuterId, getBrowserForTab */
"use strict";
const EventEmitter = require("devtools/shared/event-emitter");
const eventEmitter = new EventEmitter();
const events = require("sdk/event/core");
loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
const l10n = require("gcli/l10n");
require("devtools/server/actors/inspector");
const { MeasuringToolHighlighter, HighlighterEnvironment } =
require("devtools/server/actors/highlighters");
const highlighters = new WeakMap();
const visibleHighlighters = new Set();
const isCheckedFor = (tab) =>
tab ? visibleHighlighters.has(getBrowserForTab(tab).outerWindowID) : false;
exports.items = [
// The client measure command is used to maintain the toolbar button state
// only and redirects to the server command to actually toggle the measuring
// tool (see `measure_server` below).
{
name: "measure",
runAt: "client",
description: l10n.lookup("measureDesc"),
manual: l10n.lookup("measureManual"),
buttonId: "command-button-measure",
buttonClass: "command-button command-button-invertable",
tooltipText: l10n.lookup("measureTooltip"),
state: {
isChecked: ({_tab}) => isCheckedFor(_tab),
onChange: (target, handler) => eventEmitter.on("changed", handler),
offChange: (target, handler) => eventEmitter.off("changed", handler)
},
exec: function*(args, context) {
let { target } = context.environment;
// Pipe the call to the server command.
let response = yield context.updateExec("measure_server");
let { visible, id } = response.data;
if (visible) {
visibleHighlighters.add(id);
} else {
visibleHighlighters.delete(id);
}
eventEmitter.emit("changed", { target });
// Toggle off the button when the page navigates because the measuring
// tool is removed automatically by the MeasuringToolHighlighter on the
// server then.
let onNavigate = () => {
visibleHighlighters.delete(id);
eventEmitter.emit("changed", { target });
};
target.off("will-navigate", onNavigate);
target.once("will-navigate", onNavigate);
}
},
// The server measure command is hidden by default, it's just used by the
// client command.
{
name: "measure_server",
runAt: "server",
hidden: true,
returnType: "highlighterVisibility",
exec: function(args, context) {
let env = context.environment;
let { document } = env;
let id = getOuterId(env.window);
// Calling the command again after the measuring tool has been shown once,
// hides it.
if (highlighters.has(document)) {
let { highlighter } = highlighters.get(document);
highlighter.destroy();
return {visible: false, id};
}
// Otherwise, display the measuring tool.
let environment = new HighlighterEnvironment();
environment.initFromWindow(env.window);
let highlighter = new MeasuringToolHighlighter(environment);
// Store the instance of the measuring tool highlighter for this document
// so we can hide it later.
highlighters.set(document, { highlighter, environment });
// Listen to the highlighter's destroy event which may happen if the
// window is refreshed or closed with the measuring tool shown.
events.once(highlighter, "destroy", () => {
if (highlighters.has(document)) {
let { environment } = highlighters.get(document);
environment.destroy();
highlighters.delete(document);
}
});
highlighter.show();
return {visible: true, id};
}
}
];

View File

@ -17,6 +17,7 @@ DevToolsModules(
'inject.js',
'jsb.js',
'listen.js',
'measure.js',
'media.js',
'pagemod.js',
'paintflashing.js',

View File

@ -57,6 +57,7 @@ JAVA_CLASSPATH += \
$(ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB) \
$(ANDROID_SUPPORT_V4_AAR_LIB) \
$(ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB) \
$(ANDROID_APPCOMPAT_V7_AAR_LIB) \
$(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
$(NULL)
@ -68,7 +69,6 @@ ifdef MOZ_NATIVE_DEVICES
$(ANDROID_PLAY_SERVICES_CAST_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB) \
$(ANDROID_APPCOMPAT_V7_AAR_LIB) \
$(NULL)
endif
@ -79,6 +79,7 @@ JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
java_bundled_libs := \
$(ANDROID_SUPPORT_V4_AAR_LIB) \
$(ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB) \
$(ANDROID_APPCOMPAT_V7_AAR_LIB) \
$(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
$(NULL)
@ -88,7 +89,6 @@ ifdef MOZ_NATIVE_DEVICES
$(ANDROID_PLAY_SERVICES_CAST_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_LIB) \
$(ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB) \
$(ANDROID_APPCOMPAT_V7_AAR_LIB) \
$(NULL)
endif

View File

@ -319,6 +319,10 @@ public class HistoryPanel extends HomeFragment {
emptyHint.setVisibility(View.VISIBLE);
}
if (!RestrictedProfiles.isAllowed(getActivity(), Restriction.DISALLOW_PRIVATE_BROWSING)) {
emptyHint.setVisibility(View.GONE);
}
mList.setEmptyView(mEmptyView);
}
}

View File

@ -48,6 +48,10 @@ if CONFIG['ANDROID_SUPPORT_V4_AAR']:
ANDROID_EXTRA_PACKAGES += ['android.support.v4']
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_SUPPORT_V4_AAR_RES']]
resjar.generated_sources += ['android/support/v4/R.java']
if CONFIG['ANDROID_APPCOMPAT_V7_AAR']:
ANDROID_EXTRA_PACKAGES += ['android.support.v7.appcompat']
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_APPCOMPAT_V7_AAR_RES']]
resjar.generated_sources += ['android/support/v7/appcompat/R.java']
if CONFIG['ANDROID_RECYCLERVIEW_V7_AAR']:
ANDROID_EXTRA_PACKAGES += ['android.support.v7.recyclerview']
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_RECYCLERVIEW_V7_AAR_RES']]
@ -647,7 +651,6 @@ gbjar.extra_jars += [
]
moz_native_devices_jars = [
CONFIG['ANDROID_APPCOMPAT_V7_AAR_LIB'],
CONFIG['ANDROID_MEDIAROUTER_V7_AAR_LIB'],
CONFIG['ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB'],
CONFIG['ANDROID_PLAY_SERVICES_BASE_AAR_LIB'],
@ -663,11 +666,6 @@ if CONFIG['MOZ_NATIVE_DEVICES']:
gbjar.extra_jars += moz_native_devices_jars
gbjar.sources += moz_native_devices_sources
if CONFIG['ANDROID_APPCOMPAT_V7_AAR']:
ANDROID_EXTRA_PACKAGES += ['android.support.v7.appcompat']
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_APPCOMPAT_V7_AAR_RES']]
resjar.generated_sources += ['android/support/v7/appcompat/R.java']
if CONFIG['ANDROID_MEDIAROUTER_V7_AAR']:
ANDROID_EXTRA_PACKAGES += ['android.support.v7.mediarouter']
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_MEDIAROUTER_V7_AAR_RES']]
@ -683,6 +681,7 @@ if CONFIG['MOZ_NATIVE_DEVICES']:
ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_CAST_AAR_RES']]
resjar.generated_sources += ['com/google/android/gms/cast/R.java']
gbjar.extra_jars += [CONFIG['ANDROID_APPCOMPAT_V7_AAR_LIB']]
gbjar.extra_jars += [CONFIG['ANDROID_RECYCLERVIEW_V7_AAR_LIB']]
gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough', '-J-Xmx512m', '-J-Xms128m']

View File

@ -70,12 +70,12 @@ android {
}
dependencies {
compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:recyclerview-v7:22.2.0'
compile 'com.android.support:support-v4:22.2.1'
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'
if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:mediarouter-v7:22.2.0'
compile 'com.android.support:mediarouter-v7:22.2.1'
compile 'com.google.android.gms:play-services-base:7.8.0'
compile 'com.google.android.gms:play-services-cast:7.8.0'
}

View File

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import urllib
import os
from marionette_driver import By, errors
from marionette.marionette_test import MarionetteTestCase, skip_if_b2g
@ -16,7 +17,18 @@ elements = inline("<p>foo</p> <p>bar</p>")
class TestExecuteContent(MarionetteTestCase):
def test_stack_trace(self):
def test_execute_js_script_stack_trace(self):
try:
self.marionette.execute_js_script("""
let a = 1;
throwHere();
""", filename="file.js")
self.assertFalse(True)
except errors.JavascriptException as inst:
self.assertIn('throwHere is not defined', inst.msg)
self.assertIn('@file.js:2', inst.stacktrace)
def test_execute_script_stack_trace(self):
try:
self.marionette.execute_script("""
let a = 1;
@ -24,7 +36,10 @@ class TestExecuteContent(MarionetteTestCase):
""")
self.assertFalse(True)
except errors.JavascriptException as inst:
self.assertTrue('return b' in inst.stacktrace)
# By default execute_script pass the name of the python file
self.assertIn(os.path.basename(__file__.replace(".pyc", ".py")), inst.stacktrace)
self.assertIn('b is not defined', inst.msg)
self.assertIn('return b', inst.stacktrace)
def test_execute_simple(self):
self.assertEqual(1, self.marionette.execute_script("return 1;"))

View File

@ -816,6 +816,9 @@ GeckoDriver.prototype.applyArgumentsToSandbox = function(win, sb, args) {
* True if the script is asynchronous.
* @param {number} timeout
* When to interrupt script in milliseconds.
* @param {string} filename
* Optional. URI or name of the file we are executing.
* (Used to improve stack trace readability)
*/
GeckoDriver.prototype.executeScriptInSandbox = function(
resp,
@ -823,7 +826,8 @@ GeckoDriver.prototype.executeScriptInSandbox = function(
script,
directInject,
async,
timeout) {
timeout,
filename) {
if (directInject && async && (timeout === null || timeout === 0)) {
throw new TimeoutError("Please set a timeout");
}
@ -837,7 +841,7 @@ GeckoDriver.prototype.executeScriptInSandbox = function(
script = data + script;
}
let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
let res = Cu.evalInSandbox(script, sandbox, "1.8", filename ? filename : "dummy file", 0);
if (directInject && !async &&
(typeof res == "undefined" || typeof res.passed == "undefined")) {
@ -945,7 +949,8 @@ GeckoDriver.prototype.execute = function(cmd, resp, directInject) {
script,
directInject,
false /* async */,
scriptTimeout);
scriptTimeout,
filename);
} catch (e) {
throw new JavaScriptError(e, "execute_script", filename, line, script);
}
@ -1180,7 +1185,8 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
script,
directInject,
true /* async */,
scriptTimeout);
scriptTimeout,
filename);
} catch (e) {
chromeAsyncError(e, "execute_async_script", filename, line, script);
}

View File

@ -207,6 +207,7 @@ this.JavaScriptError = function(err, fnName, file, line, script) {
"inline javascript, line " + jsLine + "\n" +
"src: \"" + src + "\"";
}
trace += "\nStack:\n" + String(err.stack);
}
WebDriverError.call(this, msg);

View File

@ -576,6 +576,7 @@ function executeScript(msg, directInject) {
asyncTestCommandId = msg.json.command_id;
let script = msg.json.script;
let filename = msg.json.filename;
sandboxName = msg.json.sandboxName;
if (msg.json.newSandbox ||
@ -602,7 +603,7 @@ function executeScript(msg, directInject) {
stream.close();
script = data + script;
}
let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file" ,0);
let res = Cu.evalInSandbox(script, sandbox, "1.8", filename ? filename : "dummy file" ,0);
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
@ -633,7 +634,7 @@ function executeScript(msg, directInject) {
stream.close();
script = data + script;
}
let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
let res = Cu.evalInSandbox(script, sandbox, "1.8", filename ? filename : "dummy file", 0);
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
@ -724,6 +725,7 @@ function executeWithCallback(msg, useFinish) {
}
let script = msg.json.script;
let filename = msg.json.filename;
asyncTestCommandId = msg.json.command_id;
sandboxName = msg.json.sandboxName;
@ -788,7 +790,7 @@ function executeWithCallback(msg, useFinish) {
stream.close();
scriptSrc = data + scriptSrc;
}
Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0);
Cu.evalInSandbox(scriptSrc, sandbox, "1.8", filename ? filename : "dummy file", 0);
} catch (e) {
let err = new JavaScriptError(
e,

View File

@ -412,12 +412,14 @@ class Graph(object):
build_parameters['build_slugid'],
build_task['task']['extra']['locations']['build']
)
build_parameters['build_url'] = build_url
# img_url is only necessary for device builds
img_url = ARTIFACT_URL.format(
build_parameters['build_slugid'],
build_task['task']['extra']['locations'].get('img', '')
)
build_parameters['img_url'] = img_url
define_task = DEFINE_TASK.format(build_task['task']['workerType'])
@ -459,8 +461,6 @@ class Graph(object):
for test in build['dependents']:
test = test['allowed_build_tasks'][build['task']]
test_parameters = copy.copy(build_parameters)
test_parameters['build_url'] = build_url
test_parameters['img_url'] = img_url
if tests_url:
test_parameters['tests_url'] = tests_url
if test_packages_url:

View File

@ -0,0 +1,113 @@
#!/bin/bash
usage() {
echo "Usage: $0 GECKO_DIR GAIA_DIR MULET_TARBALL_URL"
}
if [ "$#" -ne 3 ]; then
usage
exit
fi
if [ -z $1 ] || [ ! -d $1 ]; then
usage
echo "First argument should be path to a gecko checkout"
exit
fi
GECKO_DIR=$1
if [ -z $2 ] || [ ! -d $2 ]; then
usage
echo "Second argument should be path to a gaia checkout"
exit
fi
GAIA_DIR=$2
url_re='^(http|https|ftp)://.*tar.bz2'
if [ -z $3 ] || ! [[ $3 =~ $url_re ]] ; then
usage
echo "Third argument should be URL to mulet tarball"
exit
fi
BUILD_URL=$3
set -ex
WORKDIR=simulator
SIM_DIR=$GECKO_DIR/b2g/simulator
if [[ $BUILD_URL =~ 'linux-x86_64' ]] ; then
PLATFORM=linux64
elif [[ $BUILD_URL =~ 'linux' ]] ; then
PLATFORM=linux
elif [[ $BUILD_URL =~ 'win32' ]] ; then
PLATFORM=win32
elif [[ $BUILD_URL =~ 'mac64' ]] ; then
PLATFORM=mac64
fi
if [ -z $PLATFORM ]; then
echo "Unable to compute platform out of mulet URL: $BUILD_URL"
exit
fi
echo "PLATFORM: $PLATFORM"
VERSION=$(sed -nE "s/MOZ_B2G_VERSION=([0-9]+\.[0-9]+).*prerelease/\1/p" $GECKO_DIR/b2g/confvars.sh)
re='^[0-9]\.[0-9]$'
if ! [[ $VERSION =~ $re ]] ; then
echo "Invalid b2g version: $VERSION"
exit
fi
echo "B2G VERSION: $VERSION"
mkdir -p $WORKDIR
if [ ! -f mulet.tar.bz2 ]; then
wget -O mulet.tar.bz2 $BUILD_URL
fi
(cd $WORKDIR/ && tar jxvf ../mulet.tar.bz2)
# Not sure it is still useful with mulet...
# remove useless stuff we don't want to ship in simulators
rm -rf $WORKDIR/firefox/gaia $WORKDIR/firefox/B2G.app/Contents/MacOS/gaia $WORKDIR/firefox/B2G.app/Contents/Resources/gaia
# Build a gaia profile specific to the simulator in order to:
# - disable the FTU
# - set custom prefs to enable devtools debugger server
# - set custom settings to disable lockscreen and screen timeout
# - only ship production apps
NOFTU=1 GAIA_APP_TARGET=production SETTINGS_PATH=$SIM_DIR/custom-settings.json make -j1 -C $GAIA_DIR profile
mv $GAIA_DIR/profile $WORKDIR/
cat $SIM_DIR/custom-prefs.js >> $WORKDIR/profile/user.js
APP_BUILDID=$(sed -n "s/BuildID=\([0-9]\{8\}\)/\1/p" $WORKDIR/firefox/application.ini)
echo "BUILDID $APP_BUILDID -- VERSION $VERSION"
XPI_NAME=fxos-simulator-$VERSION.$APP_BUILDID-$PLATFORM.xpi
ADDON_ID=fxos_$(echo $VERSION | sed "s/\./_/")_simulator@mozilla.org
ADDON_VERSION=$VERSION.$APP_BUILDID
ADDON_NAME="Firefox OS $VERSION Simulator"
ADDON_DESCRIPTION="a Firefox OS $VERSION Simulator"
echo "ADDON_ID: $ADDON_ID"
echo "ADDON_VERSION: $ADDON_VERSION"
FTP_ROOT_PATH=/pub/mozilla.org/labs/fxos-simulator
UPDATE_PATH=$VERSION/$PLATFORM
UPDATE_LINK=https://ftp.mozilla.org$FTP_ROOT_PATH/$UPDATE_PATH/$XPI_NAME
UPDATE_URL=https://ftp.mozilla.org$FTP_ROOT_PATH/$UPDATE_PATH/update.rdf
# Replace all template string in install.rdf
sed -e "s/@ADDON_VERSION@/$ADDON_VERSION/" \
-e "s/@ADDON_ID@/$ADDON_ID/" \
-e "s#@ADDON_UPDATE_URL@#$UPDATE_URL#" \
-e "s/@ADDON_NAME@/$ADDON_NAME/" \
-e "s/@ADDON_DESCRIPTION@/$ADDON_DESCRIPTION/" \
$SIM_DIR/install.rdf.in > $WORKDIR/install.rdf
# copy all useful addon file
cp $SIM_DIR/icon.png $WORKDIR/
cp $SIM_DIR/icon64.png $WORKDIR/
mkdir -p artifacts
(cd $WORKDIR && zip -r - .) > artifacts/$XPI_NAME

View File

@ -1,5 +1,9 @@
#! /bin/bash -e
# Ensure all the scripts in this dir are on the path....
DIRNAME=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
PATH=$DIRNAME:$PATH
gecko_dir=$1
target=$2

View File

@ -9,6 +9,7 @@ $inherits:
flags:
post-build:
- upload-symbols
- simulator
builds:
linux64_gecko:
@ -212,6 +213,10 @@ post-build:
- tasks/builds/dbg_linux64.yml
- tasks/builds/android_api_11.yml
task: tasks/post-builds/upload_symbols.yml
simulator:
allowed_build_tasks:
- tasks/builds/mulet_linux.yml
task: tasks/post-builds/mulet_simulator.yml
tests:
cppunit:

View File

@ -0,0 +1,58 @@
# This tasks takes a mulet build, pull gaia and craft a xpi file for FxOS simulator addon
---
taskId: {{taskId}}
task:
created: '{{now}}'
deadline: '{{#from_now}}24 hours{{/from_now}}'
metadata:
source: https://hg.mozilla.org/mozilla-central/file/tip/b2g/simulator
owner: apoirot@mozilla.com
name: '[TC] FxOS Simulator'
description: 'Firefox OS Simulator addon'
tags:
createdForUser: {{owner}}
workerType: b2gbuild
provisionerId: aws-provisioner-v1
schedulerId: task-graph-scheduler
scopes:
- 'docker-worker:cache:tc-vcs'
- 'docker-worker:image:{{#docker_image}}builder{{/docker_image}}'
- 'queue:define-task:aws-provisioner-v1/build-c4-2xlarge'
- 'queue:create-task:aws-provisioner-v1/build-c4-2xlarge'
payload:
image: '{{#docker_image}}builder{{/docker_image}}'
maxRunTime: 600
command:
- /bin/bash
- -exc
- >
tc-vcs checkout ./gecko {{base_repository}} {{head_repository}} {{head_rev}} {{head_ref}} &&
./gecko/testing/taskcluster/scripts/builder/pull-gaia.sh ./gecko ./gaia &&
./gecko/testing/taskcluster/scripts/builder/build-simulator.sh ./gecko ./gaia {{build_url}}
artifacts:
'public/build':
type: directory
path: '/home/worker/artifacts/'
expires: '{{#from_now}}1 year{{/from_now}}'
env:
GECKO_BASE_REPOSITORY: '{{base_repository}}'
GECKO_HEAD_REPOSITORY: '{{head_repository}}'
GECKO_HEAD_REV: '{{head_rev}}'
GECKO_HEAD_REF: '{{head_ref}}'
extra:
treeherderEnv:
- production
- staging
treeherder:
groupSymbol: tc-Sim
groupName: Simulator build
symbol: 'S'

View File

@ -8319,6 +8319,13 @@
"releaseChannelCollection": "opt-out",
"description": "Number times room context is clicked to visit the attached URL"
},
"LOOP_ROOM_SESSION_WITHCHAT": {
"alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
"expires_in_version": "45",
"kind": "count",
"releaseChannelCollection": "opt-out",
"description": "Number of sessions where at least one chat message was exchanged"
},
"E10S_AUTOSTART": {
"expires_in_version": "never",
"kind": "boolean",

View File

@ -1618,3 +1618,17 @@ rulersManual=Toggle the horizontal and vertical rulers for the current page
# LOCALIZATION NOTE (rulersTooltip) A string displayed as the
# tooltip of button in devtools toolbox which toggles the rulers.
rulersTooltip=Toggle rulers for the page
# LOCALIZATION NOTE (measureDesc) A very short description of the
# 'measure' command. See measureManual for a fuller description of what
# it does. This string is designed to be shown in a menu alongside the
# command name, which is why it should be as short as possible.
measureDesc=Measure a portion of the page
# LOCALIZATION NOTE (measureManual) A fuller description of the 'measure'
# command, displayed when the user asks for help on what it does.
measureManual=Activate the measuring tool to measure an arbitrary area of the page
# LOCALIZATION NOTE (measureTooltip) A string displayed as the
# tooltip of button in devtools toolbox which toggles the measuring tool.
measureTooltip=Measure a portion of the page