зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
e6e089c791
|
@ -48,6 +48,9 @@
|
|||
return decodeURIComponent(url.slice(error + 2, duffUrl));
|
||||
}
|
||||
|
||||
// Set to true on init if the error code is nssBadCert.
|
||||
var gIsCertError;
|
||||
|
||||
function getCSSClass()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
|
@ -103,7 +106,7 @@
|
|||
'none': 'block',
|
||||
'block': 'none'
|
||||
};
|
||||
node.style.display = toggle[node.style.display];
|
||||
return (node.style.display = toggle[node.style.display]);
|
||||
}
|
||||
|
||||
function showCertificateErrorReporting() {
|
||||
|
@ -118,17 +121,27 @@
|
|||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
|
||||
// Get the hostname and add it to the panel
|
||||
var panel = document.getElementById("weakCryptoAdvancedPanel");
|
||||
var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
|
||||
var panel = document.getElementById(panelId);
|
||||
for (var span of panel.querySelectorAll("span.hostname")) {
|
||||
span.textContent = document.location.hostname;
|
||||
}
|
||||
panel.replaceChild(document.getElementById("errorLongDesc"),
|
||||
document.getElementById("advancedLongDesc"));
|
||||
if (!gIsCertError) {
|
||||
panel.replaceChild(document.getElementById("errorLongDesc"),
|
||||
document.getElementById("advancedLongDesc"));
|
||||
}
|
||||
|
||||
// Register click handler for the weakCryptoAdvancedPanel
|
||||
document.getElementById("advancedButton")
|
||||
.addEventListener("click", function togglePanelVisibility() {
|
||||
toggleDisplay(panel);
|
||||
if (gIsCertError) {
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
div.style.display = "none";
|
||||
}
|
||||
|
||||
if (panel.style.display == "block") {
|
||||
// send event to trigger telemetry ping
|
||||
|
@ -144,9 +157,68 @@
|
|||
}
|
||||
}
|
||||
|
||||
function initPageCertError() {
|
||||
document.body.className = "certerror";
|
||||
document.title = document.getElementById("certErrorPageTitle").textContent;
|
||||
for (let host of document.querySelectorAll(".hostname")) {
|
||||
host.textContent = document.location.hostname;
|
||||
}
|
||||
|
||||
showAdvancedButton(true);
|
||||
|
||||
var cssClass = getCSSClass();
|
||||
if (cssClass == "expertBadCert") {
|
||||
toggleDisplay(document.getElementById("badCertAdvancedPanel"));
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
div.style.display = "none";
|
||||
}
|
||||
|
||||
document.getElementById("learnMoreContainer").style.display = "block";
|
||||
|
||||
var checkbox = document.getElementById("automaticallyReportInFuture");
|
||||
checkbox.addEventListener("change", function ({target: {checked}}) {
|
||||
document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
|
||||
detail: checked,
|
||||
bubbles: true
|
||||
}));
|
||||
});
|
||||
|
||||
addEventListener("AboutNetErrorOptions", function (event) {
|
||||
var options = JSON.parse(event.detail);
|
||||
if (options && options.enabled) {
|
||||
// Display error reporting UI
|
||||
document.getElementById("certificateErrorReporting").style.display = "block";
|
||||
|
||||
// set the checkbox
|
||||
checkbox.checked = !!options.automatic;
|
||||
}
|
||||
}, true, true);
|
||||
|
||||
// Disallow overrides if this is a Strict-Transport-Security
|
||||
// host and the cert is bad (STS Spec section 7.3) or if the
|
||||
// certerror is in a frame (bug 633691).
|
||||
if (cssClass == "badStsCert" || window != top) {
|
||||
document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
|
||||
}
|
||||
if (cssClass == "badStsCert") {
|
||||
document.getElementById("badStsCertExplanation").removeAttribute("hidden");
|
||||
}
|
||||
|
||||
document.getElementById("badCertTechnicalInfo").textContent = getDescription();
|
||||
|
||||
var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
|
||||
document.getElementById("advancedButton").dispatchEvent(event);
|
||||
|
||||
addDomainErrorLinks();
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
var err = getErrorCode();
|
||||
gIsCertError = (err == "nssBadCert");
|
||||
|
||||
// if it's an unknown error or there's no title or description
|
||||
// defined, get the generic message
|
||||
|
@ -167,8 +239,18 @@
|
|||
}
|
||||
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
if (sd)
|
||||
sd.textContent = getDescription();
|
||||
if (sd) {
|
||||
if (gIsCertError) {
|
||||
sd.textContent = document.getElementById("ed_nssBadCert").textContent;
|
||||
}
|
||||
else {
|
||||
sd.textContent = getDescription();
|
||||
}
|
||||
}
|
||||
if (gIsCertError) {
|
||||
initPageCertError();
|
||||
return;
|
||||
}
|
||||
|
||||
var ld = document.getElementById("errorLongDesc");
|
||||
if (ld)
|
||||
|
@ -211,9 +293,6 @@
|
|||
favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
|
||||
faviconParent.appendChild(favicon);
|
||||
}
|
||||
if (className == "expertBadCert") {
|
||||
showSecuritySection();
|
||||
}
|
||||
|
||||
if (err == "remoteXUL") {
|
||||
// Remove the "Try again" button for remote XUL errors given that
|
||||
|
@ -265,27 +344,9 @@
|
|||
var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
|
||||
document.dispatchEvent(event);
|
||||
|
||||
if (err == "nssBadCert") {
|
||||
// Remove the "Try again" button for security exceptions, since it's
|
||||
// almost certainly useless.
|
||||
document.getElementById("errorTryAgain").style.display = "none";
|
||||
document.getElementById("errorPageContainer").setAttribute("class", "certerror");
|
||||
}
|
||||
else {
|
||||
// Remove the override block for non-certificate errors. CSS-hiding
|
||||
// isn't good enough here, because of bug 39098
|
||||
var secOverride = document.getElementById("securityOverrideDiv");
|
||||
secOverride.parentNode.removeChild(secOverride);
|
||||
}
|
||||
addDomainErrorLinks();
|
||||
}
|
||||
|
||||
function showSecuritySection() {
|
||||
// Swap link out, content in
|
||||
document.getElementById('securityOverrideContent').style.display = '';
|
||||
document.getElementById('securityOverrideLink').style.display = 'none';
|
||||
}
|
||||
|
||||
/* Try to preserve the links contained in the error description, like
|
||||
the error code.
|
||||
|
||||
|
@ -297,7 +358,8 @@
|
|||
*/
|
||||
function addDomainErrorLinks() {
|
||||
// Rather than textContent, we need to treat description as HTML
|
||||
var sd = document.getElementById("errorShortDescText");
|
||||
var sdid = gIsCertError ? "badCertTechnicalInfo" : "errorShortDescText";
|
||||
var sd = document.getElementById(sdid);
|
||||
if (sd) {
|
||||
var desc = getDescription();
|
||||
|
||||
|
@ -335,6 +397,21 @@
|
|||
sd.appendChild(document.createTextNode(desc.slice(desc.lastIndexOf("</a>") + "</a>".length)));
|
||||
}
|
||||
|
||||
if (gIsCertError) {
|
||||
// Initialize the error code link embedded in the error message to
|
||||
// display debug information about the cert error.
|
||||
var errorCode = document.getElementById("errorCode");
|
||||
if (errorCode) {
|
||||
errorCode.href = "#technicalInformation";
|
||||
errorCode.addEventListener("click", () => {
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
if (toggleDisplay(div) == "block") {
|
||||
div.scrollIntoView({block: "start", behavior: "smooth"});
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the cert domain link.
|
||||
var link = document.getElementById('cert_domain_link');
|
||||
if (!link)
|
||||
|
@ -375,8 +452,17 @@
|
|||
|
||||
// If we set a link, meaning there's something helpful for
|
||||
// the user here, expand the section by default
|
||||
if (link.href && getCSSClass() != "expertBadCert")
|
||||
toggleVisibility("advancedPanel");
|
||||
if (link.href && getCSSClass() != "expertBadCert") {
|
||||
var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel"
|
||||
toggleDisplay(document.getElementById(panelId));
|
||||
if (gIsCertError) {
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
div.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createLink(el, id, text) {
|
||||
|
@ -390,6 +476,8 @@
|
|||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<!-- Contains an alternate page title set on page init for cert errors. -->
|
||||
<div id="certErrorPageTitle" style="display: none;">&certerror.pagetitle1;</div>
|
||||
|
||||
<!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
|
||||
<div id="errorContainer">
|
||||
|
@ -413,7 +501,7 @@
|
|||
<h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
|
||||
<h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
|
||||
<h1 id="et_nssFailure2">&nssFailure2.title;</h1>
|
||||
<h1 id="et_nssBadCert">&nssBadCert.title;</h1>
|
||||
<h1 id="et_nssBadCert">&certerror.longpagetitle1;</h1>
|
||||
<h1 id="et_cspBlocked">&cspBlocked.title;</h1>
|
||||
<h1 id="et_remoteXUL">&remoteXUL.title;</h1>
|
||||
<h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
|
||||
|
@ -440,7 +528,7 @@
|
|||
<div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
|
||||
<div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
|
||||
<div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
|
||||
<div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
|
||||
<div id="ed_nssBadCert">&certerror.introPara;</div>
|
||||
<div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
|
||||
<div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
|
||||
<div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
|
||||
|
@ -464,17 +552,11 @@
|
|||
<div id="errorShortDesc">
|
||||
<p id="errorShortDescText" />
|
||||
</div>
|
||||
<p id="badStsCertExplanation" hidden="true">&certerror.whatShouldIDo.badStsCertExplanation;</p>
|
||||
|
||||
<!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
|
||||
<div id="errorLongDesc" />
|
||||
|
||||
<!-- Override section - For ssl errors only. Removed on init for other
|
||||
error types. -->
|
||||
<div id="securityOverrideDiv">
|
||||
<a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a>
|
||||
<div id="securityOverrideContent" style="display: none;">&securityOverride.warningContent;</div>
|
||||
</div>
|
||||
|
||||
<div id="learnMoreContainer">
|
||||
<p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
|
||||
</div>
|
||||
|
@ -522,6 +604,18 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="badCertAdvancedPanel">
|
||||
<p id="badCertTechnicalInfo"/>
|
||||
<button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="certificateErrorDebugInformation">
|
||||
<a name="technicalInformation"></a>
|
||||
<button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
|
||||
<div id="certificateErrorText"/>
|
||||
<button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
|
|
|
@ -1,17 +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/. */
|
||||
|
||||
/* Logical CSS rules belong here, but presentation & theming rules
|
||||
should live in the CSS of the appropriate theme */
|
||||
|
||||
#technicalContentText {
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.expander[hidden],
|
||||
.expander[hidden] + *,
|
||||
.expander[collapsed] + * {
|
||||
display: none;
|
||||
}
|
|
@ -1,308 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE html [
|
||||
<!ENTITY % htmlDTD
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"DTD/xhtml1-strict.dtd">
|
||||
%htmlDTD;
|
||||
<!ENTITY % globalDTD
|
||||
SYSTEM "chrome://global/locale/global.dtd">
|
||||
%globalDTD;
|
||||
<!ENTITY % certerrorDTD
|
||||
SYSTEM "chrome://browser/locale/aboutCertError.dtd">
|
||||
%certerrorDTD;
|
||||
]>
|
||||
|
||||
<!-- 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 xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&certerror.pagetitle1;</title>
|
||||
<link rel="stylesheet" href="chrome://browser/skin/aboutCertError.css" type="text/css" media="all" />
|
||||
<link rel="stylesheet" href="chrome://browser/content/certerror/aboutCertError.css" type="text/css" media="all" />
|
||||
<!-- This page currently uses the same favicon as neterror.xhtml.
|
||||
If the location of the favicon is changed for both pages, the
|
||||
FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h
|
||||
should be updated. If this page starts using a different favicon
|
||||
than neterror.xhtml nsFaviconService->SetAndLoadFaviconForPage
|
||||
should be updated to ignore this one as well. -->
|
||||
<link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
// Error url MUST be formatted like this:
|
||||
// about:certerror?e=error&u=url&d=desc
|
||||
|
||||
// Note that this file uses document.documentURI to get
|
||||
// the URL (with the format from above). This is because
|
||||
// document.location.href gets the current URI off the docshell,
|
||||
// which is the URL displayed in the location bar, i.e.
|
||||
// the URI that the user attempted to load.
|
||||
|
||||
function getCSSClass()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var matches = url.match(/s\=([^&]+)\&/);
|
||||
// s is optional, if no match just return nothing
|
||||
if (!matches || matches.length < 2)
|
||||
return "";
|
||||
|
||||
// parenthetical match is the second entry
|
||||
return decodeURIComponent(matches[1]);
|
||||
}
|
||||
|
||||
function toggleVisibility(id)
|
||||
{
|
||||
var node = document.getElementById(id);
|
||||
node.style.visibility = node.style.visibility == "" ? "hidden" : "";
|
||||
// Toggling the advanced panel must ensure that the debugging
|
||||
// information panel is hidden as well, since it's opened by the
|
||||
// error code link in the advanced panel.
|
||||
if (id == "advancedPanel") {
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
div.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function getDescription()
|
||||
{
|
||||
var url = document.documentURI;
|
||||
var desc = url.search(/d\=/);
|
||||
|
||||
// desc == -1 if not found; if so, return an empty string
|
||||
// instead of what would turn out to be portions of the URI
|
||||
if (desc == -1)
|
||||
return "";
|
||||
|
||||
return decodeURIComponent(url.slice(desc + 2));
|
||||
}
|
||||
|
||||
function initPage()
|
||||
{
|
||||
for (let host of document.querySelectorAll(".hostname")) {
|
||||
host.textContent = document.location.hostname;
|
||||
}
|
||||
|
||||
var cssClass = getCSSClass();
|
||||
if (cssClass == "expertBadCert") {
|
||||
toggleVisibility('advancedPanel');
|
||||
}
|
||||
|
||||
var checkbox = document.getElementById("automaticallyReportInFuture");
|
||||
checkbox.addEventListener("change", function ({target: {checked}}) {
|
||||
document.dispatchEvent(new CustomEvent("AboutCertErrorSetAutomatic", {
|
||||
detail: checked,
|
||||
bubbles: true
|
||||
}));
|
||||
});
|
||||
|
||||
addEventListener("AboutCertErrorOptions", function (event) {
|
||||
var options = JSON.parse(event.detail);
|
||||
if (options && options.enabled) {
|
||||
// Display error reporting UI
|
||||
document.getElementById("certificateErrorReporting").style.display = "block";
|
||||
|
||||
// set the checkbox
|
||||
checkbox.checked = !!options.automatic;
|
||||
}
|
||||
}, true, true);
|
||||
|
||||
// Disallow overrides if this is a Strict-Transport-Security
|
||||
// host and the cert is bad (STS Spec section 7.3) or if the
|
||||
// certerror is in a frame (bug 633691).
|
||||
if (cssClass == "badStsCert" || window != top) {
|
||||
document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
|
||||
}
|
||||
if (cssClass != "badStsCert") {
|
||||
document.getElementById("badStsCertExplanation").setAttribute("hidden", "true");
|
||||
}
|
||||
|
||||
var tech = document.getElementById("technicalContentText");
|
||||
if (tech)
|
||||
tech.textContent = getDescription();
|
||||
|
||||
var event = new CustomEvent("AboutCertErrorLoad", {bubbles:true});
|
||||
document.getElementById("advancedButton").dispatchEvent(event);
|
||||
|
||||
addDomainErrorLinks();
|
||||
}
|
||||
|
||||
/* Try to preserve the links contained in the error description, like
|
||||
the error code.
|
||||
|
||||
Also, in the case of SSL error pages about domain mismatch, see if
|
||||
we can hyperlink the user to the correct site. We don't want
|
||||
to do this generically since it allows MitM attacks to redirect
|
||||
users to a site under attacker control, but in certain cases
|
||||
it is safe (and helpful!) to do so. Bug 402210
|
||||
*/
|
||||
function addDomainErrorLinks() {
|
||||
// Rather than textContent, we need to treat description as HTML
|
||||
var sd = document.getElementById("technicalContentText");
|
||||
if (sd) {
|
||||
var desc = getDescription();
|
||||
|
||||
// sanitize description text - see bug 441169
|
||||
|
||||
// First, find the index of the <a> tags we care about, being
|
||||
// careful not to use an over-greedy regex.
|
||||
var codeRe = /<a id="errorCode" title="([^"]+)">/;
|
||||
var codeResult = codeRe.exec(desc);
|
||||
var domainRe = /<a id="cert_domain_link" title="([^"]+)">/;
|
||||
var domainResult = domainRe.exec(desc);
|
||||
|
||||
// The order of these links in the description is fixed in
|
||||
// TransportSecurityInfo.cpp:formatOverridableCertErrorMessage.
|
||||
var firstResult = domainResult;
|
||||
if(!domainResult)
|
||||
firstResult = codeResult;
|
||||
if (!firstResult)
|
||||
return;
|
||||
|
||||
// Remove sd's existing children
|
||||
sd.textContent = "";
|
||||
|
||||
// Everything up to the first link should be text content.
|
||||
sd.appendChild(document.createTextNode(desc.slice(0, firstResult.index)));
|
||||
|
||||
// Now create the actual links.
|
||||
if (domainResult) {
|
||||
createLink(sd, "cert_domain_link", domainResult[1])
|
||||
// Append text for anything between the two links.
|
||||
sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length, codeResult.index)));
|
||||
}
|
||||
createLink(sd, "errorCode", codeResult[1])
|
||||
|
||||
// Finally, append text for anything after the last closing </a>.
|
||||
sd.appendChild(document.createTextNode(desc.slice(desc.lastIndexOf("</a>") + "</a>".length)));
|
||||
}
|
||||
|
||||
// Initialize the error code link embedded in the error message to
|
||||
// display debug information about the cert error.
|
||||
var errorCode = document.getElementById("errorCode");
|
||||
if (errorCode) {
|
||||
errorCode.href = "#technicalInformation";
|
||||
errorCode.addEventListener("click", () => {
|
||||
var div = document.getElementById("certificateErrorDebugInformation");
|
||||
if (div.style.display == "block") {
|
||||
div.style.display = "none";
|
||||
} else {
|
||||
div.style.display = "block";
|
||||
div.scrollIntoView(true);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
// Then initialize the cert domain link.
|
||||
var link = document.getElementById('cert_domain_link');
|
||||
if (!link)
|
||||
return;
|
||||
|
||||
var okHost = link.getAttribute("title");
|
||||
var thisHost = document.location.hostname;
|
||||
var proto = document.location.protocol;
|
||||
|
||||
// If okHost is a wildcard domain ("*.example.com") let's
|
||||
// use "www" instead. "*.example.com" isn't going to
|
||||
// get anyone anywhere useful. bug 432491
|
||||
okHost = okHost.replace(/^\*\./, "www.");
|
||||
|
||||
/* case #1:
|
||||
* example.com uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for www.example.com
|
||||
*
|
||||
* Make sure to include the "." ahead of thisHost so that
|
||||
* a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
|
||||
*
|
||||
* We'd normally just use a RegExp here except that we lack a
|
||||
* library function to escape them properly (bug 248062), and
|
||||
* domain names are famous for having '.' characters in them,
|
||||
* which would allow spurious and possibly hostile matches.
|
||||
*/
|
||||
if (okHost.endsWith("." + thisHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
/* case #2:
|
||||
* browser.garage.maemo.org uses an invalid security certificate.
|
||||
*
|
||||
* The certificate is only valid for garage.maemo.org
|
||||
*/
|
||||
if (thisHost.endsWith("." + okHost))
|
||||
link.href = proto + okHost;
|
||||
|
||||
// If we set a link, meaning there's something helpful for
|
||||
// the user here, expand the section by default
|
||||
if (link.href && getCSSClass() != "expertBadCert")
|
||||
toggleVisibility("advancedPanel");
|
||||
}
|
||||
|
||||
function createLink(el, id, text) {
|
||||
var anchorEl = document.createElement("a");
|
||||
anchorEl.setAttribute("id", id);
|
||||
anchorEl.setAttribute("title", text);
|
||||
anchorEl.appendChild(document.createTextNode(text));
|
||||
el.appendChild(anchorEl);
|
||||
}
|
||||
]]></script>
|
||||
</head>
|
||||
|
||||
<body dir="&locale.dir;">
|
||||
<!-- PAGE CONTAINER (for styling purposes only) -->
|
||||
<div id="errorPageContainer">
|
||||
|
||||
<!-- Error Title -->
|
||||
<div id="errorTitle">
|
||||
<h1 id="errorTitleText">&certerror.longpagetitle1;</h1>
|
||||
</div>
|
||||
|
||||
<!-- LONG CONTENT (the section most likely to require scrolling) -->
|
||||
<div id="errorLongContent">
|
||||
|
||||
<!-- Short Description -->
|
||||
<div id="errorShortDesc">
|
||||
<p>&certerror.introPara;</p>
|
||||
</div>
|
||||
<p id="badStsCertExplanation">&certerror.whatShouldIDo.badStsCertExplanation;</p>
|
||||
<div>
|
||||
<p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&certerror.learnMore;</a></p>
|
||||
</div>
|
||||
|
||||
<div id="buttonContainer">
|
||||
<button id="returnButton" autocomplete="off" autofocus="true">&certerror.returnToPreviousPage.label;</button>
|
||||
<div id="buttonSpacer"></div>
|
||||
<button id="advancedButton" autocomplete="off" onclick="toggleVisibility('advancedPanel');" autofocus="true">&certerror.advanced.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UI for option to report certificate errors to Mozilla. -->
|
||||
<div id="certificateErrorReporting">
|
||||
<p>
|
||||
<input type="checkbox" id="automaticallyReportInFuture" />
|
||||
<label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic;</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Advanced panel, which is hidden by default -->
|
||||
<div id="advancedPanel" style="visibility: hidden;">
|
||||
<p id="technicalContentText"/>
|
||||
<button id="exceptionDialogButton">&certerror.addException.label;</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="certificateErrorDebugInformation">
|
||||
<a name="technicalInformation"></a>
|
||||
<button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
|
||||
<div id="certificateErrorText"/>
|
||||
<button id="copyToClipboard">&certerror.copyToClipboard.label;</button>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
- Note: It is important to run the script this way, instead of using
|
||||
- an onload handler. This is because error pages are loaded as
|
||||
- LOAD_BACKGROUND, which means that onload handlers will not be executed.
|
||||
-->
|
||||
<script type="application/javascript">initPage();</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -2664,9 +2664,9 @@ var BrowserOnClick = {
|
|||
receiveMessage: function (msg) {
|
||||
switch (msg.name) {
|
||||
case "Browser:CertExceptionError":
|
||||
this.onAboutCertError(msg.target, msg.data.elementId,
|
||||
msg.data.isTopFrame, msg.data.location,
|
||||
msg.data.securityInfoAsString);
|
||||
this.onCertError(msg.target, msg.data.elementId,
|
||||
msg.data.isTopFrame, msg.data.location,
|
||||
msg.data.securityInfoAsString);
|
||||
break;
|
||||
case "Browser:SiteBlockedError":
|
||||
this.onAboutBlocked(msg.data.elementId, msg.data.reason,
|
||||
|
@ -2727,7 +2727,7 @@ var BrowserOnClick = {
|
|||
uri.host, uri.port);
|
||||
},
|
||||
|
||||
onAboutCertError: function (browser, elementId, isTopFrame, location, securityInfoAsString) {
|
||||
onCertError: function (browser, elementId, isTopFrame, location, securityInfoAsString) {
|
||||
let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
|
||||
|
||||
switch (elementId) {
|
||||
|
@ -2778,7 +2778,7 @@ var BrowserOnClick = {
|
|||
|
||||
let errorInfo = getDetailedCertErrorInfo(location,
|
||||
securityInfoAsString);
|
||||
browser.messageManager.sendAsyncMessage("AboutCertErrorDetails",
|
||||
browser.messageManager.sendAsyncMessage("CertErrorDetails",
|
||||
{ info: errorInfo });
|
||||
break;
|
||||
|
||||
|
|
|
@ -212,98 +212,10 @@ const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1;
|
|||
const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6;
|
||||
const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7;
|
||||
|
||||
var AboutCertErrorListener = {
|
||||
init(chromeGlobal) {
|
||||
addMessageListener("AboutCertErrorDetails", this);
|
||||
chromeGlobal.addEventListener("AboutCertErrorLoad", this, false, true);
|
||||
chromeGlobal.addEventListener("AboutCertErrorSetAutomatic", this, false, true);
|
||||
},
|
||||
|
||||
get isAboutCertError() {
|
||||
return content.document.documentURI.startsWith("about:certerror");
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
if (!this.isAboutCertError) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case "AboutCertErrorLoad":
|
||||
this.onLoad(event);
|
||||
break;
|
||||
case "AboutCertErrorSetAutomatic":
|
||||
this.onSetAutomatic(event);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage(msg) {
|
||||
if (!this.isAboutCertError) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.name) {
|
||||
case "AboutCertErrorDetails":
|
||||
this.onDetails(msg);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(event) {
|
||||
let originalTarget = event.originalTarget;
|
||||
let ownerDoc = originalTarget.ownerDocument;
|
||||
ClickEventHandler.onAboutCertError(originalTarget, ownerDoc);
|
||||
|
||||
// Set up the TLS Error Reporting UI - reports are sent automatically
|
||||
// (from nsHttpChannel::OnStopRequest) if the user has previously enabled
|
||||
// automatic sending of reports. The UI ensures that a report is sent
|
||||
// for the certificate error currently displayed if the user enables it
|
||||
// here.
|
||||
let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
|
||||
content.dispatchEvent(new content.CustomEvent("AboutCertErrorOptions", {
|
||||
detail: JSON.stringify({
|
||||
enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"),
|
||||
automatic,
|
||||
})
|
||||
}));
|
||||
},
|
||||
|
||||
onDetails(msg) {
|
||||
let div = content.document.getElementById("certificateErrorText");
|
||||
div.textContent = msg.data.info;
|
||||
},
|
||||
|
||||
onSetAutomatic(event) {
|
||||
sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
|
||||
automatic: event.detail
|
||||
});
|
||||
|
||||
// if we're enabling reports, send a report for this failure
|
||||
if (event.detail) {
|
||||
let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
|
||||
.getService(Ci.nsISerializationHelper);
|
||||
|
||||
let serializable = docShell.failedChannel.securityInfo
|
||||
.QueryInterface(Ci.nsITransportSecurityInfo)
|
||||
.QueryInterface(Ci.nsISerializable);
|
||||
|
||||
let serializedSecurityInfo = serhelper.serializeToString(serializable);
|
||||
|
||||
let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
|
||||
sendAsyncMessage("Browser:SendSSLErrorReport", {
|
||||
uri: { host, port },
|
||||
securityInfo: serializedSecurityInfo
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
AboutCertErrorListener.init(this);
|
||||
|
||||
|
||||
var AboutNetErrorListener = {
|
||||
var AboutNetAndCertErrorListener = {
|
||||
init: function(chromeGlobal) {
|
||||
addMessageListener("CertErrorDetails", this);
|
||||
chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);
|
||||
chromeGlobal.addEventListener('AboutNetErrorSetAutomatic', this, false, true);
|
||||
chromeGlobal.addEventListener('AboutNetErrorOverride', this, false, true);
|
||||
|
@ -313,8 +225,29 @@ var AboutNetErrorListener = {
|
|||
return content.document.documentURI.startsWith("about:neterror");
|
||||
},
|
||||
|
||||
get isAboutCertError() {
|
||||
return content.document.documentURI.startsWith("about:certerror");
|
||||
},
|
||||
|
||||
receiveMessage: function(msg) {
|
||||
if (!this.isAboutCertError) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.name) {
|
||||
case "CertErrorDetails":
|
||||
this.onCertErrorDetails(msg);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
onCertErrorDetails(msg) {
|
||||
let div = content.document.getElementById("certificateErrorText");
|
||||
div.textContent = msg.data.info;
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
if (!this.isAboutNetError) {
|
||||
if (!this.isAboutNetError && !this.isAboutCertError) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -332,6 +265,12 @@ var AboutNetErrorListener = {
|
|||
},
|
||||
|
||||
onPageLoad: function(evt) {
|
||||
if (this.isAboutCertError) {
|
||||
let originalTarget = evt.originalTarget;
|
||||
let ownerDoc = originalTarget.ownerDocument;
|
||||
ClickEventHandler.onCertError(originalTarget, ownerDoc);
|
||||
}
|
||||
|
||||
let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
|
||||
content.dispatchEvent(new content.CustomEvent("AboutNetErrorOptions", {
|
||||
detail: JSON.stringify({
|
||||
|
@ -375,7 +314,7 @@ var AboutNetErrorListener = {
|
|||
}
|
||||
}
|
||||
|
||||
AboutNetErrorListener.init(this);
|
||||
AboutNetAndCertErrorListener.init(this);
|
||||
|
||||
|
||||
var ClickEventHandler = {
|
||||
|
@ -398,7 +337,7 @@ var ClickEventHandler = {
|
|||
|
||||
// Handle click events from about pages
|
||||
if (ownerDoc.documentURI.startsWith("about:certerror")) {
|
||||
this.onAboutCertError(originalTarget, ownerDoc);
|
||||
this.onCertError(originalTarget, ownerDoc);
|
||||
return;
|
||||
} else if (ownerDoc.documentURI.startsWith("about:blocked")) {
|
||||
this.onAboutBlocked(originalTarget, ownerDoc);
|
||||
|
@ -458,7 +397,7 @@ var ClickEventHandler = {
|
|||
}
|
||||
},
|
||||
|
||||
onAboutCertError: function (targetElement, ownerDoc) {
|
||||
onCertError: function (targetElement, ownerDoc) {
|
||||
let docshell = ownerDoc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
|
|
|
@ -22,11 +22,11 @@ add_task(function* () {
|
|||
let advancedDiv, advancedDivVisibility, technicalDivCollapsed;
|
||||
|
||||
yield remote(() => {
|
||||
let div = content.document.getElementById("advancedPanel");
|
||||
let div = content.document.getElementById("badCertAdvancedPanel");
|
||||
// Confirm that the expert section is collapsed
|
||||
Assert.ok(div, "Advanced content div should exist");
|
||||
Assert.equal(div.ownerDocument.defaultView.getComputedStyle(div, "").visibility,
|
||||
"hidden", "Advanced content should not be visible by default");
|
||||
Assert.equal(div.ownerDocument.defaultView.getComputedStyle(div, "").display,
|
||||
"none", "Advanced content should not be visible by default");
|
||||
});
|
||||
|
||||
// Tweak the expert mode pref
|
||||
|
@ -39,10 +39,10 @@ add_task(function* () {
|
|||
yield promise;
|
||||
|
||||
yield remote(() => {
|
||||
let div = content.document.getElementById("advancedPanel");
|
||||
let div = content.document.getElementById("badCertAdvancedPanel");
|
||||
Assert.ok(div, "Advanced content div should exist");
|
||||
Assert.equal(div.ownerDocument.defaultView.getComputedStyle(div, "").visibility,
|
||||
"visible", "Advanced content should be visible by default");
|
||||
Assert.equal(div.ownerDocument.defaultView.getComputedStyle(div, "").display,
|
||||
"block", "Advanced content should be visible by default");
|
||||
});
|
||||
|
||||
// Clean up
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedTab = gBrowser.addTab("data:text/html,<iframe width='700' height='700' src='about:certerror'></iframe>");
|
||||
gBrowser.selectedTab = gBrowser.addTab("data:text/html,<iframe width='700' height='700' src='about:certerror?e=nssBadCert&u='></iframe>");
|
||||
// Open a html page with about:certerror in an iframe
|
||||
BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(testIframeCert);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ function test() {
|
|||
function testIframeCert(e) {
|
||||
// Confirm that the expert section is hidden
|
||||
var doc = gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument;
|
||||
var aP = doc.getElementById("advancedPanel");
|
||||
var aP = doc.getElementById("badCertAdvancedPanel");
|
||||
ok(aP, "Advanced content should exist");
|
||||
is_element_hidden(aP, "Advanced content should not be visible by default")
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@ let gWhitelist = [{
|
|||
file: "search.properties",
|
||||
key: "searchForSomethingWith",
|
||||
type: "single-quote"
|
||||
}, {
|
||||
file: "aboutCertError.dtd",
|
||||
key: "certerror.introPara",
|
||||
type: "single-quote"
|
||||
}, {
|
||||
file: "browser.dtd",
|
||||
key: "social.activated.description",
|
||||
type: "single-quote"
|
||||
}, {
|
||||
file: "netError.dtd",
|
||||
key: "certerror.introPara",
|
||||
type: "single-quote"
|
||||
}, {
|
||||
file: "netError.dtd",
|
||||
key: "weakCryptoAdvanced.longDesc",
|
||||
|
|
|
@ -61,8 +61,6 @@ browser.jar:
|
|||
content/browser/aboutaccounts/images/graphic_sync_intro.png (content/aboutaccounts/images/graphic_sync_intro.png)
|
||||
content/browser/aboutaccounts/images/graphic_sync_intro@2x.png (content/aboutaccounts/images/graphic_sync_intro@2x.png)
|
||||
|
||||
content/browser/certerror/aboutCertError.xhtml (content/aboutcerterror/aboutCertError.xhtml)
|
||||
content/browser/certerror/aboutCertError.css (content/aboutcerterror/aboutCertError.css)
|
||||
|
||||
content/browser/aboutRobots-icon.png (content/aboutRobots-icon.png)
|
||||
content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png)
|
||||
|
|
|
@ -46,7 +46,7 @@ static RedirEntry kRedirMap[] = {
|
|||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
nsIAboutModule::HIDE_FROM_ABOUTABOUT },
|
||||
#endif
|
||||
{ "certerror", "chrome://browser/content/certerror/aboutCertError.xhtml",
|
||||
{ "certerror", "chrome://browser/content/aboutNetError.xhtml",
|
||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||
nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
|
||||
nsIAboutModule::ALLOW_SCRIPT |
|
||||
|
|
|
@ -5,6 +5,3 @@
|
|||
# This file is included by all browser mozconfigs
|
||||
|
||||
. "$topsrcdir/build/mozconfig.common"
|
||||
|
||||
# Enable Telemetry
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
|
|
@ -11,6 +11,8 @@ ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ ac_add_options --with-mozilla-api-keyfile=/c/builds/mozilla-desktop-geoloc-api.k
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
. $topsrcdir/build/win32/mozconfig.vs2015-win64
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
|
|
|
@ -23,6 +23,8 @@ ac_add_options --with-mozilla-api-keyfile=/c/builds/mozilla-desktop-geoloc-api.k
|
|||
# Needed to enable breakpad in application.ini
|
||||
export MOZILLA_OFFICIAL=1
|
||||
|
||||
export MOZ_TELEMETRY_REPORTING=1
|
||||
|
||||
# Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
|
||||
ac_add_options --enable-warnings-as-errors
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ const kBrowserSharingNotificationId = "loop-sharing-notification";
|
|||
const CURSOR_MIN_DELTA = 3;
|
||||
const CURSOR_MIN_INTERVAL = 100;
|
||||
const CURSOR_CLICK_DELAY = 1000;
|
||||
// Due to bug 1051238 frame scripts are cached forever, so we can't update them
|
||||
// as a restartless add-on. The Math.random() is the work around for this.
|
||||
const FRAME_SCRIPT = "chrome://loop/content/modules/tabFrame.js?" + Math.random();
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
@ -57,6 +60,7 @@ var WindowListener = {
|
|||
let FileReader = window.FileReader;
|
||||
let menuItem = null;
|
||||
let isSlideshowOpen = false;
|
||||
let titleChangedListener = null;
|
||||
|
||||
// the "exported" symbols
|
||||
var LoopUI = {
|
||||
|
@ -115,6 +119,10 @@ var WindowListener = {
|
|||
return this._constants;
|
||||
},
|
||||
|
||||
get mm() {
|
||||
return window.getGroupMessageManager("browsers");
|
||||
},
|
||||
|
||||
/**
|
||||
* @return {Promise}
|
||||
*/
|
||||
|
@ -313,6 +321,10 @@ var WindowListener = {
|
|||
return;
|
||||
}
|
||||
|
||||
// Load the frame script into any tab, plus any that get created in the
|
||||
// future.
|
||||
this.mm.loadFrameScript(FRAME_SCRIPT, true);
|
||||
|
||||
// Cleanup when the window unloads.
|
||||
window.addEventListener("unload", () => {
|
||||
Services.obs.removeObserver(this, "loop-status-changed");
|
||||
|
@ -522,9 +534,13 @@ var WindowListener = {
|
|||
gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
this._listeningToTabSelect = true;
|
||||
|
||||
titleChangedListener = this.handleDOMTitleChanged.bind(this);
|
||||
|
||||
// Watch for title changes as opposed to location changes as more
|
||||
// metadata about the page is available when this event fires.
|
||||
gBrowser.addEventListener("DOMTitleChanged", this);
|
||||
this.mm.addMessageListener("loop@mozilla.org:DOMTitleChanged",
|
||||
titleChangedListener);
|
||||
|
||||
this._browserSharePaused = false;
|
||||
|
||||
// Add this event to the parent gBrowser to avoid adding and removing
|
||||
|
@ -561,7 +577,12 @@ var WindowListener = {
|
|||
|
||||
this._hideBrowserSharingInfoBar();
|
||||
gBrowser.tabContainer.removeEventListener("TabSelect", this);
|
||||
gBrowser.removeEventListener("DOMTitleChanged", this);
|
||||
|
||||
if (titleChangedListener) {
|
||||
this.mm.removeMessageListener("loop@mozilla.org:DOMTitleChanged",
|
||||
titleChangedListener);
|
||||
titleChangedListener = null;
|
||||
}
|
||||
|
||||
// Remove shared pointers related events
|
||||
gBrowser.removeEventListener("mousemove", this);
|
||||
|
@ -799,15 +820,27 @@ var WindowListener = {
|
|||
gBrowser.selectedBrowser.outerWindowID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles events from the frame script.
|
||||
*
|
||||
* @param {Object} message The message received from the frame script.
|
||||
*/
|
||||
handleDOMTitleChanged: function(message) {
|
||||
if (!this._listeningToTabSelect || this._browserSharePaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gBrowser.selectedBrowser == message.target) {
|
||||
// Get the new title of the shared tab
|
||||
this._notifyBrowserSwitch();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles events from gBrowser.
|
||||
*/
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "DOMTitleChanged":
|
||||
// Get the new title of the shared tab
|
||||
this._notifyBrowserSwitch();
|
||||
break;
|
||||
case "TabSelect":
|
||||
let wasVisible = false;
|
||||
// Hide the infobar from the previous tab.
|
||||
|
@ -950,6 +983,10 @@ var WindowListener = {
|
|||
if (window.LoopUI) {
|
||||
window.LoopUI.removeMenuItem();
|
||||
|
||||
// This stops the frame script being loaded to new tabs, but doesn't
|
||||
// remove it from existing tabs (there's no way to do that).
|
||||
window.LoopUI.mm.removeDelayedFrameScript(FRAME_SCRIPT);
|
||||
|
||||
// XXX Bug 1229352 - Add in tear-down of the panel.
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global sendAsyncMessage */
|
||||
|
||||
/**
|
||||
* This script runs in the content process and is attached to browsers when
|
||||
* they are created.
|
||||
*/
|
||||
|
||||
// Listen for when the title is changed and send a message back to the chrome
|
||||
// process.
|
||||
addEventListener("DOMTitleChanged", ({ target }) => {
|
||||
sendAsyncMessage("loop@mozilla.org:DOMTitleChanged", {
|
||||
details: "titleChanged"
|
||||
}, {
|
||||
target: target
|
||||
});
|
||||
});
|
|
@ -218,6 +218,7 @@ body {
|
|||
/* min-height because there is a browser min-height on panel */
|
||||
min-height: 53px;
|
||||
padding-top: 4px;
|
||||
transition: opacity .2s ease-in-out .2s;
|
||||
}
|
||||
|
||||
.room-list-empty {
|
||||
|
@ -259,6 +260,12 @@ body {
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Disable interacting with the room list when creating */
|
||||
.room-list-pending-creation {
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.room-list > .room-entry > h2 {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
|
|
@ -255,7 +255,7 @@ loop.panel = function (_, mozL10n) {
|
|||
loop.requestMulti(["GetUserProfile"], ["GetDoNotDisturb"]).then(function (results) {
|
||||
this.setState({
|
||||
signedIn: !!results[0],
|
||||
doNotDisturb: results[2]
|
||||
doNotDisturb: results[1]
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
|
@ -867,7 +867,9 @@ loop.panel = function (_, mozL10n) {
|
|||
"room-list": true,
|
||||
// add extra space to last item so when scrolling to bottom,
|
||||
// last item is not covered by the gradient
|
||||
"room-list-add-space": this.state.rooms.length && this.state.rooms.length > 5
|
||||
"room-list-add-space": this.state.rooms.length && this.state.rooms.length > 5,
|
||||
// Indicate there's a pending action to disable opening more rooms.
|
||||
"room-list-pending-creation": this.state.pendingCreation
|
||||
});
|
||||
|
||||
if (this.state.error) {
|
||||
|
|
|
@ -221,7 +221,9 @@ describe("loop.panel", function() {
|
|||
},
|
||||
locale: "en-US"
|
||||
});
|
||||
sandbox.stub(document.mozL10n, "get").returns("Fake title");
|
||||
sandbox.stub(document.mozL10n, "get", function(stringName) {
|
||||
return stringName;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
@ -491,6 +493,18 @@ describe("loop.panel", function() {
|
|||
view = mountTestComponent();
|
||||
});
|
||||
|
||||
it("should show a message to turn notifications off when they are on", function() {
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
GetDoNotDisturb: function() { return false; }
|
||||
});
|
||||
|
||||
view.showDropdownMenu();
|
||||
|
||||
var menuitem = view.getDOMNode().querySelector(".entry-settings-notifications");
|
||||
|
||||
expect(menuitem.textContent).eql("settings_menu_item_turnnotificationsoff");
|
||||
});
|
||||
|
||||
it("should toggle mozLoop.doNotDisturb to false", function() {
|
||||
var stub = sinon.stub();
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
|
@ -505,6 +519,18 @@ describe("loop.panel", function() {
|
|||
sinon.assert.calledWithExactly(stub, false);
|
||||
});
|
||||
|
||||
it("should show a message to turn notifications on when they are off", function() {
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
GetDoNotDisturb: function() { return true; }
|
||||
});
|
||||
|
||||
view.showDropdownMenu();
|
||||
|
||||
var menuitem = view.getDOMNode().querySelector(".entry-settings-notifications");
|
||||
|
||||
expect(menuitem.textContent).eql("settings_menu_item_turnnotificationson");
|
||||
});
|
||||
|
||||
it("should toggle mozLoop.doNotDisturb to true", function() {
|
||||
var stub = sinon.stub();
|
||||
LoopMochaUtils.stubLoopRequest({
|
||||
|
@ -1155,6 +1181,17 @@ describe("loop.panel", function() {
|
|||
expect(view.getDOMNode().querySelectorAll(".room-list-loading").length).to.eql(1);
|
||||
});
|
||||
|
||||
it("should disable the room list after room creation", function() {
|
||||
// Simulate that the user has clicked the browse button with other rooms.
|
||||
var view = createTestComponent();
|
||||
roomStore.setStoreState({
|
||||
pendingCreation: true,
|
||||
rooms: roomList
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelectorAll(".room-list-pending-creation").length).to.eql(1);
|
||||
});
|
||||
|
||||
it("should show multiple rooms in list with no opened room", function() {
|
||||
roomStore.setStoreState({ rooms: roomList });
|
||||
|
||||
|
@ -1306,7 +1343,7 @@ describe("loop.panel", function() {
|
|||
var view = createTestComponent();
|
||||
|
||||
var node = view.getDOMNode();
|
||||
expect(node.querySelector(".room-entry h2").textContent).to.equal("Fake title");
|
||||
expect(node.querySelector(".room-entry h2").textContent).to.equal("room_name_untitled_page");
|
||||
});
|
||||
|
||||
describe("computeAdjustedTopPosition", function() {
|
||||
|
|
|
@ -45,7 +45,7 @@ fte_slide_1_copy=Egal, ob Sie eine Reise planen oder ein Geschenk einkaufen, mit
|
|||
fte_slide_2_title2=Zum Teilen des Internets entwickelt
|
||||
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
fte_slide_2_copy2=Wenn Sie jetzt einen Freund zu einer Sitzung einladen teilt {{clientShortname2}} automatisch die aktuelle Webseite. Planen. Einkaufen. Entscheiden. Gemeinsam.
|
||||
fte_slide_2_copy2=Wenn Sie jetzt einen Freund zu einer Sitzung einladen, teilt {{clientShortname2}} automatisch die aktuelle Webseite. Planen. Einkaufen. Entscheiden. Gemeinsam.
|
||||
fte_slide_3_title=Laden Sie einen Freund ein, indem Sie ihm einen Link senden
|
||||
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brand name.
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
# Panel Strings
|
||||
|
||||
|
||||
|
||||
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
|
||||
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
|
||||
## use "..." if \u2026 doesn't suit traditions in your locale.
|
||||
loopMenuItem_label=Aloita keskustelu…
|
||||
loopMenuItem_accesskey=k
|
||||
|
||||
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
|
||||
## These are displayed together at the top of the panel when a user is needed to
|
||||
|
@ -16,19 +16,29 @@
|
|||
## and this is displayed in slightly larger font. Please arrange as necessary for
|
||||
## your locale.
|
||||
## {{clientShortname2}} will be replaced by the brand name for either string.
|
||||
sign_in_again_title_line_one=Kirjaudu uudestaan sisään
|
||||
sign_in_again_title_line_two2=jatkaaksesi {{clientShortname2}}n käyttöä
|
||||
sign_in_again_button=Kirjaudu
|
||||
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brandname.
|
||||
sign_in_again_use_as_guest_button2=Käytä {{clientSuperShortname}}-palvelua vierastunnuksilla
|
||||
|
||||
panel_browse_with_friend_button=Selaa sivua kaverin kanssa
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
|
||||
## user to create his or her first conversation.
|
||||
first_time_experience_subheading2=Napsauta Hello-painiketta selataksesi verkkosivuja kaverin kanssa.
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
|
||||
## ways to use Hello project.
|
||||
first_time_experience_content=Käytä sitä suunnittelemaan, työskentelemään tai nauramaan yhdessä.
|
||||
first_time_experience_button_label2=Katso miten se toimii
|
||||
|
||||
## First Time Experience Slides
|
||||
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brand name.
|
||||
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
|
||||
|
@ -36,35 +46,72 @@
|
|||
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
|
||||
## will be replaced by the brand short name.
|
||||
|
||||
invite_header_text_bold2=Kutsu kaveri mukaasi!
|
||||
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
|
||||
## invite_email_link_button, invite_facebook_button2): These labels appear under
|
||||
## an iconic button for the invite view.
|
||||
invite_copy_link_button=Kopioi linkki
|
||||
invite_copied_link_button=Kopioitu!
|
||||
invite_email_link_button=Lähetä linkki
|
||||
invite_facebook_button3=Facebook
|
||||
invite_your_link=Linkkisi:
|
||||
|
||||
# Error bars
|
||||
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
|
||||
## These may be displayed at the top of the panel.
|
||||
session_expired_error_description=Istunto vanhentunut. Kaikki aikaisemmin luodut ja jaetut linkit eivät enää toimi.
|
||||
could_not_authenticate=Todentaminen ei onnistunut
|
||||
password_changed_question=Oletko vaihtanut salasanaa?
|
||||
try_again_later=Yritä uudestaan myöhemmin
|
||||
could_not_connect=Ei voitu muodostaa yhteyttä palvelimeen
|
||||
check_internet_connection=Tarkista Internet-yhteytesi
|
||||
login_expired=Kirjautumisesi on vanhentunut
|
||||
service_not_available=Palvelu ei ole saatavilla tällä hetkellä
|
||||
problem_accessing_account=Pääsyssä tilillesi oli ongelma
|
||||
|
||||
## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
|
||||
## the appropriate action.
|
||||
retry_button=Yritä uudestaan
|
||||
|
||||
share_email_subject7=Sinut on kutsuttu selaamaan verkkoa yhdessä
|
||||
## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
|
||||
## part between {{..}} and leave the \n\n part alone
|
||||
share_email_body7=Kaveri odottaa sinua Firefox Hellossa. Napsauta linkkiä liittyäksesi selaamaan verkkoa yhdessä: {{callUrl}}
|
||||
## LOCALIZATION NOTE (share_email_body_context3): In this item, don't translate
|
||||
## the part between {{..}} and leave the \n\n part alone.
|
||||
share_email_body_context3=Kaveri odottaa sinua Firefox Hellossa. Napsauta linkkiä liittyäksesi selaamaan sivustoa {{title}} yhdessä: {{callUrl}}
|
||||
## LOCALIZATION NOTE (share_email_footer2): Common footer content for both email types
|
||||
share_email_footer2=\n\n____________\nFirefox Hello mahdollistaa verkon selaamisen kavereiden kanssa. Käytä sitä, kun haluat saada hommat hoidettua: suunnittele, työskentele ja naura yhdessä. Lue lisää osoitteesta http://www.firefox.com/hello
|
||||
## LOCALIZATION NOTE (share_tweeet): In this item, don't translate the part
|
||||
## between {{..}}. Please keep the text below 117 characters to make sure it fits
|
||||
## in a tweet.
|
||||
share_tweet=Aloita kanssani {{clientShortname2}}-pohjainen videokeskustelu!
|
||||
|
||||
share_add_service_button=Lisää palvelu
|
||||
|
||||
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
|
||||
## These menu items are displayed from a panel's context menu for a conversation.
|
||||
copy_link_menuitem=Kopioi linkki
|
||||
email_link_menuitem=Lähetä linkki
|
||||
delete_conversation_menuitem2=Poista
|
||||
|
||||
panel_footer_signin_or_signup_link=Kirjaudu tai rekisteröidy
|
||||
|
||||
settings_menu_item_account=Tili
|
||||
settings_menu_item_settings=Asetukset
|
||||
settings_menu_item_signout=Kirjaudu ulos
|
||||
settings_menu_item_signin=Kirjaudu sisään
|
||||
settings_menu_item_turnnotificationson=Ota huomautukset käyttöön
|
||||
settings_menu_item_turnnotificationsoff=Poista huomautukset käytöstä
|
||||
settings_menu_item_feedback=Lähetä palautetta
|
||||
settings_menu_button_tooltip=Asetukset
|
||||
|
||||
|
||||
# Conversation Window Strings
|
||||
|
||||
initiate_call_button_label2=Oletko valmis aloittamaan keskustelun?
|
||||
incoming_call_title2=Keskustelupyyntö
|
||||
incoming_call_block_button=Estä
|
||||
hangup_button_title=Katkaise
|
||||
hangup_button_caption2=Lopeta
|
||||
|
||||
|
@ -72,45 +119,81 @@ hangup_button_caption2=Lopeta
|
|||
## LOCALIZATION NOTE (call_with_contact_title): The title displayed
|
||||
## when calling a contact. Don't translate the part between {{..}} because
|
||||
## this will be replaced by the contact's name.
|
||||
call_with_contact_title=Keskustelussa {{contactName}}
|
||||
|
||||
# Outgoing conversation
|
||||
|
||||
outgoing_call_title=Aloitetaanko keskustelu?
|
||||
initiate_audio_video_call_button2=Aloita
|
||||
initiate_audio_video_call_tooltip2=Aloita videokeskustelu
|
||||
initiate_audio_call_button2=Äänikeskustelu
|
||||
|
||||
peer_ended_conversation2=Keskustelukumppani päätti keskustelun.
|
||||
restart_call=Liity uudestaan
|
||||
|
||||
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
|
||||
## contact is offline.
|
||||
contact_offline_title=Tämä henkilö ei ole verkkoyhteyden päässä
|
||||
## LOCALIZATION NOTE (call_timeout_notification_text): Title which is displayed
|
||||
## when the call didn't go through.
|
||||
call_timeout_notification_text=Puhelusi ei mennyt läpi.
|
||||
|
||||
## LOCALIZATION NOTE (cancel_button):
|
||||
## This button is displayed when a call has failed.
|
||||
cancel_button=Peruuta
|
||||
rejoin_button=Liity uudestaan keskusteluun
|
||||
|
||||
cannot_start_call_session_not_ready=Ei voida soittaa, istunto ei ole valmis.
|
||||
network_disconnected=Verkkoyhteys katkesi yllättäen.
|
||||
connection_error_see_console_notification=Puhelu epäonnistui; katso lisätietoja konsolista.
|
||||
no_media_failure_message=Ei löytynyt kameraa tai mikrofonia.
|
||||
ice_failure_message=Yhteys epäonnistui. Palomuurisi voi estää puhelut.
|
||||
|
||||
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
|
||||
## parts between {{..}} because these will be replaced with links with the labels
|
||||
## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
|
||||
## by the brand name.
|
||||
legal_text_and_links3=Käyttämällä {{clientShortname}} -palvelua hyväksyt sen {{terms_of_use}} ja {{privacy_notice}}.
|
||||
legal_text_tos=käyttöehdot
|
||||
legal_text_privacy=tietosuojakäytännön
|
||||
|
||||
## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
|
||||
## These 2 strings are displayed before and after a 'Telefonica'
|
||||
## logo.
|
||||
powered_by_beforeLogo=Perustuu
|
||||
powered_by_afterLogo=teknologioihin
|
||||
|
||||
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
|
||||
## a signed-in to signed-in user call.
|
||||
feedback_rejoin_button=Liity uudestaan
|
||||
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
|
||||
## an abusive user.
|
||||
feedback_report_user_button=Ilmoita käyttäjä
|
||||
feedback_window_heading=Miten keskustelu sujui?
|
||||
feedback_request_button=Anna palautetta
|
||||
|
||||
tour_label=Esittely
|
||||
|
||||
rooms_list_recently_browsed2=Viimeiset sivustot
|
||||
rooms_list_currently_browsing2=Avoimet sivut
|
||||
rooms_signout_alert=Avoimet keskustelut suljetaan
|
||||
room_name_untitled_page=Nimetön sivu
|
||||
|
||||
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
|
||||
door_hanger_return=Nähdään myöhemmin! Voit palata tähän jaettuun istuntoon milloin tahansa Hello-paneelin kautta.
|
||||
door_hanger_prompt_name=Haluatko antaa sille nimen, joka on helpompi muistaa? Nykyinen nimi:
|
||||
door_hanger_button=OK
|
||||
|
||||
# Infobar strings
|
||||
|
||||
infobar_screenshare_browser_message2=Jaat parhaillaan välilehtiäsi. Kaverisi näkevät kaikki välilehdet, joita napsautat
|
||||
infobar_button_stop_label2=Lopeta jakaminen
|
||||
infobar_button_stop_accesskey=L
|
||||
|
||||
# E10s not supported strings
|
||||
|
||||
e10s_not_supported_button_label=Avaa uusi ikkuna
|
||||
e10s_not_supported_subheading={{brandShortname}} ei toimi useampaa prosessia hyödyntävässä ikkunassa.
|
||||
# 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/.
|
||||
|
@ -118,12 +201,14 @@ door_hanger_button=OK
|
|||
## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
|
||||
|
||||
# Text chat strings
|
||||
chat_textbox_placeholder=Kirjoita tähän…
|
||||
|
||||
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
|
||||
## should remain "Firefox Hello" for all locales.
|
||||
clientShortname2=Firefox Hello
|
||||
|
||||
conversation_has_ended=Keskustelu on päättynyt.
|
||||
generic_failure_message=Palvelulla on tällä hetkellä teknisiä vaikeuksia…
|
||||
|
||||
generic_failure_no_reason2=Yritetäänkö uudestaan?
|
||||
|
||||
|
@ -131,6 +216,8 @@ help_label=Lähetä
|
|||
|
||||
mute_local_audio_button_title=Mykistä ääni
|
||||
unmute_local_audio_button_title=Palauta ääni
|
||||
mute_local_video_button_title2=Piilota video
|
||||
unmute_local_video_button_title2=Näytä video
|
||||
|
||||
## LOCALIZATION NOTE (retry_call_button):
|
||||
## This button is displayed when a call has failed.
|
||||
|
@ -144,12 +231,15 @@ rooms_room_full_call_to_action_label=Lue lisää {{clientShortname}}ista »
|
|||
rooms_room_full_call_to_action_nonFx_label=Lataa {{brandShortname}} aloittaaksesi oman keskustelun
|
||||
rooms_room_full_label=Keskustelussa on jo kaksi henkilöä paikalla.
|
||||
rooms_room_join_label=Liity keskusteluun
|
||||
rooms_room_joined_label=Joku on liittynyt keskusteluun!
|
||||
|
||||
self_view_hidden_message=Omanäkymä piilotettu, mutta lähetetään edelleen. Muuta ikkunan kokoa nähdäksesi.
|
||||
|
||||
|
||||
## LOCALIZATION NOTE (tos_failure_message): Don't translate {{clientShortname}}
|
||||
## as this will be replaced by clientShortname2.
|
||||
tos_failure_message={{clientShortname}} ei ole saatavilla maassasi.
|
||||
|
||||
display_name_guest=Vieras
|
||||
|
||||
## LOCALIZATION NOTE(clientSuperShortname): This should not be localized and
|
||||
## should remain "Hello" for all locales.
|
||||
|
|
|
@ -46,7 +46,7 @@ fte_slide_2_title2=Realizzato per condividere il Web
|
|||
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
fte_slide_2_copy2=Adesso ogni volta che inviti un utente a una sessione di chat, {clientShortname2} condividerà automaticamente la pagina web che stai visitando. Organizza. Fai acquisti. Decidi. In compagnia.
|
||||
fte_slide_3_title=Invita un link a un altro utente per invitarlo
|
||||
fte_slide_3_title=Invia un link a un altro utente per invitarlo
|
||||
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brand name.
|
||||
fte_slide_3_copy={{clientSuperShortname}} è compatibile con la maggior parte dei browser per desktop. Il servizio è gratuito e non richiede la registrazione di alcun account.
|
||||
|
|
|
@ -29,6 +29,7 @@ panel_disconnect_button=Deconnectar
|
|||
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
|
||||
## user to create his or her first conversation.
|
||||
first_time_experience_subheading2=Clicca sin il buttun da Hello per navigar cun in ami en il web.
|
||||
first_time_experience_subheading_button_above=Clicca sin il buttun survart per navigar cun in ami en il web.
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
|
||||
## ways to use Hello project.
|
||||
|
@ -36,17 +37,27 @@ first_time_experience_content=Fa diever da la funcziun per planisar ensemen, lav
|
|||
first_time_experience_button_label2=Mussar co che quai funcziunescha
|
||||
|
||||
## First Time Experience Slides
|
||||
fte_slide_1_title=Navighescha cun in(a) ami(a) sin paginas web
|
||||
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
fte_slide_1_copy=Sche ti vul planisar en dus in viadi u la cumpra dad in regal, ta gida {{clientShortname2}} da prender pli svelt ina decisiun en temp real.
|
||||
fte_slide_2_title2=Fatg per cundivider il web
|
||||
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
fte_slide_2_copy2=Sche ti envidas ussa in(a) ami(a) per ina sesida, cundivida {{clientShortname2}} automaticamain mintga pagina d'internet che ti visitas. Planisai. Cumprai. Decidai. En dus.
|
||||
fte_slide_3_title=Envida in(a) ami(a) cun trametter ina colliaziun
|
||||
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brand name.
|
||||
fte_slide_3_copy={{clientSuperShortname}} funcziuna cun la gronda part dals navigaturs per computers classics. I na dovra nagin conto e la connexiun è gratuita.
|
||||
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brand name.
|
||||
fte_slide_4_title=Cun agid da l'icona da {{clientSuperShortname}} cumenzi
|
||||
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
|
||||
## will be replaced by the brand short name.
|
||||
fte_slide_4_copy=Cura che ti chattas ina pagina che ti vuls discutar, clicca sin l'icona da {{brandShortname}} per crear ina colliaziun. Lura la pos ti trametter - en ina moda u l'autra - a tia amia u tes ami!
|
||||
|
||||
invite_header_text_bold2=Envida in ami a participar!
|
||||
invite_header_text4=Cundivida questa colliaziun per che vus possias cumenzar a navigar en dus tras il web.
|
||||
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
|
||||
## invite_email_link_button, invite_facebook_button2): These labels appear under
|
||||
## an iconic button for the invite view.
|
||||
|
@ -93,6 +104,7 @@ share_add_service_button=Agiuntar in servetsch
|
|||
## These menu items are displayed from a panel's context menu for a conversation.
|
||||
copy_link_menuitem=Copiar la colliaziun
|
||||
email_link_menuitem=Trametter la colliaziun via e-mail
|
||||
edit_name_menuitem=Modifitgar il num
|
||||
delete_conversation_menuitem2=Stizzar
|
||||
|
||||
panel_footer_signin_or_signup_link=S'annunziar u sa registrar
|
||||
|
@ -187,7 +199,9 @@ door_hanger_button=OK
|
|||
|
||||
# Infobar strings
|
||||
|
||||
infobar_screenshare_no_guest_message=Uschespert che tia amia u tes ami è da la partida, po el(la) vesair mintga tab sin il qual ti cliccas.
|
||||
infobar_screenshare_browser_message2=Ti cundividas tes tabs. Mintga tab che ti avras vesan era tes amis
|
||||
infobar_screenshare_browser_message3=Ti cundividas ussa tes tabs. Tia amia u tes ami vesan ussa mintga tab sin il qual ti cliccas.
|
||||
infobar_screenshare_stop_sharing_message=Ti na cundividas betg pli tes tabs
|
||||
infobar_button_restart_label2=Puspè cundivider
|
||||
infobar_button_restart_accesskey=e
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
# Panel Strings
|
||||
|
||||
|
||||
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
|
||||
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
|
||||
## use "..." if \u2026 doesn't suit traditions in your locale.
|
||||
loopMenuItem_label=ఒక సంభాషణను ప్రారంభించేందుకు...
|
||||
loopMenuItem_accesskey=టీ
|
||||
|
||||
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):
|
||||
## These are displayed together at the top of the panel when a user is needed to
|
||||
|
@ -15,29 +16,70 @@
|
|||
## and this is displayed in slightly larger font. Please arrange as necessary for
|
||||
## your locale.
|
||||
## {{clientShortname2}} will be replaced by the brand name for either string.
|
||||
sign_in_again_title_line_one=మళ్ళీ సైన్ ఇన్ చేయండి
|
||||
sign_in_again_title_line_two2=ఉపయోగించడం కొనసాగించాలని{{clientShortname2}}
|
||||
sign_in_again_button=సైన్ ఇన్
|
||||
## LOCALIZATION_NOTE(sign_in_again_use_as_guest_button2): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brandname.
|
||||
sign_in_again_use_as_guest_button2=వా డు{{clientSuperShortname}}ఒక అతిథి వలె
|
||||
|
||||
panel_browse_with_friend_button=ఒక స్నేహితుడు ఈ పేజీ బ్రౌజ్
|
||||
panel_disconnect_button=డిస్కనెక్ట్
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience_subheading2): Message inviting the
|
||||
## LOCALIZATION_NOTE(first_time_experience_subheading2, first_time_experience_subheading_button_above): Message inviting the
|
||||
## user to create his or her first conversation.
|
||||
first_time_experience_subheading2=ఒక స్నేహితుడు తో వెబ్ పేజీలను బ్రౌజ్ హలో బటన్ క్లిక్ చేయండి.
|
||||
first_time_experience_subheading_button_above=బటన్ పైన స్నేహితునితో వెబ్ పేజీలను బ్రౌజ్ చేయండి.
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience_content): Message describing
|
||||
## LOCALIZATION_NOTE(first_time_experience_content, first_time_experience_content2): Message describing
|
||||
## ways to use Hello project.
|
||||
first_time_experience_content=కలిసి పని, కలిసి ప్లాన్ దాన్ని ఉపయోగించండి, కలిసి నవ్వు.
|
||||
first_time_experience_content2=పనులు పూర్తి చేయడానికి దీనిని ఉపయోగిస్తారు: కలిసి నవ్వు, కలిసి ప్లాన్ కలిసి పని.
|
||||
first_time_experience_button_label2=ఎలా పనిచేస్తుందో చూడండి
|
||||
|
||||
## First Time Experience Slides
|
||||
fte_slide_1_title=ఒక స్నేహితుడు ఈ పేజీ బ్రౌజ్
|
||||
## LOCALIZATION_NOTE(fte_slide_1_copy): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
fte_slide_2_title2=వెబ్ భాగస్వామ్యం కోసం మేడ్
|
||||
## LOCALIZATION_NOTE(fte_slide_2_copy2): {{clientShortname2}}
|
||||
## will be replaced by the short name 2.
|
||||
fte_slide_3_title=ఒక లింక్ పంపడం మిత్రులని ఆహ్వానించండి
|
||||
## LOCALIZATION_NOTE(fte_slide_3_copy): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brand name.
|
||||
## LOCALIZATION_NOTE(fte_slide_4_title): {{clientSuperShortname}}
|
||||
## will be replaced by the super short brand name.
|
||||
## LOCALIZATION_NOTE(fte_slide_4_copy): {{brandShortname}}
|
||||
## will be replaced by the brand short name.
|
||||
|
||||
invite_header_text_bold2=మీరు చేరడానికి కు ఆహ్వానించండి !
|
||||
invite_header_text4=కాబట్టి మీరు కలిసి వెబ్ బ్రౌజింగ్ ప్రారంభించవచ్చు లింక్ను భాగస్వామ్యం చేయండి.
|
||||
## LOCALIZATION_NOTE(invite_copy_link_button, invite_copied_link_button,
|
||||
## invite_email_link_button, invite_facebook_button2): These labels appear under
|
||||
## an iconic button for the invite view.
|
||||
|
||||
# Status text
|
||||
invite_copy_link_button=లంకె నకలుతీయి
|
||||
invite_copied_link_button=నకలుతీసెను!
|
||||
invite_email_link_button=లంకెను ఈమెయిలు చెయ్యి
|
||||
invite_facebook_button3=ఫేస్బుక్
|
||||
invite_your_link=మీ లంకె:
|
||||
|
||||
# Error bars
|
||||
## LOCALIZATION NOTE(session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
|
||||
## These may be displayed at the top of the panel.
|
||||
could_not_authenticate=ధృవీకరించలేక పోయింది
|
||||
password_changed_question=మీరు మీ సంకేతపదం మార్చారా?
|
||||
try_again_later=దయచేసి తరువాత ప్రయత్నించు
|
||||
could_not_connect=సేవికకు అనుసంధానము కాలేకపోయింది
|
||||
check_internet_connection=మీ అంతర్జాలం అనుసంధానం పరీక్షించండి
|
||||
login_expired=మీ లాగిన్ గడువుతీరినది
|
||||
service_not_available=ఈ సమయంలో సేవ అందుబాటులోలేదు
|
||||
problem_accessing_account=మీ ఖాతాను ఏక్సెస్ చేయుటలో సమస్యవుంది
|
||||
|
||||
## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
|
||||
## the appropriate action.
|
||||
retry_button=పునఃప్రయత్నించండి
|
||||
|
||||
share_email_subject7=జాలంలో కలిసి విహరించడానికి ఆహ్వానం
|
||||
## LOCALIZATION NOTE (share_email_body7): In this item, don't translate the
|
||||
## part between {{..}} and leave the \n\n part alone
|
||||
## LOCALIZATION NOTE (share_email_body_context3): In this item, don't translate
|
||||
|
@ -47,78 +89,102 @@
|
|||
## between {{..}}. Please keep the text below 117 characters to make sure it fits
|
||||
## in a tweet.
|
||||
|
||||
share_add_service_button=సేవను చేర్చు
|
||||
|
||||
## LOCALIZATION NOTE (copy_link_menuitem, email_link_menuitem, delete_conversation_menuitem):
|
||||
## These menu items are displayed from a panel's context menu for a conversation.
|
||||
copy_link_menuitem=లంకె నకలుతీయి
|
||||
email_link_menuitem=లంకెను ఈమెయిలు చెయ్యి
|
||||
edit_name_menuitem=పేరుని సంకలనం చెయ్యి
|
||||
delete_conversation_menuitem2=తొలగించు
|
||||
|
||||
panel_footer_signin_or_signup_link=సైన్ ఇన్ లేదా సైన్ అప్
|
||||
|
||||
settings_menu_item_account=ఖాతా
|
||||
settings_menu_item_settings=సెట్టింగ్లు
|
||||
settings_menu_item_signout=నిష్క్రమించు
|
||||
settings_menu_item_signin=ప్రవేశించు
|
||||
settings_menu_item_turnnotificationson=గమనింపులను చేతనించు
|
||||
settings_menu_item_turnnotificationsoff=గమనింపులను అచేతనించు
|
||||
settings_menu_item_feedback=ప్రతిస్పందనను తెలియజేయండి
|
||||
settings_menu_button_tooltip=సెట్టింగ్లు
|
||||
|
||||
|
||||
# Conversation Window Strings
|
||||
|
||||
initiate_call_button_label2=Ready to start your conversation?
|
||||
hangup_button_title=Hang up
|
||||
hangup_button_caption2=Exit
|
||||
initiate_call_button_label2=మీ సంభాషణ ప్రారంభించుటకు సిద్దమేనా?
|
||||
incoming_call_title2=సంభాషణ అభ్యర్దన
|
||||
incoming_call_block_button=అడ్డగించు
|
||||
hangup_button_title=పెట్టెయ్యండి
|
||||
hangup_button_caption2=నిష్క్రమణ
|
||||
|
||||
|
||||
## LOCALIZATION NOTE (call_with_contact_title): The title displayed
|
||||
## when calling a contact. Don't translate the part between {{..}} because
|
||||
## this will be replaced by the contact's name.
|
||||
call_with_contact_title=Conversation with {{incomingCallIdentity}}
|
||||
call_with_contact_title={{contactName}} తో సంభాషణ
|
||||
|
||||
# Outgoing conversation
|
||||
|
||||
outgoing_call_title=Start conversation?
|
||||
initiate_audio_video_call_button2=Start
|
||||
initiate_audio_video_call_tooltip2=Start a video conversation
|
||||
initiate_audio_call_button2=Voice conversation
|
||||
outgoing_call_title=సంభాషణ ప్రారంభించాలా?
|
||||
initiate_audio_video_call_button2=ప్రారంభం
|
||||
initiate_audio_video_call_tooltip2=వీడియో సంభాషణ ప్రారంభించుము
|
||||
initiate_audio_call_button2=మాట సంభాషణ
|
||||
|
||||
peer_ended_conversation2=The person you were calling has ended the conversation.
|
||||
restart_call=Rejoin
|
||||
peer_ended_conversation2=మీ కాల్చేసిన వ్యక్తి సంభాషణను ముగించారు.
|
||||
restart_call=తిరిగిచేరు
|
||||
|
||||
## LOCALIZATION NOTE (contact_offline_title): Title which is displayed when the
|
||||
## contact is offline.
|
||||
contact_offline_title=ఈ వ్యక్తి ఆన్లైన్ నందు లేరు
|
||||
## LOCALIZATION NOTE (call_timeout_notification_text): Title which is displayed
|
||||
## when the call didn't go through.
|
||||
call_timeout_notification_text=Your call did not go through.
|
||||
call_timeout_notification_text=మీ కాల్ వెళ్ళలేదు.
|
||||
|
||||
## LOCALIZATION NOTE (cancel_button):
|
||||
## This button is displayed when a call has failed.
|
||||
cancel_button=రద్దుచేయి
|
||||
rejoin_button=మళ్ళీ సంభాషణలో చేరండి
|
||||
|
||||
network_disconnected=The network connection terminated abruptly.
|
||||
connection_error_see_console_notification=Call failed; see console for details.
|
||||
cannot_start_call_session_not_ready=కాల్ ప్రారంభించలేదు, సెషన్ సిద్ధంగా లేదు.
|
||||
network_disconnected=అకస్మాత్తుగా నెట్వర్క్ అనుసంధానం పోయింది.
|
||||
connection_error_see_console_notification=కాల్ విఫలమైంది; వివరాలకు కన్సోల్ చూడు.
|
||||
no_media_failure_message=కేమెరా లేదా మైక్రోఫోన్ కనబడలేదు.
|
||||
ice_failure_message=అనుసంధానం విఫలమయ్యింది. మీ ఫైర్వాల్ కాల్సుని నిరోధిస్తూండవచ్చు.
|
||||
|
||||
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
|
||||
## parts between {{..}} because these will be replaced with links with the labels
|
||||
## from legal_text_tos and legal_text_privacy. clientShortname will be replaced
|
||||
## by the brand name.
|
||||
legal_text_tos=వినియోగ నియమాలు
|
||||
legal_text_privacy=గోప్యతా విధానం
|
||||
|
||||
## LOCALIZATION NOTE (powered_by_beforeLogo, powered_by_afterLogo):
|
||||
## These 2 strings are displayed before and after a 'Telefonica'
|
||||
## logo.
|
||||
powered_by_beforeLogo=వీరి సహకారంతో
|
||||
powered_by_afterLogo=
|
||||
|
||||
## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
|
||||
## a signed-in to signed-in user call.
|
||||
feedback_rejoin_button=Rejoin
|
||||
feedback_rejoin_button=తిరిగిచేరు
|
||||
## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
|
||||
## an abusive user.
|
||||
feedback_report_user_button=Report User
|
||||
feedback_report_user_button=వాడుకరిని నివేదించు
|
||||
feedback_window_heading=మీ సంభాషణ ఎలావుంది?
|
||||
feedback_request_button=ప్రతిస్పందన తెలియజేయండి
|
||||
|
||||
tour_label=Tour
|
||||
tour_label=ప్రదర్శన
|
||||
|
||||
rooms_list_recently_browsed2=ఇటీవల చూసినవి
|
||||
rooms_list_currently_browsing2=ప్రస్తుతం చూస్తున్నవి
|
||||
rooms_signout_alert=తెరిచిన సంభాషణలు మూయబడతాయి
|
||||
|
||||
## LOCALIZATION NOTE (door_hanger_return, door_hanger_prompt_name, door_hanger_button): Dialog message on leaving conversation
|
||||
|
||||
# Infobar strings
|
||||
|
||||
|
||||
# Context in conversation strings
|
||||
|
||||
## LOCALIZATION NOTE(no_conversations_message_heading2): Title shown when user
|
||||
## has no conversations available.
|
||||
## LOCALIZATION NOTE(no_conversations_start_message2): Subheading inviting the
|
||||
## user to start a new conversation.
|
||||
|
||||
# E10s not supported strings
|
||||
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
|
|
|
@ -15,4 +15,5 @@ support-files =
|
|||
[browser_mozLoop_socialShare.js]
|
||||
[browser_mozLoop_sharingListeners.js]
|
||||
[browser_mozLoop_telemetry.js]
|
||||
[browser_sharingTitleListeners.js]
|
||||
[browser_toolbarbutton.js]
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/*
|
||||
* This file contains tests for the browser sharing document title listeners.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
var [, gHandlers] = LoopAPI.inspect();
|
||||
|
||||
function promiseBrowserSwitch() {
|
||||
return new Promise(resolve => {
|
||||
LoopAPI.stub([{
|
||||
sendAsyncMessage: function(messageName, data) {
|
||||
if (data[0] == "BrowserSwitch") {
|
||||
LoopAPI.restore();
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}]);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* setup() {
|
||||
Services.prefs.setBoolPref("loop.remote.autostart", true);
|
||||
|
||||
gHandlers.AddBrowserSharingListener({ data: [42] }, () => {});
|
||||
|
||||
let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank", true);
|
||||
|
||||
registerCleanupFunction(function* () {
|
||||
// Remove the listener.
|
||||
gHandlers.RemoveBrowserSharingListener({ data: [42] }, function() {});
|
||||
|
||||
yield BrowserTestUtils.removeTab(newTab);
|
||||
|
||||
Services.prefs.clearUserPref("loop.remote.autostart");
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* test_notifyOnTitleChanged() {
|
||||
// Hook up the async listener and wait for the BrowserSwitch to happen.
|
||||
let browserSwitchPromise = promiseBrowserSwitch();
|
||||
|
||||
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, "about:mozilla");
|
||||
|
||||
// Now check we get the notification of the browser switch.
|
||||
yield browserSwitchPromise;
|
||||
|
||||
Assert.ok(true, "We got notification of the browser switch");
|
||||
});
|
|
@ -9,7 +9,7 @@
|
|||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>loop@mozilla.org</em:id>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:version>1.2.4</em:version>
|
||||
<em:version>1.2.6</em:version>
|
||||
<em:type>2</em:type>
|
||||
|
||||
<!-- Target Application this extension can install into,
|
||||
|
@ -32,7 +32,7 @@
|
|||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Firefox Hello Beta</em:name>
|
||||
<em:name>Firefox Hello</em:name>
|
||||
<em:description>Web sharing for Firefox</em:description>
|
||||
<em:creator>Mozilla</em:creator>
|
||||
</Description>
|
||||
|
|
|
@ -1,38 +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/. -->
|
||||
|
||||
<!ENTITY % brandDTD
|
||||
SYSTEM "chrome://branding/locale/brand.dtd">
|
||||
%brandDTD;
|
||||
|
||||
<!-- These strings are used by Firefox's custom about:certerror page,
|
||||
a replacement for the standard security certificate errors produced
|
||||
by NSS/PSM via netError.xhtml. -->
|
||||
|
||||
<!ENTITY certerror.pagetitle1 "Insecure Connection">
|
||||
<!ENTITY certerror.longpagetitle1 "Your connection is not secure">
|
||||
|
||||
<!-- Localization note (certerror.introPara) - The text content of the span tag
|
||||
will be replaced at runtime with the name of the server to which the user
|
||||
was trying to connect. -->
|
||||
<!ENTITY certerror.introPara "The owner of <span class='hostname'/> has configured their website improperly. To protect your information from being stolen, &brandShortName; has not connected to this website.">
|
||||
<!ENTITY certerror.returnToPreviousPage.label "Go Back">
|
||||
<!ENTITY certerror.learnMore "Learn more…">
|
||||
<!ENTITY certerror.advanced.label "Advanced">
|
||||
|
||||
<!ENTITY certerror.whatShouldIDo.badStsCertExplanation "This site uses HTTP
|
||||
Strict Transport Security (HSTS) to specify that &brandShortName; only connect
|
||||
to it securely. As a result, it is not possible to add an exception for this
|
||||
certificate.">
|
||||
|
||||
<!ENTITY certerror.expert.content "If you understand what’s going on, you
|
||||
can tell &brandShortName; to start trusting this site’s identification.
|
||||
<b>Even if you trust the site, this error could mean that someone is
|
||||
tampering with your connection.</b>">
|
||||
<!ENTITY certerror.expert.contentPara2 "Don’t add an exception unless
|
||||
you know there’s a good reason why this site doesn’t use trusted identification.">
|
||||
<!ENTITY certerror.addException.label "Add Exception…">
|
||||
<!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
|
||||
|
||||
<!ENTITY errorReporting.automatic "Report errors like this to help Mozilla identify misconfigured sites">
|
|
@ -139,15 +139,11 @@
|
|||
</ul>
|
||||
">
|
||||
|
||||
<!ENTITY nssBadCert.title "Secure Connection Failed">
|
||||
<!ENTITY nssBadCert.longDesc2 "
|
||||
<ul>
|
||||
<li>This could be a problem with the server’s configuration, or it could be
|
||||
someone trying to impersonate the server.</li>
|
||||
<li>If you have connected to this server successfully in the past, the error may
|
||||
be temporary, and you can try again later.</li>
|
||||
</ul>
|
||||
">
|
||||
<!ENTITY certerror.longpagetitle1 "Your connection is not secure">
|
||||
<!-- Localization note (certerror.introPara) - The text content of the span tag
|
||||
will be replaced at runtime with the name of the server to which the user
|
||||
was trying to connect. -->
|
||||
<!ENTITY certerror.introPara "The owner of <span class='hostname'/> has configured their website improperly. To protect your information from being stolen, &brandShortName; has not connected to this website.">
|
||||
|
||||
<!ENTITY sharedLongDesc "
|
||||
<ul>
|
||||
|
@ -167,22 +163,8 @@ be temporary, and you can try again later.</li>
|
|||
<!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
|
||||
|
||||
|
||||
<!ENTITY securityOverride.linkText "Or you can add an exception…">
|
||||
<!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
|
||||
<!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">
|
||||
|
||||
<!-- LOCALIZATION NOTE (securityOverride.warningContent) - Do not translate the
|
||||
contents of the <button> tags. It uses strings already defined above. The
|
||||
button is included here (instead of netError.xhtml) because it exposes
|
||||
functionality specific to firefox. -->
|
||||
|
||||
<!ENTITY securityOverride.warningContent "
|
||||
<p>You should not add an exception if you are using an internet connection that you do not trust completely or if you are not used to seeing a warning for this server.</p>
|
||||
|
||||
<button id='getMeOutOfHereButton'>&securityOverride.getMeOutOfHereButton;</button>
|
||||
<button id='exceptionDialogButton'>&securityOverride.exceptionButtonLabel;</button>
|
||||
">
|
||||
|
||||
<!ENTITY errorReporting.automatic2 "Report errors like this to help Mozilla identify and block malicious sites">
|
||||
<!ENTITY errorReporting.learnMore "Learn more…">
|
||||
|
||||
|
@ -201,3 +183,10 @@ functionality specific to firefox. -->
|
|||
<!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">
|
||||
|
||||
<!ENTITY certerror.pagetitle1 "Insecure Connection">
|
||||
<!ENTITY certerror.whatShouldIDo.badStsCertExplanation "This site uses HTTP
|
||||
Strict Transport Security (HSTS) to specify that &brandShortName; only connect
|
||||
to it securely. As a result, it is not possible to add an exception for this
|
||||
certificate.">
|
||||
<!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
|
|
@ -8,7 +8,6 @@
|
|||
% locale browser @AB_CD@ %locale/browser/
|
||||
* locale/browser/bookmarks.html (generic/profile/bookmarks.html.in)
|
||||
locale/browser/aboutAccounts.dtd (%chrome/browser/aboutAccounts.dtd)
|
||||
locale/browser/aboutCertError.dtd (%chrome/browser/aboutCertError.dtd)
|
||||
locale/browser/aboutDialog.dtd (%chrome/browser/aboutDialog.dtd)
|
||||
locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)
|
||||
locale/browser/aboutPrivateBrowsing.properties (%chrome/browser/aboutPrivateBrowsing.properties)
|
||||
|
|
|
@ -1,119 +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/. */
|
||||
|
||||
@import url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
padding: 0 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#errorPageContainer {
|
||||
position: relative;
|
||||
min-width: 320px;
|
||||
max-width: 512px;
|
||||
}
|
||||
|
||||
#errorTitle {
|
||||
background: url("chrome://browser/skin/cert-error.svg") left 0 no-repeat;
|
||||
background-size: 3em;
|
||||
margin-inline-start: -5em;
|
||||
padding-inline-start: 5em;
|
||||
}
|
||||
|
||||
#errorTitle:-moz-dir(rtl) {
|
||||
background-position: right 0;
|
||||
}
|
||||
|
||||
#errorTitleText {
|
||||
border-bottom: 1px solid #C1C1C1;
|
||||
padding-bottom: 0.4em;
|
||||
}
|
||||
|
||||
@media (max-width: 675px) {
|
||||
#errorTitle {
|
||||
padding-top: 0;
|
||||
background-image: none;
|
||||
margin-inline-start: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#buttonContainer {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
#buttonSpacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#certificateErrorDebugInformation {
|
||||
display: none;
|
||||
background-color: var(--in-content-box-background-hover) !important;
|
||||
border-top: 1px solid var(--in-content-border-color);
|
||||
position: absolute;
|
||||
left: 0%;
|
||||
top: 100%;
|
||||
width: 65%;
|
||||
padding: 1em 17.5%;
|
||||
}
|
||||
|
||||
#certificateErrorText {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
#errorCode {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#returnButton {
|
||||
background-color: var(--in-content-primary-button-background);
|
||||
border: none;
|
||||
color: var(--in-content-selected-text);
|
||||
min-width: 250px;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
#returnButton:hover {
|
||||
background-color: var(--in-content-primary-button-background-hover) !important;
|
||||
}
|
||||
|
||||
#returnButton:hover:active {
|
||||
background-color: var(--in-content-primary-button-background-active) !important;
|
||||
}
|
||||
|
||||
#advancedButton {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* Advanced section is hidden via inline styles until the link is clicked */
|
||||
#advancedPanel {
|
||||
background-color: white;
|
||||
color: var(--in-content-text-color);
|
||||
border: 1px lightgray solid;
|
||||
/* Don't use top padding because the default p style has top padding, and it
|
||||
* makes the overall div look uneven */
|
||||
padding: 0 12px 10px;
|
||||
margin-top: 10px;
|
||||
box-shadow: 0 0 4px #ddd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hostname {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#reportCertificateErrorRetry,
|
||||
#certificateErrorReporting,
|
||||
#reportSendingMessage,
|
||||
#reportSentMessage {
|
||||
display: none;
|
||||
}
|
|
@ -40,6 +40,10 @@ ul {
|
|||
-moz-padding-start: 5em;
|
||||
}
|
||||
|
||||
body.certerror #errorTitle {
|
||||
background-image: url("chrome://browser/skin/cert-error.svg");
|
||||
}
|
||||
|
||||
#errorTitleText {
|
||||
border-bottom: 1px solid #C1C1C1;
|
||||
padding-bottom: 0.4em;
|
||||
|
@ -115,36 +119,12 @@ button:disabled {
|
|||
min-width: 150px;
|
||||
}
|
||||
|
||||
#certificateErrorReporting,
|
||||
#reportSentMessage {
|
||||
#certificateErrorReporting {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#weakCryptoAdvanced {
|
||||
display: none;
|
||||
float: right;
|
||||
/* Align with the "Try Again" button */
|
||||
margin-top: 24px;
|
||||
-moz-margin-end: 24px;
|
||||
}
|
||||
|
||||
div#weakCryptoAdvanced a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#weakCryptoAdvanced a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
span.downArrow {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-size: 0.6em;
|
||||
-moz-margin-start: 0.5em;
|
||||
transform: scaleY(0.7);
|
||||
}
|
||||
|
||||
div#weakCryptoAdvancedPanel {
|
||||
#weakCryptoAdvancedPanel,
|
||||
#badCertAdvancedPanel {
|
||||
/* Hidden until the link is clicked */
|
||||
display: none;
|
||||
background-color: white;
|
||||
|
@ -154,6 +134,7 @@ div#weakCryptoAdvancedPanel {
|
|||
padding: 0 12px 12px 12px;
|
||||
box-shadow: 0 0 4px #ddd;
|
||||
font-size: 0.9em;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
#overrideWeakCryptoPanel {
|
||||
|
@ -174,8 +155,38 @@ span#hostname {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#errorCode {
|
||||
body:not(.certerror) #errorCode {
|
||||
color: var(--in-content-page-color);
|
||||
cursor: text;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
body.certerror #errorCode {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#badCertTechnicalInfo {
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#certificateErrorReporting {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#certificateErrorDebugInformation {
|
||||
display: none;
|
||||
background-color: var(--in-content-box-background-hover) !important;
|
||||
border-top: 1px solid var(--in-content-border-color);
|
||||
position: absolute;
|
||||
left: 0%;
|
||||
top: 100%;
|
||||
width: 65%;
|
||||
padding: 1em 17.5%;
|
||||
}
|
||||
|
||||
#certificateErrorText {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
|
|
@ -675,8 +675,15 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
list-style-image: url(chrome://browser/skin/sync-horizontalbar.png);
|
||||
}
|
||||
|
||||
#PanelUI-remotetabs {
|
||||
--panel-ui-sync-illustration-height: 157.5px;
|
||||
}
|
||||
|
||||
.PanelUI-remotetabs-instruction-label,
|
||||
#PanelUI-remotetabs-mobile-promo {
|
||||
/* If you change the margin here, the min-height of the synced tabs panel
|
||||
(e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
|
||||
need adjusting (see bug 1248506) */
|
||||
margin: 15px;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
|
@ -687,6 +694,9 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
/* The boxes with "instructions" get extra top and bottom padding for space
|
||||
around the illustration and buttons */
|
||||
.PanelUI-remotetabs-instruction-box {
|
||||
/* If you change the padding here, the min-height of the synced tabs panel
|
||||
(e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync, etc) may
|
||||
need adjusting (see bug 1248506) */
|
||||
padding-bottom: 30px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
@ -698,6 +708,9 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
is used for buttons in the toolbox overrides. See bug 1238531 for details */
|
||||
color: white !important;
|
||||
border-radius: 2px;
|
||||
/* If you change the margin or padding below, the min-height of the synced tabs
|
||||
panel (e.g. #PanelUI-remotetabs[mainview] #PanelUI-remotetabs-setupsync,
|
||||
etc) may need adjusting (see bug 1248506) */
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
|
@ -721,7 +734,7 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
}
|
||||
|
||||
.fxaSyncIllustration {
|
||||
width: 180px;
|
||||
height: var(--panel-ui-sync-illustration-height);
|
||||
list-style-image: url(chrome://browser/skin/fxa/sync-illustration.svg);
|
||||
}
|
||||
|
||||
|
@ -742,7 +755,12 @@ toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton {
|
|||
#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-reauthsync,
|
||||
#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-nodevicespane,
|
||||
#PanelUI-remotetabs[mainview] #PanelUI-remotetabs-tabsdisabledpane {
|
||||
min-height: 33em;
|
||||
min-height: calc(var(--panel-ui-sync-illustration-height) +
|
||||
20px + /* margin of .PanelUI-remotetabs-prefs-button */
|
||||
16px + /* padding of .PanelUI-remotetabs-prefs-button */
|
||||
30px + /* margin of .PanelUI-remotetabs-instruction-label */
|
||||
30px + 15px + /* padding of .PanelUI-remotetabs-instruction-box */
|
||||
11em);
|
||||
}
|
||||
|
||||
#PanelUI-remotetabs-tabslist > label[itemtype="client"] {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
# be specified once. As a result, the source file paths are relative
|
||||
# to the location of the actual manifest.
|
||||
|
||||
skin/classic/browser/aboutCertError.css (../shared/aboutCertError.css)
|
||||
skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css)
|
||||
* skin/classic/browser/aboutProviderDirectory.css (../shared/aboutProviderDirectory.css)
|
||||
* skin/classic/browser/aboutSessionRestore.css (../shared/aboutSessionRestore.css)
|
||||
|
|
|
@ -3,98 +3,72 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var toolbox;
|
||||
"use strict";
|
||||
|
||||
function test() {
|
||||
addTab("about:blank").then(function() {
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "webconsole").then(testSelect);
|
||||
});
|
||||
}
|
||||
const PAGE_URL = "data:text/html;charset=utf-8,test select events";
|
||||
|
||||
var called = {
|
||||
inspector: false,
|
||||
webconsole: false,
|
||||
styleeditor: false,
|
||||
//jsdebugger: false,
|
||||
}
|
||||
add_task(function*() {
|
||||
let tab = yield addTab(PAGE_URL);
|
||||
|
||||
function testSelect(aToolbox) {
|
||||
toolbox = aToolbox;
|
||||
let toolbox = yield openToolboxForTab(tab, "webconsole", "bottom");
|
||||
yield testSelectEvent("inspector");
|
||||
yield testSelectEvent("webconsole");
|
||||
yield testSelectEvent("styleeditor");
|
||||
yield testSelectEvent("inspector");
|
||||
yield testSelectEvent("webconsole");
|
||||
yield testSelectEvent("styleeditor");
|
||||
|
||||
info("Toolbox fired a `ready` event");
|
||||
yield testToolSelectEvent("inspector");
|
||||
yield testToolSelectEvent("webconsole");
|
||||
yield testToolSelectEvent("styleeditor");
|
||||
yield toolbox.destroy();
|
||||
|
||||
toolbox.on("select", selectCB);
|
||||
toolbox = yield openToolboxForTab(tab, "webconsole", "side");
|
||||
yield testSelectEvent("inspector");
|
||||
yield testSelectEvent("webconsole");
|
||||
yield testSelectEvent("styleeditor");
|
||||
yield testSelectEvent("inspector");
|
||||
yield testSelectEvent("webconsole");
|
||||
yield testSelectEvent("styleeditor");
|
||||
yield toolbox.destroy();
|
||||
|
||||
toolbox.selectTool("inspector");
|
||||
toolbox.selectTool("webconsole");
|
||||
toolbox.selectTool("styleeditor");
|
||||
//toolbox.selectTool("jsdebugger");
|
||||
}
|
||||
toolbox = yield openToolboxForTab(tab, "webconsole", "window");
|
||||
yield testSelectEvent("inspector");
|
||||
yield testSelectEvent("webconsole");
|
||||
yield testSelectEvent("styleeditor");
|
||||
yield testSelectEvent("inspector");
|
||||
yield testSelectEvent("webconsole");
|
||||
yield testSelectEvent("styleeditor");
|
||||
yield toolbox.destroy();
|
||||
|
||||
function selectCB(event, id) {
|
||||
called[id] = true;
|
||||
info("toolbox-select event from " + id);
|
||||
|
||||
for (let tool in called) {
|
||||
if (!called[tool]) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Assert that selecting the given toolId raises a select event
|
||||
* @param {toolId} Id of the tool to test
|
||||
*/
|
||||
function testSelectEvent(toolId) {
|
||||
return new Promise(resolve => {
|
||||
toolbox.once("select", (event, id) => {
|
||||
is(id, toolId, toolId + " selected");
|
||||
resolve();
|
||||
});
|
||||
toolbox.selectTool(toolId);
|
||||
});
|
||||
}
|
||||
|
||||
ok(true, "All the tools fired a 'select event'");
|
||||
toolbox.off("select", selectCB);
|
||||
|
||||
reselect();
|
||||
}
|
||||
|
||||
function reselect() {
|
||||
for (let tool in called) {
|
||||
called[tool] = false;
|
||||
/**
|
||||
* Assert that selecting the given toolId raises its corresponding
|
||||
* selected event
|
||||
* @param {toolId} Id of the tool to test
|
||||
*/
|
||||
function testToolSelectEvent(toolId) {
|
||||
return new Promise(resolve => {
|
||||
toolbox.once(toolId + "-selected", () => {
|
||||
let msg = toolId + " tool selected";
|
||||
is(toolbox.currentToolId, toolId, msg);
|
||||
resolve();
|
||||
});
|
||||
toolbox.selectTool(toolId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
toolbox.once("inspector-selected", function() {
|
||||
tidyUpIfAllCalled("inspector");
|
||||
});
|
||||
|
||||
toolbox.once("webconsole-selected", function() {
|
||||
tidyUpIfAllCalled("webconsole");
|
||||
});
|
||||
|
||||
/*
|
||||
toolbox.once("jsdebugger-selected", function() {
|
||||
tidyUpIfAllCalled("jsdebugger");
|
||||
});
|
||||
*/
|
||||
|
||||
toolbox.once("styleeditor-selected", function() {
|
||||
tidyUpIfAllCalled("styleeditor");
|
||||
});
|
||||
|
||||
toolbox.selectTool("inspector");
|
||||
toolbox.selectTool("webconsole");
|
||||
toolbox.selectTool("styleeditor");
|
||||
//toolbox.selectTool("jsdebugger");
|
||||
}
|
||||
|
||||
function tidyUpIfAllCalled(id) {
|
||||
called[id] = true;
|
||||
info("select event from " + id);
|
||||
|
||||
for (let tool in called) {
|
||||
if (!called[tool]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ok(true, "All the tools fired a {id}-selected event");
|
||||
tidyUp();
|
||||
}
|
||||
|
||||
function tidyUp() {
|
||||
toolbox.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
toolbox = null;
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/* import-globals-from shared-head.js */
|
||||
|
||||
// shared-head.js handles imports, constants, and utility functions
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
|
||||
|
||||
|
|
|
@ -1242,9 +1242,9 @@ Toolbox.prototype = {
|
|||
if (typeof panel.open == "function") {
|
||||
built = panel.open();
|
||||
} else {
|
||||
let deferred = promise.defer();
|
||||
deferred.resolve(panel);
|
||||
built = deferred.promise;
|
||||
let buildDeferred = promise.defer();
|
||||
buildDeferred.resolve(panel);
|
||||
built = buildDeferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1286,12 +1286,32 @@ Toolbox.prototype = {
|
|||
iframe.removeEventListener("DOMContentLoaded", callback);
|
||||
onLoad();
|
||||
};
|
||||
|
||||
iframe.addEventListener("DOMContentLoaded", callback);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Mark all in collection as unselected; and id as selected
|
||||
* @param {string} collection
|
||||
* DOM collection of items
|
||||
* @param {string} id
|
||||
* The Id of the item within the collection to select
|
||||
*/
|
||||
selectSingleNode: function(collection, id) {
|
||||
[...collection].forEach(node => {
|
||||
if (node.id === id) {
|
||||
node.setAttribute("selected", "true");
|
||||
node.setAttribute("aria-selected", "true");
|
||||
} else {
|
||||
node.removeAttribute("selected");
|
||||
node.removeAttribute("aria-selected");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to the tool with the given id
|
||||
*
|
||||
|
@ -1301,15 +1321,8 @@ Toolbox.prototype = {
|
|||
selectTool: function(id) {
|
||||
this.emit("before-select", id);
|
||||
|
||||
let selected = this.doc.querySelector(".devtools-tab[selected]");
|
||||
if (selected) {
|
||||
selected.removeAttribute("selected");
|
||||
selected.setAttribute("aria-selected", "false");
|
||||
}
|
||||
|
||||
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
||||
tab.setAttribute("selected", "true");
|
||||
tab.setAttribute("aria-selected", "true");
|
||||
let tabs = this.doc.querySelectorAll(".devtools-tab");
|
||||
this.selectSingleNode(tabs, "toolbox-tab-" + id);
|
||||
|
||||
// If options is selected, the separator between it and the
|
||||
// command buttons should be hidden.
|
||||
|
@ -1332,7 +1345,7 @@ Toolbox.prototype = {
|
|||
throw new Error("Can't select tool, wait for toolbox 'ready' event");
|
||||
}
|
||||
|
||||
tab = this.doc.getElementById("toolbox-tab-" + id);
|
||||
let tab = this.doc.getElementById("toolbox-tab-" + id);
|
||||
|
||||
if (tab) {
|
||||
if (this.currentToolId) {
|
||||
|
@ -1350,9 +1363,8 @@ Toolbox.prototype = {
|
|||
tabstrip.selectedItem = tab || tabstrip.childNodes[0];
|
||||
|
||||
// and select the right iframe
|
||||
let deck = this.doc.getElementById("toolbox-deck");
|
||||
let panel = this.doc.getElementById("toolbox-panel-" + id);
|
||||
deck.selectedPanel = panel;
|
||||
let toolboxPanels = this.doc.querySelectorAll(".toolbox-panel");
|
||||
this.selectSingleNode(toolboxPanels, "toolbox-panel-" + id);
|
||||
|
||||
this.lastUsedToolId = this.currentToolId;
|
||||
this.currentToolId = id;
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
height set to a small value without flexing to fill up extra
|
||||
space. There must be a flex on both to ensure that the console
|
||||
panel itself is sized properly -->
|
||||
<deck id="toolbox-deck" flex="1000" minheight="75" />
|
||||
<box id="toolbox-deck" flex="1000" minheight="75" />
|
||||
<splitter id="toolbox-console-splitter" class="devtools-horizontal-splitter" hidden="true" />
|
||||
<box minheight="75" flex="1" id="toolbox-panel-webconsole" collapsed="true" />
|
||||
</vbox>
|
||||
|
|
|
@ -447,6 +447,25 @@ InspectorPanel.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Can a new HTML element be inserted into the currently selected element?
|
||||
* @return {Boolean}
|
||||
*/
|
||||
canAddHTMLChild: function() {
|
||||
let selection = this.selection;
|
||||
|
||||
// Don't allow to insert an element into these elements. This should only
|
||||
// contain elements where walker.insertAdjacentHTML has no effect.
|
||||
let invalidTagNames = ["html", "iframe"];
|
||||
|
||||
return selection.isHTMLNode() &&
|
||||
selection.isElementNode() &&
|
||||
!selection.isPseudoElementNode() &&
|
||||
!selection.isAnonymousNode() &&
|
||||
invalidTagNames.indexOf(
|
||||
selection.nodeFront.nodeName.toLowerCase()) === -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* When a new node is selected.
|
||||
*/
|
||||
|
@ -459,6 +478,15 @@ InspectorPanel.prototype = {
|
|||
// client know.
|
||||
let selection = this.selection.nodeFront;
|
||||
|
||||
// Update the state of the add button in the toolbar depending on the
|
||||
// current selection.
|
||||
let btn = this.panelDoc.querySelector("#inspector-element-add-button");
|
||||
if (this.canAddHTMLChild()) {
|
||||
btn.removeAttribute("disabled");
|
||||
} else {
|
||||
btn.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
// On any new selection made by the user, store the unique css selector
|
||||
// of the selected node so it can be restored after reload of the same page
|
||||
if (this.canGetUniqueSelector &&
|
||||
|
@ -705,6 +733,14 @@ InspectorPanel.prototype = {
|
|||
deleteNode.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
// Disable add item if needed
|
||||
let addNode = this.panelDoc.getElementById("node-menu-add");
|
||||
if (this.canAddHTMLChild()) {
|
||||
addNode.removeAttribute("disabled");
|
||||
} else {
|
||||
addNode.setAttribute("disabled", "true");
|
||||
}
|
||||
|
||||
// Disable / enable "Copy Unique Selector", "Copy inner HTML",
|
||||
// "Copy outer HTML", "Scroll Into View" & "Screenshot Node" as appropriate
|
||||
let unique = this.panelDoc.getElementById("node-menu-copyuniqueselector");
|
||||
|
@ -1012,6 +1048,27 @@ InspectorPanel.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new node as the last child of the current selection, expand the
|
||||
* parent and select the new node.
|
||||
*/
|
||||
addNode: Task.async(function*() {
|
||||
let root = this.selection.nodeFront;
|
||||
if (!this.canAddHTMLChild(root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let html = "<div></div>";
|
||||
|
||||
// Insert the html and expect a childList markup mutation.
|
||||
let onMutations = this.once("markupmutation");
|
||||
let {nodes} = yield this.walker.insertAdjacentHTML(root, "beforeEnd", html);
|
||||
yield onMutations;
|
||||
|
||||
// Select the new node (this will auto-expand its parent).
|
||||
this.selection.setNodeFront(nodes[0]);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Toggle a pseudo class.
|
||||
*/
|
||||
|
|
|
@ -110,6 +110,10 @@
|
|||
<menuitem id="node-menu-screenshotnode"
|
||||
label="&inspectorScreenshotNode.label;"
|
||||
oncommand="inspector.screenshotNode()" />
|
||||
<menuitem id="node-menu-add"
|
||||
label="&inspectorAddNode.label;"
|
||||
accesskey="&inspectorAddNode.accesskey;"
|
||||
oncommand="inspector.addNode()"/>
|
||||
<menuitem id="node-menu-duplicatenode"
|
||||
label="&inspectorDuplicateNode.label;"
|
||||
oncommand="inspector.duplicateNode()"/>
|
||||
|
@ -157,6 +161,10 @@
|
|||
<toolbar id="inspector-toolbar"
|
||||
class="devtools-toolbar"
|
||||
nowindowdrag="true">
|
||||
<toolbarbutton id="inspector-element-add-button"
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="&inspectorAddNode.label;"
|
||||
oncommand="inspector.addNode()" />
|
||||
<spacer flex="1"/>
|
||||
<box id="inspector-searchlabel" />
|
||||
<textbox id="inspector-searchbox"
|
||||
|
|
|
@ -72,6 +72,7 @@ support-files =
|
|||
[browser_rules_completion-new-property_02.js]
|
||||
[browser_rules_completion-new-property_03.js]
|
||||
[browser_rules_completion-new-property_04.js]
|
||||
[browser_rules_completion-new-property_multiline.js]
|
||||
[browser_rules_computed-lists_01.js]
|
||||
[browser_rules_computed-lists_02.js]
|
||||
[browser_rules_completion-popup-hidden-after-navigation.js]
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the behaviour of the CSS autocomplete for CSS value displayed on
|
||||
// multiple lines. Expected behavior is:
|
||||
// - UP/DOWN should navigate in the input and not increment/decrement numbers
|
||||
// - typing a new value should still trigger the autocomplete
|
||||
// - UP/DOWN when the autocomplete popup is displayed should cycle through
|
||||
// suggestions
|
||||
|
||||
const LONG_CSS_VALUE =
|
||||
"transparent linear-gradient(0deg, blue 0%, white 5%, red 10%, blue 15%, " +
|
||||
"white 20%, red 25%, blue 30%, white 35%, red 40%, blue 45%, white 50%, " +
|
||||
"red 55%, blue 60%, white 65%, red 70%, blue 75%, white 80%, red 85%, " +
|
||||
"blue 90%, white 95% ) repeat scroll 0% 0%";
|
||||
|
||||
const EXPECTED_CSS_VALUE = LONG_CSS_VALUE.replace("95%", "95%, red");
|
||||
|
||||
const TEST_URI =
|
||||
`<style>
|
||||
.title {
|
||||
background: ${LONG_CSS_VALUE};
|
||||
}
|
||||
</style>
|
||||
<h1 class=title>Header</h1>`;
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||
let { inspector, view} = yield openRuleView();
|
||||
|
||||
info("Selecting the test node");
|
||||
yield selectNode("h1", inspector);
|
||||
|
||||
info("Focusing the property editable field");
|
||||
let rule = getRuleViewRuleEditor(view, 1).rule;
|
||||
let prop = rule.textProps[0];
|
||||
|
||||
info("Focusing the css property editable value");
|
||||
let rect = prop.editor.valueSpan.getBoundingClientRect();
|
||||
let editor = yield focusEditableField(view, prop.editor.valueSpan,
|
||||
rect.width / 2, rect.height / 2);
|
||||
|
||||
info("Moving the caret next to a number");
|
||||
let pos = editor.input.value.indexOf("0deg") + 1;
|
||||
editor.input.setSelectionRange(pos, pos);
|
||||
is(editor.input.value[editor.input.selectionStart - 1], "0",
|
||||
"Input caret is after a 0");
|
||||
|
||||
info("Check that UP/DOWN navigates in the input, even when next to a number");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, view.styleWindow);
|
||||
ok(editor.input.selectionStart != pos, "Input caret moved");
|
||||
is(editor.input.value, LONG_CSS_VALUE, "Input value was not decremented.");
|
||||
|
||||
info("Move the caret to the end of the gradient definition.");
|
||||
pos = editor.input.value.indexOf("95%") + 3;
|
||||
editor.input.setSelectionRange(pos, pos);
|
||||
|
||||
info("Sending \", re\" to the editable field.");
|
||||
for (let key of ", re") {
|
||||
yield synthesizeKeyForAutocomplete(key, editor, view.styleWindow);
|
||||
}
|
||||
|
||||
info("Check the autocomplete can still be displayed.");
|
||||
ok(editor.popup && editor.popup.isOpen, "Autocomplete popup is displayed.");
|
||||
is(editor.popup.selectedIndex, 0,
|
||||
"Autocomplete has an item selected by default");
|
||||
|
||||
let item = editor.popup.getItemAtIndex(editor.popup.selectedIndex);
|
||||
is(item.label, "rebeccapurple",
|
||||
"Check autocomplete displays expected value.");
|
||||
|
||||
info("Check autocomplete suggestions can be cycled using UP/DOWN arrows.");
|
||||
|
||||
yield synthesizeKeyForAutocomplete("VK_DOWN", editor, view.styleWindow);
|
||||
ok(editor.popup.selectedIndex, 1, "Using DOWN cycles autocomplete values.");
|
||||
yield synthesizeKeyForAutocomplete("VK_DOWN", editor, view.styleWindow);
|
||||
ok(editor.popup.selectedIndex, 2, "Using DOWN cycles autocomplete values.");
|
||||
yield synthesizeKeyForAutocomplete("VK_UP", editor, view.styleWindow);
|
||||
is(editor.popup.selectedIndex, 1, "Using UP cycles autocomplete values.");
|
||||
item = editor.popup.getItemAtIndex(editor.popup.selectedIndex);
|
||||
is(item.label, "red", "Check autocomplete displays expected value.");
|
||||
|
||||
info("Select the background-color suggestion with a mouse click.");
|
||||
let onRuleviewChanged = view.once("ruleview-changed");
|
||||
let onInputFocus = once(editor.input, "focus", true);
|
||||
let node = editor.popup._list.childNodes[editor.popup.selectedIndex];
|
||||
EventUtils.synthesizeMouseAtCenter(node, {}, view.styleWindow);
|
||||
yield onInputFocus;
|
||||
yield onRuleviewChanged;
|
||||
|
||||
is(editor.input.value, EXPECTED_CSS_VALUE,
|
||||
"Input value correctly autocompleted");
|
||||
|
||||
info("Press ESCAPE to leave the input.");
|
||||
onRuleviewChanged = view.once("ruleview-changed");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
|
||||
yield onRuleviewChanged;
|
||||
});
|
||||
|
||||
/**
|
||||
* Send the provided key to the currently focused input of the provided window.
|
||||
* Wait for the editor to emit "after-suggest" to make sure the autocompletion
|
||||
* process is finished.
|
||||
*
|
||||
* @param {String} key
|
||||
* The key to send to the input.
|
||||
* @param {InplaceEditor} editor
|
||||
* The inplace editor which owns the focused input.
|
||||
* @param {Window} win
|
||||
* Window in which the key event will be dispatched.
|
||||
*/
|
||||
function* synthesizeKeyForAutocomplete(key, editor, win) {
|
||||
let onSuggest = editor.once("after-suggest");
|
||||
EventUtils.synthesizeKey(key, {}, win);
|
||||
yield onSuggest;
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_inspector_add_node.html
|
||||
doc_inspector_breadcrumbs.html
|
||||
doc_inspector_delete-selected-node-01.html
|
||||
doc_inspector_delete-selected-node-02.html
|
||||
|
@ -36,6 +37,9 @@ support-files =
|
|||
!/devtools/client/shared/test/test-actor.js
|
||||
!/devtools/client/shared/test/test-actor-registry.js
|
||||
|
||||
[browser_inspector_addNode_01.js]
|
||||
[browser_inspector_addNode_02.js]
|
||||
[browser_inspector_addNode_03.js]
|
||||
[browser_inspector_breadcrumbs.js]
|
||||
[browser_inspector_breadcrumbs_highlight_hover.js]
|
||||
[browser_inspector_breadcrumbs_keybinding.js]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the add node button and context menu items are present in the UI.
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,<h1>Add node</h1>";
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield openInspectorForURL(TEST_URL);
|
||||
let {panelDoc} = inspector;
|
||||
|
||||
let toolbarButton =
|
||||
panelDoc.querySelector("#inspector-toolbar #inspector-element-add-button");
|
||||
let menuItem =
|
||||
panelDoc.querySelector("#inspector-node-popup #node-menu-add");
|
||||
|
||||
ok(toolbarButton, "The add button is in the toolbar");
|
||||
ok(menuItem, "The item is in the menu");
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the add node button and context menu items have the right state
|
||||
// depending on the current selection.
|
||||
|
||||
const TEST_URL = URL_ROOT + "doc_inspector_add_node.html";
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield openInspectorForURL(TEST_URL);
|
||||
|
||||
info("Select the DOCTYPE element");
|
||||
let {nodes} = yield inspector.walker.children(inspector.walker.rootNode);
|
||||
yield selectNode(nodes[0], inspector);
|
||||
assertState(false, inspector,
|
||||
"The button and item are disabled on DOCTYPE");
|
||||
|
||||
info("Select the ::before pseudo-element");
|
||||
let body = yield getNodeFront("body", inspector);
|
||||
({nodes} = yield inspector.walker.children(body));
|
||||
yield selectNode(nodes[0], inspector);
|
||||
assertState(false, inspector,
|
||||
"The button and item are disabled on a pseudo-element");
|
||||
|
||||
info("Select the svg element");
|
||||
yield selectNode("svg", inspector);
|
||||
assertState(false, inspector,
|
||||
"The button and item are disabled on a SVG element");
|
||||
|
||||
info("Select the div#foo element");
|
||||
yield selectNode("#foo", inspector);
|
||||
assertState(true, inspector,
|
||||
"The button and item are enabled on a DIV element");
|
||||
|
||||
info("Select the documentElement element (html)");
|
||||
yield selectNode("html", inspector);
|
||||
assertState(false, inspector,
|
||||
"The button and item are disabled on the documentElement");
|
||||
|
||||
info("Select the iframe element");
|
||||
yield selectNode("iframe", inspector);
|
||||
assertState(false, inspector,
|
||||
"The button and item are disabled on an IFRAME element");
|
||||
});
|
||||
|
||||
function assertState(isEnabled, inspector, desc) {
|
||||
let doc = inspector.panelDoc;
|
||||
let btn = doc.querySelector("#inspector-element-add-button");
|
||||
let item = doc.querySelector("#node-menu-add");
|
||||
|
||||
// Force an update of the context menu to make sure menu items are updated
|
||||
// according to the current selection. This normally happens when the menu is
|
||||
// opened, but for the sake of this test's simplicity, we directly call the
|
||||
// private update function instead.
|
||||
inspector._setupNodeMenu({target: {}});
|
||||
|
||||
is(!btn.hasAttribute("disabled"), isEnabled, desc);
|
||||
is(!item.hasAttribute("disabled"), isEnabled, desc);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that adding nodes does work as expected: the parent gets expanded and
|
||||
// the new node gets selected.
|
||||
|
||||
const TEST_URL = URL_ROOT + "doc_inspector_add_node.html";
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield openInspectorForURL(TEST_URL);
|
||||
|
||||
info("Adding in element that has no children and is collapsed");
|
||||
let parentNode = yield getNodeFront("#foo", inspector);
|
||||
yield selectNode(parentNode, inspector);
|
||||
yield testAddNode(parentNode, inspector);
|
||||
|
||||
info("Adding in element with children but that has not been expanded yet");
|
||||
parentNode = yield getNodeFront("#bar", inspector);
|
||||
yield selectNode(parentNode, inspector);
|
||||
yield testAddNode(parentNode, inspector);
|
||||
|
||||
info("Adding in element with children that has been expanded then collapsed");
|
||||
// Select again #bar and collapse it.
|
||||
parentNode = yield getNodeFront("#bar", inspector);
|
||||
yield selectNode(parentNode, inspector);
|
||||
collapseNode(parentNode, inspector);
|
||||
yield testAddNode(parentNode, inspector);
|
||||
|
||||
info("Adding in element with children that is expanded");
|
||||
parentNode = yield getNodeFront("#bar", inspector);
|
||||
yield selectNode(parentNode, inspector);
|
||||
yield testAddNode(parentNode, inspector);
|
||||
});
|
||||
|
||||
function* testAddNode(parentNode, inspector) {
|
||||
let btn = inspector.panelDoc.querySelector("#inspector-element-add-button");
|
||||
|
||||
info("Clicking on the 'add node' button and expecting a markup mutation");
|
||||
let onMutation = inspector.once("markupmutation");
|
||||
btn.click();
|
||||
let mutations = yield onMutation;
|
||||
|
||||
info("Expecting an inspector-updated event right after the mutation event "+
|
||||
"to wait for the new node selection");
|
||||
yield inspector.once("inspector-updated");
|
||||
|
||||
is(mutations.length, 1, "There is one mutation only");
|
||||
is(mutations[0].added.length, 1, "There is one new node only");
|
||||
|
||||
let newNode = mutations[0].added[0];
|
||||
|
||||
is(newNode, inspector.selection.nodeFront,
|
||||
"The new node is selected");
|
||||
|
||||
ok(inspector.markup.getContainer(parentNode).expanded,
|
||||
"The parent node is now expanded");
|
||||
|
||||
is(inspector.selection.nodeFront.parentNode(), parentNode,
|
||||
"The new node is inside the right parent");
|
||||
}
|
||||
|
||||
function collapseNode(node, inspector) {
|
||||
let container = inspector.markup.getContainer(node);
|
||||
container.setExpanded(false);
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Add elements tests</title>
|
||||
<style>
|
||||
body::before {
|
||||
content: "pseudo-element";
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="foo"></div>
|
||||
<svg>
|
||||
<rect x="0" y="0" width="100" height="50"></rect>
|
||||
</svg>
|
||||
<div id="bar">
|
||||
<div id="baz"></div>
|
||||
</div>
|
||||
<iframe src="data:text/html;charset=utf-8,Test iframe content"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -155,3 +155,9 @@
|
|||
shown in the inspector contextual-menu for the item that lets users
|
||||
duplicate the currently selected node. -->
|
||||
<!ENTITY inspectorDuplicateNode.label "Duplicate Node">
|
||||
|
||||
<!-- LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
|
||||
the inspector toolbar for the button that lets users add elements to the
|
||||
DOM (as children of the currently selected element). -->
|
||||
<!ENTITY inspectorAddNode.label "Create New Node">
|
||||
<!ENTITY inspectorAddNode.accesskey "C">
|
||||
|
|
|
@ -31,10 +31,22 @@ let App = createClass({
|
|||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
},
|
||||
|
||||
onBrowserMounted() {
|
||||
window.postMessage({ type: "browser-mounted" }, "*");
|
||||
},
|
||||
|
||||
onChangeViewportDevice(id, device) {
|
||||
this.props.dispatch(changeDevice(id, device));
|
||||
},
|
||||
|
||||
onContentResize({ width, height }) {
|
||||
window.postMessage({
|
||||
type: "content-resize",
|
||||
width,
|
||||
height,
|
||||
}, "*");
|
||||
},
|
||||
|
||||
onExit() {
|
||||
window.postMessage({ type: "exit" }, "*");
|
||||
},
|
||||
|
@ -60,7 +72,9 @@ let App = createClass({
|
|||
} = this.props;
|
||||
|
||||
let {
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onExit,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
|
@ -81,7 +95,9 @@ let App = createClass({
|
|||
location,
|
||||
screenshot,
|
||||
viewports,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onRotateViewport,
|
||||
onResizeViewport,
|
||||
})
|
||||
|
|
|
@ -2,12 +2,18 @@
|
|||
* 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/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM: dom, createClass, PropTypes, addons } =
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { getToplevelWindow } = require("sdk/window/utils");
|
||||
const { DOM: dom, createClass, addons, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
const { waitForMessage } = require("../utils/e10s");
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
|
@ -15,32 +21,96 @@ module.exports = createClass({
|
|||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
/**
|
||||
* This component is not allowed to depend directly on frequently changing
|
||||
* data (width, height) due to the use of `dangerouslySetInnerHTML` below.
|
||||
* Any changes in props will cause the <iframe> to be removed and added again,
|
||||
* throwing away the current state of the page.
|
||||
*/
|
||||
propTypes: {
|
||||
location: Types.location.isRequired,
|
||||
width: Types.viewport.width.isRequired,
|
||||
height: Types.viewport.height.isRequired,
|
||||
isResizing: PropTypes.bool.isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
/**
|
||||
* Once the browser element has mounted, load the frame script and enable
|
||||
* various features, like floating scrollbars.
|
||||
*/
|
||||
componentDidMount: Task.async(function*() {
|
||||
let { onContentResize } = this;
|
||||
let browser = this.refs.browserContainer.querySelector("iframe.browser");
|
||||
let mm = browser.frameLoader.messageManager;
|
||||
|
||||
// Notify tests when the content has received a resize event. This is not
|
||||
// quite the same timing as when we _set_ a new size around the browser,
|
||||
// since it still needs to do async work before the content is actually
|
||||
// resized to match.
|
||||
mm.addMessageListener("ResponsiveMode:OnContentResize", onContentResize);
|
||||
|
||||
let ready = waitForMessage(mm, "ResponsiveMode:ChildScriptReady");
|
||||
mm.loadFrameScript("resource://devtools/client/responsivedesign/" +
|
||||
"responsivedesign-child.js", true);
|
||||
yield ready;
|
||||
|
||||
let browserWindow = getToplevelWindow(window);
|
||||
let requiresFloatingScrollbars =
|
||||
!browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
|
||||
let started = waitForMessage(mm, "ResponsiveMode:Start:Done");
|
||||
mm.sendAsyncMessage("ResponsiveMode:Start", {
|
||||
requiresFloatingScrollbars,
|
||||
// Tests expect events on resize to yield on various size changes
|
||||
notifyOnResize: DevToolsUtils.testing,
|
||||
});
|
||||
yield started;
|
||||
|
||||
// manager.js waits for this signal before allowing browser tests to start
|
||||
this.props.onBrowserMounted();
|
||||
}),
|
||||
|
||||
componentWillUnmount() {
|
||||
let { onContentResize } = this;
|
||||
let browser = this.refs.browserContainer.querySelector("iframe.browser");
|
||||
let mm = browser.frameLoader.messageManager;
|
||||
mm.removeMessageListener("ResponsiveMode:OnContentResize", onContentResize);
|
||||
mm.sendAsyncMessage("ResponsiveMode:Stop");
|
||||
},
|
||||
|
||||
onContentResize(msg) {
|
||||
let { onContentResize } = this.props;
|
||||
let { width, height } = msg.data;
|
||||
onContentResize({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
location,
|
||||
width,
|
||||
height,
|
||||
isResizing,
|
||||
} = this.props;
|
||||
|
||||
let className = "browser";
|
||||
if (isResizing) {
|
||||
className += " resizing";
|
||||
}
|
||||
// innerHTML expects & to be an HTML entity
|
||||
location = location.replace(/&/g, "&");
|
||||
|
||||
return dom.iframe(
|
||||
return dom.div(
|
||||
{
|
||||
className,
|
||||
src: location,
|
||||
width,
|
||||
height,
|
||||
ref: "browserContainer",
|
||||
className: "browser-container",
|
||||
|
||||
/**
|
||||
* React uses a whitelist for attributes, so we need some way to set
|
||||
* attributes it does not know about, such as @mozbrowser. If this were
|
||||
* the only issue, we could use componentDidMount or ref: node => {} to
|
||||
* set the atttibutes. In the case of @remote, the attribute must be set
|
||||
* before the element is added to the DOM to have any effect, which we
|
||||
* are able to do with this approach.
|
||||
*/
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: `<iframe class="browser" mozbrowser="true" remote="true"
|
||||
noisolation="true" src="${location}"
|
||||
width="100%" height="100%"></iframe>`
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
|
|
@ -26,7 +26,9 @@ module.exports = createClass({
|
|||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
},
|
||||
|
@ -115,17 +117,23 @@ module.exports = createClass({
|
|||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
} = this.props;
|
||||
|
||||
let resizeHandleClass = "viewport-resize-handle";
|
||||
|
||||
if (screenshot.isCapturing) {
|
||||
resizeHandleClass += " hidden";
|
||||
}
|
||||
|
||||
let contentClass = "viewport-content";
|
||||
if (this.state.isResizing) {
|
||||
contentClass += " resizing";
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "resizable-viewport",
|
||||
|
@ -137,12 +145,20 @@ module.exports = createClass({
|
|||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
}),
|
||||
Browser({
|
||||
location,
|
||||
width: viewport.width,
|
||||
height: viewport.height,
|
||||
isResizing: this.state.isResizing
|
||||
}),
|
||||
dom.div(
|
||||
{
|
||||
className: contentClass,
|
||||
style: {
|
||||
width: viewport.width + "px",
|
||||
height: viewport.height + "px",
|
||||
},
|
||||
},
|
||||
Browser({
|
||||
location,
|
||||
onBrowserMounted,
|
||||
onContentResize,
|
||||
})
|
||||
),
|
||||
dom.div({
|
||||
className: resizeHandleClass,
|
||||
onMouseDown: this.onResizeStart,
|
||||
|
|
|
@ -20,7 +20,9 @@ module.exports = createClass({
|
|||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
},
|
||||
|
@ -58,6 +60,8 @@ module.exports = createClass({
|
|||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onContentResize,
|
||||
onBrowserMounted,
|
||||
} = this.props;
|
||||
|
||||
let {
|
||||
|
@ -75,7 +79,9 @@ module.exports = createClass({
|
|||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
}),
|
||||
|
|
|
@ -19,7 +19,9 @@ module.exports = createClass({
|
|||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
},
|
||||
|
@ -30,7 +32,9 @@ module.exports = createClass({
|
|||
location,
|
||||
screenshot,
|
||||
viewports,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
} = this.props;
|
||||
|
@ -46,7 +50,9 @@ module.exports = createClass({
|
|||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
});
|
||||
|
|
|
@ -200,19 +200,28 @@ body {
|
|||
background-image: url("./images/rotate-viewport.svg");
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Content
|
||||
*/
|
||||
|
||||
.viewport-content.resizing {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Browser
|
||||
*/
|
||||
|
||||
.browser-container {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.browser {
|
||||
display: block;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.browser.resizing {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Resize Handles
|
||||
*/
|
||||
|
|
|
@ -15,6 +15,7 @@ const { require } = BrowserLoader({
|
|||
});
|
||||
const { GetDevices } = require("devtools/client/shared/devices");
|
||||
const Telemetry = require("devtools/client/shared/telemetry");
|
||||
const { loadSheet } = require("sdk/stylesheet/utils");
|
||||
|
||||
const { createFactory, createElement } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
@ -25,8 +26,7 @@ const App = createFactory(require("./app"));
|
|||
const Store = require("./store");
|
||||
const { addDevice, addDeviceType } = require("./actions/devices");
|
||||
const { changeLocation } = require("./actions/location");
|
||||
const { addViewport } = require("./actions/viewports");
|
||||
const { loadSheet } = require("sdk/stylesheet/utils");
|
||||
const { addViewport, resizeViewport } = require("./actions/viewports");
|
||||
|
||||
let bootstrap = {
|
||||
|
||||
|
@ -43,7 +43,6 @@ let bootstrap = {
|
|||
this.telemetry.toolOpened("responsive");
|
||||
let store = this.store = Store();
|
||||
let provider = createElement(Provider, { store }, App());
|
||||
|
||||
ReactDOM.render(provider, document.querySelector("#root"));
|
||||
this.initDevices();
|
||||
window.postMessage({ type: "init" }, "*");
|
||||
|
@ -115,3 +114,32 @@ window.addInitialViewport = contentURI => {
|
|||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by manager.js when tests want to check the viewport size.
|
||||
*/
|
||||
window.getViewportSize = () => {
|
||||
let { width, height } = bootstrap.store.getState().viewports[0];
|
||||
return { width, height };
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by manager.js to set viewport size from GCLI.
|
||||
*/
|
||||
window.setViewportSize = (width, height) => {
|
||||
try {
|
||||
bootstrap.dispatch(resizeViewport(0, width, height));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by manager.js when tests want to use the viewport's message manager.
|
||||
* It is packed into an object because this is the format most easily usable
|
||||
* with ContentTask.spawn().
|
||||
*/
|
||||
window.getViewportMessageManager = () => {
|
||||
let { messageManager } = document.querySelector("iframe.browser").frameLoader;
|
||||
return { messageManager };
|
||||
};
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Ci, Cr } = require("chrome");
|
||||
const promise = require("promise");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
|
||||
|
@ -69,14 +71,14 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
|
|||
* @return Promise
|
||||
* Resolved (with no value) when closing is complete.
|
||||
*/
|
||||
closeIfNeeded(window, tab) {
|
||||
closeIfNeeded: Task.async(function*(window, tab) {
|
||||
if (this.isActiveForTab(tab)) {
|
||||
this.activeTabs.get(tab).destroy();
|
||||
yield this.activeTabs.get(tab).destroy();
|
||||
this.activeTabs.delete(tab);
|
||||
this.emit("off", { tab });
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns true if responsive UI is active for a given tab.
|
||||
|
@ -118,8 +120,7 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
|
|||
switch (command) {
|
||||
case "resize to":
|
||||
completed = this.openIfNeeded(window, tab);
|
||||
// TODO: Probably the wrong API
|
||||
this.activeTabs.get(tab).setSize(args.width, args.height);
|
||||
this.activeTabs.get(tab).setViewportSize(args.width, args.height);
|
||||
break;
|
||||
case "resize on":
|
||||
completed = this.openIfNeeded(window, tab);
|
||||
|
@ -196,19 +197,23 @@ ResponsiveUI.prototype = {
|
|||
tabBrowser.loadURI(TOOL_URL);
|
||||
yield tabLoaded(this.tab);
|
||||
let toolWindow = this.toolWindow = tabBrowser.contentWindow;
|
||||
toolWindow.addEventListener("message", this);
|
||||
yield waitForMessage(toolWindow, "init");
|
||||
toolWindow.addInitialViewport(contentURI);
|
||||
toolWindow.addEventListener("message", this);
|
||||
yield waitForMessage(toolWindow, "browser-mounted");
|
||||
}),
|
||||
|
||||
destroy() {
|
||||
destroy: Task.async(function*() {
|
||||
let tabBrowser = this.tab.linkedBrowser;
|
||||
tabBrowser.goBack();
|
||||
this.window = null;
|
||||
let browserWindow = this.browserWindow;
|
||||
this.browserWindow = null;
|
||||
this.tab = null;
|
||||
this.inited = null;
|
||||
this.toolWindow = null;
|
||||
},
|
||||
let loaded = waitForDocLoadComplete(browserWindow.gBrowser);
|
||||
tabBrowser.goBack();
|
||||
yield loaded;
|
||||
}),
|
||||
|
||||
handleEvent(event) {
|
||||
let { tab, window } = this;
|
||||
|
@ -219,14 +224,37 @@ ResponsiveUI.prototype = {
|
|||
}
|
||||
|
||||
switch (event.data.type) {
|
||||
case "content-resize":
|
||||
let { width, height } = event.data;
|
||||
this.emit("content-resize", {
|
||||
width,
|
||||
height,
|
||||
});
|
||||
break;
|
||||
case "exit":
|
||||
toolWindow.removeEventListener(event.type, this);
|
||||
ResponsiveUIManager.closeIfNeeded(window, tab);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
getViewportSize() {
|
||||
return this.toolWindow.getViewportSize();
|
||||
},
|
||||
|
||||
setViewportSize: Task.async(function*(width, height) {
|
||||
yield this.inited;
|
||||
this.toolWindow.setViewportSize(width, height);
|
||||
}),
|
||||
|
||||
getViewportMessageManager() {
|
||||
return this.toolWindow.getViewportMessageManager();
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
EventEmitter.decorate(ResponsiveUI.prototype);
|
||||
|
||||
function waitForMessage(win, type) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
|
@ -257,3 +285,27 @@ function tabLoaded(tab) {
|
|||
tab.linkedBrowser.addEventListener("load", handle, true);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the next load to complete in the current browser.
|
||||
*/
|
||||
function waitForDocLoadComplete(gBrowser) {
|
||||
let deferred = promise.defer();
|
||||
let progressListener = {
|
||||
onStateChange: function(webProgress, req, flags, status) {
|
||||
let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
|
||||
Ci.nsIWebProgressListener.STATE_STOP;
|
||||
|
||||
// When a load needs to be retargetted to a new process it is cancelled
|
||||
// with NS_BINDING_ABORTED so ignore that case
|
||||
if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
|
||||
gBrowser.removeProgressListener(progressListener);
|
||||
deferred.resolve();
|
||||
}
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
gBrowser.addProgressListener(progressListener);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
subsuite = devtools
|
||||
skip-if = !e10s && debug # Bug 1262416 - Intermittent crash at MessageLoop::DeletePendingTasks
|
||||
support-files =
|
||||
devices.json
|
||||
head.js
|
||||
!/devtools/client/commandline/test/helpers.js
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
!/devtools/client/framework/test/shared-redux-head.js
|
||||
|
||||
[browser_device_width.js]
|
||||
skip-if = (!e10s && debug) || (e10s && debug && os == "win") # Bug 1262432 - crashes at nsLayoutUtils::HasDisplayPort(content), Bug 1262416 - Intermittent crash at MessageLoop::DeletePendingTasks
|
||||
[browser_exit_button.js]
|
||||
[browser_resize_cmd.js]
|
||||
[browser_screenshot_button.js]
|
||||
[browser_viewport_basics.js]
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "about:logo";
|
||||
|
||||
addRDMTask(TEST_URL, function*({ ui, manager }) {
|
||||
ok(ui, "An instance of the RDM should be attached to the tab.");
|
||||
yield setViewportSize(ui, manager, 110, 500);
|
||||
|
||||
info("Checking initial width/height properties.");
|
||||
yield doInitialChecks(ui);
|
||||
|
||||
info("Changing the RDM size");
|
||||
yield setViewportSize(ui, manager, 90, 500);
|
||||
|
||||
info("Checking for screen props");
|
||||
yield checkScreenProps(ui);
|
||||
|
||||
info("Setting docShell.deviceSizeIsPageSize to false");
|
||||
yield ContentTask.spawn(ui.getViewportMessageManager(), {}, function*() {
|
||||
let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
docShell.deviceSizeIsPageSize = false;
|
||||
});
|
||||
|
||||
info("Checking for screen props once again.");
|
||||
yield checkScreenProps2(ui);
|
||||
});
|
||||
|
||||
function* doInitialChecks(ui) {
|
||||
let { innerWidth, matchesMedia } = yield grabContentInfo(ui);
|
||||
is(innerWidth, 110, "initial width should be 110px");
|
||||
ok(!matchesMedia, "media query shouldn't match.");
|
||||
}
|
||||
|
||||
function* checkScreenProps(ui) {
|
||||
let { matchesMedia, screen } = yield grabContentInfo(ui);
|
||||
ok(matchesMedia, "media query should match");
|
||||
isnot(window.screen.width, screen.width,
|
||||
"screen.width should not be the size of the screen.");
|
||||
is(screen.width, 90, "screen.width should be the page width");
|
||||
is(screen.height, 500, "screen.height should be the page height");
|
||||
}
|
||||
|
||||
function* checkScreenProps2(ui) {
|
||||
let { matchesMedia, screen } = yield grabContentInfo(ui);
|
||||
ok(!matchesMedia, "media query should be re-evaluated.");
|
||||
is(window.screen.width, screen.width,
|
||||
"screen.width should be the size of the screen.");
|
||||
}
|
||||
|
||||
function grabContentInfo(ui) {
|
||||
return ContentTask.spawn(ui.getViewportMessageManager(), {}, function*() {
|
||||
return {
|
||||
screen: {
|
||||
width: content.screen.width,
|
||||
height: content.screen.height
|
||||
},
|
||||
innerWidth: content.innerWidth,
|
||||
matchesMedia: content.matchMedia("(max-device-width:100px)").matches
|
||||
};
|
||||
});
|
||||
}
|
|
@ -14,10 +14,9 @@ addRDMTask(TEST_URL, function*({ ui, manager }) {
|
|||
// Wait until the viewport has been added
|
||||
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||
|
||||
let browser = toolWindow.document.querySelector(".browser");
|
||||
let exitButton = toolWindow.document.getElementById("global-exit-button");
|
||||
|
||||
yield waitForFrameLoad(browser, TEST_URL);
|
||||
yield waitForFrameLoad(ui, TEST_URL);
|
||||
|
||||
ok(manager.isActiveForTab(ui.tab),
|
||||
"Responsive Design Mode active for the tab");
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global ResponsiveUIManager */
|
||||
/* eslint key-spacing: 0 */
|
||||
|
||||
add_task(function*() {
|
||||
let manager = ResponsiveUIManager;
|
||||
let done;
|
||||
|
||||
function isOpen() {
|
||||
return ResponsiveUIManager.isActiveForTab(gBrowser.selectedTab);
|
||||
}
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,hi";
|
||||
yield helpers.addTabWithToolbar(TEST_URL, (options) => {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
return helpers.setInput(options, "resize toggle");
|
||||
},
|
||||
check: {
|
||||
input: "resize toggle",
|
||||
hints: "",
|
||||
markup: "VVVVVVVVVVVVV",
|
||||
status: "VALID"
|
||||
},
|
||||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(isOpen(), "responsive mode is open");
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "off");
|
||||
return helpers.setInput(options, "resize toggle");
|
||||
},
|
||||
check: {
|
||||
input: "resize toggle",
|
||||
hints: "",
|
||||
markup: "VVVVVVVVVVVVV",
|
||||
status: "VALID"
|
||||
},
|
||||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
yield helpers.addTabWithToolbar(TEST_URL, (options) => {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
return helpers.setInput(options, "resize on");
|
||||
},
|
||||
check: {
|
||||
input: "resize on",
|
||||
hints: "",
|
||||
markup: "VVVVVVVVV",
|
||||
status: "VALID"
|
||||
},
|
||||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(isOpen(), "responsive mode is open");
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "off");
|
||||
return helpers.setInput(options, "resize off");
|
||||
},
|
||||
check: {
|
||||
input: "resize off",
|
||||
hints: "",
|
||||
markup: "VVVVVVVVVV",
|
||||
status: "VALID"
|
||||
},
|
||||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
yield helpers.addTabWithToolbar(TEST_URL, (options) => {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
return helpers.setInput(options, "resize to 400 400");
|
||||
},
|
||||
check: {
|
||||
input: "resize to 400 400",
|
||||
hints: "",
|
||||
markup: "VVVVVVVVVVVVVVVVV",
|
||||
status: "VALID",
|
||||
args: {
|
||||
width: { value: 400 },
|
||||
height: { value: 400 },
|
||||
}
|
||||
},
|
||||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(isOpen(), "responsive mode is open");
|
||||
}),
|
||||
},
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "off");
|
||||
return helpers.setInput(options, "resize off");
|
||||
},
|
||||
check: {
|
||||
input: "resize off",
|
||||
hints: "",
|
||||
markup: "VVVVVVVVVV",
|
||||
status: "VALID"
|
||||
},
|
||||
exec: {
|
||||
output: ""
|
||||
},
|
||||
post: Task.async(function*() {
|
||||
yield done;
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -14,16 +14,17 @@ addRDMTask(TEST_URL, function*({ ui }) {
|
|||
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||
|
||||
// A single viewport of default size appeared
|
||||
let browser = ui.toolWindow.document.querySelector(".browser");
|
||||
is(browser.width, "320", "Viewport has default width");
|
||||
is(browser.height, "480", "Viewport has default height");
|
||||
let viewport = ui.toolWindow.document.querySelector(".viewport-content");
|
||||
|
||||
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"),
|
||||
"320px", "Viewport has default width");
|
||||
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"),
|
||||
"480px", "Viewport has default height");
|
||||
|
||||
// Browser's location should match original tab
|
||||
// TODO: For the moment, we have parent process <iframe>s and we can just
|
||||
// check the location directly. Bug 1240896 will change this to <iframe
|
||||
// mozbrowser remote>, which is in the child process, so ContentTask or
|
||||
// similar will be needed.
|
||||
yield waitForFrameLoad(browser, TEST_URL);
|
||||
is(browser.contentWindow.location.href, TEST_URL,
|
||||
"Viewport location matches");
|
||||
yield waitForFrameLoad(ui, TEST_URL);
|
||||
let location = yield spawnViewportTask(ui, {}, function*() {
|
||||
return content.location.href;
|
||||
});
|
||||
is(location, TEST_URL, "Viewport location matches");
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
/* eslint no-unused-vars: [2, {"vars": "local"}] */
|
||||
/* import-globals-from ../../../framework/test/shared-head.js */
|
||||
/* import-globals-from ../../../framework/test/shared-redux-head.js */
|
||||
/* import-globals-from ../../../commandline/test/helpers.js */
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
|
||||
|
@ -14,9 +15,15 @@ Services.scriptloader.loadSubScript(
|
|||
"chrome://mochitests/content/browser/devtools/client/framework/test/shared-redux-head.js",
|
||||
this);
|
||||
|
||||
// Import the GCLI test helper
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/devtools/client/commandline/test/helpers.js",
|
||||
this);
|
||||
|
||||
const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/";
|
||||
|
||||
SimpleTest.requestCompleteLog();
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
DevToolsUtils.testing = true;
|
||||
Services.prefs.setCharPref("devtools.devices.url",
|
||||
|
@ -47,7 +54,7 @@ var openRDM = Task.async(function*(tab) {
|
|||
var closeRDM = Task.async(function*(tab) {
|
||||
info("Closing responsive design mode");
|
||||
let manager = ResponsiveUIManager;
|
||||
manager.closeIfNeeded(window, tab);
|
||||
yield manager.closeIfNeeded(window, tab);
|
||||
info("Responsive design mode closed");
|
||||
});
|
||||
|
||||
|
@ -78,12 +85,43 @@ function addRDMTask(url, generator) {
|
|||
});
|
||||
}
|
||||
|
||||
var waitForFrameLoad = Task.async(function*(frame, targetURL) {
|
||||
let window = frame.contentWindow;
|
||||
if ((window.document.readyState == "complete" ||
|
||||
window.document.readyState == "interactive") &&
|
||||
window.location.href == targetURL) {
|
||||
return;
|
||||
function spawnViewportTask(ui, args, task) {
|
||||
return ContentTask.spawn(ui.getViewportMessageManager(), args, task);
|
||||
}
|
||||
|
||||
function waitForFrameLoad(ui, targetURL) {
|
||||
return spawnViewportTask(ui, { targetURL }, function*(args) {
|
||||
if ((content.document.readyState == "complete" ||
|
||||
content.document.readyState == "interactive") &&
|
||||
content.location.href == args.targetURL) {
|
||||
return;
|
||||
}
|
||||
yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded");
|
||||
});
|
||||
}
|
||||
|
||||
function waitForViewportResizeTo(ui, width, height) {
|
||||
return new Promise(resolve => {
|
||||
let onResize = (_, data) => {
|
||||
if (data.width != width || data.height != height) {
|
||||
return;
|
||||
}
|
||||
ui.off("content-resize", onResize);
|
||||
info(`Got content-resize to ${width} x ${height}`);
|
||||
resolve();
|
||||
};
|
||||
info(`Waiting for content-resize to ${width} x ${height}`);
|
||||
ui.on("content-resize", onResize);
|
||||
});
|
||||
}
|
||||
|
||||
var setViewportSize = Task.async(function*(ui, manager, width, height) {
|
||||
let size = ui.getViewportSize();
|
||||
info(`Current size: ${size.width} x ${size.height}, ` +
|
||||
`set to: ${width} x ${height}`);
|
||||
if (size.width != width || size.height != height) {
|
||||
let resized = waitForViewportResizeTo(ui, width, height);
|
||||
ui.setViewportSize(width, height);
|
||||
yield resized;
|
||||
}
|
||||
yield once(frame, "load");
|
||||
});
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const promise = require("promise");
|
||||
|
||||
module.exports = {
|
||||
|
||||
waitForMessage(mm, message) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let onMessage = event => {
|
||||
mm.removeMessageListener(message, onMessage);
|
||||
deferred.resolve();
|
||||
};
|
||||
mm.addMessageListener(message, onMessage);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
};
|
|
@ -5,5 +5,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'e10s.js',
|
||||
'l10n.js',
|
||||
)
|
||||
|
|
|
@ -42,6 +42,22 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
Cu.import("resource://devtools/shared/event-emitter.js");
|
||||
const { findMostRelevantCssPropertyIndex } = require("./suggestion-picker");
|
||||
|
||||
/**
|
||||
* Helper to check if the provided key matches one of the expected keys.
|
||||
* Keys will be prefixed with DOM_VK_ and should match a key in nsIDOMKeyEvent.
|
||||
*
|
||||
* @param {String} key
|
||||
* the key to check (can be a keyCode).
|
||||
* @param {...String} keys
|
||||
* list of possible keys allowed.
|
||||
* @return {Boolean} true if the key matches one of the keys.
|
||||
*/
|
||||
function isKeyIn(key, ...keys) {
|
||||
return keys.some(expectedKey => {
|
||||
return key === Ci.nsIDOMKeyEvent["DOM_VK_" + expectedKey];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a span editable. |editableField| will listen for the span to
|
||||
* be focused and create an InlineEditor to handle text input.
|
||||
|
@ -142,8 +158,7 @@ function editableItem(options, callback) {
|
|||
// If focused by means other than a click, start editing by
|
||||
// pressing enter or space.
|
||||
element.addEventListener("keypress", function(evt) {
|
||||
if (evt.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
|
||||
evt.charCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
|
||||
if (isKeyIn(evt.keyCode, "RETURN") || isKeyIn(evt.charCode, "SPACE")) {
|
||||
callback(element);
|
||||
}
|
||||
}, true);
|
||||
|
@ -968,14 +983,20 @@ InplaceEditor.prototype = {
|
|||
_onKeyPress: function(event) {
|
||||
let prevent = false;
|
||||
|
||||
let isPlainText = this.contentType == CONTENT_TYPES.PLAIN_TEXT;
|
||||
let increment = isPlainText ? 0 : this._getIncrement(event);
|
||||
let key = event.keyCode;
|
||||
let input = this.input;
|
||||
|
||||
// Use default cursor movement rather than providing auto-suggestions.
|
||||
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
|
||||
let multilineNavigation = !this._isSingleLine() &&
|
||||
isKeyIn(key, "UP", "DOWN", "LEFT", "RIGHT");
|
||||
let isPlainText = this.contentType == CONTENT_TYPES.PLAIN_TEXT;
|
||||
let isPopupOpen = this.popup && this.popup.isOpen;
|
||||
|
||||
let increment = 0;
|
||||
if (!isPlainText && !multilineNavigation) {
|
||||
increment = this._getIncrement(event);
|
||||
}
|
||||
|
||||
if (isKeyIn(key, "HOME", "END", "PAGE_UP", "PAGE_DOWN")) {
|
||||
this._preventSuggestions = true;
|
||||
}
|
||||
|
||||
|
@ -984,48 +1005,40 @@ InplaceEditor.prototype = {
|
|||
this._updateSize();
|
||||
prevent = true;
|
||||
cycling = true;
|
||||
} else if (increment && this.popup && this.popup.isOpen) {
|
||||
cycling = true;
|
||||
}
|
||||
|
||||
if (isPopupOpen && isKeyIn(key, "UP", "DOWN", "PAGE_UP", "PAGE_DOWN")) {
|
||||
prevent = true;
|
||||
this._cycleCSSSuggestion(increment > 0);
|
||||
cycling = true;
|
||||
this._cycleCSSSuggestion(isKeyIn(key, "UP", "PAGE_UP"));
|
||||
this._doValidation();
|
||||
}
|
||||
|
||||
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DELETE ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_LEFT ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
|
||||
if (this.popup && this.popup.isOpen) {
|
||||
if (isKeyIn(key, "BACK_SPACE", "DELETE", "LEFT", "RIGHT")) {
|
||||
if (isPopupOpen) {
|
||||
this.popup.hidePopup();
|
||||
}
|
||||
} else if (!cycling && !event.metaKey && !event.altKey && !event.ctrlKey) {
|
||||
} else if (!cycling && !multilineNavigation &&
|
||||
!event.metaKey && !event.altKey && !event.ctrlKey) {
|
||||
this._maybeSuggestCompletion(true);
|
||||
}
|
||||
|
||||
if (this.multiline &&
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN &&
|
||||
event.shiftKey) {
|
||||
if (this.multiline && event.shiftKey && isKeyIn(key, "RETURN")) {
|
||||
prevent = false;
|
||||
} else if (this._advanceChars(event.charCode, this.input.value,
|
||||
this.input.selectionStart) ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB) {
|
||||
} else if (
|
||||
this._advanceChars(event.charCode, input.value, input.selectionStart) ||
|
||||
isKeyIn(key, "RETURN", "TAB")) {
|
||||
prevent = true;
|
||||
|
||||
let direction = FOCUS_FORWARD;
|
||||
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
|
||||
event.shiftKey) {
|
||||
if (this.stopOnShiftTab) {
|
||||
direction = null;
|
||||
} else {
|
||||
direction = FOCUS_BACKWARD;
|
||||
}
|
||||
}
|
||||
if ((this.stopOnReturn &&
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) ||
|
||||
(this.stopOnTab && event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
|
||||
!event.shiftKey)) {
|
||||
let direction;
|
||||
if ((this.stopOnReturn && isKeyIn(key, "RETURN")) ||
|
||||
(this.stopOnTab && !event.shiftKey && isKeyIn(key, "TAB")) ||
|
||||
(this.stopOnShiftTab && event.shiftKey && isKeyIn(key, "TAB"))) {
|
||||
direction = null;
|
||||
} else if (event.shiftKey && isKeyIn(key, "TAB")) {
|
||||
direction = FOCUS_BACKWARD;
|
||||
} else {
|
||||
direction = FOCUS_FORWARD;
|
||||
}
|
||||
|
||||
// Now we don't want to suggest anything as we are moving out.
|
||||
|
@ -1037,10 +1050,7 @@ InplaceEditor.prototype = {
|
|||
this._preventSuggestions = false;
|
||||
}
|
||||
|
||||
let input = this.input;
|
||||
|
||||
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
|
||||
this.contentType == CONTENT_TYPES.CSS_MIXED) {
|
||||
if (isKeyIn(key, "TAB") && this.contentType == CONTENT_TYPES.CSS_MIXED) {
|
||||
if (this.popup && input.selectionStart < input.selectionEnd) {
|
||||
event.preventDefault();
|
||||
input.setSelectionRange(input.selectionEnd, input.selectionEnd);
|
||||
|
@ -1075,7 +1085,7 @@ InplaceEditor.prototype = {
|
|||
}
|
||||
|
||||
this._clear();
|
||||
} else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
|
||||
} else if (isKeyIn(key, "ESCAPE")) {
|
||||
// Cancel and blur ourselves.
|
||||
// Now we don't want to suggest anything as we are moving out.
|
||||
this._preventSuggestions = true;
|
||||
|
@ -1088,11 +1098,11 @@ InplaceEditor.prototype = {
|
|||
this._apply();
|
||||
this._clear();
|
||||
event.stopPropagation();
|
||||
} else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
|
||||
} else if (isKeyIn(key, "SPACE")) {
|
||||
// No need for leading spaces here. This is particularly
|
||||
// noticable when adding a property: it's very natural to type
|
||||
// <name>: (which advances to the next property) then spacebar.
|
||||
prevent = !this.input.value;
|
||||
prevent = !input.value;
|
||||
}
|
||||
|
||||
if (prevent) {
|
||||
|
@ -1109,18 +1119,16 @@ InplaceEditor.prototype = {
|
|||
const smallIncrement = 0.1;
|
||||
|
||||
let increment = 0;
|
||||
let key = event.keyCode;
|
||||
|
||||
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
|
||||
if (isKeyIn(key, "UP", "PAGE_UP")) {
|
||||
increment = 1;
|
||||
} else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
|
||||
} else if (isKeyIn(key, "DOWN", "PAGE_DOWN")) {
|
||||
increment = -1;
|
||||
}
|
||||
|
||||
if (event.shiftKey && !event.altKey) {
|
||||
if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP ||
|
||||
event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
|
||||
if (isKeyIn(key, "PAGE_UP", "PAGE_DOWN")) {
|
||||
increment *= largeIncrement;
|
||||
} else {
|
||||
increment *= mediumIncrement;
|
||||
|
|
|
@ -38,6 +38,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Add element toolbar button */
|
||||
|
||||
#inspector-element-add-button {
|
||||
list-style-image: url("chrome://devtools/skin/images/add.svg");
|
||||
}
|
||||
|
||||
/* Tooltip: Events */
|
||||
|
||||
#devtools-tooltip-events-container {
|
||||
|
|
|
@ -796,6 +796,16 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.toolbox-panel {
|
||||
display: -moz-box;
|
||||
-moz-box-flex: 1;
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
.toolbox-panel[selected] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.devtools-tab {
|
||||
-moz-appearance: none;
|
||||
-moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
|
||||
|
|
|
@ -286,7 +286,9 @@ this.PushService = {
|
|||
break;
|
||||
|
||||
case "idle-daily":
|
||||
this._dropExpiredRegistrations();
|
||||
this._dropExpiredRegistrations().catch(error => {
|
||||
console.error("Failed to drop expired registrations on idle", error);
|
||||
});
|
||||
break;
|
||||
|
||||
case "perm-changed":
|
||||
|
@ -441,6 +443,9 @@ this.PushService = {
|
|||
return this._stopService(STOPPING_SERVICE_EVENT);
|
||||
}
|
||||
}
|
||||
default:
|
||||
console.error("Unexpected event in _changeServerURL", event);
|
||||
return Promise.reject(new Error(`Unexpected event ${event}`));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -571,7 +576,7 @@ this.PushService = {
|
|||
console.debug("stopService()");
|
||||
|
||||
if (this._state < PUSH_SERVICE_ACTIVATING) {
|
||||
return;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this._stopObservers();
|
||||
|
|
|
@ -567,7 +567,7 @@ const std::string Histogram::GetAsciiBucketRange(size_t i) const {
|
|||
|
||||
// Update histogram data with new sample.
|
||||
void Histogram::Accumulate(Sample value, Count count, size_t index) {
|
||||
sample_.AccumulateWithLinearStats(value, count, index);
|
||||
sample_.Accumulate(value, count, index);
|
||||
}
|
||||
|
||||
void Histogram::SetBucketRange(size_t i, Sample value) {
|
||||
|
@ -720,9 +720,6 @@ void Histogram::WriteAsciiBucketGraph(double current_size, double max_size,
|
|||
Histogram::SampleSet::SampleSet()
|
||||
: counts_(),
|
||||
sum_(0),
|
||||
sum_squares_(0),
|
||||
log_sum_(0),
|
||||
log_sum_squares_(0),
|
||||
redundant_count_(0),
|
||||
mutex_("Histogram::SampleSet::SampleSet") {
|
||||
}
|
||||
|
@ -747,12 +744,11 @@ void Histogram::SampleSet::Accumulate(const OffTheBooksMutexAutoLock& ev,
|
|||
DCHECK_GE(redundant_count_, 0);
|
||||
}
|
||||
|
||||
void Histogram::SampleSet::AccumulateWithLinearStats(Sample value,
|
||||
Count count,
|
||||
size_t index) {
|
||||
void Histogram::SampleSet::Accumulate(Sample value,
|
||||
Count count,
|
||||
size_t index) {
|
||||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
Accumulate(locker, value, count, index);
|
||||
sum_squares_ += static_cast<int64_t>(count) * value * value;
|
||||
}
|
||||
|
||||
Count Histogram::SampleSet::TotalCount(const OffTheBooksMutexAutoLock& ev)
|
||||
|
@ -770,9 +766,6 @@ void Histogram::SampleSet::Add(const SampleSet& other) {
|
|||
OffTheBooksMutexAutoLock locker(mutex_);
|
||||
DCHECK_EQ(counts_.size(), other.counts_.size());
|
||||
sum_ += other.sum_;
|
||||
sum_squares_ += other.sum_squares_;
|
||||
log_sum_ += other.log_sum_;
|
||||
log_sum_squares_ += other.log_sum_squares_;
|
||||
redundant_count_ += other.redundant_count_;
|
||||
for (size_t index = 0; index < counts_.size(); ++index)
|
||||
counts_[index] += other.counts_[index];
|
||||
|
@ -868,7 +861,7 @@ Histogram::ClassType LinearHistogram::histogram_type() const {
|
|||
}
|
||||
|
||||
void LinearHistogram::Accumulate(Sample value, Count count, size_t index) {
|
||||
sample_.AccumulateWithLinearStats(value, count, index);
|
||||
sample_.Accumulate(value, count, index);
|
||||
}
|
||||
|
||||
void LinearHistogram::SetRangeDescriptions(
|
||||
|
|
|
@ -353,7 +353,7 @@ class Histogram {
|
|||
void Resize(const Histogram& histogram);
|
||||
|
||||
// Accessor for histogram to make routine additions.
|
||||
void AccumulateWithLinearStats(Sample value, Count count, size_t index);
|
||||
void Accumulate(Sample value, Count count, size_t index);
|
||||
|
||||
// Arithmetic manipulation of corresponding elements of the set.
|
||||
void Add(const SampleSet& other);
|
||||
|
@ -375,15 +375,6 @@ class Histogram {
|
|||
int64_t sum(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return sum_;
|
||||
}
|
||||
uint64_t sum_squares(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return sum_squares_;
|
||||
}
|
||||
double log_sum(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return log_sum_;
|
||||
}
|
||||
double log_sum_squares(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return log_sum_squares_;
|
||||
}
|
||||
int64_t redundant_count(const OffTheBooksMutexAutoLock& ev) const {
|
||||
return redundant_count_;
|
||||
}
|
||||
|
@ -396,9 +387,6 @@ class Histogram {
|
|||
const SampleSet& operator=(const SampleSet& other) {
|
||||
counts_ = other.counts_;
|
||||
sum_ = other.sum_;
|
||||
sum_squares_ = other.sum_squares_;
|
||||
log_sum_ = other.log_sum_;
|
||||
log_sum_squares_ = other.log_sum_squares_;
|
||||
redundant_count_ = other.redundant_count_;
|
||||
return *this;
|
||||
}
|
||||
|
@ -415,13 +403,6 @@ class Histogram {
|
|||
// Save simple stats locally. Note that this MIGHT get done in base class
|
||||
// without shared memory at some point.
|
||||
int64_t sum_; // sum of samples.
|
||||
uint64_t sum_squares_; // sum of squares of samples.
|
||||
|
||||
// These fields may or may not be updated at the discretion of the
|
||||
// histogram. We use the natural log and compute ln(sample+1) so that
|
||||
// zeros are handled sanely.
|
||||
double log_sum_; // sum of logs of samples.
|
||||
double log_sum_squares_; // sum of squares of logs of samples
|
||||
|
||||
// To help identify memory corruption, we reduntantly save the number of
|
||||
// samples we've accumulated into all of our buckets. We can compare this
|
||||
|
|
|
@ -38,8 +38,13 @@
|
|||
<module name="FileTabCharacter"> <!-- No tabs! -->
|
||||
<property name="eachLine" value="true"/>
|
||||
</module>
|
||||
<module name="RegexpSingleline"> <!-- excess whitespace -->
|
||||
<property name="format" value="\s+$"/>
|
||||
<property name="message" value="Trailing whitespace"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<module name="GenericWhitespace"/> <!-- whitespace for generics -->
|
||||
<module name="NoLineWrap">
|
||||
<property name="tokens" value="IMPORT,PACKAGE_DEF"/>
|
||||
</module>
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils.BitmapLoader;
|
||||
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
|
||||
import org.mozilla.gecko.gfx.Layer;
|
||||
import org.mozilla.gecko.gfx.LayerView;
|
||||
import org.mozilla.gecko.gfx.LayerView.DrawListener;
|
||||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.menu.GeckoMenuItem;
|
||||
import org.mozilla.gecko.EventDispatcher;
|
||||
import org.mozilla.gecko.text.TextSelection;
|
||||
import org.mozilla.gecko.util.FloatUtils;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
@ -21,7 +19,6 @@ import org.mozilla.gecko.ActionModeCompat.Callback;
|
|||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
|
||||
import android.content.Context;
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
@ -36,8 +33,7 @@ import java.util.TimerTask;
|
|||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
class TextSelection extends Layer implements GeckoEventListener,
|
||||
LayerView.DynamicToolbarListener {
|
||||
class ActionBarTextSelection extends Layer implements TextSelection, GeckoEventListener, LayerView.DynamicToolbarListener {
|
||||
private static final String LOGTAG = "GeckoTextSelection";
|
||||
private static final int SHUTDOWN_DELAY_MS = 250;
|
||||
|
||||
|
@ -74,9 +70,9 @@ class TextSelection extends Layer implements GeckoEventListener,
|
|||
};
|
||||
private ActionModeTimerTask mActionModeTimerTask;
|
||||
|
||||
TextSelection(TextSelectionHandle anchorHandle,
|
||||
TextSelectionHandle caretHandle,
|
||||
TextSelectionHandle focusHandle) {
|
||||
ActionBarTextSelection(TextSelectionHandle anchorHandle,
|
||||
TextSelectionHandle caretHandle,
|
||||
TextSelectionHandle focusHandle) {
|
||||
this.anchorHandle = anchorHandle;
|
||||
this.caretHandle = caretHandle;
|
||||
this.focusHandle = focusHandle;
|
||||
|
@ -89,7 +85,10 @@ class TextSelection extends Layer implements GeckoEventListener,
|
|||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create() {
|
||||
// Only register listeners if we have valid start/middle/end handles
|
||||
if (anchorHandle == null || caretHandle == null || focusHandle == null) {
|
||||
Log.e(LOGTAG, "Failed to initialize text selection because at least one handle is null");
|
||||
|
@ -106,7 +105,14 @@ class TextSelection extends Layer implements GeckoEventListener,
|
|||
}
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
@Override
|
||||
public boolean dismiss() {
|
||||
// We do not call endActionMode() here because this is already handled by the activity.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
|
||||
"TextSelection:ActionbarInit",
|
||||
"TextSelection:ActionbarStatus",
|
||||
|
@ -159,8 +165,8 @@ class TextSelection extends Layer implements GeckoEventListener,
|
|||
LayerView layerView = GeckoAppShell.getLayerView();
|
||||
if (layerView != null) {
|
||||
layerView.addDrawListener(mDrawListener);
|
||||
layerView.addLayer(TextSelection.this);
|
||||
layerView.getDynamicToolbarAnimator().addTranslationListener(TextSelection.this);
|
||||
layerView.addLayer(ActionBarTextSelection.this);
|
||||
layerView.getDynamicToolbarAnimator().addTranslationListener(ActionBarTextSelection.this);
|
||||
}
|
||||
|
||||
if (handles.length() > 1)
|
||||
|
@ -174,8 +180,8 @@ class TextSelection extends Layer implements GeckoEventListener,
|
|||
LayerView layerView = GeckoAppShell.getLayerView();
|
||||
if (layerView != null) {
|
||||
layerView.removeDrawListener(mDrawListener);
|
||||
layerView.removeLayer(TextSelection.this);
|
||||
layerView.getDynamicToolbarAnimator().removeTranslationListener(TextSelection.this);
|
||||
layerView.removeLayer(ActionBarTextSelection.this);
|
||||
layerView.getDynamicToolbarAnimator().removeTranslationListener(ActionBarTextSelection.this);
|
||||
}
|
||||
|
||||
mActionModeTimerTask = new ActionModeTimerTask();
|
||||
|
@ -303,7 +309,7 @@ class TextSelection extends Layer implements GeckoEventListener,
|
|||
private class TextSelectionActionModeCallback implements Callback {
|
||||
private JSONArray mItems;
|
||||
private ActionModeCompat mActionMode;
|
||||
|
||||
|
||||
public TextSelectionActionModeCallback(JSONArray items) {
|
||||
mItems = items;
|
||||
}
|
|
@ -614,6 +614,20 @@ public class BrowserApp extends GeckoApp
|
|||
mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
|
||||
|
||||
mBrowserToolbar = (BrowserToolbar) findViewById(R.id.browser_toolbar);
|
||||
mBrowserToolbar.setTouchEventInterceptor(new TouchEventInterceptor() {
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(View view, MotionEvent event) {
|
||||
// Manually dismiss text selection bar if it's not overlaying the toolbar.
|
||||
mTextSelection.dismiss();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
mProgressView = (ToolbarProgressView) findViewById(R.id.progress);
|
||||
mBrowserToolbar.setProgressBar(mProgressView);
|
||||
|
||||
|
@ -946,6 +960,10 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mTextSelection.dismiss()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
|
||||
super.onBackPressed();
|
||||
return;
|
||||
|
@ -3786,6 +3804,7 @@ public class BrowserApp extends GeckoApp
|
|||
// Launched from a "content notification"
|
||||
if (intent.hasExtra(CheckForUpdatesAction.EXTRA_CONTENT_NOTIFICATION)) {
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, "content_update");
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "content_update");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -234,17 +234,17 @@ public class CrashReporter extends AppCompatActivity
|
|||
|
||||
private void savePrefs() {
|
||||
SharedPreferences.Editor editor = GeckoSharedPrefs.forApp(this).edit();
|
||||
|
||||
|
||||
final boolean allowContact = ((CheckBox) findViewById(R.id.allow_contact)).isChecked();
|
||||
final boolean includeUrl = ((CheckBox) findViewById(R.id.include_url)).isChecked();
|
||||
final boolean sendReport = ((CheckBox) findViewById(R.id.send_report)).isChecked();
|
||||
final String contactEmail = ((EditText) findViewById(R.id.email)).getText().toString();
|
||||
|
||||
|
||||
editor.putBoolean(PREFS_ALLOW_CONTACT, allowContact);
|
||||
editor.putBoolean(PREFS_INCLUDE_URL, includeUrl);
|
||||
editor.putBoolean(PREFS_SEND_REPORT, sendReport);
|
||||
editor.putString(PREFS_CONTACT_EMAIL, contactEmail);
|
||||
|
||||
|
||||
// A slight performance improvement via async apply() vs. blocking on commit().
|
||||
editor.apply();
|
||||
}
|
||||
|
|
|
@ -198,7 +198,7 @@ public class FormAssistPopup extends RelativeLayout implements GeckoEventListene
|
|||
|
||||
addView(mAutoCompleteList);
|
||||
}
|
||||
|
||||
|
||||
AutoCompleteListAdapter adapter = new AutoCompleteListAdapter(mContext, R.layout.autocomplete_list_item);
|
||||
adapter.populateSuggestionsList(suggestions);
|
||||
mAutoCompleteList.setAdapter(adapter);
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
|
@ -34,6 +31,8 @@ import org.mozilla.gecko.preferences.GeckoPreferences;
|
|||
import org.mozilla.gecko.prompts.PromptService;
|
||||
import org.mozilla.gecko.restrictions.Restrictions;
|
||||
import org.mozilla.gecko.tabqueue.TabQueueHelper;
|
||||
import org.mozilla.gecko.text.FloatingToolbarTextSelection;
|
||||
import org.mozilla.gecko.text.TextSelection;
|
||||
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
||||
import org.mozilla.gecko.util.ActivityResultHandler;
|
||||
import org.mozilla.gecko.util.ActivityUtils;
|
||||
|
@ -48,10 +47,10 @@ import org.mozilla.gecko.util.NativeJSObject;
|
|||
import org.mozilla.gecko.util.PrefUtils;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
|
@ -96,6 +95,8 @@ import android.view.ViewGroup;
|
|||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.widget.AbsoluteLayout;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RelativeLayout;
|
||||
|
@ -179,7 +180,7 @@ public abstract class GeckoApp
|
|||
|
||||
private ContactService mContactService;
|
||||
private PromptService mPromptService;
|
||||
private TextSelection mTextSelection;
|
||||
protected TextSelection mTextSelection;
|
||||
|
||||
protected DoorHangerPopup mDoorHangerPopup;
|
||||
protected FormAssistPopup mFormAssistPopup;
|
||||
|
@ -1268,6 +1269,16 @@ public abstract class GeckoApp
|
|||
// Use global layout state change to kick off additional initialization
|
||||
mMainLayout.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||
|
||||
if (Versions.preMarshmallow || !AppConstants.NIGHTLY_BUILD) {
|
||||
mTextSelection = new ActionBarTextSelection(
|
||||
(TextSelectionHandle) findViewById(R.id.anchor_handle),
|
||||
(TextSelectionHandle) findViewById(R.id.caret_handle),
|
||||
(TextSelectionHandle) findViewById(R.id.focus_handle));
|
||||
} else {
|
||||
mTextSelection = new FloatingToolbarTextSelection(this, mLayerView);
|
||||
}
|
||||
mTextSelection.create();
|
||||
|
||||
// Determine whether we should restore tabs.
|
||||
mShouldRestore = getSessionRestoreState(savedInstanceState);
|
||||
if (mShouldRestore && savedInstanceState != null) {
|
||||
|
@ -1541,10 +1552,6 @@ public abstract class GeckoApp
|
|||
|
||||
mPromptService = new PromptService(this);
|
||||
|
||||
mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.anchor_handle),
|
||||
(TextSelectionHandle) findViewById(R.id.caret_handle),
|
||||
(TextSelectionHandle) findViewById(R.id.focus_handle));
|
||||
|
||||
// Trigger the completion of the telemetry timer that wraps activity startup,
|
||||
// then grab the duration to give to FHR.
|
||||
mJavaUiStartupTimer.stop();
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.mozilla.gecko.util.ThreadUtils;
|
|||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class GeckoApplication extends Application
|
||||
public class GeckoApplication extends Application
|
||||
implements ContextGetter {
|
||||
private static final String LOG_TAG = "GeckoApplication";
|
||||
|
||||
|
@ -110,7 +110,7 @@ public class GeckoApplication extends Application
|
|||
// shutdown, closing the disk cache cleanly. If the android
|
||||
// low memory killer subsequently kills us, the disk cache will
|
||||
// be left in a consistent state, avoiding costly cleanup and
|
||||
// re-creation.
|
||||
// re-creation.
|
||||
GeckoThread.onPause();
|
||||
mPausedGecko = true;
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.mozilla.gecko.util.INISection;
|
|||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import android.text.TextUtils;
|
||||
|
@ -91,7 +92,7 @@ public final class GeckoProfile {
|
|||
* Access to this member should be synchronized to avoid
|
||||
* races during creation -- particularly between getDir and GeckoView#init.
|
||||
*
|
||||
* Not final because this is lazily computed.
|
||||
* Not final because this is lazily computed.
|
||||
*/
|
||||
private File mProfileDir;
|
||||
|
||||
|
@ -535,6 +536,7 @@ public final class GeckoProfile {
|
|||
return false;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public boolean inGuestMode() {
|
||||
return mInGuestMode;
|
||||
}
|
||||
|
@ -547,6 +549,7 @@ public final class GeckoProfile {
|
|||
}
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
@ -555,6 +558,7 @@ public final class GeckoProfile {
|
|||
return CUSTOM_PROFILE.equals(mName);
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public synchronized File getDir() {
|
||||
forceCreate();
|
||||
return mProfileDir;
|
||||
|
@ -680,15 +684,38 @@ public final class GeckoProfile {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return the profile creation date in the format returned by {@link System#currentTimeMillis()} or -1 if the value
|
||||
* was not found.
|
||||
* Gets the profile creation date and persists it if it had to be generated.
|
||||
*
|
||||
* To get this value, we first look in times.json. If that could not be accessed, we
|
||||
* return the package's first install date. This is not a perfect solution because a
|
||||
* user may have large gap between install time and first use.
|
||||
*
|
||||
* A more correct algorithm could be the one performed by the JS code in ProfileAge.jsm
|
||||
* getOldestProfileTimestamp: walk the tree and return the oldest timestamp on the files
|
||||
* within the profile. However, since times.json will only not exist for the small
|
||||
* number of really old profiles, we're okay with the package install date compromise for
|
||||
* simplicity.
|
||||
*
|
||||
* @return the profile creation date in the format returned by {@link System#currentTimeMillis()}
|
||||
* or -1 if the value could not be persisted.
|
||||
*/
|
||||
@WorkerThread
|
||||
public long getProfileCreationDate() {
|
||||
public long getAndPersistProfileCreationDate(final Context context) {
|
||||
try {
|
||||
return getProfileCreationDateFromTimesFile();
|
||||
} catch (final IOException e) {
|
||||
return getAndPersistProfileCreationDateFromFilesystem();
|
||||
Log.d(LOGTAG, "Unable to retrieve profile creation date from times.json. Getting from system...");
|
||||
final long packageInstallMillis = org.mozilla.gecko.util.ContextUtils.getPackageInstallTime(context);
|
||||
try {
|
||||
persistProfileCreationDateToTimesFile(packageInstallMillis);
|
||||
} catch (final IOException ioEx) {
|
||||
// We return -1 to ensure the profileCreationDate
|
||||
// will either be an error (-1) or a consistent value.
|
||||
Log.w(LOGTAG, "Unable to persist profile creation date - returning -1");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return packageInstallMillis;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -703,14 +730,17 @@ public final class GeckoProfile {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO (bug 1246816): Implement ProfileAge.jsm - getOldestProfileTimestamp. Persist results to times.json.
|
||||
* Update comment in getProfileCreationDate too.
|
||||
* @return -1 until implemented.
|
||||
*/
|
||||
@WorkerThread
|
||||
private long getAndPersistProfileCreationDateFromFilesystem() {
|
||||
return -1;
|
||||
private void persistProfileCreationDateToTimesFile(final long profileCreationMillis) throws IOException {
|
||||
final JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put(PROFILE_CREATION_DATE_JSON_ATTR, profileCreationMillis);
|
||||
} catch (final JSONException e) {
|
||||
// Don't log to avoid leaking data in JSONObject.
|
||||
throw new IOException("Unable to persist profile creation date to times file");
|
||||
}
|
||||
Log.d(LOGTAG, "Attempting to write new profile creation date");
|
||||
writeFile(TIMES_PATH, obj.toString()); // Ideally we'd throw here too.
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -939,7 +939,7 @@ public class GeckoSmsManager
|
|||
for (int i = 1; i < mNumbersCount; ++i) {
|
||||
formatter.format(", '%s'", mNumbers[i]);
|
||||
}
|
||||
|
||||
|
||||
formatter.format(") AND ");
|
||||
}
|
||||
|
||||
|
|
|
@ -637,7 +637,7 @@ public class GeckoView extends LayerView
|
|||
* Defaults to cancel requests.
|
||||
*/
|
||||
public void onAlert(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result);
|
||||
|
||||
|
||||
/**
|
||||
* Tell the host application to display a confirmation dialog.
|
||||
* @param view The GeckoView that initiated the callback.
|
||||
|
@ -647,7 +647,7 @@ public class GeckoView extends LayerView
|
|||
* Defaults to cancel requests.
|
||||
*/
|
||||
public void onConfirm(GeckoView view, GeckoView.Browser browser, String message, GeckoView.PromptResult result);
|
||||
|
||||
|
||||
/**
|
||||
* Tell the host application to display an input prompt dialog.
|
||||
* @param view The GeckoView that initiated the callback.
|
||||
|
@ -658,7 +658,7 @@ public class GeckoView extends LayerView
|
|||
* Defaults to cancel requests.
|
||||
*/
|
||||
public void onPrompt(GeckoView view, GeckoView.Browser browser, String message, String defaultValue, GeckoView.PromptResult result);
|
||||
|
||||
|
||||
/**
|
||||
* Tell the host application to display a remote debugging request dialog.
|
||||
* @param view The GeckoView that initiated the callback.
|
||||
|
@ -685,7 +685,7 @@ public class GeckoView extends LayerView
|
|||
* @param url The resource being loaded.
|
||||
*/
|
||||
public void onPageStart(GeckoView view, GeckoView.Browser browser, String url);
|
||||
|
||||
|
||||
/**
|
||||
* A Browser has finished loading content from the network.
|
||||
* @param view The GeckoView that initiated the callback.
|
||||
|
|
|
@ -53,7 +53,7 @@ class TextSelectionHandle extends ImageView implements View.OnTouchListener {
|
|||
|
||||
private float mLeft;
|
||||
private float mTop;
|
||||
private boolean mIsRTL;
|
||||
private boolean mIsRTL;
|
||||
private PointF mGeckoPoint;
|
||||
private PointF mTouchStart;
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ public class PropertyAnimator implements Runnable {
|
|||
|
||||
float interpolation = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
|
||||
|
||||
for (ElementHolder element : mElementsList) {
|
||||
for (ElementHolder element : mElementsList) {
|
||||
float delta = element.from + ((element.to - element.from) * interpolation);
|
||||
invalidate(element, delta);
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ public abstract class SQLiteBridgeContentProvider extends ContentProvider {
|
|||
}
|
||||
return bridge;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute path of a database file depending on the specified profile and dbName.
|
||||
* @param profile
|
||||
|
@ -183,7 +183,7 @@ public abstract class SQLiteBridgeContentProvider extends ContentProvider {
|
|||
* current provider instance.
|
||||
* @param profile
|
||||
* the id of the profile to be used to retrieve the related SQLiteBridge
|
||||
* @return the <code>SQLiteBridge</code> related to the specified profile id or <code>null</code> if it was
|
||||
* @return the <code>SQLiteBridge</code> related to the specified profile id or <code>null</code> if it was
|
||||
* not possible to retrieve a valid SQLiteBridge
|
||||
*/
|
||||
private SQLiteBridge getDatabaseForProfile(String profile) {
|
||||
|
@ -202,7 +202,7 @@ public abstract class SQLiteBridgeContentProvider extends ContentProvider {
|
|||
return db;
|
||||
}
|
||||
final String dbPath = getDatabasePathForProfile(profile, dbName);
|
||||
if (dbPath == null) {
|
||||
if (dbPath == null) {
|
||||
Log.e(mLogTag, "Failed to get a valid db path for profile '" + profile + "'' dbName '" + dbName + "'");
|
||||
return null;
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ public abstract class SQLiteBridgeContentProvider extends ContentProvider {
|
|||
* Returns a SQLiteBridge object according to the specified file path.
|
||||
* @param dbPath
|
||||
* the path of the file to be used to retrieve the related SQLiteBridge
|
||||
* @return the <code>SQLiteBridge</code> related to the specified file path or <code>null</code> if it was
|
||||
* @return the <code>SQLiteBridge</code> related to the specified file path or <code>null</code> if it was
|
||||
* not possible to retrieve a valid <code>SQLiteBridge</code>
|
||||
*
|
||||
*/
|
||||
|
@ -255,7 +255,7 @@ public abstract class SQLiteBridgeContentProvider extends ContentProvider {
|
|||
* Returns a SQLiteBridge object to be used to perform operations on the given <code>Uri</code>.
|
||||
* @param uri
|
||||
* the <code>Uri</code> to be used to retrieve the related SQLiteBridge
|
||||
* @return a <code>SQLiteBridge</code> object to be used on the given uri or <code>null</code> if it was
|
||||
* @return a <code>SQLiteBridge</code> object to be used on the given uri or <code>null</code> if it was
|
||||
* not possible to retrieve a valid <code>SQLiteBridge</code>
|
||||
*
|
||||
*/
|
||||
|
|
|
@ -567,7 +567,7 @@ public class Distribution {
|
|||
} else {
|
||||
value = status / 100;
|
||||
}
|
||||
|
||||
|
||||
Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, value);
|
||||
|
||||
if (status != 200) {
|
||||
|
|
|
@ -11,6 +11,8 @@ import android.content.Intent;
|
|||
import android.database.Cursor;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.mozilla.gecko.Telemetry;
|
||||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.UrlAnnotations;
|
||||
|
@ -81,6 +83,8 @@ public class WithdrawSubscriptionsAction extends FeedAction {
|
|||
log("Removing subscription for feed: " + subscription.getFeedUrl());
|
||||
|
||||
urlAnnotations.deleteFeedSubscription(resolver, subscription);
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.SERVICE, "content_update");
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
|
|
|
@ -380,7 +380,7 @@ final class DisplayPortCalculator {
|
|||
margins.left = xAmount / 2.0f;
|
||||
}
|
||||
margins.right = xAmount - margins.left;
|
||||
|
||||
|
||||
if (velocity.y > VELOCITY_THRESHOLD) {
|
||||
margins.top = yAmount * REVERSE_BUFFER;
|
||||
} else if (velocity.y < -VELOCITY_THRESHOLD) {
|
||||
|
|
|
@ -664,6 +664,10 @@ public class LayerView extends ScrollView implements Tabs.OnTabsChangedListener
|
|||
return super.getOverScrollMode();
|
||||
}
|
||||
|
||||
public float getZoomFactor() {
|
||||
return getLayerClient().getViewportMetrics().zoomFactor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChanged (boolean gainFocus, int direction, Rect previouslyFocusedRect) {
|
||||
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
|
||||
|
|
|
@ -67,7 +67,7 @@ public class TextureGenerator {
|
|||
Log.e(LOGTAG, String.format("Failed to generate textures: %#x", error), new Exception());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < numNeeded; i++) {
|
||||
mTextureIds.offer(textures[i]);
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ public class LightweightTheme implements GeckoEventListener {
|
|||
private boolean mIsLight;
|
||||
|
||||
public static interface OnChangeListener {
|
||||
// The View should change its background/text color.
|
||||
// The View should change its background/text color.
|
||||
public void onLightweightThemeChanged();
|
||||
|
||||
// The View should reset to its default background/text color.
|
||||
|
|
|
@ -34,7 +34,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GeckoMenu extends ListView
|
||||
public class GeckoMenu extends ListView
|
||||
implements Menu,
|
||||
AdapterView.OnItemClickListener,
|
||||
GeckoMenuItem.OnShowAsActionChangedListener {
|
||||
|
@ -76,7 +76,7 @@ public class GeckoMenu extends ListView
|
|||
/*
|
||||
* An interface for a presenter of action-items.
|
||||
* Either an Activity or a View can be a presenter, that can watch for events
|
||||
* and add/remove action-items. If not ActionItemBarPresenter, the menu uses a
|
||||
* and add/remove action-items. If not ActionItemBarPresenter, the menu uses a
|
||||
* DefaultActionItemBar, that shows the action-items as a header over list-view.
|
||||
*/
|
||||
public static interface ActionItemBarPresenter {
|
||||
|
@ -509,7 +509,7 @@ public class GeckoMenu extends ListView
|
|||
mPrimaryActionItems.remove(item);
|
||||
mItems.remove(item);
|
||||
|
||||
if (mPrimaryActionItems.size() == 0 &&
|
||||
if (mPrimaryActionItems.size() == 0 &&
|
||||
mPrimaryActionItemBar instanceof DefaultActionItemBar) {
|
||||
removePrimaryActionBarView();
|
||||
}
|
||||
|
@ -840,7 +840,7 @@ public class GeckoMenu extends ListView
|
|||
// Initialize the view.
|
||||
view.setShowIcon(mShowIcons);
|
||||
view.initialize(item);
|
||||
return (View) view;
|
||||
return (View) view;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,7 +11,7 @@ import android.view.MenuItem;
|
|||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
|
||||
public class GeckoSubMenu extends GeckoMenu
|
||||
public class GeckoSubMenu extends GeckoMenu
|
||||
implements SubMenu {
|
||||
private static final String LOGTAG = "GeckoSubMenu";
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class GeckoSubMenu extends GeckoMenu
|
|||
}
|
||||
|
||||
@Override
|
||||
public SubMenu setHeaderView(View view) {
|
||||
public SubMenu setHeaderView(View view) {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ public class MenuItemDefault extends TextView
|
|||
if (item == null)
|
||||
return;
|
||||
|
||||
setTitle(item.getTitle());
|
||||
setTitle(item.getTitle());
|
||||
setIcon(item.getIcon());
|
||||
setEnabled(item.isEnabled());
|
||||
setCheckable(item.isCheckable());
|
||||
|
|
|
@ -16,7 +16,7 @@ import android.widget.LinearLayout;
|
|||
|
||||
/**
|
||||
* The outer container for the custom menu. On phones with h/w menu button,
|
||||
* this is given to Android which inflates it to the right panel. On phones
|
||||
* this is given to Android which inflates it to the right panel. On phones
|
||||
* with s/w menu button, this is added to a MenuPopup.
|
||||
*/
|
||||
public class MenuPanel extends LinearLayout {
|
||||
|
|
|
@ -59,7 +59,7 @@ class MultiChoicePreference extends DialogPreference implements DialogInterface.
|
|||
public void setEntries(CharSequence[] entries) {
|
||||
mEntries = entries.clone();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param entriesResId The entries array as a resource.
|
||||
*/
|
||||
|
@ -108,7 +108,7 @@ class MultiChoicePreference extends DialogPreference implements DialogInterface.
|
|||
|
||||
/**
|
||||
* The list of translated strings corresponding to each preference.
|
||||
*
|
||||
*
|
||||
* @return The array of entries.
|
||||
*/
|
||||
public CharSequence[] getEntries() {
|
||||
|
@ -117,7 +117,7 @@ class MultiChoicePreference extends DialogPreference implements DialogInterface.
|
|||
|
||||
/**
|
||||
* The list of values corresponding to each preference.
|
||||
*
|
||||
*
|
||||
* @return The array of values.
|
||||
*/
|
||||
public CharSequence[] getEntryValues() {
|
||||
|
@ -127,7 +127,7 @@ class MultiChoicePreference extends DialogPreference implements DialogInterface.
|
|||
/**
|
||||
* The list of initial values for each preference. Each string in this list
|
||||
* should be either "true" or "false".
|
||||
*
|
||||
*
|
||||
* @return The array of initial values.
|
||||
*/
|
||||
public CharSequence[] getInitialValues() {
|
||||
|
@ -142,7 +142,7 @@ class MultiChoicePreference extends DialogPreference implements DialogInterface.
|
|||
/**
|
||||
* The list of values for each preference. These values are updated after
|
||||
* the dialog has been displayed.
|
||||
*
|
||||
*
|
||||
* @return The array of values.
|
||||
*/
|
||||
public Set<String> getValues() {
|
||||
|
|
|
@ -145,7 +145,7 @@ public class IconGridInput extends PromptInput implements OnItemClickListener {
|
|||
lp.width = lp.height = mIconSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class IconGridItem {
|
||||
final String label;
|
||||
final String description;
|
||||
|
|
|
@ -280,7 +280,7 @@ public class Prompt implements OnClickListener, OnCancelListener, OnItemClickLis
|
|||
* @param listItems
|
||||
* The items to add.
|
||||
* @param choiceMode
|
||||
* One of the ListView.CHOICE_MODE constants to designate whether this list shows checkmarks, radios buttons, or nothing.
|
||||
* One of the ListView.CHOICE_MODE constants to designate whether this list shows checkmarks, radios buttons, or nothing.
|
||||
*/
|
||||
private void addListItems(AlertDialog.Builder builder, PromptListItem[] listItems, int choiceMode) {
|
||||
switch(choiceMode) {
|
||||
|
|
|
@ -218,7 +218,7 @@ public class PromptInput {
|
|||
mView = (View)input;
|
||||
} else if (mType.equals("datetime-local") || mType.equals("datetime")) {
|
||||
DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T"," ").replace("Z", ""),
|
||||
DateTimePicker.PickersState.DATETIME,
|
||||
DateTimePicker.PickersState.DATETIME,
|
||||
mMinValue.replace("T"," ").replace("Z",""), mMaxValue.replace("T"," ").replace("Z", ""));
|
||||
input.toggleCalendar(true);
|
||||
mView = (View)input;
|
||||
|
|
|
@ -27,7 +27,7 @@ public class TabHistoryController {
|
|||
};
|
||||
|
||||
public interface OnShowTabHistory {
|
||||
void onShowHistory(List<TabHistoryPage>historyPageList, int toIndex);
|
||||
void onShowHistory(List<TabHistoryPage> historyPageList, int toIndex);
|
||||
}
|
||||
|
||||
public TabHistoryController(OnShowTabHistory showTabHistoryListener) {
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче