Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-11-17 16:21:20 -05:00
commit e7718a4c34
26 changed files with 351 additions and 116 deletions

View File

@ -1319,7 +1319,7 @@ pref("devtools.devedition.promo.shown", false);
pref("devtools.devedition.promo.url", "https://mozilla.org/firefox/developer");
// Only potentially show in beta release
#ifdef MOZ_UPDATE_CHANNEL == beta
#if MOZ_UPDATE_CHANNEL == beta
pref("devtools.devedition.promo.enabled", true);
#else
pref("devtools.devedition.promo.enabled", false);

View File

@ -50,6 +50,17 @@ let DevEdition = {
}
},
_inferBrightness: function() {
ToolbarIconColor.inferFromText();
// Get an inverted full screen button if the dark theme is applied.
if (this.styleSheet &&
document.documentElement.getAttribute("devtoolstheme") == "dark") {
document.documentElement.setAttribute("brighttitlebarforeground", "true");
} else {
document.documentElement.removeAttribute("brighttitlebarforeground");
}
},
_updateDevtoolsThemeAttribute: function() {
// Set an attribute on root element to make it possible
// to change colors based on the selected devtools theme.
@ -58,7 +69,7 @@ let DevEdition = {
devtoolsTheme = "light";
}
document.documentElement.setAttribute("devtoolstheme", devtoolsTheme);
ToolbarIconColor.inferFromText();
this._inferBrightness();
this._updateStyleSheetFromPrefs();
},
@ -83,7 +94,7 @@ let DevEdition = {
if (e.type === "load") {
this.styleSheet.removeEventListener("load", this);
gBrowser.tabContainer._positionPinnedTabs();
ToolbarIconColor.inferFromText();
this._inferBrightness();
Services.obs.notifyObservers(window, "devedition-theme-state-changed", true);
}
},
@ -102,7 +113,7 @@ let DevEdition = {
this.styleSheet.remove();
this.styleSheet = null;
gBrowser.tabContainer._positionPinnedTabs();
ToolbarIconColor.inferFromText();
this._inferBrightness();
Services.obs.notifyObservers(window, "devedition-theme-state-changed", false);
}
},

View File

@ -11,7 +11,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
(function() {
LoopUI = {
get toolbarButton() {
delete this.toolbarButton;
@ -84,8 +83,28 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
state = "active";
} else if (MozLoopService.doNotDisturb) {
state = "disabled";
} else if (MozLoopService.roomsParticipantsCount > 0) {
state = "active";
}
this.toolbarButton.node.setAttribute("state", state);
},
/**
* Play a sound in this window IF there's no sound playing yet.
*
* @param {String} name Name of the sound, like 'ringtone' or 'room-joined'
*/
playSound: function(name) {
if (this.ActiveSound || MozLoopService.doNotDisturb) {
return;
}
this.activeSound = new window.Audio();
this.activeSound.src = `chrome://browser/content/loop/shared/sounds/${name}.ogg`;
this.activeSound.load();
this.activeSound.play();
this.activeSound.addEventListener("ended", () => this.activeSound = undefined, false);
},
};
})();

View File

@ -87,41 +87,61 @@ addEventListener("blur", function(event) {
LoginManagerContent.onUsernameInput(event);
});
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
let handleContentContextMenu = function (event) {
let defaultPrevented = event.defaultPrevented;
if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
let plugin = null;
try {
plugin = event.target.QueryInterface(Ci.nsIObjectLoadingContent);
} catch (e) {}
if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
// Don't open a context menu for plugins.
return;
}
defaultPrevented = false;
let handleContentContextMenu = function (event) {
let defaultPrevented = event.defaultPrevented;
if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
let plugin = null;
try {
plugin = event.target.QueryInterface(Ci.nsIObjectLoadingContent);
} catch (e) {}
if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
// Don't open a context menu for plugins.
return;
}
if (!defaultPrevented) {
let editFlags = SpellCheckHelper.isEditable(event.target, content);
let spellInfo;
if (editFlags &
(SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
spellInfo =
InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
}
sendSyncMessage("contextmenu", { editFlags, spellInfo }, { event });
}
defaultPrevented = false;
}
Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService)
.addSystemEventListener(global, "contextmenu", handleContentContextMenu, true);
if (defaultPrevented)
return;
let addonInfo = {};
let subject = {
event: event,
addonInfo: addonInfo,
};
subject.wrappedJSObject = subject;
Services.obs.notifyObservers(subject, "content-contextmenu", null);
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
let editFlags = SpellCheckHelper.isEditable(event.target, content);
let spellInfo;
if (editFlags &
(SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
spellInfo =
InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
}
sendSyncMessage("contextmenu", { editFlags, spellInfo, addonInfo }, { event, popupNode: event.target });
}
else {
// Break out to the parent window and pass the add-on info along
let browser = docShell.chromeEventHandler;
let mainWin = browser.ownerDocument.defaultView;
mainWin.gContextMenuContentData = {
isRemote: false,
event: event,
popupNode: event.target,
browser: browser,
addonInfo: addonInfo,
};
}
}
Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService)
.addSystemEventListener(global, "contextmenu", handleContentContextMenu, false);
let AboutNetErrorListener = {
init: function(chromeGlobal) {
chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);

View File

@ -528,17 +528,16 @@ nsContextMenu.prototype = {
// Set various context menu attributes based on the state of the world.
setTarget: function (aNode, aRangeParent, aRangeOffset) {
// If gContextMenuContentData is not null, this event was forwarded from a
// child process, so use that information instead.
// gContextMenuContentData.isRemote tells us if the event came from a remote
// process. gContextMenuContentData can be null if something (like tests)
// opens the context menu directly.
let editFlags;
if (gContextMenuContentData) {
this.isRemote = true;
this.isRemote = gContextMenuContentData && gContextMenuContentData.isRemote;
if (this.isRemote) {
aNode = gContextMenuContentData.event.target;
aRangeParent = gContextMenuContentData.event.rangeParent;
aRangeOffset = gContextMenuContentData.event.rangeOffset;
editFlags = gContextMenuContentData.editFlags;
} else {
this.isRemote = false;
}
const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
@ -647,7 +646,7 @@ nsContextMenu.prototype = {
this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
if (this.onEditableArea) {
if (gContextMenuContentData) {
if (this.isRemote) {
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
}
else {
@ -772,7 +771,7 @@ nsContextMenu.prototype = {
this.hasBGImage = false;
this.isDesignMode = true;
this.onEditableArea = true;
if (gContextMenuContentData) {
if (this.isRemote) {
InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
}
else {

View File

@ -3082,10 +3082,13 @@
let spellInfo = aMessage.data.spellInfo;
if (spellInfo)
spellInfo.target = aMessage.target.messageManager;
gContextMenuContentData = { event: aMessage.objects.event,
gContextMenuContentData = { isRemote: true,
event: aMessage.objects.event,
popupNode: aMessage.objects.popupNode,
browser: browser,
editFlags: aMessage.data.editFlags,
spellInfo: spellInfo };
spellInfo: spellInfo,
addonInfo: aMessage.data.addonInfo };
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
let event = gContextMenuContentData.event;
let pos = browser.mapScreenCoordinatesFromContent(event.screenX, event.screenY);

View File

@ -16,12 +16,7 @@ registerCleanupFunction(() => {
Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME);
});
function test() {
waitForExplicitFinish();
startTests();
}
function startTests() {
add_task(function* startTests() {
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
info ("Setting browser.devedition.theme.enabled to false.");
@ -37,40 +32,54 @@ function startTests() {
ok (!DevEdition.styleSheet, "The devedition stylesheet has been removed when a lightweight theme is applied.");
info ("Removing a lightweight theme.");
let onAttributeAdded = waitForBrightTitlebarAttribute();
Services.prefs.setBoolPref(PREF_LWTHEME, false);
ok (DevEdition.styleSheet, "The devedition stylesheet has been added when a lightweight theme is removed.");
yield onAttributeAdded;
is (document.documentElement.getAttribute("brighttitlebarforeground"), "true",
"The brighttitlebarforeground attribute is set on the window.");
info ("Setting browser.devedition.theme.enabled to false.");
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, false);
ok (!DevEdition.styleSheet, "The devedition stylesheet has been removed.");
testDevtoolsTheme();
testLightweightThemePreview();
finish();
}
ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
"The brighttitlebarforeground attribute is not set on the window after devedition.theme is false.");
});
function testDevtoolsTheme() {
add_task(function* testDevtoolsTheme() {
info ("Checking that Australis is shown when the light devtools theme is applied.");
let onAttributeAdded = waitForBrightTitlebarAttribute();
Services.prefs.setBoolPref(PREF_DEVEDITION_THEME, true);
ok (DevEdition.styleSheet, "The devedition stylesheet exists.");
yield onAttributeAdded;
ok (document.documentElement.hasAttribute("brighttitlebarforeground"),
"The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
info ("Checking stylesheet and :root attributes based on devtools theme.");
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
is (document.documentElement.getAttribute("devtoolstheme"), "light",
"The documentElement has an attribute based on devtools theme.");
ok (DevEdition.styleSheet, "The devedition stylesheet is still there with the light devtools theme.");
ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
"The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
is (document.documentElement.getAttribute("devtoolstheme"), "dark",
"The documentElement has an attribute based on devtools theme.");
ok (DevEdition.styleSheet, "The devedition stylesheet is still there with the dark devtools theme.");
is (document.documentElement.getAttribute("brighttitlebarforeground"), "true",
"The brighttitlebarforeground attribute is set on the window with dark devtools theme.");
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "foobar");
is (document.documentElement.getAttribute("devtoolstheme"), "light",
"The documentElement has 'light' as a default for the devtoolstheme attribute");
ok (DevEdition.styleSheet, "The devedition stylesheet is still there with the foobar devtools theme.");
}
ok (!document.documentElement.hasAttribute("brighttitlebarforeground"),
"The brighttitlebarforeground attribute is not set on the window with light devtools theme.");
});
function dummyLightweightTheme(id) {
return {
@ -83,7 +92,7 @@ function dummyLightweightTheme(id) {
};
}
function testLightweightThemePreview() {
add_task(function* testLightweightThemePreview() {
let {LightweightThemeManager} = Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", {});
info ("Turning the pref on, then previewing lightweight themes");
@ -114,4 +123,21 @@ function testLightweightThemePreview() {
ok (DevEdition.styleSheet, "The devedition stylesheet is still enabled after the default theme is applied.");
LightweightThemeManager.resetPreview();
ok (DevEdition.styleSheet, "The devedition stylesheet is still enabled after resetting the preview.");
});
// Use a mutation observer to wait for the brighttitlebarforeground
// attribute to change. Using this instead of waiting for the load
// event on the DevEdition styleSheet.
function waitForBrightTitlebarAttribute() {
return new Promise((resolve, reject) => {
let mutationObserver = new MutationObserver(function (mutations) {
for (let mutation of mutations) {
if (mutation.attributeName == "brighttitlebarforeground") {
mutationObserver.disconnect();
resolve();
}
}
});
mutationObserver.observe(document.documentElement, { attributes: true });
});
}

View File

@ -56,7 +56,11 @@ const extend = function(target, source) {
*/
const containsParticipant = function(room, participant) {
for (let user of room.participants) {
if (user.roomConnectionId == participant.roomConnectionId) {
// XXX until a bug 1100318 is implemented and deployed,
// we need to check the "id" field here as well - roomConnectionId is the
// official value for the interface.
if (user.roomConnectionId == participant.roomConnectionId &&
user.id == participant.id) {
return true;
}
}
@ -107,13 +111,34 @@ const checkForParticipantsUpdate = function(room, updatedRoom) {
* violated. You'll notice this as well in the documentation for each method.
*/
let LoopRoomsInternal = {
/**
* @var {Map} rooms Collection of rooms currently in cache.
*/
rooms: new Map(),
/**
* @var {String} sessionType The type of user session. May be 'FXA' or 'GUEST'.
*/
get sessionType() {
return MozLoopService.userProfile ? LOOP_SESSION_TYPE.FXA :
LOOP_SESSION_TYPE.GUEST;
},
/**
* @var {Number} participantsCount The total amount of participants currently
* inside all rooms.
*/
get participantsCount() {
let count = 0;
for (let room of this.rooms.values()) {
if (!("participants" in room)) {
continue;
}
count += room.participants.length;
}
return count;
},
/**
* Fetch a list of rooms that the currently registered user is a member of.
*
@ -154,6 +179,10 @@ let LoopRoomsInternal = {
if (orig) {
checkForParticipantsUpdate(orig, room);
}
// Remove the `currSize` for posterity.
if ("currSize" in room) {
delete room.currSize;
}
this.rooms.set(room.roomToken, room);
// When a version is specified, all the data is already provided by this
// request.
@ -203,11 +232,6 @@ let LoopRoomsInternal = {
room.roomToken = roomToken;
checkForParticipantsUpdate(room, data);
extend(room, data);
// Remove the `currSize` for posterity.
if ("currSize" in room) {
delete room.currSize;
}
this.rooms.set(roomToken, room);
let eventName = !needsUpdate ? "update" : "add";
@ -382,6 +406,10 @@ Object.freeze(LoopRoomsInternal);
* See the internal code for the API documentation.
*/
this.LoopRooms = {
get participantsCount() {
return LoopRoomsInternal.participantsCount;
},
getAll: function(version, callback) {
return LoopRoomsInternal.getAll(version, callback);
},

View File

@ -81,6 +81,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
"@mozilla.org/network/dns-service;1",
"nsIDNSService");
XPCOMUtils.defineLazyServiceGetter(this, "gWM",
"@mozilla.org/appshell/window-mediator;1",
"nsIWindowMediator");
// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
XPCOMUtils.defineLazyGetter(this, "log", () => {
let ConsoleAPI = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).ConsoleAPI;
@ -965,6 +969,10 @@ this.MozLoopService = {
gInitializeTimerFunc = value;
},
get roomsParticipantsCount() {
return LoopRooms.participantsCount;
},
/**
* Initialized the loop service, and starts registration with the
* push and loop servers.
@ -995,6 +1003,26 @@ this.MozLoopService = {
}
}
// The Loop toolbar button should change icon when the room participant count
// changes from 0 to something.
const onRoomsChange = () => {
MozLoopServiceInternal.notifyStatusChanged();
};
LoopRooms.on("add", onRoomsChange);
LoopRooms.on("update", onRoomsChange);
LoopRooms.on("joined", (e, roomToken, participant) => {
// Don't alert if we're in the doNotDisturb mode, or the participant
// is the owner - the content code deals with the rest of the sounds.
if (MozLoopServiceInternal.doNotDisturb || participant.owner) {
return;
}
let window = gWM.getMostRecentWindow("navigator:browser");
if (window) {
window.LoopUI.playSound("room-joined");
}
});
// If expiresTime is not in the future and the user hasn't
// previously authenticated then skip registration.
if (!MozLoopServiceInternal.urlExpiryTimeIsInFuture() &&

View File

@ -31,13 +31,19 @@ loop.panel = (function(_, mozL10n) {
getDefaultProps: function() {
return {
buttonsHidden: false,
selectedTab: "call"
buttonsHidden: false
};
},
getInitialState: function() {
return {selectedTab: this.props.selectedTab};
// XXX Work around props.selectedTab being undefined initially.
// When we don't need to rely on the pref, this can move back to
// getDefaultProps (bug 1100258).
return {
selectedTab: this.props.selectedTab ||
(navigator.mozLoop.getLoopBoolPref("rooms.enabled") ?
"rooms" : "call")
};
},
handleSelectTab: function(event) {
@ -678,13 +684,17 @@ loop.panel = (function(_, mozL10n) {
}
},
_roomsEnabled: function() {
return navigator.mozLoop.getLoopBoolPref("rooms.enabled");
},
_onStatusChanged: function() {
var profile = navigator.mozLoop.userProfile;
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
var newUid = profile ? profile.uid : null;
if (currUid != newUid) {
// On profile change (login, logout), switch back to the default tab.
this.selectTab("call");
this.selectTab(this._roomsEnabled() ? "rooms" : "call");
this.setState({userProfile: profile});
}
this.updateServiceErrors();
@ -692,17 +702,28 @@ loop.panel = (function(_, mozL10n) {
/**
* The rooms feature is hidden by default for now. Once it gets mainstream,
* this method can be safely removed.
* this method can be simplified.
*/
_renderRoomsTab: function() {
if (!navigator.mozLoop.getLoopBoolPref("rooms.enabled")) {
return null;
_renderRoomsOrCallTab: function() {
if (!this._roomsEnabled()) {
return (
Tab({name: "call"},
React.DOM.div({className: "content-area"},
CallUrlResult({client: this.props.client,
notifications: this.props.notifications,
callUrl: this.props.callUrl}),
ToSView(null)
)
)
);
}
return (
Tab({name: "rooms"},
RoomList({dispatcher: this.props.dispatcher,
store: this.props.roomStore,
userDisplayName: this._getUserDisplayName()})
userDisplayName: this._getUserDisplayName()}),
ToSView(null)
)
);
},
@ -735,21 +756,14 @@ loop.panel = (function(_, mozL10n) {
render: function() {
var NotificationListView = sharedViews.NotificationListView;
return (
React.DOM.div(null,
NotificationListView({notifications: this.props.notifications,
clearOnDocumentHidden: true}),
TabView({ref: "tabView", selectedTab: this.props.selectedTab,
buttonsHidden: !this.state.userProfile && !this.props.showTabButtons},
Tab({name: "call"},
React.DOM.div({className: "content-area"},
CallUrlResult({client: this.props.client,
notifications: this.props.notifications,
callUrl: this.props.callUrl}),
ToSView(null)
)
),
this._renderRoomsTab(),
this._renderRoomsOrCallTab(),
Tab({name: "contacts"},
ContactsList({selectTab: this.selectTab,
startForm: this.startForm})

View File

@ -31,13 +31,19 @@ loop.panel = (function(_, mozL10n) {
getDefaultProps: function() {
return {
buttonsHidden: false,
selectedTab: "call"
buttonsHidden: false
};
},
getInitialState: function() {
return {selectedTab: this.props.selectedTab};
// XXX Work around props.selectedTab being undefined initially.
// When we don't need to rely on the pref, this can move back to
// getDefaultProps (bug 1100258).
return {
selectedTab: this.props.selectedTab ||
(navigator.mozLoop.getLoopBoolPref("rooms.enabled") ?
"rooms" : "call")
};
},
handleSelectTab: function(event) {
@ -678,13 +684,17 @@ loop.panel = (function(_, mozL10n) {
}
},
_roomsEnabled: function() {
return navigator.mozLoop.getLoopBoolPref("rooms.enabled");
},
_onStatusChanged: function() {
var profile = navigator.mozLoop.userProfile;
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
var newUid = profile ? profile.uid : null;
if (currUid != newUid) {
// On profile change (login, logout), switch back to the default tab.
this.selectTab("call");
this.selectTab(this._roomsEnabled() ? "rooms" : "call");
this.setState({userProfile: profile});
}
this.updateServiceErrors();
@ -692,17 +702,28 @@ loop.panel = (function(_, mozL10n) {
/**
* The rooms feature is hidden by default for now. Once it gets mainstream,
* this method can be safely removed.
* this method can be simplified.
*/
_renderRoomsTab: function() {
if (!navigator.mozLoop.getLoopBoolPref("rooms.enabled")) {
return null;
_renderRoomsOrCallTab: function() {
if (!this._roomsEnabled()) {
return (
<Tab name="call">
<div className="content-area">
<CallUrlResult client={this.props.client}
notifications={this.props.notifications}
callUrl={this.props.callUrl} />
<ToSView />
</div>
</Tab>
);
}
return (
<Tab name="rooms">
<RoomList dispatcher={this.props.dispatcher}
store={this.props.roomStore}
userDisplayName={this._getUserDisplayName()}/>
<ToSView />
</Tab>
);
},
@ -735,21 +756,14 @@ loop.panel = (function(_, mozL10n) {
render: function() {
var NotificationListView = sharedViews.NotificationListView;
return (
<div>
<NotificationListView notifications={this.props.notifications}
clearOnDocumentHidden={true} />
<TabView ref="tabView" selectedTab={this.props.selectedTab}
buttonsHidden={!this.state.userProfile && !this.props.showTabButtons}>
<Tab name="call">
<div className="content-area">
<CallUrlResult client={this.props.client}
notifications={this.props.notifications}
callUrl={this.props.callUrl} />
<ToSView />
</div>
</Tab>
{this._renderRoomsTab()}
{this._renderRoomsOrCallTab()}
<Tab name="contacts">
<ContactsList selectTab={this.selectTab}
startForm={this.startForm} />

View File

@ -140,7 +140,6 @@ body {
/* Rooms */
.rooms {
background: #f5f5f5;
min-height: 100px;
}

View File

@ -154,6 +154,10 @@ loop.shared.mixins = (function() {
* @param {String} name The filename to play (excluding the extension).
*/
play: function(name, options) {
if (this._isLoopDesktop() && rootObject.navigator.mozLoop.doNotDisturb) {
return;
}
options = options || {};
options.loop = options.loop || false;

View File

@ -86,6 +86,7 @@ browser.jar:
content/browser/loop/shared/sounds/connecting.ogg (content/shared/sounds/connecting.ogg)
content/browser/loop/shared/sounds/connected.ogg (content/shared/sounds/connected.ogg)
content/browser/loop/shared/sounds/terminated.ogg (content/shared/sounds/terminated.ogg)
content/browser/loop/shared/sounds/room-joined.ogg (content/shared/sounds/room-joined.ogg)
content/browser/loop/shared/sounds/failure.ogg (content/shared/sounds/failure.ogg)
# Partner SDK assets

View File

@ -917,6 +917,7 @@ describe("loop.conversation", function() {
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
navigator.mozLoop.doNotDisturb = false;
sandbox.stub(window, "Audio").returns(fakeAudio);
view = TestUtils.renderIntoDocument(

View File

@ -184,7 +184,7 @@ describe("loop.panel", function() {
view = createTestPanelView();
[callTab, roomsTab, contactsTab] =
[roomsTab, contactsTab] =
TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
});
@ -203,14 +203,6 @@ describe("loop.panel", function() {
expect(roomsTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
it("should select call tab when clicking tab button", function() {
TestUtils.Simulate.click(
view.getDOMNode().querySelector("li[data-tab-name=\"call\"]"));
expect(callTab.getDOMNode().classList.contains("selected"))
.to.be.true;
});
});
describe("loop.rooms.enabled off", function() {

View File

@ -8,6 +8,7 @@
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
const {LoopRoomsInternal} = Components.utils.import("resource:///modules/loop/LoopRooms.jsm", {});
registerCleanupFunction(function*() {
MozLoopService.doNotDisturb = false;
@ -79,3 +80,14 @@ add_task(function* test_active() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
});
add_task(function* test_room_participants() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
LoopRoomsInternal.rooms.set("test_room", {participants: [{displayName: "hugh", id: "008"}]});
MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
LoopRoomsInternal.rooms.set("test_room", {participants: []});
MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
LoopRoomsInternal.rooms.delete("test_room");
});

View File

@ -166,4 +166,52 @@ describe("loop.shared.mixins", function() {
sinon.assert.calledOnce(onDocumentHiddenStub);
});
});
describe("loop.shared.mixins.AudioMixin", function() {
var view, fakeAudio, TestComp;
beforeEach(function() {
navigator.mozLoop = {
doNotDisturb: true,
getAudioBlob: sinon.spy(function(name, callback) {
callback(null, new Blob([new ArrayBuffer(10)], {type: 'audio/ogg'}));
})
};
fakeAudio = {
play: sinon.spy(),
pause: sinon.spy(),
removeAttribute: sinon.spy()
};
sandbox.stub(window, "Audio").returns(fakeAudio);
TestComp = React.createClass({
mixins: [loop.shared.mixins.AudioMixin],
componentDidMount: function() {
this.play("failure");
},
render: function() {
return React.DOM.div();
}
});
});
it("should not play a failure sound when doNotDisturb true", function() {
view = TestUtils.renderIntoDocument(TestComp());
sinon.assert.notCalled(navigator.mozLoop.getAudioBlob);
sinon.assert.notCalled(fakeAudio.play);
});
it("should play a failure sound, once", function() {
navigator.mozLoop.doNotDisturb = false;
view = TestUtils.renderIntoDocument(TestComp());
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
"failure", sinon.match.func);
sinon.assert.calledOnce(fakeAudio.play);
expect(fakeAudio.loop).to.equal(false);
});
});
});

View File

@ -12,6 +12,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
@ -93,12 +94,21 @@ public class TabsLayoutItemView extends LinearLayout
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
final Rect r = new Rect();
mCloseButton.getHitRect(r);
r.left -= 25;
r.bottom += 25;
final Rect hitRect = new Rect();
mCloseButton.getHitRect(hitRect);
setTouchDelegate(new TouchDelegate(r, mCloseButton));
// Ideally we want the close button hit area to be 40x40dp but we are constrained by the height of the parent, so
// we make it as tall as the parent view and 40dp across.
final int targetHitArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());;
final View parent = ((View) mCloseButton.getParent());
hitRect.top = 0;
hitRect.right = getWidth();
hitRect.left = getWidth() - targetHitArea;
hitRect.bottom = parent.getHeight();
setTouchDelegate(new TouchDelegate(hitRect, mCloseButton));
return true;
}

View File

@ -47,6 +47,9 @@ function installAddon(url) {
logLevel: "verbose",
format: "tbpl",
},
console: {
logLevel: "info",
},
}
setPrefs("extensions." + install.addon.id + ".sdk", options);

View File

@ -156,6 +156,9 @@ function testInit() {
logLevel: "verbose",
format: "tbpl",
},
console: {
logLevel: "info",
},
}
setPrefs("extensions." + TEST_ID + ".sdk", options);

View File

@ -103,7 +103,7 @@
height: 0;
width: 0;
border: 14px solid hsl(210,2%,22%);
border: 14px solid hsl(214,13%,24%);
border-left-color: transparent;
border-right-color: transparent;
}

View File

@ -14,7 +14,7 @@ menubar:-moz-lwtheme,
toolbar:-moz-lwtheme {
-moz-appearance: none;
background: none;
border-style: none;
border-color: transparent;
}
menubar {

View File

@ -127,7 +127,7 @@ menubar > menu[_moz-menuactive="true"][open="true"] {
menubar > menu:-moz-lwtheme {
-moz-appearance: none;
border-style: none;
border-color: transparent;
}
menubar > menu:-moz-lwtheme:not([disabled="true"]) {

View File

@ -43,7 +43,7 @@ toolbox:-moz-lwtheme,
toolbar:-moz-lwtheme {
-moz-appearance: none;
background: none;
border-style: none;
border-color: transparent;
}
/* ::::: toolbar decorations ::::: */