merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-08-07 11:52:29 +02:00
commit a4253a9b50
84 changed files with 1695 additions and 598 deletions

View File

@ -310,7 +310,8 @@ pref("browser.urlbar.match.url", "@");
pref("browser.urlbar.suggest.history", true);
pref("browser.urlbar.suggest.bookmark", true);
pref("browser.urlbar.suggest.openpage", true);
pref("browser.urlbar.suggest.searches", true);
pref("browser.urlbar.suggest.searches", false);
pref("browser.urlbar.userMadeSearchSuggestionsChoice", false);
// Limit the number of characters sent to the current search engine to fetch
// suggestions.

View File

@ -477,6 +477,27 @@ searchbar[oneoffui] {
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification {
transition: height 100ms;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
visibility: collapse;
transition: margin-top 100ms;
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > hbox[anonid="search-suggestions-notification"] {
visibility: visible;
}
#PopupAutoCompleteRichResult > richlistbox {
transition: height 100ms;
}
#PopupAutoCompleteRichResult.showSearchSuggestionsNotification > richlistbox {
transition: none;
}
#urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon,
#urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
#urlbar[pageproxystate="valid"] > #urlbar-go-button,

View File

@ -458,6 +458,7 @@ skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
[browser_urlbarRevert.js]
[browser_urlbarSearchSingleWordNotification.js]
[browser_urlbarSearchSuggestionsNotification.js]
[browser_urlbarStop.js]
[browser_urlbarTrimURLs.js]
[browser_urlbar_autoFill_backspaced.js]

View File

@ -0,0 +1,192 @@
const SUGGEST_ALL_PREF = "browser.search.suggest.enabled";
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
const CHOICE_PREF = "browser.urlbar.userMadeSearchSuggestionsChoice";
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
// Must run first.
add_task(function* prepare() {
let engine = yield promiseNewEngine(TEST_ENGINE_BASENAME);
let oldCurrentEngine = Services.search.currentEngine;
Services.search.currentEngine = engine;
registerCleanupFunction(function () {
Services.search.currentEngine = oldCurrentEngine;
Services.prefs.clearUserPref(SUGGEST_ALL_PREF);
Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
// Disable the notification for future tests so it doesn't interfere with
// them. clearUserPref() won't work because by default the pref is false.
Services.prefs.setBoolPref(CHOICE_PREF, true);
// Make sure the popup is closed for the next test.
gURLBar.blur();
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
});
});
add_task(function* focus_allSuggestionsDisabled() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, false);
Services.prefs.setBoolPref(CHOICE_PREF, false);
gURLBar.blur();
gURLBar.focus();
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
yield promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(false);
});
add_task(function* focus_noChoiceMade() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(CHOICE_PREF, false);
gURLBar.blur();
gURLBar.focus();
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
gURLBar.blur();
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
gURLBar.focus();
Assert.ok(gURLBar.popup.popupOpen, "popup should be open again");
assertVisible(true);
});
add_task(function* dismissWithoutResults() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(CHOICE_PREF, false);
gURLBar.blur();
gURLBar.focus();
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
Assert.equal(gURLBar.popup._matchCount, 0, "popup should have no results");
let disableButton = document.getAnonymousElementByAttribute(
gURLBar.popup, "anonid", "search-suggestions-notification-disable"
);
let transitionPromise = promiseTransition();
disableButton.click();
yield transitionPromise;
Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
gURLBar.blur();
gURLBar.focus();
Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
yield promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(false);
});
add_task(function* dismissWithResults() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(CHOICE_PREF, false);
gURLBar.blur();
gURLBar.focus();
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
yield promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup._matchCount > 0, "popup should have results");
let disableButton = document.getAnonymousElementByAttribute(
gURLBar.popup, "anonid", "search-suggestions-notification-disable"
);
let transitionPromise = promiseTransition();
disableButton.click();
yield transitionPromise;
Assert.ok(gURLBar.popup.popupOpen, "popup should remain open");
gURLBar.blur();
gURLBar.focus();
Assert.ok(!gURLBar.popup.popupOpen, "popup should remain closed");
yield promiseAutocompleteResultPopup("foo");
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(false);
});
add_task(function* disable() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(CHOICE_PREF, false);
gURLBar.blur();
gURLBar.focus();
Assert.ok(gURLBar.popup.popupOpen, "popup should be open");
assertVisible(true);
let disableButton = document.getAnonymousElementByAttribute(
gURLBar.popup, "anonid", "search-suggestions-notification-disable"
);
let transitionPromise = promiseTransition();
disableButton.click();
yield transitionPromise;
gURLBar.blur();
yield promiseAutocompleteResultPopup("foo");
assertSuggestionsPresent(false);
});
add_task(function* enable() {
Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
Services.prefs.setBoolPref(CHOICE_PREF, false);
gURLBar.blur();
gURLBar.focus();
yield promiseAutocompleteResultPopup("foo");
assertVisible(true);
assertSuggestionsPresent(false);
let enableButton = document.getAnonymousElementByAttribute(
gURLBar.popup, "anonid", "search-suggestions-notification-enable"
);
let searchPromise = promiseSearchComplete();
enableButton.click();
yield searchPromise;
// Clicking Yes should trigger a new search so that suggestions appear
// immediately.
assertSuggestionsPresent(true);
gURLBar.blur();
gURLBar.focus();
// Suggestions should still be present in a new search of course.
yield promiseAutocompleteResultPopup("bar");
assertSuggestionsPresent(true);
});
function assertSuggestionsPresent(expectedPresent) {
let controller = gURLBar.popup.input.controller;
let matchCount = controller.matchCount;
let actualPresent = false;
for (let i = 0; i < matchCount; i++) {
let url = controller.getValueAt(i);
let [, type, paramStr] = url.match(/^moz-action:([^,]+),(.*)$/);
let params = {};
try {
params = JSON.parse(paramStr);
} catch (err) {}
let isSuggestion = type == "searchengine" && "searchSuggestion" in params;
actualPresent = actualPresent || isSuggestion;
}
Assert.equal(actualPresent, expectedPresent);
}
function assertVisible(visible) {
let style =
window.getComputedStyle(gURLBar.popup.searchSuggestionsNotification);
Assert.equal(style.visibility, visible ? "visible" : "collapse");
}
function promiseNewEngine(basename) {
return new Promise((resolve, reject) => {
info("Waiting for engine to be added: " + basename);
let url = getRootDirectory(gTestPath) + basename;
Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "",
false, {
onSuccess: function (engine) {
info("Search engine added: " + basename);
registerCleanupFunction(() => Services.search.removeEngine(engine));
resolve(engine);
},
onError: function (errCode) {
Assert.ok(false, "addEngine failed with error code " + errCode);
reject();
},
});
});
}
function promiseTransition() {
return new Promise(resolve => {
gURLBar.popup.addEventListener("transitionend", function onEnd() {
gURLBar.popup.removeEventListener("transitionend", onEnd, true);
// The urlbar needs to handle the transitionend first, but that happens
// naturally since promises are resolved at the end of the current tick.
resolve();
}, true);
});
}

View File

@ -66,6 +66,8 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
this.timeout = this._prefs.getIntPref("delay");
this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
this._userMadeSearchSuggestionsChoice =
this._prefs.getBoolPref("userMadeSearchSuggestionsChoice");
this._ignoreNextSelect = false;
this.inputField.controllers.insertControllerAt(0, this._copyCutController);
@ -660,6 +662,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
case "formatting.enabled":
this._formattingEnabled = this._prefs.getBoolPref(aData);
break;
case "userMadeSearchSuggestionsChoice":
this._userMadeSearchSuggestionsChoice =
this._prefs.getBoolPref(aData);
break;
case "trimURLs":
this._mayTrimURLs = this._prefs.getBoolPref(aData);
break;
@ -913,6 +919,25 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
]]></body>
</method>
<field name="_userMadeSearchSuggestionsChoice"><![CDATA[
false
]]></field>
<method name="_maybeShowSearchSuggestionsNotification">
<body><![CDATA[
let showNotification =
!this._userMadeSearchSuggestionsChoice &&
// When _urlbarFocused is true, tabbrowser would close the popup if
// it's opened here, so don't show the notification.
!gBrowser.selectedBrowser._urlbarFocused &&
Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
this._prefs.getBoolPref("unifiedcomplete");
if (showNotification) {
this.popup.showSearchSuggestionsNotification(this, this);
}
]]></body>
</method>
</implementation>
<handlers>
@ -943,6 +968,7 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
if (event.originalTarget == this.inputField) {
this._hideURLTooltip();
this.formatValue();
this._maybeShowSearchSuggestionsNotification();
}
]]></handler>
@ -1474,6 +1500,29 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
</binding>
<binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
<content ignorekeys="true" level="top" consumeoutsideclicks="never">
<xul:hbox anonid="search-suggestions-notification" align="center">
<xul:description flex="1">
&urlbar.searchSuggestionsNotification.question;
<xul:label anonid="search-suggestions-notification-learn-more"
class="text-link"
value="&urlbar.searchSuggestionsNotification.learnMore;"/>
</xul:description>
<xul:button anonid="search-suggestions-notification-disable"
label="&urlbar.searchSuggestionsNotification.disable;"
onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(false);"/>
<xul:button anonid="search-suggestions-notification-enable"
label="&urlbar.searchSuggestionsNotification.enable;"
onclick="document.getBindingParent(this).dismissSearchSuggestionsNotification(true);"/>
</xul:hbox>
<xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
flex="1"/>
<xul:hbox anonid="footer">
<children/>
</xul:hbox>
</content>
<implementation>
<field name="_maxResults">0</field>
@ -1483,6 +1532,39 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
createBundle("chrome://browser/locale/places/places.properties");
</field>
<field name="searchSuggestionsNotification" readonly="true">
document.getAnonymousElementByAttribute(
this, "anonid", "search-suggestions-notification"
);
</field>
<field name="searchSuggestionsNotificationLearnMoreLink" readonly="true">
document.getAnonymousElementByAttribute(
this, "anonid", "search-suggestions-notification-learn-more"
);
</field>
<field name="footer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "footer");
</field>
<method name="dismissSearchSuggestionsNotification">
<parameter name="enableSuggestions"/>
<body><![CDATA[
Services.prefs.setBoolPref(
"browser.urlbar.userMadeSearchSuggestionsChoice", true
);
Services.prefs.setBoolPref(
"browser.urlbar.suggest.searches", enableSuggestions
);
this._hideSearchSuggestionsNotification(true);
if (enableSuggestions && this.input.textValue) {
// Start a new search so that suggestions appear immediately.
this.input.controller.startSearch(this.input.textValue);
}
]]></body>
</method>
<!-- Override this so that when UnifiedComplete is enabled, navigating
between items results in an item always being selected. This is
contrary to the old behaviour (UnifiedComplete disabled) where
@ -1549,9 +1631,102 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
// to avoid impacting startup / new window performance
aInput.popup.hidden = false;
// this method is defined on the base binding
// The popup may already be open if it's showing the search
// suggestions notification. In that case, its footer visibility
// needs to be updated.
if (this.popupOpen) {
this._updateFooterVisibility();
}
this._openAutocompletePopup(aInput, aElement);
]]></body>
]]>
</body>
</method>
<method name="_updateFooterVisibility">
<body>
<![CDATA[
this.footer.collapsed = this._matchCount == 0;
]]>
</body>
</method>
<method name="showSearchSuggestionsNotification">
<parameter name="aInput"/>
<parameter name="aElement"/>
<body>
<![CDATA[
// Set the learn-more link href.
let link = this.searchSuggestionsNotificationLearnMoreLink;
if (!link.hasAttribute("href")) {
let url = Services.urlFormatter.formatURL(
Services.prefs.getCharPref("app.support.baseURL") + "suggestions"
);
link.setAttribute("href", url);
}
// With the notification shown, the listbox's height can sometimes be
// too small when it's flexed, as it normally is. Also, it can start
// out slightly scrolled down. Both problems appear together, most
// often when the popup is very narrow and the notification's text
// must wrap. Work around them by removing the flex.
//
// But without flexing the listbox, the listbox's height animation
// sometimes fails to complete, leaving the popup too tall. Work
// around that problem by disabling the listbox animation.
this.richlistbox.flex = 0;
this.setAttribute("dontanimate", "true");
this.classList.add("showSearchSuggestionsNotification");
this._updateFooterVisibility();
this.openAutocompletePopup(aInput, aElement);
]]>
</body>
</method>
<method name="_hideSearchSuggestionsNotification">
<parameter name="animate"/>
<body>
<![CDATA[
if (animate) {
this._hideSearchSuggestionsNotificationWithAnimation();
return;
}
this.classList.remove("showSearchSuggestionsNotification");
this.richlistbox.flex = 1;
this.removeAttribute("dontanimate");
if (this._matchCount) {
// Update popup height.
this._invalidate();
} else {
this.closePopup();
}
]]>
</body>
</method>
<method name="_hideSearchSuggestionsNotificationWithAnimation">
<body>
<![CDATA[
let notificationHeight = this.searchSuggestionsNotification
.getBoundingClientRect()
.height;
this.searchSuggestionsNotification.style.marginTop =
"-" + notificationHeight + "px";
let popupHeightPx =
(this.getBoundingClientRect().height - notificationHeight) + "px";
this.style.height = popupHeightPx;
let onTransitionEnd = () => {
this.removeEventListener("transitionend", onTransitionEnd, true);
this.searchSuggestionsNotification.style.marginTop = "0px";
this.style.removeProperty("height");
this._hideSearchSuggestionsNotification(false);
};
this.addEventListener("transitionend", onTransitionEnd, true);
]]>
</body>
</method>
<method name="onPopupClick">
@ -1669,6 +1844,15 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
}
]]></handler>
<handler event="mousedown"><![CDATA[
// Required to make the xul:label.text-link elements in the search
// suggestions notification work correctly when clicked on Linux.
// This is copied from the mousedown handler in
// browser-search-autocomplete-result-popup, which apparently had a
// similar problem.
event.preventDefault();
]]></handler>
</handlers>
</binding>

View File

@ -25,7 +25,7 @@
defaultlabel="&fxaSignIn.label;"
signedinTooltiptext="&fxaSignedIn.tooltip;"
errorlabel="&fxaSignInError.label;"
onclick="gFxAccounts.onMenuPanelCommand();">
onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
<image id="PanelUI-fxa-avatar"/>
<toolbarbutton id="PanelUI-fxa-label"
fxabrandname="&syncBrand.fxAccount.label;"/>

View File

