This commit is contained in:
Ryan VanderMeulen 2014-01-24 17:10:50 -05:00
Родитель e72ae5743e 1e0482e1c1
Коммит ac20328af6
314 изменённых файлов: 9001 добавлений и 5672 удалений

Просмотреть файл

@ -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');*/
}

Двоичные данные
browser/base/content/aboutaccounts/fonts/clearsans-regular.woff Normal file

Двоичный файл не отображается.

Двоичные данные
browser/base/content/aboutaccounts/fonts/firasans-light.woff Normal file

Двоичный файл не отображается.

Двоичные данные
browser/base/content/aboutaccounts/fonts/firasans-regular.woff Normal file

Двоичный файл не отображается.

Двоичные данные
browser/base/content/aboutaccounts/images/fox.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.4 KiB

Двоичные данные
browser/base/content/aboutaccounts/images/graphic_sync_intro.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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);
}
}

406
browser/base/content/aboutaccounts/normalize.css поставляемый Normal file
Просмотреть файл

@ -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

Двоичные данные
browser/extensions/pdfjs/content/web/images/grab.cur Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 326 B

Двоичные данные
browser/extensions/pdfjs/content/web/images/grabbing.cur Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 326 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.8 KiB

После

Ширина:  |  Высота:  |  Размер: 3.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.3 KiB

После

Ширина:  |  Высота:  |  Размер: 340 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 250 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

После

Ширина:  |  Высота:  |  Размер: 315 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 3.0 KiB

После

Ширина:  |  Высота:  |  Размер: 414 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 3.0 KiB

После

Ширина:  |  Высота:  |  Размер: 414 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 454 B

После

Ширина:  |  Высота:  |  Размер: 290 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше