Merge f-t to m-c, a=merge

This commit is contained in:
Phil Ringnalda 2015-10-17 11:19:46 -07:00
commit 9ea53214d8
108 changed files with 1426 additions and 4709 deletions

View File

@ -36,3 +36,4 @@ cspBlocked=This page has a content security policy that prevents it from being l
corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.

View File

@ -69,6 +69,9 @@ pref("extensions.hotfix.cert.checkAttributes", true);
pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35:22:9E:11:C9:A7:31:04:49:A1:AA");
pref("extensions.hotfix.certs.2.sha1Fingerprint", "39:E7:2B:7A:5B:CF:37:78:F9:5D:4A:E0:53:2D:2F:3D:68:53:C5:60");
// Check AUS for system add-on updates.
pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
// Disable add-ons that are not installed by the user in all scopes by default.
// See the SCOPE constants in AddonManager.jsm for values to use here.
pref("extensions.autoDisableScopes", 15);
@ -1411,8 +1414,6 @@ pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
pref("loop.fxa_oauth.tokendata", "");
pref("loop.fxa_oauth.profile", "");
pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc");
pref("loop.contacts.gravatars.show", false);
pref("loop.contacts.gravatars.promo", true);
pref("loop.browserSharing.showInfoBar", true);
pref("social.sidebar.unload_timeout_ms", 10000);

View File

@ -91,6 +91,12 @@
buttonEl.disabled = true;
}
function doOverride(buttonEl) {
var event = new CustomEvent("AboutNetErrorOverride", {bubbles:true});
document.dispatchEvent(event);
retryThis(buttonEl);
}
function toggleDisplay(node) {
toggle = {
'': 'block',
@ -121,6 +127,23 @@
});
}
function showWeakCryptoAdvanced() {
// Display weak crypto advanced UI
document.getElementById("weakCryptoAdvanced").style.display = "block";
// Get the hostname and add it to the panel
var panel = document.getElementById("weakCryptoAdvancedPanel");
for (var span of panel.querySelectorAll("span.hostname")) {
span.textContent = document.location.hostname;
}
// Register click handler for the weakCryptoAdvancedPanel
document.getElementById("showWeakCryptoAdvancedPanel")
.addEventListener("click", () => toggleDisplay(panel));
var overrideLink = document.getElementById("overrideWeakCrypto");
overrideLink.addEventListener("click", () => doOverride(overrideLink), false);
}
function sendErrorReport() {
var event = new CustomEvent("AboutNetErrorSendReport", {bubbles:true});
@ -172,6 +195,16 @@
retryBtn.setAttribute("onclick", "learnMoreSSLV3()");
}
if (err == "weakCryptoUsed") {
var learnMoreText = document.getElementById("learn_more_weak_crypto");
document.getElementById("errorTitle").setAttribute("weakCrypto", "true");
var retryBtn = document.getElementById("errorTryAgain");
retryBtn.textContent = learnMoreText.textContent;
retryBtn.setAttribute("onclick", "learnMoreWeakCrypto()");
}
// remove undisplayed errors to avoid bug 39098
var errContainer = document.getElementById("errorContainer");
errContainer.parentNode.removeChild(errContainer);
@ -240,6 +273,9 @@
retryBtn.addEventListener('click', sendErrorReport, false);
}
}
if (getErrorCode() == "weakCryptoUsed") {
showWeakCryptoAdvanced();
}
}.bind(this), true, true);
var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
@ -351,6 +387,12 @@
// Ensure users don't re-click the button:
e.target.disabled = true;
}
function learnMoreWeakCrypto() {
location.href = "https://support.mozilla.org/kb/how-resolve-weak-crypto-error-messages-firefox";
// Ensure users don't re-click the button:
e.target.disabled = true;
}
]]></script>
</head>
@ -385,6 +427,7 @@
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
<h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
<h1 id="et_sslv3Used">&sslv3Used.title;</h1>
<h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
</div>
<div id="errorDescriptionsContainer">
<div id="ed_generic">&generic.longDesc;</div>
@ -414,6 +457,8 @@
<div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
<div id="ed_sslv3Used">&sslv3Used.longDesc;</div>
<div id="learn_more_ssl3">&sslv3Used.learnMore;</div>
<div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc;</div>
<div id="learn_more_weak_crypto">&weakCryptoUsed.learnMore;</div>
</div>
</div>
@ -488,6 +533,21 @@
</div>
</div>
<!-- UI for option to override weak crypto errors. Removed on
init for other error types .-->
<div id="weakCryptoAdvanced">
<a id="showWeakCryptoAdvancedPanel" href="#">&weakCryptoAdvanced.title;<span class="downArrow"> &#x25bc;</span></a>
</div>
<div id="weakCryptoAdvancedPanel">
<div id="weakCryptoAdvancedDescription">
<p>&weakCryptoAdvanced.longDesc;</p>
</div>
<div id="overrideWeakCryptoPanel">
<a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
</div>
</div>
</div>
<!--

View File

@ -276,18 +276,48 @@ var LoopUI;
return;
}
let state = "";
let mozL10nId = "loop-call-button3";
let suffix = ".tooltiptext";
if (this.MozLoopService.errors.size) {
state = "error";
mozL10nId += "-error";
} else if (this.MozLoopService.screenShareActive) {
state = "action";
mozL10nId += "-screensharing";
} else if (aReason == "login" && this.MozLoopService.userProfile) {
state = "active";
mozL10nId += "-active";
} else if (this.MozLoopService.doNotDisturb) {
state = "disabled";
mozL10nId += "-donotdisturb";
} else if (this.MozLoopService.roomsParticipantsCount > 0) {
state = "active";
this.roomsWithNonOwners().then(roomsWithNonOwners => {
if (roomsWithNonOwners.length > 0) {
mozL10nId += "-participantswaiting";
} else {
mozL10nId += "-active";
}
this.updateTooltiptext(mozL10nId + suffix);
this.toolbarButton.node.setAttribute("state", state);
});
return;
}
this.toolbarButton.node.setAttribute("state", state);
this.updateTooltiptext(mozL10nId + suffix);
},
/**
* Updates the tootltiptext to reflect Loop status.
*
* @param {string} [mozL10nId] l10n ID that refelct the current
* Loop status.
*/
updateTooltiptext: function(mozL10nId) {
this.toolbarButton.node.setAttribute("tooltiptext", mozL10nId);
var tooltiptext = CustomizableUI.getLocalizedProperty(this.toolbarButton, "tooltiptext");
this.toolbarButton.node.setAttribute("tooltiptext", tooltiptext);
},
/**

View File

@ -2748,6 +2748,7 @@ var BrowserOnClick = {
mm.addMessageListener("Browser:SendSSLErrorReport", this);
mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
mm.addMessageListener("Browser:SSLErrorReportTelemetry", this);
mm.addMessageListener("Browser:OverrideWeakCrypto", this);
},
uninit: function () {
@ -2758,6 +2759,7 @@ var BrowserOnClick = {
mm.removeMessageListener("Browser:SendSSLErrorReport", this);
mm.removeMessageListener("Browser:SetSSLErrorReportAuto", this);
mm.removeMessageListener("Browser:SSLErrorReportTelemetry", this);
mm.removeMessageListener("Browser:OverrideWeakCrypto", this);
},
handleEvent: function (event) {
@ -2815,6 +2817,14 @@ var BrowserOnClick = {
Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI")
.add(reportStatus);
break;
case "Browser:OverrideWeakCrypto":
let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
.getService(Ci.nsIWeakCryptoOverride);
weakCryptoOverride.addWeakCryptoOverride(
msg.data.location.hostname,
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser),
true /* temporary */);
break;
}
},
@ -7021,6 +7031,7 @@ var gIdentityHandler = {
* processed by nsIURIFixup.createExposableURI.
*/
updateIdentity(state, uri) {
let shouldHidePopup = this._uri && (this._uri.spec != uri.spec);
this._state = state;
this._uri = uri;
@ -7045,15 +7056,19 @@ var gIdentityHandler = {
}
// Then, update the user interface with the available data.
if (this._identityBox) {
this.refreshIdentityBlock();
}
// Handle a location change while the Control Center is focused
// by closing the popup (bug 1207542)
if (shouldHidePopup) {
this._identityPopup.hidePopup();
}
// NOTE: We do NOT update the identity popup (the control center) when
// we receive a new security state. If the user opened the popup and looks
// at the provided information we don't want to suddenly change the panel
// contents.
// we receive a new security state on the existing page (i.e. from a
// subframe). If the user opened the popup and looks at the provided
// information we don't want to suddenly change the panel contents.
},
/**

View File

@ -213,6 +213,7 @@ var AboutNetErrorListener = {
chromeGlobal.addEventListener('AboutNetErrorSetAutomatic', this, false, true);
chromeGlobal.addEventListener('AboutNetErrorSendReport', this, false, true);
chromeGlobal.addEventListener('AboutNetErrorUIExpanded', this, false, true);
chromeGlobal.addEventListener('AboutNetErrorOverride', this, false, true);
},
get isAboutNetError() {
@ -238,6 +239,9 @@ var AboutNetErrorListener = {
sendAsyncMessage("Browser:SSLErrorReportTelemetry",
{reportStatus: TLS_ERROR_REPORT_TELEMETRY_EXPANDED});
break;
case "AboutNetErrorOverride":
this.onOverride(aEvent);
break;
}
},
@ -330,6 +334,16 @@ var AboutNetErrorListener = {
location: {hostname: contentDoc.location.hostname, port: contentDoc.location.port},
securityInfo: serializedSecurityInfo
});
},
onOverride: function(evt) {
let contentDoc = content.document;
let location = contentDoc.location;
sendAsyncMessage("Browser:OverrideWeakCrypto", {
documentURI: contentDoc.documentURI,
location: {hostname: location.hostname, port: location.port}
});
}
}

View File

@ -1579,6 +1579,11 @@
if (wasActive)
aBrowser.focus();
// If the findbar has been initialised, reset its browser reference.
if (this.isFindBarInitialized(tab)) {
this.getFindBar(tab).browser = aBrowser;
}
let evt = document.createEvent("Events");
evt.initEvent("TabRemotenessChange", true, false);
tab.dispatchEvent(evt);

View File

@ -1,14 +1,15 @@
/* Tests for correct behaviour of getEffectiveHost on identity handler */
function test() {
waitForExplicitFinish();
requestLongerTimeout(2);
ok(gIdentityHandler, "gIdentityHandler should exist");
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", checkResult, true);
nextTest();
BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true).then(() => {
gBrowser.selectedBrowser.addEventListener("load", checkResult, true);
nextTest();
});
}
// Greek IDN for 'example.test'.
@ -59,7 +60,7 @@ var tests = [
},
]
var gCurrentTest, gCurrentTestIndex = -1, gTestDesc;
var gCurrentTest, gCurrentTestIndex = -1, gTestDesc, gPopupHidden;
// Go through the tests in both directions, to add additional coverage for
// transitions between different states.
var gForward = true;
@ -91,7 +92,25 @@ function nextTest() {
if (gCurrentTest.isHTTPS) {
gCheckETLD = true;
}
content.location = gCurrentTest.location;
// Navigate to the next page, which will cause checkResult to fire.
let spec = gBrowser.selectedBrowser.currentURI.spec;
if (spec == "about:blank" || spec == gCurrentTest.location) {
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
} else {
// Open the Control Center and make sure it closes after nav (Bug 1207542).
let popupShown = promisePopupShown(gIdentityHandler._identityPopup);
gPopupHidden = promisePopupHidden(gIdentityHandler._identityPopup);
gIdentityHandler._identityBox.click();
info("Waiting for the Control Center to be shown");
popupShown.then(() => {
is_element_visible(gIdentityHandler._identityPopup, "Control Center is visible");
// Show the subview, which is an easy way in automation to reproduce
// Bug 1207542, where the CC wouldn't close on navigation.
gBrowser.ownerDocument.querySelector("#identity-popup-security-expander").click();
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, gCurrentTest.location);
});
}
} else {
gCheckETLD = false;
gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + " without eTLD in identity icon label)";
@ -113,5 +132,14 @@ function checkResult() {
is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
}
executeSoon(nextTest);
if (gPopupHidden) {
info("Waiting for the Control Center to hide");
gPopupHidden.then(() => {
gPopupHidden = null;
is_element_hidden(gIdentityHandler._identityPopup, "control center is hidden");
executeSoon(nextTest);
});
} else {
executeSoon(nextTest);
}
}

View File

@ -15,7 +15,6 @@ test/shared/vendor
test/coverage
test/node_modules
# These are generated react files that we don't need to check
content/js/contacts.js
content/js/conversation.js
content/js/conversationViews.js
content/js/panel.js

View File

@ -1,491 +0,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/. */
html {
font-size: 10px;
font-family: sans-serif; /* XXX will be changed to a system font in bug 1191398 */
}
.contacts-container {
flex: 1;
display: flex;
flex-flow: column nowrap;
overflow: auto;
}
.contact-list-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: auto;
}
/* Don't show the Gravatar if we're showing a contacts list. */
.contact-list ~ .contacts-gravatar-promo {
display: none;
}
.contact-list-wrapper {
flex: 1;
overflow-x: hidden;
overflow-y: auto;
}
/* Don't show the empty contacts image if we're showing gravatar promo. */
.contacts-gravatar-promo ~ .contact-list-empty {
background-image: none;
padding-top: 0;
padding-bottom: 0;
margin-top: 2px;
}
.contact-list-empty-container {
flex: 1;
overflow-x: hidden;
overflow-y: auto;
}
.contact-list-empty,
.contact-search-list-empty {
margin-top: 4rem;
padding-top: 11.5rem;
padding-bottom: 0;
}
.contact-search-list-empty {
background-image: url("../shared/img/empty_search.svg");
}
.contact-list-empty {
background-image: url("../shared/img/empty_contacts.svg");
}
.contact-import-spinner {
display: none;
}
.contact-import-spinner.busy {
display: inline-block;
vertical-align: middle;
-moz-margin-start: 10px;
}
.contact-filter-container {
display: flex;
height: 2em;
}
.contact-filter {
margin: 0;
-moz-padding-start: 34px;
width: 100%;
height: 28px;
border: 0;
border-bottom: 1px solid #ddd;
background-image: url("../shared/img/icons-14x14.svg#magnifier");
background-position: 10px center;
background-size: 14px;
background-repeat: no-repeat;
color: #999;
font-size: 1.2rem;
}
html[dir="rtl"] .contact-filter {
background-position: right 10px center;
}
.contact-filter:focus + .clear-search,
.contact-filter:focus {
border-bottom: 1px solid #5cccee;
color: #4a4a4a;
}
.clear-search {
width: 34px;
height: 28px;
border: none;
border-bottom: 1px solid #ddd;
background-color: #fff;
background-image: url("../shared/img/icons-14x14.svg#clear");
background-position: center;
background-size: 14px;
background-repeat: no-repeat;
cursor: pointer;
flex: 0 1 auto;
align-self: stretch;
}
.contact-list {
/* Space for six contacts, not affected by filtering. This is enough space
to show the dropdown menu when there is only one contact. */
/* Contact list title goes away when searching, needed for spacing. */
margin-top: 4px;
}
.contact-list-title {
padding: 0.75rem 1rem;
color: #666;
font-weight: 500;
font-size: .9em;
}
.contact,
.contact-separator {
padding: .5rem 15px;
font-size: 13px;
}
.contact {
display: flex;
flex-direction: row;
align-items: center;
color: #666;
}
.contact-separator {
background-color: #eee;
color: #888;
}
.contact-separator:not(:first-child) {
border-top: 1px solid #ccc;
}
.contact:hover {
background-color: #E3F7FE;
}
.contact:hover > .icons {
display: block;
z-index: 1;
}
.contact > .details {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: auto;
}
.contact > .avatar {
width: 40px;
height: 40px;
background-color: #ccc;
border-radius: 50%;
-moz-margin-end: 10px;
overflow: hidden;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
background-image: url("../shared/img/audio-call-avatar.svg");
background-repeat: no-repeat;
background-color: #4ba6e7;
background-size: contain;
-moz-user-select: none;
flex: 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%;
}
.panel-text-medium{
margin: 3px;
color: #4a4a4a;
font-size: 1.3rem;
}
.contact > .details > .username {
font-size: 1.3rem;
line-height: 20px;
color: #000;
}
.contact.blocked > .details > .username {
color: #d74345;
}
.contact > .details > .username > strong {
font-weight: bold;
}
.contact > .details > .username > i.icon-blocked {
display: inline-block;
width: 10px;
height: 20px;
-moz-margin-start: 3px;
background-image: url("../shared/img/icons-16x16.svg#block-red");
background-position: center;
background-size: 10px 10px;
background-repeat: no-repeat;
}
.contact > .details > .email {
color: #4a4a4a;
font-size: 11px;
line-height: 14px;
}
.icons {
cursor: pointer;
display: none;
-moz-margin-start: 10px;
color: #fff;
-moz-user-select: none;
flex: none;
}
.icons:hover {
display: block;
}
.icons i {
display: inline-block;
background-position: center;
background-repeat: no-repeat;
}
.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: 14px 14px;
}
.icon-contact-video-call:hover,
.icon-contact-video-call:active {
background-color: #50E3C2;
}
.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: 37px;
right: 22px;
bottom: auto;
left: auto;
z-index: 2;
}
html[dir="rtl"] .contact > .dropdown-menu {
right: auto;
left: 22px;
}
.contact > .dropdown-menu-up {
bottom: 25px;
top: auto;
}
.contact-form {
padding: 14px 15px 0 15px; /* Override based on spacing in Mockup */
flex: 1;
display: flex;
flex-direction: column;
}
/* This will effect the header displayed at the top of the contact details form
*/
.contact-form header {
text-align: center;
font-size: 1.3rem;
font-weight: 500;
color: #4a4a4a;
}
.contact-form .form-content-container {
/* flex is needed to fill and place the buttons above the footer */
flex: 1;
padding-top: 4px; /* Based on spacing in Mockup
replaced margin-top
See http://stackoverflow.com/questions/6204670/css-clean-solution-to-the-margin-collapse-issue-when-floating-an-element
*/
}
.contacts-gravatar-promo {
border: 1px solid #5cccee;
border-radius: 2px;
background-color: #fff;
font-size: 1.2rem;
margin: 1.5rem;
padding: 1.5rem 1rem;
position: relative;
}
.contacts-gravatar-promo > p {
margin-top: 0;
word-wrap: break-word;
}
.contacts-gravatar-promo > p > a {
color: #0295df;
text-decoration: none;
}
.contacts-gravatar-promo > p > a:hover {
text-decoration: underline;
}
.contacts-gravatar-promo > .button-close {
position: absolute;
top: 8px;
right: 8px;
}
html[dir="rtl"] .contacts-gravatar-promo > .button-close {
right: auto;
left: 8px;
}
.contacts-gravatar-avatars {
height: 50px;
margin: 1.5rem auto;
text-align: center;
width: 200px;
}
.contacts-gravatar-avatars img {
margin: 0 1.5rem;
vertical-align: middle;
width: 50px;
}
/* Adjust the Firefox avatar because it has pointy ears. */
.contacts-gravatar-avatars img:last-child {
transform: scale(1.08) translateY(-2px);
}
.contacts-gravatar-arrow {
border-color: #9b9b9b;
border-style: solid solid none none;
border-width: 2px;
display: inline-block;
height: 1.5rem;
-moz-margin-start: -.75rem;
transform: rotateZ(45deg);
vertical-align: middle;
width: 1.5rem;
}
html[dir="rtl"] .contacts-gravatar-arrow {
transform: rotateZ(225deg);
}
.contacts-gravatar-buttons {
padding: 0 .5rem;
}
.contact-controls {
padding-left: 15px;
padding-right: 15px;
border-top: 1px solid #D8D8D8;
}
.contact-controls > .button {
padding: .5em;
border: none;
border-radius: 5px;
}
.button.primary {
background: #00A9DC;
color: #fff;
}
.button.primary:active,
.button.primary:hover {
background: #5cccee;
}
.button.secondary {
background: #ebebeb;
color: #4D4D4D;
}
.button.secondary:hover,
.button.secondary:active {
background: #dad6d6;
color: #4D4D4D;
}
.contact-controls > .primary {
flex: 5;
}
.contact-controls > .secondary {
flex: 3;
}

View File

@ -130,29 +130,22 @@ body {
border: 0.1rem solid #5cccee;
}
/* Rooms and contacts shared CSS */
/* Rooms CSS */
.contact-list-empty,
.contact-search-list-empty,
.no-conversations-message {
background-repeat: no-repeat;
background-position: top center;
/* example of vertical aligning a container in an element see:
http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/ */
text-align: center;
color: #4a4a4a;
font-weight: lighter;
}
.no-conversations-message {
/* example of vertical aligning a container in an element
see: http://zerosixthree.se/vertical-align-anything-with-just-3-lines-of-css/ */
position: relative;
top: 50%;
transform: translateY(-50%);
padding-top: 11rem;
padding-bottom: 1rem;
background-image: url("../shared/img/empty_conversations.svg");
background-repeat: no-repeat;
background-position: top center;
}
.panel-text-medium,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@
<link rel="stylesheet" type="text/css" href="loop/shared/css/reset.css">
<link rel="stylesheet" type="text/css" href="loop/shared/css/common.css">
<link rel="stylesheet" type="text/css" href="loop/css/panel.css">
<link rel="stylesheet" type="text/css" href="loop/css/contacts.css">
</head>
<body class="panel">
@ -28,7 +27,6 @@
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
<script type="text/javascript" src="loop/shared/js/store.js"></script>
<script type="text/javascript" src="loop/shared/js/activeRoomStore.js"></script>
<script type="text/javascript;version=1.8" src="loop/js/contacts.js"></script>
<script type="text/javascript" src="loop/js/roomStore.js"></script>
<script type="text/javascript" src="loop/js/panel.js"></script>
</body>

View File

@ -1 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><title>Hello_Contacts@1x</title><g fill="none" fill-rule="evenodd"><path d="M100.775283,86.186984 C101.434135,86.2367068 102.099801,86.2620192 102.77138,86.2620192 C110.607172,86.2620192 117.638125,82.8160926 122.431499,77.3569783 L122.431499,77.3569783 C118.230985,71.4565775 111.335219,67.6081731 103.54061,67.6081731 C97.4351053,67.6081731 91.8810912,69.9693867 87.741918,73.8284641 C93.9310034,78.1269515 98.0505206,82.473669 100.775283,86.186984 Z M103.54061,64.6213942 C110.339621,64.6213942 115.851308,59.1097074 115.851308,52.3106971 C115.851308,45.5116868 110.339621,40 103.54061,40 C96.7416002,40 91.2299134,45.5116868 91.2299134,52.3106971 C91.2299134,59.1097074 96.7416002,64.6213942 103.54061,64.6213942 Z" fill-opacity=".8" fill="#D8D8D8"/><path d="M27.813984,86.0869317 C26.8158308,86.2025746 25.8005473,86.2620192 24.7713797,86.2620192 C17.5491945,86.2620192 11.010733,83.3346503 6.27781775,78.601735 C6.18450717,78.5084245 6.09189839,78.4144121 6,78.3197065 C10.1161798,71.878651 17.3296834,67.6081731 25.5406105,67.6081731 C31.5276188,67.6081731 36.9843353,69.8786223 41.0967088,73.6054698 C34.7836878,77.9378687 30.5867176,82.3312911 27.813984,86.0869317 Z M25.5406105,64.6213942 C32.3396208,64.6213942 37.8513076,59.1097074 37.8513076,52.3106971 C37.8513076,45.5116868 32.3396208,40 25.5406105,40 C18.7416002,40 13.2299134,45.5116868 13.2299134,52.3106971 C13.2299134,59.1097074 18.7416002,64.6213942 25.5406105,64.6213942 Z" fill-opacity=".8" fill="#D8D8D8"/><path d="M97.4953223,88.6081931 C89.0769582,98.1957627 76.7288474,104.247671 62.9672376,104.247671 C50.2832749,104.247671 38.8001018,99.1064796 30.4879194,90.7942972 C30.324042,90.6304198 30.1613972,90.4653099 30,90.2989825 C37.2290427,78.9868808 49.8977583,71.486854 64.3181991,71.486854 C78.0074805,71.486854 90.1181688,78.2456142 97.4953223,88.6081931 L97.4953223,88.6081931 Z M64.3181991,66.2413236 C76.2589609,66.2413236 85.9388609,56.5614236 85.9388609,44.6206618 C85.9388609,32.6799 76.2589609,23 64.3181991,23 C52.3774373,23 42.6975373,32.6799 42.6975373,44.6206618 C42.6975373,56.5614236 52.3774373,66.2413236 64.3181991,66.2413236 Z" fill="#00A9DC"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -42,7 +42,6 @@
<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"/>
</g>
<path id="contacts-shape" fill-rule="evenodd" transform="translate(-79.000000, -59.000000)" d="M91.5000066,69.9765672 C91.5000066,68.2109401 91.0859436,65.4999994 88.7968783,65.4999994 C88.5546906,65.4999994 87.5312518,66.5859382 86,66.5859382 C84.4687482,66.5859382 83.4453095,65.4999994 83.2031217,65.4999994 C80.9140564,65.4999994 80.4999935,68.2109401 80.4999935,69.9765672 C80.4999935,71.2421938 81.3437445,72.0000072 82.5859334,72.0000072 L89.4140666,72.0000072 C90.6562555,72.0000072 91.5000066,71.2421938 91.5000066,69.9765672 L91.5000066,69.9765672 L91.5000066,69.9765672 Z M89.0000036,62.9999964 C89.0000036,61.3437444 87.656252,59.9999928 86,59.9999928 C84.343748,59.9999928 82.9999964,61.3437444 82.9999964,62.9999964 C82.9999964,64.6562484 84.343748,66 86,66 C87.656252,66 89.0000036,64.6562484 89.0000036,62.9999964 L89.0000036,62.9999964 L89.0000036,62.9999964 Z" />
<path id="hello-shape" fill-rule="evenodd" transform="translate(-261.000000, -59.000000)" d="M268.273778,60 C264.809073,60 262,62.4730749 262,65.523237 C262,67.0417726 262.697086,68.4174001 263.822897,69.4155754 C263.627626,70.1061164 263.240356,71.0442922 262.474542,71.959559 C262.605451,72.1919211 264.761073,71.3737446 266.2807,70.7617485 C266.907968,70.946111 267.577782,71.046474 268.274868,71.046474 C271.740664,71.046474 274.549737,68.5733991 274.549737,65.523237 C274.549737,62.4730749 271.739573,60 268.274868,60 L268.273778,60 Z M270.15122,63.3119786 C270.609399,63.3119786 270.980306,63.6850671 270.980306,64.1432459 C270.980306,64.6036066 270.609399,64.9756042 270.15122,64.9756042 C269.693041,64.9756042 269.321044,64.6036066 269.321044,64.1432459 C269.321044,63.6850671 269.693041,63.3119786 270.15122,63.3119786 L270.15122,63.3119786 Z M266.36579,63.3119786 C266.823969,63.3119786 267.195966,63.6850671 267.195966,64.1432459 C267.195966,64.6036066 266.823969,64.9756042 266.36579,64.9756042 C265.907611,64.9756042 265.535613,64.6036066 265.535613,64.1432459 C265.535613,63.6850671 265.907611,63.3119786 266.36579,63.3119786 L266.36579,63.3119786 Z M268.283596,69.3675757 L268.258505,69.3664848 L268.233414,69.3675757 C266.557789,69.3675757 264.685801,68.2777646 264.254894,66.4428674 C265.38616,66.9675913 266.967968,67.1966807 268.258505,67.1966807 C269.549042,67.1966807 271.13085,66.9675913 272.262115,66.4428674 C271.8323,68.2777646 269.959221,69.3675757 268.283596,69.3675757 L268.283596,69.3675757 Z" />
<path id="clear-shape" d="M215.55504,63.8820643 C215.688665,63.7472288 215.688665,63.6111212 215.55504,63.4686535 L214.529316,62.463747 C214.382965,62.3174632 214.245523,62.3174632 214.116989,62.463747 L211.98791,64.6007632 L210.042087,62.6621843 C209.983547,62.6049428 209.917371,62.574414 209.841014,62.574414 C209.76593,62.574414 209.694664,62.6049428 209.631034,62.6621843 L208.666394,63.6518263 C208.520044,63.7866618 208.520044,63.9227694 208.666394,64.0639651 L210.586765,65.9821915 L208.466594,68.1192077 C208.408054,68.1789933 208.377511,68.245139 208.377511,68.3227331 C208.377511,68.3990551 208.408054,68.4690169 208.466594,68.5313465 L209.47323,69.5489733 C209.53177,69.6074868 209.599218,69.6380156 209.67812,69.6405597 C209.758295,69.6418317 209.827016,69.612575 209.885556,69.5489733 L212.023543,67.4208613 L213.942642,69.3568962 C214.001182,69.4141377 214.06863,69.4433945 214.150077,69.4433945 C214.228979,69.4433945 214.298973,69.4141377 214.357513,69.3568962 L215.347605,68.3672542 C215.481229,68.2324187 215.481229,68.0963111 215.347605,67.9551154 L213.399236,66.036889 L215.556313,63.8807923 L215.55504,63.8820643 Z M216.958731,61.0505179 C217.642123,61.7335999 218.152441,62.5057242 218.490955,63.3630747 C218.82947,64.2229693 219,65.0994003 219,66 C219,66.8980556 218.82947,67.7783027 218.490955,68.6394694 C218.152441,69.4955479 217.640851,70.2664001 216.958731,70.9507541 C216.274066,71.6338361 215.501591,72.1477376 214.63876,72.4873705 C213.775929,72.8295475 212.897827,73 212.001909,73 C211.103445,73 210.224071,72.8295475 209.36124,72.4873705 C208.498409,72.1464656 207.728479,71.6325641 207.043814,70.9507541 C206.360422,70.2664001 205.848832,69.4955479 205.509045,68.6394694 C205.17053,67.7795748 205,66.8993276 205,66 C205,65.0994003 205.171803,64.2216973 205.514135,63.3592586 C205.855195,62.4968199 206.36424,61.7285117 207.042542,61.0492459 C207.727207,60.3661639 208.498409,59.8548065 209.359967,59.5126295 C210.222798,59.1704525 211.1009,59 212.000636,59 C212.896555,59 213.774657,59.1704525 214.637488,59.5126295 C215.500318,59.8548065 216.272793,60.3674359 216.957458,61.0492459 L216.958731,61.0505179 Z" transform="translate(-205 -59)" fill="#9B9B9B" fill-rule="evenodd"/>
<path id="magnifier-shape" d="M191.499904,64.0475302 C191.499904,66.8377127 193.779753,69.0950604 196.596937,69.0950604 C199.414121,69.0950604 201.693971,66.8377127 201.693971,64.0475302 C201.693971,61.2573477 199.414121,59 196.596937,59 C193.779753,59 191.499904,61.2573477 191.499904,64.0475302 L191.499904,64.0475302 Z M193.353125,64.0475302 C193.353125,62.2762143 194.80852,60.8352201 196.596937,60.8352201 C198.385354,60.8352201 199.840749,62.2762143 199.840749,64.0475302 C199.840749,65.8188461 198.385354,67.2598403 196.596937,67.2598403 C194.80852,67.2598403 193.353125,65.8188461 193.353125,64.0475302 L193.353125,64.0475302 Z M200.600399,69.8475096 L203.046759,72.2704681 C203.324877,72.5458861 203.927017,72.5827885 204.418449,72.3343723 C204.946783,72.0679548 205.150197,70.9941846 204.881979,70.6548624 L202.472522,68.2229033 C202.055794,67.7089697 201.23044,67.7179703 200.693105,68.2508051 C200.211574,68.7278363 200.192672,69.4442832 200.600399,69.8475096 L200.600399,69.8475096 Z" transform="translate(-191 -59)" fill="#9B9B9B" fill-rule="evenodd"/>
@ -52,9 +51,6 @@
<use id="audio-disabled" xlink:href="#audio-shape"/>
<use id="audio-still" xlink:href="#audio-shape"/>
<use id="audio-white" xlink:href="#audio-shape"/>
<use id="contacts" xlink:href="#contacts-shape"/>
<use id="contacts-hover" xlink:href="#contacts-shape"/>
<use id="contacts-active" xlink:href="#contacts-shape"/>
<use id="exit" xlink:href="#exit-shape"/>
<use id="exit-active" xlink:href="#exit-shape"/>
<use id="exit-white" xlink:href="#exit-shape"/>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -36,7 +36,6 @@
<polygon id="add-shape" points="16,6.4 9.6,6.4 9.6,0 6.4,0 6.4,6.4 0,6.4 0,9.6 6.4,9.6 6.4,16 9.6,16 9.6,9.6 16,9.6"/>
<path id="audio-shape" fill-rule="evenodd" d="M11.429,6.857v2.286c0,1.894-1.535,3.429-3.429,3.429 c-1.894,0-3.429-1.535-3.429-3.429V6.857H3.429v2.286c0,2.129,1.458,3.913,3.429,4.422v1.293H6.286 c-0.746,0-1.379,0.477-1.615,1.143h6.658c-0.236-0.665-0.869-1.143-1.615-1.143H9.143v-1.293c1.971-0.508,3.429-2.292,3.429-4.422 V6.857H11.429z M8,12c1.578,0,2.857-1.279,2.857-2.857V2.857C10.857,1.279,9.578,0,8,0C6.422,0,5.143,1.279,5.143,2.857v6.286 C5.143,10.721,6.422,12,8,12z"/>
<path id="block-shape" fill-rule="evenodd" d="M8,0C3.582,0,0,3.582,0,8c0,4.418,3.582,8,8,8 c4.418,0,8-3.582,8-8C16,3.582,12.418,0,8,0z M8,2.442c1.073,0,2.075,0.301,2.926,0.821l-7.673,7.673 C2.718,10.085,2.408,9.079,2.408,8C2.408,4.931,4.911,2.442,8,2.442z M8,13.557c-1.073,0-2.075-0.301-2.926-0.821l7.673-7.673 C13.282,5.915,13.592,6.921,13.592,8C13.592,11.069,11.089,13.557,8,13.557z"/>
<path id="contacts-shape" fill-rule="evenodd" d="M8,6.526c1.802,0,3.263-1.461,3.263-3.263 C11.263,1.461,9.802,0,8,0C6.198,0,4.737,1.461,4.737,3.263C4.737,5.066,6.198,6.526,8,6.526z M14.067,11.421c0,0,0-0.001,0-0.001 c0-1.676-1.397-3.119-3.419-3.807L8.001,10.26L5.354,7.613C3.331,8.3,1.933,9.744,1.933,11.42v0.001H1.93 c0,1.679,0.328,3.246,0.896,4.579h10.348c0.568-1.333,0.896-2.9,0.896-4.579H14.067z"/>
<g id="google-shape">
<path fill-rule="evenodd" d="M8.001,9.278c-0.9,0.03-1.989,0.454-2.144,1.274 c-0.292,1.54,1.284,2.004,2.455,1.932c1.097-0.067,1.737-0.593,1.813-1.26c0.063-0.554-0.184-1.153-0.959-1.644 c-0.142-0.09-0.28-0.185-0.413-0.282C8.504,9.291,8.25,9.27,8.001,9.278z"/>
<path fill-rule="evenodd" d="M7.381,3.409C6.638,3.64,6.32,4.405,6.627,5.61 C6.908,6.708,7.78,7.322,8.569,7.104c0.77-0.213,0.987-1.021,0.847-1.873C9.201,3.929,8.261,3.136,7.381,3.409z"/>
@ -89,9 +88,6 @@
<use id="block-red" xlink:href="#block-shape"/>
<use id="block-hover" xlink:href="#block-shape"/>
<use id="block-active" xlink:href="#block-shape"/>
<use id="contacts" xlink:href="#contacts-shape"/>
<use id="contacts-hover" xlink:href="#contacts-shape"/>
<use id="contacts-active" xlink:href="#contacts-shape"/>
<use id="copy" xlink:href="#copy-shape"/>
<use id="checkmark" xlink:href="#checkmark-shape"/>
<use id="globe" xlink:href="#globe-shape"/>

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -15,13 +15,11 @@ browser.jar:
content/browser/loop/js/conversationAppStore.js (content/js/conversationAppStore.js)
content/browser/loop/js/otconfig.js (content/js/otconfig.js)
content/browser/loop/js/panel.js (content/js/panel.js)
content/browser/loop/js/contacts.js (content/js/contacts.js)
content/browser/loop/js/roomStore.js (content/js/roomStore.js)
content/browser/loop/js/roomViews.js (content/js/roomViews.js)
content/browser/loop/js/feedbackViews.js (content/js/feedbackViews.js)
# Desktop styles
content/browser/loop/css/contacts.css (content/css/contacts.css)
content/browser/loop/css/panel.css (content/css/panel.css)
# Shared styles
@ -86,7 +84,6 @@ browser.jar:
content/browser/loop/shared/img/hello_logo.svg (content/shared/img/hello_logo.svg)
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/empty_conversations.svg (content/shared/img/empty_conversations.svg)
content/browser/loop/shared/img/empty_search.svg (content/shared/img/empty_search.svg)
content/browser/loop/shared/img/animated-spinner.svg (content/shared/img/animated-spinner.svg)

View File

@ -1116,12 +1116,14 @@ this.LoopRooms = {
*/
_setRoomsCache: function(roomsCache) {
LoopRoomsInternal.rooms.clear();
gDirty = true;
if (roomsCache) {
// Need a clone as the internal map is read-only.
for (let [key, value] of roomsCache) {
LoopRoomsInternal.rooms.set(key, value);
}
gDirty = false;
}
}
};

View File

@ -1,963 +0,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/. */
describe("loop.contacts", function() {
"use strict";
var expect = chai.expect;
var TestUtils = React.addons.TestUtils;
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
var fakeAddContactButtonText = "Fake Add Contact Button";
var fakeAddContactTitleText = "Fake Add Contact Title";
var fakeEditContactButtonText = "Fake Edit Contact";
var fakeDoneButtonText = "Fake Done";
var sandbox, fakeWindow, fakeMozLoop, mozL10nGetSpy, listView, notifications;
var fakeManyContacts, fakeFewerContacts;
var oldMozLoop = navigator.mozLoop;
// The fake contacts array is copied each time mozLoop.contacts.getAll() is called.
function getFakeContacts() {
// Return a copy, so that tests that affect it, don't have impact on each other.
return [].concat([
{
id: 1,
_guid: 1,
name: ["Ally Avocado"],
email: [{
"pref": true,
"type": ["work"],
"value": "ally@mail.com"
}],
tel: [{
"pref": true,
"type": ["mobile"],
"value": "+31-6-12345678"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 2,
_guid: 2,
name: ["Bob Banana"],
email: [{
"pref": true,
"type": ["work"],
"value": "bob@gmail.com"
}],
tel: [{
"pref": true,
"type": ["mobile"],
"value": "+1-214-5551234"
}],
category: ["local"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 3,
_guid: 3,
name: ["Caitlin Cantaloupe"],
email: [{
"pref": true,
"type": ["work"],
"value": "caitlin.cant@hotmail.com"
}],
category: ["local"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 4,
_guid: 4,
name: ["Dave Dragonfruit"],
email: [{
"pref": true,
"type": ["work"],
"value": "dd@dragons.net"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 5,
_guid: 5,
name: ["Erin J. Bazile"],
email: [{
"pref": true,
"type": ["work"],
"value": "erinjbazile@armyspy.com"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 6,
_guid: 6,
name: ["Kelly F. Maldanado"],
email: [{
"pref": true,
"type": ["work"],
"value": "kellyfmaldonado@jourrapide.com"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 7,
_guid: 7,
name: ["John J. Brown"],
email: [{
"pref": true,
"type": ["work"],
"value": "johnjbrow@johndoe.com"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748,
blocked: true
}
]);
}
beforeEach(function() {
sandbox = sinon.sandbox.create();
mozL10nGetSpy = sandbox.spy(document.mozL10n, "get");
fakeManyContacts = getFakeContacts();
fakeFewerContacts = fakeManyContacts.slice(0, 4);
fakeMozLoop = navigator.mozLoop = {
getStrings: function(entityName) {
var textContentValue = "fakeText";
if (entityName === "add_contact_title") {
textContentValue = fakeAddContactTitleText;
} else if (entityName === "add_contact_button") {
textContentValue = fakeAddContactButtonText;
} else if (entityName === "edit_contact_title") {
textContentValue = fakeEditContactButtonText;
} else if (entityName === "edit_contact_done_button") {
textContentValue = fakeDoneButtonText;
}
return JSON.stringify({textContent: textContentValue});
},
getLoopPref: function(pref) {
if (pref === "contacts.gravatars.promo") {
return true;
} else if (pref === "contacts.gravatars.show") {
return false;
}
return "";
},
setLoopPref: sandbox.stub(),
getUserAvatar: function() {
if (this.getLoopPref("contacts.gravatars.show")) {
return "gravatarsEnabled";
}
return "gravatarsDisabled";
},
contacts: {
getAll: function(callback) {
callback(null, [].concat(fakeFewerContacts));
},
add: sandbox.stub(),
on: sandbox.stub()
},
calls: {
startDirectCall: sinon.stub(),
clearCallInProgress: sinon.stub()
},
generateUUID: sandbox.stub()
};
fakeWindow = {
close: sandbox.stub(),
addEventListener: function() {},
removeEventListener: function() {}
};
loop.shared.mixins.setRootObject(fakeWindow);
notifications = new loop.shared.models.NotificationCollection();
document.mozL10n.initialize(fakeMozLoop);
});
afterEach(function() {
listView = null;
loop.shared.mixins.setRootObject(window);
navigator.mozLoop = oldMozLoop;
sandbox.restore();
});
describe("GravatarsPromo", function() {
function checkGravatarContacts(enabled) {
var node = listView.getDOMNode();
// When gravatars are enabled, contacts should be rendered with gravatars.
var gravatars = node.querySelectorAll(".contact img[src=gravatarsEnabled]");
expect(gravatars.length).to.equal(enabled ? fakeFewerContacts.length : 0);
// Sanity check the reverse:
gravatars = node.querySelectorAll(".contact img[src=gravatarsDisabled]");
expect(gravatars.length).to.equal(enabled ? 0 : fakeFewerContacts.length);
}
it("should show the gravatars promo box", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.not.equal(null);
var avatars = listView.getDOMNode().querySelectorAll(".contacts-gravatar-avatars img");
expect(avatars).to.have.length(2, "two example avatars are shown");
checkGravatarContacts(false);
});
it("should not show the gravatars promo box when the 'contacts.gravatars.promo' pref is set", function() {
sandbox.stub(fakeMozLoop, "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: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.equal(null);
checkGravatarContacts(true);
});
it("should hide the gravatars promo box when the 'use' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .secondary:last-child"));
sinon.assert.calledTwice(fakeMozLoop.setLoopPref);
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.equal(null);
});
it("should should set the prefs correctly when the 'use' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .secondary:last-child"));
sinon.assert.calledTwice(fakeMozLoop.setLoopPref);
sinon.assert.calledWithExactly(fakeMozLoop.setLoopPref, "contacts.gravatars.promo", false);
sinon.assert.calledWithExactly(fakeMozLoop.setLoopPref, "contacts.gravatars.show", true);
});
it("should hide the gravatars promo box when the 'close' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .secondary:first-child"));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.equal(null);
});
it("should set prefs correctly when the 'close' button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .secondary:first-child"));
sinon.assert.calledOnce(fakeMozLoop.setLoopPref);
sinon.assert.calledWithExactly(fakeMozLoop.setLoopPref,
"contacts.gravatars.promo", false);
});
it("should hide the gravatars promo box when the 'close' X button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .button-close"));
var promo = listView.getDOMNode().querySelector(".contacts-gravatar-promo");
expect(promo).to.equal(null);
});
it("should set prefs correctly when the 'close' X button is clicked", function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
React.addons.TestUtils.Simulate.click(listView.getDOMNode().querySelector(
".contacts-gravatar-promo .button-close"));
sinon.assert.calledOnce(fakeMozLoop.setLoopPref);
sinon.assert.calledWithExactly(fakeMozLoop.setLoopPref,
"contacts.gravatars.promo", false);
});
});
describe("ContactsControllerView - contactAdd", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsControllerView, {
initialSelectedTabComponent: "contactAdd",
mozLoop: fakeMozLoop,
notifications: notifications
}));
});
it("should switch component to Contact List view", function() {
view.switchComponentView("contactList")();
expect(view.refs.contacts_list).to.not.eql(null);
});
});
describe("ContactsControllerView - contactEdit", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsControllerView, {
initialSelectedTabComponent: "contactEdit",
mozLoop: fakeMozLoop,
notifications: notifications
}));
});
it("should switch component to Contact List view", function() {
view.switchComponentView("contactList")();
expect(view.refs.contacts_list).to.not.eql(null);
});
});
describe("ContactsControllerView - contactList", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsControllerView, {
initialSelectedTabComponent: "contactList",
mozLoop: fakeMozLoop,
notifications: notifications
}));
});
it("should switch component to Contact Add view", function() {
view.handleAddEditContact("contactAdd")({});
expect(view.refs.contacts_add).to.not.eql(null);
});
it("should switch component to Contact Edit view", function() {
view.handleAddEditContact("contactEdit")();
expect(view.refs.contacts_edit).to.not.eql(null);
});
});
describe("ContactsList", function () {
var node;
describe("#RenderNoContacts", function() {
beforeEach(function() {
sandbox.stub(fakeMozLoop.contacts, "getAll", function(cb) {
cb(null, []);
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
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_heading2");
sinon.assert.calledWithExactly(mozL10nGetSpy,
"no_contacts_import_or_add2");
});
});
describe("#RenderWithContacts", function() {
beforeEach(function() {
sandbox.stub(fakeMozLoop.contacts, "getAll", function(cb) {
cb(null, [].concat(fakeFewerContacts));
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
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");
});
it("should not render the filter view unless MIN_CONTACTS_FOR_FILTERING",
function() {
var filterView = listView.getDOMNode()
.querySelector(".contact-filter-container");
expect(filterView).to.eql(null);
});
});
describe("ContactsFiltering", function() {
beforeEach(function() {
fakeMozLoop.contacts = {
getAll: function(callback) {
callback(null, [].concat(fakeManyContacts));
},
on: sandbox.stub()
};
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
node = listView.getDOMNode();
});
it("should filter a non-existent user name", function() {
expect(listView.filterContact("foo")(fakeFewerContacts[0]))
.to.eql(false);
});
it("should display search returned no contacts view", function() {
listView.setState({
filter: "foo"
});
var view = node.querySelector(".contact-search-list-empty");
expect(view).to.not.eql(null);
});
it("should display the no search results strings", function() {
listView.setState({
filter: "foo"
});
sinon.assert.calledWithExactly(mozL10nGetSpy,
"contacts_no_search_results");
});
it("should filter the user name correctly", function() {
expect(listView.filterContact("ally")(fakeFewerContacts[0]))
.to.eql(true);
});
it("should filter and render a contact", function() {
listView.setState({
filter: "Ally"
});
var contacts = node.querySelectorAll(".contact");
expect(contacts.length).to.eql(1);
});
it("should render a list of contacts", function() {
var contactList = listView.getDOMNode().querySelectorAll(".contact");
expect(contactList.length).to.eql(fakeManyContacts.length);
});
it("should render the filter view for >= MIN_CONTACTS_FOR_FILTERING",
function() {
var filterView = listView.getDOMNode()
.querySelector(".contact-filter-container");
expect(filterView).to.not.eql(null);
});
it("should filter by name", function() {
var input = listView.getDOMNode()
.querySelector(".contact-filter-container input");
React.addons.TestUtils.Simulate.change(input,
{ target: { value: "Ally" } });
var contactList = listView.getDOMNode().querySelectorAll(".contact");
expect(contactList.length).to.eql(1);
});
it("should filter by email", function() {
var input = listView.getDOMNode()
.querySelector(".contact-filter-container input");
React.addons.TestUtils.Simulate.change(input,
{ target: { value: "@hotmail" } });
var contactList = listView.getDOMNode().querySelectorAll(".contact");
expect(contactList.length).to.eql(1);
});
it("should filter by phone number", function() {
var input = listView.getDOMNode()
.querySelector(".contact-filter-container input");
React.addons.TestUtils.Simulate.change(input,
{ target: { value: "12345678" } });
var contactList = listView.getDOMNode().querySelectorAll(".contact");
expect(contactList.length).to.eql(1);
});
});
describe("#handleContactAddEdit", function() {
beforeEach(function() {
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
});
it("should call switchToContactAdd function when Add Contact button is clicked",
function() {
var addContactBttn = listView.getDOMNode().querySelector(".contact-controls .primary");
React.addons.TestUtils.Simulate.click(addContactBttn);
sinon.assert.calledOnce(listView.props.switchToContactAdd);
});
it("should call switchToContactEdit function when selecting to Edit Contact",
function() {
listView.handleContactAction({}, "edit");
sinon.assert.calledOnce(listView.props.switchToContactEdit);
});
});
describe("#handleImportButtonClick", function() {
beforeEach(function() {
sandbox.stub(fakeMozLoop.contacts, "getAll", function(cb) {
cb(null, []);
});
listView = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, {
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}));
node = listView.getDOMNode();
});
it("should notify the end user from a successful import", function() {
sandbox.stub(notifications, "successL10n");
fakeMozLoop.startImport = function(opts, cb) {
cb(null, {success: 42});
};
listView.handleImportButtonClick();
sinon.assert.calledWithExactly(
notifications.successL10n,
"import_contacts_success_message",
// Num is for the plural selection.
{num: 42, total: 42});
});
it("should notify the end user from any encountered error", function() {
sandbox.stub(notifications, "errorL10n");
fakeMozLoop.startImport = function(opts, cb) {
cb(new Error("fake error"));
};
listView.handleImportButtonClick();
sinon.assert.calledWithExactly(notifications.errorL10n,
"import_contacts_failure_message");
});
});
describe("Individual Contacts", function() {
describe("Contact Menu", function() {
var view, contactMenu, contact;
function mountTestComponent(options) {
var props = _.extend({
mozLoop: fakeMozLoop,
notifications: notifications,
switchToContactAdd: sandbox.stub(),
switchToContactEdit: sandbox.stub()
}, options);
return TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactsList, props));
}
beforeEach(function() {
contact = fakeFewerContacts[0];
fakeMozLoop.contacts.getAll = function(callback) {
callback(null, [contact]);
};
view = mountTestComponent();
node = view.getDOMNode();
// Open the menu
var menuButton = node.querySelector(".icon-contact-menu-button");
var eventStub = {"pageY": 20};
TestUtils.Simulate.click(menuButton, eventStub);
// Get the menu for use in the tests.
contactMenu = node.querySelector(".contact > .dropdown-menu");
});
describe("Video Conversation button", function() {
it("should call startDirectCall when the button is clicked", function() {
TestUtils.Simulate.click(contactMenu.querySelector(".video-call-item"));
sinon.assert.calledOnce(fakeMozLoop.calls.startDirectCall);
sinon.assert.calledWithExactly(fakeMozLoop.calls.startDirectCall,
contact,
CALL_TYPES.AUDIO_VIDEO);
});
it("should close the window when the button is clicked", function() {
TestUtils.Simulate.click(contactMenu.querySelector(".video-call-item"));
sinon.assert.calledOnce(fakeWindow.close);
});
it("should not do anything if the contact is blocked", function() {
contact.blocked = true;
TestUtils.Simulate.click(contactMenu.querySelector(".video-call-item"));
sinon.assert.notCalled(fakeMozLoop.calls.startDirectCall);
sinon.assert.notCalled(fakeWindow.close);
});
});
describe("Audio Conversation button", function() {
it("should call startDirectCall when the button is clicked", function() {
TestUtils.Simulate.click(contactMenu.querySelector(".audio-call-item"));
sinon.assert.calledOnce(fakeMozLoop.calls.startDirectCall);
sinon.assert.calledWithExactly(fakeMozLoop.calls.startDirectCall,
contact,
CALL_TYPES.AUDIO_ONLY);
});
it("should close the window when the button is clicked", function() {
TestUtils.Simulate.click(contactMenu.querySelector(".audio-call-item"));
sinon.assert.calledOnce(fakeWindow.close);
});
it("should not do anything if the contact is blocked", function() {
contact.blocked = true;
TestUtils.Simulate.click(contactMenu.querySelector(".audio-call-item"));
sinon.assert.notCalled(fakeMozLoop.calls.startDirectCall);
sinon.assert.notCalled(fakeWindow.close);
});
});
});
});
});
describe("ContactDetailsForm", function() {
describe("#render", function() {
describe("add mode", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactDetailsForm, {
contactFormData: {},
mode: "add",
mozLoop: fakeMozLoop,
switchToInitialView: sandbox.stub()
}));
});
it("should render 'add' header", function() {
var header = view.getDOMNode().querySelector("header");
expect(header).to.not.equal(null);
expect(header.textContent).to.eql(fakeAddContactTitleText);
});
it("should render name input", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
expect(nameInput).to.not.equal(null);
});
it("should render email input", function() {
var emailInput = view.getDOMNode().querySelector("input[type='email']");
expect(emailInput).to.not.equal(null);
});
it("should render tel input", function() {
var telInput = view.getDOMNode().querySelector("input[type='tel']");
expect(telInput).to.not.equal(null);
});
it("should render 'add contact' button", function() {
var addButton = view.getDOMNode().querySelector(".button-accept");
expect(addButton).to.not.equal(null);
expect(addButton.textContent).to.eql(fakeAddContactButtonText);
});
it("should have all fields required by default", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
var telInput = view.getDOMNode().querySelector("input[type='tel']");
var emailInput = view.getDOMNode().querySelector("input[type='email']");
expect(nameInput.required).to.equal(true);
expect(emailInput.required).to.equal(true);
expect(telInput.required).to.equal(true);
});
it("should have email and tel required after a name is input", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
TestUtils.Simulate.change(nameInput, {target: {value: "Jenny"}});
var telInput = view.getDOMNode().querySelector("input[type='tel']");
var emailInput = view.getDOMNode().querySelector("input[type='email']");
expect(nameInput.required).to.equal(true);
expect(emailInput.required).to.equal(true);
expect(telInput.required).to.equal(true);
});
it("should allow a contact with only a name and a phone number", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
TestUtils.Simulate.change(nameInput, {target: {value: "Jenny"}});
var telInput = view.getDOMNode().querySelector("input[type='tel']");
TestUtils.Simulate.change(telInput, {target: {value: "867-5309"}});
var emailInput = view.getDOMNode().querySelector("input[type='email']");
expect(nameInput.checkValidity()).to.equal(true, "nameInput");
expect(emailInput.required).to.equal(false, "emailInput");
expect(telInput.checkValidity()).to.equal(true, "telInput");
});
it("should allow a contact with only a name and email", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
TestUtils.Simulate.change(nameInput, {target: {value: "Example"}});
var emailInput = view.getDOMNode().querySelector("input[type='email']");
TestUtils.Simulate.change(emailInput, {target: {value: "test@example.com"}});
var telInput = view.getDOMNode().querySelector("input[type='tel']");
expect(nameInput.checkValidity()).to.equal(true);
expect(emailInput.checkValidity()).to.equal(true);
expect(telInput.required).to.equal(false);
});
it("should not allow a contact with only a name", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
TestUtils.Simulate.change(nameInput, {target: {value: "Example"}});
var emailInput = view.getDOMNode().querySelector("input[type='email']");
var telInput = view.getDOMNode().querySelector("input[type='tel']");
expect(nameInput.checkValidity()).to.equal(true);
expect(emailInput.checkValidity()).to.equal(false);
expect(telInput.checkValidity()).to.equal(false);
});
it("should not allow a contact without name", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
var emailInput = view.getDOMNode().querySelector("input[type='email']");
TestUtils.Simulate.change(emailInput, {target: {value: "test@example.com"}});
var telInput = view.getDOMNode().querySelector("input[type='tel']");
TestUtils.Simulate.change(telInput, {target: {value: "867-5309"}});
expect(nameInput.checkValidity()).to.equal(false);
expect(emailInput.checkValidity()).to.equal(true);
expect(telInput.checkValidity()).to.equal(true);
});
it("should call switchToInitialView when Add Contact button is clicked", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
var emailInput = view.getDOMNode().querySelector("input[type='email']");
var addButton = view.getDOMNode().querySelector(".button-accept");
TestUtils.Simulate.change(nameInput, {target: {value: "Example"}});
TestUtils.Simulate.change(emailInput, {target: {value: "test@example.com"}});
React.addons.TestUtils.Simulate.click(addButton);
sinon.assert.calledOnce(view.props.switchToInitialView);
});
it("should call switchToInitialView when Cancel button is clicked", function() {
var cancelButton = view.getDOMNode().querySelector(".button-cancel");
React.addons.TestUtils.Simulate.click(cancelButton);
sinon.assert.calledOnce(view.props.switchToInitialView);
});
});
describe("edit mode", function() {
var view;
beforeEach(function() {
view = TestUtils.renderIntoDocument(
React.createElement(loop.contacts.ContactDetailsForm, {
contactFormData: {},
mode: "edit",
mozLoop: fakeMozLoop,
switchToInitialView: sandbox.stub()
}));
});
it("should render 'edit' header", function() {
var header = view.getDOMNode().querySelector("header");
expect(header).to.not.equal(null);
expect(header.textContent).to.eql(fakeEditContactButtonText);
});
it("should render name input", function() {
var nameInput = view.getDOMNode().querySelector("input[type='text']");
expect(nameInput).to.not.equal(null);
});
it("should render email input", function() {
var emailInput = view.getDOMNode().querySelector("input[type='email']");
expect(emailInput).to.not.equal(null);
});
it("should render tel input", function() {
var telInput = view.getDOMNode().querySelector("input[type='tel']");
expect(telInput).to.not.equal(null);
});
it("should render 'done' button", function() {
var doneButton = view.getDOMNode().querySelector(".button-accept");
expect(doneButton).to.not.equal(null);
expect(doneButton.textContent).to.eql(fakeDoneButtonText);
});
});
});
});
describe("_getPreferred", function() {
it("should return an dummy object if the field doesn't exist", function() {
var obj = loop.contacts._getPreferred({}, "fieldThatDoesntExist");
expect(obj.value).to.eql("");
});
it("should return the preferred value when the field exists", function() {
var correctValue = "correct value";
var fakeContact = {fakeField: [{value: "wrong value"}, {value: correctValue, pref: true}]};
var obj = loop.contacts._getPreferred(fakeContact, "fakeField");
expect(obj.value).to.eql(correctValue);
});
});
describe("_setPreferred", function() {
it("should not set the value on the object if the new value is empty," +
" it didn't exist before, and it is optional", function() {
var contact = {};
loop.contacts._setPreferred(contact, "fakeField", "");
expect(contact).to.not.have.property("fakeField");
});
it("should clear the value on the object if the new value is empty," +
" it existed before, and it is optional", function() {
var contact = {fakeField: [{value: "foobar"}]};
loop.contacts._setPreferred(contact, "fakeField", "");
expect(contact.fakeField[0].value).to.eql("");
});
it("should set the value on the object if the new value is empty," +
" and it did not exist before", function() {
var contact = {fakeField: [{value: "foobar"}]};
loop.contacts._setPreferred(contact, "fakeField", "barbaz");
expect(contact.fakeField[0].value).to.eql("barbaz");
});
});
});

View File

@ -185,7 +185,6 @@ describe("loop.conversation", function() {
it("should display the RoomFailureView for failures", function() {
conversationAppStore.setStoreState({
contact: {},
outgoing: false,
windowType: "failed"
});

View File

@ -62,7 +62,6 @@
<script src="../../content/js/roomViews.js"></script>
<script src="../../content/js/feedbackViews.js"></script>
<script src="../../content/js/conversation.js"></script>
<script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
<script src="../../content/js/panel.js"></script>
<!-- Test scripts -->
@ -71,7 +70,6 @@
<script src="feedbackViews_test.js"></script>
<script src="panel_test.js"></script>
<script src="roomViews_test.js"></script>
<script src="contacts_test.js"></script>
<script src="l10n_test.js"></script>
<script src="roomStore_test.js"></script>
<script>

View File

@ -56,12 +56,6 @@ describe("loop.panel", function() {
getPluralForm: function() {
return "fakeText";
},
contacts: {
getAll: function(callback) {
callback(null, []);
},
on: sandbox.stub()
},
rooms: {
getAll: function(version, callback) {
callback(null, []);

View File

@ -38,8 +38,7 @@ module.exports = function(config) {
// List of files to exclude.
baseConfig.exclude = baseConfig.exclude.concat([
"test/desktop-local/panel_test.js",
"test/desktop-local/contacts_test.js"
"test/desktop-local/panel_test.js"
]);
// Preprocess matching files before serving them to the browser.

View File

@ -34,6 +34,7 @@
"LoopUI": false,
// Other items
"Chat": true,
"WebChannel": true
"WebChannel": true,
"executeSoon": true
}
}

View File

@ -15,14 +15,12 @@ support-files =
[browser_GoogleImporter.js]
skip-if = e10s
[browser_loop_fxa_server.js]
[browser_LoopContacts.js]
[browser_LoopRooms_channel.js]
[browser_mozLoop_appVersionInfo.js]
[browser_mozLoop_context.js]
[browser_mozLoop_prefs.js]
[browser_mozLoop_doNotDisturb.js]
skip-if = buildapp == 'mulet'
[browser_mozLoop_pluralStrings.js]
[browser_mozLoop_socialShare.js]
[browser_mozLoop_sharingListeners.js]
skip-if = e10s

View File

@ -1,23 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This is an integration test from navigator.mozLoop through to the end
* effects - rather than just testing MozLoopAPI alone.
*/
"use strict";
Components.utils.import("resource://gre/modules/Promise.jsm", this);
add_task(loadLoopPanel);
add_task(function* test_mozLoop_pluralStrings() {
Assert.ok(gMozLoopAPI, "mozLoop should exist");
var strings = JSON.parse(gMozLoopAPI.getStrings("import_contacts_success_message"));
Assert.equal(gMozLoopAPI.getPluralForm(1, strings.textContent),
"{{total}} contact was successfully imported.");
Assert.equal(gMozLoopAPI.getPluralForm(3, strings.textContent),
"{{total}} contacts were successfully imported.");
});

View File

@ -43,75 +43,112 @@ add_task(function* test_LoopUI_getters() {
add_task(function* test_doNotDisturb() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
yield MozLoopService.doNotDisturb = true;
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Do not disturb", "Check button has disabled tooltiptext");
yield MozLoopService.doNotDisturb = false;
Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is not in disabled state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
});
add_task(function* test_doNotDisturb_with_login() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
yield MozLoopService.doNotDisturb = true;
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Do not disturb", "Check button has disabled tooltiptext");
MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken;
MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile;
yield MozLoopServiceInternal.notifyStatusChanged("login");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Active conversation", "Check button has active tooltiptext");
yield loadLoopPanel();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state after opening panel");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Do not disturb", "Check button has disabled tooltiptext");
LoopUI.panel.hidePopup();
yield MozLoopService.doNotDisturb = false;
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
MozLoopServiceInternal.fxAOAuthTokenData = null;
yield MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
});
add_task(function* test_error() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
yield MozLoopServiceInternal.setError("testing", {});
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Error!", "Check button has error tooltiptext");
yield MozLoopServiceInternal.clearError("testing");
Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is not in error state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
});
add_task(function* test_error_with_login() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
yield MozLoopServiceInternal.setError("testing", {});
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Error!", "Check button has error tooltiptext");
MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile;
MozLoopServiceInternal.notifyStatusChanged("login");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Error!", "Check button has error tooltiptext");
yield MozLoopServiceInternal.clearError("testing");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
MozLoopServiceInternal.fxAOAuthProfile = null;
MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
});
add_task(function* test_active() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken;
MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile;
yield MozLoopServiceInternal.notifyStatusChanged("login");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Active conversation", "Check button has active tooltiptext");
yield loadLoopPanel();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state after opening panel");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
LoopUI.panel.hidePopup();
MozLoopServiceInternal.fxAOAuthTokenData = null;
MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
});
add_task(function* test_room_participants() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
LoopRoomsInternal.rooms.set("test_room", {participants: [{displayName: "hugh", id: "008"}]});
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
let roomsCache = new Map([[ "test_room", {participants: [{displayName: "hugh", id: "008", owner: true}]} ]]);
LoopRooms._setRoomsCache(roomsCache);
MozLoopServiceInternal.notifyStatusChanged();
// Since we're changing the rooms map directly, we're expecting it to be a synchronous operation.
// But Promises have the inherent property of then-ables being async so even though the operation returns immediately,
// because the cache is hit, the promise won't be resolved until after the next tick.
// And that's what the line below does, waits until the next tick
yield new Promise(resolve => executeSoon(resolve));
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
LoopRoomsInternal.rooms.set("test_room", {participants: []});
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Active conversation", "Check button has active tooltiptext");
roomsCache.set("test_room", {participants: [{displayName: "hugh", id: "008", owner: false}]});
LoopRooms._setRoomsCache(roomsCache);
MozLoopServiceInternal.notifyStatusChanged();
yield new Promise(resolve => executeSoon(resolve));
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Someone is waiting for you in a conversation", "Check button has participantswaiting tooltiptext");
roomsCache.set("test_room", {participants: []});
LoopRooms._setRoomsCache(roomsCache);
MozLoopServiceInternal.notifyStatusChanged();
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
LoopRoomsInternal.rooms.delete("test_room");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
LoopRooms._setRoomsCache();
});
add_task(function* test_panelToggle_on_click() {
@ -130,10 +167,13 @@ add_task(function* test_panelToggle_on_click() {
add_task(function* test_screen_share() {
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
MozLoopService.setScreenShareState("1", true);
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "action", "Check button is in action state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "You are sharing your screen", "Check button has sharingscreen tooltiptext");
MozLoopService.setScreenShareState("1", false);
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("tooltiptext"), "Start a conversation", "Check button has default tooltiptext");
});
add_task(function* test_private_browsing_window() {

View File

@ -103,103 +103,6 @@ var fakeRooms = [
}
];
var fakeManyContacts = [{
id: 1,
_guid: 1,
name: ["Ally Avocado"],
email: [{
"pref": true,
"type": ["work"],
"value": "ally@mail.com"
}],
tel: [{
"pref": true,
"type": ["mobile"],
"value": "+31-6-12345678"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 2,
_guid: 2,
name: ["Bob Banana"],
email: [{
"pref": true,
"type": ["work"],
"value": "bob@gmail.com"
}],
tel: [{
"pref": true,
"type": ["mobile"],
"value": "+1-214-5551234"
}],
category: ["local"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 3,
_guid: 3,
name: ["Caitlin Cantaloupe"],
email: [{
"pref": true,
"type": ["work"],
"value": "caitlin.cant@hotmail.com"
}],
category: ["local"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 4,
_guid: 4,
name: ["Dave Dragonfruit"],
email: [{
"pref": true,
"type": ["work"],
"value": "dd@dragons.net"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 5,
_guid: 5,
name: ["Erin J. Bazile"],
email: [{
"pref": true,
"type": ["work"],
"value": "erinjbazile@armyspy.com"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 6,
_guid: 6,
name: ["Kelly F. Maldanado"],
email: [{
"pref": true,
"type": ["work"],
"value": "kellyfmaldonado@jourrapide.com"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}, {
id: 7,
_guid: 7,
name: ["John J. Brown"],
email: [{
"pref": true,
"type": ["work"],
"value": "johnjbrow@johndoe.com"
}],
category: ["google"],
published: 1406798311748,
updated: 1406798311748
}];
var fakeFewerContacts = fakeManyContacts.slice(0, 4);
(function() {
"use strict";
@ -214,10 +117,6 @@ var fakeFewerContacts = fakeManyContacts.slice(0, 4);
switch(pref) {
// Ensure we skip FTE completely.
case "gettingStarted.seen":
case "contacts.gravatars.promo":
return true;
case "contacts.gravatars.show":
return false;
}
},
hasEncryptionKey: true,
@ -235,12 +134,6 @@ var fakeFewerContacts = fakeManyContacts.slice(0, 4);
url: "https://www.example.com"
});
},
contacts: {
getAll: function(callback) {
callback(null, [].concat(fakeManyContacts));
},
on: function() {}
},
rooms: {
getAll: function(version, callback) {
callback(null, [].concat(fakeRooms));

View File

@ -11,8 +11,6 @@
<link rel="stylesheet" type="text/css" href="../content/shared/css/conversation.css">
<link class="fx-embedded-panel" rel="stylesheet" type="text/css"
href="../content/css/panel.css">
<link class="fx-embedded-panel" rel="stylesheet" type="text/css"
href="../content/css/contacts.css">
<link class="standalone" rel="stylesheet" type="text/css"
href="../content/css/webapp.css">
<link rel="stylesheet" type="text/css" href="ui-showcase.css">
@ -50,22 +48,6 @@
<script src="../content/js/roomViews.js"></script>
<script src="../standalone/content/js/webapp.js"></script>
<script src="../standalone/content/js/standaloneRoomViews.js"></script>
<script type="text/javascript;version=1.8" src="../content/js/contacts.js"></script>
<script>
if (!loop.contacts) {
// For browsers that don't support ES6 without special flags (all but Fx
// at the moment), we shim the contacts namespace with its most barebone
// implementation.
loop.contacts = {
ContactsList: React.createClass({render: function() {
return React.DOM.div();
}}),
ContactDetailsForm: React.createClass({render: function() {
return React.DOM.div();
}})
};
}
</script>
<script src="../content/js/panel.js"></script>
<script src="../content/js/conversation.js"></script>
<script src="react-frame-component.js"></script>

View File

@ -165,7 +165,6 @@ body {
}
/* Force dropdown menus to display. */
.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 fakeManyContacts:true fakeFewerContacts:true */
/* global Frame:false uncaughtError:true */
(function() {
"use strict";
@ -17,17 +17,12 @@
// 1.1 Panel
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
var ContactDetailsForm = loop.contacts.ContactDetailsForm;
var ContactDropdown = loop.contacts.ContactDropdown;
var ContactDetail = loop.contacts.ContactDetail;
var GettingStartedView = loop.panel.GettingStartedView;
// 1.2. Conversation Window
var DesktopRoomEditContextView = loop.roomViews.DesktopRoomEditContextView;
var RoomFailureView = loop.roomViews.RoomFailureView;
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
// 2. Standalone webapp
var HomeView = loop.webapp.HomeView;
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
@ -441,27 +436,6 @@
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
mozLoopNoContacts.contacts.getAll = function(callback) {
callback(null, []);
};
mozLoopNoContacts.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
mozLoopNoContacts.contacts.getAll = function(callback) {
callback(null, []);
};
var mozLoopNoContactsFilter = _.cloneDeep(navigator.mozLoop);
mozLoopNoContactsFilter.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
mozLoopNoContactsFilter.contacts.getAll = function(callback) {
callback(null, fakeFewerContacts); // Defined in fake-mozLoop.js.
};
var firstTimeUseMozLoop = _.cloneDeep(navigator.mozLoop);
firstTimeUseMozLoop.getLoopPref = function(prop) {
if (prop === "gettingStarted.seen") {
@ -471,13 +445,6 @@
return true;
};
var mockContact = {
name: ["Mr Smith"],
email: [{
value: "smith@invalid.com"
}]
};
var mockClient = {
requestCallUrlInfo: noop
};
@ -530,8 +497,7 @@
"volume-disabled", "clear", "magnifier"
],
"16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
"block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
"contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"block", "block-red", "block-hover", "block-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"google-active", "history", "history-hover", "history-active", "leave",
"screen-white", "screenmute-white", "settings", "settings-hover", "settings-active",
"share-darkgrey", "tag", "tag-hover", "tag-active", "trash", "unblock",
@ -752,62 +718,6 @@
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact list tab",
width: 330},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mockMozLoopLoggedIn,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts"})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact list tab (no search filter)",
width: 332},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mozLoopNoContactsFilter,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts"})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact list tab long email",
width: 330},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mockMozLoopLoggedInLongEmail,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts"})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact list tab (no contacts)",
width: 330},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
mozLoop: mozLoopNoContacts,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts"})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
@ -833,107 +743,6 @@
notifications: errNotifications,
roomStore: roomStore})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact import success",
width: 330},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {dispatcher: dispatcher,
mozLoop: mockMozLoopLoggedIn,
notifications: new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}]),
roomStore: roomStore,
selectedTab: "contacts"})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact import error",
width: 330},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {dispatcher: dispatcher,
mozLoop: mockMozLoopLoggedIn,
notifications: new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}]),
roomStore: roomStore,
selectedTab: "contacts"})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact Form - Add",
width: 330},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
initialSelectedTabComponent: "contactAdd",
mozLoop: mockMozLoopLoggedIn,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts",
userProfile: {email: "test@example.com"}})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 410,
summary: "Contact Form - Edit",
width: 330},
React.createElement("div", {className: "panel"},
React.createElement(PanelView, {client: mockClient,
dispatcher: dispatcher,
initialSelectedTabComponent: "contactEdit",
mozLoop: mockMozLoopLoggedIn,
notifications: notifications,
roomStore: roomStore,
selectedTab: "contacts",
userProfile: {email: "test@example.com"}})
)
)
),
React.createElement(Section, {name: "ContactDetail"},
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 50,
summary: "ContactDetail",
width: 334},
React.createElement("div", {className: "panel force-menu-show"},
React.createElement(ContactDetail, {contact: fakeManyContacts[0],
getContainerCoordinates: function() { return {"top": 0, "height": 0 }; },
handleContactAction: function() {}})
)
)
),
React.createElement(Section, {name: "ContactDropdown"},
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 272,
summary: "ContactDropdown not blocked can edit",
width: 300},
React.createElement("div", {className: "panel"},
React.createElement(ContactDropdown, {blocked: false,
canEdit: true,
eventPosY: 0,
getContainerCoordinates: function() { return {"top": 0, "height": 0 }; },
handleAction: function () {}})
)
),
React.createElement(FramedExample, {cssClass: "fx-embedded-panel",
dashed: true,
height: 272,
summary: "ContactDropdown blocked can't edit",
width: 300},
React.createElement("div", {className: "panel"},
React.createElement(ContactDropdown, {blocked: true,
canEdit: false,
eventPosY: 0,
getContainerCoordinates: function() { return {"top": 0, "height": 0 }; },
handleAction: function () {}})
)
)
),

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 fakeManyContacts:true fakeFewerContacts:true */
/* global Frame:false uncaughtError:true */
(function() {
"use strict";
@ -17,17 +17,12 @@
// 1.1 Panel
var PanelView = loop.panel.PanelView;
var SignInRequestView = loop.panel.SignInRequestView;
var ContactDetailsForm = loop.contacts.ContactDetailsForm;
var ContactDropdown = loop.contacts.ContactDropdown;
var ContactDetail = loop.contacts.ContactDetail;
var GettingStartedView = loop.panel.GettingStartedView;
// 1.2. Conversation Window
var DesktopRoomEditContextView = loop.roomViews.DesktopRoomEditContextView;
var RoomFailureView = loop.roomViews.RoomFailureView;
var DesktopRoomConversationView = loop.roomViews.DesktopRoomConversationView;
// 2. Standalone webapp
var HomeView = loop.webapp.HomeView;
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
@ -441,27 +436,6 @@
var mockMozLoopRooms = _.extend({}, navigator.mozLoop);
var mozLoopNoContacts = _.cloneDeep(navigator.mozLoop);
mozLoopNoContacts.contacts.getAll = function(callback) {
callback(null, []);
};
mozLoopNoContacts.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
mozLoopNoContacts.contacts.getAll = function(callback) {
callback(null, []);
};
var mozLoopNoContactsFilter = _.cloneDeep(navigator.mozLoop);
mozLoopNoContactsFilter.userProfile = {
email: "reallyreallylongtext@example.com",
uid: "0354b278a381d3cb408bb46ffc01266"
};
mozLoopNoContactsFilter.contacts.getAll = function(callback) {
callback(null, fakeFewerContacts); // Defined in fake-mozLoop.js.
};
var firstTimeUseMozLoop = _.cloneDeep(navigator.mozLoop);
firstTimeUseMozLoop.getLoopPref = function(prop) {
if (prop === "gettingStarted.seen") {
@ -471,13 +445,6 @@
return true;
};
var mockContact = {
name: ["Mr Smith"],
email: [{
value: "smith@invalid.com"
}]
};
var mockClient = {
requestCallUrlInfo: noop
};
@ -530,8 +497,7 @@
"volume-disabled", "clear", "magnifier"
],
"16x16": ["add", "add-hover", "add-active", "audio", "audio-hover", "audio-active",
"block", "block-red", "block-hover", "block-active", "contacts", "contacts-hover",
"contacts-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"block", "block-red", "block-hover", "block-active", "copy", "checkmark", "delete", "globe", "google", "google-hover",
"google-active", "history", "history-hover", "history-active", "leave",
"screen-white", "screenmute-white", "settings", "settings-hover", "settings-active",
"share-darkgrey", "tag", "tag-hover", "tag-active", "trash", "unblock",
@ -752,62 +718,6 @@
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact list tab"
width={330}>
<div className="panel">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mockMozLoopLoggedIn}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts" />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact list tab (no search filter)"
width={332}>
<div className="panel">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mozLoopNoContactsFilter}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts" />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact list tab long email"
width={330}>
<div className="panel">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mockMozLoopLoggedInLongEmail}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts" />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact list tab (no contacts)"
width={330}>
<div className="panel">
<PanelView client={mockClient}
dispatcher={dispatcher}
mozLoop={mozLoopNoContacts}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts" />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
@ -834,107 +744,6 @@
roomStore={roomStore} />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact import success"
width={330}>
<div className="panel">
<PanelView dispatcher={dispatcher}
mozLoop={mockMozLoopLoggedIn}
notifications={new loop.shared.models.NotificationCollection([{level: "success", message: "Import success"}])}
roomStore={roomStore}
selectedTab="contacts" />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact import error"
width={330}>
<div className="panel">
<PanelView dispatcher={dispatcher}
mozLoop={mockMozLoopLoggedIn}
notifications={new loop.shared.models.NotificationCollection([{level: "error", message: "Import error"}])}
roomStore={roomStore}
selectedTab="contacts" />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact Form - Add"
width={330}>
<div className="panel">
<PanelView client={mockClient}
dispatcher={dispatcher}
initialSelectedTabComponent="contactAdd"
mozLoop={mockMozLoopLoggedIn}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts"
userProfile={{email: "test@example.com"}} />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={410}
summary="Contact Form - Edit"
width={330}>
<div className="panel">
<PanelView client={mockClient}
dispatcher={dispatcher}
initialSelectedTabComponent="contactEdit"
mozLoop={mockMozLoopLoggedIn}
notifications={notifications}
roomStore={roomStore}
selectedTab="contacts"
userProfile={{email: "test@example.com"}} />
</div>
</FramedExample>
</Section>
<Section name="ContactDetail">
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={50}
summary="ContactDetail"
width={334}>
<div className="panel force-menu-show">
<ContactDetail contact={fakeManyContacts[0]}
getContainerCoordinates={function() { return {"top": 0, "height": 0 }; }}
handleContactAction={function() {}} />
</div>
</FramedExample>
</Section>
<Section name="ContactDropdown">
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={272}
summary="ContactDropdown not blocked can edit"
width={300}>
<div className="panel">
<ContactDropdown blocked={false}
canEdit={true}
eventPosY={0}
getContainerCoordinates={function() { return {"top": 0, "height": 0 }; }}
handleAction={function () {}} />
</div>
</FramedExample>
<FramedExample cssClass="fx-embedded-panel"
dashed={true}
height={272}
summary="ContactDropdown blocked can't edit"
width={300}>
<div className="panel">
<ContactDropdown blocked={true}
canEdit={false}
eventPosY={0}
getContainerCoordinates={function() { return {"top": 0, "height": 0 }; }}
handleAction={function () {}} />
</div>
</FramedExample>
</Section>
<Section name="ConversationToolbar">

View File

@ -251,6 +251,7 @@ var tests = [
chatWin.document.querySelector(".btn-email").click();
});
});
setupFakeRoom();
LoopRooms.open("fakeTourRoom");
}),
taskify(function* test_arrow_panel_position() {
@ -349,6 +350,7 @@ function setupFakeRoom() {
for (let prop of ["roomToken", "roomOwner", "roomUrl", "participants"])
room[prop] = "fakeTourRoom";
room.decryptedContext = {roomName: "fakeTourRoom"};
room.participants = [];
let roomsMap = new Map([
[room.roomToken, room]
]);

View File

@ -1,3 +1,3 @@
This is the pdf.js project output, https://github.com/mozilla/pdf.js
Current extension version is: 1.1.469
Current extension version is: 1.1.527

View File

@ -92,7 +92,8 @@ var DEFAULT_PREFERENCES = {
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
useOnlyCssZoom: false
useOnlyCssZoom: false,
externalLinkTarget: 0,
};

View File

@ -998,18 +998,8 @@ PdfStreamConverter.prototype = {
var ssm = Cc['@mozilla.org/scriptsecuritymanager;1']
.getService(Ci.nsIScriptSecurityManager);
var uri = NetUtil.newURI(PDF_VIEWER_WEB_PAGE, null, null);
// FF16 and below had getCodebasePrincipal, it was replaced by
// getNoAppCodebasePrincipal (bug 758258).
// FF 43 added createCodebasePrincipal to replace getNoAppCodebasePrincipal
// (bug 1165272).
var resourcePrincipal
if ('createCodebasePrincipal' in ssm) {
resourcePrincipal = ssm.createCodebasePrincipal(uri, {});
} else if ('getNoAppCodebasePrincipal' in ssm) {
resourcePrincipal = ssm.getNoAppCodebasePrincipal(uri)
} else {
resourcePrincipal = ssm.getCodebasePrincipal(uri);
}
var resourcePrincipal;
resourcePrincipal = ssm.createCodebasePrincipal(uri, {});
aRequest.owner = resourcePrincipal;
channel.asyncOpen(proxy, aContext);
},

View File

@ -50,7 +50,8 @@ var DEFAULT_PREFERENCES = {
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
useOnlyCssZoom: false
useOnlyCssZoom: false,
externalLinkTarget: 0,
};

View File

@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '1.1.469';
PDFJS.build = 'f06aa6a';
PDFJS.version = '1.1.527';
PDFJS.build = '2096a2a';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -340,6 +340,38 @@ function shadow(obj, prop, value) {
}
PDFJS.shadow = shadow;
var LinkTarget = PDFJS.LinkTarget = {
NONE: 0, // Default value.
SELF: 1,
BLANK: 2,
PARENT: 3,
TOP: 4,
};
var LinkTargetStringMap = [
'',
'_self',
'_blank',
'_parent',
'_top'
];
function isExternalLinkTargetSet() {
switch (PDFJS.externalLinkTarget) {
case LinkTarget.NONE:
return false;
case LinkTarget.SELF:
case LinkTarget.BLANK:
case LinkTarget.PARENT:
case LinkTarget.TOP:
return true;
}
warn('PDFJS.externalLinkTarget is invalid: ' + PDFJS.externalLinkTarget);
// Reset the external link target, to suppress further warnings.
PDFJS.externalLinkTarget = LinkTarget.NONE;
return false;
}
PDFJS.isExternalLinkTargetSet = isExternalLinkTargetSet;
var PasswordResponses = PDFJS.PasswordResponses = {
NEED_PASSWORD: 1,
INCORRECT_PASSWORD: 2
@ -1435,14 +1467,27 @@ PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ?
16777216 : PDFJS.maxCanvasPixels);
/**
* Opens external links in a new window if enabled. The default behavior opens
* external links in the PDF.js window.
* (Deprecated) Opens external links in a new window if enabled.
* The default behavior opens external links in the PDF.js window.
* @var {boolean}
*/
PDFJS.openExternalLinksInNewWindow = (
PDFJS.openExternalLinksInNewWindow === undefined ?
false : PDFJS.openExternalLinksInNewWindow);
/**
* Specifies the |target| attribute for external links.
* The constants from PDFJS.LinkTarget should be used:
* - NONE [default]
* - SELF
* - BLANK
* - PARENT
* - TOP
* @var {number}
*/
PDFJS.externalLinkTarget = (PDFJS.externalLinkTarget === undefined ?
PDFJS.LinkTarget.NONE : PDFJS.externalLinkTarget);
/**
* Determines if we can eval strings as JS. Primarily used to improve
* performance for font rendering.
@ -4082,12 +4127,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.fill();
ctx.mozFillRule = 'nonzero';
} else {
try {
ctx.fill('evenodd');
} catch (ex) {
// shouldn't really happen, but browsers might think differently
ctx.fill();
}
ctx.fill('evenodd');
}
this.pendingEOFill = false;
} else {
@ -4541,8 +4581,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var pattern;
if (IR[0] === 'TilingPattern') {
var color = IR[1];
var baseTransform = this.baseTransform ||
this.ctx.mozCurrentTransform.slice();
pattern = new TilingPattern(IR, color, this.ctx, this.objs,
this.commonObjs, this.baseTransform);
this.commonObjs, baseTransform);
} else {
pattern = getShadingPatternFromIR(IR);
}
@ -5098,12 +5140,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.clip();
ctx.mozFillRule = 'nonzero';
} else {
try {
ctx.clip('evenodd');
} catch (ex) {
// shouldn't really happen, but browsers might think differently
ctx.clip();
}
ctx.clip('evenodd');
}
} else {
ctx.clip();
@ -6329,8 +6366,9 @@ var AnnotationUtils = (function AnnotationUtilsClosure() {
var link = document.createElement('a');
link.href = link.title = item.url || '';
if (item.url && PDFJS.openExternalLinksInNewWindow) {
link.target = '_blank';
if (item.url && isExternalLinkTargetSet()) {
link.target = LinkTargetStringMap[PDFJS.externalLinkTarget];
}
container.appendChild(link);

View File

@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
PDFJS.version = '1.1.469';
PDFJS.build = 'f06aa6a';
PDFJS.version = '1.1.527';
PDFJS.build = '2096a2a';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
@ -340,6 +340,38 @@ function shadow(obj, prop, value) {
}
PDFJS.shadow = shadow;
var LinkTarget = PDFJS.LinkTarget = {
NONE: 0, // Default value.
SELF: 1,
BLANK: 2,
PARENT: 3,
TOP: 4,
};
var LinkTargetStringMap = [
'',
'_self',
'_blank',
'_parent',
'_top'
];
function isExternalLinkTargetSet() {
switch (PDFJS.externalLinkTarget) {
case LinkTarget.NONE:
return false;
case LinkTarget.SELF:
case LinkTarget.BLANK:
case LinkTarget.PARENT:
case LinkTarget.TOP:
return true;
}
warn('PDFJS.externalLinkTarget is invalid: ' + PDFJS.externalLinkTarget);
// Reset the external link target, to suppress further warnings.
PDFJS.externalLinkTarget = LinkTarget.NONE;
return false;
}
PDFJS.isExternalLinkTargetSet = isExternalLinkTargetSet;
var PasswordResponses = PDFJS.PasswordResponses = {
NEED_PASSWORD: 1,
INCORRECT_PASSWORD: 2
@ -2654,6 +2686,22 @@ var Dict = (function DictClosure() {
return Promise.resolve(value);
},
// Same as get(), but dereferences all elements if the result is an Array.
getArray: function Dict_getArray(key1, key2, key3) {
var value = this.get(key1, key2, key3);
if (!isArray(value)) {
return value;
}
value = value.slice(); // Ensure that we don't modify the Dict data.
for (var i = 0, ii = value.length; i < ii; i++) {
if (!isRef(value[i])) {
continue;
}
value[i] = this.xref.fetch(value[i]);
}
return value;
},
// no dereferencing
getRaw: function Dict_getRaw(key) {
return this.map[key];
@ -3563,6 +3611,7 @@ var XRef = (function XRefClosure() {
}
return skipped;
}
var objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114,
101, 102]);
@ -3600,7 +3649,7 @@ var XRef = (function XRefClosure() {
position += skipUntil(buffer, position, trailerBytes);
trailers.push(position);
position += skipUntil(buffer, position, startxrefBytes);
} else if ((m = /^(\d+)\s+(\d+)\s+obj\b/.exec(token))) {
} else if ((m = objRegExp.exec(token))) {
if (typeof this.entries[m[1]] === 'undefined') {
this.entries[m[1]] = {
offset: position - stream.start,
@ -3621,6 +3670,10 @@ var XRef = (function XRefClosure() {
}
position += contentLength;
} else if (token.indexOf('trailer') === 0 &&
(token.length === 7 || /\s/.test(token[7]))) {
trailers.push(position);
position += skipUntil(buffer, position, startxrefBytes);
} else {
position += token.length + 1;
}
@ -10296,8 +10349,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
xobj, smask,
operatorList,
initialState) {
var matrix = xobj.dict.get('Matrix');
var bbox = xobj.dict.get('BBox');
var matrix = xobj.dict.getArray('Matrix');
var bbox = xobj.dict.getArray('BBox');
var group = xobj.dict.get('Group');
if (group) {
var groupOptions = {
@ -11550,7 +11603,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (cmap instanceof IdentityCMap) {
return new IdentityToUnicodeMap(0, 0xFFFF);
}
var map = [];
var map = new Array(cmap.length);
// Convert UTF-16BE
// NOTE: cmap can be a sparse array, so use forEach instead of for(;;)
// to iterate over all keys.
@ -13163,6 +13216,10 @@ var CMap = (function CMapClosure() {
out.length = 1;
},
get length() {
return this._map.length;
},
get isIdentityCMap() {
if (!(this.name === 'Identity-H' || this.name === 'Identity-V')) {
return false;
@ -13239,6 +13296,10 @@ var IdentityCMap = (function IdentityCMapClosure() {
readCharCode: CMap.prototype.readCharCode,
get length() {
return 0x10000;
},
get isIdentityCMap() {
error('should not access .isIdentityCMap');
}
@ -30193,7 +30254,7 @@ var Parser = (function ParserClosure() {
var stream = lexer.stream;
// Parse dictionary.
var dict = new Dict(null);
var dict = new Dict(this.xref);
while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
if (!isName(this.buf1)) {
error('Dictionary key must be a name object');
@ -30207,7 +30268,7 @@ var Parser = (function ParserClosure() {
}
// Extract the name of the first (i.e. the current) image filter.
var filter = this.fetchIfRef(dict.get('Filter', 'F')), filterName;
var filter = dict.get('Filter', 'F'), filterName;
if (isName(filter)) {
filterName = filter.name;
} else if (isArray(filter) && isName(filter[0])) {
@ -30268,10 +30329,6 @@ var Parser = (function ParserClosure() {
return imageStream;
},
fetchIfRef: function Parser_fetchIfRef(obj) {
// not relying on the xref.fetchIfRef -- xref might not be set
return (isRef(obj) ? this.xref.fetch(obj) : obj);
},
makeStream: function Parser_makeStream(dict, cipherTransform) {
var lexer = this.lexer;
var stream = lexer.stream;
@ -30281,7 +30338,7 @@ var Parser = (function ParserClosure() {
var pos = stream.pos - 1;
// get length
var length = this.fetchIfRef(dict.get('Length'));
var length = dict.get('Length');
if (!isInt(length)) {
info('Bad ' + length + ' attribute in stream');
length = 0;
@ -30351,8 +30408,8 @@ var Parser = (function ParserClosure() {
return stream;
},
filter: function Parser_filter(stream, dict, length) {
var filter = this.fetchIfRef(dict.get('Filter', 'F'));
var params = this.fetchIfRef(dict.get('DecodeParms', 'DP'));
var filter = dict.get('Filter', 'F');
var params = dict.get('DecodeParms', 'DP');
if (isName(filter)) {
return this.makeFilter(stream, filter.name, length, params);
}
@ -30384,8 +30441,8 @@ var Parser = (function ParserClosure() {
return new NullStream(stream);
}
try {
if (params) {
params = this.fetchIfRef(params);
if (params && this.xref) {
params = this.xref.fetchIfRef(params);
}
var xrefStreamStats = this.xref.stats.streamTypes;
if (name === 'FlateDecode' || name === 'Fl') {

View File

@ -142,22 +142,26 @@ function getOutputScale(ctx) {
/**
* Scrolls specified element into view of its parent.
* element {Object} The element to be visible.
* spot {Object} An object with optional top and left properties,
* specifying the offset from the top left edge.
* @param {Object} element - The element to be visible.
* @param {Object} spot - An object with optional top and left properties,
* specifying the offset from the top left edge.
* @param {boolean} skipOverflowHiddenElements - Ignore elements that have
* the CSS rule `overflow: hidden;` set. The default is false.
*/
function scrollIntoView(element, spot) {
function scrollIntoView(element, spot, skipOverflowHiddenElements) {
// Assuming offsetParent is available (it's not available when viewer is in
// hidden iframe or object). We have to scroll: if the offsetParent is not set
// producing the error. See also animationStartedClosure.
var parent = element.offsetParent;
var offsetY = element.offsetTop + element.clientTop;
var offsetX = element.offsetLeft + element.clientLeft;
if (!parent) {
console.error('offsetParent is not set -- cannot scroll');
return;
}
while (parent.clientHeight === parent.scrollHeight) {
var checkOverflow = skipOverflowHiddenElements || false;
var offsetY = element.offsetTop + element.clientTop;
var offsetX = element.offsetLeft + element.clientLeft;
while (parent.clientHeight === parent.scrollHeight ||
(checkOverflow && getComputedStyle(parent).overflow === 'hidden')) {
if (parent.dataset._scaleY) {
offsetY /= parent.dataset._scaleY;
offsetX /= parent.dataset._scaleX;
@ -458,7 +462,8 @@ var DEFAULT_PREFERENCES = {
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
useOnlyCssZoom: false
useOnlyCssZoom: false,
externalLinkTarget: 0,
};
@ -1272,10 +1277,12 @@ var PDFFindController = (function PDFFindControllerClosure() {
pageIndex, index, elements, beginIdx, endIdx) {
if (this.selected.matchIdx === index &&
this.selected.pageIdx === pageIndex) {
scrollIntoView(elements[beginIdx], {
var spot = {
top: FIND_SCROLL_OFFSET_TOP,
left: FIND_SCROLL_OFFSET_LEFT
});
};
scrollIntoView(elements[beginIdx], spot,
/* skipOverflowHiddenElements = */ true);
}
},
@ -1691,38 +1698,74 @@ var PDFHistory = (function () {
var self = this;
window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
evt.preventDefault();
evt.stopPropagation();
if (!self.historyUnlocked) {
return;
}
if (evt.state) {
// Move back/forward in the history.
self._goTo(evt.state);
} else {
// Handle the user modifying the hash of a loaded document.
self.previousHash = window.location.hash.substring(1);
return;
}
// If the history is empty when the hash changes,
// update the previous entry in the browser history.
if (self.uid === 0) {
var previousParams = (self.previousHash && self.currentBookmark &&
// If the state is not set, then the user tried to navigate to a
// different hash by manually editing the URL and pressing Enter, or by
// clicking on an in-page link (e.g. the "current view" link).
// Save the current view state to the browser history.
// Note: In Firefox, history.null could also be null after an in-page
// navigation to the same URL, and without dispatching the popstate
// event: https://bugzilla.mozilla.org/show_bug.cgi?id=1183881
if (self.uid === 0) {
// Replace the previous state if it was not explicitly set.
var previousParams = (self.previousHash && self.currentBookmark &&
self.previousHash !== self.currentBookmark) ?
{hash: self.currentBookmark, page: self.currentPage} :
{page: 1};
self.historyUnlocked = false;
self.allowHashChange = false;
window.history.back();
self._pushToHistory(previousParams, false, true);
window.history.forward();
self.historyUnlocked = true;
}
self._pushToHistory({hash: self.previousHash}, false, true);
self._updatePreviousBookmark();
replacePreviousHistoryState(previousParams, function() {
updateHistoryWithCurrentHash();
});
} else {
updateHistoryWithCurrentHash();
}
}, false);
function updateHistoryWithCurrentHash() {
self.previousHash = window.location.hash.slice(1);
self._pushToHistory({hash: self.previousHash}, false, true);
self._updatePreviousBookmark();
}
function replacePreviousHistoryState(params, callback) {
// To modify the previous history entry, the following happens:
// 1. history.back()
// 2. _pushToHistory, which calls history.replaceState( ... )
// 3. history.forward()
// Because a navigation via the history API does not immediately update
// the history state, the popstate event is used for synchronization.
self.historyUnlocked = false;
// Suppress the hashchange event to avoid side effects caused by
// navigating back and forward.
self.allowHashChange = false;
window.addEventListener('popstate', rewriteHistoryAfterBack);
history.back();
function rewriteHistoryAfterBack() {
window.removeEventListener('popstate', rewriteHistoryAfterBack);
window.addEventListener('popstate', rewriteHistoryAfterForward);
self._pushToHistory(params, false, true);
history.forward();
}
function rewriteHistoryAfterForward() {
window.removeEventListener('popstate', rewriteHistoryAfterForward);
self.allowHashChange = true;
self.historyUnlocked = true;
callback();
}
}
function pdfHistoryBeforeUnload() {
var previousParams = self._getPreviousParams(null, true);
if (previousParams) {
@ -1774,19 +1817,7 @@ var PDFHistory = (function () {
if (!this.initialized) {
return true;
}
// If the current hash changes when moving back/forward in the history,
// this will trigger a 'popstate' event *as well* as a 'hashchange' event.
// Since the hash generally won't correspond to the exact the position
// stored in the history's state object, triggering the 'hashchange' event
// can thus corrupt the browser history.
//
// When the hash changes during a 'popstate' event, we *only* prevent the
// first 'hashchange' event and immediately reset allowHashChange.
// If it is not reset, the user would not be able to change the hash.
var temp = this.allowHashChange;
this.allowHashChange = true;
return temp;
return this.allowHashChange;
},
_updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
@ -3464,7 +3495,7 @@ var PDFPageView = (function PDFPageViewClosure() {
}
},
reset: function PDFPageView_reset(keepAnnotations) {
reset: function PDFPageView_reset(keepZoomLayer, keepAnnotations) {
if (this.renderTask) {
this.renderTask.cancel();
}
@ -3476,29 +3507,27 @@ var PDFPageView = (function PDFPageViewClosure() {
div.style.height = Math.floor(this.viewport.height) + 'px';
var childNodes = div.childNodes;
var currentZoomLayer = this.zoomLayer || null;
var currentZoomLayerNode = (keepZoomLayer && this.zoomLayer) || null;
var currentAnnotationNode = (keepAnnotations && this.annotationLayer &&
this.annotationLayer.div) || null;
for (var i = childNodes.length - 1; i >= 0; i--) {
var node = childNodes[i];
if (currentZoomLayer === node || currentAnnotationNode === node) {
if (currentZoomLayerNode === node || currentAnnotationNode === node) {
continue;
}
div.removeChild(node);
}
div.removeAttribute('data-loaded');
if (keepAnnotations) {
if (this.annotationLayer) {
// Hide annotationLayer until all elements are resized
// so they are not displayed on the already-resized page
this.annotationLayer.hide();
}
if (currentAnnotationNode) {
// Hide annotationLayer until all elements are resized
// so they are not displayed on the already-resized page
this.annotationLayer.hide();
} else {
this.annotationLayer = null;
}
if (this.canvas) {
if (this.canvas && !currentZoomLayerNode) {
// Zeroing the width and height causes Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
this.canvas.width = 0;
@ -3537,19 +3566,21 @@ var PDFPageView = (function PDFPageViewClosure() {
}
}
if (this.canvas &&
(PDFJS.useOnlyCssZoom ||
(this.hasRestrictedScaling && isScalingRestricted))) {
this.cssTransform(this.canvas, true);
return;
} else if (this.canvas && !this.zoomLayer) {
this.zoomLayer = this.canvas.parentNode;
this.zoomLayer.style.position = 'absolute';
if (this.canvas) {
if (PDFJS.useOnlyCssZoom ||
(this.hasRestrictedScaling && isScalingRestricted)) {
this.cssTransform(this.canvas, true);
return;
}
if (!this.zoomLayer) {
this.zoomLayer = this.canvas.parentNode;
this.zoomLayer.style.position = 'absolute';
}
}
if (this.zoomLayer) {
this.cssTransform(this.zoomLayer.firstChild);
}
this.reset(true);
this.reset(/* keepZoomLayer = */ true, /* keepAnnotations = */ true);
},
/**
@ -3662,7 +3693,7 @@ var PDFPageView = (function PDFPageViewClosure() {
var canvas = document.createElement('canvas');
canvas.id = 'page' + this.id;
canvasWrapper.appendChild(canvas);
if (this.annotationLayer) {
if (this.annotationLayer && this.annotationLayer.div) {
// annotationLayer needs to stay on top
div.insertBefore(canvasWrapper, this.annotationLayer.div);
} else {
@ -3709,7 +3740,7 @@ var PDFPageView = (function PDFPageViewClosure() {
textLayerDiv.className = 'textLayer';
textLayerDiv.style.width = canvas.style.width;
textLayerDiv.style.height = canvas.style.height;
if (this.annotationLayer) {
if (this.annotationLayer && this.annotationLayer.div) {
// annotationLayer needs to stay on top
div.insertBefore(textLayerDiv, this.annotationLayer.div);
} else {
@ -3756,6 +3787,12 @@ var PDFPageView = (function PDFPageViewClosure() {
}
if (self.zoomLayer) {
// Zeroing the width and height causes Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
var zoomLayerCanvas = self.zoomLayer.firstChild;
zoomLayerCanvas.width = 0;
zoomLayerCanvas.height = 0;
div.removeChild(self.zoomLayer);
self.zoomLayer = null;
}
@ -6195,12 +6232,24 @@ var PDFViewerApplication = {
}),
Preferences.get('useOnlyCssZoom').then(function resolved(value) {
PDFJS.useOnlyCssZoom = value;
})
}),
Preferences.get('externalLinkTarget').then(function resolved(value) {
if (PDFJS.isExternalLinkTargetSet()) {
return;
}
PDFJS.externalLinkTarget = value;
}),
// TODO move more preferences and other async stuff here
]).catch(function (reason) { });
return initializedPromise.then(function () {
PDFViewerApplication.initialized = true;
if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
// Prevent external links from "replacing" the viewer,
// when it's embedded in e.g. an iframe or an object.
PDFJS.externalLinkTarget = PDFJS.LinkTarget.TOP;
}
self.initialized = true;
});
},

View File

@ -97,6 +97,11 @@ quit-button.tooltiptext.mac = Quit %1$S (%2$S)
# approval before you change it.
loop-call-button3.label = Hello
loop-call-button3.tooltiptext = Start a conversation
loop-call-button3-error.tooltiptext = Error!
loop-call-button3-donotdisturb.tooltiptext = Do not disturb
loop-call-button3-screensharing.tooltiptext = You are sharing your screen
loop-call-button3-active.tooltiptext = Active conversation
loop-call-button3-participantswaiting.tooltiptext = Someone is waiting for you in a conversation
# LOCALIZATION NOTE(loop-call-button3-pb.tooltiptext): Shown when the button is
# placed inside a Private Browsing window. %S is the value of loop-call-button3.label.
loop-call-button3-pb.tooltiptext = %S is not available in Private Browsing

View File

@ -89,133 +89,6 @@ settings_menu_item_turnnotificationsoff=Turn Notifications Off
settings_menu_item_feedback=Submit Feedback
settings_menu_button_tooltip=Settings
# Contact Strings (Panel)
## LOCALIZATION NOTE(contacts_search_placeholder): This is the placeholder text for
## the search field.
contacts_search_placesholder2=Search…
## LOCALIZATION NOTE (new_contact_button2): This is the button to open the
## new contact sub-panel.
new_contact_button2=Add new contact
## LOCALIZATION NOTE (contact_form_*_placeholder):
## These are the placeholders for the inputs for entering or editing a contact
## Click the 'New Contact' button to see the fields.
contact_form_name_placeholder=Name
contact_form_email_placeholder=Email
contact_form_fxos_phone_placeholder=Firefox OS Phone
contact_form_phone_placeholder2=Phone
contacts_blocked_contacts=Blocked Contacts
## LOCALIZATION NOTE (add_contact_button):
## This is the button to actually add the new contact. Click the 'New Contact'
## button to see the fields.
add_contact_button=Add Contact
## LOCALIZATION NOTE(add_contact_title): This is the subtitle of the add contact
## panel. It is displayed when Add Contact is selected.
add_contact_title=Add Contact
### LOCALIZATION NOTE (valid_email_text_description): This is displayed when
### the user enters an invalid email address, preventing the addition of the
### contact.
valid_email_text_description=Please enter a valid email address
## LOCALIZATION NOTE (add_or_import_contact_title): This is the subtitle of the
## panel.
add_or_import_contact_title=Add or Import Contact
import_contacts_button2=Import from Google
## LOCALIZATION NOTE (import_contacts_button3): Text for button used to import
## contacts into the contact list.
import_contacts_button3=Import
importing_contacts_progress_button=Importing…
import_contacts_failure_message=Some contacts could not be imported. Please try again.
## LOCALIZATION NOTE(import_contacts_success_message): Success notification message
## when user's contacts have been successfully imported.
## Semicolon-separated list of plural forms. See:
## http://developer.mozilla.org/en/docs/Localization_and_Plurals
## In this item, don't translate the part between {{..}}
import_contacts_success_message={{total}} contact was successfully imported.;{{total}} contacts were successfully imported.
## 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_heading2): Title shown when user has no
## contacts in his address book
no_contacts_message_heading2=No contacts yet.
## LOCALIZATION NOTE(no_contacts_import_or_add2): Subheading inviting the user
## to add people to his contact list
no_contacts_import_or_add2=Add someone!
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
no_conversations_message_heading2=No conversations yet.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
no_conversations_start_message2=Start a new one!
## LOCALIZATION NOTE(contacts_no_search_results): Message shown when contacts
## search returned no matching results.
contacts_no_search_results=No matching results.
## LOCALIZATION NOTE(import_failed_description simple): Displayed when an import of
## contacts fails. This is displayed in the error field.
import_failed_description_simple=Sorry, contact import failed
import_failed_description_some=Some contacts could not be imported
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?
## LOCALIZATION NOTE(confirm_delete_contact_remove_button, confirm_delete_contact_cancel_button):
## 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
## the user.
block_contact_menu_button=Block Contact
## LOCALIZATION NOTE(unblock_contact_menu_button): Displayed in the contact list in
## a pop-up menu next to the contact's name, used to unblock a contact and allow them
## to call the user.
unblock_contact_menu_button=Unblock Contact
## LOCALIZATION NOTE(edit_contact_menu_button): Displayed in the contact list in a
## pop-up menu next to the contact's name.
edit_contact_menu_button=Edit Contact…
## LOCALIZATION NOTE(edit_contact_tile): This is the subtitle of the edit contact
## panel. It is displayed when Edit Contact is selected.
edit_contact_title=Edit Contact
## LOCALIZATION NOTE(edit_contact_name_label, edit_contact_email_label):
## These fields are display when the Edit Contact button is selected.
edit_contact_name_label=Name
edit_contact_email_label=Email
## LOCALIZATION NOTE(edit_contact_name_label, edit_contact_email_label):
## These button is displayed when the Edit Contact button is selected and is used
## to accept the change.
edit_contact_done_button=Done
## LOCALIZATION NOTE(audio_call_menu_button): Displayed in the contact list in a
## pop-up menu next to the contact's name.
audio_call_menu_button=Audio Conversation
## LOCALIZATION NOTE(video_call_menu_button): Displayed in the contact list in a
## pop-up menu next to the contact's name.
video_call_menu_button=Video Conversation
## LOCALIZATION NOTE(gravatars_promo_message): The {{learn_more}} part will be
## replaced with a link with the text content of gravatars_promo_message_learnmore.
gravatars_promo_message=You can automatically add profile icons to your contacts \
by sharing their email addresses with Gravatar. {{learn_more}}.
gravatars_promo_message_learnmore=Learn more
gravatars_promo_button_nothanks2=No, thanks
gravatars_promo_button_use2=Use profile icons
# Conversation Window Strings
@ -330,6 +203,13 @@ infobar_menuitem_dontshowagain_accesskey=D
# Context in conversation strings
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
## has no conversations available.
no_conversations_message_heading2=No conversations yet.
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
## user to start a new conversation.
no_conversations_start_message2=Start a new one!
# LOCALIZATION NOTE (context_inroom_header): this string is displayed in the
# conversation window when the user edits context. It is a header to the edit
# section.

View File

@ -37,3 +37,5 @@ corruptedContentError=The page you are trying to view cannot be shown because an
remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
## LOCALIZATION NOTE (weakCryptoUsed) - Do not translate "%S".
weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.

View File

@ -215,3 +215,12 @@ functionality specific to firefox. -->
"ssl_error_unsupported_version". -->
<!ENTITY sslv3Used.longDesc "Advanced info: ssl_error_unsupported_version">
<!ENTITY sslv3Used.learnMore "Learn More…">
<!ENTITY weakCryptoUsed.title "Your connection is not secure">
<!-- LOCALIZATION NOTE (weakCryptoUsed.longDesc) - Do not translate
"ssl_error_no_cypher_overlap". -->
<!ENTITY weakCryptoUsed.longDesc "Advanced info: ssl_error_no_cypher_overlap">
<!ENTITY weakCryptoUsed.learnMore "Learn More…">
<!ENTITY weakCryptoAdvanced.title "Advanced">
<!ENTITY weakCryptoAdvanced.longDesc "<span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe.">
<!ENTITY weakCryptoAdvanced.override "(Not secure) Try loading <span class='hostname'></span> using outdated security">

View File

@ -49,7 +49,8 @@ ul {
background-position: right 0;
}
#errorTitle[sslv3=true] {
#errorTitle[sslv3=true],
#errorTitle[weakCrypto=true] {
background-image: url("aboutNetError_alert.svg");
}
@ -78,7 +79,8 @@ button:disabled {
cursor: pointer;
}
div#certificateErrorReporting {
div#certificateErrorReporting,
div#weakCryptoAdvanced {
display: none;
float: right;
/* Align with the "Try Again" button */
@ -86,11 +88,13 @@ div#certificateErrorReporting {
-moz-margin-end: 24px;
}
div#certificateErrorReporting a {
div#certificateErrorReporting a,
div#weakCryptoAdvanced a {
text-decoration: none;
}
div#certificateErrorReporting a:hover {
div#certificateErrorReporting a:hover,
div#weakCryptoAdvanced a:hover {
text-decoration: underline;
}
@ -102,7 +106,8 @@ span.downArrow {
transform: scaleY(0.7);
}
div#certificateErrorReportingPanel {
div#certificateErrorReportingPanel,
div#weakCryptoAdvancedPanel {
/* Hidden until the link is clicked */
display: none;
background-color: white;
@ -117,15 +122,18 @@ div#certificateErrorReportingPanel {
margin-top: 10px;
}
div#certificateErrorReportingPanel:-moz-dir(ltr) {
div#certificateErrorReportingPanel:-moz-dir(ltr),
div#weakCryptoAdvancedPanel:-moz-dir(ltr) {
left: 34%;
}
div#certificateErrorReportingPanel:-moz-dir(rtl) {
div#certificateErrorReportingPanel:-moz-dir(rtl),
div#weakCryptoAdvancedPanel:-moz-dir(rtl) {
right: 0;
}
#errorStatePanel {
#errorStatePanel,
#overrideWeakCryptoPanel {
display: flex;
flex-direction: row;
flex-wrap: wrap;

View File

@ -456,14 +456,13 @@ exports.TimeScale = TimeScale;
function AnimationsTimeline(inspector) {
this.animations = [];
this.targetNodes = [];
this.timeBlocks = [];
this.inspector = inspector;
this.onAnimationStateChanged = this.onAnimationStateChanged.bind(this);
this.onScrubberMouseDown = this.onScrubberMouseDown.bind(this);
this.onScrubberMouseUp = this.onScrubberMouseUp.bind(this);
this.onScrubberMouseOut = this.onScrubberMouseOut.bind(this);
this.onScrubberMouseMove = this.onScrubberMouseMove.bind(this);
EventEmitter.decorate(this);
}
@ -532,24 +531,28 @@ AnimationsTimeline.prototype = {
this.win = null;
this.inspector = null;
},
destroyTargetNodes: function() {
for (let targetNode of this.targetNodes) {
targetNode.destroy();
}
this.targetNodes = [];
},
destroyTimeBlocks: function() {
for (let timeBlock of this.timeBlocks) {
timeBlock.destroy();
}
this.timeBlocks = [];
},
unrender: function() {
for (let animation of this.animations) {
animation.off("changed", this.onAnimationStateChanged);
}
TimeScale.reset();
this.destroyTargetNodes();
this.destroyTimeBlocks();
this.animationsEl.innerHTML = "";
},
onScrubberMouseDown: function(e) {
this.moveScrubberTo(e.pageX);
this.win.addEventListener("mouseup", this.onScrubberMouseUp);
@ -637,25 +640,25 @@ AnimationsTimeline.prototype = {
"class": "target"
}
});
// Draw the animated node target.
let targetNode = new AnimationTargetNode(this.inspector, {compact: true});
targetNode.init(animatedNodeEl);
targetNode.render(animation);
this.targetNodes.push(targetNode);
// Right-hand part contains the timeline itself (called time-block here).
let timeBlockEl = createNode({
parent: animationEl,
attributes: {
"class": "time-block"
}
});
this.drawTimeBlock(animation, timeBlockEl);
// Draw the animated node target.
let targetNode = new AnimationTargetNode(this.inspector, {compact: true});
targetNode.init(animatedNodeEl);
targetNode.render(animation);
// Save the targetNode so it can be destroyed later.
this.targetNodes.push(targetNode);
// Draw the animation time block.
let timeBlock = new AnimationTimeBlock();
timeBlock.init(timeBlockEl);
timeBlock.render(animation);
this.timeBlocks.push(timeBlock);
}
// Use the document's current time to position the scrubber (if the server
// doesn't provide it, hide the scrubber entirely).
// Note that because the currentTime was sent via the protocol, some time
@ -745,30 +748,34 @@ AnimationsTimeline.prototype = {
TimeScale.distanceToRelativeTime(i, width))
});
}
}
};
/**
* UI component responsible for displaying a single animation timeline, which
* basically looks like a rectangle that shows the delay and iterations.
*/
function AnimationTimeBlock() {}
exports.AnimationTimeBlock = AnimationTimeBlock;
AnimationTimeBlock.prototype = {
init: function(containerEl) {
this.containerEl = containerEl;
},
destroy: function() {
while (this.containerEl.firstChild) {
this.containerEl.firstChild.remove();
}
this.containerEl = null;
this.animation = null;
},
getAnimationTooltipText: function(state) {
let getTime = time => L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(time / 1000, 2));
render: function(animation) {
this.animation = animation;
let {state} = this.animation;
// The type isn't always available, older servers don't send it.
let title =
state.type
? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
: state.name;
let delay = L10N.getStr("player.animationDelayLabel") + " " +
getTime(state.delay);
let duration = L10N.getStr("player.animationDurationLabel") + " " +
getTime(state.duration);
let iterations = L10N.getStr("player.animationIterationCountLabel") + " " +
(state.iterationCount ||
L10N.getStr("player.infiniteIterationCountText"));
return [title, duration, iterations, delay].join("\n");
},
drawTimeBlock: function({state}, el) {
let width = el.offsetWidth;
let width = this.containerEl.offsetWidth;
// Create a container element to hold the delay and iterations.
// It is positioned according to its delay (divided by the playbackrate),
@ -783,7 +790,7 @@ AnimationsTimeline.prototype = {
let w = TimeScale.durationToDistance(duration / rate, width);
let iterations = createNode({
parent: el,
parent: this.containerEl,
attributes: {
"class": state.type + " iterations" + (count ? "" : " infinite"),
// Individual iterations are represented by setting the size of the
@ -801,7 +808,7 @@ AnimationsTimeline.prototype = {
parent: iterations,
attributes: {
"class": "name",
"title": this.getAnimationTooltipText(state),
"title": this.getTooltipText(state),
"style": delay < 0
? "margin-left:" +
TimeScale.durationToDistance(Math.abs(delay), width) + "px"
@ -813,16 +820,37 @@ AnimationsTimeline.prototype = {
// Delay.
if (delay) {
// Negative delays need to start at 0.
let x = TimeScale.durationToDistance((delay < 0 ? 0 : delay) / rate, width);
let w = TimeScale.durationToDistance(Math.abs(delay) / rate, width);
let delayX = TimeScale.durationToDistance(
(delay < 0 ? 0 : delay) / rate, width);
let delayW = TimeScale.durationToDistance(
Math.abs(delay) / rate, width);
createNode({
parent: iterations,
attributes: {
"class": "delay" + (delay < 0 ? " negative" : ""),
"style": `left:-${x}px;
width:${w}px;`
"style": `left:-${delayX}px;
width:${delayW}px;`
}
});
}
},
getTooltipText: function(state) {
let getTime = time => L10N.getFormatStr("player.timeLabel",
L10N.numberWithDecimals(time / 1000, 2));
// The type isn't always available, older servers don't send it.
let title =
state.type
? L10N.getFormatStr("timeline." + state.type + ".nameLabel", state.name)
: state.name;
let delay = L10N.getStr("player.animationDelayLabel") + " " +
getTime(state.delay);
let duration = L10N.getStr("player.animationDurationLabel") + " " +
getTime(state.duration);
let iterations = L10N.getStr("player.animationIterationCountLabel") + " " +
(state.iterationCount ||
L10N.getStr("player.infiniteIterationCountText"));
return [title, duration, iterations, delay].join("\n");
}
};

View File

@ -3,16 +3,82 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { PROMISE } = require("devtools/client/shared/redux/middleware/promise");
const { actions } = require("../constants");
// @TODO 1215606
// Use this assert instead of utils when fixed.
// const { assert } = require("devtools/shared/DevToolsUtils");
const { createSnapshot, assert } = require("../utils");
const { actions, snapshotState: states } = require("../constants");
/**
* A series of actions are fired from this task to save, read and generate the initial
* census from a snapshot.
*
* @param {MemoryFront}
* @param {HeapAnalysesClient}
* @param {Object}
*/
const takeSnapshotAndCensus = exports.takeSnapshotAndCensus = function takeSnapshotAndCensus (front, heapWorker) {
return function *(dispatch, getStore) {
let snapshot = yield dispatch(takeSnapshot(front));
yield dispatch(readSnapshot(heapWorker, snapshot));
yield dispatch(takeCensus(heapWorker, snapshot));
};
};
/**
* @param {MemoryFront}
*/
const takeSnapshot = exports.takeSnapshot = function takeSnapshot (front) {
return {
type: actions.TAKE_SNAPSHOT,
[PROMISE]: front.saveHeapSnapshot()
return function *(dispatch, getStore) {
let snapshot = createSnapshot();
dispatch({ type: actions.TAKE_SNAPSHOT_START, snapshot });
dispatch(selectSnapshot(snapshot));
let path = yield front.saveHeapSnapshot();
dispatch({ type: actions.TAKE_SNAPSHOT_END, snapshot, path });
return snapshot;
};
};
/**
* Reads a snapshot into memory; necessary to do before taking
* a census on the snapshot. May only be called once per snapshot.
*
* @param {HeapAnalysesClient}
* @param {Snapshot} snapshot,
*/
const readSnapshot = exports.readSnapshot = function readSnapshot (heapWorker, snapshot) {
return function *(dispatch, getStore) {
// @TODO 1215606
assert(snapshot.state === states.SAVED,
"Should only read a snapshot once");
dispatch({ type: actions.READ_SNAPSHOT_START, snapshot });
yield heapWorker.readHeapSnapshot(snapshot.path);
dispatch({ type: actions.READ_SNAPSHOT_END, snapshot });
};
};
/**
* @param {HeapAnalysesClient} heapWorker
* @param {Snapshot} snapshot,
*
* @see {Snapshot} model defined in devtools/client/memory/app.js
* @see `devtools/shared/heapsnapshot/HeapAnalysesClient.js`
* @see `js/src/doc/Debugger/Debugger.Memory.md` for breakdown details
*/
const takeCensus = exports.takeCensus = function takeCensus (heapWorker, snapshot) {
return function *(dispatch, getStore) {
// @TODO 1215606
assert([states.READ, states.SAVED_CENSUS].includes(snapshot.state),
"Can only take census of snapshots in READ or SAVED_CENSUS state");
let breakdown = getStore().breakdown;
dispatch({ type: actions.TAKE_CENSUS_START, snapshot, breakdown });
let census = yield heapWorker.takeCensus(snapshot.path, { breakdown }, { asTreeNode: true });
dispatch({ type: actions.TAKE_CENSUS_END, snapshot, census });
};
};
@ -26,3 +92,4 @@ const selectSnapshot = exports.selectSnapshot = function takeSnapshot (snapshot)
snapshot
};
};

View File

@ -1,9 +1,11 @@
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { selectSnapshot, takeSnapshot } = require("./actions/snapshot");
const { selectSnapshot, takeSnapshotAndCensus } = require("./actions/snapshot");
const { snapshotState } = require("./constants");
const Toolbar = createFactory(require("./components/toolbar"));
const List = createFactory(require("./components/list"));
const SnapshotListItem = createFactory(require("./components/snapshot-list-item"));
const HeapView = createFactory(require("./components/heap"));
const stateModel = {
/**
@ -12,23 +14,42 @@ const stateModel = {
*/
front: PropTypes.any,
/**
* {HeapAnalysesClient}
* Used to communicate with the worker that performs analyses on heaps.
*/
heapWorker: PropTypes.any,
/**
* The breakdown object DSL describing how we want
* the census data to be.
* @see `js/src/doc/Debugger/Debugger.Memory.md`
*/
breakdown: PropTypes.object.isRequired,
/**
* {Array<Snapshot>}
* List of references to all snapshots taken
*/
snapshots: PropTypes.arrayOf(PropTypes.shape({
// Unique ID for a snapshot
id: PropTypes.number.isRequired,
snapshotId: PropTypes.string,
// fs path to where the snapshot is stored; used to
// identify the snapshot for HeapAnalysesClient.
path: PropTypes.string,
// Whether or not this snapshot is currently selected.
selected: PropTypes.bool.isRequired,
status: PropTypes.oneOf([
"start",
"done",
"error",
]).isRequired,
// Whther or not the snapshot has been read into memory.
// Only needed to do once.
snapshotRead: PropTypes.bool.isRequired,
// State the snapshot is in
// @see ./constants.js
state: PropTypes.oneOf(Object.keys(snapshotState)).isRequired,
// Data of a census breakdown
census: PropTypes.any,
}))
};
const App = createClass({
displayName: "memory-tool",
@ -36,31 +57,42 @@ const App = createClass({
childContextTypes: {
front: PropTypes.any,
heapWorker: PropTypes.any,
},
getChildContext() {
return {
front: this.props.front,
heapWorker: this.props.heapWorker,
}
},
render() {
let { dispatch, snapshots, front } = this.props;
let { dispatch, snapshots, front, heapWorker, breakdown } = this.props;
let selectedSnapshot = snapshots.find(s => s.selected);
return (
dom.div({ id: "memory-tool" }, [
Toolbar({
buttons: [{
className: "take-snapshot",
onClick: () => dispatch(takeSnapshot(front))
onClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker))
}]
}),
List({
itemComponent: SnapshotListItem,
items: snapshots,
onClick: snapshot => dispatch(selectSnapshot(snapshot))
})
dom.div({ id: "memory-tool-container" }, [
List({
itemComponent: SnapshotListItem,
items: snapshots,
onClick: snapshot => dispatch(selectSnapshot(snapshot))
}),
HeapView({
snapshot: selectedSnapshot,
onSnapshotClick: () => dispatch(takeSnapshotAndCensus(front, heapWorker))
}),
])
])
);
},

View File

@ -0,0 +1,49 @@
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { getSnapshotStatusText } = require("../utils");
const { snapshotState: states } = require("../constants");
const TAKE_SNAPSHOT_TEXT = "Take snapshot";
/**
* Main view for the memory tool -- contains several panels for different states;
* an initial state of only a button to take a snapshot, loading states, and the
* heap view tree.
*/
const Heap = module.exports = createClass({
displayName: "heap-view",
propTypes: {
onSnapshotClick: PropTypes.func.isRequired,
snapshot: PropTypes.any,
},
render() {
let { snapshot, onSnapshotClick } = this.props;
let pane;
let census = snapshot ? snapshot.census : null;
let state = snapshot ? snapshot.state : "initial";
let statusText = getSnapshotStatusText(snapshot);
switch (state) {
case "initial":
pane = dom.div({ className: "heap-view-panel", "data-state": "initial" },
dom.button({ className: "take-snapshot", onClick: onSnapshotClick }, TAKE_SNAPSHOT_TEXT)
);
break;
case states.SAVING:
case states.SAVED:
case states.READING:
case states.READ:
case states.SAVING_CENSUS:
pane = dom.div({ className: "heap-view-panel", "data-state": state }, statusText);
break;
case states.SAVED_CENSUS:
pane = dom.div({ className: "heap-view-panel", "data-state": "loaded" }, JSON.stringify(census || {}));
break;
}
return (
dom.div({ id: "heap-view", "data-state": state }, pane)
)
}
});

View File

@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'heap.js',
'list.js',
'snapshot-list-item.js',
'toolbar.js',

View File

@ -1,4 +1,5 @@
const { DOM: dom, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
const { getSnapshotStatusText } = require("../utils");
const SnapshotListItem = module.exports = createClass({
displayName: "snapshot-list-item",
@ -12,9 +13,15 @@ const SnapshotListItem = module.exports = createClass({
render() {
let { index, item, onClick } = this.props;
let className = `snapshot-list-item ${item.selected ? " selected" : ""}`;
let statusText = getSnapshotStatusText(item);
return (
dom.li({ className, onClick },
dom.span({ className: "snapshot-title" }, `Snapshot #${index}`)
dom.span({
className: `snapshot-title ${statusText ? " devtools-throbber" : ""}`
}, `Snapshot #${index}`),
statusText ? dom.span({ className: "snapshot-state" }, statusText) : void 0
)
);
}

View File

@ -6,7 +6,34 @@
const actions = exports.actions = {};
// Fired by UI to request a snapshot from the actor.
actions.TAKE_SNAPSHOT = "take-snapshot";
actions.TAKE_SNAPSHOT_START = "take-snapshot-start";
actions.TAKE_SNAPSHOT_END = "take-snapshot-end";
// When a heap snapshot is read into memory -- only fired
// once per snapshot.
actions.READ_SNAPSHOT_START = "read-snapshot-start";
actions.READ_SNAPSHOT_END = "read-snapshot-end";
// When a census is being performed on a heap snapshot
actions.TAKE_CENSUS_START = "take-census-start";
actions.TAKE_CENSUS_END = "take-census-end";
// Fired by UI to select a snapshot to view.
actions.SELECT_SNAPSHOT = "select-snapshot";
const snapshotState = exports.snapshotState = {};
/**
* Various states a snapshot can be in.
* An FSM describing snapshot states:
*
* SAVING -> SAVED -> READING -> READ <- <- <- SAVED_CENSUS
*
* SAVING_CENSUS
*/
snapshotState.SAVING = "snapshot-state-saving";
snapshotState.SAVED = "snapshot-state-saved";
snapshotState.READING = "snapshot-state-reading";
snapshotState.READ = "snapshot-state-read";
snapshotState.SAVING_CENSUS = "snapshot-state-saving-census";
snapshotState.SAVED_CENSUS = "snapshot-state-saved-census";

View File

@ -14,20 +14,15 @@ const App = createFactory(require("devtools/client/memory/app"));
const Store = require("devtools/client/memory/store");
/**
* The current target, toolbox and MemoryFront, set by this tool's host.
* The current target, toolbox, MemoryFront, and HeapAnalysesClient, set by this tool's host.
*/
var gToolbox, gTarget, gFront;
/**
* The current target, toolbox and MemoryFront, set by this tool's host.
*/
var gToolbox, gTarget, gFront;
var gToolbox, gTarget, gFront, gHeapAnalysesClient;
function initialize () {
return Task.spawn(function*() {
let root = document.querySelector("#app");
let store = Store();
let app = createElement(App, { front: gFront });
let app = createElement(App, { front: gFront, heapWorker: gHeapAnalysesClient });
let provider = createElement(Provider, { store }, app);
render(provider, root);
});

View File

@ -17,6 +17,7 @@ DevToolsModules(
'panel.js',
'reducers.js',
'store.js',
'utils.js',
)
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']

View File

@ -9,6 +9,7 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
const { Task } = require("resource://gre/modules/Task.jsm");
const EventEmitter = require("devtools/shared/event-emitter");
const { MemoryFront } = require("devtools/server/actors/memory");
const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
const promise = require("promise");
function MemoryPanel (iframeWindow, toolbox) {
@ -31,6 +32,7 @@ MemoryPanel.prototype = {
this.panelWin.gFront = new MemoryFront(this.target.client,
this.target.form,
rootForm);
this.panelWin.gHeapAnalysesClient = new HeapAnalysesClient();
yield this.panelWin.gFront.attach();
return this._opening = this.panelWin.initialize().then(() => {
@ -57,6 +59,7 @@ MemoryPanel.prototype = {
return this._destroyer = this.panelWin.destroy().then(() => {
// Destroy front to ensure packet handler is removed from client
this.panelWin.gFront.destroy();
this.panelWin.gHeapAnalysesClient.destroy();
this.panelWin = null;
this.emit("destroyed");
return this;

View File

@ -1 +1,3 @@
exports.snapshots = require("./reducers/snapshot");
exports.snapshots = require("./reducers/snapshots");
exports.breakdown = require("./reducers/breakdown");
exports.errors = require("./reducers/errors");

View File

@ -0,0 +1,15 @@
const { actions } = require("../constants");
// Hardcoded breakdown for now
const DEFAULT_BREAKDOWN = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
};
/**
* Not much to do here yet until we can change breakdowns,
* but this gets it in our store.
*/
module.exports = function (state=DEFAULT_BREAKDOWN, action) {
return Object.assign({}, DEFAULT_BREAKDOWN);
};

View File

@ -0,0 +1,13 @@
const { ERROR_TYPE: TASK_ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
/**
* Handle errors dispatched from task middleware and
* store them so we can check in tests or dump them out.
*/
module.exports = function (state=[], action) {
switch (action.type) {
case TASK_ERROR_TYPE:
return [...state, action.error];
}
return state;
};

View File

@ -4,5 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'snapshot.js',
'breakdown.js',
'errors.js',
'snapshots.js',
)

View File

@ -1,54 +0,0 @@
const { actions } = require("../constants");
const { PROMISE } = require("devtools/client/shared/redux/middleware/promise");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
function handleTakeSnapshot (state, action) {
switch (action.status) {
case "start":
return [...state, {
id: action.seqId,
status: action.status,
// auto selected if this is the first snapshot
selected: state.length === 0
}];
case "done":
let snapshot = state.find(s => s.id === action.seqId);
if (!snapshot) {
DevToolsUtils.reportException(`No snapshot with id "${action.seqId}" for TAKE_SNAPSHOT`);
break;
}
snapshot.status = "done";
snapshot.snapshotId = action.value;
return [...state];
case "error":
DevToolsUtils.reportException(`No async state found for ${action.type}`);
}
return [...state];
}
function handleSelectSnapshot (state, action) {
let selected = state.find(s => s.id === action.snapshot.id);
if (!selected) {
DevToolsUtils.reportException(`Cannot select non-existant snapshot ${snapshot.id}`);
}
return state.map(s => {
s.selected = s === selected;
return s;
});
}
module.exports = function (state=[], action) {
switch (action.type) {
case actions.TAKE_SNAPSHOT:
return handleTakeSnapshot(state, action);
case actions.SELECT_SNAPSHOT:
return handleSelectSnapshot(state, action);
}
return state;
};

View File

@ -0,0 +1,59 @@
const { actions, snapshotState: states } = require("../constants");
const { getSnapshot } = require("../utils");
let handlers = Object.create({});
handlers[actions.TAKE_SNAPSHOT_START] = function (snapshots, { snapshot }) {
return [...snapshots, snapshot];
};
handlers[actions.TAKE_SNAPSHOT_END] = function (snapshots, action) {
return snapshots.map(snapshot => {
if (snapshot.id === action.snapshot.id) {
snapshot.state = states.SAVED;
snapshot.path = action.path;
}
return snapshot;
});
};
handlers[actions.READ_SNAPSHOT_START] = function (snapshots, action) {
let snapshot = getSnapshot(snapshots, action.snapshot);
snapshot.state = states.READING;
return [...snapshots];
};
handlers[actions.READ_SNAPSHOT_END] = function (snapshots, action) {
let snapshot = getSnapshot(snapshots, action.snapshot);
snapshot.state = states.READ;
return [...snapshots];
};
handlers[actions.TAKE_CENSUS_START] = function (snapshots, action) {
let snapshot = getSnapshot(snapshots, action.snapshot);
snapshot.state = states.SAVING_CENSUS;
snapshot.census = null;
return [...snapshots];
};
handlers[actions.TAKE_CENSUS_END] = function (snapshots, action) {
let snapshot = getSnapshot(snapshots, action.snapshot);
snapshot.state = states.SAVED_CENSUS;
snapshot.census = action.census;
return [...snapshots];
};
handlers[actions.SELECT_SNAPSHOT] = function (snapshots, action) {
return snapshots.map(s => {
s.selected = s.id === action.snapshot.id;
return s;
});
};
module.exports = function (snapshots=[], action) {
let handler = handlers[action.type];
if (handler) {
return handler(snapshots, action);
}
return snapshots;
};

View File

@ -14,6 +14,7 @@ var promise = require("promise");
var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
var { expectState } = require("devtools/server/actors/common");
var HeapSnapshotFileUtils = require("devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
var HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
var { addDebuggerToGlobal } = require("resource://gre/modules/jsdebugger.jsm");
var Store = require("devtools/client/memory/store");
var SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
@ -39,8 +40,7 @@ StubbedMemoryFront.prototype.detach = Task.async(function *() {
});
StubbedMemoryFront.prototype.saveHeapSnapshot = expectState("attached", Task.async(function *() {
let path = ThreadSafeChromeUtils.saveHeapSnapshot({ debugger: this.dbg });
return HeapSnapshotFileUtils.getSnapshotIdFromPath(path);
return ThreadSafeChromeUtils.saveHeapSnapshot({ runtime: true });
}), "saveHeapSnapshot");
function waitUntilState (store, predicate) {

View File

@ -6,6 +6,7 @@
*/
let actions = require("devtools/client/memory/actions/snapshot");
let { snapshotState: states } = require("devtools/client/memory/constants");
function run_test() {
run_next_test();
@ -22,9 +23,7 @@ add_task(function *() {
yield waitUntilState(store, ({ snapshots }) => snapshots.length === 5 && snapshots.every(isDone));
ok(store.getState().snapshots[0].selected, "snapshot[0] selected by default");
for (let i = 1; i < 5; i++) {
for (let i = 0; i < 5; i++) {
do_print(`Selecting snapshot[${i}]`);
store.dispatch(actions.selectSnapshot(store.getState().snapshots[i]));
yield waitUntilState(store, ({ snapshots }) => snapshots[i].selected);
@ -35,4 +34,4 @@ add_task(function *() {
}
});
function isDone (s) { return s.status === "done"; }
function isDone (s) { return s.state === states.SAVED; }

View File

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the async reducer responding to the action `takeCensus(heapWorker, snapshot)`
*/
var { snapshotState: states } = require("devtools/client/memory/constants");
var { ERROR_TYPE } = require("devtools/client/shared/redux/middleware/task");
var actions = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
store.dispatch(actions.takeSnapshot(front));
yield waitUntilState(store, () => {
let snapshots = store.getState().snapshots;
return snapshots.length === 1 && snapshots[0].state === states.SAVED;
});
let snapshot = store.getState().snapshots[0];
equal(snapshot.census, null, "No census data exists yet on the snapshot.");
// Test error case of wrong state
store.dispatch(actions.takeCensus(heapWorker, snapshot));
yield waitUntilState(store, () => store.getState().errors.length === 1);
ok(/Assertion failure/.test(store.getState().errors[0]),
"Error thrown when taking a census of a snapshot that has not been read.");
store.dispatch(actions.readSnapshot(heapWorker, snapshot));
yield waitUntilState(store, () => store.getState().snapshots[0].state === states.READ);
store.dispatch(actions.takeCensus(heapWorker, snapshot));
yield waitUntilState(store, () => store.getState().snapshots[0].state === states.SAVING_CENSUS);
yield waitUntilState(store, () => store.getState().snapshots[0].state === states.SAVED_CENSUS);
snapshot = store.getState().snapshots[0];
ok(snapshot.census, "Snapshot has census after saved census");
ok(snapshot.census.children.length, "Census is in tree node form with the default breakdown");
ok(snapshot.census.children.find(t => t.name === "JSObject"),
"Census is in tree node form with the default breakdown");
});

View File

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the task creator `takeSnapshotAndCensus()` for the whole flow of
* taking a snapshot, and its sub-actions.
*/
let { snapshotState: states } = require("devtools/client/memory/constants");
let actions = require("devtools/client/memory/actions/snapshot");
function run_test() {
run_next_test();
}
add_task(function *() {
let front = new StubbedMemoryFront();
let heapWorker = new HeapAnalysesClient();
yield front.attach();
let store = Store();
let i = 0;
let expected = ["SAVING", "SAVED", "READING", "READ", "SAVING_CENSUS", "SAVED_CENSUS"];
let expectStates = () => {
if (i >= expected.length) { return false; }
let snapshot = store.getState().snapshots[0] || {};
let isCorrectState = snapshot.state === states[expected[i]];
if (isCorrectState) {
ok(true, `Found expected state ${expected[i]}`);
i++;
}
return isCorrectState;
};
let unsubscribe = store.subscribe(expectStates);
store.dispatch(actions.takeSnapshotAndCensus(front, heapWorker));
yield waitUntilState(store, () => i === 6);
unsubscribe();
ok(true, "takeSnapshotAndCensus() produces the correct sequence of states in a snapshot");
let snapshot = store.getState().snapshots[0];
ok(snapshot.census, "snapshot has census data");
ok(snapshot.selected, "snapshot is selected");
});

View File

@ -5,7 +5,8 @@
* Tests the async reducer responding to the action `takeSnapshot(front)`
*/
var actions = require("devtools/client/memory/actions/snapshot");
let actions = require("devtools/client/memory/actions/snapshot");
let { snapshotState: states } = require("devtools/client/memory/constants");
function run_test() {
run_next_test();
@ -20,44 +21,34 @@ add_task(function *() {
let foundPendingState = false;
let foundDoneState = false;
let foundAllSnapshots = false;
function checkState () {
let { snapshots } = store.getState();
let lastSnapshot = snapshots[snapshots.length - 1];
if (snapshots.length === 1 && snapshots[0].status === "start") {
if (lastSnapshot.state === states.SAVING) {
foundPendingState = true;
ok(foundPendingState, "Got state change for pending heap snapshot request");
ok(snapshots[0].selected, "First snapshot is auto-selected");
ok(!(snapshots[0].snapshotId), "Snapshot does not yet have a snapshotId");
ok(!lastSnapshot.path, "Snapshot does not yet have a path");
ok(!lastSnapshot.census, "Has no census data when loading");
}
if (snapshots.length === 1 && snapshots[0].status === "done") {
else if (lastSnapshot.state === states.SAVED) {
foundDoneState = true;
ok(foundDoneState, "Got state change for completed heap snapshot request");
ok(snapshots[0].snapshotId, "Snapshot fetched with a snapshotId");
}
if (snapshots.length === 1 && snapshots[0].status === "error") {
ok(false, "takeSnapshot's promise returned with an error");
}
if (snapshots.length === 5 && snapshots.every(s => s.status === "done")) {
foundAllSnapshots = true;
ok(snapshots.every(s => s.status === "done"), "All snapshots have a snapshotId");
equal(snapshots.length, 5, "Found 5 snapshots");
ok(snapshots.every(s => s.snapshotId), "All snapshots have a snapshotId");
ok(snapshots[0].selected, "First snapshot still selected");
equal(snapshots.filter(s => !s.selected).length, 4, "All other snapshots are unselected");
ok(foundPendingState, "SAVED state occurs after SAVING state");
ok(lastSnapshot.path, "Snapshot fetched with a path");
ok(snapshots.every(s => s.selected === (s.id === lastSnapshot.id)),
"Only recent snapshot is selected");
}
}
store.dispatch(actions.takeSnapshot(front));
yield waitUntilState(store, () => foundPendingState && foundDoneState);
for (let i = 0; i < 4; i++) {
store.dispatch(actions.takeSnapshot(front));
yield waitUntilState(store, () => foundPendingState && foundDoneState);
// reset state trackers
foundDoneState = foundPendingState = false;
}
yield waitUntilState(store, () => foundAllSnapshots);
unsubscribe();
});

View File

@ -6,4 +6,6 @@ firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_action-select-snapshot.js]
[test_action-take-census.js]
[test_action-take-snapshot.js]
[test_action-take-snapshot-and-census.js]

View File

@ -0,0 +1,68 @@
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { snapshotState: states } = require("./constants");
const SAVING_SNAPSHOT_TEXT = "Saving snapshot...";
const READING_SNAPSHOT_TEXT = "Reading snapshot...";
const SAVING_CENSUS_TEXT = "Taking heap census...";
// @TODO 1215606
// Use DevToolsUtils.assert when fixed.
exports.assert = function (condition, message) {
if (!condition) {
const err = new Error("Assertion failure: " + message);
DevToolsUtils.reportException("DevToolsUtils.assert", err);
throw err;
}
};
/**
* Returns a string representing a readable form of the snapshot's state.
*
* @param {Snapshot} snapshot
* @return {String}
*/
exports.getSnapshotStatusText = function (snapshot) {
switch (snapshot && snapshot.state) {
case states.SAVING:
return SAVING_SNAPSHOT_TEXT;
case states.SAVED:
case states.READING:
return READING_SNAPSHOT_TEXT;
case states.READ:
case states.SAVING_CENSUS:
return SAVING_CENSUS_TEXT;
}
return "";
}
/**
* Takes an array of snapshots and a snapshot and returns
* the snapshot instance in `snapshots` that matches
* the snapshot passed in.
*
* @param {Array<Snapshot>} snapshots
* @param {Snapshot}
* @return ?Snapshot
*/
exports.getSnapshot = function getSnapshot (snapshots, snapshot) {
let found = snapshots.find(s => s.id === snapshot.id);
if (!found) {
DevToolsUtils.reportException(`No matching snapshot found for ${snapshot.id}`);
}
return found || null;
};
/**
* Creates a new snapshot object.
*
* @return {Snapshot}
*/
let INC_ID = 0;
exports.createSnapshot = function createSnapshot () {
let id = ++INC_ID;
return {
id,
state: states.SAVING,
census: null,
path: null,
};
};

View File

@ -243,7 +243,7 @@ body {
/* Animation block widgets */
.animation-timeline .animation {
margin: 4px 0;
padding: 2px 0;
height: var(--timeline-animation-height);
position: relative;
}
@ -264,10 +264,10 @@ body {
.animation-timeline .animation .time-block {
position: absolute;
top: 0;
top: 2px;
left: var(--timeline-sidebar-width);
right: 0;
height: 100%;
height: var(--timeline-animation-height);
}
/* Animation iterations */

View File

@ -15,10 +15,6 @@
%endif
}
.theme-body {
margin: 0;
}
.devtools-monospace {
font-family: var(--monospace-font-family);
%if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)

View File

@ -5,6 +5,10 @@
@import url(variables.css);
body {
margin: 0;
}
.theme-body {
background: var(--theme-body-background);
color: var(--theme-body-color);

View File

@ -5,6 +5,10 @@
@import url(variables.css);
body {
margin: 0;
}
.theme-body {
background: var(--theme-body-background);
color: var(--theme-body-color);

View File

@ -20,6 +20,11 @@
--row-hover-background-color: rgba(76,158,217,0.2);
}
#memory-tool-container {
display: flex;
flex-direction: row;
}
/**
* TODO bug 1213100
* should generalize toolbar buttons with images in them
@ -103,6 +108,35 @@
color: var(--theme-selection-color);
}
.snapshot-list-item span {
display: block;
}
.snapshot-list-item .snapshot-state {
font-size: 90%;
color: var(--theme-body-color-alt);
}
.snapshot-list-item.selected .snapshot-state {
/* Text inside a selected item should not be custom colored. */
color: inherit !important;
}
/**
* Main panel
*/
#heap-view {
flex: 1 1 auto;
}
#heap-view .heap-view-panel {
width: 100%;
height: 100%;
}
#heap-view .take-snapshot {
}
/**
* Heap View

View File

@ -4809,6 +4809,9 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) {
error.AssignLiteral("sslv3Used");
addHostPort = true;
} else if (securityState & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
error.AssignLiteral("weakCryptoUsed");
addHostPort = true;
} else {
// Usually we should have aFailedChannel and get a detailed message
tsi->GetErrorMessage(getter_Copies(messageStr));

View File

@ -35,3 +35,4 @@ cspBlocked=This page has a content security policy that prevents it from being l
corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
remoteXUL=This page uses an unsupported technology that is no longer available by default.
sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol.
weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, the connection to this website has not been established.

View File

@ -23,7 +23,6 @@ import java.util.zip.GZIPOutputStream;
import org.mozilla.gecko.AppConstants.Versions;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
@ -32,6 +31,7 @@ import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@ -39,7 +39,7 @@ import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
public class CrashReporter extends Activity
public class CrashReporter extends AppCompatActivity
{
private static final String LOGTAG = "GeckoCrashReporter";

View File

@ -144,13 +144,6 @@
to send a tab to a synced device is pressed and no other synced devices
are found. -->
<!ENTITY overlay_no_synced_devices "No Firefox Account connected devices found">
<!-- Localization note (overlay_share_tab_not_sent) : Used when the menu option
to send a tab to a synced device is pressed and there is an error
connecting to the server so the tab could not be sent. The text should be
divided into two lines (use the newline escape character, "\n", between
them): the first notifying the user about the failure and the second
telling the user what to do about it. -->
<!ENTITY overlay_share_tab_not_sent "Your tab could not be sent.\nPlease try again later.">
<!ENTITY pref_category_search3 "Search">
<!ENTITY pref_category_search_summary "Customize your search providers">

View File

@ -616,7 +616,6 @@ if CONFIG['MOZ_ANDROID_SHARE_OVERLAY']:
'overlays/service/sharemethods/SendTab.java',
'overlays/service/sharemethods/ShareMethod.java',
'overlays/ui/OverlayDialogButton.java',
'overlays/ui/OverlayToastHelper.java',
'overlays/ui/SendTabDeviceListArrayAdapter.java',
'overlays/ui/SendTabList.java',
'overlays/ui/SendTabTargetSelectedListener.java',

View File

@ -10,13 +10,12 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import org.mozilla.gecko.Assert;
import org.mozilla.gecko.overlays.service.sharemethods.AddBookmark;
import org.mozilla.gecko.overlays.service.sharemethods.AddToReadingList;
import org.mozilla.gecko.overlays.service.sharemethods.SendTab;
import org.mozilla.gecko.overlays.service.sharemethods.ShareMethod;
import org.mozilla.gecko.overlays.ui.OverlayToastHelper;
import org.mozilla.gecko.util.ThreadUtils;
import java.util.EnumMap;
@ -115,14 +114,12 @@ public class OverlayActionService extends Service {
// Dispatch the share to the targeted ShareMethod.
switch (result) {
case SUCCESS:
// \o/
OverlayToastHelper.showSuccessToast(getApplicationContext(), shareMethod.getSuccessMessage());
Log.d(LOGTAG, "Share was successful");
break;
case TRANSIENT_FAILURE:
// Fall-through
case PERMANENT_FAILURE:
// Show a failure toast without a retry button.
OverlayToastHelper.showFailureToast(getApplicationContext(), shareMethod.getFailureMessage());
Log.e(LOGTAG, "Share failed: " + result);
break;
default:
Assert.fail("Unknown share method result code: " + result);

View File

@ -24,17 +24,6 @@ public class AddBookmark extends ShareMethod {
return Result.SUCCESS;
}
@Override
public String getSuccessMessage() {
return context.getResources().getString(R.string.bookmark_added);
}
// Unused.
@Override
public String getFailureMessage() {
return null;
}
public AddBookmark(Context context) {
super(context);
}

View File

@ -38,17 +38,6 @@ public class AddToReadingList extends ShareMethod {
return Result.SUCCESS;
}
@Override
public String getSuccessMessage() {
return context.getResources().getString(R.string.reading_list_added);
}
// Unused.
@Override
public String getFailureMessage() {
return null;
}
public AddToReadingList(Context context) {
super(context);
}

View File

@ -266,16 +266,6 @@ public class SendTab extends ShareMethod {
}
}
@Override
public String getSuccessMessage() {
return context.getResources().getString(R.string.sync_text_tab_sent);
}
@Override
public String getFailureMessage() {
return context.getResources().getString(R.string.overlay_share_tab_not_sent);
}
/**
* Inteface for interacting with Sync accounts. Used to hide the difference in implementation
* between FXA and "old sync" accounts when sending tabs.

View File

@ -33,9 +33,6 @@ public abstract class ShareMethod {
*/
public abstract Result handle(ShareData shareData);
public abstract String getSuccessMessage();
public abstract String getFailureMessage();
/**
* Enum representing the possible results of performing a share.
*/

View File

@ -1,59 +0,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/. */
package org.mozilla.gecko.overlays.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import org.mozilla.gecko.R;
/**
* Static helper class for generating toasts for share events.
*
* The overlay toasts come in a variety of flavours: success (rectangle with happy green tick,
* failure (no tick, a retry button), and success-with-tutorial (as success, but with a pretty
* picture of some description to educate the user on how to use the feature) TODO: Bug 1048645.
*/
public class OverlayToastHelper {
/**
* Show a toast indicating a failure to share.
* @param context Context in which to inflate the toast.
* @param failureMessage String to display in the toast.
*/
public static void showFailureToast(Context context, String failureMessage) {
showToast(context, failureMessage, false);
}
/**
* Show a toast indicating a successful share.
* @param successMessage Message to show in the toast.
*/
public static void showSuccessToast(Context context, String successMessage) {
showToast(context, successMessage, true);
}
private static void showToast(Context context, String message, boolean success) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.overlay_share_toast, null);
TextView text = (TextView) layout.findViewById(R.id.overlay_toast_message);
text.setText(message);
if (!success) {
// Hide the happy green tick.
text.setCompoundDrawables(null, null, null, null);
}
Toast toast = new Toast(context);
toast.setDuration(Toast.LENGTH_SHORT);
toast.setView(layout);
toast.show();
}
}

View File

@ -36,6 +36,7 @@ import android.os.Parcelable;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.animation.AnimationSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
@ -390,21 +391,21 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
serviceIntent.putExtra(OverlayConstants.EXTRA_PARAMETERS, extraParameters);
startService(serviceIntent);
slideOut();
animateOut(true);
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.SHARE_OVERLAY, "sendtab");
}
public void addToReadingList() {
startService(getServiceIntent(ShareMethod.Type.ADD_TO_READING_LIST));
slideOut();
animateOut(true);
Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SHARE_OVERLAY, "reading_list");
}
public void addBookmark() {
startService(getServiceIntent(ShareMethod.Type.ADD_BOOKMARK));
slideOut();
animateOut(true);
Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SHARE_OVERLAY, "bookmark");
}
@ -428,34 +429,65 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
}
/**
* Slide the overlay down off the screen and destroy it.
* Slide the overlay down off the screen, display
* a check (if given), and finish the activity.
*/
private void slideOut() {
private void animateOut(final boolean shouldDisplayConfirmation) {
if (isAnimating) {
return;
}
isAnimating = true;
Animation anim = AnimationUtils.loadAnimation(this, R.anim.overlay_slide_down);
findViewById(R.id.sharedialog).startAnimation(anim);
final Animation slideOutAnim = AnimationUtils.loadAnimation(this, R.anim.overlay_slide_down);
anim.setAnimationListener(new Animation.AnimationListener() {
final Animation animationToFinishActivity;
if (!shouldDisplayConfirmation) {
animationToFinishActivity = slideOutAnim;
} else {
final View check = findViewById(R.id.check);
check.setVisibility(View.VISIBLE);
final Animation checkEntryAnim = AnimationUtils.loadAnimation(this, R.anim.overlay_check_entry);
final Animation checkExitAnim = AnimationUtils.loadAnimation(this, R.anim.overlay_check_exit);
checkExitAnim.setStartOffset(checkEntryAnim.getDuration() + 500);
final AnimationSet checkAnimationSet = new AnimationSet(this, null);
checkAnimationSet.addAnimation(checkEntryAnim);
checkAnimationSet.addAnimation(checkExitAnim);
check.startAnimation(checkAnimationSet);
animationToFinishActivity = checkExitAnim;
}
findViewById(R.id.sharedialog).startAnimation(slideOutAnim);
animationToFinishActivity.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// Unused. I can haz Miranda method?
}
public void onAnimationStart(Animation animation) { /* Unused. */ }
@Override
public void onAnimationEnd(Animation animation) {
// (bug 1132720) Hide the View so it doesn't flicker as the Activity closes.
ShareDialog.this.setVisible(false);
finish();
}
@Override
public void onAnimationRepeat(Animation animation) {
// Unused.
public void onAnimationRepeat(Animation animation) { /* Unused. */ }
});
// Allows the user to dismiss the animation early.
setFullscreenFinishOnClickListener();
}
/**
* Sets a fullscreen {@link #finish()} click listener. We do this rather than attaching an
* onClickListener to the root View because in that case, we need to remove all of the
* existing listeners, which is less robust.
*/
private void setFullscreenFinishOnClickListener() {
final View clickTarget = findViewById(R.id.fullscreen_click_target);
clickTarget.setVisibility(View.VISIBLE);
clickTarget.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
@ -465,7 +497,7 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
*/
@Override
public void onBackPressed() {
slideOut();
animateOut(false);
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.SHARE_OVERLAY);
}
@ -474,7 +506,7 @@ public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabT
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
slideOut();
animateOut(false);
Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.SHARE_OVERLAY);
return true;
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="250"
android:fromAlpha="0.0"
android:toAlpha="1.0" />

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<alpha
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="250"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:fillAfter="true" />

View File

@ -6,4 +6,5 @@
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime"
android:fromYDelta="0"
android:toYDelta="100%p" />
android:toYDelta="100%p"
android:fillAfter="true" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 389 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -75,4 +75,20 @@
gecko:disabledText="@string/overlay_share_bookmark_btn_label_already"/>
</LinearLayout>
<ImageView
android:id="@+id/check"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:src="@drawable/overlay_check"
android:visibility="invisible"/>
<!-- This transparent View is used to overlay the
entire Activity with an onClickListener. -->
<View
android:id="@+id/fullscreen_click_target"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:visibility="gone"/>
</merge>

View File

@ -11,8 +11,8 @@
-->
<style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="GeckoDialogBase" parent="@android:style/Theme.Holo.Light.Dialog">

View File

@ -11,11 +11,11 @@
<style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/text_and_tabs_tray_grey</item>
<item name="colorPrimaryDark">@color/text_and_tabs_tray_grey</item>
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:actionBarStyle">@style/GeckoActionBar</item>
<item name="android:actionBarTheme">@style/ActionBarTheme</item>
<item name="colorAccent">@color/action_orange</item>
</style>
<style name="GeckoPreferencesBase" parent="GeckoBase">

View File

@ -10,7 +10,7 @@
in other res/values-XXX/themes.xml.
-->
<style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowNoTitle">true</item>
<item name="windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
<!-- AppCompat sets this to transparent by default:
@ -68,6 +68,8 @@
<item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
<item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
<item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
<item name="colorAccent">@color/action_orange</item>
</style>
<style name="Gecko.Dialog" parent="GeckoDialogBase"/>

View File

@ -136,7 +136,6 @@
<string name="overlay_share_no_url">&overlay_share_no_url;</string>
<string name="overlay_share_select_device">&overlay_share_select_device;</string>
<string name="overlay_no_synced_devices">&overlay_no_synced_devices;</string>
<string name="overlay_share_tab_not_sent">&overlay_share_tab_not_sent;</string>
<string name="settings">&settings;</string>
<string name="settings_title">&settings_title;</string>

View File

@ -36,3 +36,4 @@ cspBlocked=This page has a content security policy that prevents it from being l
corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.

View File

@ -2008,7 +2008,6 @@ nsProtocolProxyService::PruneProxyInfo(const nsProtocolInfo &info,
if (allDisabled) {
LOG(("All proxies are disabled, try a DIRECT rule!"));
NS_RELEASE(head);
*list = nullptr;
return;
}

View File

@ -595,36 +595,45 @@ class MozbuildObject(ProcessExecutionMixin):
def _make_path(self):
baseconfig = os.path.join(self.topsrcdir, 'config', 'baseconfig.mk')
def is_xcode_lisense_error(output):
return self._is_osx() and 'Agreeing to the Xcode' in output
def validate_make(make):
if os.path.exists(baseconfig) and os.path.exists(make):
cmd = [make, '-f', baseconfig]
if self._is_windows():
cmd.append('HOST_OS_ARCH=WINNT')
try:
subprocess.check_call(cmd, stdout=open(os.devnull, 'wb'),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
return False
return True
return False
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
return False, is_xcode_lisense_error(e.output)
return True, False
return False, False
xcode_lisense_error = False
possible_makes = ['gmake', 'make', 'mozmake', 'gnumake']
if 'MAKE' in os.environ:
make = os.environ['MAKE']
if os.path.isabs(make):
if validate_make(make):
return [make]
else:
possible_makes.insert(0, make)
possible_makes.insert(0, make)
for test in possible_makes:
try:
make = which.which(test)
except which.WhichError:
continue
if validate_make(make):
if os.path.isabs(test):
make = test
else:
try:
make = which.which(test)
except which.WhichError:
continue
result, xcode_lisense_error_tmp = validate_make(make)
if result:
return [make]
if xcode_lisense_error_tmp:
xcode_lisense_error = True
if xcode_lisense_error:
raise Exception('Xcode requires accepting to the license agreement.\n'
'Please run Xcode and accept the license agreement.')
if self._is_windows():
raise Exception('Could not find a suitable make implementation.\n'
@ -641,6 +650,9 @@ class MozbuildObject(ProcessExecutionMixin):
def _is_windows(self):
return os.name in ('nt', 'ce')
def _is_osx(self):
return 'darwin' in str(sys.platform).lower()
def _spawn(self, cls):
"""Create a new MozbuildObject-derived class instance from ourselves.

Some files were not shown because too many files have changed in this diff Show More