@ -18,7 +18,6 @@
}
.contact-list {
border-top: 1px solid #ccc;
overflow-x: hidden;
overflow-y: auto;
/* Space for six contacts, not affected by filtering. This is enough space
@ -26,9 +25,16 @@
height: 306px;
}
.contact-list-title {
padding: 0 1rem;
color: #666;
font-weight: 500;
font-size: .9em;
}
.contact,
.contact-separator {
padding: .5rem 1rem;
padding: .5rem 15px;
font-size: 13px;
}
@ -45,16 +51,12 @@
color: #888;
}
.contact:not(:first-child) {
border-top: 1px solid #ddd;
}
.contact-separator:not(:first-child) {
border-top: 1px solid #ccc;
}
.contact:hover {
background-color: #eee;
background-color: #E3F7FE;
}
.contact:hover > .icons {
@ -91,14 +93,101 @@
-moz-user-select: none;
}
/*
* Loop through all 12 default avatars.
*/
.contact:nth-child(12n + 1) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#blue-avatar");
background-color: #4A90E2;
}
.contact:nth-child(12n + 2) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#orange-avatar");
background-color: #F3A35C;
}
.contact:nth-child(12n + 3) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#mintgreen-avatar");
background-color: #50E2C2;
}
.contact:nth-child(12n + 4) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#lightpink-avatar");
background-color: #E364A1;
}
.contact:nth-child(12n + 5) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#grey-avatar");
background-color: #9B9B9B;
}
.contact:nth-child(12n + 6) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#yellow-avatar");
background-color: #F3E968;
}
.contact:nth-child(12n + 7) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#purple-avatar");
background-color: #9C61AF;
}
.contact:nth-child(12n + 8) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#lightgreen-avatar");
background-color: #9AC967;
}
.contact:nth-child(12n + 9) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#darkblue-avatar");
background-color: #607CAE;
}
.contact:nth-child(12n + 10) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#darkpink-avatar");
background-color: #CE4D6E;
}
.contact:nth-child(12n + 11) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#brown-avatar");
background-color: #8A572A;
}
.contact:nth-child(12n + 12) > .avatar.defaultAvatar {
background-image: url("../shared/img/avatars.svg#green-avatar");
background-color: #56B397;
}
.contact > .avatar > img {
width: 100%;
}
.contact-list-empty {
background-image: url("../shared/img/empty_contacts.svg");
background-repeat: no-repeat;
background-position: top center;
padding-top: 28%;
padding-bottom: 5%;
text-align: center;
color: #4a4a4a;
font-weight: lighter;
}
.panel-text-medium,
.panel-text-large {
margin: 3px;
}
.panel-text-medium {
font-size: 1.6rem;
}
.panel-text-large {
font-size: 2.2rem;
}
.contact > .details > .username {
font-size: 12px;
font-size: 1.3rem;
line-height: 20px;
color: #222;
color: #000;
}
.contact.blocked > .details > .username {
@ -120,125 +209,78 @@
background-repeat: no-repeat;
}
.contact > .details > .username > i.icon-google {
position: absolute;
right: 1rem;
top: 35%;
width: 14px;
height: 14px;
border-radius: 50%;
background-image: url("../shared/img/icons-16x16.svg#google");
background-position: center;
background-size: 16px 16px;
background-repeat: no-repeat;
background-color: #fff;
}
html[dir="rtl"] .contact > .details > .username > i.icon-google {
left: 1rem;
right: auto;
}
.contact > .details > .email {
color: #999;
color: #4a4a4a;
font-size: 11px;
line-height: 16px;
line-height: 14px;
}
.icons {
cursor: pointer;
display: none;
-moz-margin-start: auto;
padding: 10px;
border-radius: 2px;
background-color: #5bc0a4;
color: #fff;
-moz-user-select: none;
}
.icons:hover {
background-color: #47b396;
}
.icons:hover:active {
background-color: #3aa689;
display: block;
}
.icons i {
margin: 0 5px;
display: inline-block;
background-position: center;
background-repeat: no-repeat;
}
.icons i.icon-video {
background-image: url("../shared/img/icons-14x14.svg#video-white");
background-size: 14px 14px;
.icon-contact-video-call {
padding: 15px;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #5bc0a4;
background-image: url("../shared/img/icons-14x14.svg#video-white");
background-size: 16px 16px;
}
.icons i.icon-caret-down {
background-image: url("../shared/img/icons-10x10.svg#dropdown-white");
background-size: 10px 10px;
width: 10px;
height: 16px;
.icon-contact-video-call:hover {
background-color: #47b396;
}
.icon-contact-video-call:active {
background-color: #3aa689;
}
.icon-vertical-ellipsis {
/* Added padding for a larger click area. */
padding: 0 10px;
margin: 6px 0;
-moz-margin-start: 5px;
-moz-margin-end: -8px;
width: 4px;
height: 20px;
background-image: url("../shared/img/ellipsis-v.svg");
background-size: contain;
}
.contact > .dropdown-menu {
z-index: 2;
top: 10px;
top: 37px;
right: 22px;
bottom: auto;
right: 3em;
left: auto;
}
html[dir="rtl"] .contact > .dropdown-menu {
right: auto;
left: 3em;
left: 22px;
}
.contact > .dropdown-menu-up {
bottom: 10px;
bottom: 25px;
top: auto;
}
.contact > .dropdown-menu > .dropdown-menu-item > .icon {
width: 20px;
height: 10px;
background-position: center left;
background-size: 10px 10px;
margin-top: 3px;
}
html[dir="rtl"] .contact > .dropdown-menu > .dropdown-menu-item > .icon {
background-position: center right;
}
.contact > .dropdown-menu > .dropdown-menu-item > .icon-audio-call {
background-image: url("../shared/img/icons-16x16.svg#audio");
}
.contact > .dropdown-menu > .dropdown-menu-item > .icon-video-call {
background-image: url("../shared/img/icons-16x16.svg#video");
}
.contact > .dropdown-menu > .dropdown-menu-item > .icon-edit {
background-image: url("../shared/img/icons-16x16.svg#contacts");
}
.contact > .dropdown-menu > .dropdown-menu-item > .icon-block {
background-image: url("../shared/img/icons-16x16.svg#block");
}
.contact > .dropdown-menu > .dropdown-menu-item > .icon-unblock {
background-image: url("../shared/img/icons-16x16.svg#unblock");
}
.contact > .dropdown-menu > .dropdown-menu-item > .icon-remove {
background-image: url("../shared/img/icons-16x16.svg#delete");
}
.contact-form > .button-group {
margin-top: 1rem;
}

View File

@ -66,6 +66,7 @@ body {
.tab-view-container {
background-image: url("../shared/img/beta-ribbon.svg#beta-ribbon");
background-color: #fbfbfb;
background-size: 36px 36px;
background-repeat: no-repeat;
}
@ -722,6 +723,11 @@ html[dir="rtl"] .generate-url-spinner {
margin-bottom: -2px;
}
.dropdown-menu-item.status-available:before,
.dropdown-menu-item.status-unavailable:before {
margin-bottom: 2px;
}
html[dir="rtl"] .dropdown-menu-item.status-available:before,
html[dir="rtl"] .dropdown-menu-item.status-unavailable:before {
margin-right: -3px;
@ -842,7 +848,7 @@ html[dir="rtl"] .settings-menu .dropdown-menu {
align-content: stretch;
align-items: center;
font-size: 1rem;
background-color: #fff;
background-color: #fbfbfb;
color: #666666;
padding: .5rem 15px;
}

View File

@ -185,7 +185,6 @@ loop.contacts = (function(_, mozL10n) {
render: function() {
var cx = React.addons.classSet;
let blockAction = this.props.blocked ? "unblock" : "block";
let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
: "block_contact_menu_button";
@ -193,39 +192,22 @@ loop.contacts = (function(_, mozL10n) {
return (
React.createElement("ul", {className: cx({ "dropdown-menu": true,
"dropdown-menu-up": this.state.openDirUp })},
React.createElement("li", {className: cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked }),
"data-action": "video-call",
onClick: this.onItemClick},
React.createElement("i", {className: "icon icon-video-call"}),
mozL10n.get("video_call_menu_button")
),
React.createElement("li", {className: cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked }),
"data-action": "audio-call",
onClick: this.onItemClick},
React.createElement("i", {className: "icon icon-audio-call"}),
mozL10n.get("audio_call_menu_button")
),
React.createElement("li", {className: cx({ "dropdown-menu-item": true,
"disabled": !this.props.canEdit }),
"data-action": "edit",
onClick: this.onItemClick},
React.createElement("i", {className: "icon icon-edit"}),
mozL10n.get("edit_contact_menu_button")
mozL10n.get("edit_contact_title")
),
React.createElement("li", {className: "dropdown-menu-item",
"data-action": blockAction,
onClick: this.onItemClick},
React.createElement("i", {className: "icon icon-" + blockAction}),
mozL10n.get(blockLabel)
),
React.createElement("li", {className: cx({ "dropdown-menu-item": true,
"disabled": !this.props.canEdit }),
"data-action": "remove",
onClick: this.onItemClick},
React.createElement("i", {className: "icon icon-remove"}),
mozL10n.get("remove_contact_menu_button2")
"data-action": "remove",
onClick: this.onItemClick},
mozL10n.get("confirm_delete_contact_remove_button")
)
)
);
@ -295,28 +277,32 @@ loop.contacts = (function(_, mozL10n) {
render: function() {
let names = getContactNames(this.props.contact);
let email = getPreferred(this.props.contact, "email");
let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
let cx = React.addons.classSet;
let contactCSSClass = cx({
contact: true,
blocked: this.props.contact.blocked
});
let avatarCSSClass = cx({
avatar: true,
defaultAvatar: !avatarSrc
});
return (
React.createElement("li", {className: contactCSSClass, onMouseLeave: this.hideDropdownMenu},
React.createElement("div", {className: "avatar"},
React.createElement("img", {src: navigator.mozLoop.getUserAvatar(email.value)})
React.createElement("div", {className: avatarCSSClass},
avatarSrc ? React.createElement("img", {src: avatarSrc}) : null
),
React.createElement("div", {className: "details"},
React.createElement("div", {className: "username"}, React.createElement("strong", null, names.firstName), " ", names.lastName,
React.createElement("i", {className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}),
React.createElement("i", {className: cx({"icon icon-blocked": this.props.contact.blocked})})
),
React.createElement("div", {className: "email"}, email.value)
),
React.createElement("div", {className: "icons"},
React.createElement("i", {className: "icon icon-video",
React.createElement("i", {className: "icon icon-contact-video-call",
onClick: this.handleAction.bind(null, "video-call")}),
React.createElement("i", {className: "icon icon-caret-down",
React.createElement("i", {className: "icon icon-vertical-ellipsis",
onClick: this.showDropdownMenu})
),
this.state.showMenu
@ -337,10 +323,11 @@ loop.contacts = (function(_, mozL10n) {
],
propTypes: {
mozLoop: React.PropTypes.object.isRequired,
notifications: React.PropTypes.instanceOf(
loop.shared.models.NotificationCollection).isRequired,
// Callback to handle entry to the add/edit contact form.
startForm: React.PropTypes.func.isRequired
loop.shared.models.NotificationCollection).isRequired,
// Callback to handle entry to the add/edit contact form.
startForm: React.PropTypes.func.isRequired
},
/**
@ -361,7 +348,7 @@ loop.contacts = (function(_, mozL10n) {
},
refresh: function(callback = function() {}) {
let contactsAPI = navigator.mozLoop.contacts;
let contactsAPI = this.props.mozLoop.contacts;
this.handleContactRemoveAll();
@ -393,7 +380,7 @@ loop.contacts = (function(_, mozL10n) {
// Take the time to initialize class variables that are used outside
// `this.state`.
this.contacts = {};
this._userProfile = navigator.mozLoop.userProfile;
this._userProfile = this.props.mozLoop.userProfile;
},
componentDidMount: function() {
@ -404,7 +391,7 @@ loop.contacts = (function(_, mozL10n) {
throw err;
}
let contactsAPI = navigator.mozLoop.contacts;
let contactsAPI = this.props.mozLoop.contacts;
// Listen for contact changes/ updates.
contactsAPI.on("add", (eventName, contact) => {
@ -427,7 +414,7 @@ loop.contacts = (function(_, mozL10n) {
},
_onStatusChanged: function() {
let profile = navigator.mozLoop.userProfile;
let profile = this.props.mozLoop.userProfile;
let currUid = this._userProfile ? this._userProfile.uid : null;
let newUid = profile ? profile.uid : null;
if (currUid != newUid) {
@ -465,7 +452,7 @@ loop.contacts = (function(_, mozL10n) {
handleImportButtonClick: function() {
this.setState({ importBusy: true });
navigator.mozLoop.startImport({
this.props.mozLoop.startImport({
service: "google"
}, (err, stats) => {
this.setState({ importBusy: false });
@ -491,7 +478,7 @@ loop.contacts = (function(_, mozL10n) {
this.props.startForm("contacts_edit", contact);
break;
case "remove":
navigator.mozLoop.confirm({
this.props.mozLoop.confirm({
message: mozL10n.get("confirm_delete_contact_alert"),
okButton: mozL10n.get("confirm_delete_contact_remove_button"),
cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
@ -504,7 +491,7 @@ loop.contacts = (function(_, mozL10n) {
return;
}
navigator.mozLoop.contacts.remove(contact._guid, err => {
this.props.mozLoop.contacts.remove(contact._guid, err => {
if (err) {
throw err;
}
@ -514,7 +501,7 @@ loop.contacts = (function(_, mozL10n) {
case "block":
case "unblock":
// Invoke the API named like the action.
navigator.mozLoop.contacts[actionName](contact._guid, err => {
this.props.mozLoop.contacts[actionName](contact._guid, err => {
if (err) {
throw err;
}
@ -522,13 +509,13 @@ loop.contacts = (function(_, mozL10n) {
break;
case "video-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
this.closeWindow();
}
break;
case "audio-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
this.closeWindow();
}
break;
@ -554,7 +541,7 @@ loop.contacts = (function(_, mozL10n) {
return contact1._guid - contact2._guid;
},
render: function() {
_renderContactsList: function() {
let cx = React.addons.classSet;
let viewForItem = item => {
@ -587,6 +574,44 @@ loop.contacts = (function(_, mozL10n) {
}
}
if (shownContacts.available || shownContacts.blocked) {
return (
React.createElement("div", null,
React.createElement("div", {className: "contact-list-title"},
mozL10n.get("contact_list_title")
),
React.createElement("ul", {className: "contact-list"},
shownContacts.available ?
shownContacts.available.sort(this.sortContacts).map(viewForItem) :
null,
shownContacts.blocked && shownContacts.blocked.length > 0 ?
React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
null,
shownContacts.blocked ?
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null
)
)
);
}
return (
React.createElement("div", {className: "contact-list-empty"},
React.createElement("p", {className: "panel-text-large"},
mozL10n.get("no_contacts_message_heading")
),
React.createElement("p", {className: "panel-text-medium"},
mozL10n.get("no_contacts_import_or_add")
)
)
);
},
render: function() {
let cx = React.addons.classSet;
let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
MIN_CONTACTS_FOR_FILTERING;
return (
React.createElement("div", null,
React.createElement("div", {className: "content-area"},
@ -597,31 +622,21 @@ loop.contacts = (function(_, mozL10n) {
: null,
React.createElement(GravatarPromo, {handleUse: this.handleUseGravatar})
),
React.createElement("ul", {className: "contact-list"},
shownContacts.available ?
shownContacts.available.sort(this.sortContacts).map(viewForItem) :
null,
shownContacts.blocked && shownContacts.blocked.length > 0 ?
React.createElement("div", {className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
null,
shownContacts.blocked ?
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null
),
this._renderContactsList(),
React.createElement(ButtonGroup, {additionalClass: "contact-controls"},
React.createElement(Button, {additionalClass: "secondary",
caption: this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button3"),
disabled: this.state.importBusy,
onClick: this.handleImportButtonClick},
React.createElement("div", {className: cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})})
caption: this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button3"),
disabled: this.state.importBusy,
onClick: this.handleImportButtonClick},
React.createElement("div", {className: cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})})
),
React.createElement(Button, {additionalClass: "primary",
caption: mozL10n.get("new_contact_button"),
onClick: this.handleAddContactButtonClick})
caption: mozL10n.get("new_contact_button"),
onClick: this.handleAddContactButtonClick})
)
)
);
@ -766,7 +781,9 @@ loop.contacts = (function(_, mozL10n) {
});
return {
ContactDropdown: ContactDropdown,
ContactsList: ContactsList,
ContactDetail: ContactDetail,
ContactDetailsForm: ContactDetailsForm,
_getPreferred: getPreferred,
_setPreferred: setPreferred

View File

@ -185,7 +185,6 @@ loop.contacts = (function(_, mozL10n) {
render: function() {
var cx = React.addons.classSet;
let blockAction = this.props.blocked ? "unblock" : "block";
let blockLabel = this.props.blocked ? "unblock_contact_menu_button"
: "block_contact_menu_button";
@ -193,39 +192,22 @@ loop.contacts = (function(_, mozL10n) {
return (
<ul className={cx({ "dropdown-menu": true,
"dropdown-menu-up": this.state.openDirUp })}>
<li className={cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked })}
data-action="video-call"
onClick={this.onItemClick}>
<i className="icon icon-video-call" />
{mozL10n.get("video_call_menu_button")}
</li>
<li className={cx({ "dropdown-menu-item": true,
"disabled": this.props.blocked })}
data-action="audio-call"
onClick={this.onItemClick}>
<i className="icon icon-audio-call" />
{mozL10n.get("audio_call_menu_button")}
</li>
<li className={cx({ "dropdown-menu-item": true,
"disabled": !this.props.canEdit })}
data-action="edit"
onClick={this.onItemClick}>
<i className="icon icon-edit" />
{mozL10n.get("edit_contact_menu_button")}
{mozL10n.get("edit_contact_title")}
</li>
<li className="dropdown-menu-item"
data-action={blockAction}
onClick={this.onItemClick}>
<i className={"icon icon-" + blockAction} />
{mozL10n.get(blockLabel)}
</li>
<li className={cx({ "dropdown-menu-item": true,
"disabled": !this.props.canEdit })}
data-action="remove"
onClick={this.onItemClick}>
<i className="icon icon-remove" />
{mozL10n.get("remove_contact_menu_button2")}
data-action="remove"
onClick={this.onItemClick}>
{mozL10n.get("confirm_delete_contact_remove_button")}
</li>
</ul>
);
@ -295,28 +277,32 @@ loop.contacts = (function(_, mozL10n) {
render: function() {
let names = getContactNames(this.props.contact);
let email = getPreferred(this.props.contact, "email");
let avatarSrc = navigator.mozLoop.getUserAvatar(email.value);
let cx = React.addons.classSet;
let contactCSSClass = cx({
contact: true,
blocked: this.props.contact.blocked
});
let avatarCSSClass = cx({
avatar: true,
defaultAvatar: !avatarSrc
});
return (
<li className={contactCSSClass} onMouseLeave={this.hideDropdownMenu}>
<div className="avatar">
<img src={navigator.mozLoop.getUserAvatar(email.value)} />
<div className={avatarCSSClass}>
{avatarSrc ? <img src={avatarSrc} /> : null}
</div>
<div className="details">
<div className="username"><strong>{names.firstName}</strong> {names.lastName}
<i className={cx({"icon icon-google": this.props.contact.category[0] == "google"})} />
<i className={cx({"icon icon-blocked": this.props.contact.blocked})} />
</div>
<div className="email">{email.value}</div>
</div>
<div className="icons">
<i className="icon icon-video"
<i className="icon icon-contact-video-call"
onClick={this.handleAction.bind(null, "video-call")} />
<i className="icon icon-caret-down"
<i className="icon icon-vertical-ellipsis"
onClick={this.showDropdownMenu} />
</div>
{this.state.showMenu
@ -337,10 +323,11 @@ loop.contacts = (function(_, mozL10n) {
],
propTypes: {
mozLoop: React.PropTypes.object.isRequired,
notifications: React.PropTypes.instanceOf(
loop.shared.models.NotificationCollection).isRequired,
// Callback to handle entry to the add/edit contact form.
startForm: React.PropTypes.func.isRequired
loop.shared.models.NotificationCollection).isRequired,
// Callback to handle entry to the add/edit contact form.
startForm: React.PropTypes.func.isRequired
},
/**
@ -361,7 +348,7 @@ loop.contacts = (function(_, mozL10n) {
},
refresh: function(callback = function() {}) {
let contactsAPI = navigator.mozLoop.contacts;
let contactsAPI = this.props.mozLoop.contacts;
this.handleContactRemoveAll();
@ -393,7 +380,7 @@ loop.contacts = (function(_, mozL10n) {
// Take the time to initialize class variables that are used outside
// `this.state`.
this.contacts = {};
this._userProfile = navigator.mozLoop.userProfile;
this._userProfile = this.props.mozLoop.userProfile;
},
componentDidMount: function() {
@ -404,7 +391,7 @@ loop.contacts = (function(_, mozL10n) {
throw err;
}
let contactsAPI = navigator.mozLoop.contacts;
let contactsAPI = this.props.mozLoop.contacts;
// Listen for contact changes/ updates.
contactsAPI.on("add", (eventName, contact) => {
@ -427,7 +414,7 @@ loop.contacts = (function(_, mozL10n) {
},
_onStatusChanged: function() {
let profile = navigator.mozLoop.userProfile;
let profile = this.props.mozLoop.userProfile;
let currUid = this._userProfile ? this._userProfile.uid : null;
let newUid = profile ? profile.uid : null;
if (currUid != newUid) {
@ -465,7 +452,7 @@ loop.contacts = (function(_, mozL10n) {
handleImportButtonClick: function() {
this.setState({ importBusy: true });
navigator.mozLoop.startImport({
this.props.mozLoop.startImport({
service: "google"
}, (err, stats) => {
this.setState({ importBusy: false });
@ -491,7 +478,7 @@ loop.contacts = (function(_, mozL10n) {
this.props.startForm("contacts_edit", contact);
break;
case "remove":
navigator.mozLoop.confirm({
this.props.mozLoop.confirm({
message: mozL10n.get("confirm_delete_contact_alert"),
okButton: mozL10n.get("confirm_delete_contact_remove_button"),
cancelButton: mozL10n.get("confirm_delete_contact_cancel_button")
@ -504,7 +491,7 @@ loop.contacts = (function(_, mozL10n) {
return;
}
navigator.mozLoop.contacts.remove(contact._guid, err => {
this.props.mozLoop.contacts.remove(contact._guid, err => {
if (err) {
throw err;
}
@ -514,7 +501,7 @@ loop.contacts = (function(_, mozL10n) {
case "block":
case "unblock":
// Invoke the API named like the action.
navigator.mozLoop.contacts[actionName](contact._guid, err => {
this.props.mozLoop.contacts[actionName](contact._guid, err => {
if (err) {
throw err;
}
@ -522,13 +509,13 @@ loop.contacts = (function(_, mozL10n) {
break;
case "video-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_VIDEO);
this.closeWindow();
}
break;
case "audio-call":
if (!contact.blocked) {
navigator.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
this.props.mozLoop.calls.startDirectCall(contact, CALL_TYPES.AUDIO_ONLY);
this.closeWindow();
}
break;
@ -554,7 +541,7 @@ loop.contacts = (function(_, mozL10n) {
return contact1._guid - contact2._guid;
},
render: function() {
_renderContactsList: function() {
let cx = React.addons.classSet;
let viewForItem = item => {
@ -587,6 +574,44 @@ loop.contacts = (function(_, mozL10n) {
}
}
if (shownContacts.available || shownContacts.blocked) {
return (
<div>
<div className="contact-list-title">
{mozL10n.get("contact_list_title")}
</div>
<ul className="contact-list">
{shownContacts.available ?
shownContacts.available.sort(this.sortContacts).map(viewForItem) :
null}
{shownContacts.blocked && shownContacts.blocked.length > 0 ?
<div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
null}
{shownContacts.blocked ?
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null}
</ul>
</div>
);
}
return (
<div className="contact-list-empty">
<p className="panel-text-large">
{mozL10n.get("no_contacts_message_heading")}
</p>
<p className="panel-text-medium">
{mozL10n.get("no_contacts_import_or_add")}
</p>
</div>
);
},
render: function() {
let cx = React.addons.classSet;
let showFilter = Object.getOwnPropertyNames(this.contacts).length >=
MIN_CONTACTS_FOR_FILTERING;
return (
<div>
<div className="content-area">
@ -597,31 +622,21 @@ loop.contacts = (function(_, mozL10n) {
: null }
<GravatarPromo handleUse={this.handleUseGravatar}/>
</div>
<ul className="contact-list">
{shownContacts.available ?
shownContacts.available.sort(this.sortContacts).map(viewForItem) :
null}
{shownContacts.blocked && shownContacts.blocked.length > 0 ?
<div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
null}
{shownContacts.blocked ?
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null}
</ul>
{this._renderContactsList()}
<ButtonGroup additionalClass="contact-controls">
<Button additionalClass="secondary"
caption={this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button3")}
disabled={this.state.importBusy}
onClick={this.handleImportButtonClick} >
<div className={cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})} />
caption={this.state.importBusy
? mozL10n.get("importing_contacts_progress_button")
: mozL10n.get("import_contacts_button3")}
disabled={this.state.importBusy}
onClick={this.handleImportButtonClick} >
<div className={cx({"contact-import-spinner": true,
spinner: true,
busy: this.state.importBusy})} />
</Button>
<Button additionalClass="primary"
caption={mozL10n.get("new_contact_button")}
onClick={this.handleAddContactButtonClick} />
caption={mozL10n.get("new_contact_button")}
onClick={this.handleAddContactButtonClick} />
</ButtonGroup>
</div>
);
@ -766,7 +781,9 @@ loop.contacts = (function(_, mozL10n) {
});
return {
ContactDropdown: ContactDropdown,
ContactsList: ContactsList,
ContactDetail: ContactDetail,
ContactDetailsForm: ContactDetailsForm,
_getPreferred: getPreferred,
_setPreferred: setPreferred

View File

@ -316,7 +316,7 @@ loop.panel = (function(_, mozL10n) {
var SettingsDropdownEntry = React.createClass({displayName: "SettingsDropdownEntry",
propTypes: {
displayed: React.PropTypes.bool,
icon: React.PropTypes.string,
extraCSSClass: React.PropTypes.string,
label: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired
},
@ -326,15 +326,22 @@ loop.panel = (function(_, mozL10n) {
},
render: function() {
var cx = React.addons.classSet;
if (!this.props.displayed) {
return null;
}
var extraCSSClass = {
"dropdown-menu-item": true
};
if (this.props.extraCSSClass) {
extraCSSClass[this.props.extraCSSClass] = true;
}
return (
React.createElement("li", {className: "dropdown-menu-item", onClick: this.props.onClick},
this.props.icon ?
React.createElement("i", {className: "icon icon-" + this.props.icon}) :
null,
React.createElement("span", null, this.props.label)
React.createElement("li", {className: cx(extraCSSClass), onClick: this.props.onClick},
this.props.label
)
);
}
@ -385,6 +392,8 @@ loop.panel = (function(_, mozL10n) {
render: function() {
var cx = React.addons.classSet;
var accountEntryCSSClass = this._isSignedIn() ? "entry-settings-signout" :
"entry-settings-signin";
return (
React.createElement("div", {className: "settings-menu dropdown"},
@ -393,24 +402,23 @@ loop.panel = (function(_, mozL10n) {
ref: "menu-button",
title: mozL10n.get("settings_menu_button_tooltip")}),
React.createElement("ul", {className: cx({"dropdown-menu": true, hide: !this.state.showMenu})},
React.createElement(SettingsDropdownEntry, {
displayed: this._isSignedIn() && this.props.mozLoop.fxAEnabled,
extraCSSClass: "entry-settings-account",
label: mozL10n.get("settings_menu_item_account"),
onClick: this.handleClickAccountEntry}),
React.createElement(SettingsDropdownEntry, {displayed: false,
icon: "settings",
label: mozL10n.get("settings_menu_item_settings"),
onClick: this.handleClickSettingsEntry}),
React.createElement(SettingsDropdownEntry, {displayed: this._isSignedIn() && this.props.mozLoop.fxAEnabled,
icon: "account",
label: mozL10n.get("settings_menu_item_account"),
onClick: this.handleClickAccountEntry}),
React.createElement(SettingsDropdownEntry, {icon: "tour",
label: mozL10n.get("tour_label"),
React.createElement(SettingsDropdownEntry, {label: mozL10n.get("tour_label"),
onClick: this.openGettingStartedTour}),
React.createElement(SettingsDropdownEntry, {displayed: this.props.mozLoop.fxAEnabled,
icon: this._isSignedIn() ? "signout" : "signin",
extraCSSClass: accountEntryCSSClass,
label: this._isSignedIn() ?
mozL10n.get("settings_menu_item_signout") :
mozL10n.get("settings_menu_item_signin"),
onClick: this.handleClickAuthEntry}),
React.createElement(SettingsDropdownEntry, {icon: "help",
React.createElement(SettingsDropdownEntry, {extraCSSClass: "entry-settings-help",
label: mozL10n.get("help_label"),
onClick: this.handleHelpEntry})
)
@ -952,10 +960,10 @@ loop.panel = (function(_, mozL10n) {
userProfile: this.state.userProfile})
),
React.createElement(Tab, {name: "contacts"},
React.createElement(ContactsList, {
notifications: this.props.notifications,
selectTab: this.selectTab,
startForm: this.startForm})
React.createElement(ContactsList, {mozLoop: this.props.mozLoop,
notifications: this.props.notifications,
selectTab: this.selectTab,
startForm: this.startForm})
),
React.createElement(Tab, {hidden: true, name: "contacts_add"},
React.createElement(ContactDetailsForm, {

View File

@ -316,7 +316,7 @@ loop.panel = (function(_, mozL10n) {
var SettingsDropdownEntry = React.createClass({
propTypes: {
displayed: React.PropTypes.bool,
icon: React.PropTypes.string,
extraCSSClass: React.PropTypes.string,
label: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired
},
@ -326,15 +326,22 @@ loop.panel = (function(_, mozL10n) {
},
render: function() {
var cx = React.addons.classSet;
if (!this.props.displayed) {
return null;
}
var extraCSSClass = {
"dropdown-menu-item": true
};
if (this.props.extraCSSClass) {
extraCSSClass[this.props.extraCSSClass] = true;
}
return (
<li className="dropdown-menu-item" onClick={this.props.onClick}>
{this.props.icon ?
<i className={"icon icon-" + this.props.icon}></i> :
null}
<span>{this.props.label}</span>
<li className={cx(extraCSSClass)} onClick={this.props.onClick}>
{this.props.label}
</li>
);
}
@ -385,6 +392,8 @@ loop.panel = (function(_, mozL10n) {
render: function() {
var cx = React.addons.classSet;
var accountEntryCSSClass = this._isSignedIn() ? "entry-settings-signout" :
"entry-settings-signin";
return (
<div className="settings-menu dropdown">
@ -393,24 +402,23 @@ loop.panel = (function(_, mozL10n) {
ref="menu-button"
title={mozL10n.get("settings_menu_button_tooltip")} />
<ul className={cx({"dropdown-menu": true, hide: !this.state.showMenu})}>
<SettingsDropdownEntry
displayed={this._isSignedIn() && this.props.mozLoop.fxAEnabled}
extraCSSClass="entry-settings-account"
label={mozL10n.get("settings_menu_item_account")}
onClick={this.handleClickAccountEntry} />
<SettingsDropdownEntry displayed={false}
icon="settings"
label={mozL10n.get("settings_menu_item_settings")}
onClick={this.handleClickSettingsEntry} />
<SettingsDropdownEntry displayed={this._isSignedIn() && this.props.mozLoop.fxAEnabled}
icon="account"
label={mozL10n.get("settings_menu_item_account")}
onClick={this.handleClickAccountEntry} />
<SettingsDropdownEntry icon="tour"
label={mozL10n.get("tour_label")}
<SettingsDropdownEntry label={mozL10n.get("tour_label")}
onClick={this.openGettingStartedTour} />
<SettingsDropdownEntry displayed={this.props.mozLoop.fxAEnabled}
icon={this._isSignedIn() ? "signout" : "signin"}
extraCSSClass={accountEntryCSSClass}
label={this._isSignedIn() ?
mozL10n.get("settings_menu_item_signout") :
mozL10n.get("settings_menu_item_signin")}
onClick={this.handleClickAuthEntry} />
<SettingsDropdownEntry icon="help"
<SettingsDropdownEntry extraCSSClass="entry-settings-help"
label={mozL10n.get("help_label")}
onClick={this.handleHelpEntry} />
</ul>
@ -952,10 +960,10 @@ loop.panel = (function(_, mozL10n) {
userProfile={this.state.userProfile} />
</Tab>
<Tab name="contacts">
<ContactsList
notifications={this.props.notifications}
selectTab={this.selectTab}
startForm={this.startForm} />
<ContactsList mozLoop={this.props.mozLoop}
notifications={this.props.notifications}
selectTab={this.selectTab}
startForm={this.startForm} />
</Tab>
<Tab hidden={true} name="contacts_add">
<ContactDetailsForm

View File

@ -336,6 +336,16 @@ p {
background-position: 80% center;
}
.pseudo-icon:before {
content: "";
display: inline-block;
background-repeat: no-repeat;
width: 14px;
height: 14px;
vertical-align: top;
margin: 0 .7rem;
}
.icon-small {
background-size: 10px;
}
@ -419,7 +429,7 @@ p {
position: absolute;
bottom: 0;
left: 0;
background-color: #fdfdfd;
background-color: #fbfbfb;
box-shadow: 0 1px 3px rgba(0,0,0,.3);
list-style: none;
border-radius: 2px;
@ -433,22 +443,37 @@ html[dir="rtl"] .dropdown-menu {
.dropdown-menu-item {
width: 100%;
text-align: start;
padding: .5em 15px;
padding: .3rem .8rem;
cursor: pointer;
border: 1px solid transparent;
font-size: 1em;
font-size: 1.2rem;
line-height: 22px;
white-space: nowrap;
color: #4a4a4a;
}
.dropdown-menu-item:first-child {
padding-top: .8rem;
}
.dropdown-menu-item:last-child {
padding-bottom: .8rem;
}
.dropdown-menu-item:first-child:hover {
border-top-right-radius: 2px;
border-top-left-radius: 2px;
}
.dropdown-menu-item:last-child {
border-bottom-right-radius: 2px;
border-bottom-left-radius: 2px;
}
.dropdown-menu-item:hover {
background-color: #dbf7ff;
}
.dropdown-menu-item > .icon {
background-repeat: no-repeat;
display: inline-block;
}
.dropdown-menu-separator {
height: 1px;
margin: 2px -2px 1px -2px;

View File

@ -0,0 +1,70 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
<style>
use:not(:target) {
display: none;
}
use {
fill: #ccc;
}
use[id$="-hover"] {
fill: #444;
}
use[id$="-active"] {
fill: #0095dd;
}
use[id$="-white"] {
fill: #fff;
}
</style>
<defs>
<g id="blue" transform="translate(-2588 -413) translate(2588 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#4A90E2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="orange" transform="translate(-2638 -317) translate(2638 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3A35C"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="mintgreen" transform="translate(-2588 -317) translate(2588 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#50E2C2"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="lightpink" transform="translate(-2687 -366) translate(2687 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#E364A1"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="grey" transform="translate(-2736 -366) translate(2736 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9B9B9B"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="yellow" transform="translate(-2732 -317) translate(2732 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#F3E968"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="purple" transform="translate(-2588 -366) translate(2588 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9C61AF"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="lightgreen" transform="translate(-2686 -317) translate(2686 317)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#9AC967"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="darkblue" transform="translate(-2686 -413) translate(2686 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#607CAE"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="darkpink" transform="translate(-2638 -413) translate(2638 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#CE4D6E"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="brown" transform="translate(-2736 -413) translate(2736 413)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#8A572A"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
<g id="green" transform="translate(-2638 -366) translate(2637.857 366)" fill="none">
<path d="M18 36c9.941 0 18-8.059 18-18s-8.059-18-18-18-18 8.059-18 18 8.059 18 18 18zm-12.021-5.979c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M5.979 30.021c3.076 3.076 7.326 4.979 12.021 4.979 9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17c0 4.694 1.903 8.944 4.979 12.021z" fill="#56B397"/><path d="M30.779 29.212c-3.116 3.548-7.686 5.788-12.779 5.788-4.694 0-8.944-1.903-12.021-4.979l-.181-.183c2.676-4.187 7.364-6.962 12.701-6.962 5.066 0 9.549 2.501 12.279 6.337z" fill-opacity=".7" fill="#fff"/><path d="M18 35c9.389 0 17-7.611 17-17s-7.611-17-17-17v34z" fill-opacity=".1" fill="#000"/><ellipse fill-opacity=".7" fill="#fff" cx="18.5" cy="12.932" rx="8.002" ry="8.002"/>
</g>
</defs>
<use id="blue-avatar" xlink:href="#blue"/>
<use id="orange-avatar" xlink:href="#orange"/>
<use id="mintgreen-avatar" xlink:href="#mintgreen"/>
<use id="lightpink-avatar" xlink:href="#lightpink"/>
<use id="grey-avatar" xlink:href="#grey"/>
<use id="yellow-avatar" xlink:href="#yellow"/>
<use id="purple-avatar" xlink:href="#purple"/>
<use id="lightgreen-avatar" xlink:href="#lightgreen"/>
<use id="darkblue-avatar" xlink:href="#darkblue"/>
<use id="darkpink-avatar" xlink:href="#darkpink"/>
<use id="brown-avatar" xlink:href="#brown"/>
<use id="green-avatar" xlink:href="#green"/>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1 @@
<svg width="4" height="20" viewBox="0 0 4 20" xmlns="http://www.w3.org/2000/svg"><g fill="#3A99DA"><ellipse cx="2" cy="2" rx="2" ry="2"/><ellipse cx="2" cy="10" rx="2" ry="2"/><ellipse cx="2" cy="18" rx="2" ry="2"/></g></svg>

After

Width:  |  Height:  |  Size: 225 B

View File

@ -0,0 +1 @@
<svg width="117" height="91" viewBox="0 0 117 91" xmlns="http://www.w3.org/2000/svg"><g fill="#D8D8D8"><path d="M116.431 59.357c-4.793 5.459-11.824 8.905-19.66 8.905-7.222 0-13.761-2.927-18.494-7.66l-.278-.282c4.116-6.441 11.33-10.712 19.541-10.712 7.795 0 14.69 3.848 18.891 9.749zm-18.891-12.736c6.799 0 12.311-5.512 12.311-12.311s-5.512-12.311-12.311-12.311-12.311 5.512-12.311 12.311 5.512 12.311 12.311 12.311zM38.431 59.357c-4.793 5.459-11.824 8.905-19.66 8.905-7.222 0-13.761-2.927-18.494-7.66l-.278-.282c4.116-6.441 11.33-10.712 19.541-10.712 7.795 0 14.69 3.848 18.891 9.749zm-18.891-12.736c6.799 0 12.311-5.512 12.311-12.311s-5.512-12.311-12.311-12.311-12.311 5.512-12.311 12.311 5.512 12.311 12.311 12.311z" id="Mask-Copy-5" fill-opacity=".8"/><path d="M91.495 70.608c-8.418 9.588-20.766 15.639-34.528 15.639-12.684 0-24.167-5.141-32.479-13.453l-.488-.495c7.229-11.312 19.898-18.812 34.318-18.812 13.689 0 25.8 6.759 33.177 17.121zm-33.177-22.367c11.941 0 21.621-9.68 21.621-21.621 0-11.941-9.68-21.621-21.621-21.621-11.941 0-21.621 9.68-21.621 21.621 0 11.941 9.68 21.621 21.621 21.621z" stroke="#FBFBFB" stroke-width="4"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -35,7 +35,7 @@
<path fill-rule="evenodd" d="M9.741,7.857c0,1.42-1.151,2.572-2.572,2.572 c-0.625,0-1.199-0.223-1.645-0.595l-0.605,0.605c0.395,0.344,0.87,0.599,1.393,0.734v0.97H5.884c-0.56,0-1.034,0.359-1.212,0.858 h4.994c-0.178-0.499-0.652-0.858-1.212-0.858H8.026v-0.97c1.478-0.38,2.572-1.718,2.572-3.316V6.142H9.741V7.857z"/>
</g>
<path id="pause-shape" fill-rule="evenodd" d="M4.75,1h-1.5C2.836,1,2.5,1.336,2.5,1.75v10.5 C2.5,12.664,2.836,13,3.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C5.5,1.336,5.164,1,4.75,1z M10.75,1h-1.5 C8.836,1,8.5,1.336,8.5,1.75v10.5C8.5,12.664,8.836,13,9.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C11.5,1.336,11.164,1,10.75,1 z"/>
<path id="video-shape" fill-rule="evenodd" d="M12.175,3.347L9.568,5.651V3.905c0-0.657-0.497-1.19-1.111-1.19 H2.111C1.498,2.714,1,3.247,1,3.905v6.191c0,0.658,0.498,1.19,1.111,1.19h6.345c0.614,0,1.111-0.533,1.111-1.19V8.322l2.607,2.305 C12.4,10.867,12.71,10.938,13,10.874V3.099C12.71,3.035,12.4,3.106,12.175,3.347z"/>
<path id="video-shape" d="M1.59247473,11.4075253 C1.9956945,11.7983901 2.46237364,12 3.02957694,12 L7.98333957,12 C8.53762636,12 9.01666043,11.7983901 9.40752527,11.4075253 C9.81130663,11.0043055 10,10.5376264 10,9.97042306 L10,8.81074504 L12.8360165,11.6467615 C12.9247473,11.7354923 13.0252714,11.7731187 13.1516286,11.7731187 C13.2145264,11.7731187 13.2650693,11.7607638 13.3279671,11.7354923 C13.517222,11.659678 13.6053912,11.5209659 13.6053912,11.319356 L13.6053912,3.66772744 C13.6053912,3.47903407 13.517222,3.34032198 13.3279671,3.25215275 C13.2650693,3.23923624 13.2145264,3.22688132 13.1516286,3.22688132 C13.0252714,3.22688132 12.9247473,3.26450768 12.8360165,3.3526769 L10,6.17633845 L10,5.01666043 C10,4.46181206 9.81130663,3.98333957 9.40752527,3.59247473 C9.01666043,3.18869337 8.53762636,3 7.98333957,3 L3.02957694,3 C2.46237364,3 1.9956945,3.18869337 1.59247473,3.59247473 C1.20160988,3.98333957 1,4.46181206 1,5.01666043 L1,9.97042306 C1,10.5376264 1.20160988,11.0043055 1.59247473,11.4075253" fill="#fff" fill-rule="evenodd"/>
<g id="volume-shape">
<path fill-rule="evenodd" d="M3.513,4.404H1.896c-0.417,0-0.756,0.338-0.756,0.755v3.679 c0,0.417,0.338,0.755,0.756,0.755H3.51l2.575,2.575c0.261,0.261,0.596,0.4,0.938,0.422V1.409C6.682,1.431,6.346,1.57,6.085,1.831 L3.513,4.404z M8.555,5.995C8.619,6.32,8.653,6.656,8.653,7c0,0.344-0.034,0.679-0.098,1.004l0.218,0.142 C8.852,7.777,8.895,7.393,8.895,7c0-0.394-0.043-0.777-0.123-1.147L8.555,5.995z M12.224,3.6l-0.475,0.31 c0.359,0.962,0.557,2.003,0.557,3.09c0,1.087-0.198,2.128-0.557,3.09l0.475,0.31c0.41-1.054,0.635-2.201,0.635-3.4 C12.859,5.8,12.634,4.654,12.224,3.6z M10.061,5.012C10.25,5.642,10.353,6.308,10.353,7c0,0.691-0.103,1.358-0.293,1.987 l0.351,0.229C10.634,8.517,10.756,7.772,10.756,7c0-0.773-0.121-1.517-0.345-2.216L10.061,5.012z"/>
<path d="M7.164,12.74l-0.15-0.009c-0.389-0.024-0.754-0.189-1.028-0.463L3.452,9.735H1.896 C1.402,9.735,1,9.333,1,8.838V5.16c0-0.494,0.402-0.896,0.896-0.896h1.558l2.531-2.531C6.26,1.458,6.625,1.293,7.014,1.269 l0.15-0.009V12.74z M1.896,4.545c-0.339,0-0.615,0.276-0.615,0.615v3.679c0,0.339,0.276,0.615,0.615,0.615h1.672l2.616,2.616 c0.19,0.19,0.434,0.316,0.697,0.363V1.568C6.619,1.615,6.375,1.741,6.185,1.931L3.571,4.545H1.896z M12.292,10.612l-0.714-0.467 l0.039-0.105C11.981,9.067,12.165,8.044,12.165,7c0-1.044-0.184-2.067-0.548-3.041l-0.039-0.105l0.714-0.467l0.063,0.162 C12.783,4.649,13,5.81,13,7s-0.217,2.351-0.645,3.451L12.292,10.612z M11.92,10.033l0.234,0.153 c0.374-1.019,0.564-2.09,0.564-3.186s-0.19-2.167-0.564-3.186L11.92,3.966C12.27,4.94,12.447,5.96,12.447,7 C12.447,8.04,12.27,9.059,11.92,10.033z M10.489,9.435L9.895,9.047l0.031-0.101C10.116,8.315,10.212,7.66,10.212,7 c0-0.661-0.096-1.316-0.287-1.947L9.895,4.952l0.594-0.388l0.056,0.176C10.779,5.471,10.897,6.231,10.897,7 c0,0.769-0.118,1.529-0.351,2.259L10.489,9.435z M10.225,8.926l0.106,0.069C10.52,8.348,10.615,7.677,10.615,7 c0-0.677-0.095-1.348-0.284-1.996l-0.106,0.07C10.403,5.699,10.494,6.347,10.494,7C10.494,7.652,10.403,8.3,10.225,8.926z M8.867,8.376L8.398,8.07l0.018-0.093C8.48,7.654,8.512,7.325,8.512,7S8.48,6.345,8.417,6.022L8.398,5.929l0.469-0.306l0.043,0.2 C8.994,6.211,9.036,6.607,9.036,7c0,0.393-0.042,0.789-0.126,1.176L8.867,8.376z"/>

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -72,6 +72,9 @@ browser.jar:
content/browser/loop/shared/img/02@2x.png (content/shared/img/02@2x.png)
content/browser/loop/shared/img/telefonica.png (content/shared/img/telefonica.png)
content/browser/loop/shared/img/telefonica@2x.png (content/shared/img/telefonica@2x.png)
content/browser/loop/shared/img/ellipsis-v.svg (content/shared/img/ellipsis-v.svg)
content/browser/loop/shared/img/empty_contacts.svg (content/shared/img/empty_contacts.svg)
content/browser/loop/shared/img/avatars.svg (content/shared/img/avatars.svg)
# Shared scripts
content/browser/loop/shared/js/actions.js (content/shared/js/actions.js)

View File

@ -898,21 +898,20 @@ function injectLoopAPI(targetWindow) {
/**
* Compose a URL pointing to the location of an avatar by email address.
* At the moment we use the Gravatar service to match email addresses with
* avatars. This might change in the future as avatars might come from another
* source.
* avatars. If no email address is found we return null.
*
* @param {String} emailAddress Users' email address
* @param {Number} size Size of the avatar image to return in pixels.
* Optional. Default value: 40.
* @return the URL pointing to an avatar matching the provided email address.
* @return the URL pointing to an avatar matching the provided email address
* or null if this is not available.
*/
getUserAvatar: {
enumerable: true,
writable: true,
value: function(emailAddress, size = 40) {
const kEmptyGif = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
if (!emailAddress || !MozLoopService.getLoopPref("contacts.gravatars.show")) {
return kEmptyGif;
return null;
}
// Do the MD5 dance.

View File

@ -76,9 +76,13 @@ describe("loop.contacts", function() {
var notifications;
var listView;
var oldMozLoop = navigator.mozLoop;
var mozL10nGetSpy;
beforeEach(function() {
sandbox = sinon.sandbox.create();
mozL10nGetSpy = sandbox.spy(document.mozL10n, "get");
navigator.mozLoop = {
getStrings: function(entityName) {
var textContentValue = "fakeText";
@ -111,6 +115,10 @@ describe("loop.contacts", function() {
callback(null, [].concat(fakeContacts));
},
on: sandbox.stub()
},
calls: {
startDirectCall: function() {},
clearCallInProgress: function() {}
}
};
@ -146,6 +154,7 @@ describe("loop.contacts", function() {
it("should show the gravatars promo box", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
@ -157,16 +166,18 @@ describe("loop.contacts", function() {
});
it("should not show the gravatars promo box when the 'contacts.gravatars.promo' pref is set", function() {
navigator.mozLoop.getLoopPref = function(pref) {
sandbox.stub(navigator.mozLoop, "getLoopPref", function(pref) {
if (pref == "contacts.gravatars.promo") {
return false;
} else if (pref == "contacts.gravatars.show") {
return true;
}
return "";
};
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
@ -180,6 +191,7 @@ describe("loop.contacts", function() {
it("should hide the gravatars promo box when the 'use' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
@ -196,6 +208,7 @@ describe("loop.contacts", function() {
it("should should set the prefs correctly when the 'use' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
@ -211,6 +224,7 @@ describe("loop.contacts", function() {
it("should hide the gravatars promo box when the 'close' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
@ -225,6 +239,7 @@ describe("loop.contacts", function() {
it("should set prefs correctly when the 'close' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
@ -239,21 +254,77 @@ describe("loop.contacts", function() {
});
describe("ContactsList", function () {
var node;
beforeEach(function() {
navigator.mozLoop.calls = {
startDirectCall: sandbox.stub(),
clearCallInProgress: sandbox.stub()
};
navigator.mozLoop.contacts = {getAll: sandbox.stub()};
sandbox.stub(navigator.mozLoop.calls, "startDirectCall");
sandbox.stub(navigator.mozLoop.calls, "clearCallInProgress");
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
notifications: notifications,
startForm: function() {}
}));
describe("#RenderNoContacts", function() {
beforeEach(function() {
sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
cb(null, []);
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
node = listView.getDOMNode();
});
it("should not show a contacts title if no contacts", function() {
expect(node.querySelector(".contact-list-title")).to.eql(null);
sinon.assert.neverCalledWith(mozL10nGetSpy, "contact_list_title");
});
it("should show the no contacts view", function() {
expect(node.querySelector(".contact-list-empty")).to.not.eql(null);
});
it("should display the no contacts strings", function() {
sinon.assert.calledWithExactly(mozL10nGetSpy,
"no_contacts_message_heading");
sinon.assert.calledWithExactly(mozL10nGetSpy,
"no_contacts_import_or_add");
});
});
describe("#RenderWithContacts", function() {
beforeEach(function() {
sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
cb(null, [].concat(fakeContacts));
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
node = listView.getDOMNode();
});
it("should show a contacts title", function() {
expect(node.querySelector(".contact-list-title")).not.to.eql(null);
sinon.assert.calledWithExactly(mozL10nGetSpy, "contact_list_title");
});
});
describe("#handleContactAction", function() {
beforeEach(function() {
sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
cb(null, []);
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
node = listView.getDOMNode();
});
it("should call window.close when called with 'video-call' action",
function() {
listView.handleContactAction({}, "video-call");
@ -270,6 +341,19 @@ describe("loop.contacts", function() {
});
describe("#handleImportButtonClick", function() {
beforeEach(function() {
sandbox.stub(navigator.mozLoop.contacts, "getAll", function(cb) {
cb(null, []);
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: navigator.mozLoop,
notifications: notifications,
startForm: function() {}
}));
node = listView.getDOMNode();
});
it("should notify the end user from a succesful import", function() {
sandbox.stub(notifications, "successL10n");
navigator.mozLoop.startImport = function(opts, cb) {

View File

@ -13,6 +13,7 @@ describe("loop.panel", function() {
var sandbox, notifications;
var fakeXHR, fakeWindow, fakeMozLoop;
var requests = [];
var mozL10nGetSpy;
beforeEach(function() {
sandbox = sinon.sandbox.create();
@ -77,6 +78,7 @@ describe("loop.panel", function() {
};
document.mozL10n.initialize(navigator.mozLoop);
sandbox.stub(document.mozL10n, "get").returns("Fake title");
});
afterEach(function() {
@ -89,7 +91,6 @@ describe("loop.panel", function() {
beforeEach(function() {
sandbox.stub(React, "render");
sandbox.stub(document.mozL10n, "initialize");
sandbox.stub(document.mozL10n, "get").returns("Fake title");
});
it("should initalize L10n", function() {
@ -349,9 +350,9 @@ describe("loop.panel", function() {
function() {
var view = mountTestComponent();
expect(view.getDOMNode().querySelectorAll(".icon-signout"))
expect(view.getDOMNode().querySelectorAll(".entry-settings-signout"))
.to.have.length.of(0);
expect(view.getDOMNode().querySelectorAll(".icon-signin"))
expect(view.getDOMNode().querySelectorAll(".entry-settings-signin"))
.to.have.length.of(1);
});
@ -368,7 +369,7 @@ describe("loop.panel", function() {
var view = mountTestComponent();
TestUtils.Simulate.click(view.getDOMNode()
.querySelector(".icon-signin"));
.querySelector(".entry-settings-signin"));
sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
});
@ -379,10 +380,10 @@ describe("loop.panel", function() {
var view = mountTestComponent();
expect(view.getDOMNode().querySelectorAll(".icon-signout"))
.to.have.length.of(1);
expect(view.getDOMNode().querySelectorAll(".icon-signin"))
.to.have.length.of(0);
sinon.assert.calledWithExactly(document.mozL10n.get,
"settings_menu_item_signout");
sinon.assert.neverCalledWith(document.mozL10n.get,
"settings_menu_item_signin");
});
it("should show an account entry when user is authenticated", function() {
@ -390,8 +391,8 @@ describe("loop.panel", function() {
var view = mountTestComponent();
expect(view.getDOMNode().querySelectorAll(".icon-account"))
.to.have.length.of(1);
sinon.assert.calledWithExactly(document.mozL10n.get,
"settings_menu_item_settings");
});
it("should open the FxA settings when the account entry is clicked",
@ -401,7 +402,7 @@ describe("loop.panel", function() {
var view = mountTestComponent();
TestUtils.Simulate.click(view.getDOMNode()
.querySelector(".icon-account"));
.querySelector(".entry-settings-account"));
sinon.assert.calledOnce(navigator.mozLoop.openFxASettings);
});
@ -411,7 +412,7 @@ describe("loop.panel", function() {
var view = mountTestComponent();
TestUtils.Simulate.click(view.getDOMNode()
.querySelector(".icon-signout"));
.querySelector(".entry-settings-signout"));
sinon.assert.calledOnce(navigator.mozLoop.logOutFromFxA);
});
@ -442,7 +443,7 @@ describe("loop.panel", function() {
view = mountTestComponent();
TestUtils.Simulate
.click(view.getDOMNode().querySelector(".icon-help"));
.click(view.getDOMNode().querySelector(".entry-settings-help"));
sinon.assert.calledOnce(fakeMozLoop.openURL);
sinon.assert.calledWithExactly(fakeMozLoop.openURL, supportUrl);
@ -452,7 +453,7 @@ describe("loop.panel", function() {
view = mountTestComponent();
TestUtils.Simulate
.click(view.getDOMNode().querySelector(".icon-help"));
.click(view.getDOMNode().querySelector(".entry-settings-help"));
sinon.assert.calledOnce(fakeWindow.close);
});
@ -837,7 +838,7 @@ describe("loop.panel", function() {
TestUtils.Simulate.click(view.getDOMNode().querySelector(".new-room-button"));
sinon.assert.calledWith(dispatch, new sharedActions.CreateRoom({
nameTemplate: "fakeText",
nameTemplate: "Fake title",
roomOwner: fakeEmail
}));
});
@ -868,7 +869,7 @@ describe("loop.panel", function() {
TestUtils.Simulate.click(node.querySelector(".new-room-button"));
sinon.assert.calledWith(dispatch, new sharedActions.CreateRoom({
nameTemplate: "fakeText",
nameTemplate: "Fake title",
roomOwner: fakeEmail,
urls: [{
location: "http://invalid.com",

View File

@ -140,8 +140,8 @@ var fakeContacts = [{
releaseCallData: function() {},
copyString: function() {},
getUserAvatar: function(emailAddress) {
return "http://www.gravatar.com/avatar/" + (Math.ceil(Math.random() * 3) === 2 ?
"0a996f0fe2727ef1668bdb11897e4459" : "foo") + ".jpg?default=blank&s=40";
var avatarUrl = "http://www.gravatar.com/avatar/0a996f0fe2727ef1668bdb11897e4459.jpg?default=blank&s=40";
return Math.ceil(Math.random() * 3) === 2 ? avatarUrl : null;
},
getSelectedTabMetadata: function(callback) {
callback({

View File

@ -58,6 +58,8 @@ body {
}
.showcase > section .comp {
/* contain absolute positioned elements such as dropdowns */
position: relative;
margin: 0 auto; /* width is usually set programmatically */
}
@ -167,7 +169,7 @@ body {
}
/* Force dropdown menus to display. */
.force-menu-show * {
.force-menu-show .icons,
.force-menu-show .dropdown-menu {
display: inline-block !important;
}

View File

@ -2,7 +2,7 @@
* 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/. */
/* global Frame:false uncaughtError:true */
/* global Frame:false uncaughtError:true fakeContacts:true */
(function() {
"use strict";
@ -18,6 +18,8 @@
var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
var ContactDropdown = loop.contacts.ContactDropdown;
var ContactDetail = loop.contacts.ContactDetail;
// 1.2. Conversation Window
var AcceptCallView = loop.conversationViews.AcceptCallView;
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
@ -451,6 +453,17 @@
uid: "0354b278a381d3cb408bb46ffc01266"
};
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
mozLoopNoContacts.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
mozLoopNoContacts.contacts.getAll = function(callback) {
callback(null, []);
};
var mockContact = {
name: ["Mr Smith"],
email: [{
@ -739,6 +752,14 @@
roomStore: roomStore,
selectedTab: "contacts"})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Contact list tab (no contacts)"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mozLoopNoContacts,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts"})
),
React.createElement(Example, {dashed: true, style: {width: "332px"}, summary: "Error Notification"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
@ -784,6 +805,30 @@
)
),
React.createElement(Section, {name: "ContactDetail"},
React.createElement(Example, {cssClass: "force-menu-show", dashed: true,
style: {width: "300px", height: "272px"},
summary: "ContactDetail"},
React.createElement(ContactDetail, {contact: fakeContacts[0],
handleContactAction: function() {}})
)
),
React.createElement(Section, {name: "ContactDropdown"},
React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"},
summary: "ContactDropdown not blocked can edit"},
React.createElement(ContactDropdown, {blocked: false,
canEdit: true,
handleAction: function () {}})
),
React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"},
summary: "ContactDropdown blocked can't edit"},
React.createElement(ContactDropdown, {blocked: true,
canEdit: false,
handleAction: function () {}})
)
),
React.createElement(Section, {name: "AcceptCallView"},
React.createElement(Example, {dashed: true, style: {width: "300px", height: "272px"},
summary: "Default / incoming video call"},

View File

@ -2,7 +2,7 @@
* 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/. */
/* global Frame:false uncaughtError:true */
/* global Frame:false uncaughtError:true fakeContacts:true */
(function() {
"use strict";
@ -18,6 +18,8 @@
var AvailabilityDropdown = loop.panel.AvailabilityDropdown;
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
var ContactDropdown = loop.contacts.ContactDropdown;
var ContactDetail = loop.contacts.ContactDetail;
// 1.2. Conversation Window
var AcceptCallView = loop.conversationViews.AcceptCallView;
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
@ -451,6 +453,17 @@
uid: "0354b278a381d3cb408bb46ffc01266"
};
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
mozLoopNoContacts.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
mozLoopNoContacts.contacts.getAll = function(callback) {
callback(null, []);
};
var mockContact = {
name: ["Mr Smith"],
email: [{
@ -739,6 +752,14 @@
roomStore={roomStore}
selectedTab="contacts" />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Contact list tab (no contacts)">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mozLoopNoContacts}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts" />
</Example>
<Example dashed={true} style={{width: "332px"}} summary="Error Notification">
<PanelView client={mockClient}
dispatcher={dispatcher}
@ -784,6 +805,30 @@
</Example>
</Section>
<Section name="ContactDetail">
<Example cssClass="force-menu-show" dashed={true}
style={{width: "300px", height: "272px"}}
summary="ContactDetail">
<ContactDetail contact={fakeContacts[0]}
handleContactAction={function() {}} />
</Example>
</Section>
<Section name="ContactDropdown">
<Example dashed={true} style={{width: "300px", height: "272px"}}
summary="ContactDropdown not blocked can edit">
<ContactDropdown blocked={false}
canEdit={true}
handleAction={function () {}} />
</Example>
<Example dashed={true} style={{width: "300px", height: "272px"}}
summary="ContactDropdown blocked can't edit">
<ContactDropdown blocked={true}
canEdit={false}
handleAction={function () {}} />
</Example>
</Section>
<Section name="AcceptCallView">
<Example dashed={true} style={{width: "300px", height: "272px"}}
summary="Default / incoming video call">

View File

@ -360,6 +360,20 @@ const Formatters = {
}
},
GCFields: function (marker) {
let fields = Object.create(null);
let cause = marker.cause;
let label = L10N.getStr(`marker.gcreason.label.${cause}`) || cause;
fields[L10N.getStr("marker.field.causeName")] = label;
if ("nonincrementalReason" in marker) {
fields[L10N.getStr("marker.field.nonIncrementalCause")] = marker.nonincrementalReason;
}
return fields;
},
DOMEventFields: function (marker) {
let fields = Object.create(null);
if ("type" in marker) {

View File

@ -101,10 +101,7 @@ const TIMELINE_BLUEPRINT = {
group: 1,
colorName: "graphs-red",
label: Formatters.GCLabel,
fields: [
{ property: "causeName", label: L10N.getStr("marker.field.causeName") },
{ property: "nonincrementalReason", label: L10N.getStr("marker.field.nonIncrementalCause") }
],
fields: Formatters.GCFields,
},
"nsCycleCollector::Collect": {
group: 1,

View File

@ -39,6 +39,12 @@ add_task(function () {
equal(fields[1].label, "Phase:", "getMarkerFields() correctly returns fields via function (3)");
equal(fields[1].value, "Target", "getMarkerFields() correctly returns fields via function (4)");
fields = Utils.getMarkerFields({ name: "GarbageCollection", cause: "ALLOC_TRIGGER" });
equal(fields[0].value, "Too Many Allocations", "Uses L10N for GC reasons");
fields = Utils.getMarkerFields({ name: "GarbageCollection", cause: "NOT_A_GC_REASON" });
equal(fields[0].value, "NOT_A_GC_REASON", "Defaults to enum for GC reasons when not L10N'd");
equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "(Gecko)",
"Correctly obfuscates JS markers when platform data is off.");
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);

View File

@ -395,6 +395,11 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY urlbar.accesskey "d">
<!ENTITY urlbar.switchToTab.label "Switch to tab:">
<!ENTITY urlbar.searchSuggestionsNotification.question "Would you like to improve your search experience with suggestions?">
<!ENTITY urlbar.searchSuggestionsNotification.learnMore "Learn more…">
<!ENTITY urlbar.searchSuggestionsNotification.disable "No">
<!ENTITY urlbar.searchSuggestionsNotification.enable "Yes">
<!--
Comment duplicated from browser-sets.inc:

View File

@ -128,6 +128,12 @@ import_contacts_success_message={{total}} contact was successfully imported.;{{t
## LOCALIZATION NOTE(sync_contacts_button): This button is displayed in place of
## importing_contacts_button once contacts have been imported once.
sync_contacts_button=Sync Contacts
## LOCALIZATION NOTE(no_contacts_message_heading): Title shown when user has no
## contacts in his address book
no_contacts_message_heading=No contacts yet
## LOCALIZATION NOTE(no_contacts_import_or_add): Subheading inviting the user
## to add people to his contact list
no_contacts_import_or_add=Import or add someone
## LOCALIZATION NOTE(import_failed_description simple): Displayed when an import of
## contacts fails. This is displayed in the error field.
@ -138,6 +144,9 @@ import_failed_support_button=Help
## LOCALIZATION NOTE(remove_contact_menu_button2): Displayed in the contact list in
## a pop-up menu next to the contact's name.
remove_contact_menu_button2=Remove Contact…
## LOCALIZATION NOTE(remove_contact_title): Displayed in the contact list in
## a pop-up menu next to the contact's name.
remove_contact_menu_button3=Remove Contact
## LOCALIZATION NOTE(confirm_delete_contact_alert): This is an alert that is displayed
## to confirm deletion of a contact.
confirm_delete_contact_alert=Are you sure you want to delete this contact?
@ -145,6 +154,9 @@ confirm_delete_contact_alert=Are you sure you want to delete this contact?
## These are displayed on the alert with confirm_delete_contact_alert
confirm_delete_contact_remove_button=Remove Contact
confirm_delete_contact_cancel_button=Cancel
## LOCALIZATION NOTE(contact_list_title): This is in uppercase in English for
## emphasis, please do what is appropriate for specific locales.
contact_list_title=MY CONTACTS
## LOCALIZATION NOTE(block_contact_menu_button): Displayed in the contact list in
## a pop-up menu next to the contact's name, used to block a contact from calling

View File

@ -28,6 +28,9 @@
<!ENTITY locbar.searches.label "Related searches from the default search engine">
<!ENTITY locbar.searches.accesskey "d">
<!ENTITY suggestionSettings.label "Change preferences for search engine suggestions…">
<!ENTITY suggestionSettings.accesskey "g">
<!ENTITY acceptCookies.label "Accept cookies from sites">
<!ENTITY acceptCookies.accesskey "A">

View File

@ -9,6 +9,9 @@
<!ENTITY provideSearchSuggestions.label "Provide search suggestions">
<!ENTITY provideSearchSuggestions.accesskey "s">
<!ENTITY showURLBarSuggestions.label "Show search suggestions in location bar results">
<!ENTITY showURLBarSuggestions.accesskey "l">
<!ENTITY redirectWindowsSearch.label "Use this search engine for searches from Windows">
<!ENTITY redirectWindowsSearch.accesskey "W">

View File

@ -962,9 +962,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
color: GrayText;
}
#PopupAutoCompleteRichResult > richlistbox {
transition: height 100ms;
}
%include ../shared/urlbarSearchSuggestionsNotification.inc.css
#search-container {
min-width: calc(54px + 11ch);
@ -1136,7 +1134,9 @@ richlistitem[type~="action"][actiontype="searchengine"][selected="true"] > .ac-t
height: 16px;
}
.ac-comment {
.ac-comment,
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description,
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
font-size: 1.05em;
}

View File

@ -96,6 +96,7 @@ browser.jar:
skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg)
skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
skin/classic/browser/info.svg (../shared/info.svg)
skin/classic/browser/Security-broken.png
skin/classic/browser/setDesktopBackground.css
skin/classic/browser/slowStartup-16.png

View File

@ -1792,14 +1792,12 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
color: GrayText;
}
#PopupAutoCompleteRichResult > richlistbox {
transition: height 100ms;
}
#PopupAutoCompleteRichResult {
margin-top: 2px;
}
%include ../shared/urlbarSearchSuggestionsNotification.inc.css
/* ----- AUTOCOMPLETE ----- */
#treecolAutoCompleteImage {
@ -1845,8 +1843,8 @@ richlistitem[selected="true"][current="true"] > .ac-title-box > .ac-result-type-
.ac-url-text,
.ac-action-text {
color: -moz-nativehyperlinktext;
font: message-box;
color: -moz-nativehyperlinktext;
}
richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-icon {

View File

@ -127,6 +127,7 @@ browser.jar:
skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg)
skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
skin/classic/browser/info.svg (../shared/info.svg)
skin/classic/browser/slowStartup-16.png
skin/classic/browser/theme-switcher-icon.png (../shared/theme-switcher-icon.png)
skin/classic/browser/theme-switcher-icon@2x.png (../shared/theme-switcher-icon@2x.png)

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- 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/. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<circle fill="#00a1f2" cx="8" cy="8" r="8" />
<circle fill="#fff" cx="8" cy="4" r="1.25" />
<rect x="7" y="7" width="2" height="6" rx="1" ry="1" fill="#fff" />
</svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@ -0,0 +1,55 @@
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] {
border-bottom: 1px solid hsla(210, 4%, 10%, 0.14);
background-color: hsla(210, 4%, 10%, 0.07);
padding: 6px 0;
-moz-padding-start: 44px;
-moz-padding-end: 6px;
background-image: url("chrome://browser/skin/info.svg");
background-clip: padding-box;
background-position: 20px center;
background-repeat: no-repeat;
background-size: 16px 16px;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"]:-moz-locale-dir(rtl) {
background-position: right 20px center;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description {
margin: 0;
padding: 0;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description > label.text-link {
-moz-margin-start: 0;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
-moz-appearance: none;
-moz-user-focus: ignore;
min-width: 80px;
border-radius: 3px;
padding: 4px 16px;
margin: 0;
-moz-margin-start: 10px;
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"] {
color: hsl(210, 0%, 38%);
background-color: hsl(210, 0%, 88%);
border: 1px solid hsl(210, 0%, 82%);
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-disable"]:hover {
background-color: hsl(210, 0%, 84%);
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"] {
color: white;
background-color: hsl(93, 82%, 44%);
border: 1px solid hsl(93, 82%, 44%);
}
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button[anonid="search-suggestions-notification-enable"]:hover {
background-color: hsl(93, 82%, 40%);
}

View File

@ -1429,9 +1429,7 @@ html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
color: GrayText;
}
#PopupAutoCompleteRichResult > richlistbox {
transition: height 100ms;
}
%include ../shared/urlbarSearchSuggestionsNotification.inc.css
#search-container {
min-width: calc(54px + 11ch);
@ -1564,17 +1562,20 @@ richlistitem[type~="action"][actiontype="searchengine"] > .ac-title-box > .ac-si
height: 16px;
}
.ac-comment {
.ac-comment,
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > description,
#PopupAutoCompleteRichResult > hbox[anonid="search-suggestions-notification"] > button {
font-size: 1.06em;
}
.ac-extra > .ac-comment {
.ac-extra > .ac-comment,
.ac-url-text,
.ac-action-text {
font-size: 1em;
}
.ac-url-text,
.ac-action-text {
font-size: 1em;
color: -moz-nativehyperlinktext;
}

View File

@ -124,6 +124,7 @@ browser.jar:
skin/classic/browser/search-history-icon.svg (../shared/search/history-icon.svg)
skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
skin/classic/browser/info.svg (../shared/info.svg)
skin/classic/browser/setDesktopBackground.css
skin/classic/browser/slowStartup-16.png
skin/classic/browser/theme-switcher-icon.png (../shared/theme-switcher-icon.png)

View File

@ -13,13 +13,14 @@ import android.text.TextUtils;
public class SiteIdentity {
private final String LOGTAG = "GeckoSiteIdentity";
private SecurityMode mSecurityMode;
private MixedMode mMixedMode;
private boolean mSecure;
private MixedMode mMixedModeActive;
private MixedMode mMixedModeDisplay;
private TrackingMode mTrackingMode;
private String mHost;
private String mOwner;
private String mSupplemental;
private String mVerifier;
private boolean mEncrypted;
private String mOrigin;
// The order of the items here relate to image levels in
@ -59,8 +60,8 @@ public class SiteIdentity {
// site_security_level.xml
public enum MixedMode {
UNKNOWN("unknown"),
MIXED_CONTENT_BLOCKED("mixed_content_blocked"),
MIXED_CONTENT_LOADED("mixed_content_loaded");
MIXED_CONTENT_BLOCKED("blocked"),
MIXED_CONTENT_LOADED("loaded");
private final String mId;
@ -132,12 +133,13 @@ public class SiteIdentity {
mOwner = null;
mSupplemental = null;
mVerifier = null;
mEncrypted = false;
mSecure = false;
}
public void reset() {
resetIdentity();
mMixedMode = MixedMode.UNKNOWN;
mMixedModeActive = MixedMode.UNKNOWN;
mMixedModeDisplay = MixedMode.UNKNOWN;
mTrackingMode = TrackingMode.UNKNOWN;
}
@ -151,9 +153,15 @@ public class SiteIdentity {
JSONObject mode = identityData.getJSONObject("mode");
try {
mMixedMode = MixedMode.fromString(mode.getString("mixed"));
mMixedModeDisplay = MixedMode.fromString(mode.getString("mixed_display"));
} catch (Exception e) {
mMixedMode = MixedMode.UNKNOWN;
mMixedModeDisplay = MixedMode.UNKNOWN;
}
try {
mMixedModeActive = MixedMode.fromString(mode.getString("mixed_active"));
} catch (Exception e) {
mMixedModeActive = MixedMode.UNKNOWN;
}
try {
@ -175,7 +183,7 @@ public class SiteIdentity {
mOwner = identityData.optString("owner", null);
mSupplemental = identityData.optString("supplemental", null);
mVerifier = identityData.getString("verifier");
mEncrypted = identityData.optBoolean("encrypted", false);
mSecure = identityData.optBoolean("secure", false);
} catch (Exception e) {
resetIdentity();
}
@ -208,12 +216,16 @@ public class SiteIdentity {
return mVerifier;
}
public boolean getEncrypted() {
return mEncrypted;
public boolean isSecure() {
return mSecure;
}
public MixedMode getMixedMode() {
return mMixedMode;
public MixedMode getMixedModeActive() {
return mMixedModeActive;
}
public MixedMode getMixedModeDisplay() {
return mMixedModeDisplay;
}
public TrackingMode getTrackingMode() {

View File

@ -16,6 +16,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.background.fxa.SkewHandler;
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
@ -43,6 +44,7 @@ import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import org.mozilla.gecko.sync.telemetry.TelemetryContract;
import org.mozilla.gecko.tokenserver.TokenServerClient;
import org.mozilla.gecko.tokenserver.TokenServerClientDelegate;
import org.mozilla.gecko.tokenserver.TokenServerException;
@ -88,12 +90,14 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
public void handleSuccess() {
Logger.info(LOG_TAG, "Sync succeeded.");
super.handleSuccess();
TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_COMPLETED, 1);
}
@Override
public void handleError(Exception e) {
Logger.error(LOG_TAG, "Got exception syncing.", e);
super.handleError(e);
TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_FAILED, 1);
}
@Override
@ -403,6 +407,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
(this.lastSyncRealtimeMillis + MINIMUM_SYNC_DELAY_MILLIS) > SystemClock.elapsedRealtime()) {
Logger.info(LOG_TAG, "Not syncing FxAccount " + Utils.obfuscateEmail(account.name) +
": minimum interval not met.");
TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_FAILED_BACKOFF, 1);
return;
}
@ -481,6 +486,8 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
return;
}
TelemetryWrapper.addToHistogram(TelemetryContract.SYNC_STARTED, 1);
final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
stateMachine.advance(state, StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
@Override

View File

@ -8,7 +8,9 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
import org.mozilla.gecko.home.PanelLayout.PanelView;
import org.mozilla.gecko.home.RecyclerViewItemClickListener.OnClickListener;
import org.mozilla.gecko.widget.RecyclerViewClickSupport;
import org.mozilla.gecko.widget.RecyclerViewClickSupport.OnItemClickListener;
import org.mozilla.gecko.widget.RecyclerViewClickSupport.OnItemLongClickListener;
import android.annotation.SuppressLint;
import android.content.Context;
@ -22,7 +24,8 @@ import android.view.View;
* RecyclerView implementation for grid home panels.
*/
@SuppressLint("ViewConstructor") // View is only created from code
public class PanelRecyclerView extends RecyclerView implements DatasetBacked, PanelView, OnClickListener {
public class PanelRecyclerView extends RecyclerView
implements DatasetBacked, PanelView, OnItemClickListener, OnItemLongClickListener {
private final PanelRecyclerViewAdapter adapter;
private final GridLayoutManager layoutManager;
private final PanelViewItemHandler itemHandler;
@ -69,7 +72,9 @@ public class PanelRecyclerView extends RecyclerView implements DatasetBacked, Pa
setPadding(outerSpacing, outerSpacing, outerSpacing, outerSpacing);
setClipToPadding(false);
addOnItemTouchListener(new RecyclerViewItemClickListener(context, this, this));
RecyclerViewClickSupport.addTo(this)
.setOnItemClickListener(this)
.setOnItemLongClickListener(this);
}
@Override
@ -123,7 +128,7 @@ public class PanelRecyclerView extends RecyclerView implements DatasetBacked, Pa
}
@Override
public void onClick(View view, int position) {
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
if (viewConfig.hasHeaderConfig()) {
if (position == 0) {
itemOpenListener.onItemOpen(viewConfig.getHeaderConfig().getUrl(), null);
@ -137,12 +142,12 @@ public class PanelRecyclerView extends RecyclerView implements DatasetBacked, Pa
}
@Override
public void onLongClick(View view, int position) {
public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
Cursor cursor = adapter.getCursor();
cursor.moveToPosition(position);
contextMenuInfo = contextMenuInfoFactory.makeInfoForCursor(view, position, -1, cursor);
showContextMenuForChild(PanelRecyclerView.this);
contextMenuInfo = contextMenuInfoFactory.makeInfoForCursor(recyclerView, position, -1, cursor);
return showContextMenuForChild(PanelRecyclerView.this);
}
private class PanelSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {

View File

@ -1,87 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
/**
* RecyclerView.OnItemTouchListener implementation that will notify an OnClickListener about clicks and long clicks
* on items displayed by the RecyclerView.
*/
public class RecyclerViewItemClickListener implements RecyclerView.OnItemTouchListener,
GestureDetector.OnGestureListener {
public interface OnClickListener {
void onClick(View view, int position);
void onLongClick(View view, int position);
}
private final OnClickListener clickListener;
private final GestureDetector gestureDetector;
private final RecyclerView recyclerView;
public RecyclerViewItemClickListener(Context context, RecyclerView recyclerView, OnClickListener clickListener) {
this.clickListener = clickListener;
this.gestureDetector = new GestureDetector(context, this);
this.recyclerView = recyclerView;
}
@Override
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
@Override
public boolean onSingleTapUp(MotionEvent event) {
View childView = recyclerView.findChildViewUnder(event.getX(), event.getY());
if (childView != null) {
final int position = recyclerView.getChildAdapterPosition(childView);
clickListener.onClick(childView, position);
}
return true;
}
@Override
public void onLongPress(MotionEvent event) {
View childView = recyclerView.findChildViewUnder(event.getX(), event.getY());
if (childView != null) {
final int position = recyclerView.getChildAdapterPosition(childView);
childView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
clickListener.onLongClick(childView, position);
}
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {}
@Override
public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {}
}

View File

@ -17,11 +17,12 @@ import android.view.View;
import org.mozilla.gecko.R;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.widget.RecyclerViewClickSupport;
import java.util.List;
public class SearchEngineBar extends RecyclerView
implements RecyclerViewItemClickListener.OnClickListener {
implements RecyclerViewClickSupport.OnItemClickListener {
private static final String LOGTAG = SearchEngineBar.class.getSimpleName();
private static final float ICON_CONTAINER_MIN_WIDTH_DP = 72;
@ -66,7 +67,9 @@ public class SearchEngineBar extends RecyclerView
setAdapter(mAdapter);
setLayoutManager(mLayoutManager);
addOnItemTouchListener(new RecyclerViewItemClickListener(context, this, this));
RecyclerViewClickSupport.addTo(this)
.setOnItemClickListener(this);
}
public void setSearchEngines(List<SearchEngine> searchEngines) {
@ -114,7 +117,7 @@ public class SearchEngineBar extends RecyclerView
}
@Override
public void onClick(View view, int position) {
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
if (mOnSearchBarClickListener == null) {
throw new IllegalStateException(
OnSearchBarClickListener.class.getSimpleName() + " is not initializer."
@ -129,11 +132,6 @@ public class SearchEngineBar extends RecyclerView
mOnSearchBarClickListener.onSearchBarClickListener(searchEngine);
}
@Override
public void onLongClick(View view, int position) {
// do nothing
}
/**
* We manually add the override for getAdapter because we see this method getting stripped
* out during compile time by aggressive proguard rules.

View File

@ -564,6 +564,8 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!-- Mixed content notifications in site identity popup -->
<!ENTITY mixed_content_blocked_all "&brandShortName; has blocked insecure elements on this page.">
<!ENTITY mixed_content_blocked_some "&brandShortName; has blocked some insecure elements on this page.">
<!ENTITY mixed_content_display_loaded "This page has some insecure elements.">
<!ENTITY mixed_content_protection_disabled "You have disabled protection from insecure elements.">
<!ENTITY loaded_mixed_content_message "This page is displaying content that isn\'t secure.">

View File

@ -342,7 +342,6 @@ gbjar.sources += [
'home/ReadingListPanel.java',
'home/ReadingListRow.java',
'home/RecentTabsPanel.java',
'home/RecyclerViewItemClickListener.java',
'home/RemoteTabsBaseFragment.java',
'home/RemoteTabsExpandableListFragment.java',
'home/RemoteTabsExpandableListState.java',
@ -539,6 +538,7 @@ gbjar.sources += [
'widget/GeckoViewFlipper.java',
'widget/IconTabWidget.java',
'widget/LoginDoorHanger.java',
'widget/RecyclerViewClickSupport.java',
'widget/ResizablePathDrawable.java',
'widget/RoundedCornerLayout.java',
'widget/SiteLogins.java',

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

View File

@ -7,10 +7,10 @@
<item android:maxLevel="0" android:drawable="@drawable/site_security_unknown"/>
<item android:maxLevel="1" android:drawable="@drawable/lock_secure"/>
<!-- Bug 1185600 will handle Passive Mixed Content and resolve this icon duplication -->
<item android:maxLevel="2" android:drawable="@drawable/lock_secure"/>
<item android:maxLevel="3" android:drawable="@drawable/shield_enabled"/>
<item android:maxLevel="4" android:drawable="@drawable/shield_disabled"/>
<item android:maxLevel="5" android:drawable="@drawable/lock_disabled"/>
<item android:maxLevel="3" android:drawable="@drawable/warning_minor"/>
<item android:maxLevel="4" android:drawable="@drawable/lock_disabled"/>
<item android:maxLevel="5" android:drawable="@drawable/shield_enabled"/>
<item android:maxLevel="6" android:drawable="@drawable/shield_disabled"/>
</level-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

View File

@ -7,10 +7,10 @@
<item android:maxLevel="0" android:drawable="@android:color/transparent"/>
<item android:maxLevel="1" android:drawable="@drawable/lock_secure"/>
<!-- Bug 1185600 will handle Passive Mixed Content and resolve this icon duplication -->
<item android:maxLevel="2" android:drawable="@drawable/lock_secure"/>
<item android:maxLevel="3" android:drawable="@drawable/shield_enabled"/>
<item android:maxLevel="4" android:drawable="@drawable/shield_disabled"/>
<item android:maxLevel="5" android:drawable="@drawable/lock_disabled"/>
<item android:maxLevel="3" android:drawable="@drawable/warning_minor"/>
<item android:maxLevel="4" android:drawable="@drawable/lock_disabled"/>
<item android:maxLevel="5" android:drawable="@drawable/shield_enabled"/>
<item android:maxLevel="6" android:drawable="@drawable/shield_disabled"/>
</level-list>

View File

@ -9,5 +9,6 @@
<item type="id" name="guestNotification"/>
<item type="id" name="original_height"/>
<item type="id" name="menu_items"/>
<item type="id" name="recycler_view_click_support" />
</resources>

View File

@ -469,6 +469,8 @@
<string name="identity_connection_insecure">&identity_connection_insecure;</string>
<string name="mixed_content_blocked_all">&mixed_content_blocked_all;</string>
<string name="mixed_content_blocked_some">&mixed_content_blocked_some;</string>
<string name="mixed_content_display_loaded">&mixed_content_display_loaded;</string>
<string name="mixed_content_protection_disabled">&mixed_content_protection_disabled;</string>
<string name="doorhanger_tracking_title">&doorhanger_tracking_title;</string>

View File

@ -45,4 +45,12 @@ public class TelemetryContract {
* Firefox Account credentials.
*/
public static final String SYNC11_MIGRATIONS_COMPLETED = "FENNEC_SYNC11_MIGRATIONS_COMPLETED";
public static final String SYNC_STARTED = "FENNEC_SYNC_NUMBER_OF_SYNCS_STARTED";
public static final String SYNC_COMPLETED = "FENNEC_SYNC_NUMBER_OF_SYNCS_COMPLETED";
public static final String SYNC_FAILED = "FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED";
public static final String SYNC_FAILED_BACKOFF = "FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED_BACKOFF";
}

View File

@ -296,18 +296,60 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
mIdentityKnownContainer.setVisibility(identityInfoVisibility);
}
/**
* Update the Site Identity content to reflect connection state.
*
* The connection state should reflect the combination of:
* a) Connection encryption
* b) Mixed Content state (Active/Display Mixed content, loaded, blocked, none, etc)
* and update the icons and strings to inform the user of that state.
*
* @param siteIdentity SiteIdentity information about the connection.
*/
private void updateConnectionState(final SiteIdentity siteIdentity) {
if (siteIdentity.getEncrypted()) {
if (!siteIdentity.isSecure()) {
if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_LOADED) {
// Active Mixed Content loaded because user has disabled blocking.
mIcon.setImageResource(R.drawable.lock_disabled);
clearSecurityStateIcon();
mMixedContentActivity.setVisibility(View.VISIBLE);
mMixedContentActivity.setText(R.string.mixed_content_protection_disabled);
mLink.setVisibility(View.VISIBLE);
} else if (siteIdentity.getMixedModeDisplay() == MixedMode.MIXED_CONTENT_LOADED) {
// Passive Mixed Content loaded.
mIcon.setImageResource(R.drawable.lock_inactive);
setSecurityStateIcon(R.drawable.warning_major, 1);
mMixedContentActivity.setVisibility(View.VISIBLE);
if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_BLOCKED) {
mMixedContentActivity.setText(R.string.mixed_content_blocked_some);
} else {
mMixedContentActivity.setText(R.string.mixed_content_display_loaded);
}
mLink.setVisibility(View.VISIBLE);
} else {
// Unencrypted connection with no mixed content.
mIcon.setImageResource(R.drawable.globe_light);
clearSecurityStateIcon();
mMixedContentActivity.setVisibility(View.GONE);
mLink.setVisibility(View.GONE);
}
mSecurityState.setText(R.string.identity_connection_insecure);
mSecurityState.setTextColor(mResources.getColor(R.color.placeholder_active_grey));
} else {
// Connection is secure.
mIcon.setImageResource(R.drawable.lock_secure);
setSecurityStateIcon(R.drawable.img_check, 2);
mSecurityState.setTextColor(mResources.getColor(R.color.affirmative_green));
final Drawable stateIcon = ContextCompat.getDrawable(mContext, R.drawable.img_check);
stateIcon.setBounds(0, 0, stateIcon.getIntrinsicWidth()/2, stateIcon.getIntrinsicHeight()/2);
mSecurityState.setCompoundDrawables(stateIcon, null, null, null);
mSecurityState.setCompoundDrawablePadding((int) mResources.getDimension(R.dimen.doorhanger_drawable_padding));
mSecurityState.setText(R.string.identity_connection_secure);
if (siteIdentity.getMixedMode() == MixedMode.MIXED_CONTENT_BLOCKED) {
// Mixed content has been blocked, if present.
if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_BLOCKED ||
siteIdentity.getMixedModeDisplay() == MixedMode.MIXED_CONTENT_BLOCKED) {
mMixedContentActivity.setVisibility(View.VISIBLE);
mMixedContentActivity.setText(R.string.mixed_content_blocked_all);
mLink.setVisibility(View.VISIBLE);
@ -315,25 +357,20 @@ public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListen
mMixedContentActivity.setVisibility(View.GONE);
mLink.setVisibility(View.GONE);
}
} else {
if (siteIdentity.getMixedMode() == MixedMode.MIXED_CONTENT_LOADED) {
mIcon.setImageResource(R.drawable.lock_disabled);
mMixedContentActivity.setVisibility(View.VISIBLE);
mMixedContentActivity.setText(R.string.mixed_content_protection_disabled);
mLink.setVisibility(View.VISIBLE);
} else {
mIcon.setImageResource(R.drawable.globe_light);
mMixedContentActivity.setVisibility(View.GONE);
mLink.setVisibility(View.GONE);
}
mSecurityState.setText(R.string.identity_connection_insecure);
mSecurityState.setTextColor(mResources.getColor(R.color.placeholder_active_grey));
mSecurityState.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
mSecurityState.setCompoundDrawablePadding(0);
}
}
private void clearSecurityStateIcon() {
mSecurityState.setCompoundDrawablePadding(0);
mSecurityState.setCompoundDrawables(null, null, null, null);
}
private void setSecurityStateIcon(int resource, int factor) {
final Drawable stateIcon = ContextCompat.getDrawable(mContext, resource);
stateIcon.setBounds(0, 0, stateIcon.getIntrinsicWidth()/factor, stateIcon.getIntrinsicHeight()/factor);
mSecurityState.setCompoundDrawables(stateIcon, null, null, null);
mSecurityState.setCompoundDrawablePadding((int) mResources.getDimension(R.dimen.doorhanger_drawable_padding));
}
private void updateIdentityInformation(final SiteIdentity siteIdentity) {
String owner = siteIdentity.getOwner();
if (owner == null) {

View File

@ -125,9 +125,12 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
private final SiteIdentityPopup mSiteIdentityPopup;
private int mSecurityImageLevel;
private final int LEVEL_SHIELD_ENABLED = 3;
private final int LEVEL_SHIELD_DISABLED = 4;
private final int LEVEL_LOCK_DISABLED = 5;
// Levels for displaying Mixed Content state icons.
private final int LEVEL_WARNING_MINOR = 3;
private final int LEVEL_LOCK_DISABLED = 4;
// Levels for displaying Tracking Protection state icons.
private final int LEVEL_SHIELD_ENABLED = 5;
private final int LEVEL_SHIELD_DISABLED = 6;
private PropertyAnimator mForwardAnim;
@ -419,15 +422,18 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
mSiteIdentityPopup.setSiteIdentity(siteIdentity);
final SecurityMode securityMode;
final MixedMode mixedMode;
final MixedMode activeMixedMode;
final MixedMode displayMixedMode;
final TrackingMode trackingMode;
if (siteIdentity == null) {
securityMode = SecurityMode.UNKNOWN;
mixedMode = MixedMode.UNKNOWN;
activeMixedMode = MixedMode.UNKNOWN;
displayMixedMode = MixedMode.UNKNOWN;
trackingMode = TrackingMode.UNKNOWN;
} else {
securityMode = siteIdentity.getSecurityMode();
mixedMode = siteIdentity.getMixedMode();
activeMixedMode = siteIdentity.getMixedModeActive();
displayMixedMode = siteIdentity.getMixedModeDisplay();
trackingMode = siteIdentity.getTrackingMode();
}
@ -440,8 +446,10 @@ public class ToolbarDisplayLayout extends ThemedLinearLayout
imageLevel = LEVEL_SHIELD_DISABLED;
} else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
imageLevel = LEVEL_SHIELD_ENABLED;
} else if (mixedMode == MixedMode.MIXED_CONTENT_LOADED) {
} else if (activeMixedMode == MixedMode.MIXED_CONTENT_LOADED) {
imageLevel = LEVEL_LOCK_DISABLED;
} else if (displayMixedMode == MixedMode.MIXED_CONTENT_LOADED) {
imageLevel = LEVEL_WARNING_MINOR;
}
if (mSecurityImageLevel != imageLevel) {

View File

@ -0,0 +1,105 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.widget;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import org.mozilla.gecko.R;
/**
* {@link RecyclerViewClickSupport} implementation that will notify an OnClickListener about clicks and long clicks
* on items displayed by the RecyclerView.
* @see <a href="http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/">littlerobots.nl</a>
*/
public class RecyclerViewClickSupport {
private final RecyclerView mRecyclerView;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
}
};
private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
return false;
}
};
private RecyclerView.OnChildAttachStateChangeListener mAttachListener
= new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(View view) {
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener);
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener);
}
}
@Override
public void onChildViewDetachedFromWindow(View view) {
}
};
private RecyclerViewClickSupport(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mRecyclerView.setTag(R.id.recycler_view_click_support, this);
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
}
public static RecyclerViewClickSupport addTo(RecyclerView view) {
RecyclerViewClickSupport support = (RecyclerViewClickSupport) view.getTag(R.id.recycler_view_click_support);
if (support == null) {
support = new RecyclerViewClickSupport(view);
}
return support;
}
public static RecyclerViewClickSupport removeFrom(RecyclerView view) {
RecyclerViewClickSupport support = (RecyclerViewClickSupport) view.getTag(R.id.recycler_view_click_support);
if (support != null) {
support.detach(view);
}
return support;
}
public RecyclerViewClickSupport setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}
public RecyclerViewClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
return this;
}
private void detach(RecyclerView view) {
view.removeOnChildAttachStateChangeListener(mAttachListener);
view.setTag(R.id.recycler_view_click_support, null);
}
public interface OnItemClickListener {
void onItemClicked(RecyclerView recyclerView, int position, View v);
}
public interface OnItemLongClickListener {
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
}
}

View File

@ -7087,23 +7087,23 @@ var IdentityHandler = {
// No trusted identity information. No site identity icon is shown.
IDENTITY_MODE_UNKNOWN: "unknown",
// Minimal SSL CA-signed domain verification. Blue lock icon is shown.
IDENTITY_MODE_DOMAIN_VERIFIED: "verified",
// High-quality identity information. Green lock icon is shown.
// Domain-Validation SSL CA-signed domain verification (DV).
IDENTITY_MODE_IDENTIFIED: "identified",
// Extended-Validation SSL CA-signed identity information (EV). A more rigorous validation process.
IDENTITY_MODE_VERIFIED: "verified",
// The following mixed content modes are only used if "security.mixed_content.block_active_content"
// is enabled. Our Java frontend coalesces them into one indicator.
// No mixed content information. No mixed content icon is shown.
MIXED_MODE_UNKNOWN: "unknown",
// Blocked active mixed content. Shield icon is shown, with a popup option to load content.
MIXED_MODE_CONTENT_BLOCKED: "mixed_content_blocked",
// Blocked active mixed content.
MIXED_MODE_CONTENT_BLOCKED: "blocked",
// Loaded active mixed content. Yellow triangle icon is shown.
MIXED_MODE_CONTENT_LOADED: "mixed_content_loaded",
// Loaded active mixed content.
MIXED_MODE_CONTENT_LOADED: "loaded",
// The following tracking content modes are only used if tracking protection
// is enabled. Our Java frontend coalesces them into one indicator.
@ -7159,27 +7159,39 @@ var IdentityHandler = {
*/
getIdentityMode: function getIdentityMode(aState) {
if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
return this.IDENTITY_MODE_IDENTIFIED;
return this.IDENTITY_MODE_VERIFIED;
}
if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) {
return this.IDENTITY_MODE_DOMAIN_VERIFIED;
return this.IDENTITY_MODE_IDENTIFIED;
}
return this.IDENTITY_MODE_UNKNOWN;
},
getMixedMode: function getMixedMode(aState) {
if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
return this.MIXED_MODE_CONTENT_BLOCKED;
getMixedDisplayMode: function getMixedDisplayMode(aState) {
if (aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT) {
return this.MIXED_MODE_CONTENT_LOADED;
}
if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT) {
return this.MIXED_MODE_CONTENT_BLOCKED;
}
return this.MIXED_MODE_UNKNOWN;
},
getMixedActiveMode: function getActiveDisplayMode(aState) {
// Only show an indicator for loaded mixed content if the pref to block it is enabled
if ((aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
!Services.prefs.getBoolPref("security.mixed_content.block_active_content")) {
return this.MIXED_MODE_CONTENT_LOADED;
}
if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
return this.MIXED_MODE_CONTENT_BLOCKED;
}
return this.MIXED_MODE_UNKNOWN;
},
@ -7230,13 +7242,15 @@ var IdentityHandler = {
this._lastLocation = locationObj;
let identityMode = this.getIdentityMode(aState);
let mixedMode = this.getMixedMode(aState);
let mixedDisplay = this.getMixedDisplayMode(aState);
let mixedActive = this.getMixedActiveMode(aState);
let trackingMode = this.getTrackingMode(aState, aBrowser);
let result = {
origin: locationObj.origin,
mode: {
identity: identityMode,
mixed: mixedMode,
mixed_display: mixedDisplay,
mixed_active: mixedActive,
tracking: trackingMode
}
};
@ -7244,10 +7258,12 @@ var IdentityHandler = {
// Don't show identity data for pages with an unknown identity or if any
// mixed content is loaded (mixed display content is loaded by default).
if (identityMode == this.IDENTITY_MODE_UNKNOWN || aState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) {
result.secure = false;
return result;
}
result.encrypted = true;
result.secure = true;
result.host = this.getEffectiveHost();
let iData = this.getIdentityData();

View File

@ -147,6 +147,7 @@ skip-if = os == "linux" # Bug 1085717
[test_modal_dialogs.py]
b2g = false
skip-if = true # Disabled so bug 959567 can land
[test_key_actions.py]
[test_mouse_action.py]
b2g = false

View File

@ -335,4 +335,8 @@ user_pref("media.webspeech.synth.test", true);
// connections.
user_pref("browser.urlbar.suggest.searches", false);
// Turn off the location bar search suggestions opt-in. It interferes with
// tests that don't expect it to be there.
user_pref("browser.urlbar.userMadeSearchSuggestionsChoice", true);
user_pref("dom.audiochannel.mutedByDefault", false);

View File

@ -429,6 +429,11 @@ let View = {
updateCategory: function(subset, id, nature, deltaT, currentMode) {
subset = subset.slice().sort(Delta.revCompare);
let watcherAlerts = null;
if (nature == "addons") {
watcherAlerts = AddonWatcher.alerts;
}
// Grab everything from the DOM before cleaning up
let eltContainer = this._setupStructure(id);
@ -441,6 +446,16 @@ let View = {
cachedElements.eltName.textContent = `Full name: ${delta.fullName}.`;
cachedElements.eltLoaded.textContent = `Measure start: ${Math.round(delta.age/1000)} seconds ago.`
cachedElements.eltProcess.textContent = `Process: ${delta.processId} (${delta.isChildProcess?"child":"parent"}).`;
let jankSuffix = "";
let cpowSuffix = "";
if (watcherAlerts) {
let deltaAlerts = watcherAlerts.get(delta.addonId);
if (deltaAlerts) {
jankSuffix = ` (${deltaAlerts.alerts.longestDuration} alerts)`;
cpowSuffix = ` (${deltaAlerts.alerts.totalCPOWTime} alerts)`;
}
}
let eltImpact = cachedElements.eltImpact;
if (currentMode == MODE_RECENT) {
cachedElements.eltRoot.setAttribute("impact", delta.jank.longestDuration + 1);
@ -451,10 +466,10 @@ let View = {
} else {
eltImpact.textContent = ` is currently considerably slowing down ${BRAND_NAME}.`;
}
cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.jank.longestDuration + 1}/${delta.jank.durations.length}.`;
cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.jank.longestDuration + 1}/${delta.jank.durations.length}${jankSuffix}.`;
cachedElements.eltCPU.textContent = `CPU usage: ${Math.min(100, Math.ceil(delta.jank.totalUserTime/deltaT))}%.`;
cachedElements.eltSystem.textContent = `System usage: ${Math.min(100, Math.ceil(delta.jank.totalSystemTime/deltaT))}%.`;
cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.cpow.totalCPOWTime/deltaT)}%.`;
cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.cpow.totalCPOWTime/deltaT)}%${cpowSuffix}.`;
} else {
if (delta.alerts.length == 0) {
eltImpact.textContent = " has performed well so far.";
@ -484,11 +499,11 @@ let View = {
}
eltImpact.textContent = ` ${describeFrequency} ${describeImpact}`;
cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.alerts[1]} high-impacts, ${delta.alerts[0]} medium-impact.`;
cachedElements.eltFPS.textContent = `Impact on framerate: ${delta.alerts[1]} high-impacts, ${delta.alerts[0]} medium-impact${jankSuffix}.`;
}
cachedElements.eltCPU.textContent = `CPU usage: ${Math.min(100, Math.ceil(delta.jank.totalUserTime/delta.age))}% (total ${delta.jank.totalUserTime}ms).`;
cachedElements.eltSystem.textContent = `System usage: ${Math.min(100, Math.ceil(delta.jank.totalSystemTime/delta.age))}% (total ${delta.jank.totalSystemTime}ms).`;
cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.cpow.totalCPOWTime/delta.age)}% (total ${delta.cpow.totalCPOWTime}ms).`;
cachedElements.eltCPOW.textContent = `Blocking process calls: ${Math.ceil(delta.cpow.totalCPOWTime/delta.age)}% (total ${delta.cpow.totalCPOWTime}ms)${cpowSuffix}.`;
}
}
}
@ -681,7 +696,7 @@ let View = {
["eltSystem", "system"],
["eltCPOW", "cpow"],
["eltLoaded", "loaded"],
["eltProcess", "process"]
["eltProcess", "process"],
]) {
let elt = document.createElement("li");
elt.classList.add(className);

View File

@ -187,7 +187,8 @@ let Bookmarks = Object.freeze({
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
toPRTime(entry.lastModified),
entry.type, entry._parentId,
entry.guid, entry.parentGuid ]);
entry.guid, entry.parentGuid,
"" ]);
}
}
@ -325,7 +326,7 @@ let Bookmarks = Object.freeze({
updatedItem.type,
updatedItem._parentId,
updatedItem.guid,
updatedItem.parentGuid ]);
updatedItem.parentGuid, "" ]);
}
if (updateInfo.hasOwnProperty("title")) {
notify(observers, "onItemChanged", [ updatedItem._id, "title",
@ -334,7 +335,7 @@ let Bookmarks = Object.freeze({
updatedItem.type,
updatedItem._parentId,
updatedItem.guid,
updatedItem.parentGuid ]);
updatedItem.parentGuid, "" ]);
}
if (updateInfo.hasOwnProperty("url")) {
notify(observers, "onItemChanged", [ updatedItem._id, "uri",
@ -343,7 +344,8 @@ let Bookmarks = Object.freeze({
updatedItem.type,
updatedItem._parentId,
updatedItem.guid,
updatedItem.parentGuid ]);
updatedItem.parentGuid,
item.url.href ]);
}
// If the item was moved, notify onItemMoved.
if (item.parentGuid != updatedItem.parentGuid ||
@ -410,7 +412,8 @@ let Bookmarks = Object.freeze({
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
toPRTime(entry.lastModified),
entry.type, entry._parentId,
entry.guid, entry.parentGuid ]);
entry.guid, entry.parentGuid,
"" ]);
}
}
@ -872,7 +875,8 @@ function fetchBookmarksByURL(info) {
Task.async(function*(db) {
let rows = yield db.executeCached(
`SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
`/* do not warn (bug no): not worth to add an index */
SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
b.id AS _id, b.parent AS _parentId,
(SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
@ -1428,7 +1432,8 @@ Task.async(function* (db, folderGuids) {
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
toPRTime(entry.lastModified),
entry.type, entry._parentId,
entry.guid, entry.parentGuid ]);
entry.guid, entry.parentGuid,
"" ]);
}
}
}

View File

@ -117,7 +117,8 @@ function* notifyKeywordChange(url, keyword) {
bookmark.lastModified * 1000,
bookmark.type,
bookmark.parentId,
bookmark.guid, bookmark.parentGuid
bookmark.guid, bookmark.parentGuid,
""
]);
}
gIgnoreKeywordNotifications = false;

View File

@ -36,7 +36,7 @@ const PREF_SUGGEST_HISTORY = [ "suggest.history", true ];
const PREF_SUGGEST_BOOKMARK = [ "suggest.bookmark", true ];
const PREF_SUGGEST_OPENPAGE = [ "suggest.openpage", true ];
const PREF_SUGGEST_HISTORY_ONLYTYPED = [ "suggest.history.onlyTyped", false ];
const PREF_SUGGEST_SEARCHES = [ "suggest.searches", true ];
const PREF_SUGGEST_SEARCHES = [ "suggest.searches", false ];
const PREF_MAX_CHARS_FOR_SUGGEST = [ "maxCharsForSearchSuggestions", 20];

View File

@ -13,7 +13,7 @@ interface nsINavHistoryBatchCallback;
/**
* Observer for bookmarks changes.
*/
[scriptable, uuid(8ab925f8-af9b-4837-afa0-ffed507212ce)]
[scriptable, uuid(cff3efcc-e144-490d-9f23-8b6f6dd09e7f)]
interface nsINavBookmarkObserver : nsISupports
{
/**
@ -122,17 +122,30 @@ interface nsINavBookmarkObserver : nsISupports
* The unique ID associated with the item.
* @param aParentGuid
* The unique ID associated with the item's parent.
* @param aOldValue
* For certain properties, this is set to the new value of the
* property (see the list below).
*
* @note List of values that may be associated with properties:
* aProperty | aNewValue
* =====================================================================
* cleartime | Empty string (all visits to this item were removed).
* cleartime | Empty string (all visits to this item were removed).
* title | The new title.
* favicon | The "moz-anno" URL of the new favicon.
* uri | new URL.
* tags | Empty string (tags for this item changed)
* dateAdded | PRTime (as string) when the item was first added.
* lastModified | PRTime (as string) when the item was last modified.
*
* aProperty | aOldValue
* =====================================================================
* cleartime | Empty string (currently unused).
* title | Empty string (currently unused).
* favicon | Empty string (currently unused).
* uri | old URL.
* tags | Empty string (currently unused).
* dateAdded | Empty string (currently unused).
* lastModified | Empty string (currently unused).
*/
void onItemChanged(in long long aItemId,
in ACString aProperty,
@ -142,7 +155,8 @@ interface nsINavBookmarkObserver : nsISupports
in unsigned short aItemType,
in long long aParentId,
in ACString aGuid,
in ACString aParentGuid);
in ACString aParentGuid,
in AUTF8String aOldValue);
/**
* Notifies that the item was visited. Can be invoked only for TYPE_BOOKMARK

View File

@ -551,7 +551,8 @@ nsNavBookmarks::InsertBookmark(int64_t aFolder,
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid));
bookmarks[i].parentGuid,
EmptyCString()));
}
}
@ -656,7 +657,8 @@ nsNavBookmarks::RemoveItem(int64_t aItemId)
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid));
bookmarks[i].parentGuid,
EmptyCString()));
}
}
@ -1132,7 +1134,8 @@ nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId)
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid));
bookmarks[i].parentGuid,
EmptyCString()));
}
}
}
@ -1413,7 +1416,8 @@ nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded)
bookmark.type,
bookmark.parentId,
bookmark.guid,
bookmark.parentGuid));
bookmark.parentGuid,
EmptyCString()));
return NS_OK;
}
@ -1459,7 +1463,8 @@ nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified)
bookmark.type,
bookmark.parentId,
bookmark.guid,
bookmark.parentGuid));
bookmark.parentGuid,
EmptyCString()));
return NS_OK;
}
@ -1527,7 +1532,8 @@ nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle)
bookmark.type,
bookmark.parentId,
bookmark.guid,
bookmark.parentGuid));
bookmark.parentGuid,
EmptyCString()));
return NS_OK;
}
@ -2010,7 +2016,8 @@ nsNavBookmarks::ChangeBookmarkURI(int64_t aBookmarkId, nsIURI* aNewURI)
bookmark.type,
bookmark.parentId,
bookmark.guid,
bookmark.parentGuid));
bookmark.parentGuid,
bookmark.url));
return NS_OK;
}
@ -2044,6 +2051,7 @@ nsNavBookmarks::GetBookmarkIdsForURITArray(nsIURI* aURI,
// importing, syncing or due to extensions.
// Note: not using a JOIN is cheaper in this case.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"/* do not warn (bug 1175249) */ "
"SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
"FROM moz_bookmarks b "
"JOIN moz_bookmarks t on t.id = b.parent "
@ -2087,6 +2095,7 @@ nsNavBookmarks::GetBookmarksForURI(nsIURI* aURI,
// importing, syncing or due to extensions.
// Note: not using a JOIN is cheaper in this case.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"/* do not warn (bug 1175249) */ "
"SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
"FROM moz_bookmarks b "
"JOIN moz_bookmarks t on t.id = b.parent "
@ -2302,7 +2311,8 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid));
bookmarks[i].parentGuid,
EmptyCString()));
}
return NS_OK;
@ -2354,7 +2364,8 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid));
bookmarks[i].parentGuid,
EmptyCString()));
}
stmt = mDB->GetStatement(
@ -2393,7 +2404,8 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
TYPE_BOOKMARK,
bookmarks[i].parentId,
bookmarks[i].guid,
bookmarks[i].parentGuid));
bookmarks[i].parentGuid,
EmptyCString()));
}
return NS_OK;
@ -2589,7 +2601,8 @@ nsNavBookmarks::NotifyItemChanged(const ItemChangeData& aData)
aData.bookmark.type,
aData.bookmark.parentId,
aData.bookmark.guid,
aData.bookmark.parentGuid));
aData.bookmark.parentGuid,
aData.oldValue));
}
////////////////////////////////////////////////////////////////////////////////
@ -2820,7 +2833,8 @@ nsNavBookmarks::OnItemAnnotationSet(int64_t aItemId, const nsACString& aName)
bookmark.type,
bookmark.parentId,
bookmark.guid,
bookmark.parentGuid));
bookmark.parentGuid,
EmptyCString()));
return NS_OK;
}

View File

@ -55,6 +55,7 @@ namespace places {
nsCString property;
bool isAnnotation;
nsCString newValue;
nsCString oldValue;
};
typedef void (nsNavBookmarks::*ItemVisitMethod)(const ItemVisitData&);

View File

@ -2856,7 +2856,8 @@ nsNavHistoryQueryResultNode::OnItemChanged(int64_t aItemId,
uint16_t aItemType,
int64_t aParentId,
const nsACString& aGUID,
const nsACString& aParentGUID)
const nsACString& aParentGUID,
const nsACString& aOldValue)
{
// History observers should not get OnItemChanged
// but should get the corresponding history notifications instead.
@ -2902,7 +2903,7 @@ nsNavHistoryQueryResultNode::OnItemChanged(int64_t aItemId,
aIsAnnotationProperty,
aNewValue, aLastModified,
aItemType, aParentId, aGUID,
aParentGUID);
aParentGUID, aOldValue);
}
NS_IMETHODIMP
@ -3667,7 +3668,8 @@ nsNavHistoryResultNode::OnItemChanged(int64_t aItemId,
uint16_t aItemType,
int64_t aParentId,
const nsACString& aGUID,
const nsACString& aParentGUID)
const nsACString& aParentGUID,
const nsACString& aOldValue)
{
if (aItemId != mItemId)
return NS_OK;
@ -3757,7 +3759,8 @@ nsNavHistoryFolderResultNode::OnItemChanged(int64_t aItemId,
uint16_t aItemType,
int64_t aParentId,
const nsACString& aGUID,
const nsACString&aParentGUID)
const nsACString& aParentGUID,
const nsACString& aOldValue)
{
RESTART_AND_RETURN_IF_ASYNC_PENDING();
@ -3765,7 +3768,7 @@ nsNavHistoryFolderResultNode::OnItemChanged(int64_t aItemId,
aIsAnnotationProperty,
aNewValue, aLastModified,
aItemType, aParentId, aGUID,
aParentGUID);
aParentGUID, aOldValue);
}
/**
@ -4462,11 +4465,13 @@ nsNavHistoryResult::OnItemChanged(int64_t aItemId,
uint16_t aItemType,
int64_t aParentId,
const nsACString& aGUID,
const nsACString& aParentGUID)
const nsACString& aParentGUID,
const nsACString& aOldValue)
{
ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
OnItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue,
aLastModified, aItemType, aParentId, aGUID, aParentGUID));
aLastModified, aItemType, aParentId, aGUID, aParentGUID,
aOldValue));
// Note: folder-nodes set their own bookmark observer only once they're
// opened, meaning we cannot optimize this code path for changes done to
@ -4490,7 +4495,7 @@ nsNavHistoryResult::OnItemChanged(int64_t aItemId,
folder->StartIncrementalUpdate()) {
node->OnItemChanged(aItemId, aProperty, aIsAnnotationProperty,
aNewValue, aLastModified, aItemType, aParentId,
aGUID, aParentGUID);
aGUID, aParentGUID, aOldValue);
}
}
}

View File

@ -281,7 +281,8 @@ public:
uint16_t aItemType,
int64_t aParentId,
const nsACString& aGUID,
const nsACString& aParentGUID);
const nsACString& aParentGUID,
const nsACString &aOldValue);
protected:
virtual ~nsNavHistoryResultNode() {}

View File

@ -43,11 +43,13 @@ let bookmarksObserver = {
this._itemRemovedIndex = index;
},
onItemChanged: function(id, property, isAnnotationProperty, value,
lastModified, itemType) {
lastModified, itemType, parentId, guid, parentGuid,
oldValue) {
this._itemChangedId = id;
this._itemChangedProperty = property;
this._itemChanged_isAnnotationProperty = isAnnotationProperty;
this._itemChangedValue = value;
this._itemChangedOldValue = oldValue;
},
onItemVisited: function(id, visitID, time) {
this._itemVisitedId = id;
@ -452,6 +454,7 @@ add_task(function test_bookmarks() {
do_check_eq(bookmarksObserver._itemChangedId, newId10);
do_check_eq(bookmarksObserver._itemChangedProperty, "uri");
do_check_eq(bookmarksObserver._itemChangedValue, "http://foo11.com/");
do_check_eq(bookmarksObserver._itemChangedOldValue, "http://foo10.com/");
// test getBookmarkURI
let newId11 = bs.insertBookmark(testRoot, uri("http://foo11.com/"),

View File

@ -94,7 +94,7 @@ add_task(function* insert_bookmark_tag_notification() {
{ name: "onItemChanged",
arguments: [ itemId, "tags", false, "",
bm.lastModified, bm.type, parentId,
bm.guid, bm.parentGuid ] }
bm.guid, bm.parentGuid, "" ] }
]);
});
@ -111,7 +111,7 @@ add_task(function* update_bookmark_lastModified() {
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "lastModified", false,
`${bm.lastModified * 1000}`, bm.lastModified,
bm.type, parentId, bm.guid, bm.parentGuid ] }
bm.type, parentId, bm.guid, bm.parentGuid, "" ] }
]);
});
@ -128,7 +128,7 @@ add_task(function* update_bookmark_title() {
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "title", false, bm.title,
bm.lastModified, bm.type, parentId, bm.guid,
bm.parentGuid ] }
bm.parentGuid, "" ] }
]);
});
@ -145,7 +145,7 @@ add_task(function* update_bookmark_uri() {
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "uri", false, bm.url.href,
bm.lastModified, bm.type, parentId, bm.guid,
bm.parentGuid ] }
bm.parentGuid, "http://url.example.com/" ] }
]);
});
@ -264,7 +264,7 @@ add_task(function* remove_bookmark_tag_notification() {
{ name: "onItemChanged",
arguments: [ itemId, "tags", false, "",
bm.lastModified, bm.type, parentId,
bm.guid, bm.parentGuid ] }
bm.guid, bm.parentGuid, "" ] }
]);
});

View File

@ -44,7 +44,7 @@ function expectNotifications() {
}
if (name.startsWith("onItemChanged")) {
return (id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid) => {
return (id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal) => {
if (prop != "keyword")
return;
let args = Array.from(arguments, arg => {
@ -97,7 +97,7 @@ add_task(function test_addBookmarkAndKeyword() {
arguments: [ itemId, "keyword", false, "keyword",
bookmark.lastModified, bookmark.type,
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
bookmark.guid, bookmark.parentGuid ] }
bookmark.guid, bookmark.parentGuid, "" ] }
]);
yield PlacesTestUtils.promiseAsyncUpdates();
@ -148,12 +148,12 @@ add_task(function test_sameKeywordDifferentURI() {
"keyword", false, "",
bookmark1.lastModified, bookmark1.type,
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
bookmark1.guid, bookmark1.parentGuid ] },
bookmark1.guid, bookmark1.parentGuid, "" ] },
{ name: "onItemChanged",
arguments: [ itemId, "keyword", false, "keyword",
bookmark2.lastModified, bookmark2.type,
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
bookmark2.guid, bookmark2.parentGuid ] }
bookmark2.guid, bookmark2.parentGuid, "" ] }
]);
yield PlacesTestUtils.promiseAsyncUpdates();
@ -187,13 +187,13 @@ add_task(function test_sameURIDifferentKeyword() {
"keyword", false, "keyword2",
bookmarks[0].lastModified, bookmarks[0].type,
(yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
bookmarks[0].guid, bookmarks[0].parentGuid ] },
bookmarks[0].guid, bookmarks[0].parentGuid, "" ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
"keyword", false, "keyword2",
bookmarks[1].lastModified, bookmarks[1].type,
(yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
bookmarks[1].guid, bookmarks[1].parentGuid ] }
bookmarks[1].guid, bookmarks[1].parentGuid, "" ] }
]);
yield PlacesTestUtils.promiseAsyncUpdates();
@ -243,19 +243,19 @@ add_task(function test_unsetKeyword() {
"keyword", false, "",
bookmarks[0].lastModified, bookmarks[0].type,
(yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
bookmarks[0].guid, bookmarks[0].parentGuid ] },
bookmarks[0].guid, bookmarks[0].parentGuid, "" ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
"keyword", false, "",
bookmarks[1].lastModified, bookmarks[1].type,
(yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
bookmarks[1].guid, bookmarks[1].parentGuid ] },
bookmarks[1].guid, bookmarks[1].parentGuid, "" ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[2].guid)),
"keyword", false, "",
bookmarks[2].lastModified, bookmarks[2].type,
(yield PlacesUtils.promiseItemId(bookmarks[2].parentGuid)),
bookmarks[2].guid, bookmarks[2].parentGuid ] }
bookmarks[2].guid, bookmarks[2].parentGuid, "" ] }
]);
check_keyword(URI1, null);
@ -286,7 +286,7 @@ add_task(function test_addRemoveBookmark() {
"keyword", false, "keyword",
bookmark.lastModified, bookmark.type,
parentId,
bookmark.guid, bookmark.parentGuid ] }
bookmark.guid, bookmark.parentGuid, "" ] }
]);
check_keyword(URI3, null);

View File

@ -21,20 +21,27 @@ let gBookmarksObserver = {
},
// nsINavBookmarkObserver
onBeginUpdateBatch: function onBeginUpdateBatch()
this.validate(arguments.callee.name, arguments),
onEndUpdateBatch: function onEndUpdateBatch()
this.validate(arguments.callee.name, arguments),
onItemAdded: function onItemAdded()
this.validate(arguments.callee.name, arguments),
onItemRemoved: function onItemRemoved()
this.validate(arguments.callee.name, arguments),
onItemChanged: function onItemChanged()
this.validate(arguments.callee.name, arguments),
onItemVisited: function onItemVisited()
this.validate(arguments.callee.name, arguments),
onItemMoved: function onItemMoved()
this.validate(arguments.callee.name, arguments),
onBeginUpdateBatch() {
return this.validate("onBeginUpdateBatch", arguments);
},
onEndUpdateBatch() {
return this.validate("onEndUpdateBatch", arguments);
},
onItemAdded() {
return this.validate("onItemAdded", arguments);
},
onItemRemoved() {
return this.validate("onItemRemoved", arguments);
},
onItemChanged() {
return this.validate("onItemChanged", arguments);
},
onItemVisited() {
return this.validate("onItemVisited", arguments);
},
onItemMoved() {
return this.validate("onItemMoved", arguments);
},
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
@ -132,6 +139,7 @@ add_test(function onItemChanged_title_bookmark() {
{ name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "oldValue", check: function (v) typeof(v) == "string" },
] },
];
PlacesUtils.bookmarks.setItemTitle(id, TITLE);
@ -178,6 +186,7 @@ add_test(function onItemChanged_tags_bookmark() {
{ name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "oldValue", check: function (v) typeof(v) == "string" },
] },
{ name: "onItemRemoved", // This is the tag.
args: [
@ -210,6 +219,7 @@ add_test(function onItemChanged_tags_bookmark() {
{ name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "oldValue", check: function (v) typeof(v) == "string" },
] },
];
PlacesUtils.tagging.tagURI(uri, [TAG]);
@ -283,6 +293,7 @@ add_test(function onItemRemoved_bookmark() {
{ name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "oldValue", check: function (v) typeof(v) == "string" },
] },
{ name: "onItemRemoved",
args: [
@ -312,6 +323,7 @@ add_test(function onItemRemoved_separator() {
{ name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "oldValue", check: function (v) typeof(v) == "string" },
] },
{ name: "onItemRemoved",
args: [
@ -342,6 +354,7 @@ add_test(function onItemRemoved_folder() {
{ name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
{ name: "oldValue", check: function (v) typeof(v) == "string" },
] },
{ name: "onItemRemoved",
args: [

View File

@ -59,6 +59,10 @@ add_task(function* test_sync_enabled() {
Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", false);
Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", true);
for (let type of types.filter(t => t != "history")) {
Assert.equal(Services.prefs.getBoolPref("browser.urlbar.suggest." + type), true);
if (type == "searches") {
Assert.equal(Services.prefs.getBoolPref("browser.urlbar.suggest." + type), false);
} else {
Assert.equal(Services.prefs.getBoolPref("browser.urlbar.suggest." + type), true);
}
}
});

View File

@ -185,7 +185,7 @@ add_task(function* test_addBookmarkAndKeyword() {
"keyword", false, "keyword",
bookmark.lastModified, bookmark.type,
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
bookmark.guid, bookmark.parentGuid ] } ]);
bookmark.guid, bookmark.parentGuid, "" ] } ]);
yield check_keyword(true, "http://example.com/", "keyword");
Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // +1 bookmark +1 keyword
@ -199,7 +199,7 @@ add_task(function* test_addBookmarkAndKeyword() {
"keyword", false, "",
bookmark.lastModified, bookmark.type,
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
bookmark.guid, bookmark.parentGuid ] } ]);
bookmark.guid, bookmark.parentGuid, "" ] } ]);
yield check_keyword(false, "http://example.com/", "keyword");
Assert.equal((yield foreign_count("http://example.com/")), fc + 1); // -1 keyword
@ -312,13 +312,13 @@ add_task(function* test_sameKeywordDifferentURL() {
"keyword", false, "",
bookmark1.lastModified, bookmark1.type,
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
bookmark1.guid, bookmark1.parentGuid ] },
bookmark1.guid, bookmark1.parentGuid, "" ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
"keyword", false, "keyword",
bookmark2.lastModified, bookmark2.type,
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
bookmark2.guid, bookmark2.parentGuid ] } ]);
bookmark2.guid, bookmark2.parentGuid, "" ] } ]);
yield check_keyword(false, "http://example1.com/", "keyword");
Assert.equal((yield foreign_count("http://example1.com/")), fc1 + 1); // -1 keyword
@ -333,7 +333,7 @@ add_task(function* test_sameKeywordDifferentURL() {
"keyword", false, "",
bookmark2.lastModified, bookmark2.type,
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
bookmark2.guid, bookmark2.parentGuid ] } ]);
bookmark2.guid, bookmark2.parentGuid, "" ] } ]);
yield check_keyword(false, "http://example1.com/", "keyword");
yield check_keyword(false, "http://example2.com/", "keyword");
@ -365,7 +365,7 @@ add_task(function* test_sameURIDifferentKeyword() {
"keyword", false, "keyword",
bookmark.lastModified, bookmark.type,
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
bookmark.guid, bookmark.parentGuid ] } ]);
bookmark.guid, bookmark.parentGuid, "" ] } ]);
observer = expectBookmarkNotifications();
yield PlacesUtils.keywords.insert({ keyword: "keyword2", url: "http://example.com/" });
@ -377,7 +377,7 @@ add_task(function* test_sameURIDifferentKeyword() {
"keyword", false, "keyword2",
bookmark.lastModified, bookmark.type,
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
bookmark.guid, bookmark.parentGuid ] } ]);
bookmark.guid, bookmark.parentGuid, "" ] } ]);
// Add a third keyword.
yield PlacesUtils.keywords.insert({ keyword: "keyword3", url: "http://example.com/" });
@ -397,7 +397,7 @@ add_task(function* test_sameURIDifferentKeyword() {
"keyword", false, "",
bookmark.lastModified, bookmark.type,
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
bookmark.guid, bookmark.parentGuid ] } ]);
bookmark.guid, bookmark.parentGuid, "" ] } ]);
Assert.equal((yield foreign_count("http://example.com/")), fc + 3); // -1 keyword
// Now remove the bookmark.
@ -429,13 +429,13 @@ add_task(function* test_deleteKeywordMultipleBookmarks() {
"keyword", false, "keyword",
bookmark2.lastModified, bookmark2.type,
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
bookmark2.guid, bookmark2.parentGuid ] },
bookmark2.guid, bookmark2.parentGuid, "" ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
"keyword", false, "keyword",
bookmark1.lastModified, bookmark1.type,
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
bookmark1.guid, bookmark1.parentGuid ] } ]);
bookmark1.guid, bookmark1.parentGuid, "" ] } ]);
observer = expectBookmarkNotifications();
yield PlacesUtils.keywords.remove("keyword");
@ -446,13 +446,13 @@ add_task(function* test_deleteKeywordMultipleBookmarks() {
"keyword", false, "",
bookmark2.lastModified, bookmark2.type,
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
bookmark2.guid, bookmark2.parentGuid ] },
bookmark2.guid, bookmark2.parentGuid, "" ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
"keyword", false, "",
bookmark1.lastModified, bookmark1.type,
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
bookmark1.guid, bookmark1.parentGuid ] } ]);
bookmark1.guid, bookmark1.parentGuid, "" ] } ]);
// Now remove the bookmarks.
yield PlacesUtils.bookmarks.remove(bookmark1);

View File

@ -8459,6 +8459,26 @@
"kind": "count",
"description": "The number of Sync 1.5 migrations completed by Android Sync."
},
"FENNEC_SYNC_NUMBER_OF_SYNCS_STARTED": {
"expires_in_version": "45",
"kind": "count",
"description": "Counts the number of times that a sync has started."
},
"FENNEC_SYNC_NUMBER_OF_SYNCS_COMPLETED": {
"expires_in_version": "45",
"kind": "count",
"description": "Counts the number of times that a sync has completed with no errors."
},
"FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED": {
"expires_in_version": "45",
"kind": "count",
"description": "Counts the number of times that a sync has failed with errors."
},
"FENNEC_SYNC_NUMBER_OF_SYNCS_FAILED_BACKOFF": {
"expires_in_version": "45",
"kind": "count",
"description": "Counts the number of times that a sync has failed because of trying to sync before server backoff interval has passed."
},
"SLOW_SCRIPT_NOTICE_COUNT": {
"alert_emails": ["perf-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",

View File

@ -1078,25 +1078,17 @@ extends="chrome://global/content/bindings/popup.xml#popup">
<method name="_invalidate">
<body>
<![CDATA[
// To get a fixed height for the popup, instead of the default
// behavior that grows and shrinks it on result change, a consumer
// can set the height attribute. In such a case, instead of adjusting
// the richlistbox height, we just need to collapse unused items.
if (!this.hasAttribute("height")) {
// collapsed if no matches
this.richlistbox.collapsed = (this._matchCount == 0);
// collapsed if no matches
this.richlistbox.collapsed = (this._matchCount == 0);
// Update the richlistbox height.
if (this._adjustHeightTimeout) {
clearTimeout(this._adjustHeightTimeout);
}
if (this._shrinkTimeout) {
clearTimeout(this._shrinkTimeout);
}
this._adjustHeightTimeout = setTimeout(() => this.adjustHeight(), 0);
} else {
this._collapseUnusedItems();
// Update the richlistbox height.
if (this._adjustHeightTimeout) {
clearTimeout(this._adjustHeightTimeout);
}
if (this._shrinkTimeout) {
clearTimeout(this._shrinkTimeout);
}
this._adjustHeightTimeout = setTimeout(() => this.adjustHeight(), 0);
this._currentIndex = 0;
if (this._appendResultTimeout) {
@ -1143,13 +1135,18 @@ extends="chrome://global/content/bindings/popup.xml#popup">
let rows = this.richlistbox.childNodes;
let numRows = Math.min(this._matchCount, this.maxRows, rows.length);
this.removeAttribute("height");
// Default the height to 0 if we have no rows to show
let height = 0;
if (numRows) {
if (!this._rowHeight) {
let firstRowRect = rows[0].getBoundingClientRect();
this._rowHeight = firstRowRect.height;
this._rlbAnimated = !!window.getComputedStyle(this.richlistbox).transitionProperty;
let transition =
window.getComputedStyle(this.richlistbox).transitionProperty;
this._rlbAnimated = transition && transition != "none";
// Set a fixed max-height to avoid flicker when growing the panel.
this.richlistbox.style.maxHeight = (this._rowHeight * this.maxRows) + "px";
@ -1159,22 +1156,28 @@ extends="chrome://global/content/bindings/popup.xml#popup">
height = this._rowHeight * numRows;
}
let animate = this._rlbAnimated &&
this.getAttribute("dontanimate") != "true";
let currentHeight = this.richlistbox.getBoundingClientRect().height;
if (height > currentHeight) {
// Grow immediately.
this.richlistbox.style.height = height + "px";
if (animate) {
this.richlistbox.removeAttribute("height");
this.richlistbox.style.height = height + "px";
} else {
this.richlistbox.style.removeProperty("height");
this.richlistbox.height = height;
}
} else {
// Delay shrinking to avoid flicker.
this._shrinkTimeout = setTimeout(() => {
this.richlistbox.style.height = height + "px";
if (this._rlbAnimated) {
let onTransitionEnd = () => {
this.removeEventListener("transitionend", onTransitionEnd, true);
this._collapseUnusedItems();
};
this.addEventListener("transitionend", onTransitionEnd, true);
this._collapseUnusedItems();
if (animate) {
this.richlistbox.removeAttribute("height");
this.richlistbox.style.height = height + "px";
} else {
this._collapseUnusedItems();
this.richlistbox.style.removeProperty("height");
this.richlistbox.height = height;
}
}, this.mInput.shrinkDelay);
}