зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2ginbound, a=merge
This commit is contained in:
Коммит
bf91aa6821
|
@ -399,7 +399,7 @@ var FullScreen = {
|
|||
Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
|
||||
hostLabel.value = utils.DownloadUtils.getURIHost(uri.spec)[0];
|
||||
}
|
||||
this._element.className = gIdentityHandler.getMode();
|
||||
this._element.className = gIdentityHandler.fullscreenWarningClassName;
|
||||
|
||||
// User should be allowed to explicitly disable
|
||||
// the prompt if they really want.
|
||||
|
|
|
@ -4506,33 +4506,6 @@ var XULBrowserWindow = {
|
|||
throw "onSecurityChange: aIsSimulated receieved an unexpected type";
|
||||
}
|
||||
|
||||
// aState is defined as a bitmask that may be extended in the future.
|
||||
// We filter out any unknown bits before testing for known values.
|
||||
const wpl = Components.interfaces.nsIWebProgressListener;
|
||||
const wpl_security_bits = wpl.STATE_IS_SECURE |
|
||||
wpl.STATE_IS_BROKEN |
|
||||
wpl.STATE_IS_INSECURE;
|
||||
var level;
|
||||
|
||||
switch (this._state & wpl_security_bits) {
|
||||
case wpl.STATE_IS_SECURE:
|
||||
level = "high";
|
||||
break;
|
||||
case wpl.STATE_IS_BROKEN:
|
||||
level = "broken";
|
||||
break;
|
||||
}
|
||||
|
||||
if (level) {
|
||||
// We don't style the Location Bar based on the the 'level' attribute
|
||||
// anymore, but still set it for third-party themes.
|
||||
if (gURLBar)
|
||||
gURLBar.setAttribute("level", level);
|
||||
} else {
|
||||
if (gURLBar)
|
||||
gURLBar.removeAttribute("level");
|
||||
}
|
||||
|
||||
// Make sure the "https" part of the URL is striked out or not,
|
||||
// depending on the current mixed active content blocking state.
|
||||
gURLBar.formatValue();
|
||||
|
@ -4540,7 +4513,7 @@ var XULBrowserWindow = {
|
|||
try {
|
||||
uri = Services.uriFixup.createExposableURI(uri);
|
||||
} catch (e) {}
|
||||
gIdentityHandler.checkIdentity(this._state, uri);
|
||||
gIdentityHandler.updateIdentity(this._state, uri);
|
||||
TrackingProtection.onSecurityChange(this._state, aIsSimulated);
|
||||
},
|
||||
|
||||
|
@ -6741,22 +6714,64 @@ function formatURL(aFormat, aIsPref) {
|
|||
* Utility object to handle manipulations of the identity indicators in the UI
|
||||
*/
|
||||
var gIdentityHandler = {
|
||||
// Mode strings used to control CSS display
|
||||
IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information
|
||||
IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification
|
||||
IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information
|
||||
IDENTITY_MODE_USES_WEAK_CIPHER : "unknownIdentity weakCipher", // SSL with RC4 cipher suite or SSL3
|
||||
IDENTITY_MODE_MIXED_DISPLAY_LOADED : "unknownIdentity mixedContent mixedDisplayContent", // SSL with unauthenticated display content
|
||||
IDENTITY_MODE_MIXED_ACTIVE_LOADED : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated active (and perhaps also display) content
|
||||
IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED : "unknownIdentity mixedContent mixedDisplayContentLoadedActiveBlocked", // SSL with unauthenticated display content; unauthenticated active content is blocked.
|
||||
IDENTITY_MODE_MIXED_ACTIVE_BLOCKED : "verifiedDomain mixedContent mixedActiveBlocked", // SSL with unauthenticated active content blocked; no unauthenticated display content
|
||||
IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED : "verifiedIdentity mixedContent mixedActiveBlocked", // SSL with unauthenticated active content blocked; no unauthenticated display content
|
||||
IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI
|
||||
|
||||
_isChromeUI: false,
|
||||
_sslStatus: null,
|
||||
/**
|
||||
* nsIURI for which the identity UI is displayed. This has been already
|
||||
* processed by nsIURIFixup.createExposableURI.
|
||||
*/
|
||||
_uri: null,
|
||||
|
||||
/**
|
||||
* We only know the connection type if this._uri has a defined "host" part.
|
||||
*
|
||||
* These URIs, like "about:" and "data:" URIs, will usually be treated as a
|
||||
* non-secure connection, unless they refer to an internally implemented
|
||||
* browser page or resolve to "file:" URIs.
|
||||
*/
|
||||
_uriHasHost: false,
|
||||
|
||||
/**
|
||||
* Whether this._uri refers to an internally implemented browser page.
|
||||
*
|
||||
* Note that this is set for some "about:" pages, but general "chrome:" URIs
|
||||
* are not included in this category by default.
|
||||
*/
|
||||
_isSecureInternalUI: false,
|
||||
|
||||
/**
|
||||
* nsISSLStatus metadata provided by gBrowser.securityUI the last time the
|
||||
* identity UI was updated, or null if the connection is not secure.
|
||||
*/
|
||||
_sslStatus: null,
|
||||
|
||||
/**
|
||||
* Bitmask provided by nsIWebProgressListener.onSecurityChange.
|
||||
*/
|
||||
_state: 0,
|
||||
|
||||
get _isBroken() {
|
||||
return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
|
||||
},
|
||||
|
||||
get _isSecure() {
|
||||
return this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
|
||||
},
|
||||
|
||||
get _isEV() {
|
||||
return this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
|
||||
},
|
||||
|
||||
get _isMixedActiveContentLoaded() {
|
||||
return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
|
||||
},
|
||||
|
||||
get _isMixedActiveContentBlocked() {
|
||||
return this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
|
||||
},
|
||||
|
||||
get _isMixedPassiveContentLoaded() {
|
||||
return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
|
||||
},
|
||||
|
||||
// smart getters
|
||||
get _identityPopup () {
|
||||
delete this._identityPopup;
|
||||
|
@ -6888,8 +6903,7 @@ var gIdentityHandler = {
|
|||
*/
|
||||
getIdentityData : function() {
|
||||
var result = {};
|
||||
var status = this._sslStatus.QueryInterface(Ci.nsISSLStatus);
|
||||
var cert = status.serverCert;
|
||||
var cert = this._sslStatus.serverCert;
|
||||
|
||||
// Human readable name of Subject
|
||||
result.subjectOrg = cert.organization;
|
||||
|
@ -6916,69 +6930,45 @@ var gIdentityHandler = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Determine the identity of the page being displayed by examining its SSL cert
|
||||
* (if available) and, if necessary, update the UI to reflect this. Intended to
|
||||
* be called by onSecurityChange
|
||||
* Update the identity user interface for the page currently being displayed.
|
||||
*
|
||||
* @param PRUint32 state
|
||||
* @param nsIURI uri The address for which the UI should be updated.
|
||||
* This examines the SSL certificate metadata, if available, as well as the
|
||||
* connection type and other security-related state information for the page.
|
||||
*
|
||||
* @param state
|
||||
* Bitmask provided by nsIWebProgressListener.onSecurityChange.
|
||||
* @param uri
|
||||
* nsIURI for which the identity UI should be displayed, already
|
||||
* processed by nsIURIFixup.createExposableURI.
|
||||
*/
|
||||
checkIdentity : function(state, uri) {
|
||||
let nsIWebProgressListener = Ci.nsIWebProgressListener;
|
||||
updateIdentity(state, uri) {
|
||||
this._state = state;
|
||||
this._uri = uri;
|
||||
|
||||
// Firstly, populate the state properties required to display the UI. See
|
||||
// the documentation of the individual properties for details.
|
||||
|
||||
// For some URIs like data: we can't get a host. URIs without a host will
|
||||
// usually be treated as a non-secure connection if they're not on the
|
||||
// whitelist below and don't resolve to file:// URIs internally.
|
||||
let unknown = false;
|
||||
try {
|
||||
uri.host;
|
||||
} catch (e) { unknown = true; }
|
||||
|
||||
// Chrome URIs however get special treatment. Some chrome URIs are
|
||||
// whitelisted to provide a positive security signal to the user.
|
||||
let whitelist = /^(?:accounts|addons|app-manager|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|sessionrestore|support|welcomeback)(?:[?#]|$)/i;
|
||||
let isChromeUI = uri.schemeIs("about") && whitelist.test(uri.path);
|
||||
let mode = this.IDENTITY_MODE_UNKNOWN;
|
||||
|
||||
if (isChromeUI) {
|
||||
mode = this.IDENTITY_MODE_CHROMEUI;
|
||||
} else if (unknown) {
|
||||
// Use default mode.
|
||||
} else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
|
||||
if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
|
||||
mode = this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED;
|
||||
} else {
|
||||
mode = this.IDENTITY_MODE_IDENTIFIED;
|
||||
}
|
||||
} else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
|
||||
if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
|
||||
mode = this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED;
|
||||
} else {
|
||||
mode = this.IDENTITY_MODE_DOMAIN_VERIFIED;
|
||||
}
|
||||
} else if (state & nsIWebProgressListener.STATE_IS_BROKEN) {
|
||||
if (state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) {
|
||||
mode = this.IDENTITY_MODE_MIXED_ACTIVE_LOADED;
|
||||
} else if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
|
||||
mode = this.IDENTITY_MODE_MIXED_DISPLAY_LOADED_ACTIVE_BLOCKED;
|
||||
} else if (state & nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT) {
|
||||
mode = this.IDENTITY_MODE_MIXED_DISPLAY_LOADED;
|
||||
} else {
|
||||
mode = this.IDENTITY_MODE_USES_WEAK_CIPHER;
|
||||
}
|
||||
this._uri.host;
|
||||
this._uriHasHost = true;
|
||||
} catch (ex) {
|
||||
this._uriHasHost = false;
|
||||
}
|
||||
|
||||
// We need those values later when populating the control center.
|
||||
this._uri = uri;
|
||||
this._state = state;
|
||||
this._isChromeUI = isChromeUI;
|
||||
this._sslStatus =
|
||||
gBrowser.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus;
|
||||
let whitelist = /^(?:accounts|addons|app-manager|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|sessionrestore|support|welcomeback)(?:[?#]|$)/i;
|
||||
this._isSecureInternalUI = uri.schemeIs("about") && whitelist.test(uri.path);
|
||||
|
||||
this._sslStatus = gBrowser.securityUI
|
||||
.QueryInterface(Ci.nsISSLStatusProvider)
|
||||
.SSLStatus;
|
||||
if (this._sslStatus) {
|
||||
this._sslStatus.QueryInterface(Ci.nsISSLStatus);
|
||||
}
|
||||
|
||||
// Then, update the user interface with the available data.
|
||||
|
||||
// Update the identity block.
|
||||
if (this._identityBox) {
|
||||
this._identityBox.className = mode;
|
||||
this.refreshIdentityBlock(mode);
|
||||
this.refreshIdentityBlock();
|
||||
}
|
||||
|
||||
// NOTE: We do NOT update the identity popup (the control center) when
|
||||
|
@ -7004,48 +6994,40 @@ var gIdentityHandler = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Return the current mode, which should be one of IDENTITY_MODE_*.
|
||||
* Return the CSS class name to set on the "fullscreen-warning" element to
|
||||
* display information about connection security in the notification shown
|
||||
* when a site enters the fullscreen mode.
|
||||
*/
|
||||
getMode: function() {
|
||||
return this._mode;
|
||||
get fullscreenWarningClassName() {
|
||||
// Note that the fullscreen warning does not handle _isSecureInternalUI.
|
||||
if (this._uriHasHost && this._isEV) {
|
||||
return "verifiedIdentity";
|
||||
}
|
||||
if (this._uriHasHost && this._isSecure) {
|
||||
return "verifiedDomain";
|
||||
}
|
||||
return "unknownIdentity";
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up the messages for the primary identity UI based on the specified mode,
|
||||
* and the details of the SSL cert, where applicable
|
||||
*
|
||||
* @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
|
||||
* Updates the identity block user interface with the data from this object.
|
||||
*/
|
||||
refreshIdentityBlock(newMode) {
|
||||
refreshIdentityBlock() {
|
||||
let icon_label = "";
|
||||
let tooltip = "";
|
||||
let icon_country_label = "";
|
||||
let icon_labels_dir = "ltr";
|
||||
|
||||
switch (newMode) {
|
||||
case this.IDENTITY_MODE_DOMAIN_VERIFIED:
|
||||
case this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED: {
|
||||
let iData = this.getIdentityData();
|
||||
if (this._isSecureInternalUI) {
|
||||
this._identityBox.className = "chromeUI";
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
icon_label = brandBundle.getString("brandShorterName");
|
||||
} else if (this._uriHasHost && this._isEV) {
|
||||
this._identityBox.className = "verifiedIdentity";
|
||||
if (this._isMixedActiveContentBlocked) {
|
||||
this._identityBox.classList.add("mixedActiveBlocked");
|
||||
}
|
||||
|
||||
// Verifier is either the CA Org, for a normal cert, or a special string
|
||||
// for certs that are trusted because of a security exception.
|
||||
tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
|
||||
[iData.caOrg]);
|
||||
|
||||
// This can't throw, because URI's with a host that throw don't end up in this case.
|
||||
let host = this._uri.host;
|
||||
let port = 443;
|
||||
try {
|
||||
if (this._uri.port > 0)
|
||||
port = this._uri.port;
|
||||
} catch (e) {}
|
||||
|
||||
if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {}))
|
||||
tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
|
||||
|
||||
break; }
|
||||
case this.IDENTITY_MODE_IDENTIFIED:
|
||||
case this.IDENTITY_MODE_MIXED_ACTIVE_BLOCKED_IDENTIFIED: {
|
||||
// If it's identified, then we can populate the dialog with credentials
|
||||
let iData = this.getIdentityData();
|
||||
tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
|
||||
|
@ -7062,12 +7044,43 @@ var gIdentityHandler = {
|
|||
// Unicode Bidirectional Algorithm proper (at the paragraph level).
|
||||
icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
|
||||
"rtl" : "ltr";
|
||||
break; }
|
||||
case this.IDENTITY_MODE_CHROMEUI:
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
icon_label = brandBundle.getString("brandShorterName");
|
||||
break;
|
||||
default:
|
||||
|
||||
} else if (this._uriHasHost && this._isSecure) {
|
||||
this._identityBox.className = "verifiedDomain";
|
||||
if (this._isMixedActiveContentBlocked) {
|
||||
this._identityBox.classList.add("mixedActiveBlocked");
|
||||
}
|
||||
|
||||
let iData = this.getIdentityData();
|
||||
|
||||
// Verifier is either the CA Org, for a normal cert, or a special string
|
||||
// for certs that are trusted because of a security exception.
|
||||
tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
|
||||
[iData.caOrg]);
|
||||
|
||||
let host = this._uri.host;
|
||||
let port = 443;
|
||||
try {
|
||||
if (this._uri.port > 0)
|
||||
port = this._uri.port;
|
||||
} catch (e) {}
|
||||
|
||||
if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {})) {
|
||||
tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
|
||||
}
|
||||
} else {
|
||||
this._identityBox.className = "unknownIdentity";
|
||||
if (this._isBroken) {
|
||||
if (this._isMixedActiveContentLoaded) {
|
||||
this._identityBox.classList.add("mixedActiveContent");
|
||||
} else if (this._isMixedActiveContentBlocked) {
|
||||
this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked");
|
||||
} else if (this._isMixedPassiveContentLoaded) {
|
||||
this._identityBox.classList.add("mixedDisplayContent");
|
||||
} else {
|
||||
this._identityBox.classList.add("weakCipher");
|
||||
}
|
||||
}
|
||||
tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
|
||||
}
|
||||
|
||||
|
@ -7093,39 +7106,26 @@ var gIdentityHandler = {
|
|||
let learnMoreHref = `${baseURL}mixed-content`;
|
||||
this._identityPopupMixedContentLearnMore.setAttribute("href", learnMoreHref);
|
||||
|
||||
// Basic connection properties.
|
||||
let isBroken = this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
|
||||
let isSecure = this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
|
||||
let isEV = this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
|
||||
|
||||
// Determine connection security information.
|
||||
let connection = "not-secure";
|
||||
if (this._isChromeUI) {
|
||||
if (this._isSecureInternalUI) {
|
||||
connection = "chrome";
|
||||
} else if (this._isURILoadedFromFile(this._uri)) {
|
||||
} else if (this._isURILoadedFromFile) {
|
||||
connection = "file";
|
||||
} else if (isEV) {
|
||||
} else if (this._isEV) {
|
||||
connection = "secure-ev";
|
||||
} else if (isSecure) {
|
||||
} else if (this._isSecure) {
|
||||
connection = "secure";
|
||||
}
|
||||
|
||||
// Mixed content flags.
|
||||
let isMixedActiveContentLoaded =
|
||||
this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
|
||||
let isMixedActiveContentBlocked =
|
||||
this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
|
||||
let isMixedPassiveContentLoaded =
|
||||
this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
|
||||
|
||||
// Determine the mixed content state.
|
||||
let mixedcontent = [];
|
||||
if (isMixedPassiveContentLoaded) {
|
||||
if (this._isMixedPassiveContentLoaded) {
|
||||
mixedcontent.push("passive-loaded");
|
||||
}
|
||||
if (isMixedActiveContentLoaded) {
|
||||
if (this._isMixedActiveContentLoaded) {
|
||||
mixedcontent.push("active-loaded");
|
||||
} else if (isMixedActiveContentBlocked) {
|
||||
} else if (this._isMixedActiveContentBlocked) {
|
||||
mixedcontent.push("active-blocked");
|
||||
}
|
||||
mixedcontent = mixedcontent.join(" ");
|
||||
|
@ -7134,7 +7134,7 @@ var gIdentityHandler = {
|
|||
// broken and we can't detect any mixed content loaded then it's a weak
|
||||
// cipher.
|
||||
let ciphers = "";
|
||||
if (isBroken && !isMixedActiveContentLoaded && !isMixedPassiveContentLoaded) {
|
||||
if (this._isBroken && !this._isMixedActiveContentLoaded && !this._isMixedPassiveContentLoaded) {
|
||||
ciphers = "weak";
|
||||
}
|
||||
|
||||
|
@ -7157,7 +7157,7 @@ var gIdentityHandler = {
|
|||
updateAttribute(element, "connection", connection);
|
||||
updateAttribute(element, "ciphers", ciphers);
|
||||
updateAttribute(element, "mixedcontent", mixedcontent);
|
||||
updateAttribute(element, "isbroken", isBroken);
|
||||
updateAttribute(element, "isbroken", this._isBroken);
|
||||
}
|
||||
|
||||
// Initialize the optional strings to empty values
|
||||
|
@ -7179,12 +7179,12 @@ var gIdentityHandler = {
|
|||
}
|
||||
|
||||
// Fill in the CA name if we have a valid TLS certificate.
|
||||
if (isSecure) {
|
||||
if (this._isSecure) {
|
||||
verifier = this._identityBox.tooltipText;
|
||||
}
|
||||
|
||||
// Fill in organization information if we have a valid EV certificate.
|
||||
if (isEV) {
|
||||
if (this._isEV) {
|
||||
crop = "end";
|
||||
|
||||
let iData = this.getIdentityData();
|
||||
|
@ -7216,10 +7216,10 @@ var gIdentityHandler = {
|
|||
this.updateSitePermissions();
|
||||
},
|
||||
|
||||
_isURILoadedFromFile(uri) {
|
||||
get _isURILoadedFromFile() {
|
||||
// Create a channel for the sole purpose of getting the resolved URI
|
||||
// of the request to determine if it's loaded from the file system.
|
||||
let chanOptions = {uri, loadUsingSystemPrincipal: true};
|
||||
let chanOptions = {uri: this._uri, loadUsingSystemPrincipal: true};
|
||||
let resolvedURI;
|
||||
try {
|
||||
resolvedURI = NetUtil.newChannel(chanOptions).URI;
|
||||
|
|
|
@ -623,17 +623,12 @@ ContentSearchUIController.prototype = {
|
|||
return;
|
||||
}
|
||||
let searchWithHeader = document.getElementById("contentSearchSearchWithHeader");
|
||||
while (searchWithHeader.firstChild) {
|
||||
searchWithHeader.firstChild.remove();
|
||||
}
|
||||
if (this.input.value) {
|
||||
let html = "<span class='contentSearchSearchWithHeaderSearchText'>" +
|
||||
this.input.value + "</span>";
|
||||
html = this._strings.searchForKeywordsWith.replace("%S", html);
|
||||
searchWithHeader.innerHTML = html;
|
||||
return;
|
||||
searchWithHeader.innerHTML = this._strings.searchForSomethingWith;
|
||||
searchWithHeader.querySelector('.contentSearchSearchWithHeaderSearchText').textContent = this.input.value;
|
||||
} else {
|
||||
searchWithHeader.textContent = this._strings.searchWithHeader;
|
||||
}
|
||||
searchWithHeader.appendChild(document.createTextNode(this._strings.searchWithHeader));
|
||||
},
|
||||
|
||||
_speculativeConnect: function () {
|
||||
|
|
|
@ -21,8 +21,7 @@ add_task(function* checkIdentityOfAboutSupport() {
|
|||
|
||||
yield promiseTabLoaded(tab);
|
||||
let identityBox = document.getElementById("identity-box");
|
||||
is(identityBox.className, gIdentityHandler.IDENTITY_MODE_CHROMEUI,
|
||||
"Should know that we're chrome.");
|
||||
is(identityBox.className, "chromeUI", "Should know that we're chrome.");
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ function test() {
|
|||
gBrowser.selectedBrowser.addEventListener("load", function () {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
is(document.getElementById("identity-box").className,
|
||||
gIdentityHandler.IDENTITY_MODE_MIXED_DISPLAY_LOADED,
|
||||
"unknownIdentity mixedDisplayContent",
|
||||
"identity box has class name for mixed content");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
|
|
@ -107,7 +107,8 @@ function checkResult() {
|
|||
// getEffectiveHost can't be called for all modes
|
||||
if (gCurrentTest.effectiveHost === null) {
|
||||
let identityBox = document.getElementById("identity-box");
|
||||
is(identityBox.className == gIdentityHandler.IDENTITY_MODE_UNKNOWN || identityBox.className == gIdentityHandler.IDENTITY_MODE_CHROMEUI, true, "mode matched");
|
||||
ok(identityBox.className == "unknownIdentity" ||
|
||||
identityBox.className == "chromeUI", "mode matched");
|
||||
} else {
|
||||
is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
|
||||
}
|
||||
|
|
|
@ -806,7 +806,6 @@ function assertMixedContentBlockingState(tabbrowser, states = {}) {
|
|||
ok(!classList.contains("mixedActiveBlocked"), "No MCB icon on HTTP page");
|
||||
ok(!classList.contains("mixedDisplayContent"), "No MCB icon on HTTP page");
|
||||
ok(!classList.contains("mixedDisplayContentLoadedActiveBlocked"), "No MCB icon on HTTP page");
|
||||
ok(!classList.contains("mixedContent"), "No MCB icon on HTTP page");
|
||||
} else {
|
||||
// Make sure the identity box UI has the correct mixedcontent states and icons
|
||||
is(classList.contains("mixedActiveContent"), activeLoaded,
|
||||
|
@ -817,8 +816,6 @@ function assertMixedContentBlockingState(tabbrowser, states = {}) {
|
|||
"identityBox has expected class for passiveLoaded && !(activeLoaded || activeBlocked)");
|
||||
is(classList.contains("mixedDisplayContentLoadedActiveBlocked"), passiveLoaded && activeBlocked,
|
||||
"identityBox has expected class for passiveLoaded && activeBlocked");
|
||||
is (classList.contains("mixedContent"), activeBlocked || activeLoaded || passiveLoaded,
|
||||
"identityBox has expected class for mixed content");
|
||||
|
||||
if (activeLoaded) {
|
||||
is(identityBoxImage, "url(\"chrome://browser/skin/identity-mixed-active-loaded.svg\")",
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
|
||||
<!-- Active Mixed Content Blocking Disabled -->
|
||||
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded;</description>
|
||||
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded2;</description>
|
||||
<description when-mixedcontent="active-loaded">&identity.description.activeLoaded2; <label observes="identity-popup-mcb-learn-more"/></description>
|
||||
|
||||
<!-- Buttons to enable/disable mixed content blocking. -->
|
||||
<button when-mixedcontent="active-blocked"
|
||||
|
|
|
@ -12,3 +12,4 @@ support-files =
|
|||
[browser_ext_tabs_query.js]
|
||||
[browser_ext_tabs_update.js]
|
||||
[browser_ext_windows_update.js]
|
||||
[browser_ext_contentscript_connect.js]
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
add_task(function* () {
|
||||
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"permissions": ["tabs"]
|
||||
},
|
||||
|
||||
background: function() {
|
||||
var ports_received = 0;
|
||||
var port_messages_received = 0;
|
||||
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
browser.test.assertTrue(!!port, "port1 received");
|
||||
|
||||
ports_received++;
|
||||
browser.test.assertEq(1, ports_received, "1 port received");
|
||||
|
||||
port.onMessage.addListener((msg, sender) => {
|
||||
browser.test.assertEq("port message", msg, "listener1 port message received");
|
||||
|
||||
port_messages_received++
|
||||
browser.test.assertEq(1, port_messages_received, "1 port message received");
|
||||
})
|
||||
});
|
||||
browser.runtime.onConnect.addListener((port) => {
|
||||
browser.test.assertTrue(!!port, "port2 received");
|
||||
|
||||
ports_received++;
|
||||
browser.test.assertEq(2, ports_received, "2 ports received");
|
||||
|
||||
port.onMessage.addListener((msg, sender) => {
|
||||
browser.test.assertEq("port message", msg, "listener2 port message received");
|
||||
|
||||
port_messages_received++
|
||||
browser.test.assertEq(2, port_messages_received, "2 port messages received");
|
||||
|
||||
browser.test.notifyPass("contentscript_connect.pass");
|
||||
});
|
||||
});
|
||||
|
||||
browser.tabs.executeScript({ file: "script.js" });
|
||||
},
|
||||
|
||||
files: {
|
||||
"script.js": function() {
|
||||
var port = browser.runtime.connect();
|
||||
port.postMessage("port message");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitFinish("contentscript_connect.pass");
|
||||
yield extension.unload();
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
});
|
|
@ -401,6 +401,9 @@ loop.conversationViews = (function(mozL10n) {
|
|||
case FAILURE_DETAILS.NO_MEDIA:
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
return mozL10n.get("no_media_failure_message");
|
||||
case FAILURE_DETAILS.TOS_FAILURE:
|
||||
return mozL10n.get("tos_failure_message",
|
||||
{ clientShortname: mozL10n.get("clientShortname2") });
|
||||
default:
|
||||
return mozL10n.get("generic_failure_message");
|
||||
}
|
||||
|
|
|
@ -401,6 +401,9 @@ loop.conversationViews = (function(mozL10n) {
|
|||
case FAILURE_DETAILS.NO_MEDIA:
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
return mozL10n.get("no_media_failure_message");
|
||||
case FAILURE_DETAILS.TOS_FAILURE:
|
||||
return mozL10n.get("tos_failure_message",
|
||||
{ clientShortname: mozL10n.get("clientShortname2") });
|
||||
default:
|
||||
return mozL10n.get("generic_failure_message");
|
||||
}
|
||||
|
|
|
@ -450,21 +450,6 @@ loop.shared.actions = (function() {
|
|||
failedJoinRequest: Boolean
|
||||
}),
|
||||
|
||||
/**
|
||||
* Sets up the room information when it is received.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
*
|
||||
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
|
||||
*/
|
||||
SetupRoomInfo: Action.define("setupRoomInfo", {
|
||||
// roomContextUrls: Array - Optional.
|
||||
// roomDescription: String - Optional.
|
||||
// roomName: String - Optional.
|
||||
roomToken: String,
|
||||
roomUrl: String,
|
||||
socialShareProviders: Array
|
||||
}),
|
||||
|
||||
/**
|
||||
* Updates the room information when it is received.
|
||||
* XXX: should move to some roomActions module - refs bug 1079284
|
||||
|
@ -472,11 +457,15 @@ loop.shared.actions = (function() {
|
|||
* @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
|
||||
*/
|
||||
UpdateRoomInfo: Action.define("updateRoomInfo", {
|
||||
// description: String - Optional.
|
||||
// roomName: String - Optional.
|
||||
roomUrl: String
|
||||
// urls: Array - Optional.
|
||||
// participants: Array - Optional.
|
||||
// roomContextUrls: Array - Optional.
|
||||
// See https://wiki.mozilla.org/Loop/Architecture/Context#Format_of_context.value
|
||||
// roomDescription: String - Optional.
|
||||
// roomInfoFailure: String - Optional.
|
||||
// roomName: String - Optional.
|
||||
// roomState: String - Optional.
|
||||
roomUrl: String
|
||||
// socialShareProviders: Array - Optional.
|
||||
}),
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,12 +49,13 @@ loop.store.ActiveRoomStore = (function() {
|
|||
var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
|
||||
|
||||
var OPTIONAL_ROOMINFO_FIELDS = {
|
||||
urls: "roomContextUrls",
|
||||
description: "roomDescription",
|
||||
participants: "participants",
|
||||
roomContextUrls: "roomContextUrls",
|
||||
roomDescription: "roomDescription",
|
||||
roomInfoFailure: "roomInfoFailure",
|
||||
roomName: "roomName",
|
||||
roomState: "roomState"
|
||||
roomState: "roomState",
|
||||
socialShareProviders: "socialShareProviders"
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -142,8 +143,6 @@ loop.store.ActiveRoomStore = (function() {
|
|||
receivingScreenShare: false,
|
||||
// Any urls (aka context) associated with the room.
|
||||
roomContextUrls: null,
|
||||
// The roomCryptoKey to decode the context data if necessary.
|
||||
roomCryptoKey: null,
|
||||
// The description for a room as stored in the context data.
|
||||
roomDescription: null,
|
||||
// Room information failed to be obtained for a reason. See ROOM_INFO_FAILURES.
|
||||
|
@ -237,7 +236,6 @@ loop.store.ActiveRoomStore = (function() {
|
|||
this.dispatcher.register(this, [
|
||||
"roomFailure",
|
||||
"retryAfterRoomFailure",
|
||||
"setupRoomInfo",
|
||||
"updateRoomInfo",
|
||||
"gotMediaPermission",
|
||||
"joinRoom",
|
||||
|
@ -262,6 +260,15 @@ loop.store.ActiveRoomStore = (function() {
|
|||
"connectionStatus",
|
||||
"mediaConnected"
|
||||
]);
|
||||
|
||||
this._onUpdateListener = this._handleRoomUpdate.bind(this);
|
||||
this._onDeleteListener = this._handleRoomDelete.bind(this);
|
||||
this._onSocialShareUpdate = this._handleSocialShareUpdate.bind(this);
|
||||
|
||||
this._mozLoop.rooms.on("update:" + this._storeState.roomToken, this._onUpdateListener);
|
||||
this._mozLoop.rooms.on("delete:" + this._storeState.roomToken, this._onDeleteListener);
|
||||
window.addEventListener("LoopShareWidgetChanged", this._onSocialShareUpdate);
|
||||
window.addEventListener("LoopSocialProvidersChanged", this._onSocialShareUpdate);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -278,13 +285,14 @@ loop.store.ActiveRoomStore = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
this._registerPostSetupActions();
|
||||
|
||||
this.setStoreState({
|
||||
roomState: ROOM_STATES.GATHER,
|
||||
roomToken: actionData.roomToken,
|
||||
windowId: actionData.windowId
|
||||
});
|
||||
|
||||
this._registerPostSetupActions();
|
||||
|
||||
// Get the window data from the mozLoop api.
|
||||
this._mozLoop.rooms.get(actionData.roomToken,
|
||||
function(error, roomData) {
|
||||
|
@ -296,12 +304,12 @@ loop.store.ActiveRoomStore = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
this.dispatchAction(new sharedActions.SetupRoomInfo({
|
||||
this.dispatchAction(new sharedActions.UpdateRoomInfo({
|
||||
participants: roomData.participants,
|
||||
roomToken: actionData.roomToken,
|
||||
roomContextUrls: roomData.decryptedContext.urls,
|
||||
roomDescription: roomData.decryptedContext.description,
|
||||
roomName: roomData.decryptedContext.roomName,
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomUrl: roomData.roomUrl,
|
||||
socialShareProviders: this._mozLoop.getSocialShareProviders()
|
||||
}));
|
||||
|
@ -326,23 +334,18 @@ loop.store.ActiveRoomStore = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
this._registerPostSetupActions();
|
||||
|
||||
this.setStoreState({
|
||||
roomToken: actionData.token,
|
||||
roomState: ROOM_STATES.GATHER,
|
||||
roomCryptoKey: actionData.cryptoKey
|
||||
roomToken: actionData.token,
|
||||
standalone: true
|
||||
});
|
||||
|
||||
this._mozLoop.rooms.on("update:" + actionData.roomToken,
|
||||
this._handleRoomUpdate.bind(this));
|
||||
this._mozLoop.rooms.on("delete:" + actionData.roomToken,
|
||||
this._handleRoomDelete.bind(this));
|
||||
this._registerPostSetupActions();
|
||||
|
||||
this._getRoomDataForStandalone();
|
||||
this._getRoomDataForStandalone(actionData.cryptoKey);
|
||||
},
|
||||
|
||||
_getRoomDataForStandalone: function() {
|
||||
_getRoomDataForStandalone: function(roomCryptoKey) {
|
||||
this._mozLoop.rooms.get(this._storeState.roomToken, function(err, result) {
|
||||
if (err) {
|
||||
this.dispatchAction(new sharedActions.RoomFailure({
|
||||
|
@ -353,15 +356,14 @@ loop.store.ActiveRoomStore = (function() {
|
|||
}
|
||||
|
||||
var roomInfoData = new sharedActions.UpdateRoomInfo({
|
||||
// If we've got this far, then we want to go to the ready state
|
||||
// regardless of success of failure. This is because failures of
|
||||
// crypto don't stop the user using the room, they just stop
|
||||
// us putting up the information.
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomUrl: result.roomUrl
|
||||
});
|
||||
|
||||
// If we've got this far, then we want to go to the ready state
|
||||
// regardless of success of failure. This is because failures of
|
||||
// crypto don't stop the user using the room, they just stop
|
||||
// us putting up the information.
|
||||
roomInfoData.roomState = ROOM_STATES.READY;
|
||||
|
||||
if (!result.context && !result.roomName) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_DATA;
|
||||
this.dispatcher.dispatch(roomInfoData);
|
||||
|
@ -381,8 +383,6 @@ loop.store.ActiveRoomStore = (function() {
|
|||
return;
|
||||
}
|
||||
|
||||
var roomCryptoKey = this.getStoreState("roomCryptoKey");
|
||||
|
||||
if (!roomCryptoKey) {
|
||||
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_CRYPTO_KEY;
|
||||
this.dispatcher.dispatch(roomInfoData);
|
||||
|
@ -395,8 +395,8 @@ loop.store.ActiveRoomStore = (function() {
|
|||
.then(function(decryptedResult) {
|
||||
var realResult = JSON.parse(decryptedResult);
|
||||
|
||||
roomInfoData.description = realResult.description;
|
||||
roomInfoData.urls = realResult.urls;
|
||||
roomInfoData.roomDescription = realResult.description;
|
||||
roomInfoData.roomContextUrls = realResult.urls;
|
||||
roomInfoData.roomName = realResult.roomName;
|
||||
|
||||
dispatcher.dispatch(roomInfoData);
|
||||
|
@ -407,39 +407,6 @@ loop.store.ActiveRoomStore = (function() {
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the setupRoomInfo action. Sets up the initial room data and
|
||||
* sets the state to `READY`.
|
||||
*
|
||||
* @param {sharedActions.SetupRoomInfo} actionData
|
||||
*/
|
||||
setupRoomInfo: function(actionData) {
|
||||
if (this._onUpdateListener) {
|
||||
console.error("Room info already set up!");
|
||||
return;
|
||||
}
|
||||
|
||||
this.setStoreState({
|
||||
participants: actionData.participants,
|
||||
roomContextUrls: actionData.roomContextUrls,
|
||||
roomDescription: actionData.roomDescription,
|
||||
roomName: actionData.roomName,
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomToken: actionData.roomToken,
|
||||
roomUrl: actionData.roomUrl,
|
||||
socialShareProviders: actionData.socialShareProviders
|
||||
});
|
||||
|
||||
this._onUpdateListener = this._handleRoomUpdate.bind(this);
|
||||
this._onDeleteListener = this._handleRoomDelete.bind(this);
|
||||
this._onSocialShareUpdate = this._handleSocialShareUpdate.bind(this);
|
||||
|
||||
this._mozLoop.rooms.on("update:" + actionData.roomToken, this._onUpdateListener);
|
||||
this._mozLoop.rooms.on("delete:" + actionData.roomToken, this._onDeleteListener);
|
||||
window.addEventListener("LoopShareWidgetChanged", this._onSocialShareUpdate);
|
||||
window.addEventListener("LoopSocialProvidersChanged", this._onSocialShareUpdate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the updateRoomInfo action. Updates the room data.
|
||||
*
|
||||
|
|
|
@ -917,25 +917,38 @@ loop.OTSdkDriver = (function() {
|
|||
},
|
||||
|
||||
_onOTException: function(event) {
|
||||
if (event.code === OT.ExceptionCodes.UNABLE_TO_PUBLISH &&
|
||||
event.message === "GetUserMedia") {
|
||||
// We free up the publisher here in case the store wants to try
|
||||
// grabbing the media again.
|
||||
if (this.publisher) {
|
||||
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
|
||||
this.publisher.destroy();
|
||||
delete this.publisher;
|
||||
delete this._mockPublisherEl;
|
||||
}
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
}));
|
||||
} else if (event.code === OT.ExceptionCodes.UNABLE_TO_PUBLISH) {
|
||||
// We need to log the message so that we can understand where the exception
|
||||
// is coming from. Potentially a temporary addition.
|
||||
this._notifyMetricsEvent("sdk.exception." + event.code + "." + event.message);
|
||||
} else {
|
||||
this._notifyMetricsEvent("sdk.exception." + event.code);
|
||||
switch (event.code) {
|
||||
case OT.ExceptionCodes.UNABLE_TO_PUBLISH:
|
||||
if (event.message === "GetUserMedia") {
|
||||
// We free up the publisher here in case the store wants to try
|
||||
// grabbing the media again.
|
||||
if (this.publisher) {
|
||||
this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
|
||||
this.publisher.destroy();
|
||||
delete this.publisher;
|
||||
delete this._mockPublisherEl;
|
||||
}
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
}));
|
||||
// No exception logging as this is a handled event.
|
||||
} else {
|
||||
// We need to log the message so that we can understand where the exception
|
||||
// is coming from. Potentially a temporary addition.
|
||||
this._notifyMetricsEvent("sdk.exception." + event.code + "." + event.message);
|
||||
}
|
||||
break;
|
||||
case OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE:
|
||||
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.TOS_FAILURE
|
||||
}));
|
||||
// We still need to log the exception so that the server knows why this
|
||||
// attempt failed.
|
||||
this._notifyMetricsEvent("sdk.exception." + event.code);
|
||||
break;
|
||||
default:
|
||||
this._notifyMetricsEvent("sdk.exception." + event.code);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -80,6 +80,9 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
|
|||
COULD_NOT_CONNECT: "reason-could-not-connect",
|
||||
NETWORK_DISCONNECTED: "reason-network-disconnected",
|
||||
EXPIRED_OR_INVALID: "reason-expired-or-invalid",
|
||||
// TOS_FAILURE reflects the sdk error code 1026:
|
||||
// https://tokbox.com/developer/sdks/js/reference/ExceptionEvent.html
|
||||
TOS_FAILURE: "reason-tos-failure",
|
||||
UNKNOWN: "reason-unknown"
|
||||
};
|
||||
|
||||
|
|
|
@ -43,6 +43,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
return mozL10n.get("rooms_media_denied_message");
|
||||
case FAILURE_DETAILS.EXPIRED_OR_INVALID:
|
||||
return mozL10n.get("rooms_unavailable_notification_message");
|
||||
case FAILURE_DETAILS.TOS_FAILURE:
|
||||
return mozL10n.get("tos_failure_message",
|
||||
{ clientShortname: mozL10n.get("clientShortname2") });
|
||||
default:
|
||||
return mozL10n.get("status_error");
|
||||
}
|
||||
|
@ -52,7 +55,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
* This renders a retry button if one is necessary.
|
||||
*/
|
||||
renderRetryButton: function() {
|
||||
if (this.props.failureReason === FAILURE_DETAILS.EXPIRED_OR_INVALID) {
|
||||
if (this.props.failureReason === FAILURE_DETAILS.EXPIRED_OR_INVALID ||
|
||||
this.props.failureReason === FAILURE_DETAILS.TOS_FAILURE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -593,6 +597,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
});
|
||||
|
||||
return {
|
||||
StandaloneRoomFailureView: StandaloneRoomFailureView,
|
||||
StandaloneRoomFooter: StandaloneRoomFooter,
|
||||
StandaloneRoomHeader: StandaloneRoomHeader,
|
||||
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
|
||||
|
|
|
@ -43,6 +43,9 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
return mozL10n.get("rooms_media_denied_message");
|
||||
case FAILURE_DETAILS.EXPIRED_OR_INVALID:
|
||||
return mozL10n.get("rooms_unavailable_notification_message");
|
||||
case FAILURE_DETAILS.TOS_FAILURE:
|
||||
return mozL10n.get("tos_failure_message",
|
||||
{ clientShortname: mozL10n.get("clientShortname2") });
|
||||
default:
|
||||
return mozL10n.get("status_error");
|
||||
}
|
||||
|
@ -52,7 +55,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
* This renders a retry button if one is necessary.
|
||||
*/
|
||||
renderRetryButton: function() {
|
||||
if (this.props.failureReason === FAILURE_DETAILS.EXPIRED_OR_INVALID) {
|
||||
if (this.props.failureReason === FAILURE_DETAILS.EXPIRED_OR_INVALID ||
|
||||
this.props.failureReason === FAILURE_DETAILS.TOS_FAILURE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -593,6 +597,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
|
|||
});
|
||||
|
||||
return {
|
||||
StandaloneRoomFailureView: StandaloneRoomFailureView,
|
||||
StandaloneRoomFooter: StandaloneRoomFooter,
|
||||
StandaloneRoomHeader: StandaloneRoomHeader,
|
||||
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
|
||||
|
|
|
@ -3,6 +3,10 @@ conversation_has_ended=Your conversation has ended.
|
|||
generic_failure_message=We're having technical difficulties…
|
||||
generic_failure_with_reason2=You can try again or email a link to be reached at later.
|
||||
generic_failure_no_reason2=Would you like to try again?
|
||||
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname2}}
|
||||
## as this will be replaced by the shortname.
|
||||
tos_failure_message={{clientShortname}} is not available in your country.
|
||||
|
||||
retry_call_button=Retry
|
||||
unable_retrieve_call_info=Unable to retrieve conversation information.
|
||||
hangup_button_title=Hang up
|
||||
|
|
|
@ -307,6 +307,16 @@ describe("loop.conversationViews", function () {
|
|||
expect(message.textContent).eql("contact_unavailable_title");
|
||||
});
|
||||
|
||||
it("should display a ToS failure message for the ToS failure reason", function() {
|
||||
view = mountTestComponent({
|
||||
failureReason: FAILURE_DETAILS.TOS_FAILURE
|
||||
});
|
||||
|
||||
var message = view.getDOMNode().querySelector(".failure-info-message");
|
||||
|
||||
expect(message.textContent).eql("tos_failure_message");
|
||||
});
|
||||
|
||||
it("should display a generic unavailable message if the contact doesn't have a display name", function() {
|
||||
view = mountTestComponent({
|
||||
contact: {
|
||||
|
|
|
@ -326,19 +326,29 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
);
|
||||
});
|
||||
|
||||
it("should set the state to `GATHER`",
|
||||
function() {
|
||||
store.setupWindowData(new sharedActions.SetupWindowData({
|
||||
windowId: "42",
|
||||
type: "room",
|
||||
roomToken: fakeToken
|
||||
}));
|
||||
it("should set the state to `GATHER`", function() {
|
||||
store.setupWindowData(new sharedActions.SetupWindowData({
|
||||
windowId: "42",
|
||||
type: "room",
|
||||
roomToken: fakeToken
|
||||
}));
|
||||
|
||||
expect(store.getStoreState()).to.have.property(
|
||||
"roomState", ROOM_STATES.GATHER);
|
||||
});
|
||||
expect(store.getStoreState()).to.have.property(
|
||||
"roomState", ROOM_STATES.GATHER);
|
||||
});
|
||||
|
||||
it("should dispatch an SetupRoomInfo action if the get is successful",
|
||||
it("should store the room token and window id", function() {
|
||||
store.setupWindowData(new sharedActions.SetupWindowData({
|
||||
windowId: "42",
|
||||
type: "room",
|
||||
roomToken: fakeToken
|
||||
}));
|
||||
|
||||
expect(store.getStoreState().windowId).eql("42");
|
||||
expect(store.getStoreState().roomToken).eql(fakeToken);
|
||||
});
|
||||
|
||||
it("should dispatch an UpdateRoomInfo action if the get is successful",
|
||||
function() {
|
||||
store.setupWindowData(new sharedActions.SetupWindowData({
|
||||
windowId: "42",
|
||||
|
@ -348,12 +358,12 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.SetupRoomInfo({
|
||||
new sharedActions.UpdateRoomInfo({
|
||||
roomContextUrls: undefined,
|
||||
roomDescription: undefined,
|
||||
participants: [],
|
||||
roomToken: fakeToken,
|
||||
roomName: fakeRoomData.decryptedContext.roomName,
|
||||
roomState: ROOM_STATES.READY,
|
||||
roomUrl: fakeRoomData.roomUrl,
|
||||
socialShareProviders: []
|
||||
}));
|
||||
|
@ -551,8 +561,11 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
store.fetchServerData(fetchServerAction);
|
||||
|
||||
var expectedData = _.extend({
|
||||
roomContextUrls: roomContext.urls,
|
||||
roomDescription: roomContext.description,
|
||||
roomName: roomContext.roomName,
|
||||
roomState: ROOM_STATES.READY
|
||||
}, roomContext, expectedDetails);
|
||||
}, expectedDetails);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
|
@ -586,47 +599,18 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#setupRoomInfo", function() {
|
||||
var fakeRoomInfo;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeRoomInfo = {
|
||||
roomName: "Its a room",
|
||||
roomToken: "fakeToken",
|
||||
roomUrl: "http://invalid",
|
||||
socialShareProviders: []
|
||||
};
|
||||
});
|
||||
|
||||
it("should set the state to READY", function() {
|
||||
store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
|
||||
|
||||
expect(store._storeState.roomState).eql(ROOM_STATES.READY);
|
||||
});
|
||||
|
||||
it("should save the room information", function() {
|
||||
store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
|
||||
|
||||
var state = store.getStoreState();
|
||||
expect(state.roomName).eql(fakeRoomInfo.roomName);
|
||||
expect(state.roomToken).eql(fakeRoomInfo.roomToken);
|
||||
expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
|
||||
expect(state.socialShareProviders).eql([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#updateRoomInfo", function() {
|
||||
var fakeRoomInfo;
|
||||
|
||||
beforeEach(function() {
|
||||
fakeRoomInfo = {
|
||||
roomName: "Its a room",
|
||||
roomUrl: "http://invalid",
|
||||
urls: [{
|
||||
roomContextUrls: [{
|
||||
description: "fake site",
|
||||
location: "http://invalid.com",
|
||||
thumbnail: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
|
||||
}]
|
||||
}],
|
||||
roomName: "Its a room",
|
||||
roomUrl: "http://invalid"
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -636,7 +620,7 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
var state = store.getStoreState();
|
||||
expect(state.roomName).eql(fakeRoomInfo.roomName);
|
||||
expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
|
||||
expect(state.roomContextUrls).eql(fakeRoomInfo.urls);
|
||||
expect(state.roomContextUrls).eql(fakeRoomInfo.roomContextUrls);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1602,11 +1586,10 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
describe("Events", function() {
|
||||
describe("update:{roomToken}", function() {
|
||||
beforeEach(function() {
|
||||
store.setupRoomInfo(new sharedActions.SetupRoomInfo({
|
||||
roomName: "Its a room",
|
||||
store.setupWindowData(new sharedActions.SetupWindowData({
|
||||
roomToken: "fakeToken",
|
||||
roomUrl: "http://invalid",
|
||||
socialShareProviders: []
|
||||
type: "room",
|
||||
windowId: "42"
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -1667,11 +1650,11 @@ describe("loop.store.ActiveRoomStore", function () {
|
|||
};
|
||||
|
||||
beforeEach(function() {
|
||||
store.setupRoomInfo(new sharedActions.SetupRoomInfo(
|
||||
_.extend(fakeRoomData, {
|
||||
socialShareProviders: []
|
||||
})
|
||||
));
|
||||
store.setupWindowData(new sharedActions.SetupWindowData({
|
||||
roomToken: "fakeToken",
|
||||
type: "room",
|
||||
windowId: "42"
|
||||
}));
|
||||
});
|
||||
|
||||
it("should disconnect all room connections", function() {
|
||||
|
|
|
@ -68,6 +68,7 @@ describe("loop.OTSdkDriver", function () {
|
|||
window.OT = {
|
||||
ExceptionCodes: {
|
||||
CONNECT_FAILED: 1006,
|
||||
TERMS_OF_SERVICE_FAILURE: 1026,
|
||||
UNABLE_TO_PUBLISH: 1500
|
||||
}
|
||||
};
|
||||
|
@ -1599,6 +1600,38 @@ describe("loop.OTSdkDriver", function () {
|
|||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("ToS Failure", function() {
|
||||
it("should dispatch a ConnectionFailure action", function() {
|
||||
sdk.trigger("exception", {
|
||||
code: OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE,
|
||||
message: "Fake"
|
||||
});
|
||||
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionFailure({
|
||||
reason: FAILURE_DETAILS.TOS_FAILURE
|
||||
}));
|
||||
});
|
||||
|
||||
it("should notify metrics", function() {
|
||||
sdk.trigger("exception", {
|
||||
code: OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE,
|
||||
message: "Fake"
|
||||
});
|
||||
|
||||
sinon.assert.calledTwice(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.ConnectionStatus({
|
||||
event: "sdk.exception." + OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE,
|
||||
state: "starting",
|
||||
connections: 0,
|
||||
sendStreams: 0,
|
||||
recvStreams: 0
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ describe("loop.standaloneRoomViews", function() {
|
|||
var fixtures = document.querySelector("#fixtures");
|
||||
|
||||
var sandbox, dispatcher, activeRoomStore, dispatch;
|
||||
var clock, fakeWindow;
|
||||
var clock, fakeWindow, view;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
@ -63,6 +63,7 @@ describe("loop.standaloneRoomViews", function() {
|
|||
sandbox.restore();
|
||||
clock.restore();
|
||||
React.unmountComponentAtNode(fixtures);
|
||||
view = null;
|
||||
});
|
||||
|
||||
describe("StandaloneRoomHeader", function() {
|
||||
|
@ -75,7 +76,7 @@ describe("loop.standaloneRoomViews", function() {
|
|||
}
|
||||
|
||||
it("should dispatch a RecordClick action when the support link is clicked", function() {
|
||||
var view = mountTestComponent();
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode().querySelector("a"));
|
||||
|
||||
|
@ -87,13 +88,93 @@ describe("loop.standaloneRoomViews", function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("StandaloneRoomFailureView", function() {
|
||||
function mountTestComponent(extraProps) {
|
||||
var props = _.extend({
|
||||
dispatcher: dispatcher
|
||||
}, extraProps);
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(
|
||||
loop.standaloneRoomViews.StandaloneRoomFailureView, props));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.FAILED });
|
||||
});
|
||||
|
||||
it("should display a status error message if not reason is supplied", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
expect(view.getDOMNode().querySelector(".failed-room-message").textContent)
|
||||
.eql("status_error");
|
||||
});
|
||||
|
||||
it("should display a denied message on MEDIA_DENIED", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.MEDIA_DENIED });
|
||||
|
||||
expect(view.getDOMNode().querySelector(".failed-room-message").textContent)
|
||||
.eql("rooms_media_denied_message");
|
||||
});
|
||||
|
||||
it("should display a denied message on NO_MEDIA", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.NO_MEDIA });
|
||||
|
||||
expect(view.getDOMNode().querySelector(".failed-room-message").textContent)
|
||||
.eql("rooms_media_denied_message");
|
||||
});
|
||||
|
||||
it("should display an unavailable message on EXPIRED_OR_INVALID", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.EXPIRED_OR_INVALID });
|
||||
|
||||
expect(view.getDOMNode().querySelector(".failed-room-message").textContent)
|
||||
.eql("rooms_unavailable_notification_message");
|
||||
});
|
||||
|
||||
it("should display an tos failure message on TOS_FAILURE", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.TOS_FAILURE });
|
||||
|
||||
expect(view.getDOMNode().querySelector(".failed-room-message").textContent)
|
||||
.eql("tos_failure_message");
|
||||
});
|
||||
|
||||
it("should not display a retry button when the failure reason is expired or invalid", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.EXPIRED_OR_INVALID });
|
||||
|
||||
expect(view.getDOMNode().querySelector(".btn-info")).eql(null);
|
||||
});
|
||||
|
||||
it("should not display a retry button when the failure reason is tos failure", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.TOS_FAILURE });
|
||||
|
||||
expect(view.getDOMNode().querySelector(".btn-info")).eql(null);
|
||||
});
|
||||
|
||||
it("should display a retry button for any other reason", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.NO_MEDIA });
|
||||
|
||||
expect(view.getDOMNode().querySelector(".btn-info")).not.eql(null);
|
||||
});
|
||||
|
||||
it("should dispatch a RetryAfterRoomFailure action when the retry button is pressed", function() {
|
||||
view = mountTestComponent({ failureReason: FAILURE_DETAILS.NO_MEDIA });
|
||||
|
||||
var button = view.getDOMNode().querySelector(".btn-info");
|
||||
|
||||
TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RetryAfterRoomFailure());
|
||||
});
|
||||
});
|
||||
|
||||
describe("StandaloneRoomInfoArea in fixture", function() {
|
||||
it("should dispatch a RecordClick action when the tile is clicked", function(done) {
|
||||
// Point the iframe to a page that will auto-"click"
|
||||
loop.config.tilesIframeUrl = "data:text/html,<script>parent.postMessage('tile-click', '*');</script>";
|
||||
|
||||
// Render the iframe into the fixture to cause it to load
|
||||
var view = React.render(
|
||||
view = React.render(
|
||||
React.createElement(
|
||||
loop.standaloneRoomViews.StandaloneRoomInfoArea, {
|
||||
activeRoomStore: activeRoomStore,
|
||||
|
@ -135,7 +216,7 @@ describe("loop.standaloneRoomViews", function() {
|
|||
}));
|
||||
}
|
||||
|
||||
function expectActionDispatched(view) {
|
||||
function expectActionDispatched() {
|
||||
sinon.assert.calledOnce(dispatch);
|
||||
sinon.assert.calledWithExactly(dispatch,
|
||||
sinon.match.instanceOf(sharedActions.SetupStreamElements));
|
||||
|
@ -144,7 +225,7 @@ describe("loop.standaloneRoomViews", function() {
|
|||
describe("#componentWillUpdate", function() {
|
||||
it("should set document.title to roomName and brand name when the READY state is dispatched", function() {
|
||||
activeRoomStore.setStoreState({roomName: "fakeName", roomState: ROOM_STATES.INIT});
|
||||
var view = mountTestComponent();
|
||||
view = mountTestComponent();
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
|
||||
|
||||
expect(fakeWindow.document.title).to.equal("fakeName — clientShortname2");
|
||||
|
@ -153,7 +234,7 @@ describe("loop.standaloneRoomViews", function() {
|
|||
it("should dispatch a `SetupStreamElements` action when the MEDIA_WAIT state " +
|
||||
"is entered", function() {
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
|
||||
var view = mountTestComponent();
|
||||
view = mountTestComponent();
|
||||
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
|
||||
|
||||
|
@ -163,7 +244,7 @@ describe("loop.standaloneRoomViews", function() {
|
|||
it("should dispatch a `SetupStreamElements` action on MEDIA_WAIT state is " +
|
||||
"re-entered", function() {
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.ENDED});
|
||||
var view = mountTestComponent();
|
||||
view = mountTestComponent();
|
||||
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.MEDIA_WAIT});
|
||||
|
||||
|
@ -172,8 +253,6 @@ describe("loop.standaloneRoomViews", function() {
|
|||
});
|
||||
|
||||
describe("#componentDidUpdate", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINING});
|
||||
|
@ -223,8 +302,6 @@ describe("loop.standaloneRoomViews", function() {
|
|||
});
|
||||
|
||||
describe("#componentWillReceiveProps", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
|
@ -280,8 +357,6 @@ describe("loop.standaloneRoomViews", function() {
|
|||
});
|
||||
|
||||
describe("#publishStream", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
view.setState({
|
||||
|
@ -314,8 +389,6 @@ describe("loop.standaloneRoomViews", function() {
|
|||
});
|
||||
|
||||
describe("#render", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
view = mountTestComponent();
|
||||
activeRoomStore.setStoreState({roomState: ROOM_STATES.JOINING});
|
||||
|
@ -424,35 +497,11 @@ describe("loop.standaloneRoomViews", function() {
|
|||
});
|
||||
|
||||
describe("Failed room message", function() {
|
||||
beforeEach(function() {
|
||||
it("should display the StandaloneRoomFailureView", function() {
|
||||
activeRoomStore.setStoreState({ roomState: ROOM_STATES.FAILED });
|
||||
});
|
||||
|
||||
it("should display a failed room message on FAILED", function() {
|
||||
expect(view.getDOMNode().querySelector(".failed-room-message"))
|
||||
.not.eql(null);
|
||||
});
|
||||
|
||||
it("should display a retry button", function() {
|
||||
expect(view.getDOMNode().querySelector(".btn-info")).not.eql(null);
|
||||
});
|
||||
|
||||
it("should not display a retry button when the failure reason is expired or invalid", function() {
|
||||
activeRoomStore.setStoreState({
|
||||
failureReason: FAILURE_DETAILS.EXPIRED_OR_INVALID
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector(".btn-info")).eql(null);
|
||||
});
|
||||
|
||||
it("should dispatch a RetryAfterRoomFailure action when the retry button is pressed", function() {
|
||||
var button = view.getDOMNode().querySelector(".btn-info");
|
||||
|
||||
TestUtils.Simulate.click(button);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.RetryAfterRoomFailure());
|
||||
TestUtils.findRenderedComponentWithType(view,
|
||||
loop.standaloneRoomViews.StandaloneRoomFailureView);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -686,8 +686,9 @@ Cookies.prototype = {
|
|||
host = "." + host;
|
||||
}
|
||||
|
||||
// Fallback: expire in 1h
|
||||
let expireTime = (Date.now() + 3600 * 1000) * 1000;
|
||||
// Fallback: expire in 1h (NB: time is in seconds since epoch, so we have
|
||||
// to divide the result of Date.now() (which is in milliseconds) by 1000).
|
||||
let expireTime = Math.floor(Date.now() / 1000) + 3600;
|
||||
try {
|
||||
expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
|
||||
Number(expireTimeLo));
|
||||
|
@ -751,10 +752,14 @@ function getTypedURLs(registryKeyPath) {
|
|||
try {
|
||||
let hi = parseInt(urlTimeHex.slice(0, 4).join(''), 16);
|
||||
let lo = parseInt(urlTimeHex.slice(4, 8).join(''), 16);
|
||||
// Convert to seconds since epoch:
|
||||
timeTyped = cTypes.fileTimeToSecondsSinceEpoch(hi, lo);
|
||||
// Callers expect PRTime, which is microseconds since epoch:
|
||||
timeTyped *= 1000 * 1000;
|
||||
} catch (ex) {}
|
||||
} catch (ex) {
|
||||
// Ignore conversion exceptions. Callers will have to deal
|
||||
// with the fallback value (0).
|
||||
}
|
||||
}
|
||||
}
|
||||
typedURLs.set(url, timeTyped);
|
||||
|
@ -862,9 +867,16 @@ WindowsVaultFormPasswords.prototype = {
|
|||
}
|
||||
|
||||
let password = credential.contents.pAuthenticatorElement.contents.itemValue.readString();
|
||||
let creation = ctypesKernelHelpers.
|
||||
let creation = Date.now();
|
||||
try {
|
||||
// login manager wants time in milliseconds since epoch, so convert
|
||||
// to seconds since epoch and multiply to get milliseconds:
|
||||
creation = ctypesKernelHelpers.
|
||||
fileTimeToSecondsSinceEpoch(item.contents.highLastModified,
|
||||
item.contents.lowLastModified) * 1000;
|
||||
} catch (ex) {
|
||||
// Ignore exceptions in the dates and just create the login for right now.
|
||||
}
|
||||
// create a new login
|
||||
let login = {
|
||||
username, password,
|
||||
|
|
|
@ -280,6 +280,9 @@ generic_contact_unavailable_title=This person is unavailable.
|
|||
generic_failure_message=We're having technical difficulties…
|
||||
generic_failure_with_reason2=You can try again or email a link to be reached at later.
|
||||
generic_failure_no_reason2=Would you like to try again?
|
||||
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname2}}
|
||||
## as this will be replaced by the shortname.
|
||||
tos_failure_message={{clientShortname}} is not available in your country.
|
||||
|
||||
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
|
||||
## contact is offline.
|
||||
|
|
|
@ -33,14 +33,15 @@ cmd_addFoundEngine=Add "%S"
|
|||
# grouped in a submenu using cmd_addFoundEngineMenu as a label.
|
||||
cmd_addFoundEngineMenu=Add search engine
|
||||
|
||||
# LOCALIZATION NOTE (searchForKeywordsWith):
|
||||
# LOCALIZATION NOTE (searchForSomethingWith):
|
||||
# This string is used to build the header above the list of one-click
|
||||
# search providers: "Search for <user-typed keywords> with:"
|
||||
searchForKeywordsWith=Search for %S with:
|
||||
# search providers: "Search for <user-typed string> with:"
|
||||
# NB: please leave the <span> and its class exactly as it is in English.
|
||||
searchForSomethingWith=Search for <span class='contentSearchSearchWithHeaderSearchText'></span> with:
|
||||
|
||||
# LOCALIZATION NOTE (searchWithHeader):
|
||||
# The wording of this string should be as close as possible to
|
||||
# searchForKeywordsWith. This string will be used when the user
|
||||
# searchForSomethingWith. This string will be used when the user
|
||||
# has not typed anything.
|
||||
searchWithHeader=Search with:
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ this.ContentSearch = {
|
|||
}
|
||||
this._searchSuggestionUIStrings = {};
|
||||
let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
|
||||
let stringNames = ["searchHeader", "searchPlaceholder", "searchForKeywordsWith",
|
||||
let stringNames = ["searchHeader", "searchPlaceholder", "searchForSomethingWith",
|
||||
"searchWithHeader", "searchSettings"];
|
||||
for (let name of stringNames) {
|
||||
this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name);
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
--toolbarbutton-active-bordercolor: rgb(154,154,154);
|
||||
--toolbarbutton-active-background: rgba(154,154,154,.5) linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.4));
|
||||
|
||||
--toolbarbutton-checkedhover-backgroundcolor: rgba(90%,90%,90%,.4);
|
||||
--toolbarbutton-checkedhover-backgroundcolor: rgba(154,154,154,.15);
|
||||
|
||||
--identity-box-verified-background-color: #fff;
|
||||
|
||||
|
@ -1598,7 +1598,7 @@ richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-
|
|||
}
|
||||
|
||||
#TabsToolbar .toolbarbutton-1 {
|
||||
margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
|
||||
margin-bottom: var(--tab-toolbar-navbar-overlap);
|
||||
}
|
||||
|
||||
#alltabs-button {
|
||||
|
|
|
@ -2805,7 +2805,7 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
|
|||
}
|
||||
|
||||
#TabsToolbar .toolbarbutton-1 {
|
||||
margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
|
||||
margin-bottom: var(--tab-toolbar-navbar-overlap);
|
||||
}
|
||||
|
||||
#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
--toolbarbutton-active-bordercolor: rgba(0,0,0,.15);
|
||||
--toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0,0,0,.15) inset;
|
||||
|
||||
--toolbarbutton-checkedhover-backgroundcolor: rgba(0,0,0,.15);
|
||||
--toolbarbutton-checkedhover-backgroundcolor: rgba(0,0,0,.1);
|
||||
|
||||
--identity-box-verified-background-color: #fff;
|
||||
|
||||
|
@ -59,6 +59,8 @@
|
|||
--toolbarbutton-active-background: rgba(255,255,255,.3);
|
||||
--toolbarbutton-active-bordercolor: rgba(255,255,255,.3);
|
||||
--toolbarbutton-active-boxshadow: 0 0 0 1px rgba(255,255,255,.3) inset;
|
||||
|
||||
--toolbarbutton-checkedhover-backgroundcolor: rgba(255,255,255,.2);
|
||||
}
|
||||
|
||||
#menubar-items {
|
||||
|
@ -899,7 +901,7 @@ toolbarbutton[constrain-size="true"][cui-areatype="toolbar"] > .toolbarbutton-ba
|
|||
}
|
||||
|
||||
#TabsToolbar .toolbarbutton-1 {
|
||||
margin-bottom: var(--navbar-tab-toolbar-highlight-overlap);
|
||||
margin-bottom: var(--tab-toolbar-navbar-overlap);
|
||||
}
|
||||
|
||||
#TabsToolbar .toolbarbutton-1:not([disabled=true]):hover,
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
public class AlertNotification
|
||||
extends Notification
|
||||
{
|
||||
private static final String LOGTAG = "GeckoAlertNotification";
|
||||
|
||||
private final int mId;
|
||||
private final int mIcon;
|
||||
private final String mTitle;
|
||||
private final String mText;
|
||||
private final NotificationManager mNotificationManager;
|
||||
|
||||
private boolean mProgressStyle;
|
||||
private double mPrevPercent = -1;
|
||||
private String mPrevAlertText = "";
|
||||
|
||||
private static final double UPDATE_THRESHOLD = .01;
|
||||
private final Context mContext;
|
||||
|
||||
public AlertNotification(Context aContext, int aNotificationId, int aIcon,
|
||||
String aTitle, String aText, long aWhen, Uri aIconUri) {
|
||||
super(aIcon, (aText.length() > 0) ? aText : aTitle, aWhen);
|
||||
|
||||
mIcon = aIcon;
|
||||
mTitle = aTitle;
|
||||
mText = aText;
|
||||
mId = aNotificationId;
|
||||
mContext = aContext;
|
||||
|
||||
mNotificationManager = (NotificationManager) aContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
if (aIconUri == null || aIconUri.getScheme() == null)
|
||||
return;
|
||||
|
||||
// Custom view
|
||||
int layout = R.layout.notification_icon_text;
|
||||
RemoteViews view = new RemoteViews(mContext.getPackageName(), layout);
|
||||
try {
|
||||
Bitmap bm = BitmapUtils.decodeUrl(aIconUri);
|
||||
if (bm == null) {
|
||||
Log.e(LOGTAG, "failed to decode icon");
|
||||
return;
|
||||
}
|
||||
view.setImageViewBitmap(R.id.notification_image, bm);
|
||||
view.setTextViewText(R.id.notification_title, mTitle);
|
||||
if (mText.length() > 0) {
|
||||
view.setTextViewText(R.id.notification_text, mText);
|
||||
}
|
||||
contentView = view;
|
||||
} catch (Exception e) {
|
||||
Log.e(LOGTAG, "failed to create bitmap", e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
public synchronized boolean isProgressStyle() {
|
||||
return mProgressStyle;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
mNotificationManager.notify(mId, this);
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mNotificationManager.cancel(mId);
|
||||
}
|
||||
|
||||
public synchronized void updateProgress(String aAlertText, long aProgress, long aProgressMax) {
|
||||
if (!mProgressStyle) {
|
||||
// Custom view
|
||||
int layout = aAlertText.length() > 0 ? R.layout.notification_progress_text : R.layout.notification_progress;
|
||||
|
||||
RemoteViews view = new RemoteViews(mContext.getPackageName(), layout);
|
||||
view.setImageViewResource(R.id.notification_image, mIcon);
|
||||
view.setTextViewText(R.id.notification_title, mTitle);
|
||||
contentView = view;
|
||||
flags |= FLAG_ONGOING_EVENT | FLAG_ONLY_ALERT_ONCE;
|
||||
|
||||
mProgressStyle = true;
|
||||
}
|
||||
|
||||
String text;
|
||||
double percent = 0;
|
||||
if (aProgressMax > 0)
|
||||
percent = ((double)aProgress / (double)aProgressMax);
|
||||
|
||||
if (aAlertText.length() > 0)
|
||||
text = aAlertText;
|
||||
else
|
||||
text = NumberFormat.getPercentInstance().format(percent);
|
||||
|
||||
if (mPrevAlertText.equals(text) && Math.abs(mPrevPercent - percent) < UPDATE_THRESHOLD)
|
||||
return;
|
||||
|
||||
contentView.setTextViewText(R.id.notification_text, text);
|
||||
contentView.setProgressBar(R.id.notification_progressbar, (int)aProgressMax, (int)aProgress, false);
|
||||
|
||||
// Update the notification
|
||||
mNotificationManager.notify(mId, this);
|
||||
|
||||
mPrevPercent = percent;
|
||||
mPrevAlertText = text;
|
||||
}
|
||||
}
|
|
@ -12,6 +12,8 @@ import android.app.NotificationManager;
|
|||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
@ -20,7 +22,7 @@ public class NotificationHandler {
|
|||
private final ConcurrentHashMap<Integer, Notification>
|
||||
mNotifications = new ConcurrentHashMap<Integer, Notification>();
|
||||
private final Context mContext;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final NotificationManagerCompat mNotificationManager;
|
||||
|
||||
/**
|
||||
* Notification associated with this service's foreground state.
|
||||
|
@ -37,7 +39,7 @@ public class NotificationHandler {
|
|||
|
||||
public NotificationHandler(Context context) {
|
||||
mContext = context;
|
||||
mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationManager = NotificationManagerCompat.from(context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,7 +50,6 @@ public class NotificationHandler {
|
|||
* @param aAlertTitle title of the notification
|
||||
* @param aAlertText text of the notification
|
||||
* @param contentIntent Intent used when the notification is clicked
|
||||
* @param clearIntent Intent used when the notification is removed
|
||||
*/
|
||||
public void add(int notificationID, String aImageUrl, String aAlertTitle,
|
||||
String aAlertText, PendingIntent contentIntent) {
|
||||
|
@ -57,10 +58,14 @@ public class NotificationHandler {
|
|||
|
||||
Uri imageUri = Uri.parse(aImageUrl);
|
||||
int icon = BitmapUtils.getResource(imageUri, R.drawable.ic_status_logo);
|
||||
final AlertNotification notification = new AlertNotification(mContext, notificationID,
|
||||
icon, aAlertTitle, aAlertText, System.currentTimeMillis(), imageUri);
|
||||
|
||||
notification.setLatestEventInfo(mContext, aAlertTitle, aAlertText, contentIntent);
|
||||
Notification notification = new NotificationCompat.Builder(mContext)
|
||||
.setContentTitle(aAlertTitle)
|
||||
.setContentText(aAlertText)
|
||||
.setSmallIcon(icon)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setContentIntent(contentIntent)
|
||||
.build();
|
||||
|
||||
mNotificationManager.notify(notificationID, notification);
|
||||
mNotifications.put(notificationID, notification);
|
||||
|
@ -70,7 +75,7 @@ public class NotificationHandler {
|
|||
* Adds a notification.
|
||||
*
|
||||
* @param id the unique ID of the notification
|
||||
* @param aNotification the Notification to add
|
||||
* @param notification the Notification to add
|
||||
*/
|
||||
public void add(int id, Notification notification) {
|
||||
mNotificationManager.notify(id, notification);
|
||||
|
@ -90,19 +95,20 @@ public class NotificationHandler {
|
|||
* @param aAlertText text of the notification
|
||||
*/
|
||||
public void update(int notificationID, long aProgress, long aProgressMax, String aAlertText) {
|
||||
final Notification notification = mNotifications.get(notificationID);
|
||||
Notification notification = mNotifications.get(notificationID);
|
||||
if (notification == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification instanceof AlertNotification) {
|
||||
AlertNotification alert = (AlertNotification)notification;
|
||||
alert.updateProgress(aAlertText, aProgress, aProgressMax);
|
||||
}
|
||||
notification = new NotificationCompat.Builder(mContext)
|
||||
.setContentText(aAlertText)
|
||||
.setSmallIcon(notification.icon)
|
||||
.setWhen(notification.when)
|
||||
.setContentIntent(notification.contentIntent)
|
||||
.setProgress((int) aProgressMax, (int) aProgress, false)
|
||||
.build();
|
||||
|
||||
if (mForegroundNotification == null && isOngoing(notification)) {
|
||||
setForegroundNotification(notificationID, notification);
|
||||
}
|
||||
add(notificationID, notification);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,26 +154,12 @@ public class NotificationHandler {
|
|||
* @return whether the notification is ongoing
|
||||
*/
|
||||
public boolean isOngoing(Notification notification) {
|
||||
if (notification != null && (isProgressStyle(notification) || ((notification.flags & Notification.FLAG_ONGOING_EVENT) > 0))) {
|
||||
if (notification != null && (notification.flags & Notification.FLAG_ONGOING_EVENT) > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determines whether a notification is an AlertNotification that is showing progress
|
||||
* This method will be deprecated when AlertNotifications are removed (bug 893289).
|
||||
*
|
||||
* @param notification the notification to check
|
||||
* @return whether the notification is an AlertNotification showing progress.
|
||||
*/
|
||||
private boolean isProgressStyle(Notification notification) {
|
||||
if (notification instanceof AlertNotification) {
|
||||
return ((AlertNotification)notification).isProgressStyle();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void setForegroundNotification(int id, Notification notification) {
|
||||
mForegroundNotificationId = id;
|
||||
mForegroundNotification = notification;
|
||||
|
|
|
@ -46,7 +46,6 @@ show allResults
|
|||
|
||||
[middle] = \
|
||||
org.mozilla.gecko.prompts.* \
|
||||
org.mozilla.gecko.AlertNotification \
|
||||
org.mozilla.gecko.FormAssistPopup \
|
||||
org.mozilla.gecko.GeckoActivity \
|
||||
org.mozilla.gecko.GeckoApp \
|
||||
|
|
|
@ -156,7 +156,6 @@ gbjar.sources += [
|
|||
'ActionModeCompat.java',
|
||||
'ActionModeCompatView.java',
|
||||
'ActivityHandlerHelper.java',
|
||||
'AlertNotification.java',
|
||||
'AndroidGamepadManager.java',
|
||||
'animation/AnimationUtils.java',
|
||||
'animation/AnimatorProxy.java',
|
||||
|
|
|
@ -133,6 +133,7 @@ EventManager.prototype = {
|
|||
let fireWithoutClone = this.fireWithoutClone.bind(this);
|
||||
fireFunc.withoutClone = fireWithoutClone;
|
||||
this.unregister = this.register(fireFunc);
|
||||
this.registered = true;
|
||||
}
|
||||
this.callbacks.add(callback);
|
||||
},
|
||||
|
@ -601,4 +602,3 @@ this.ExtensionUtils = {
|
|||
Messenger,
|
||||
flushJarCache,
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче