Merge m-c to b2g-inbound.
|
@ -1105,6 +1105,7 @@ let RemoteDebugger = {
|
|||
DebuggerServer.registerModule("devtools/server/actors/stylesheets");
|
||||
DebuggerServer.registerModule("devtools/server/actors/tracer");
|
||||
DebuggerServer.registerModule("devtools/server/actors/webgl");
|
||||
DebuggerServer.registerModule("devtools/server/actors/memory");
|
||||
}
|
||||
DebuggerServer.addActors('chrome://browser/content/dbg-browser-actors.js');
|
||||
DebuggerServer.addActors("resource://gre/modules/devtools/server/actors/webapps.js");
|
||||
|
|
|
@ -871,8 +871,6 @@ pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
|
|||
pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
|
||||
// End-users should not run sessionstore in debug mode
|
||||
pref("browser.sessionstore.debug", false);
|
||||
// Enable asynchronous data collection by default.
|
||||
pref("browser.sessionstore.async", true);
|
||||
|
||||
// allow META refresh by default
|
||||
pref("accessibility.blockautorefresh", false);
|
||||
|
@ -1337,7 +1335,12 @@ pref("browser.uiCustomization.debug", false);
|
|||
|
||||
// The URL where remote content that composes the UI for Firefox Accounts should
|
||||
// be fetched. Must use HTTPS.
|
||||
pref("identity.fxaccounts.remote.uri", "https://accounts.dev.lcip.org/?service=sync");
|
||||
pref("identity.fxaccounts.remote.uri", "https://accounts.firefox.com/?service=sync&context=fx_desktop_v1");
|
||||
|
||||
// The URL we take the user to when they opt to "manage" their Firefox Account.
|
||||
// Note that this will always need to be in the same TLD as the
|
||||
// "identity.fxaccounts.remote.uri" pref.
|
||||
pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
|
||||
|
||||
// The URL of the Firefox Accounts auth server backend
|
||||
pref("identity.fxaccounts.auth.uri", "https://api-accounts.dev.lcip.org/v1");
|
||||
pref("identity.fxaccounts.auth.uri", "https://api.accounts.firefox.com/v1");
|
||||
|
|
|
@ -18,4 +18,33 @@ html, body {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#manage, #intro, #stage {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#stage {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.graphic-sync-intro {
|
||||
background-image: url(images/graphic_sync_intro.png);
|
||||
}
|
||||
|
||||
.description, .button-row {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* Retina */
|
||||
@media
|
||||
only screen and ( min--moz-device-pixel-ratio: 2),
|
||||
only screen and ( -moz-min-device-pixel-ratio: 2),
|
||||
only screen and ( min-device-pixel-ratio: 2),
|
||||
only screen and ( min-resolution: 192dpi),
|
||||
only screen and ( min-resolution: 2dppx) {
|
||||
.graphic-sync-intro {
|
||||
background-image: url(images/graphic_sync_intro@2x.png);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,5 +129,53 @@ let wrapper = {
|
|||
},
|
||||
};
|
||||
|
||||
wrapper.init();
|
||||
|
||||
// Button onclick handlers
|
||||
function handleOldSync() {
|
||||
// we just want to navigate the current tab to the new location...
|
||||
window.location = "https://services.mozilla.com/legacysync";
|
||||
}
|
||||
|
||||
function getStarted() {
|
||||
hide("intro");
|
||||
hide("stage");
|
||||
show("remote");
|
||||
}
|
||||
|
||||
function openPrefs() {
|
||||
window.openPreferences("paneSync");
|
||||
}
|
||||
|
||||
function init() {
|
||||
let signinQuery = window.location.href.match(/signin=true$/);
|
||||
|
||||
if (signinQuery) {
|
||||
show("remote");
|
||||
wrapper.init();
|
||||
} else {
|
||||
// Check if we have a local account
|
||||
fxAccounts.getSignedInUser().then(user => {
|
||||
if (user) {
|
||||
show("stage");
|
||||
show("manage");
|
||||
} else {
|
||||
show("stage");
|
||||
show("intro");
|
||||
// load the remote frame in the background
|
||||
wrapper.init();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function show(id) {
|
||||
document.getElementById(id).style.display = 'block';
|
||||
}
|
||||
function hide(id) {
|
||||
document.getElementById(id).style.display = 'none';
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function onload() {
|
||||
document.removeEventListener("DOMContentLoaded", onload, true);
|
||||
init();
|
||||
}, true);
|
||||
|
|
|
@ -9,19 +9,78 @@
|
|||
%brandDTD;
|
||||
<!ENTITY % aboutAccountsDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
|
||||
%aboutAccountsDTD;
|
||||
<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
|
||||
%syncBrandDTD;
|
||||
]>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>&aboutAccounts.pageTitle;</title>
|
||||
<meta name="viewport" content="width=device-width"/>
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" id="favicon"
|
||||
href="chrome://branding/content/icon32.png"/>
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/aboutaccounts/fonts.css"
|
||||
type="text/css" />
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/aboutaccounts/normalize.css"
|
||||
type="text/css" />
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/aboutaccounts/main.css"
|
||||
type="text/css" />
|
||||
<link rel="stylesheet"
|
||||
href="chrome://browser/content/aboutaccounts/aboutaccounts.css"
|
||||
type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="stage">
|
||||
|
||||
<div id="manage">
|
||||
<header>
|
||||
<h1>&aboutAccounts.pageTitle;</h1>
|
||||
|
||||
<h2>&syncBrand.shortName.label;</h2>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="graphic graphic-sync-intro"> </div>
|
||||
|
||||
<div class="button-row">
|
||||
<a class="button" href="#" onclick="openPrefs()">Manage</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="intro">
|
||||
<header>
|
||||
<h1>&aboutAccounts.pageTitle;</h1>
|
||||
|
||||
<h2>&syncBrand.shortName.label;</h2>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<div class="graphic graphic-sync-intro"> </div>
|
||||
|
||||
<div class="description">&aboutAccountsSetup.description;</div>
|
||||
|
||||
<div class="button-row">
|
||||
<a class="button" href="#" onclick="getStarted()">&aboutAccountsSetup.startButton.label;</a>
|
||||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a id="oldsync" href="#" onclick="handleOldSync();">&aboutAccountsSetup.useOldSync.label;</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<iframe mozframetype="content" id="remote" />
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://browser/content/utilityOverlay.js"/>
|
||||
<script type="text/javascript;version=1.8"
|
||||
src="chrome://browser/content/aboutaccounts/aboutaccounts.js" />
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
@font-face {
|
||||
font-family: 'Fira Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Fira Sans'),
|
||||
local('FiraSans'),
|
||||
url('fonts/firasans-regular.woff') format('woff'),
|
||||
/*url('/fonts/latin/firasans-regular.ttf') format('truetype'),*/
|
||||
/*url('/fonts/latin/firasans-regular.svg#Fira Sans') format('svg');*/
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fira Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local('Fira Sans Light'),
|
||||
local('FiraSansLight'),
|
||||
url('fonts/firasans-light.woff') format('woff'),
|
||||
/*url('/fonts/latin/firasans-light.ttf') format('truetype'),*/
|
||||
/*url('/fonts/latin/firasans-light.svg#Fira Sans') format('svg');*/
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Clear Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Clear Sans'),
|
||||
local('ClearSans'),
|
||||
url('fonts/clearsans-regular.woff') format('woff'),
|
||||
/*url('/fonts/latin/clearsans-regular.ttf') format('truetype'),*/
|
||||
/*url('/fonts/latin/clearsans-regular.svg#Clear Sans') format('svg');*/
|
||||
}
|
||||
|
После Ширина: | Высота: | Размер: 2.4 KiB |
После Ширина: | Высота: | Размер: 5.4 KiB |
После Ширина: | Высота: | Размер: 10 KiB |
|
@ -0,0 +1,388 @@
|
|||
/* Border box all the things by default */
|
||||
*, *:before, *:after {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #f1f1f1;
|
||||
background-image: -webkit-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
|
||||
background-image: -moz-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
|
||||
background-image: -ms-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
|
||||
background-image: -o-linear-gradient(-90deg, #fefefe 0%, #f1f1f1 100%);
|
||||
background-image: linear-gradient(-180deg, #fefefe 0%, #f1f1f1 100%);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #424F59;
|
||||
font-family: "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0096DD;
|
||||
cursor: pointer; /* Use the correct cursor for anchors without an href */
|
||||
}
|
||||
|
||||
noscript {
|
||||
color: #f00;
|
||||
display: block;
|
||||
margin: 160px 0 0 0;
|
||||
}
|
||||
|
||||
#stage {
|
||||
background: #fff url(images/fox.png) no-repeat left 50% bottom 30px;
|
||||
background-size: 30px 31px;
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
-webkit-box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.25);;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.25);
|
||||
margin: 60px auto 0;
|
||||
min-height: 550px;
|
||||
opacity: 0;
|
||||
/* padding-bottom = 30px (below icon) + 31px (firefox icon) + 40px (above icon) */
|
||||
padding: 50px 25px 101px 25px;
|
||||
text-align: center;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
header h1, header h2 {
|
||||
font-family: "Fira Sans", Helvetica, Arial, sans-serif;
|
||||
line-height: 1em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin-bottom: 21px;
|
||||
}
|
||||
|
||||
header h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 200;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
section p {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #D63920;
|
||||
color: #fff;
|
||||
margin-bottom: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.error:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.graphic {
|
||||
background-repeat: none;
|
||||
background-size: 150px 130px;
|
||||
height: 130px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
text-indent: 100%;
|
||||
white-space: nowrap;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.graphic-checkbox {
|
||||
background-image: url(../images/graphic_checkbox.png);
|
||||
}
|
||||
|
||||
.graphic-mail {
|
||||
background-image: url(../images/graphic_mail.png);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
margin-bottom: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-row input, .input-row select {
|
||||
border: 1px solid #C0C9D0;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
color: #454f59;
|
||||
font-size: 18px;
|
||||
height: 45px;
|
||||
padding: 0 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-row input::-webkit-input-placeholder {
|
||||
color: #C0C9D0;
|
||||
}
|
||||
|
||||
.input-row input:-moz-placeholder { /* Firefox 18- */
|
||||
color: #C0C9D0;
|
||||
}
|
||||
|
||||
.input-row input::-moz-placeholder { /* Firefox 19+ */
|
||||
color: #C0C9D0;
|
||||
}
|
||||
|
||||
.input-row input:-ms-input-placeholder {
|
||||
color: #C0C9D0;
|
||||
}
|
||||
|
||||
.input-row select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
-moz-user-select: none;
|
||||
appearance: none;
|
||||
background: transparent url(../images/ddarrow_inactive.png) no-repeat right 14px top 50%;
|
||||
background-size: 10px 17px;
|
||||
color: #C0C9D0;
|
||||
height: auto;
|
||||
outline: none;
|
||||
padding: 8px 20px;
|
||||
|
||||
/* Some hackery for Firefox since moz-appearance: none doesn't remove the arrow */
|
||||
text-indent: 0.01px;
|
||||
text-overflow: "";
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-row input:focus, .input-row select:focus {
|
||||
border-color: #6f7a85;
|
||||
}
|
||||
|
||||
.input-row select:focus {
|
||||
background-image: url(../images/ddarrow_active.png);
|
||||
color: #454f59;
|
||||
}
|
||||
|
||||
.input-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-row .input-help {
|
||||
margin-top: 10px;
|
||||
color: #C0C9D0;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 45px;
|
||||
}
|
||||
|
||||
.button-row button, .button-row a.button {
|
||||
background: #E66000;
|
||||
border: none;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
font-size: 24px;
|
||||
padding: 15px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-row a.button {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button-row button:active,
|
||||
.button-row button:hover,
|
||||
.button-row button:focus {
|
||||
background: #FF9500;
|
||||
}
|
||||
|
||||
.button-row button:disabled {
|
||||
background: #C0C9D0;
|
||||
}
|
||||
|
||||
/* Custom rows */
|
||||
.input-row-month-day select {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-bottom: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.links a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.links .left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.links .right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.privacy-links, .privacy-links a {
|
||||
color: #C0C9D0;
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.privacy-links + .button-row {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.verification-email-message {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.password-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.password-row > .password {
|
||||
padding-right: 75px;
|
||||
}
|
||||
|
||||
.password-row > .password + label {
|
||||
border-left: 1px solid #C0C9D0;
|
||||
color: #C0C9D0;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
/* it is very easy to accidentally select the text when clicking */
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.password-row > .password:focus + label {
|
||||
border-color: #6F7A85;
|
||||
}
|
||||
|
||||
.password-row > .password + label > input[type=checkbox] {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.links + .privacy-links {
|
||||
margin: 20px 0 15px 0;
|
||||
}
|
||||
|
||||
.cannot-create-account-content {
|
||||
margin-top: 105px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
-webkit-animation: 0.9s spin infinite linear;
|
||||
-moz-animation: 0.9s spin infinite linear;
|
||||
animation: 0.9s spin infinite linear;
|
||||
background-image: url(../images/spinnerlight.png);
|
||||
background-size: 30px 30px;
|
||||
display: block;
|
||||
margin: 130px auto 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
overflow: hidden;
|
||||
text-indent: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
from {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(365deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes spin {
|
||||
from {
|
||||
-moz-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-moz-transform: rotate(365deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(365deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: These need further consideration */
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Responsiveness */
|
||||
|
||||
/* TODO: Confirm breakpoint sizes */
|
||||
@media only screen and (max-width: 500px) {
|
||||
html {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#stage {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
margin: 0 auto;
|
||||
min-height: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Retina */
|
||||
|
||||
@media
|
||||
only screen and (-webkit-min-device-pixel-ratio: 2),
|
||||
only screen and ( min--moz-device-pixel-ratio: 2),
|
||||
only screen and ( -moz-min-device-pixel-ratio: 2),
|
||||
only screen and ( -o-min-device-pixel-ratio: 2/1),
|
||||
only screen and ( min-device-pixel-ratio: 2),
|
||||
only screen and ( min-resolution: 192dpi),
|
||||
only screen and ( min-resolution: 2dppx) {
|
||||
#stage {
|
||||
background-image: url(../images/fox@2x.png);
|
||||
}
|
||||
|
||||
.graphic-checkbox {
|
||||
background-image: url(../images/graphic_checkbox@2x.png);
|
||||
}
|
||||
|
||||
.graphic-mail {
|
||||
background-image: url(../images/graphic_mail@2x.png);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
background-image: url(../images/spinnerlight@2x.png);
|
||||
}
|
||||
|
||||
.input-row select {
|
||||
background-image: url(../images/ddarrow_inactive@2x.png)
|
||||
}
|
||||
|
||||
.input-row select:focus {
|
||||
background-image: url(../images/ddarrow_active@2x.png);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
/*! normalize.css v2.1.3 | MIT License | git.io/normalize */
|
||||
|
||||
/* ==========================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct `inline-block` display not defined in IE 8/9.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9.
|
||||
* Hide the `template` element in IE, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `outline` inconsistency between Chrome and other browsers.
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address variable `h1` font-size and margin within `section` and `article`
|
||||
* contexts in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address differences between Firefox and other browsers.
|
||||
*/
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address styling not present in IE 8/9.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct font family set oddly in Safari 5 and Chrome.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability of pre-formatted text in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set consistent quote types.
|
||||
*/
|
||||
|
||||
q {
|
||||
quotes: "\201C" "\201D" "\2018" "\2019";
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent and variable font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow displayed oddly in IE 9.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Address margin not present in IE 8/9 and Safari 5.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Define consistent border, margin, and padding.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `color` not being inherited in IE 8/9.
|
||||
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct font family not being inherited in all browsers.
|
||||
* 2. Correct font size not being inherited in all browsers.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
|
||||
* Correct `select` style inheritance in Firefox 4+ and Opera.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||
* 2. Remove excess padding in IE 8/9/10.
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari 5 and Chrome
|
||||
* on OS X.
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove default vertical scrollbar in IE 8/9.
|
||||
* 2. Improve readability and alignment in all browsers.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
|
@ -56,7 +56,6 @@ var FeedHandler = {
|
|||
item.setAttribute("crop", "center");
|
||||
let className = "feed-" + itemNodeType;
|
||||
if (isSubview) {
|
||||
item.setAttribute("tabindex", "0");
|
||||
className += " subviewbutton";
|
||||
}
|
||||
item.setAttribute("class", className);
|
||||
|
|
|
@ -114,7 +114,6 @@
|
|||
<command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
|
||||
<command id="Social:Toggle" oncommand="Social.toggle();" hidden="true"/>
|
||||
<command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
|
||||
<command id="MenuPanel:Toggle" oncommand="PanelUI.toggle(event);"/>
|
||||
</commandset>
|
||||
|
||||
<commandset id="placesCommands">
|
||||
|
@ -406,13 +405,6 @@
|
|||
#endif
|
||||
<key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
|
||||
|
||||
<key id="key_menuButton" command="MenuPanel:Toggle"
|
||||
#ifdef XP_MACOSX
|
||||
key="&toggleMenuPanelMac.key;" modifiers="accel,shift"/>
|
||||
#else
|
||||
key="&toggleMenuPanel.key;" modifiers="accel"/>
|
||||
#endif
|
||||
|
||||
#ifdef XP_GNOME
|
||||
#define NUM_SELECT_TAB_MODIFIER alt
|
||||
#else
|
||||
|
|
|
@ -34,12 +34,12 @@ function preparePendingTab(aCallback) {
|
|||
let tab = gBrowser.addTab(URL);
|
||||
|
||||
whenLoaded(tab.linkedBrowser, function () {
|
||||
let state = SessionStore.getTabState(tab);
|
||||
gBrowser.removeTab(tab);
|
||||
let [{state}] = JSON.parse(SessionStore.getClosedTabData(window));
|
||||
|
||||
tab = gBrowser.addTab("about:blank");
|
||||
whenLoaded(tab.linkedBrowser, function () {
|
||||
SessionStore.setTabState(tab, state);
|
||||
SessionStore.setTabState(tab, JSON.stringify(state));
|
||||
ok(tab.hasAttribute("pending"), "tab should be pending");
|
||||
aCallback(tab);
|
||||
});
|
||||
|
|
|
@ -53,6 +53,13 @@ browser.jar:
|
|||
content/browser/aboutaccounts/aboutaccounts.xhtml (content/aboutaccounts/aboutaccounts.xhtml)
|
||||
content/browser/aboutaccounts/aboutaccounts.js (content/aboutaccounts/aboutaccounts.js)
|
||||
content/browser/aboutaccounts/aboutaccounts.css (content/aboutaccounts/aboutaccounts.css)
|
||||
content/browser/aboutaccounts/main.css (content/aboutaccounts/main.css)
|
||||
content/browser/aboutaccounts/normalize.css (content/aboutaccounts/normalize.css)
|
||||
content/browser/aboutaccounts/fonts.css (content/aboutaccounts/fonts.css)
|
||||
content/browser/aboutaccounts/images/fox.png (content/aboutaccounts/images/fox.png)
|
||||
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/aboutRobots-icon.png (content/aboutRobots-icon.png)
|
||||
content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png)
|
||||
content/browser/aboutSocialError.xhtml (content/aboutSocialError.xhtml)
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
<!-- The parentNode is used so that the footer is presented as the anchor
|
||||
instead of just the button being the anchor. -->
|
||||
<toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
|
||||
exitLabel="&appMenuCustomizeExit.label;" tabindex="0"
|
||||
exitLabel="&appMenuCustomizeExit.label;"
|
||||
oncommand="gCustomizeMode.toggle();"/>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="PanelUI-help" label="&helpMenu.label;" tabindex="0"
|
||||
<toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
|
||||
tooltiptext="&helpMenu.label;"
|
||||
oncommand="PanelUI.showHelpView(this.parentNode);"/>
|
||||
<toolbarseparator/>
|
||||
<toolbarbutton id="PanelUI-quit" tabindex="0"
|
||||
<toolbarbutton id="PanelUI-quit"
|
||||
#ifdef XP_WIN
|
||||
label="&quitApplicationCmdWin.label;"
|
||||
tooltiptext="&quitApplicationCmdWin.label;"
|
||||
|
@ -39,14 +39,14 @@
|
|||
|
||||
<panelview id="PanelUI-history" flex="1">
|
||||
<label value="&appMenuHistory.label;" class="panel-subview-header"/>
|
||||
<toolbarbutton id="appMenuViewHistorySidebar" tabindex="0"
|
||||
<toolbarbutton id="appMenuViewHistorySidebar"
|
||||
label="&appMenuHistory.viewSidebar.label;"
|
||||
type="checkbox"
|
||||
class="subviewbutton"
|
||||
oncommand="toggleSidebar('viewHistorySidebar'); PanelUI.hide();">
|
||||
<observes element="viewHistorySidebar" attribute="checked"/>
|
||||
</toolbarbutton>
|
||||
<toolbarbutton id="appMenuClearRecentHistory" tabindex="0"
|
||||
<toolbarbutton id="appMenuClearRecentHistory"
|
||||
label="&appMenuHistory.clearRecent.label;"
|
||||
class="subviewbutton"
|
||||
command="Tools:Sanitize"/>
|
||||
|
@ -57,7 +57,7 @@
|
|||
oncommand="BrowserOpenSyncTabs();"
|
||||
disabled="true"/>
|
||||
#endif
|
||||
<toolbarbutton id="appMenuRestoreLastSession" tabindex="0"
|
||||
<toolbarbutton id="appMenuRestoreLastSession"
|
||||
label="&appMenuHistory.restoreSession.label;"
|
||||
class="subviewbutton"
|
||||
command="Browser:RestoreLastSession"/>
|
||||
|
@ -67,7 +67,7 @@
|
|||
<vbox id="PanelUI-recentlyClosedWindows" tooltip="bhTooltip"/>
|
||||
<menuseparator id="PanelUI-historyItems-separator"/>
|
||||
<vbox id="PanelUI-historyItems" tooltip="bhTooltip"/>
|
||||
<toolbarbutton id="PanelUI-historyMore" tabindex="0"
|
||||
<toolbarbutton id="PanelUI-historyMore"
|
||||
class="panel-subview-footer subviewbutton"
|
||||
label="&appMenuHistory.showAll.label;"
|
||||
oncommand="PlacesCommandHook.showPlacesOrganizer('History'); CustomizableUI.hidePanelForNode(this);"/>
|
||||
|
|
|
@ -141,12 +141,6 @@ const PanelUI = {
|
|||
let iconAnchor =
|
||||
document.getAnonymousElementByAttribute(anchor, "class",
|
||||
"toolbarbutton-icon");
|
||||
|
||||
// Only focus the panel if it's opened using the keyboard, so that
|
||||
// cut/copy/paste buttons will work for mouse users.
|
||||
let keyboardOpened = aEvent && aEvent.sourceEvent &&
|
||||
aEvent.sourceEvent.target.localName == "key";
|
||||
this.panel.setAttribute("noautofocus", !keyboardOpened);
|
||||
this.panel.openPopup(iconAnchor || anchor, "bottomcenter topright");
|
||||
|
||||
this.panel.addEventListener("popupshown", function onPopupShown() {
|
||||
|
|
|
@ -266,6 +266,10 @@
|
|||
<method name="handleEvent">
|
||||
<parameter name="aEvent"/>
|
||||
<body><![CDATA[
|
||||
if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
|
||||
// Shouldn't act on e.g. context menus being shown from within the panel.
|
||||
return;
|
||||
}
|
||||
switch(aEvent.type) {
|
||||
case "click":
|
||||
if (aEvent.originalTarget == this._clickCapturer) {
|
||||
|
|
|
@ -477,9 +477,12 @@ let CustomizableUIInternal = {
|
|||
}
|
||||
|
||||
this.ensureButtonContextMenu(node, aAreaNode);
|
||||
if (node.localName == "toolbarbutton" && aArea == CustomizableUI.AREA_PANEL) {
|
||||
node.setAttribute("tabindex", "0");
|
||||
node.setAttribute("wrap", "true");
|
||||
if (node.localName == "toolbarbutton") {
|
||||
if (aArea == CustomizableUI.AREA_PANEL) {
|
||||
node.setAttribute("wrap", "true");
|
||||
} else {
|
||||
node.removeAttribute("wrap");
|
||||
}
|
||||
}
|
||||
|
||||
this.insertWidgetBefore(node, currentNode, container, aArea);
|
||||
|
@ -655,7 +658,6 @@ let CustomizableUIInternal = {
|
|||
continue;
|
||||
}
|
||||
this.ensureButtonContextMenu(child, aPanel);
|
||||
child.setAttribute("tabindex", "0");
|
||||
child.setAttribute("wrap", "true");
|
||||
}
|
||||
|
||||
|
@ -701,11 +703,10 @@ let CustomizableUIInternal = {
|
|||
// We remove location attributes here to make sure they're gone too when a
|
||||
// widget is removed from a toolbar to the palette. See bug 930950.
|
||||
this.removeLocationAttributes(widgetNode);
|
||||
widgetNode.removeAttribute("wrap");
|
||||
if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
|
||||
container.removeChild(widgetNode);
|
||||
} else {
|
||||
widgetNode.removeAttribute("tabindex");
|
||||
widgetNode.removeAttribute("wrap");
|
||||
areaNode.toolbox.palette.appendChild(widgetNode);
|
||||
}
|
||||
this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
|
||||
|
@ -854,7 +855,6 @@ let CustomizableUIInternal = {
|
|||
if (isNew) {
|
||||
this.ensureButtonContextMenu(widgetNode, aAreaNode);
|
||||
if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) {
|
||||
widgetNode.setAttribute("tabindex", "0");
|
||||
widgetNode.setAttribute("wrap", "true");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,6 @@ const CustomizableWidgets = [{
|
|||
|
||||
let item = doc.createElementNS(kNSXUL, "toolbarbutton");
|
||||
item.setAttribute("label", title || uri);
|
||||
item.setAttribute("tabindex", "0");
|
||||
item.setAttribute("targetURI", uri);
|
||||
item.setAttribute("class", "subviewbutton");
|
||||
item.addEventListener("command", function (aEvent) {
|
||||
|
@ -256,7 +255,6 @@ const CustomizableWidgets = [{
|
|||
item = doc.createElementNS(kNSXUL, "menuseparator");
|
||||
} else if (node.localName == "menuitem") {
|
||||
item = doc.createElementNS(kNSXUL, "toolbarbutton");
|
||||
item.setAttribute("tabindex", "0");
|
||||
item.setAttribute("class", "subviewbutton");
|
||||
} else {
|
||||
continue;
|
||||
|
@ -367,8 +365,6 @@ const CustomizableWidgets = [{
|
|||
node.appendChild(aDocument.createElementNS(kNSXUL, "separator"));
|
||||
let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton");
|
||||
setAttributes(btnNode, aButton);
|
||||
if (inPanel)
|
||||
btnNode.setAttribute("tabindex", "0");
|
||||
node.appendChild(btnNode);
|
||||
});
|
||||
|
||||
|
|
|
@ -76,37 +76,27 @@ let EventListener = {
|
|||
|
||||
init: function () {
|
||||
addEventListener("load", this, true);
|
||||
addEventListener("pageshow", this, true);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
switch (event.type) {
|
||||
case "load":
|
||||
// Ignore load events from subframes.
|
||||
if (event.target == content.document) {
|
||||
// If we're in the process of restoring, this load may signal
|
||||
// the end of the restoration.
|
||||
let epoch = gContentRestore.getRestoreEpoch();
|
||||
if (epoch) {
|
||||
// Restore the form data and scroll position.
|
||||
gContentRestore.restoreDocument();
|
||||
|
||||
// Ask SessionStore.jsm to trigger SSTabRestored.
|
||||
sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
|
||||
}
|
||||
|
||||
// Send a load message for all loads so we can invalidate the TabStateCache.
|
||||
sendAsyncMessage("SessionStore:load");
|
||||
}
|
||||
break;
|
||||
case "pageshow":
|
||||
if (event.persisted && event.target == content.document)
|
||||
sendAsyncMessage("SessionStore:pageshow");
|
||||
break;
|
||||
default:
|
||||
debug("received unknown event '" + event.type + "'");
|
||||
break;
|
||||
// Ignore load events from subframes.
|
||||
if (event.target != content.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're in the process of restoring, this load may signal
|
||||
// the end of the restoration.
|
||||
let epoch = gContentRestore.getRestoreEpoch();
|
||||
if (epoch) {
|
||||
// Restore the form data and scroll position.
|
||||
gContentRestore.restoreDocument();
|
||||
|
||||
// Ask SessionStore.jsm to trigger SSTabRestored.
|
||||
sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
|
||||
}
|
||||
|
||||
// Send a load message for all loads.
|
||||
sendAsyncMessage("SessionStore:load");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -116,8 +106,6 @@ let EventListener = {
|
|||
let MessageListener = {
|
||||
|
||||
MESSAGES: [
|
||||
"SessionStore:collectSessionHistory",
|
||||
|
||||
"SessionStore:restoreHistory",
|
||||
"SessionStore:restoreTabContent",
|
||||
"SessionStore:resetRestore",
|
||||
|
@ -128,12 +116,7 @@ let MessageListener = {
|
|||
},
|
||||
|
||||
receiveMessage: function ({name, data}) {
|
||||
let id = data ? data.id : 0;
|
||||
switch (name) {
|
||||
case "SessionStore:collectSessionHistory":
|
||||
let history = SessionHistory.collect(docShell);
|
||||
sendAsyncMessage(name, {id: id, data: history});
|
||||
break;
|
||||
case "SessionStore:restoreHistory":
|
||||
let reloadCallback = () => {
|
||||
// Inform SessionStore.jsm about the reload. It will send
|
||||
|
@ -180,12 +163,12 @@ let MessageListener = {
|
|||
};
|
||||
|
||||
/**
|
||||
* If session data must be collected synchronously, we do it via
|
||||
* method calls to this object (rather than via messages to
|
||||
* MessageListener). When using multiple processes, these methods run
|
||||
* in the content process, but the parent synchronously waits on them
|
||||
* using cross-process object wrappers. Without multiple processes, we
|
||||
* still use this code for synchronous collection.
|
||||
* On initialization, this handler gets sent to the parent process as a CPOW.
|
||||
* The parent will use it only to flush pending data from the frame script
|
||||
* when needed, i.e. when closing a tab, closing a window, shutting down, etc.
|
||||
*
|
||||
* This will hopefully not be needed in the future once we have async APIs for
|
||||
* closing windows and tabs.
|
||||
*/
|
||||
let SyncHandler = {
|
||||
init: function () {
|
||||
|
@ -196,10 +179,6 @@ let SyncHandler = {
|
|||
sendSyncMessage("SessionStore:setupSyncHandler", {}, {handler: this});
|
||||
},
|
||||
|
||||
collectSessionHistory: function (includePrivateData) {
|
||||
return SessionHistory.collect(docShell);
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is used to make the tab process flush all data that
|
||||
* hasn't been sent to the parent process, yet.
|
||||
|
@ -225,22 +204,57 @@ let SyncHandler = {
|
|||
}
|
||||
};
|
||||
|
||||
let ProgressListener = {
|
||||
init: function() {
|
||||
let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
|
||||
/**
|
||||
* Listens for changes to the session history. Whenever the user navigates
|
||||
* we will collect URLs and everything belonging to session history.
|
||||
*
|
||||
* Causes a SessionStore:update message to be sent that contains the current
|
||||
* session history.
|
||||
*
|
||||
* Example:
|
||||
* {entries: [{url: "about:mozilla", ...}, ...], index: 1}
|
||||
*/
|
||||
let SessionHistoryListener = {
|
||||
init: function () {
|
||||
gFrameTree.addObserver(this);
|
||||
addEventListener("load", this, true);
|
||||
addEventListener("hashchange", this, true);
|
||||
Services.obs.addObserver(this, "browser:purge-session-history", false);
|
||||
},
|
||||
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
|
||||
// We are changing page, so time to invalidate the state of the tab
|
||||
sendAsyncMessage("SessionStore:loadStart");
|
||||
|
||||
uninit: function () {
|
||||
Services.obs.removeObserver(this, "browser:purge-session-history");
|
||||
},
|
||||
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {},
|
||||
onProgressChange: function() {},
|
||||
onStatusChange: function() {},
|
||||
onSecurityChange: function() {},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference])
|
||||
|
||||
observe: function () {
|
||||
// We need to use setTimeout() here because we listen for
|
||||
// "browser:purge-session-history". When that is fired all observers are
|
||||
// expected to purge their data. We can't expect to be called *after* the
|
||||
// observer in browser.xml that clears session history so we need to wait
|
||||
// a tick before actually collecting data.
|
||||
setTimeout(() => this.collect(), 0);
|
||||
},
|
||||
|
||||
handleEvent: function (event) {
|
||||
// We are only interested in "load" events from subframes.
|
||||
if (event.type == "hashchange" || event.target != content.document) {
|
||||
this.collect();
|
||||
}
|
||||
},
|
||||
|
||||
collect: function () {
|
||||
if (docShell) {
|
||||
MessageQueue.push("history", () => SessionHistory.collect(docShell));
|
||||
}
|
||||
},
|
||||
|
||||
onFrameTreeCollected: function () {
|
||||
this.collect();
|
||||
},
|
||||
|
||||
onFrameTreeReset: function () {
|
||||
this.collect();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -370,9 +384,7 @@ let PageStyleListener = {
|
|||
|
||||
onFrameTreeReset: function () {
|
||||
MessageQueue.push("pageStyle", () => null);
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -456,9 +468,7 @@ let SessionStorageListener = {
|
|||
|
||||
onFrameTreeReset: function () {
|
||||
this.collect();
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -657,8 +667,8 @@ EventListener.init();
|
|||
MessageListener.init();
|
||||
FormDataListener.init();
|
||||
SyncHandler.init();
|
||||
ProgressListener.init();
|
||||
PageStyleListener.init();
|
||||
SessionHistoryListener.init();
|
||||
SessionStorageListener.init();
|
||||
ScrollPositionListener.init();
|
||||
DocShellCapabilitiesListener.init();
|
||||
|
@ -668,6 +678,7 @@ addEventListener("unload", () => {
|
|||
// Remove all registered nsIObservers.
|
||||
PageStyleListener.uninit();
|
||||
SessionStorageListener.uninit();
|
||||
SessionHistoryListener.uninit();
|
||||
|
||||
// We don't need to take care of any gFrameTree observers as the gFrameTree
|
||||
// will die with the content script. The same goes for the privacy transition
|
||||
|
|
|
@ -1,71 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Messenger"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://gre/modules/Timer.jsm", this);
|
||||
|
||||
/**
|
||||
* The external API exported by this module.
|
||||
*/
|
||||
this.Messenger = Object.freeze({
|
||||
send: function (tab, type, options = {}) {
|
||||
return MessengerInternal.send(tab, type, options);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A module that handles communication between the main and content processes.
|
||||
*/
|
||||
let MessengerInternal = {
|
||||
// The id of the last message we sent. This is used to assign a unique ID to
|
||||
// every message we send to handle multiple responses from the same browser.
|
||||
_latestMessageID: 0,
|
||||
|
||||
/**
|
||||
* Sends a message to the given tab and waits for a response.
|
||||
*
|
||||
* @param tab
|
||||
* tabbrowser tab
|
||||
* @param type
|
||||
* {string} the type of the message
|
||||
* @param options (optional)
|
||||
* {timeout: int} to set the timeout in milliseconds
|
||||
* @return {Promise} A promise that will resolve to the response message or
|
||||
* be reject when timing out.
|
||||
*/
|
||||
send: function (tab, type, options = {}) {
|
||||
let browser = tab.linkedBrowser;
|
||||
let mm = browser.messageManager;
|
||||
let deferred = Promise.defer();
|
||||
let id = ++this._latestMessageID;
|
||||
let timeout;
|
||||
|
||||
function onMessage({data: {id: mid, data}}) {
|
||||
if (mid == id) {
|
||||
mm.removeMessageListener(type, onMessage);
|
||||
clearTimeout(timeout);
|
||||
deferred.resolve(data);
|
||||
}
|
||||
}
|
||||
|
||||
mm.addMessageListener(type, onMessage);
|
||||
mm.sendAsyncMessage(type, {id: id});
|
||||
|
||||
function onTimeout() {
|
||||
mm.removeMessageListener(type, onMessage);
|
||||
deferred.reject(new Error("Timed out while waiting for a " + type + " " +
|
||||
"response message."));
|
||||
}
|
||||
|
||||
let delay = (options && options.timeout) || 5000;
|
||||
timeout = setTimeout(onTimeout, delay);
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
|
@ -234,22 +234,8 @@ let SessionSaverInternal = {
|
|||
// Allow scheduling delayed saves again.
|
||||
this._timeoutID = null;
|
||||
|
||||
// Check whether asynchronous data collection is disabled.
|
||||
if (!Services.prefs.getBoolPref("browser.sessionstore.async")) {
|
||||
this._saveState();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the last save time to make sure we wait at least another interval
|
||||
// length until we call _saveStateAsync() again.
|
||||
this.updateLastSaveTime();
|
||||
|
||||
// Save state synchronously after all tab caches have been filled. The data
|
||||
// for the tab caches is collected asynchronously. We will reuse this
|
||||
// cached data if the tab hasn't been invalidated in the meantime. In that
|
||||
// case we will just fall back to synchronous data collection for single
|
||||
// tabs.
|
||||
SessionStore.fillTabCachesAsynchronously().then(() => this._saveState());
|
||||
// Write to disk.
|
||||
this._saveState();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,15 +49,6 @@ const WINDOW_HIDEABLE_FEATURES = [
|
|||
];
|
||||
|
||||
const MESSAGES = [
|
||||
// The content script has received a pageshow event. This happens when a
|
||||
// page is loaded from bfcache without any network activity, i.e. when
|
||||
// clicking the back or forward button.
|
||||
"SessionStore:pageshow",
|
||||
|
||||
// The content script tells us that a new page just started loading in a
|
||||
// browser.
|
||||
"SessionStore:loadStart",
|
||||
|
||||
// The content script gives us a reference to an object that performs
|
||||
// synchronous collection of session data.
|
||||
"SessionStore:setupSyncHandler",
|
||||
|
@ -66,7 +57,7 @@ const MESSAGES = [
|
|||
// be saved to disk.
|
||||
"SessionStore:update",
|
||||
|
||||
// A "load" event happened. Invalidate the TabStateCache.
|
||||
// A "load" event happened.
|
||||
"SessionStore:load",
|
||||
|
||||
// The restoreHistory code has run. This is a good time to run SSTabRestoring.
|
||||
|
@ -95,11 +86,6 @@ const TAB_EVENTS = [
|
|||
"TabUnpinned"
|
||||
];
|
||||
|
||||
// Browser events observed.
|
||||
const BROWSER_EVENTS = [
|
||||
"SwapDocShells", "UserTypedValueChanged"
|
||||
];
|
||||
|
||||
// The number of milliseconds in a day
|
||||
const MS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;
|
||||
|
||||
|
@ -121,14 +107,13 @@ XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
|
||||
"resource:///modules/sessionstore/GlobalState.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
|
||||
"resource:///modules/sessionstore/Messenger.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
|
||||
"resource:///modules/sessionstore/PrivacyFilter.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
||||
"resource:///modules/RecentWindow.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
|
||||
"resource:///modules/sessionstore/GlobalState.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
|
||||
"resource:///modules/sessionstore/PrivacyFilter.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
|
||||
"resource:///modules/devtools/scratchpad-manager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
|
||||
|
@ -288,10 +273,6 @@ this.SessionStore = {
|
|||
return SessionStoreInternal.getCurrentState(aUpdateAll);
|
||||
},
|
||||
|
||||
fillTabCachesAsynchronously: function () {
|
||||
return SessionStoreInternal.fillTabCachesAsynchronously();
|
||||
},
|
||||
|
||||
/**
|
||||
* Backstage pass to implementation details, used for testing purpose.
|
||||
* Controlled by preference "browser.sessionstore.testmode".
|
||||
|
@ -609,12 +590,6 @@ let SessionStoreInternal = {
|
|||
var win = browser.ownerDocument.defaultView;
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "SessionStore:pageshow":
|
||||
this.onTabLoad(win, browser);
|
||||
break;
|
||||
case "SessionStore:loadStart":
|
||||
TabStateCache.delete(browser);
|
||||
break;
|
||||
case "SessionStore:setupSyncHandler":
|
||||
TabState.setSyncHandler(browser, aMessage.objects.handler);
|
||||
break;
|
||||
|
@ -624,7 +599,6 @@ let SessionStoreInternal = {
|
|||
this.saveStateDelayed(win);
|
||||
break;
|
||||
case "SessionStore:load":
|
||||
TabStateCache.delete(browser);
|
||||
this.onTabLoad(win, browser);
|
||||
break;
|
||||
case "SessionStore:restoreHistoryComplete":
|
||||
|
@ -738,22 +712,6 @@ let SessionStoreInternal = {
|
|||
var win = aEvent.currentTarget.ownerDocument.defaultView;
|
||||
let browser;
|
||||
switch (aEvent.type) {
|
||||
case "SwapDocShells":
|
||||
browser = aEvent.currentTarget;
|
||||
let otherBrowser = aEvent.detail;
|
||||
TabState.onBrowserContentsSwapped(browser, otherBrowser);
|
||||
TabStateCache.onBrowserContentsSwapped(browser, otherBrowser);
|
||||
break;
|
||||
case "UserTypedValueChanged":
|
||||
browser = aEvent.currentTarget;
|
||||
if (browser.userTypedValue) {
|
||||
TabStateCache.updateField(browser, "userTypedValue", browser.userTypedValue);
|
||||
TabStateCache.updateField(browser, "userTypedClear", browser.userTypedClear);
|
||||
} else {
|
||||
TabStateCache.removeField(browser, "userTypedValue");
|
||||
TabStateCache.removeField(browser, "userTypedClear");
|
||||
}
|
||||
break;
|
||||
case "TabOpen":
|
||||
this.onTabAdd(win, aEvent.originalTarget);
|
||||
break;
|
||||
|
@ -773,13 +731,7 @@ let SessionStoreInternal = {
|
|||
this.onTabHide(win, aEvent.originalTarget);
|
||||
break;
|
||||
case "TabPinned":
|
||||
// If possible, update cached data without having to invalidate it
|
||||
TabStateCache.updateField(aEvent.originalTarget, "pinned", true);
|
||||
this.saveStateDelayed(win);
|
||||
break;
|
||||
case "TabUnpinned":
|
||||
// If possible, update cached data without having to invalidate it
|
||||
TabStateCache.updateField(aEvent.originalTarget, "pinned", false);
|
||||
this.saveStateDelayed(win);
|
||||
break;
|
||||
}
|
||||
|
@ -1222,7 +1174,6 @@ let SessionStoreInternal = {
|
|||
let openWindows = {};
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
|
||||
TabStateCache.delete(aTab);
|
||||
delete aTab.linkedBrowser.__SS_data;
|
||||
if (aTab.linkedBrowser.__SS_restoreState)
|
||||
this._resetTabRestoringState(aTab);
|
||||
|
@ -1340,10 +1291,7 @@ let SessionStoreInternal = {
|
|||
* bool Do not save state if we're updating an existing tab
|
||||
*/
|
||||
onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
|
||||
let browser = aTab.linkedBrowser;
|
||||
BROWSER_EVENTS.forEach(msg => browser.addEventListener(msg, this, true));
|
||||
|
||||
let mm = browser.messageManager;
|
||||
let mm = aTab.linkedBrowser.messageManager;
|
||||
MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
|
||||
|
||||
// Load the frame script after registering listeners.
|
||||
|
@ -1367,8 +1315,6 @@ let SessionStoreInternal = {
|
|||
*/
|
||||
onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
|
||||
let browser = aTab.linkedBrowser;
|
||||
BROWSER_EVENTS.forEach(msg => browser.removeEventListener(msg, this, true));
|
||||
|
||||
let mm = browser.messageManager;
|
||||
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
|
||||
|
||||
|
@ -1412,7 +1358,7 @@ let SessionStoreInternal = {
|
|||
TabState.flush(aTab.linkedBrowser);
|
||||
|
||||
// Get the latest data for this tab (generally, from the cache)
|
||||
let tabState = TabState.collectSync(aTab);
|
||||
let tabState = TabState.collect(aTab);
|
||||
|
||||
// Don't save private tabs
|
||||
let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
|
||||
|
@ -1448,8 +1394,6 @@ let SessionStoreInternal = {
|
|||
* Browser reference
|
||||
*/
|
||||
onTabLoad: function ssi_onTabLoad(aWindow, aBrowser) {
|
||||
// react on "load" and solitary "pageshow" events (the first "pageshow"
|
||||
// following "load" is too late for deleting the data caches)
|
||||
// It's possible to get a load event after calling stop on a browser (when
|
||||
// overwriting tabs). We want to return early if the tab hasn't been restored yet.
|
||||
if (aBrowser.__SS_restoreState &&
|
||||
|
@ -1457,8 +1401,6 @@ let SessionStoreInternal = {
|
|||
return;
|
||||
}
|
||||
|
||||
TabStateCache.delete(aBrowser);
|
||||
|
||||
delete aBrowser.__SS_data;
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
||||
|
@ -1499,9 +1441,6 @@ let SessionStoreInternal = {
|
|||
this.restoreNextTab();
|
||||
}
|
||||
|
||||
// If possible, update cached data without having to invalidate it
|
||||
TabStateCache.updateField(aTab, "hidden", false);
|
||||
|
||||
// Default delay of 2 seconds gives enough time to catch multiple TabShow
|
||||
// events due to changing groups in Panorama.
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
@ -1514,9 +1453,6 @@ let SessionStoreInternal = {
|
|||
TabRestoreQueue.visibleToHidden(aTab);
|
||||
}
|
||||
|
||||
// If possible, update cached data without having to invalidate it
|
||||
TabStateCache.updateField(aTab, "hidden", true);
|
||||
|
||||
// Default delay of 2 seconds gives enough time to catch multiple TabHide
|
||||
// events due to changing groups in Panorama.
|
||||
this.saveStateDelayed(aWindow);
|
||||
|
@ -1526,10 +1462,8 @@ let SessionStoreInternal = {
|
|||
// On the first gather-telemetry notification of the session,
|
||||
// gather telemetry data.
|
||||
Services.obs.removeObserver(this, "gather-telemetry");
|
||||
this.fillTabCachesAsynchronously().then(function() {
|
||||
let stateString = SessionStore.getBrowserState();
|
||||
return SessionFile.gatherTelemetry(stateString);
|
||||
});
|
||||
let stateString = SessionStore.getBrowserState();
|
||||
return SessionFile.gatherTelemetry(stateString);
|
||||
},
|
||||
|
||||
/* ........ nsISessionStore API .............. */
|
||||
|
@ -1623,7 +1557,7 @@ let SessionStoreInternal = {
|
|||
throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
|
||||
}
|
||||
|
||||
let tabState = TabState.collectSync(aTab);
|
||||
let tabState = TabState.collect(aTab);
|
||||
|
||||
return this._toJSONString(tabState);
|
||||
},
|
||||
|
@ -1656,7 +1590,6 @@ let SessionStoreInternal = {
|
|||
this._resetTabRestoringState(aTab);
|
||||
}
|
||||
|
||||
TabStateCache.delete(aTab);
|
||||
this._setWindowStateBusy(window);
|
||||
this.restoreTabs(window, [aTab], [tabState], 0);
|
||||
},
|
||||
|
@ -1861,7 +1794,6 @@ let SessionStoreInternal = {
|
|||
}
|
||||
|
||||
saveTo[aKey] = aStringValue;
|
||||
TabStateCache.updateField(aTab, "extData", saveTo);
|
||||
this.saveStateDelayed(aTab.ownerDocument.defaultView);
|
||||
},
|
||||
|
||||
|
@ -1879,15 +1811,6 @@ let SessionStoreInternal = {
|
|||
|
||||
if (deleteFrom && aKey in deleteFrom) {
|
||||
delete deleteFrom[aKey];
|
||||
|
||||
// Keep the extData object only if it is not empty, to save
|
||||
// a little disk space when serializing the tab state later.
|
||||
if (Object.keys(deleteFrom).length) {
|
||||
TabStateCache.updateField(aTab, "extData", deleteFrom);
|
||||
} else {
|
||||
TabStateCache.removeField(aTab, "extData");
|
||||
}
|
||||
|
||||
this.saveStateDelayed(aTab.ownerDocument.defaultView);
|
||||
}
|
||||
},
|
||||
|
@ -1908,7 +1831,6 @@ let SessionStoreInternal = {
|
|||
|
||||
persistTabAttribute: function ssi_persistTabAttribute(aName) {
|
||||
if (TabAttributes.persist(aName)) {
|
||||
TabStateCache.clear();
|
||||
this.saveStateDelayed();
|
||||
}
|
||||
},
|
||||
|
@ -2086,68 +2008,6 @@ let SessionStoreInternal = {
|
|||
return [true, canOverwriteTabs];
|
||||
},
|
||||
|
||||
/* ........ Async Data Collection .............. */
|
||||
|
||||
/**
|
||||
* Kicks off asynchronous data collection for all tabs that do not have any
|
||||
* cached data. The returned promise will only notify that the tab collection
|
||||
* has been finished without resolving to any data. The tab collection for a
|
||||
* a few or all tabs might have failed or timed out. By calling
|
||||
* fillTabCachesAsynchronously() and waiting for the promise to be resolved
|
||||
* before calling getCurrentState(), callers ensure that most of the data
|
||||
* should have been collected asynchronously, without blocking the main
|
||||
* thread.
|
||||
*
|
||||
* @return {Promise} the promise that is fulfilled when the tab data is ready
|
||||
*/
|
||||
fillTabCachesAsynchronously: function () {
|
||||
let countdown = 0;
|
||||
let deferred = Promise.defer();
|
||||
let activeWindow = this._getMostRecentBrowserWindow();
|
||||
|
||||
// The callback that will be called when a promise has been resolved
|
||||
// successfully, i.e. the tab data has been collected.
|
||||
function done() {
|
||||
if (--countdown === 0) {
|
||||
deferred.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// The callback that will be called when a promise is rejected, i.e. we
|
||||
// we couldn't collect the tab data because of a script error or a timeout.
|
||||
function fail(reason) {
|
||||
debug("Failed collecting tab data asynchronously: " + reason);
|
||||
done();
|
||||
}
|
||||
|
||||
this._forEachBrowserWindow(win => {
|
||||
if (!this._isWindowLoaded(win)) {
|
||||
// Bail out if the window hasn't even loaded, yet.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DirtyWindows.has(win) && win != activeWindow) {
|
||||
// Bail out if the window is not dirty and inactive.
|
||||
return;
|
||||
}
|
||||
|
||||
for (let tab of win.gBrowser.tabs) {
|
||||
if (!tab.closing && !TabStateCache.has(tab)) {
|
||||
countdown++;
|
||||
TabState.collect(tab).then(done, fail);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If no dirty tabs were found, return a resolved
|
||||
// promise because there is nothing to do here.
|
||||
if (countdown == 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/* ........ Saving Functionality .............. */
|
||||
|
||||
/**
|
||||
|
@ -2323,7 +2183,7 @@ let SessionStoreInternal = {
|
|||
|
||||
// update the internal state data for this window
|
||||
for (let tab of tabs) {
|
||||
tabsData.push(TabState.collectSync(tab));
|
||||
tabsData.push(TabState.collect(tab));
|
||||
}
|
||||
winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
|
||||
|
||||
|
@ -2703,10 +2563,6 @@ let SessionStoreInternal = {
|
|||
Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
|
||||
}
|
||||
|
||||
// Any data that's in the process of being collected for this tab will be
|
||||
// out of date now that we're restoring it.
|
||||
TabState.dropPendingCollections(browser);
|
||||
|
||||
if (!tabData.entries) {
|
||||
tabData.entries = [];
|
||||
}
|
||||
|
@ -2735,7 +2591,7 @@ let SessionStoreInternal = {
|
|||
// message. If a message is received that relates to a previous epoch, we
|
||||
// discard it.
|
||||
let epoch = this._nextRestoreEpoch++;
|
||||
this._browserEpochs.set(browser, epoch);
|
||||
this._browserEpochs.set(browser.permanentKey, epoch);
|
||||
|
||||
// keep the data around to prevent dataloss in case
|
||||
// a tab gets closed before it's been properly restored
|
||||
|
@ -2745,7 +2601,8 @@ let SessionStoreInternal = {
|
|||
tab.setAttribute("pending", "true");
|
||||
|
||||
// Update the persistent tab state cache with |tabData| information.
|
||||
TabStateCache.updatePersistent(browser, {
|
||||
TabStateCache.update(browser, {
|
||||
history: {entries: tabData.entries, index: tabData.index},
|
||||
scroll: tabData.scroll || null,
|
||||
storage: tabData.storage || null,
|
||||
formdata: tabData.formdata || null,
|
||||
|
@ -3600,7 +3457,7 @@ let SessionStoreInternal = {
|
|||
|
||||
// The browser is no longer in any sort of restoring state.
|
||||
delete browser.__SS_restoreState;
|
||||
this._browserEpochs.delete(browser);
|
||||
this._browserEpochs.delete(browser.permanentKey);
|
||||
|
||||
aTab.removeAttribute("pending");
|
||||
browser.removeAttribute("pending");
|
||||
|
@ -3632,7 +3489,7 @@ let SessionStoreInternal = {
|
|||
* with respect to |browser|.
|
||||
*/
|
||||
isCurrentEpoch: function (browser, epoch) {
|
||||
return this._browserEpochs.get(browser, 0) == epoch;
|
||||
return this._browserEpochs.get(browser.permanentKey, 0) == epoch;
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
@ -14,16 +14,12 @@ Cu.import("resource://gre/modules/Task.jsm", this);
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
|
||||
"resource:///modules/sessionstore/Messenger.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
|
||||
"resource:///modules/sessionstore/PrivacyFilter.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
|
||||
"resource:///modules/sessionstore/TabStateCache.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
|
||||
"resource:///modules/sessionstore/TabAttributes.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
"resource:///modules/sessionstore/Utils.jsm");
|
||||
|
||||
/**
|
||||
* Module that contains tab state collection methods.
|
||||
|
@ -33,10 +29,6 @@ this.TabState = Object.freeze({
|
|||
TabStateInternal.setSyncHandler(browser, handler);
|
||||
},
|
||||
|
||||
onBrowserContentsSwapped: function (browser, otherBrowser) {
|
||||
TabStateInternal.onBrowserContentsSwapped(browser, otherBrowser);
|
||||
},
|
||||
|
||||
update: function (browser, data) {
|
||||
TabStateInternal.update(browser, data);
|
||||
},
|
||||
|
@ -53,24 +45,12 @@ this.TabState = Object.freeze({
|
|||
return TabStateInternal.collect(tab);
|
||||
},
|
||||
|
||||
collectSync: function (tab) {
|
||||
return TabStateInternal.collectSync(tab);
|
||||
},
|
||||
|
||||
clone: function (tab) {
|
||||
return TabStateInternal.clone(tab);
|
||||
},
|
||||
|
||||
dropPendingCollections: function (browser) {
|
||||
TabStateInternal.dropPendingCollections(browser);
|
||||
}
|
||||
});
|
||||
|
||||
let TabStateInternal = {
|
||||
// A map (xul:browser -> promise) that keeps track of tabs and
|
||||
// their promises when collecting tab data asynchronously.
|
||||
_pendingCollections: new WeakMap(),
|
||||
|
||||
// A map (xul:browser -> handler) that maps a tab to the
|
||||
// synchronous collection handler object for that tab.
|
||||
// See SyncHandler in content-sessionStore.js.
|
||||
|
@ -84,8 +64,8 @@ let TabStateInternal = {
|
|||
* Install the sync handler object from a given tab.
|
||||
*/
|
||||
setSyncHandler: function (browser, handler) {
|
||||
this._syncHandlers.set(browser, handler);
|
||||
this._latestMessageID.set(browser, 0);
|
||||
this._syncHandlers.set(browser.permanentKey, handler);
|
||||
this._latestMessageID.set(browser.permanentKey, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -95,9 +75,9 @@ let TabStateInternal = {
|
|||
// Only ever process messages that have an ID higher than the last one we
|
||||
// saw. This ensures we don't use stale data that has already been received
|
||||
// synchronously.
|
||||
if (id > this._latestMessageID.get(browser)) {
|
||||
this._latestMessageID.set(browser, id);
|
||||
TabStateCache.updatePersistent(browser, data);
|
||||
if (id > this._latestMessageID.get(browser.permanentKey)) {
|
||||
this._latestMessageID.set(browser.permanentKey, id);
|
||||
TabStateCache.update(browser, data);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -105,9 +85,9 @@ let TabStateInternal = {
|
|||
* Flushes all data currently queued in the given browser's content script.
|
||||
*/
|
||||
flush: function (browser) {
|
||||
if (this._syncHandlers.has(browser)) {
|
||||
let lastID = this._latestMessageID.get(browser);
|
||||
this._syncHandlers.get(browser).flush(lastID);
|
||||
if (this._syncHandlers.has(browser.permanentKey)) {
|
||||
let lastID = this._latestMessageID.get(browser.permanentKey);
|
||||
this._syncHandlers.get(browser.permanentKey).flush(lastID);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -120,98 +100,6 @@ let TabStateInternal = {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When a docshell swap happens, a xul:browser element will be
|
||||
* associated with a different content-sessionStore.js script
|
||||
* global. In this case, the sync handler for the element needs to
|
||||
* be swapped just like the docshell.
|
||||
*/
|
||||
onBrowserContentsSwapped: function (browser, otherBrowser) {
|
||||
// Data collected while docShells have been swapped should not go into
|
||||
// the TabStateCache. Collections will most probably time out but we want
|
||||
// to make sure.
|
||||
this.dropPendingCollections(browser);
|
||||
this.dropPendingCollections(otherBrowser);
|
||||
|
||||
// Swap data stored per-browser.
|
||||
[this._syncHandlers, this._latestMessageID]
|
||||
.forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
|
||||
},
|
||||
|
||||
/**
|
||||
* Collect data related to a single tab, asynchronously.
|
||||
*
|
||||
* @param tab
|
||||
* tabbrowser tab
|
||||
*
|
||||
* @returns {Promise} A promise that will resolve to a TabData instance.
|
||||
*/
|
||||
collect: function (tab) {
|
||||
if (!tab) {
|
||||
throw new TypeError("Expecting a tab");
|
||||
}
|
||||
|
||||
// Don't collect if we don't need to.
|
||||
if (TabStateCache.has(tab)) {
|
||||
return Promise.resolve(TabStateCache.get(tab));
|
||||
}
|
||||
|
||||
// If the tab was recently added, or if it's being restored, we
|
||||
// just collect basic data about it and skip the cache.
|
||||
if (!this._tabNeedsExtraCollection(tab)) {
|
||||
let tabData = this._collectBaseTabData(tab);
|
||||
return Promise.resolve(tabData);
|
||||
}
|
||||
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
let promise = Task.spawn(function task() {
|
||||
// Collect session history data asynchronously.
|
||||
let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
|
||||
|
||||
// The tab could have been closed while waiting for a response.
|
||||
if (!tab.linkedBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect basic tab data, without session history and storage.
|
||||
let tabData = this._collectBaseTabData(tab);
|
||||
|
||||
// Apply collected data.
|
||||
tabData.entries = history.entries;
|
||||
if ("index" in history) {
|
||||
tabData.index = history.index;
|
||||
}
|
||||
|
||||
// If we're still the latest async collection for the given tab and
|
||||
// the cache hasn't been filled by collect() in the meantime, let's
|
||||
// fill the cache with the data we received.
|
||||
if (this._pendingCollections.get(browser) == promise) {
|
||||
TabStateCache.set(tab, tabData);
|
||||
this._pendingCollections.delete(browser);
|
||||
}
|
||||
|
||||
// Copy data from the persistent cache. We need to create an explicit
|
||||
// copy of the |tabData| object so that the properties injected by
|
||||
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
|
||||
// The persistent cache does not store "null" values, so any values that
|
||||
// have been cleared by the frame script would not be overriden by
|
||||
// |_copyFromPersistentCache|. These two caches are only an interim
|
||||
// solution and the non-persistent one will go away soon.
|
||||
tabData = Utils.copy(tabData);
|
||||
this._copyFromPersistentCache(tab, tabData);
|
||||
|
||||
throw new Task.Result(tabData);
|
||||
}.bind(this));
|
||||
|
||||
// Save the current promise as the latest asynchronous collection that is
|
||||
// running. This will be used to check whether the collected data is still
|
||||
// valid and will be used to fill the tab state cache.
|
||||
this._pendingCollections.set(browser, promise);
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Collect data related to a single tab, synchronously.
|
||||
*
|
||||
|
@ -220,61 +108,10 @@ let TabStateInternal = {
|
|||
*
|
||||
* @returns {TabData} An object with the data for this tab. If the
|
||||
* tab has not been invalidated since the last call to
|
||||
* collectSync(aTab), the same object is returned.
|
||||
* collect(aTab), the same object is returned.
|
||||
*/
|
||||
collectSync: function (tab) {
|
||||
if (!tab) {
|
||||
throw new TypeError("Expecting a tab");
|
||||
}
|
||||
if (TabStateCache.has(tab)) {
|
||||
// Copy data from the persistent cache. We need to create an explicit
|
||||
// copy of the |tabData| object so that the properties injected by
|
||||
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
|
||||
// The persistent cache does not store "null" values, so any values that
|
||||
// have been cleared by the frame script would not be overriden by
|
||||
// |_copyFromPersistentCache|. These two caches are only an interim
|
||||
// solution and the non-persistent one will go away soon.
|
||||
let tabData = Utils.copy(TabStateCache.get(tab));
|
||||
this._copyFromPersistentCache(tab, tabData);
|
||||
return tabData;
|
||||
}
|
||||
|
||||
let tabData = this._collectSyncUncached(tab);
|
||||
|
||||
if (this._tabCachingAllowed(tab)) {
|
||||
TabStateCache.set(tab, tabData);
|
||||
}
|
||||
|
||||
// Copy data from the persistent cache. We need to create an explicit
|
||||
// copy of the |tabData| object so that the properties injected by
|
||||
// |_copyFromPersistentCache| don't end up in the non-persistent cache.
|
||||
// The persistent cache does not store "null" values, so any values that
|
||||
// have been cleared by the frame script would not be overriden by
|
||||
// |_copyFromPersistentCache|. These two caches are only an interim
|
||||
// solution and the non-persistent one will go away soon.
|
||||
tabData = Utils.copy(tabData);
|
||||
this._copyFromPersistentCache(tab, tabData);
|
||||
|
||||
// Prevent all running asynchronous collections from filling the cache.
|
||||
// Every asynchronous data collection started before a collectSync() call
|
||||
// can't expect to retrieve different data than the sync call. That's why
|
||||
// we just fill the cache with the data collected from the sync call and
|
||||
// discard any data collected asynchronously.
|
||||
this.dropPendingCollections(tab.linkedBrowser);
|
||||
|
||||
return tabData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Drop any pending calls to TabState.collect. These calls will
|
||||
* continue to run, but they won't store their results in the
|
||||
* TabStateCache.
|
||||
*
|
||||
* @param browser
|
||||
* xul:browser
|
||||
*/
|
||||
dropPendingCollections: function (browser) {
|
||||
this._pendingCollections.delete(browser);
|
||||
collect: function (tab) {
|
||||
return this._collectBaseTabData(tab);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -289,155 +126,7 @@ let TabStateInternal = {
|
|||
* up-to-date.
|
||||
*/
|
||||
clone: function (tab) {
|
||||
let options = {includePrivateData: true};
|
||||
let tabData = this._collectSyncUncached(tab, options);
|
||||
|
||||
// Copy data from the persistent cache.
|
||||
this._copyFromPersistentCache(tab, tabData, options);
|
||||
|
||||
return tabData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronously collect all session data for a tab. The
|
||||
* TabStateCache is not consulted, and the resulting data is not put
|
||||
* in the cache.
|
||||
*/
|
||||
_collectSyncUncached: function (tab, options = {}) {
|
||||
// Collect basic tab data, without session history and storage.
|
||||
let tabData = this._collectBaseTabData(tab);
|
||||
|
||||
// If we don't need any other data, return what we have.
|
||||
if (!this._tabNeedsExtraCollection(tab)) {
|
||||
return tabData;
|
||||
}
|
||||
|
||||
// In multiprocess Firefox, there is a small window of time after
|
||||
// tab creation when we haven't received a sync handler object. In
|
||||
// this case the tab shouldn't have any history or cookie data, so we
|
||||
// just return the base data already collected.
|
||||
if (!this._syncHandlers.has(tab.linkedBrowser)) {
|
||||
return tabData;
|
||||
}
|
||||
|
||||
let syncHandler = this._syncHandlers.get(tab.linkedBrowser);
|
||||
|
||||
let includePrivateData = options && options.includePrivateData;
|
||||
|
||||
let history;
|
||||
try {
|
||||
history = syncHandler.collectSessionHistory(includePrivateData);
|
||||
} catch (e) {
|
||||
// This may happen if the tab has crashed.
|
||||
console.error(e);
|
||||
return tabData;
|
||||
}
|
||||
|
||||
tabData.entries = history.entries;
|
||||
if ("index" in history) {
|
||||
tabData.index = history.index;
|
||||
}
|
||||
|
||||
return tabData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy tab data for the given |tab| from the persistent cache to |tabData|.
|
||||
*
|
||||
* @param tab (xul:tab)
|
||||
* The tab belonging to the given |tabData| object.
|
||||
* @param tabData (object)
|
||||
* The tab data belonging to the given |tab|.
|
||||
* @param options (object)
|
||||
* {includePrivateData: true} to always include private data
|
||||
*/
|
||||
_copyFromPersistentCache: function (tab, tabData, options = {}) {
|
||||
let data = TabStateCache.getPersistent(tab.linkedBrowser);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The caller may explicitly request to omit privacy checks.
|
||||
let includePrivateData = options && options.includePrivateData;
|
||||
|
||||
for (let key of Object.keys(data)) {
|
||||
let value = data[key];
|
||||
|
||||
// Filter sensitive data according to the current privacy level.
|
||||
if (!includePrivateData) {
|
||||
if (key === "storage") {
|
||||
value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
|
||||
} else if (key === "formdata") {
|
||||
value = PrivacyFilter.filterFormData(value, tab.pinned);
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
tabData[key] = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns true if the xul:tab element is newly added (i.e., if it's
|
||||
* showing about:blank with no history).
|
||||
*/
|
||||
_tabIsNew: function (tab) {
|
||||
let browser = tab.linkedBrowser;
|
||||
return (!browser || !browser.currentURI);
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns true if the xul:tab element is in the process of being
|
||||
* restored.
|
||||
*/
|
||||
_tabIsRestoring: function (tab) {
|
||||
return !!tab.linkedBrowser.__SS_data;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function returns true if we need to collect history, page
|
||||
* style, and text and scroll data from the tab. Normally we do. The
|
||||
* cases when we don't are:
|
||||
* 1. the tab is about:blank with no history, or
|
||||
* 2. the tab is waiting to be restored.
|
||||
*
|
||||
* @param tab A xul:tab element.
|
||||
* @returns True if the tab is in the process of being restored.
|
||||
*/
|
||||
_tabNeedsExtraCollection: function (tab) {
|
||||
if (this._tabIsNew(tab)) {
|
||||
// Tab is about:blank with no history.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._tabIsRestoring(tab)) {
|
||||
// Tab is waiting to be restored.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise we need the extra data.
|
||||
return true;
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns true if we should cache the tabData for the given the
|
||||
* xul:tab element.
|
||||
*/
|
||||
_tabCachingAllowed: function (tab) {
|
||||
if (this._tabIsNew(tab)) {
|
||||
// No point in caching data for newly created tabs.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._tabIsRestoring(tab)) {
|
||||
// If the tab is being restored, we just return the data being
|
||||
// restored. This data may be incomplete (if supplied by
|
||||
// setBrowserState, for example), so we don't want to cache it.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return this._collectBaseTabData(tab, {includePrivateData: true});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -445,10 +134,12 @@ let TabStateInternal = {
|
|||
*
|
||||
* @param tab
|
||||
* tabbrowser tab
|
||||
* @param options (object)
|
||||
* {includePrivateData: true} to always include private data
|
||||
*
|
||||
* @returns {object} An object with the basic data for this tab.
|
||||
*/
|
||||
_collectBaseTabData: function (tab) {
|
||||
_collectBaseTabData: function (tab, options) {
|
||||
let tabData = {entries: [], lastAccessed: tab.lastAccessed };
|
||||
let browser = tab.linkedBrowser;
|
||||
|
||||
|
@ -507,6 +198,53 @@ let TabStateInternal = {
|
|||
else if (tabData.extData)
|
||||
delete tabData.extData;
|
||||
|
||||
// Copy data from the tab state cache only if the tab has fully finished
|
||||
// restoring. We don't want to overwrite data contained in __SS_data.
|
||||
this._copyFromCache(tab, tabData, options);
|
||||
|
||||
return tabData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy tab data for the given |tab| from the cache to |tabData|.
|
||||
*
|
||||
* @param tab (xul:tab)
|
||||
* The tab belonging to the given |tabData| object.
|
||||
* @param tabData (object)
|
||||
* The tab data belonging to the given |tab|.
|
||||
* @param options (object)
|
||||
* {includePrivateData: true} to always include private data
|
||||
*/
|
||||
_copyFromCache: function (tab, tabData, options = {}) {
|
||||
let data = TabStateCache.get(tab.linkedBrowser);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The caller may explicitly request to omit privacy checks.
|
||||
let includePrivateData = options && options.includePrivateData;
|
||||
|
||||
for (let key of Object.keys(data)) {
|
||||
let value = data[key];
|
||||
|
||||
// Filter sensitive data according to the current privacy level.
|
||||
if (!includePrivateData) {
|
||||
if (key === "storage") {
|
||||
value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
|
||||
} else if (key === "formdata") {
|
||||
value = PrivacyFilter.filterFormData(value, tab.pinned);
|
||||
}
|
||||
}
|
||||
|
||||
if (key === "history") {
|
||||
tabData.entries = value.entries;
|
||||
|
||||
if (value.hasOwnProperty("index")) {
|
||||
tabData.index = value.index;
|
||||
}
|
||||
} else if (value) {
|
||||
tabData[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,13 +6,6 @@
|
|||
|
||||
this.EXPORTED_SYMBOLS = ["TabStateCache"];
|
||||
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/Services.jsm", this);
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
"resource:///modules/sessionstore/Utils.jsm");
|
||||
|
||||
/**
|
||||
* A cache for tabs data.
|
||||
*
|
||||
|
@ -25,272 +18,55 @@ XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
|||
*/
|
||||
this.TabStateCache = Object.freeze({
|
||||
/**
|
||||
* Tells whether an entry is in the cache.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
* @return {bool} Whether there's a cached entry for the given tab.
|
||||
*/
|
||||
has: function (aTab) {
|
||||
return TabStateCacheInternal.has(aTab);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add or replace an entry in the cache.
|
||||
*
|
||||
* @param {XULElement} aTab The key, which may be either a tab
|
||||
* or the corresponding browser. The binding will disappear
|
||||
* if the tab/browser is destroyed.
|
||||
* @param {*} aValue The data associated to |aTab|.
|
||||
*/
|
||||
set: function(aTab, aValue) {
|
||||
return TabStateCacheInternal.set(aTab, aValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the tab data associated with a tab.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
*
|
||||
* @return {*|undefined} The data if available, |undefined|
|
||||
* otherwise.
|
||||
*/
|
||||
get: function(aKey) {
|
||||
return TabStateCacheInternal.get(aKey);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the tab data associated with a tab.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
*
|
||||
* Noop of there is no tab data associated with the tab.
|
||||
*/
|
||||
delete: function(aKey) {
|
||||
return TabStateCacheInternal.delete(aKey);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete all tab data.
|
||||
*/
|
||||
clear: function() {
|
||||
return TabStateCacheInternal.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update in place a piece of data.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
* If the tab/browser is not present, do nothing.
|
||||
* @param {string} aField The field to update.
|
||||
* @param {*} aValue The new value to place in the field.
|
||||
*/
|
||||
updateField: function(aKey, aField, aValue) {
|
||||
return TabStateCacheInternal.updateField(aKey, aField, aValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a given field from a cached tab state.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
* If the tab/browser is not present, do nothing.
|
||||
* @param {string} aField The field to remove.
|
||||
*/
|
||||
removeField: function(aKey, aField) {
|
||||
return TabStateCacheInternal.removeField(aKey, aField);
|
||||
},
|
||||
|
||||
/**
|
||||
* Swap cached data for two given browsers.
|
||||
*
|
||||
* @param {xul:browser} browser
|
||||
* The first of the two browsers that swapped docShells.
|
||||
* @param {xul:browser} otherBrowser
|
||||
* The second of the two browsers that swapped docShells.
|
||||
*/
|
||||
onBrowserContentsSwapped: function(browser, otherBrowser) {
|
||||
TabStateCacheInternal.onBrowserContentsSwapped(browser, otherBrowser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves persistently cached data for a given |browser|.
|
||||
* Retrieves cached data for a given |browser|.
|
||||
*
|
||||
* @param browser (xul:browser)
|
||||
* The browser to retrieve cached data for.
|
||||
* @return (object)
|
||||
* The persistently cached data stored for the given |browser|.
|
||||
* The cached data stored for the given |browser|.
|
||||
*/
|
||||
getPersistent: function (browser) {
|
||||
return TabStateCacheInternal.getPersistent(browser);
|
||||
get: function (browser) {
|
||||
return TabStateCacheInternal.get(browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates persistently cached data for a given |browser|. This data is
|
||||
* persistently in the sense that we never clear it, it will always be
|
||||
* overwritten.
|
||||
* Updates cached data for a given |browser|.
|
||||
*
|
||||
* @param browser (xul:browser)
|
||||
* The browser belonging to the given tab data.
|
||||
* @param newData (object)
|
||||
* The new data to be stored for the given |browser|.
|
||||
*/
|
||||
updatePersistent: function (browser, newData) {
|
||||
TabStateCacheInternal.updatePersistent(browser, newData);
|
||||
},
|
||||
|
||||
/**
|
||||
* Total number of cache hits during the session.
|
||||
*/
|
||||
get hits() {
|
||||
return TabStateCacheTelemetry.hits;
|
||||
},
|
||||
|
||||
/**
|
||||
* Total number of cache misses during the session.
|
||||
*/
|
||||
get misses() {
|
||||
return TabStateCacheTelemetry.misses;
|
||||
},
|
||||
|
||||
/**
|
||||
* Total number of cache clears during the session.
|
||||
*/
|
||||
get clears() {
|
||||
return TabStateCacheTelemetry.clears;
|
||||
},
|
||||
update: function (browser, newData) {
|
||||
TabStateCacheInternal.update(browser, newData);
|
||||
}
|
||||
});
|
||||
|
||||
let TabStateCacheInternal = {
|
||||
_data: new WeakMap(),
|
||||
_persistentData: new WeakMap(),
|
||||
|
||||
/**
|
||||
* Tells whether an entry is in the cache.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
* @return {bool} Whether there's a cached entry for the given tab.
|
||||
*/
|
||||
has: function (aTab) {
|
||||
let key = this._normalizeToBrowser(aTab);
|
||||
return this._data.has(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add or replace an entry in the cache.
|
||||
*
|
||||
* @param {XULElement} aTab The key, which may be either a tab
|
||||
* or the corresponding browser. The binding will disappear
|
||||
* if the tab/browser is destroyed.
|
||||
* @param {*} aValue The data associated to |aTab|.
|
||||
*/
|
||||
set: function(aTab, aValue) {
|
||||
let key = this._normalizeToBrowser(aTab);
|
||||
this._data.set(key, aValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the tab data associated with a tab.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
*
|
||||
* @return {*|undefined} The data if available, |undefined|
|
||||
* otherwise.
|
||||
*/
|
||||
get: function(aKey) {
|
||||
let key = this._normalizeToBrowser(aKey);
|
||||
let result = this._data.get(key);
|
||||
TabStateCacheTelemetry.recordAccess(!!result);
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the tab data associated with a tab.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
*
|
||||
* Noop of there is no tab data associated with the tab.
|
||||
*/
|
||||
delete: function(aKey) {
|
||||
let key = this._normalizeToBrowser(aKey);
|
||||
this._data.delete(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete all tab data.
|
||||
*/
|
||||
clear: function() {
|
||||
TabStateCacheTelemetry.recordClear();
|
||||
this._data.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update in place a piece of data.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
* If the tab/browser is not present, do nothing.
|
||||
* @param {string} aField The field to update.
|
||||
* @param {*} aValue The new value to place in the field.
|
||||
*/
|
||||
updateField: function(aKey, aField, aValue) {
|
||||
let key = this._normalizeToBrowser(aKey);
|
||||
let data = this._data.get(key);
|
||||
if (data) {
|
||||
data[aField] = aValue;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a given field from a cached tab state.
|
||||
*
|
||||
* @param {XULElement} aKey The tab or the associated browser.
|
||||
* If the tab/browser is not present, do nothing.
|
||||
* @param {string} aField The field to remove.
|
||||
*/
|
||||
removeField: function(aKey, aField) {
|
||||
let key = this._normalizeToBrowser(aKey);
|
||||
let data = this._data.get(key);
|
||||
if (data && aField in data) {
|
||||
delete data[aField];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Swap cached data for two given browsers.
|
||||
*
|
||||
* @param {xul:browser} browser
|
||||
* The first of the two browsers that swapped docShells.
|
||||
* @param {xul:browser} otherBrowser
|
||||
* The second of the two browsers that swapped docShells.
|
||||
*/
|
||||
onBrowserContentsSwapped: function(browser, otherBrowser) {
|
||||
// Swap data stored per-browser.
|
||||
[this._data, this._persistentData]
|
||||
.forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves persistently cached data for a given |browser|.
|
||||
* Retrieves cached data for a given |browser|.
|
||||
*
|
||||
* @param browser (xul:browser)
|
||||
* The browser to retrieve cached data for.
|
||||
* @return (object)
|
||||
* The persistently cached data stored for the given |browser|.
|
||||
* The cached data stored for the given |browser|.
|
||||
*/
|
||||
getPersistent: function (browser) {
|
||||
return this._persistentData.get(browser);
|
||||
get: function (browser) {
|
||||
return this._data.get(browser.permanentKey);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates persistently cached data for a given |browser|. This data is
|
||||
* persistent in the sense that we never clear it, it will always be
|
||||
* overwritten.
|
||||
* Updates cached data for a given |browser|.
|
||||
*
|
||||
* @param browser (xul:browser)
|
||||
* The browser belonging to the given tab data.
|
||||
* @param newData (object)
|
||||
* The new data to be stored for the given |browser|.
|
||||
*/
|
||||
updatePersistent: function (browser, newData) {
|
||||
let data = this._persistentData.get(browser) || {};
|
||||
update: function (browser, newData) {
|
||||
let data = this._data.get(browser.permanentKey) || {};
|
||||
|
||||
for (let key of Object.keys(newData)) {
|
||||
let value = newData[key];
|
||||
|
@ -301,83 +77,6 @@ let TabStateCacheInternal = {
|
|||
}
|
||||
}
|
||||
|
||||
this._persistentData.set(browser, data);
|
||||
},
|
||||
|
||||
_normalizeToBrowser: function(aKey) {
|
||||
let nodeName = aKey.localName;
|
||||
if (nodeName == "tab") {
|
||||
return aKey.linkedBrowser;
|
||||
}
|
||||
if (nodeName == "browser") {
|
||||
return aKey;
|
||||
}
|
||||
throw new TypeError("Key is neither a tab nor a browser: " + nodeName);
|
||||
}
|
||||
};
|
||||
|
||||
let TabStateCacheTelemetry = {
|
||||
// Total number of hits during the session
|
||||
hits: 0,
|
||||
// Total number of misses during the session
|
||||
misses: 0,
|
||||
// Total number of clears during the session
|
||||
clears: 0,
|
||||
// |true| once we have been initialized
|
||||
_initialized: false,
|
||||
|
||||
/**
|
||||
* Record a cache access.
|
||||
*
|
||||
* @param {boolean} isHit If |true|, the access was a hit, otherwise
|
||||
* a miss.
|
||||
*/
|
||||
recordAccess: function(isHit) {
|
||||
this._init();
|
||||
if (isHit) {
|
||||
++this.hits;
|
||||
} else {
|
||||
++this.misses;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Record a cache clear
|
||||
*/
|
||||
recordClear: function() {
|
||||
this._init();
|
||||
++this.clears;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the telemetry.
|
||||
*/
|
||||
_init: function() {
|
||||
if (this._initialized) {
|
||||
// Avoid double initialization
|
||||
return;
|
||||
}
|
||||
this._initialized = true;
|
||||
Services.obs.addObserver(this, "profile-before-change", false);
|
||||
},
|
||||
|
||||
observe: function() {
|
||||
Services.obs.removeObserver(this, "profile-before-change");
|
||||
|
||||
// Record hit/miss rate
|
||||
let accesses = this.hits + this.misses;
|
||||
if (accesses == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fillHistogram("HIT_RATE", this.hits, accesses);
|
||||
this._fillHistogram("CLEAR_RATIO", this.clears, accesses);
|
||||
},
|
||||
|
||||
_fillHistogram: function(suffix, positive, total) {
|
||||
let PREFIX = "FX_SESSION_RESTORE_TABSTATECACHE_";
|
||||
let histo = Services.telemetry.getHistogramById(PREFIX + suffix);
|
||||
let rate = Math.floor( ( positive * 100 ) / total );
|
||||
histo.add(rate);
|
||||
this._data.set(browser.permanentKey, data);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -41,39 +41,5 @@ this.Utils = Object.freeze({
|
|||
let prevChar = host[index - 1];
|
||||
return (index == (host.length - domain.length)) &&
|
||||
(prevChar == "." || prevChar == "/");
|
||||
},
|
||||
|
||||
swapMapEntries: function (map, key, otherKey) {
|
||||
// Make sure that one or the other of these has an entry in the map,
|
||||
// and let it be |key|.
|
||||
if (!map.has(key)) {
|
||||
[key, otherKey] = [otherKey, key];
|
||||
if (!map.has(key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, |key| is guaranteed to have an entry,
|
||||
// although |otherKey| may not. Perform the swap.
|
||||
let value = map.get(key);
|
||||
if (map.has(otherKey)) {
|
||||
let otherValue = map.get(otherKey);
|
||||
map.set(key, otherValue);
|
||||
map.set(otherKey, value);
|
||||
} else {
|
||||
map.set(otherKey, value);
|
||||
map.delete(key);
|
||||
}
|
||||
},
|
||||
|
||||
// Copies all properties of a given object to a new one and returns it.
|
||||
copy: function (from) {
|
||||
let to = {};
|
||||
|
||||
for (let key of Object.keys(from)) {
|
||||
to[key] = from[key];
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,7 +18,6 @@ EXTRA_JS_MODULES = [
|
|||
'FormData.jsm',
|
||||
'FrameTree.jsm',
|
||||
'GlobalState.jsm',
|
||||
'Messenger.jsm',
|
||||
'PageStyle.jsm',
|
||||
'PrivacyFilter.jsm',
|
||||
'PrivacyLevel.jsm',
|
||||
|
|
|
@ -64,13 +64,12 @@ support-files =
|
|||
[browser_global_store.js]
|
||||
[browser_label_and_icon.js]
|
||||
[browser_merge_closed_tabs.js]
|
||||
[browser_pageshow.js]
|
||||
[browser_pageStyle.js]
|
||||
[browser_privatetabs.js]
|
||||
[browser_scrollPositions.js]
|
||||
[browser_sessionHistory.js]
|
||||
[browser_sessionStorage.js]
|
||||
[browser_swapDocShells.js]
|
||||
[browser_tabStateCache.js]
|
||||
[browser_telemetry.js]
|
||||
[browser_upgrade_backup.js]
|
||||
[browser_windowRestore_perwindowpb.js]
|
||||
|
@ -149,7 +148,6 @@ skip-if = true
|
|||
[browser_618151.js]
|
||||
[browser_623779.js]
|
||||
[browser_624727.js]
|
||||
[browser_625257.js]
|
||||
[browser_628270.js]
|
||||
[browser_635418.js]
|
||||
[browser_636279.js]
|
||||
|
|
|
@ -18,6 +18,7 @@ add_task(function test_set_tabstate() {
|
|||
yield promiseBrowserLoaded(tab.linkedBrowser);
|
||||
|
||||
// get the tab's state
|
||||
SyncHandlers.get(tab.linkedBrowser).flush();
|
||||
let state = ss.getTabState(tab);
|
||||
ok(state, "get the tab's state");
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ function test() {
|
|||
|
||||
whenBrowserLoaded(newWin.gBrowser.selectedBrowser, function() {
|
||||
// get the sessionstore state for the window
|
||||
SyncHandlers.get(newWin.gBrowser.selectedBrowser).flush();
|
||||
let state = ss.getWindowState(newWin);
|
||||
|
||||
// verify our cookie got set during pageload
|
||||
|
|
|
@ -18,18 +18,16 @@ function test() {
|
|||
|
||||
ss.setTabState(tab, JSON.stringify(tabState));
|
||||
whenTabRestored(tab, function() {
|
||||
SyncHandlers.get(tab.linkedBrowser).flush();
|
||||
tabState = JSON.parse(ss.getTabState(tab));
|
||||
is(tabState.entries.length, max_entries, "session history filled to the limit");
|
||||
is(tabState.entries[0].url, baseURL + 0, "... but not more");
|
||||
|
||||
// visit yet another anchor (appending it to session history)
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
let event = doc.createEvent("MouseEvents");
|
||||
event.initMouseEvent("click", true, true, doc.defaultView, 1,
|
||||
0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
doc.querySelector("a").dispatchEvent(event);
|
||||
tab.linkedBrowser.contentDocument.querySelector("a").click();
|
||||
|
||||
function check() {
|
||||
SyncHandlers.get(tab.linkedBrowser).flush();
|
||||
tabState = JSON.parse(ss.getTabState(tab));
|
||||
if (tabState.entries[tabState.entries.length - 1].url != baseURL + "end") {
|
||||
// It may take a few passes through the event loop before we
|
||||
|
|
|
@ -89,6 +89,7 @@ function test() {
|
|||
history.pushState({obj2:2}, "title-obj2", "?page2");
|
||||
history.replaceState({obj3:/^a$/}, "title-obj3");
|
||||
|
||||
SyncHandlers.get(tab.linkedBrowser).flush();
|
||||
let state = ss.getTabState(tab);
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
|
|
|
@ -1,79 +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/. */
|
||||
|
||||
let Scope = {};
|
||||
Cu.import("resource://gre/modules/Task.jsm", Scope);
|
||||
Cu.import("resource://gre/modules/Promise.jsm", Scope);
|
||||
let {Task, Promise} = Scope;
|
||||
|
||||
|
||||
// This tests that a tab which is closed while loading is not lost.
|
||||
// Specifically, that session store does not rely on an invalid cache when
|
||||
// constructing data for a tab which is loading.
|
||||
|
||||
// This test steps through the following parts:
|
||||
// 1. Tab has been created is loading URI_TO_LOAD.
|
||||
// 2. Before URI_TO_LOAD finishes loading, browser.currentURI has changed and
|
||||
// tab is scheduled to be removed.
|
||||
// 3. After the tab has been closed, undoCloseTab() has been called and the tab
|
||||
// should fully load.
|
||||
const URI_TO_LOAD = "about:mozilla";
|
||||
|
||||
function waitForLoadStarted(aTab) {
|
||||
return promiseContentMessage(aTab.linkedBrowser, "SessionStore:loadStart");
|
||||
}
|
||||
|
||||
function waitForTabLoaded(aTab) {
|
||||
let deferred = Promise.defer();
|
||||
whenBrowserLoaded(aTab.linkedBrowser, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function waitForTabClosed() {
|
||||
let deferred = Promise.defer();
|
||||
let observer = function() {
|
||||
gBrowser.tabContainer.removeEventListener("TabClose", observer, true);
|
||||
deferred.resolve();
|
||||
};
|
||||
gBrowser.tabContainer.addEventListener("TabClose", observer, true);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Task.spawn(function() {
|
||||
try {
|
||||
// Open a new tab
|
||||
let tab = gBrowser.addTab("about:blank");
|
||||
yield waitForTabLoaded(tab);
|
||||
|
||||
// Trigger a save state, to initialize any caches
|
||||
ss.getBrowserState();
|
||||
|
||||
is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
|
||||
|
||||
// Start a load and interrupt it by closing the tab
|
||||
tab.linkedBrowser.loadURI(URI_TO_LOAD);
|
||||
yield waitForLoadStarted(tab);
|
||||
|
||||
let tabClosing = waitForTabClosed();
|
||||
gBrowser.removeTab(tab);
|
||||
info("Now waiting for TabClose to close");
|
||||
yield tabClosing;
|
||||
|
||||
// Undo the tab, ensure that it proceeds with loading
|
||||
tab = ss.undoCloseTab(window, 0);
|
||||
yield waitForTabLoaded(tab);
|
||||
is(tab.linkedBrowser.currentURI.spec, URI_TO_LOAD, "loading proceeded as expected");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
executeSoon(finish);
|
||||
} catch (ex) {
|
||||
ok(false, ex);
|
||||
info(ex.stack);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -27,6 +27,7 @@ function test() {
|
|||
whenChildCount(entry, 1, function () {
|
||||
whenChildCount(entry, 2, function () {
|
||||
whenBrowserLoaded(browser, function () {
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 1, "tab has one history entry");
|
||||
ok(!entries[0].children, "history entry has no subframes");
|
||||
|
|
|
@ -24,6 +24,7 @@ function runTests() {
|
|||
// Open a second tab and close the first one.
|
||||
let tab = win.gBrowser.addTab("about:mozilla");
|
||||
yield whenBrowserLoaded(tab.linkedBrowser);
|
||||
SyncHandlers.get(tab.linkedBrowser).flush();
|
||||
win.gBrowser.removeTab(win.gBrowser.tabs[0]);
|
||||
|
||||
// Make sure our window is still tracked by sessionstore
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
TestRunner.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* This test ensures that loading a page from bfcache (by going back or forward
|
||||
* in history) marks the window as dirty and causes data about the tab that
|
||||
* changed to be re-collected.
|
||||
*
|
||||
* We will do this by creating a tab with two history entries and going back
|
||||
* to the first. When we now request the current browser state from the
|
||||
* session store service the first history entry must be selected.
|
||||
*/
|
||||
|
||||
const URL = "data:text/html,<h1>first</h1>";
|
||||
const URL2 = "data:text/html,<h1>second</h1>";
|
||||
|
||||
function runTests() {
|
||||
// Create a dummy window that is regarded as active. We need to do this
|
||||
// because we always collect data for tabs of active windows no matter if
|
||||
// the window is dirty or not.
|
||||
let win = OpenBrowserWindow();
|
||||
yield whenDelayedStartupFinished(win, next);
|
||||
|
||||
// Create a tab with two history entries.
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
|
||||
yield loadURI(URL);
|
||||
yield loadURI(URL2);
|
||||
|
||||
// All windows currently marked as dirty will be written to disk
|
||||
// and thus marked clean afterwards.
|
||||
yield forceWriteState();
|
||||
|
||||
// Go back to 'about:robots' - which is loaded from the bfcache and thus
|
||||
// will not fire a 'load' event but a 'pageshow' event with persisted=true.
|
||||
waitForPageShow();
|
||||
yield gBrowser.selectedBrowser.goBack();
|
||||
is(tab.linkedBrowser.currentURI.spec, URL, "correct url after going back");
|
||||
|
||||
// If by receiving the 'pageshow' event the first window has correctly
|
||||
// been marked as dirty, getBrowserState() should return the tab we created
|
||||
// with the right history entry (about:robots) selected.
|
||||
let state = JSON.parse(ss.getBrowserState());
|
||||
is(state.windows[0].tabs[1].index, 1, "first history entry is selected");
|
||||
|
||||
// Clean up after ourselves.
|
||||
gBrowser.removeTab(tab);
|
||||
win.close();
|
||||
}
|
||||
|
||||
function forceWriteState() {
|
||||
const PREF = "browser.sessionstore.interval";
|
||||
const TOPIC = "sessionstore-state-write";
|
||||
|
||||
Services.obs.addObserver(function observe() {
|
||||
Services.obs.removeObserver(observe, TOPIC);
|
||||
Services.prefs.clearUserPref(PREF);
|
||||
executeSoon(next);
|
||||
}, TOPIC, false);
|
||||
|
||||
Services.prefs.setIntPref(PREF, 0);
|
||||
}
|
||||
|
||||
function loadURI(aURI) {
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
waitForLoad(browser);
|
||||
browser.loadURI(aURI);
|
||||
}
|
||||
|
||||
function waitForLoad(aElement) {
|
||||
aElement.addEventListener("load", function onLoad() {
|
||||
aElement.removeEventListener("load", onLoad, true);
|
||||
executeSoon(next);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function waitForPageShow() {
|
||||
let mm = gBrowser.selectedBrowser.messageManager;
|
||||
|
||||
mm.addMessageListener("SessionStore:pageshow", function onPageShow() {
|
||||
mm.removeMessageListener("SessionStore:pageshow", onPageShow);
|
||||
executeSoon(next);
|
||||
});
|
||||
}
|
|
@ -30,6 +30,7 @@ add_task(function() {
|
|||
yield promiseBrowserLoaded(tab2.linkedBrowser);
|
||||
|
||||
info("Flush to make sure chrome received all data.");
|
||||
SyncHandlers.get(tab1.linkedBrowser).flush();
|
||||
SyncHandlers.get(tab2.linkedBrowser).flush();
|
||||
|
||||
info("Checking out state");
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Ensure that starting a load invalidates shistory.
|
||||
*/
|
||||
add_task(function test_load_start() {
|
||||
// Create a new tab.
|
||||
let tab = gBrowser.addTab("about:blank");
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Load a new URI but remove the tab before it has finished loading.
|
||||
browser.loadURI("about:mozilla");
|
||||
yield promiseContentMessage(browser, "ss-test:onFrameTreeReset");
|
||||
gBrowser.removeTab(tab);
|
||||
|
||||
// Undo close the tab.
|
||||
tab = ss.undoCloseTab(window, 0);
|
||||
browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Check that the correct URL was restored.
|
||||
is(browser.currentURI.spec, "about:mozilla", "url is correct");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensure that purging shistory invalidates.
|
||||
*/
|
||||
add_task(function test_purge() {
|
||||
// Create a new tab.
|
||||
let tab = gBrowser.addTab("about:mozilla");
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Create a second shistory entry.
|
||||
browser.loadURI("about:robots");
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Check that we now have two shistory entries.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 2, "there are two shistory entries");
|
||||
|
||||
// Purge session history.
|
||||
yield sendMessage(browser, "ss-test:purgeSessionHistory");
|
||||
|
||||
// Check that we are left with a single shistory entry.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 1, "there is one shistory entry");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensure that anchor navigation invalidates shistory.
|
||||
*/
|
||||
add_task(function test_hashchange() {
|
||||
const URL = "data:text/html;charset=utf-8,<a id=a href=%23>clickme</a>";
|
||||
|
||||
// Create a new tab.
|
||||
let tab = gBrowser.addTab(URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Check that we start with a single shistory entry.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 1, "there is one shistory entry");
|
||||
|
||||
// Click the link and wait for a hashchange event.
|
||||
browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a"});
|
||||
yield promiseContentMessage(browser, "ss-test:hashchange");
|
||||
|
||||
// Check that we now have two shistory entries.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 2, "there are two shistory entries");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensure that loading pages from the bfcache invalidates shistory.
|
||||
*/
|
||||
add_task(function test_pageshow() {
|
||||
const URL = "data:text/html;charset=utf-8,<h1>first</h1>";
|
||||
const URL2 = "data:text/html;charset=utf-8,<h1>second</h1>";
|
||||
|
||||
// Create a new tab.
|
||||
let tab = gBrowser.addTab(URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Create a second shistory entry.
|
||||
browser.loadURI(URL2);
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Go back to the previous url which is loaded from the bfcache.
|
||||
browser.goBack();
|
||||
yield promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
|
||||
is(browser.currentURI.spec, URL, "correct url after going back");
|
||||
|
||||
// Check that loading from bfcache did invalidate shistory.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {index} = JSON.parse(ss.getTabState(tab));
|
||||
is(index, 1, "first history entry is selected");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
|
||||
/**
|
||||
* Ensure that subframe navigation invalidates shistory.
|
||||
*/
|
||||
add_task(function test_subframes() {
|
||||
const URL = "data:text/html;charset=utf-8," +
|
||||
"<iframe src=http%3A//example.com/ name=t></iframe>" +
|
||||
"<a id=a1 href=http%3A//example.com/1 target=t>clickme</a>" +
|
||||
"<a id=a2 href=http%3A//example.com/%23 target=t>clickme</a>";
|
||||
|
||||
// Create a new tab.
|
||||
let tab = gBrowser.addTab(URL);
|
||||
let browser = tab.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser);
|
||||
|
||||
// Check that we have a single shistory entry.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 1, "there is one shistory entry");
|
||||
is(entries[0].children.length, 1, "the entry has one child");
|
||||
|
||||
// Navigate the subframe.
|
||||
browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a1"});
|
||||
yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
|
||||
|
||||
// Check shistory.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 2, "there now are two shistory entries");
|
||||
is(entries[1].children.length, 1, "the second entry has one child");
|
||||
|
||||
// Go back in history.
|
||||
browser.goBack();
|
||||
yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
|
||||
|
||||
// Navigate the subframe again.
|
||||
browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a2"});
|
||||
yield promiseContentMessage(browser, "ss-test:hashchange");
|
||||
|
||||
// Check shistory.
|
||||
SyncHandlers.get(browser).flush();
|
||||
let {entries} = JSON.parse(ss.getTabState(tab));
|
||||
is(entries.length, 2, "there now are two shistory entries");
|
||||
is(entries[1].children.length, 1, "the second entry has one child");
|
||||
|
||||
// Cleanup.
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
|
@ -1,139 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
let wrapper = {};
|
||||
Cu.import("resource:///modules/sessionstore/TabStateCache.jsm", wrapper);
|
||||
let {TabStateCache} = wrapper;
|
||||
|
||||
// The number of tabs that are present in the browser but that we're not dealing
|
||||
// with. This should be one (for an empty about:blank), but let's not make this
|
||||
// a magic number.
|
||||
let numberOfUntrackedTabs;
|
||||
|
||||
// Arbitrary URL prefix, used to generate the URL of pages we visit
|
||||
const URL_PREFIX = "http://example.org:80/";
|
||||
|
||||
/**
|
||||
* Check tab state cache telemetry statistics before and after an operation.
|
||||
*
|
||||
* @param {function} f The operation being measured. If it returns a promise,
|
||||
* we wait until the promise is resolved before proceeding.
|
||||
* @return {promise}
|
||||
*/
|
||||
function getTelemetryDelta(f) {
|
||||
return Task.spawn(function() {
|
||||
let KEYS = ["hits", "misses", "clears"];
|
||||
let old = {};
|
||||
for (let key of KEYS) {
|
||||
old[key] = TabStateCache[key];
|
||||
}
|
||||
yield f();
|
||||
let result = {};
|
||||
for (let key of KEYS) {
|
||||
result[key] = TabStateCache[key] - old[key];
|
||||
}
|
||||
ok(result.hits >= 0, "Sanity check: hits have not decreased");
|
||||
ok(result.misses >= 0, "Sanity check: misses have not decreased");
|
||||
ok(result.clears >= 0, "Sanity check: clears have not decreased");
|
||||
throw new Task.Result(result);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function init() {
|
||||
// Start with an empty cache
|
||||
closeAllButPrimaryWindow();
|
||||
TabStateCache.clear();
|
||||
numberOfUntrackedTabs = gBrowser.tabs.length;
|
||||
info("Starting with " + numberOfUntrackedTabs + " tabs");
|
||||
});
|
||||
|
||||
add_task(function add_remove() {
|
||||
info("Adding the first tab");
|
||||
// Initialize one tab, save to initialize cache
|
||||
let tab1 = gBrowser.addTab(URL_PREFIX + "?tab1");
|
||||
yield promiseBrowserLoaded(tab1.linkedBrowser);
|
||||
yield getTelemetryDelta(forceSaveState);
|
||||
|
||||
// Save/collect again a few times, ensure that we always hit
|
||||
info("Save/collect a few times with one tab");
|
||||
for (let collector of [forceSaveState, ss.getBrowserState]) {
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let PREFIX = "Trivial test " + i + " using " + collector.name + ": ";
|
||||
let delta = yield getTelemetryDelta(collector);
|
||||
is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " has at least one hit " + delta.hits);
|
||||
is(delta.misses, 0, PREFIX + " has no miss");
|
||||
is(delta.clears, 0, PREFIX + " has no clear");
|
||||
}
|
||||
}
|
||||
|
||||
// Add a second tab, ensure that we have both hits and misses
|
||||
info("Adding the second tab");
|
||||
let tab2 = gBrowser.addTab(URL_PREFIX + "?tab2");
|
||||
yield promiseBrowserLoaded(tab2.linkedBrowser);
|
||||
|
||||
let PREFIX = "Adding second tab: ";
|
||||
ok(!TabStateCache.has(tab2), PREFIX + " starts out of the cache");
|
||||
let delta = yield getTelemetryDelta(forceSaveState);
|
||||
is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " we hit all tabs, thanks to prefetching");
|
||||
is(delta.misses, 0, PREFIX + " we missed no tabs, thanks to prefetching");
|
||||
is(delta.clears, 0, PREFIX + " has no clear");
|
||||
|
||||
// Save/collect again a few times, ensure that we always hit
|
||||
info("Save/collect a few times with two tabs");
|
||||
for (let collector of [forceSaveState, ss.getBrowserState]) {
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let PREFIX = "With two tabs " + i + " using " + collector.name + ": ";
|
||||
let delta = yield getTelemetryDelta(collector);
|
||||
is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " both tabs hit");
|
||||
is(delta.misses, 0, PREFIX + " has no miss");
|
||||
is(delta.clears, 0, PREFIX + " has no clear");
|
||||
}
|
||||
}
|
||||
|
||||
info("Removing second tab");
|
||||
gBrowser.removeTab(tab2);
|
||||
PREFIX = "Removing second tab: ";
|
||||
delta = yield getTelemetryDelta(forceSaveState);
|
||||
is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " we hit for one tab");
|
||||
is(delta.misses, 0, PREFIX + " has no miss");
|
||||
is(delta.clears, 0, PREFIX + " has no clear");
|
||||
|
||||
info("Removing first tab");
|
||||
gBrowser.removeTab(tab1);
|
||||
});
|
||||
|
||||
add_task(function browsing() {
|
||||
info("Opening first browsing tab");
|
||||
let tab1 = gBrowser.addTab(URL_PREFIX + "?do_not_move_from_here");
|
||||
let browser1 = tab1.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser1);
|
||||
yield forceSaveState();
|
||||
|
||||
info("Opening second browsing tab");
|
||||
let tab2 = gBrowser.addTab(URL_PREFIX + "?start_browsing_from_here");
|
||||
let browser2 = tab2.linkedBrowser;
|
||||
yield promiseBrowserLoaded(browser2);
|
||||
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
let url = URL_PREFIX + "?browsing" + i; // Arbitrary url, easy to recognize
|
||||
let PREFIX = "Browsing to " + url;
|
||||
info(PREFIX);
|
||||
let delta = yield getTelemetryDelta(function() {
|
||||
return Task.spawn(function() {
|
||||
// Move to new URI then save session
|
||||
let promise = promiseBrowserLoaded(browser2);
|
||||
browser2.loadURI(url);
|
||||
yield promise;
|
||||
ok(!TabStateCache.has(browser2), PREFIX + " starts out of the cache");
|
||||
yield forceSaveState();
|
||||
});
|
||||
});
|
||||
is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " has all hits, thanks to prefetching");
|
||||
is(delta.misses, 0, PREFIX + " has no miss, thanks to prefetching");
|
||||
is(delta.clears, 0, PREFIX + " has no clear");
|
||||
}
|
||||
gBrowser.removeTab(tab2);
|
||||
gBrowser.removeTab(tab1);
|
||||
});
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
|
||||
Cu.import("resource:///modules/sessionstore/TabStateCache.jsm", tmp);
|
||||
let {SessionFile, TabStateCache} = tmp;
|
||||
let {SessionFile} = tmp;
|
||||
|
||||
// Shortcuts for histogram names
|
||||
let Keys = {};
|
||||
|
@ -63,7 +62,8 @@ add_task(function history() {
|
|||
let statistics = yield promiseStats();
|
||||
|
||||
info("Now changing history");
|
||||
tab.linkedBrowser.contentWindow.history.pushState({foo:1}, "ref");
|
||||
tab.linkedBrowser.loadURI("http://example.org:80/1");
|
||||
yield promiseBrowserLoaded(tab.linkedBrowser);
|
||||
SyncHandlers.get(tab.linkedBrowser).flush();
|
||||
let statistics2 = yield promiseStats();
|
||||
|
||||
|
@ -224,7 +224,6 @@ add_task(function formdata() {
|
|||
|
||||
yield setInputValue(tab.linkedBrowser, {id: "input", value: "This is some form data"});
|
||||
SyncHandlers.get(tab.linkedBrowser).flush();
|
||||
TabStateCache.delete(tab.linkedBrowser);
|
||||
|
||||
let statistics2 = yield promiseStats();
|
||||
|
||||
|
|
|
@ -25,6 +25,10 @@ gFrameTree.addObserver({
|
|||
* to modify and query docShell data when running with multiple processes.
|
||||
*/
|
||||
|
||||
addEventListener("hashchange", function () {
|
||||
sendAsyncMessage("ss-test:hashchange");
|
||||
});
|
||||
|
||||
addEventListener("MozStorageChanged", function () {
|
||||
sendSyncMessage("ss-test:MozStorageChanged");
|
||||
});
|
||||
|
@ -46,6 +50,11 @@ addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
|
|||
content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));
|
||||
});
|
||||
|
||||
addMessageListener("ss-test:purgeSessionHistory", function () {
|
||||
Services.obs.notifyObservers(null, "browser:purge-session-history", "");
|
||||
content.setTimeout(() => sendAsyncMessage("ss-test:purgeSessionHistory"));
|
||||
});
|
||||
|
||||
addMessageListener("ss-test:getStyleSheets", function (msg) {
|
||||
let sheets = content.document.styleSheets;
|
||||
let titles = Array.map(sheets, ss => [ss.title, ss.disabled]);
|
||||
|
@ -149,3 +158,8 @@ addMessageListener("ss-test:mapFrameTree", function (msg) {
|
|||
let result = gFrameTree.map(frame => ({href: frame.location.href}));
|
||||
sendAsyncMessage("ss-test:mapFrameTree", result);
|
||||
});
|
||||
|
||||
addMessageListener("ss-test:click", function ({data}) {
|
||||
content.document.getElementById(data.id).click();
|
||||
sendAsyncMessage("ss-test:click");
|
||||
});
|
||||
|
|
|
@ -288,17 +288,17 @@ function forceSaveState() {
|
|||
);
|
||||
}
|
||||
|
||||
function whenBrowserLoaded(aBrowser, aCallback = next) {
|
||||
function whenBrowserLoaded(aBrowser, aCallback = next, ignoreSubFrames = true) {
|
||||
aBrowser.addEventListener("load", function onLoad(event) {
|
||||
if (event.target == aBrowser.contentDocument) {
|
||||
if (!ignoreSubFrames || event.target == aBrowser.contentDocument) {
|
||||
aBrowser.removeEventListener("load", onLoad, true);
|
||||
executeSoon(aCallback);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
function promiseBrowserLoaded(aBrowser) {
|
||||
function promiseBrowserLoaded(aBrowser, ignoreSubFrames = true) {
|
||||
let deferred = Promise.defer();
|
||||
whenBrowserLoaded(aBrowser, deferred.resolve);
|
||||
whenBrowserLoaded(aBrowser, deferred.resolve, ignoreSubFrames);
|
||||
return deferred.promise;
|
||||
}
|
||||
function whenBrowserUnloaded(aBrowser, aContainer, aCallback = next) {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
.font:not(.has-code) .font-css-code,
|
||||
.font-is-local,
|
||||
.font-is-remote,
|
||||
.font.is-local .font-format-url {
|
||||
.font.is-local .font-format-url,
|
||||
#template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@ FontInspector.prototype = {
|
|||
this.onNewNode = this.onNewNode.bind(this);
|
||||
this.inspector.selection.on("new-node", this.onNewNode);
|
||||
this.inspector.sidebar.on("fontinspector-selected", this.onNewNode);
|
||||
this.showAll = this.showAll.bind(this);
|
||||
this.showAllButton = this.chromeDoc.getElementById("showall");
|
||||
this.showAllButton.addEventListener("click", this.showAll);
|
||||
this.update();
|
||||
},
|
||||
|
||||
|
@ -40,6 +43,7 @@ FontInspector.prototype = {
|
|||
this.chromeDoc = null;
|
||||
this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
|
||||
this.inspector.selection.off("new-node", this.onNewNode);
|
||||
this.showAllButton.removeEventListener("click", this.showAll);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
<script type="application/javascript;version=1.8" src="font-inspector.js"></script>
|
||||
<div id="root">
|
||||
<ul id="all-fonts"></ul>
|
||||
<button id="showall" onclick="fontInspector.showAll()">&showAllFonts;</button>
|
||||
<button id="showall">&showAllFonts;</button>
|
||||
</div>
|
||||
<div id="template" style="display:none">
|
||||
<div id="template">
|
||||
<section class="font">
|
||||
<iframe sandbox="" class="font-preview"></iframe>
|
||||
<div class="font-info">
|
||||
|
|
|
@ -73,6 +73,7 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
|||
this._refreshHostTitle = this._refreshHostTitle.bind(this);
|
||||
this._splitConsoleOnKeypress = this._splitConsoleOnKeypress.bind(this)
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.stopPicker = this.stopPicker.bind(this);
|
||||
|
||||
this._target.on("close", this.destroy);
|
||||
|
||||
|
@ -1112,6 +1113,7 @@ Toolbox.prototype = {
|
|||
|
||||
let done = () => {
|
||||
this.emit("picker-started");
|
||||
this.on("select", this.stopPicker);
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
|
@ -1156,6 +1158,7 @@ Toolbox.prototype = {
|
|||
|
||||
let done = () => {
|
||||
this.emit("picker-stopped");
|
||||
this.off("select", this.stopPicker);
|
||||
deferred.resolve();
|
||||
};
|
||||
|
||||
|
|
|
@ -48,3 +48,4 @@ support-files =
|
|||
[browser_inspector_bug_952294_tooltips_dimensions.js]
|
||||
[browser_inspector_bug_958456_highlight_comments.js]
|
||||
[browser_inspector_bug_958169_switch_to_inspector_on_pick.js]
|
||||
[browser_inspector_bug_961771_picker_stops_on_tool_select.js]
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* 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/. */
|
||||
|
||||
// Test that the highlighter's picker should be stopped when a different tool is selected
|
||||
|
||||
function test() {
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let {require} = devtools;
|
||||
let promise = require("sdk/core/promise");
|
||||
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
let inspector, doc, toolbox;
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
doc = content.document;
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
content.location = "data:text/html,testing the highlighter goes away on tool selection";
|
||||
|
||||
function setupTest() {
|
||||
openInspector((aInspector, aToolbox) => {
|
||||
toolbox = aToolbox;
|
||||
inspector = aInspector;
|
||||
|
||||
toolbox.once("picker-stopped", () => {
|
||||
ok(true, "picker-stopped event fired after switch tools, so picker is closed");
|
||||
finishUp();
|
||||
});
|
||||
|
||||
Task.spawn(function() {
|
||||
yield toolbox.startPicker();
|
||||
yield toolbox.selectNextTool();
|
||||
}).then(null, Cu.reportError);
|
||||
});
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
inspector = doc = toolbox = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ function testFocus(sw, hud) {
|
|||
function onMessage(event, messages) {
|
||||
let msg = [...messages][0];
|
||||
|
||||
var loc = msg.querySelector(".location");
|
||||
var loc = msg.querySelector(".message-location");
|
||||
ok(loc, "location element exists");
|
||||
is(loc.textContent.trim(), sw.Scratchpad.uniqueName + ":1",
|
||||
"location value is correct");
|
||||
|
|
|
@ -145,6 +145,18 @@ ConsoleOutput.prototype = {
|
|||
return this.owner.webConsoleClient;
|
||||
},
|
||||
|
||||
/**
|
||||
* Release an actor.
|
||||
*
|
||||
* @private
|
||||
* @param string actorId
|
||||
* The actor ID you want to release.
|
||||
*/
|
||||
_releaseObject: function(actorId)
|
||||
{
|
||||
this.owner._releaseObject(actorId);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a message to output.
|
||||
*
|
||||
|
@ -847,7 +859,7 @@ Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
|
|||
|
||||
let repeatNode = this.document.createElementNS(XHTML_NS, "span");
|
||||
repeatNode.setAttribute("value", "1");
|
||||
repeatNode.className = "repeats";
|
||||
repeatNode.className = "message-repeats";
|
||||
repeatNode.textContent = 1;
|
||||
repeatNode._uid = this.getRepeatID();
|
||||
return repeatNode;
|
||||
|
@ -1077,6 +1089,134 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
|
|||
},
|
||||
}); // Messages.ConsoleGeneric.prototype
|
||||
|
||||
/**
|
||||
* The ConsoleTrace message is used for console.trace() calls.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Messages.Simple
|
||||
* @param object packet
|
||||
* The Console API call packet received from the server.
|
||||
*/
|
||||
Messages.ConsoleTrace = function(packet)
|
||||
{
|
||||
let options = {
|
||||
className: "consoleTrace cm-s-mozilla",
|
||||
timestamp: packet.timeStamp,
|
||||
category: "webdev",
|
||||
severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
|
||||
private: packet.private,
|
||||
filterDuplicates: true,
|
||||
location: {
|
||||
url: packet.filename,
|
||||
line: packet.lineNumber,
|
||||
},
|
||||
};
|
||||
|
||||
this._renderStack = this._renderStack.bind(this);
|
||||
Messages.Simple.call(this, this._renderStack, options);
|
||||
|
||||
this._repeatID.consoleApiLevel = packet.level;
|
||||
this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
|
||||
this._arguments = packet.arguments;
|
||||
};
|
||||
|
||||
Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
|
||||
{
|
||||
/**
|
||||
* Holds the stackframes received from the server.
|
||||
*
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_stacktrace: null,
|
||||
|
||||
/**
|
||||
* Holds the arguments the content script passed to the console.trace()
|
||||
* method. This array is cleared when the message is initialized, and
|
||||
* associated actors are released.
|
||||
*
|
||||
* @private
|
||||
* @type array
|
||||
*/
|
||||
_arguments: null,
|
||||
|
||||
init: function()
|
||||
{
|
||||
let result = Messages.Simple.prototype.init.apply(this, arguments);
|
||||
|
||||
// We ignore console.trace() arguments. Release object actors.
|
||||
if (Array.isArray(this._arguments)) {
|
||||
for (let arg of this._arguments) {
|
||||
if (WebConsoleUtils.isActorGrip(arg)) {
|
||||
this.output._releaseObject(arg.actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._arguments = null;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render the stack frames.
|
||||
*
|
||||
* @private
|
||||
* @return DOMElement
|
||||
*/
|
||||
_renderStack: function()
|
||||
{
|
||||
let cmvar = this.document.createElementNS(XHTML_NS, "span");
|
||||
cmvar.className = "cm-variable";
|
||||
cmvar.textContent = "console";
|
||||
|
||||
let cmprop = this.document.createElementNS(XHTML_NS, "span");
|
||||
cmprop.className = "cm-property";
|
||||
cmprop.textContent = "trace";
|
||||
|
||||
let title = this.document.createElementNS(XHTML_NS, "span");
|
||||
title.className = "title devtools-monospace";
|
||||
title.appendChild(cmvar);
|
||||
title.appendChild(this.document.createTextNode("."));
|
||||
title.appendChild(cmprop);
|
||||
title.appendChild(this.document.createTextNode("():"));
|
||||
|
||||
let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
|
||||
let location = Messages.Simple.prototype._renderLocation.call(this);
|
||||
if (location) {
|
||||
location.target = "jsdebugger";
|
||||
}
|
||||
|
||||
let widget = new Widgets.Stacktrace(this, this._stacktrace).render();
|
||||
|
||||
let body = this.document.createElementNS(XHTML_NS, "div");
|
||||
body.appendChild(title);
|
||||
if (repeatNode) {
|
||||
body.appendChild(repeatNode);
|
||||
}
|
||||
if (location) {
|
||||
body.appendChild(location);
|
||||
}
|
||||
body.appendChild(this.document.createTextNode("\n"));
|
||||
|
||||
let frag = this.document.createDocumentFragment();
|
||||
frag.appendChild(body);
|
||||
frag.appendChild(widget.element);
|
||||
|
||||
return frag;
|
||||
},
|
||||
|
||||
_renderBody: function()
|
||||
{
|
||||
let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
|
||||
body.classList.remove("devtools-monospace");
|
||||
return body;
|
||||
},
|
||||
|
||||
// no-op for the message location and .repeats elements.
|
||||
// |this._renderStack| handles customized message output.
|
||||
_renderLocation: function() { },
|
||||
_renderRepeatNode: function() { },
|
||||
}); // Messages.ConsoleTrace.prototype
|
||||
|
||||
let Widgets = {};
|
||||
|
||||
|
@ -1354,6 +1494,91 @@ Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
|||
}); // Widgets.LongString.prototype
|
||||
|
||||
|
||||
/**
|
||||
* The stacktrace widget.
|
||||
*
|
||||
* @constructor
|
||||
* @extends Widgets.BaseWidget
|
||||
* @param object message
|
||||
* The owning message.
|
||||
* @param array stacktrace
|
||||
* The stacktrace to display, array of frames as supplied by the server,
|
||||
* over the remote protocol.
|
||||
*/
|
||||
Widgets.Stacktrace = function(message, stacktrace)
|
||||
{
|
||||
Widgets.BaseWidget.call(this, message);
|
||||
this.stacktrace = stacktrace;
|
||||
};
|
||||
|
||||
Widgets.Stacktrace.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
|
||||
{
|
||||
/**
|
||||
* The stackframes received from the server.
|
||||
* @type array
|
||||
*/
|
||||
stacktrace: null,
|
||||
|
||||
render: function()
|
||||
{
|
||||
if (this.element) {
|
||||
return this;
|
||||
}
|
||||
|
||||
let result = this.element = this.document.createElementNS(XHTML_NS, "ul");
|
||||
result.className = "stacktrace devtools-monospace";
|
||||
|
||||
for (let frame of this.stacktrace) {
|
||||
result.appendChild(this._renderFrame(frame));
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render a frame object received from the server.
|
||||
*
|
||||
* @param object frame
|
||||
* The stack frame to display. This object should have the following
|
||||
* properties: functionName, filename and lineNumber.
|
||||
* @return DOMElement
|
||||
* The DOM element to display for the given frame.
|
||||
*/
|
||||
_renderFrame: function(frame)
|
||||
{
|
||||
let fn = this.document.createElementNS(XHTML_NS, "span");
|
||||
fn.className = "function";
|
||||
if (frame.functionName) {
|
||||
let span = this.document.createElementNS(XHTML_NS, "span");
|
||||
span.className = "cm-variable";
|
||||
span.textContent = frame.functionName;
|
||||
fn.appendChild(span);
|
||||
fn.appendChild(this.document.createTextNode("()"));
|
||||
} else {
|
||||
fn.classList.add("cm-comment");
|
||||
fn.textContent = l10n.getStr("stacktrace.anonymousFunction");
|
||||
}
|
||||
|
||||
let location = this.output.owner.createLocationNode(frame.filename,
|
||||
frame.lineNumber,
|
||||
"jsdebugger");
|
||||
|
||||
// .devtools-monospace sets font-size to 80%, however .body already has
|
||||
// .devtools-monospace. If we keep it here, the location would be rendered
|
||||
// smaller.
|
||||
location.classList.remove("devtools-monospace");
|
||||
|
||||
let elem = this.document.createElementNS(XHTML_NS, "li");
|
||||
elem.appendChild(fn);
|
||||
elem.appendChild(location);
|
||||
elem.appendChild(this.document.createTextNode("\n"));
|
||||
|
||||
return elem;
|
||||
},
|
||||
|
||||
}); // Widgets.Stacktrace.prototype
|
||||
|
||||
|
||||
function gSequenceId()
|
||||
{
|
||||
return gSequenceId.n++;
|
||||
|
|
|
@ -11,9 +11,9 @@ const {Cc, Ci, Cu} = require("chrome");
|
|||
let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
|
||||
let Heritage = require("sdk/core/heritage");
|
||||
|
||||
loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
|
||||
loader.lazyGetter(this, "Telemetry", () => require("devtools/shared/telemetry"));
|
||||
loader.lazyGetter(this, "WebConsoleFrame", () => require("devtools/webconsole/webconsole").WebConsoleFrame);
|
||||
loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
|
||||
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
|
||||
loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
|
||||
|
@ -110,6 +110,7 @@ HUD_SERVICE.prototype =
|
|||
function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow)
|
||||
{
|
||||
let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow);
|
||||
this._browserConsoleID = hud.hudId;
|
||||
this.consoles.set(hud.hudId, hud);
|
||||
return hud.init();
|
||||
},
|
||||
|
@ -241,7 +242,6 @@ HUD_SERVICE.prototype =
|
|||
connect().then(getTarget).then(openWindow).then((aWindow) => {
|
||||
this.openBrowserConsole(target, aWindow, aWindow)
|
||||
.then((aBrowserConsole) => {
|
||||
this._browserConsoleID = aBrowserConsole.hudId;
|
||||
this._browserConsoleDefer.resolve(aBrowserConsole);
|
||||
this._browserConsoleDefer = null;
|
||||
})
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
|
||||
loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
|
||||
loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
|
||||
loader.lazyGetter(this, "HUDService", () => require("devtools/webconsole/hudservice"));
|
||||
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
|
||||
|
||||
|
|
|
@ -103,6 +103,7 @@ support-files =
|
|||
test-bug_923281_console_log_filter.html
|
||||
test-bug_923281_test1.js
|
||||
test-bug_923281_test2.js
|
||||
test-bug_939783_console_trace_duplicates.html
|
||||
|
||||
[browser_bug664688_sandbox_update_after_navigation.js]
|
||||
[browser_bug_638949_copy_link_location.js]
|
||||
|
@ -257,3 +258,4 @@ run-if = os == "mac"
|
|||
[browser_webconsole_output_03.js]
|
||||
[browser_webconsole_output_04.js]
|
||||
[browser_webconsole_output_events.js]
|
||||
[browser_webconsole_console_trace_duplicates.js]
|
||||
|
|
|
@ -70,7 +70,7 @@ function test()
|
|||
{
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "message element found");
|
||||
let locationNode = msg.querySelector(".location");
|
||||
let locationNode = msg.querySelector(".message-location");
|
||||
ok(locationNode, "message location element found");
|
||||
|
||||
let title = locationNode.getAttribute("title");
|
||||
|
|
|
@ -49,7 +49,10 @@ function test()
|
|||
EventUtils.synthesizeKey(c, {}, hud.iframeWindow);
|
||||
}
|
||||
|
||||
hud.jsterm.execute(null, onReadProperty.bind(null, msg));
|
||||
hud.jsterm.execute(null, () => {
|
||||
// executeSoon() is needed to get out of the execute() event loop.
|
||||
executeSoon(onReadProperty.bind(null, msg));
|
||||
});
|
||||
}
|
||||
|
||||
function onReadProperty(deadObjectMessage)
|
||||
|
@ -69,6 +72,7 @@ function test()
|
|||
|
||||
function onFetched()
|
||||
{
|
||||
ok(true, "variables view fetched");
|
||||
hud.jsterm.execute("delete window.foobarzTezt; 2013-26", onCalcResult);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ function test()
|
|||
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "message element found for: " + result.text);
|
||||
let locationNode = msg.querySelector(".location");
|
||||
let locationNode = msg.querySelector(".message-location");
|
||||
ok(locationNode, "message location element found");
|
||||
|
||||
EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow);
|
||||
|
|
|
@ -6,30 +6,30 @@
|
|||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-585956-console-trace.html";
|
||||
|
||||
function test() {
|
||||
addTab("data:text/html;charset=utf8,<p>hello");
|
||||
browser.addEventListener("load", tabLoaded, true);
|
||||
Task.spawn(runner).then(finishTest);
|
||||
|
||||
function tabLoaded() {
|
||||
browser.removeEventListener("load", tabLoaded, true);
|
||||
function* runner() {
|
||||
let {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello");
|
||||
let hud = yield openConsole(tab);
|
||||
|
||||
openConsole(null, function(hud) {
|
||||
content.location = TEST_URI;
|
||||
content.location = TEST_URI;
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "console.trace output",
|
||||
consoleTrace: {
|
||||
file: "test-bug-585956-console-trace.html",
|
||||
fn: "window.foobar585956c",
|
||||
},
|
||||
}],
|
||||
}).then(performChecks);
|
||||
let [result] = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "console.trace output",
|
||||
consoleTrace: {
|
||||
file: "test-bug-585956-console-trace.html",
|
||||
fn: "window.foobar585956c",
|
||||
},
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
function performChecks(results) {
|
||||
let node = [...results[0].matched][0];
|
||||
let node = [...result.matched][0];
|
||||
ok(node, "found trace log node");
|
||||
|
||||
let obj = node._messageObject;
|
||||
ok(obj, "console.trace message object");
|
||||
|
||||
// The expected stack trace object.
|
||||
let stacktrace = [
|
||||
|
@ -39,11 +39,8 @@ function test() {
|
|||
{ filename: TEST_URI, lineNumber: 21, functionName: null, language: 2 }
|
||||
];
|
||||
|
||||
ok(node, "found trace log node");
|
||||
ok(node._stacktrace, "found stacktrace object");
|
||||
is(node._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct");
|
||||
ok(obj._stacktrace, "found stacktrace object");
|
||||
is(obj._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct");
|
||||
isnot(node.textContent.indexOf("bug-585956"), -1, "found file name");
|
||||
|
||||
finishTest();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,37 +95,24 @@ function testGen() {
|
|||
testNext();
|
||||
};
|
||||
EventUtils.synthesizeKey("VK_END", {});
|
||||
yield;
|
||||
yield undefined;
|
||||
|
||||
let oldScrollTop = scrollBox.scrollTop;
|
||||
|
||||
content.console.log("test message 151");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "test message 151",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
}],
|
||||
}).then(() => {
|
||||
scrollBox.onscroll = () => {
|
||||
if (scrollBox.scrollTop == oldScrollTop) {
|
||||
// Wait for scroll to change.
|
||||
return;
|
||||
}
|
||||
scrollBox.onscroll = null;
|
||||
isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
|
||||
testNext();
|
||||
};
|
||||
});
|
||||
scrollBox.onscroll = () => {
|
||||
if (scrollBox.scrollTop == oldScrollTop) {
|
||||
// Wait for scroll to change.
|
||||
return;
|
||||
}
|
||||
scrollBox.onscroll = null;
|
||||
isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
|
||||
hud = testDriver = null;
|
||||
finishTest();
|
||||
};
|
||||
|
||||
yield undefined;
|
||||
|
||||
hud = testDriver = null;
|
||||
finishTest();
|
||||
|
||||
yield undefined;
|
||||
}
|
||||
|
||||
function test() {
|
||||
|
|
|
@ -74,7 +74,7 @@ function testCSSPruning(hudRef) {
|
|||
"repeated nodes pruned from repeatNodes");
|
||||
|
||||
let msg = [...result.matched][0];
|
||||
let repeats = msg.querySelector(".repeats");
|
||||
let repeats = msg.querySelector(".message-repeats");
|
||||
is(repeats.getAttribute("value"), 1,
|
||||
"repeated nodes pruned from repeatNodes (confirmed)");
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ function test() {
|
|||
|
||||
let exceptionMsg = [...exceptionRule.matched][0];
|
||||
let consoleMsg = [...consoleRule.matched][0];
|
||||
let nodes = [exceptionMsg.querySelector(".location"),
|
||||
consoleMsg.querySelector(".location")];
|
||||
let nodes = [exceptionMsg.querySelector(".message-location"),
|
||||
consoleMsg.querySelector(".message-location")];
|
||||
ok(nodes[0], ".location node for the exception message");
|
||||
ok(nodes[1], ".location node for the console message");
|
||||
|
||||
|
|
|
@ -41,10 +41,10 @@ function testViewSource(aHud)
|
|||
}).then(([error1Rule, error2Rule]) => {
|
||||
let error1Msg = [...error1Rule.matched][0];
|
||||
let error2Msg = [...error2Rule.matched][0];
|
||||
nodes = [error1Msg.querySelector(".location"),
|
||||
error2Msg.querySelector(".location")];
|
||||
ok(nodes[0], ".location node for the first error");
|
||||
ok(nodes[1], ".location node for the second error");
|
||||
nodes = [error1Msg.querySelector(".message-location"),
|
||||
error2Msg.querySelector(".message-location")];
|
||||
ok(nodes[0], ".message-location node for the first error");
|
||||
ok(nodes[1], ".message-location node for the second error");
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/* 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/. */
|
||||
|
||||
function test() {
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html";
|
||||
|
||||
Task.spawn(runner).then(finishTest);
|
||||
|
||||
function* runner() {
|
||||
const {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello");
|
||||
const hud = yield openConsole(tab);
|
||||
|
||||
content.location = TEST_URI;
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
name: "console.trace output for foo1()",
|
||||
text: "foo1()",
|
||||
repeats: 2,
|
||||
consoleTrace: {
|
||||
file: "test-bug_939783_console_trace_duplicates.html",
|
||||
fn: "foo3()",
|
||||
},
|
||||
}, {
|
||||
name: "console.trace output for foo1b()",
|
||||
text: "foo1b()",
|
||||
consoleTrace: {
|
||||
file: "test-bug_939783_console_trace_duplicates.html",
|
||||
fn: "foo3()",
|
||||
},
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@ function runTests(aToolbox)
|
|||
|
||||
let [matched] = [...messages[0].matched];
|
||||
ok(matched, "Found logged message from Scratchpad");
|
||||
let anchor = matched.querySelector("a.location");
|
||||
let anchor = matched.querySelector("a.message-location");
|
||||
|
||||
aToolbox.on("scratchpad-selected", function selected() {
|
||||
aToolbox.off("scratchpad-selected", selected);
|
||||
|
|
|
@ -47,7 +47,7 @@ function testViewSource(hud) {
|
|||
function onMessage([result]) {
|
||||
let msg = [...result.matched][0];
|
||||
ok(msg, "error message");
|
||||
let locationNode = msg.querySelector(".location");
|
||||
let locationNode = msg.querySelector(".message-location");
|
||||
ok(locationNode, "location node");
|
||||
|
||||
Services.ww.registerNotification(observer);
|
||||
|
|
|
@ -3,19 +3,15 @@
|
|||
* 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/. */
|
||||
|
||||
let WebConsoleUtils, TargetFactory, require;
|
||||
let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
|
||||
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
|
||||
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let {require, TargetFactory} = devtools;
|
||||
let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils");
|
||||
let {Messages} = require("devtools/webconsole/console-output");
|
||||
|
||||
(() => {
|
||||
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let utils = devtools.require("devtools/toolkit/webconsole/utils");
|
||||
TargetFactory = devtools.TargetFactory;
|
||||
WebConsoleUtils = utils.Utils;
|
||||
require = devtools.require;
|
||||
})();
|
||||
// promise._reportErrors = true; // please never leave me.
|
||||
|
||||
let gPendingOutputTest = 0;
|
||||
|
@ -283,7 +279,7 @@ function dumpConsoles()
|
|||
function dumpMessageElement(aMessage)
|
||||
{
|
||||
let text = aMessage.textContent;
|
||||
let repeats = aMessage.querySelector(".repeats");
|
||||
let repeats = aMessage.querySelector(".message-repeats");
|
||||
if (repeats) {
|
||||
repeats = repeats.getAttribute("value");
|
||||
}
|
||||
|
@ -932,44 +928,44 @@ function waitForMessages(aOptions)
|
|||
let elemText = aElement.textContent;
|
||||
let trace = aRule.consoleTrace;
|
||||
|
||||
if (!checkText("Stack trace from ", elemText)) {
|
||||
if (!checkText("console.trace():", elemText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let clickable = aElement.querySelector(".body a");
|
||||
if (!clickable) {
|
||||
ok(false, "console.trace() message is missing .hud-clickable");
|
||||
displayErrorContext(aRule, aElement);
|
||||
return false;
|
||||
}
|
||||
aRule.clickableElements = [clickable];
|
||||
|
||||
if (trace.file &&
|
||||
!checkText("from " + trace.file + ", ", elemText)) {
|
||||
ok(false, "console.trace() message is missing the file name: " +
|
||||
trace.file);
|
||||
displayErrorContext(aRule, aElement);
|
||||
return false;
|
||||
let frame = aElement.querySelector(".stacktrace li:first-child");
|
||||
if (trace.file) {
|
||||
let file = frame.querySelector(".message-location").title;
|
||||
if (!checkText(trace.file, file)) {
|
||||
ok(false, "console.trace() message is missing the file name: " +
|
||||
trace.file);
|
||||
displayErrorContext(aRule, aElement);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (trace.fn &&
|
||||
!checkText(", function " + trace.fn + ", ", elemText)) {
|
||||
ok(false, "console.trace() message is missing the function name: " +
|
||||
trace.fn);
|
||||
displayErrorContext(aRule, aElement);
|
||||
return false;
|
||||
if (trace.fn) {
|
||||
let fn = frame.querySelector(".function").textContent;
|
||||
if (!checkText(trace.fn, fn)) {
|
||||
ok(false, "console.trace() message is missing the function name: " +
|
||||
trace.fn);
|
||||
displayErrorContext(aRule, aElement);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (trace.line &&
|
||||
!checkText(", line " + trace.line + ".", elemText)) {
|
||||
ok(false, "console.trace() message is missing the line number: " +
|
||||
trace.line);
|
||||
displayErrorContext(aRule, aElement);
|
||||
return false;
|
||||
if (trace.line) {
|
||||
let line = frame.querySelector(".message-location").sourceLine;
|
||||
if (!checkText(trace.line, line)) {
|
||||
ok(false, "console.trace() message is missing the line number: " +
|
||||
trace.line);
|
||||
displayErrorContext(aRule, aElement);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
aRule.category = CATEGORY_WEBDEV;
|
||||
aRule.severity = SEVERITY_LOG;
|
||||
aRule.type = Messages.ConsoleTrace;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1038,7 +1034,7 @@ function waitForMessages(aOptions)
|
|||
|
||||
function checkSource(aRule, aElement)
|
||||
{
|
||||
let location = aElement.querySelector(".location");
|
||||
let location = aElement.querySelector(".message-location");
|
||||
if (!location) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1090,16 +1086,23 @@ function waitForMessages(aOptions)
|
|||
return false;
|
||||
}
|
||||
|
||||
let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
|
||||
aRule.consoleTimeEnd);
|
||||
|
||||
// The rule tries to match the newer types of messages, based on their
|
||||
// object constructor.
|
||||
if (aRule.type && (!aElement._messageObject ||
|
||||
!(aElement._messageObject instanceof aRule.type))) {
|
||||
return false;
|
||||
if (aRule.type) {
|
||||
if (!aElement._messageObject ||
|
||||
!(aElement._messageObject instanceof aRule.type)) {
|
||||
if (partialMatch) {
|
||||
ok(false, "message type for rule: " + displayRule(aRule));
|
||||
displayErrorContext(aRule, aElement);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
partialMatch = true;
|
||||
}
|
||||
|
||||
let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
|
||||
aRule.consoleTimeEnd || aRule.type);
|
||||
|
||||
if ("category" in aRule && aElement.category != aRule.category) {
|
||||
if (partialMatch) {
|
||||
is(aElement.category, aRule.category,
|
||||
|
@ -1124,7 +1127,7 @@ function waitForMessages(aOptions)
|
|||
}
|
||||
|
||||
if ("repeats" in aRule) {
|
||||
let repeats = aElement.querySelector(".repeats");
|
||||
let repeats = aElement.querySelector(".message-repeats");
|
||||
if (!repeats || repeats.getAttribute("value") != aRule.repeats) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1180,7 +1183,7 @@ function waitForMessages(aOptions)
|
|||
function onMessagesAdded(aEvent, aNewElements)
|
||||
{
|
||||
for (let elem of aNewElements) {
|
||||
let location = elem.querySelector(".location");
|
||||
let location = elem.querySelector(".message-location");
|
||||
if (location) {
|
||||
let url = location.title;
|
||||
// Prevent recursion with the browser console and any potential
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Web Console test for bug 939783 - different console.trace() calls
|
||||
wrongly filtered as duplicates</title>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<script type="application/javascript">
|
||||
function foo1() {
|
||||
foo2();
|
||||
}
|
||||
|
||||
function foo1b() {
|
||||
foo2();
|
||||
}
|
||||
|
||||
function foo2() {
|
||||
foo3();
|
||||
}
|
||||
|
||||
function foo3() {
|
||||
console.trace();
|
||||
}
|
||||
|
||||
foo1(); foo1();
|
||||
foo1b();
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p>Web Console test for bug 939783 - different console.trace() calls
|
||||
wrongly filtered as duplicates</p>
|
||||
</body>
|
||||
</html>
|
|
@ -14,7 +14,7 @@ loader.lazyServiceGetter(this, "clipboardHelper",
|
|||
"@mozilla.org/widget/clipboardhelper;1",
|
||||
"nsIClipboardHelper");
|
||||
loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
|
||||
loader.lazyGetter(this, "promise", () => require("sdk/core/promise"));
|
||||
loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
|
||||
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
|
||||
loader.lazyGetter(this, "AutocompletePopup",
|
||||
() => require("devtools/shared/autocomplete-popup").AutocompletePopup);
|
||||
|
@ -1056,7 +1056,7 @@ WebConsoleFrame.prototype = {
|
|||
mergeFilteredMessageNode:
|
||||
function WCF_mergeFilteredMessageNode(aOriginal, aFiltered)
|
||||
{
|
||||
let repeatNode = aOriginal.getElementsByClassName("repeats")[0];
|
||||
let repeatNode = aOriginal.getElementsByClassName("message-repeats")[0];
|
||||
if (!repeatNode) {
|
||||
return; // no repeat node, return early.
|
||||
}
|
||||
|
@ -1081,7 +1081,7 @@ WebConsoleFrame.prototype = {
|
|||
*/
|
||||
_filterRepeatedMessage: function WCF__filterRepeatedMessage(aNode)
|
||||
{
|
||||
let repeatNode = aNode.getElementsByClassName("repeats")[0];
|
||||
let repeatNode = aNode.getElementsByClassName("message-repeats")[0];
|
||||
if (!repeatNode) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1105,7 +1105,7 @@ WebConsoleFrame.prototype = {
|
|||
return null;
|
||||
}
|
||||
|
||||
let lastRepeatNode = lastMessage.getElementsByClassName("repeats")[0];
|
||||
let lastRepeatNode = lastMessage.getElementsByClassName("message-repeats")[0];
|
||||
if (lastRepeatNode && lastRepeatNode._uid == uid) {
|
||||
dupeNode = lastMessage;
|
||||
}
|
||||
|
@ -1191,6 +1191,11 @@ WebConsoleFrame.prototype = {
|
|||
node = msg.init(this.output).render().element;
|
||||
break;
|
||||
}
|
||||
case "trace": {
|
||||
let msg = new Messages.ConsoleTrace(aMessage);
|
||||
node = msg.init(this.output).render().element;
|
||||
break;
|
||||
}
|
||||
case "dir": {
|
||||
body = { arguments: args };
|
||||
let clipboardArray = [];
|
||||
|
@ -1201,38 +1206,6 @@ WebConsoleFrame.prototype = {
|
|||
break;
|
||||
}
|
||||
|
||||
case "trace": {
|
||||
let filename = WebConsoleUtils.abbreviateSourceURL(aMessage.filename);
|
||||
let functionName = aMessage.functionName ||
|
||||
l10n.getStr("stacktrace.anonymousFunction");
|
||||
|
||||
body = this.document.createElementNS(XHTML_NS, "a");
|
||||
body.setAttribute("aria-haspopup", true);
|
||||
body.href = "#";
|
||||
body.draggable = false;
|
||||
body.textContent = l10n.getFormatStr("stacktrace.outputMessage",
|
||||
[filename, functionName,
|
||||
sourceLine]);
|
||||
|
||||
this._addMessageLinkCallback(body, () => {
|
||||
this.jsterm.openVariablesView({
|
||||
rawObject: aMessage.stacktrace,
|
||||
autofocus: true,
|
||||
});
|
||||
});
|
||||
|
||||
clipboardText = body.textContent + "\n";
|
||||
|
||||
aMessage.stacktrace.forEach(function(aFrame) {
|
||||
clipboardText += aFrame.filename + " :: " +
|
||||
aFrame.functionName + " :: " +
|
||||
aFrame.lineNumber + "\n";
|
||||
});
|
||||
|
||||
clipboardText = clipboardText.trimRight();
|
||||
break;
|
||||
}
|
||||
|
||||
case "group":
|
||||
case "groupCollapsed":
|
||||
clipboardText = body = aMessage.groupName;
|
||||
|
@ -1281,7 +1254,6 @@ WebConsoleFrame.prototype = {
|
|||
case "group":
|
||||
case "groupCollapsed":
|
||||
case "groupEnd":
|
||||
case "trace":
|
||||
case "time":
|
||||
case "timeEnd":
|
||||
for (let actor of objectActors) {
|
||||
|
@ -1307,15 +1279,11 @@ WebConsoleFrame.prototype = {
|
|||
node._objectActors = objectActors;
|
||||
|
||||
if (!node._messageObject) {
|
||||
let repeatNode = node.getElementsByClassName("repeats")[0];
|
||||
let repeatNode = node.getElementsByClassName("message-repeats")[0];
|
||||
repeatNode._uid += [...objectActors].join("-");
|
||||
}
|
||||
}
|
||||
|
||||
if (level == "trace") {
|
||||
node._stacktrace = aMessage.stacktrace;
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
|
@ -2388,7 +2356,7 @@ WebConsoleFrame.prototype = {
|
|||
|
||||
if (aNode.category == CATEGORY_CSS ||
|
||||
aNode.category == CATEGORY_SECURITY) {
|
||||
let repeatNode = aNode.getElementsByClassName("repeats")[0];
|
||||
let repeatNode = aNode.getElementsByClassName("message-repeats")[0];
|
||||
if (repeatNode && repeatNode._uid) {
|
||||
delete this._repeatNodes[repeatNode._uid];
|
||||
}
|
||||
|
@ -2501,7 +2469,7 @@ WebConsoleFrame.prototype = {
|
|||
!(aCategory == CATEGORY_CSS && aSeverity == SEVERITY_LOG)) {
|
||||
repeatNode = this.document.createElementNS(XHTML_NS, "span");
|
||||
repeatNode.setAttribute("value", "1");
|
||||
repeatNode.className = "repeats";
|
||||
repeatNode.className = "message-repeats";
|
||||
repeatNode.textContent = 1;
|
||||
repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel,
|
||||
aSourceURL, aSourceLine].join(":");
|
||||
|
@ -2567,11 +2535,18 @@ WebConsoleFrame.prototype = {
|
|||
* @param number aSourceLine [optional]
|
||||
* The line number on which the error occurred. If zero or omitted,
|
||||
* there is no line number associated with this message.
|
||||
* @param string aTarget [optional]
|
||||
* Tells which tool to open the link with, on click. Supported tools:
|
||||
* jsdebugger, styleeditor, scratchpad.
|
||||
* @return nsIDOMNode
|
||||
* The new anchor element, ready to be added to the message node.
|
||||
*/
|
||||
createLocationNode: function WCF_createLocationNode(aSourceURL, aSourceLine)
|
||||
createLocationNode:
|
||||
function WCF_createLocationNode(aSourceURL, aSourceLine, aTarget)
|
||||
{
|
||||
if (!aSourceURL) {
|
||||
aSourceURL = "";
|
||||
}
|
||||
let locationNode = this.document.createElementNS(XHTML_NS, "a");
|
||||
let filenameNode = this.document.createElementNS(XHTML_NS, "span");
|
||||
|
||||
|
@ -2592,30 +2567,39 @@ WebConsoleFrame.prototype = {
|
|||
}
|
||||
|
||||
filenameNode.className = "filename";
|
||||
filenameNode.textContent = " " + filename;
|
||||
filenameNode.textContent = " " + (filename || l10n.getStr("unknownLocation"));
|
||||
locationNode.appendChild(filenameNode);
|
||||
|
||||
locationNode.href = isScratchpad ? "#" : fullURL;
|
||||
locationNode.href = isScratchpad || !fullURL ? "#" : fullURL;
|
||||
locationNode.draggable = false;
|
||||
locationNode.target = aTarget;
|
||||
locationNode.setAttribute("title", aSourceURL);
|
||||
locationNode.className = "location theme-link devtools-monospace";
|
||||
locationNode.className = "message-location theme-link devtools-monospace";
|
||||
|
||||
// Make the location clickable.
|
||||
this._addMessageLinkCallback(locationNode, () => {
|
||||
if (isScratchpad) {
|
||||
let onClick = () => {
|
||||
let target = locationNode.target;
|
||||
if (target == "scratchpad" || isScratchpad) {
|
||||
this.owner.viewSourceInScratchpad(aSourceURL);
|
||||
return;
|
||||
}
|
||||
else if (locationNode.parentNode.category == CATEGORY_CSS) {
|
||||
|
||||
let category = locationNode.parentNode.category;
|
||||
if (target == "styleeditor" || category == CATEGORY_CSS) {
|
||||
this.owner.viewSourceInStyleEditor(fullURL, aSourceLine);
|
||||
}
|
||||
else if (locationNode.parentNode.category == CATEGORY_JS ||
|
||||
locationNode.parentNode.category == CATEGORY_WEBDEV) {
|
||||
else if (target == "jsdebugger" ||
|
||||
category == CATEGORY_JS || category == CATEGORY_WEBDEV) {
|
||||
this.owner.viewSourceInDebugger(fullURL, aSourceLine);
|
||||
}
|
||||
else {
|
||||
this.owner.viewSource(fullURL, aSourceLine);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (fullURL) {
|
||||
this._addMessageLinkCallback(locationNode, onClick);
|
||||
}
|
||||
|
||||
if (aSourceLine) {
|
||||
let lineNumberNode = this.document.createElementNS(XHTML_NS, "span");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
This is the pdf.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 0.8.759
|
||||
Current extension version is: 0.8.934
|
||||
|
||||
|
|
|
@ -377,11 +377,19 @@ ChromeActions.prototype = {
|
|||
break;
|
||||
}
|
||||
},
|
||||
fallback: function(url, sendResponse) {
|
||||
fallback: function(args, sendResponse) {
|
||||
var featureId = args.featureId;
|
||||
var url = args.url;
|
||||
|
||||
var self = this;
|
||||
var domWindow = this.domWindow;
|
||||
var strings = getLocalizedStrings('chrome.properties');
|
||||
var message = getLocalizedString(strings, 'unsupported_feature');
|
||||
var message;
|
||||
if (featureId === 'forms') {
|
||||
message = getLocalizedString(strings, 'unsupported_feature_forms');
|
||||
} else {
|
||||
message = getLocalizedString(strings, 'unsupported_feature');
|
||||
}
|
||||
|
||||
PdfJsTelemetry.onFallback();
|
||||
|
||||
|
|
|
@ -26,11 +26,13 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = ['NetworkManager'];
|
||||
|
||||
function log(aMsg) {
|
||||
var msg = 'network.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
|
||||
Services.console.logStringMessage(msg);
|
||||
// TODO(mack): dump() doesn't seem to work here...
|
||||
dump(msg + '\n');
|
||||
var console = {
|
||||
log: function console_log(aMsg) {
|
||||
var msg = 'network.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
|
||||
Services.console.logStringMessage(msg);
|
||||
// TODO(mack): dump() doesn't seem to work here...
|
||||
dump(msg + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
var NetworkManager = (function NetworkManagerClosure() {
|
||||
|
@ -42,6 +44,7 @@ var NetworkManager = (function NetworkManagerClosure() {
|
|||
this.url = url;
|
||||
args = args || {};
|
||||
this.httpHeaders = args.httpHeaders || {};
|
||||
this.withCredentials = args.withCredentials || false;
|
||||
this.getXhr = args.getXhr ||
|
||||
function NetworkManager_getXhr() {
|
||||
return new XMLHttpRequest();
|
||||
|
@ -90,6 +93,7 @@ var NetworkManager = (function NetworkManagerClosure() {
|
|||
};
|
||||
|
||||
xhr.open('GET', this.url);
|
||||
xhr.withCredentials = this.withCredentials;
|
||||
for (var property in this.httpHeaders) {
|
||||
var value = this.httpHeaders[property];
|
||||
if (typeof value === 'undefined') {
|
||||
|
|
До Ширина: | Высота: | Размер: 371 B После Ширина: | Высота: | Размер: 272 B |
До Ширина: | Высота: | Размер: 381 B После Ширина: | Высота: | Размер: 268 B |
До Ширина: | Высота: | Размер: 381 B После Ширина: | Высота: | Размер: 268 B |
До Ширина: | Высота: | Размер: 371 B После Ширина: | Высота: | Размер: 272 B |
После Ширина: | Высота: | Размер: 326 B |
После Ширина: | Высота: | Размер: 326 B |
Двоичные данные
browser/extensions/pdfjs/content/web/images/loading-small.png
До Ширина: | Высота: | Размер: 8.8 KiB После Ширина: | Высота: | Размер: 3.5 KiB |
Двоичные данные
browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-documentProperties.png
Normal file
После Ширина: | Высота: | Размер: 1.2 KiB |
До Ширина: | Высота: | Размер: 1.3 KiB После Ширина: | Высота: | Размер: 340 B |
Двоичные данные
browser/extensions/pdfjs/content/web/images/secondaryToolbarButton-handTool.png
Normal file
После Ширина: | Высота: | Размер: 250 B |
До Ширина: | Высота: | Размер: 1.2 KiB После Ширина: | Высота: | Размер: 315 B |
До Ширина: | Высота: | Размер: 3.0 KiB После Ширина: | Высота: | Размер: 414 B |
До Ширина: | Высота: | Размер: 3.0 KiB После Ширина: | Высота: | Размер: 414 B |
Двоичные данные
browser/extensions/pdfjs/content/web/images/shadow.png
До Ширина: | Высота: | Размер: 454 B После Ширина: | Высота: | Размер: 290 B |
Двоичные данные
browser/extensions/pdfjs/content/web/images/texture.png
До Ширина: | Высота: | Размер: 2.4 KiB После Ширина: | Высота: | Размер: 2.4 KiB |
До Ширина: | Высота: | Размер: 244 B После Ширина: | Высота: | Размер: 210 B |
До Ширина: | Высота: | Размер: 512 B После Ширина: | Высота: | Размер: 343 B |
До Ширина: | Высота: | Размер: 237 B После Ширина: | Высота: | Размер: 200 B |
До Ширина: | Высота: | Размер: 417 B После Ширина: | Высота: | Размер: 348 B |
До Ширина: | Высота: | Размер: 558 B После Ширина: | Высота: | Размер: 462 B |
До Ширина: | Высота: | Размер: 353 B После Ширина: | Высота: | Размер: 296 B |
До Ширина: | Высота: | Размер: 426 B После Ширина: | Высота: | Размер: 310 B |
До Ширина: | Высота: | Размер: 344 B После Ширина: | Высота: | Размер: 307 B |
До Ширина: | Высота: | Размер: 491 B После Ширина: | Высота: | Размер: 450 B |