mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
Merge fx-team to m-c. a=merge
This commit is contained in:
commit
e7718a4c34
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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 });
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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() &&
|
||||
|
@ -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})
|
||||
|
@ -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} />
|
||||
|
@ -140,7 +140,6 @@ body {
|
||||
|
||||
/* Rooms */
|
||||
.rooms {
|
||||
background: #f5f5f5;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
BIN
browser/components/loop/content/shared/sounds/room-joined.ogg
Normal file
BIN
browser/components/loop/content/shared/sounds/room-joined.ogg
Normal file
Binary file not shown.
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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() {
|
||||
|
@ -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");
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ function installAddon(url) {
|
||||
logLevel: "verbose",
|
||||
format: "tbpl",
|
||||
},
|
||||
console: {
|
||||
logLevel: "info",
|
||||
},
|
||||
}
|
||||
setPrefs("extensions." + install.addon.id + ".sdk", options);
|
||||
|
||||
|
@ -156,6 +156,9 @@ function testInit() {
|
||||
logLevel: "verbose",
|
||||
format: "tbpl",
|
||||
},
|
||||
console: {
|
||||
logLevel: "info",
|
||||
},
|
||||
}
|
||||
setPrefs("extensions." + TEST_ID + ".sdk", options);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ menubar:-moz-lwtheme,
|
||||
toolbar:-moz-lwtheme {
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
border-style: none;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
menubar {
|
||||
|
@ -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"]) {
|
||||
|
@ -43,7 +43,7 @@ toolbox:-moz-lwtheme,
|
||||
toolbar:-moz-lwtheme {
|
||||
-moz-appearance: none;
|
||||
background: none;
|
||||
border-style: none;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
/* ::::: toolbar decorations ::::: */
|
||||
|
Loading…
Reference in New Issue
Block a user