mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
merge m-c to fx-team
This commit is contained in:
commit
c6a07311e8
@ -1207,7 +1207,7 @@ BrowserGlue.prototype = {
|
||||
},
|
||||
|
||||
_migrateUI: function BG__migrateUI() {
|
||||
const UI_VERSION = 7;
|
||||
const UI_VERSION = 8;
|
||||
const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
|
||||
let currentUIVersion = 0;
|
||||
try {
|
||||
@ -1342,6 +1342,15 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (currentUIVersion < 8) {
|
||||
// Reset homepage pref for users who have it set to google.com/firefox
|
||||
let uri = Services.prefs.getComplexValue("browser.startup.homepage",
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
if (uri && /^https?:\/\/(www\.)?google(\.\w{2,3}){1,2}\/firefox\/?$/.test(uri)) {
|
||||
Services.prefs.clearUserPref("browser.startup.homepage");
|
||||
}
|
||||
}
|
||||
|
||||
if (this._dirty)
|
||||
this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
|
||||
|
||||
|
@ -88,7 +88,15 @@ MOCHITEST_BROWSER_FILES = \
|
||||
browser_581593.js \
|
||||
browser_581937.js \
|
||||
browser_586147.js \
|
||||
browser_586068-cascaded_restore.js \
|
||||
browser_586068-apptabs.js \
|
||||
browser_586068-apptabs_ondemand.js \
|
||||
browser_586068-browser_state_interrupted.js \
|
||||
browser_586068-cascade.js \
|
||||
browser_586068-multi_window.js \
|
||||
browser_586068-reload.js \
|
||||
browser_586068-select.js \
|
||||
browser_586068-window_state.js \
|
||||
browser_586068-window_state_override.js \
|
||||
browser_588426.js \
|
||||
browser_590268.js \
|
||||
browser_590563.js \
|
||||
|
@ -0,0 +1,55 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
||||
], selected: 5 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
loadCount++;
|
||||
|
||||
// We'll make sure that the loads we get come from pinned tabs or the
|
||||
// the selected tab.
|
||||
|
||||
// get the tab
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
|
||||
ok(tab.pinned || gBrowser.selectedTab == tab,
|
||||
"load came from pinned or selected tab");
|
||||
|
||||
// We should get 4 loads: 3 app tabs + 1 normal selected tab
|
||||
if (loadCount < 4)
|
||||
return;
|
||||
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
const PREF_RESTORE_PINNED_TABS_ON_DEMAND = "browser.sessionstore.restore_pinned_tabs_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_PINNED_TABS_ON_DEMAND, true);
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_PINNED_TABS_ON_DEMAND);
|
||||
});
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
||||
], selected: 5 }] };
|
||||
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
// get the tab
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
|
||||
// Check that the load only comes from the selected tab.
|
||||
ok(gBrowser.selectedTab == tab, "load came from selected tab");
|
||||
is(aNeedRestore, 6, "six tabs left to restore");
|
||||
is(aRestoring, 1, "one tab is restoring");
|
||||
is(aRestored, 0, "no tabs have been restored, yet");
|
||||
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
// The first state will be loaded using setBrowserState, followed by the 2nd
|
||||
// state also being loaded using setBrowserState, interrupting the first restore.
|
||||
let state1 = { windows: [
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#4" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 1
|
||||
},
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } },
|
||||
],
|
||||
selected: 3
|
||||
}
|
||||
] };
|
||||
let state2 = { windows: [
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.org#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#7" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#8" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 3
|
||||
},
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#7" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#8" }], extData: { "uniq": r() } },
|
||||
],
|
||||
selected: 1
|
||||
}
|
||||
] };
|
||||
|
||||
// interruptedAfter will be set after the selected tab from each window have loaded.
|
||||
let interruptedAfter = 0;
|
||||
let loadedWindow1 = false;
|
||||
let loadedWindow2 = false;
|
||||
let numTabs = state2.windows[0].tabs.length + state2.windows[1].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
loadCount++;
|
||||
|
||||
if (aBrowser.currentURI.spec == state1.windows[0].tabs[2].entries[0].url)
|
||||
loadedWindow1 = true;
|
||||
if (aBrowser.currentURI.spec == state1.windows[1].tabs[0].entries[0].url)
|
||||
loadedWindow2 = true;
|
||||
|
||||
if (!interruptedAfter && loadedWindow1 && loadedWindow2) {
|
||||
interruptedAfter = loadCount;
|
||||
ss.setBrowserState(JSON.stringify(state2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadCount < numTabs + interruptedAfter)
|
||||
return;
|
||||
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs + interruptedAfter, "all tabs were restored");
|
||||
is(aNeedRestore, 0, "there are no tabs left needing restore");
|
||||
|
||||
// Remove the progress listener from this window, it will be removed from
|
||||
// theWin when that window is closed (in setBrowserState).
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
closeAllButPrimaryWindow();
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
});
|
||||
|
||||
// We also want to catch the extra windows (there should be 2), so we need to observe domwindowopened
|
||||
Services.ww.registerNotification(function observer(aSubject, aTopic, aData) {
|
||||
if (aTopic == "domwindowopened") {
|
||||
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad);
|
||||
Services.ww.unregisterNotification(observer);
|
||||
win.gBrowser.addTabsProgressListener(gProgressListener);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state1));
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } }
|
||||
] }] };
|
||||
|
||||
let expectedCounts = [
|
||||
[3, 3, 0],
|
||||
[2, 3, 1],
|
||||
[1, 3, 2],
|
||||
[0, 3, 3],
|
||||
[0, 2, 4],
|
||||
[0, 1, 5]
|
||||
];
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
loadCount++;
|
||||
let expected = expectedCounts[loadCount - 1];
|
||||
|
||||
is(aNeedRestore, expected[0], "load " + loadCount + " - # tabs that need to be restored");
|
||||
is(aRestoring, expected[1], "load " + loadCount + " - # tabs that are restoring");
|
||||
is(aRestored, expected[2], "load " + loadCount + " - # tabs that has been restored");
|
||||
|
||||
if (loadCount == state.windows[0].tabs.length) {
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
@ -1,848 +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 stateBackup = ss.getBrowserState();
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
function test() {
|
||||
/** Test for Bug 586068 - Cascade page loads when restoring **/
|
||||
waitForExplicitFinish();
|
||||
// This test does a lot of window opening / closing and waiting for loads.
|
||||
// In order to prevent timeouts, we'll extend the default that mochitest uses.
|
||||
requestLongerTimeout(4);
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
// test_reloadCascade, test_reloadReload are generated tests that are run out
|
||||
// of cycle (since they depend on current state). They're listed in [tests] here
|
||||
// so that it is obvious when they run in respect to the other tests.
|
||||
let tests = [test_cascade, test_select, test_multiWindowState,
|
||||
test_setWindowStateNoOverwrite, test_setWindowStateOverwrite,
|
||||
test_setBrowserStateInterrupted, test_reload,
|
||||
/* test_reloadReload, */ test_reloadCascadeSetup,
|
||||
/* test_reloadCascade, */ test_apptabs_only,
|
||||
test_restore_apptabs_ondemand];
|
||||
function runNextTest() {
|
||||
// Reset the pref
|
||||
try {
|
||||
Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
|
||||
Services.prefs.clearUserPref("browser.sessionstore.restore_pinned_tabs_on_demand");
|
||||
} catch (e) {}
|
||||
|
||||
// set an empty state & run the next test, or finish
|
||||
if (tests.length) {
|
||||
// Enumerate windows and close everything but our primary window. We can't
|
||||
// use waitForFocus() because apparently it's buggy. See bug 599253.
|
||||
var windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
var currentWindow = windowsEnum.getNext();
|
||||
if (currentWindow != window) {
|
||||
currentWindow.close();
|
||||
}
|
||||
}
|
||||
|
||||
ss.setBrowserState(JSON.stringify({ windows: [{ tabs: [{ url: 'about:blank' }] }] }));
|
||||
let currentTest = tests.shift();
|
||||
info("running " + currentTest.name);
|
||||
executeSoon(currentTest);
|
||||
}
|
||||
else {
|
||||
ss.setBrowserState(stateBackup);
|
||||
executeSoon(finish);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function test_cascade() {
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
dump("\n\nload: " + aBrowser.currentURI.spec + "\n" + JSON.stringify(countTabs()) + "\n\n");
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_cascade_progressCallback();
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } }
|
||||
] }] };
|
||||
|
||||
let loadCount = 0;
|
||||
// Since our progress listener is fired before the one in sessionstore, our
|
||||
// expected counts look a little weird. This is because we inspect the state
|
||||
// before sessionstore has marked the tab as finished restoring and before it
|
||||
// starts restoring the next tab
|
||||
let expectedCounts = [
|
||||
[3, 3, 0],
|
||||
[2, 3, 1],
|
||||
[1, 3, 2],
|
||||
[0, 3, 3],
|
||||
[0, 2, 4],
|
||||
[0, 1, 5]
|
||||
];
|
||||
|
||||
function test_cascade_progressCallback() {
|
||||
loadCount++;
|
||||
let counts = countTabs();
|
||||
let expected = expectedCounts[loadCount - 1];
|
||||
|
||||
is(counts[0], expected[0], "test_cascade: load " + loadCount + " - # tabs that need to be restored");
|
||||
is(counts[1], expected[1], "test_cascade: load " + loadCount + " - # tabs that are restoring");
|
||||
is(counts[2], expected[2], "test_cascade: load " + loadCount + " - # tabs that has been restored");
|
||||
|
||||
if (loadCount < state.windows[0].tabs.length)
|
||||
return;
|
||||
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
// This progress listener will get attached before the listener in session store.
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
function test_select() {
|
||||
// Set the pref to true so we know exactly how many tabs should be restoring at
|
||||
// any given time. This guarantees that a finishing load won't start another.
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_select_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }
|
||||
], selected: 1 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
// expectedCounts looks a little wierd for the test case, but it works. See
|
||||
// comment in test_cascade for an explanation
|
||||
let expectedCounts = [
|
||||
[5, 1, 0],
|
||||
[4, 1, 1],
|
||||
[3, 1, 2],
|
||||
[2, 1, 3],
|
||||
[1, 1, 4],
|
||||
[0, 1, 5]
|
||||
];
|
||||
let tabOrder = [0, 5, 1, 4, 3, 2];
|
||||
|
||||
function test_select_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
let counts = countTabs();
|
||||
let expected = expectedCounts[loadCount - 1];
|
||||
|
||||
is(counts[0], expected[0], "test_select: load " + loadCount + " - # tabs that need to be restored");
|
||||
is(counts[1], expected[1], "test_select: load " + loadCount + " - # tabs that are restoring");
|
||||
is(counts[2], expected[2], "test_select: load " + loadCount + " - # tabs that has been restored");
|
||||
|
||||
if (loadCount < state.windows[0].tabs.length) {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = state.windows[0].tabs[tabOrder[loadCount - 1]].extData.uniq;
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
is(ss.getTabValue(tab, "uniq"), expectedData, "test_select: load " + loadCount + " - correct tab was restored");
|
||||
|
||||
// select the next tab
|
||||
window.gBrowser.selectTabAtIndex(tabOrder[loadCount]);
|
||||
return;
|
||||
}
|
||||
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
function test_multiWindowState() {
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
// We only care about load events when the tab still has
|
||||
// __SS_restoreState == TAB_STATE_RESTORING on it.
|
||||
// Since our listener is attached before the sessionstore one, this works out.
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_multiWindowState_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
// The first window will be put into the already open window and the second
|
||||
// window will be opened with _openWindowWithState, which is the source of the problem.
|
||||
let state = { windows: [
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.org#0" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 1
|
||||
},
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 4
|
||||
}
|
||||
] };
|
||||
let numTabs = state.windows[0].tabs.length + state.windows[1].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
function test_multiWindowState_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
if (loadCount < numTabs)
|
||||
return;
|
||||
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs, "test_multiWindowState: all tabs were restored");
|
||||
let count = countTabs();
|
||||
is(count[0], 0,
|
||||
"test_multiWindowState: there are no tabs left needing restore");
|
||||
|
||||
// Remove the progress listener from this window, it will be removed from
|
||||
// theWin when that window is closed (in setBrowserState).
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
// We also want to catch the 2nd window, so we need to observe domwindowopened
|
||||
function windowObserver(aSubject, aTopic, aData) {
|
||||
let theWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
if (aTopic == "domwindowopened") {
|
||||
theWin.addEventListener("load", function() {
|
||||
theWin.removeEventListener("load", arguments.callee, false);
|
||||
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
theWin.gBrowser.addTabsProgressListener(progressListener);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
Services.ww.registerNotification(windowObserver);
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
function test_setWindowStateNoOverwrite() {
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
// We only care about load events when the tab still has
|
||||
// __SS_restoreState == TAB_STATE_RESTORING on it.
|
||||
// Since our listener is attached before the sessionstore one, this works out.
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_setWindowStateNoOverwrite_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
// We'll use 2 states so that we can make sure calling setWindowState doesn't
|
||||
// wipe out currently restoring data.
|
||||
let state1 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }] },
|
||||
{ entries: [{ url: "http://example.com#2" }] },
|
||||
{ entries: [{ url: "http://example.com#3" }] },
|
||||
{ entries: [{ url: "http://example.com#4" }] },
|
||||
{ entries: [{ url: "http://example.com#5" }] },
|
||||
] }] };
|
||||
let state2 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org#1" }] },
|
||||
{ entries: [{ url: "http://example.org#2" }] },
|
||||
{ entries: [{ url: "http://example.org#3" }] },
|
||||
{ entries: [{ url: "http://example.org#4" }] },
|
||||
{ entries: [{ url: "http://example.org#5" }] }
|
||||
] }] };
|
||||
|
||||
let numTabs = state1.windows[0].tabs.length + state2.windows[0].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
function test_setWindowStateNoOverwrite_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
// When loadCount == 2, we'll also restore state2 into the window
|
||||
if (loadCount == 2)
|
||||
ss.setWindowState(window, JSON.stringify(state2), false);
|
||||
|
||||
if (loadCount < numTabs)
|
||||
return;
|
||||
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs, "test_setWindowStateNoOverwrite: all tabs were restored");
|
||||
// window.__SS_tabsToRestore isn't decremented until after the progress
|
||||
// listener is called. Since we get in here before that, we still expect
|
||||
// the count to be 1.
|
||||
is(window.__SS_tabsToRestore, 1,
|
||||
"test_setWindowStateNoOverwrite: window doesn't think there are more tabs to restore");
|
||||
let count = countTabs();
|
||||
is(count[0], 0,
|
||||
"test_setWindowStateNoOverwrite: there are no tabs left needing restore");
|
||||
|
||||
// Remove the progress listener from this window, it will be removed from
|
||||
// theWin when that window is closed (in setBrowserState).
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setWindowState(window, JSON.stringify(state1), true);
|
||||
}
|
||||
|
||||
|
||||
function test_setWindowStateOverwrite() {
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
// We only care about load events when the tab still has
|
||||
// __SS_restoreState == TAB_STATE_RESTORING on it.
|
||||
// Since our listener is attached before the sessionstore one, this works out.
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_setWindowStateOverwrite_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
// We'll use 2 states so that we can make sure calling setWindowState doesn't
|
||||
// wipe out currently restoring data.
|
||||
let state1 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }] },
|
||||
{ entries: [{ url: "http://example.com#2" }] },
|
||||
{ entries: [{ url: "http://example.com#3" }] },
|
||||
{ entries: [{ url: "http://example.com#4" }] },
|
||||
{ entries: [{ url: "http://example.com#5" }] },
|
||||
] }] };
|
||||
let state2 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org#1" }] },
|
||||
{ entries: [{ url: "http://example.org#2" }] },
|
||||
{ entries: [{ url: "http://example.org#3" }] },
|
||||
{ entries: [{ url: "http://example.org#4" }] },
|
||||
{ entries: [{ url: "http://example.org#5" }] }
|
||||
] }] };
|
||||
|
||||
let numTabs = 2 + state2.windows[0].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
function test_setWindowStateOverwrite_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
// When loadCount == 2, we'll also restore state2 into the window
|
||||
if (loadCount == 2)
|
||||
ss.setWindowState(window, JSON.stringify(state2), true);
|
||||
|
||||
if (loadCount < numTabs)
|
||||
return;
|
||||
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs, "test_setWindowStateOverwrite: all tabs were restored");
|
||||
// window.__SS_tabsToRestore isn't decremented until after the progress
|
||||
// listener is called. Since we get in here before that, we still expect
|
||||
// the count to be 1.
|
||||
is(window.__SS_tabsToRestore, 1,
|
||||
"test_setWindowStateOverwrite: window doesn't think there are more tabs to restore");
|
||||
let count = countTabs();
|
||||
is(count[0], 0,
|
||||
"test_setWindowStateOverwrite: there are no tabs left needing restore");
|
||||
|
||||
// Remove the progress listener from this window, it will be removed from
|
||||
// theWin when that window is closed (in setBrowserState).
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setWindowState(window, JSON.stringify(state1), true);
|
||||
}
|
||||
|
||||
|
||||
function test_setBrowserStateInterrupted() {
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
// We only care about load events when the tab still has
|
||||
// __SS_restoreState == TAB_STATE_RESTORING on it.
|
||||
// Since our listener is attached before the sessionstore one, this works out.
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_setBrowserStateInterrupted_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
// The first state will be loaded using setBrowserState, followed by the 2nd
|
||||
// state also being loaded using setBrowserState, interrupting the first restore.
|
||||
let state1 = { windows: [
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#4" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 1
|
||||
},
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } },
|
||||
],
|
||||
selected: 3
|
||||
}
|
||||
] };
|
||||
let state2 = { windows: [
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.org#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#7" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org#8" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 3
|
||||
},
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#7" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#8" }], extData: { "uniq": r() } },
|
||||
],
|
||||
selected: 1
|
||||
}
|
||||
] };
|
||||
|
||||
// interruptedAfter will be set after the selected tab from each window have loaded.
|
||||
let interruptedAfter = 0;
|
||||
let loadedWindow1 = false;
|
||||
let loadedWindow2 = false;
|
||||
let numTabs = state2.windows[0].tabs.length + state2.windows[1].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
function test_setBrowserStateInterrupted_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
if (aBrowser.currentURI.spec == state1.windows[0].tabs[2].entries[0].url)
|
||||
loadedWindow1 = true;
|
||||
if (aBrowser.currentURI.spec == state1.windows[1].tabs[0].entries[0].url)
|
||||
loadedWindow2 = true;
|
||||
|
||||
if (!interruptedAfter && loadedWindow1 && loadedWindow2) {
|
||||
interruptedAfter = loadCount;
|
||||
ss.setBrowserState(JSON.stringify(state2));
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadCount < numTabs + interruptedAfter)
|
||||
return;
|
||||
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs + interruptedAfter,
|
||||
"test_setBrowserStateInterrupted: all tabs were restored");
|
||||
let count = countTabs();
|
||||
is(count[0], 0,
|
||||
"test_setBrowserStateInterrupted: there are no tabs left needing restore");
|
||||
|
||||
// Remove the progress listener from this window, it will be removed from
|
||||
// theWin when that window is closed (in setBrowserState).
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
// We also want to catch the extra windows (there should be 2), so we need to observe domwindowopened
|
||||
function windowObserver(aSubject, aTopic, aData) {
|
||||
let theWin = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
if (aTopic == "domwindowopened") {
|
||||
theWin.addEventListener("load", function() {
|
||||
theWin.removeEventListener("load", arguments.callee, false);
|
||||
|
||||
Services.ww.unregisterNotification(windowObserver);
|
||||
theWin.gBrowser.addTabsProgressListener(progressListener);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
Services.ww.registerNotification(windowObserver);
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state1));
|
||||
}
|
||||
|
||||
|
||||
function test_reload() {
|
||||
// Set the pref to true so we know exactly how many tabs should be restoring at
|
||||
// any given time. This guarantees that a finishing load won't start another.
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_reload_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#8" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#9" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#10" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#11" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#12" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#13" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#14" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#15" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#16" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#17" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#18" }], extData: { "uniq": r() } }
|
||||
], selected: 1 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
function test_reload_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
is(aBrowser.currentURI.spec, state.windows[0].tabs[loadCount - 1].entries[0].url,
|
||||
"test_reload: load " + loadCount + " - browser loaded correct url");
|
||||
|
||||
if (loadCount <= state.windows[0].tabs.length) {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = state.windows[0].tabs[loadCount - 1].extData.uniq;
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
is(ss.getTabValue(tab, "uniq"), expectedData,
|
||||
"test_reload: load " + loadCount + " - correct tab was restored");
|
||||
|
||||
if (loadCount == state.windows[0].tabs.length) {
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
executeSoon(function() {
|
||||
_test_reloadAfter("test_reloadReload", state, runNextTest);
|
||||
});
|
||||
}
|
||||
else {
|
||||
// reload the next tab
|
||||
window.gBrowser.reloadTab(window.gBrowser.tabs[loadCount]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
// This doesn't actually test anything, just does a cascaded restore with default
|
||||
// settings. This really just sets up to test that reloads work.
|
||||
function test_reloadCascadeSetup() {
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_cascadeReloadSetup_progressCallback();
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com/#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#6" }], extData: { "uniq": r() } }
|
||||
] }] };
|
||||
|
||||
let loadCount = 0;
|
||||
function test_cascadeReloadSetup_progressCallback() {
|
||||
loadCount++;
|
||||
if (loadCount < state.windows[0].tabs.length)
|
||||
return;
|
||||
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
executeSoon(function() {
|
||||
_test_reloadAfter("test_reloadCascade", state, runNextTest);
|
||||
});
|
||||
}
|
||||
|
||||
// This progress listener will get attached before the listener in session store.
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
// This is a generic function that will attempt to reload each test. We do this
|
||||
// a couple times, so make it utilitarian.
|
||||
// This test expects that aState contains a single window and that each tab has
|
||||
// a unique extData value eg. { "uniq": value }.
|
||||
function _test_reloadAfter(aTestName, aState, aCallback) {
|
||||
info("starting " + aTestName);
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_reloadAfter_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate a left mouse button click with no modifiers, which is what
|
||||
// Command-R, or clicking reload does.
|
||||
let fakeEvent = {
|
||||
button: 0,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
}
|
||||
|
||||
let loadCount = 0;
|
||||
function test_reloadAfter_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
if (loadCount <= aState.windows[0].tabs.length) {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = aState.windows[0].tabs[loadCount - 1].extData.uniq;
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
is(ss.getTabValue(tab, "uniq"), expectedData,
|
||||
aTestName + ": load " + loadCount + " - correct tab was reloaded");
|
||||
|
||||
if (loadCount == aState.windows[0].tabs.length) {
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
aCallback();
|
||||
}
|
||||
else {
|
||||
// reload the next tab
|
||||
window.gBrowser.selectTabAtIndex(loadCount);
|
||||
BrowserReloadOrDuplicate(fakeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
BrowserReloadOrDuplicate(fakeEvent);
|
||||
}
|
||||
|
||||
|
||||
// This test ensures that app tabs are restored regardless of restore_on_demand
|
||||
function test_apptabs_only() {
|
||||
// Set the pref to true so we know exactly how many tabs should be restoring at
|
||||
// any given time. This guarantees that a finishing load won't start another.
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_apptabs_only_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
||||
], selected: 5 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
function test_apptabs_only_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
// We'll make sure that the loads we get come from pinned tabs or the
|
||||
// the selected tab.
|
||||
|
||||
// get the tab
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
|
||||
ok(tab.pinned || gBrowser.selectedTab == tab,
|
||||
"test_apptabs_only: load came from pinned or selected tab");
|
||||
|
||||
// We should get 4 loads: 3 app tabs + 1 normal selected tab
|
||||
if (loadCount < 4)
|
||||
return;
|
||||
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
// This test ensures that app tabs are not restored when restore_pinned_tabs_on_demand is set
|
||||
function test_restore_apptabs_ondemand() {
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
|
||||
Services.prefs.setBoolPref("browser.sessionstore.restore_pinned_tabs_on_demand", true);
|
||||
|
||||
// We have our own progress listener for this test, which we'll attach before our state is set
|
||||
let progressListener = {
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_restore_apptabs_ondemand_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() }, pinned: true },
|
||||
{ entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
||||
], selected: 5 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
let nextTestTimer;
|
||||
function test_restore_apptabs_ondemand_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
|
||||
// get the tab
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
|
||||
// Check that the load only comes from the selected tab.
|
||||
ok(gBrowser.selectedTab == tab,
|
||||
"test_restore_apptabs_ondemand: load came from selected tab");
|
||||
|
||||
// We should get only 1 load: the selected tab
|
||||
if (loadCount == 1) {
|
||||
nextTestTimer = setTimeout(nextTest, 1000);
|
||||
return;
|
||||
}
|
||||
else if (loadCount > 1) {
|
||||
clearTimeout(nextTestTimer);
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
runNextTest();
|
||||
}
|
||||
nextTest();
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
function countTabs() {
|
||||
let needsRestore = 0,
|
||||
isRestoring = 0,
|
||||
wasRestored = 0;
|
||||
|
||||
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let window = windowsEnum.getNext();
|
||||
if (window.closed)
|
||||
continue;
|
||||
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
let browser = window.gBrowser.tabs[i].linkedBrowser;
|
||||
if (browser.__SS_restoreState == TAB_STATE_RESTORING)
|
||||
isRestoring++;
|
||||
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
|
||||
needsRestore++;
|
||||
else
|
||||
wasRestored++;
|
||||
}
|
||||
}
|
||||
return [needsRestore, isRestoring, wasRestored];
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
// The first window will be put into the already open window and the second
|
||||
// window will be opened with _openWindowWithState, which is the source of the problem.
|
||||
let state = { windows: [
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.org#0" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 1
|
||||
},
|
||||
{
|
||||
tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com#6" }], extData: { "uniq": r() } }
|
||||
],
|
||||
selected: 4
|
||||
}
|
||||
] };
|
||||
let numTabs = state.windows[0].tabs.length + state.windows[1].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
if (++loadCount == numTabs) {
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs, "all tabs were restored");
|
||||
is(aNeedRestore, 0, "there are no tabs left needing restore");
|
||||
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
closeAllButPrimaryWindow();
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// We also want to catch the 2nd window, so we need to observe domwindowopened
|
||||
Services.ww.registerNotification(function observer(aSubject, aTopic, aData) {
|
||||
if (aTopic == "domwindowopened") {
|
||||
let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
|
||||
win.addEventListener("load", function onLoad() {
|
||||
win.removeEventListener("load", onLoad);
|
||||
Services.ww.unregisterNotification(observer);
|
||||
win.gBrowser.addTabsProgressListener(gProgressListener);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
137
browser/components/sessionstore/test/browser_586068-reload.js
Normal file
137
browser/components/sessionstore/test/browser_586068-reload.js
Normal file
@ -0,0 +1,137 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#8" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#9" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#10" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#11" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#12" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#13" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#14" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#15" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#16" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#17" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org/#18" }], extData: { "uniq": r() } }
|
||||
], selected: 1 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
loadCount++;
|
||||
is(aBrowser.currentURI.spec, state.windows[0].tabs[loadCount - 1].entries[0].url,
|
||||
"load " + loadCount + " - browser loaded correct url");
|
||||
|
||||
if (loadCount <= state.windows[0].tabs.length) {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = state.windows[0].tabs[loadCount - 1].extData.uniq;
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
is(ss.getTabValue(tab, "uniq"), expectedData,
|
||||
"load " + loadCount + " - correct tab was restored");
|
||||
|
||||
if (loadCount == state.windows[0].tabs.length) {
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
reloadAllTabs(state, function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), testCascade);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// reload the next tab
|
||||
window.gBrowser.reloadTab(window.gBrowser.tabs[loadCount]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
function testCascade() {
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com/#1" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#2" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#3" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#4" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#5" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com/#6" }], extData: { "uniq": r() } }
|
||||
] }] };
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
if (++loadCount < state.windows[0].tabs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
reloadAllTabs(state, function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
function reloadAllTabs(aState, aCallback) {
|
||||
// Simulate a left mouse button click with no modifiers, which is what
|
||||
// Command-R, or clicking reload does.
|
||||
let fakeEvent = {
|
||||
button: 0,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
ctrlKey: false,
|
||||
shiftKey: false
|
||||
};
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
if (++loadCount <= aState.windows[0].tabs.length) {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = aState.windows[0].tabs[loadCount - 1].extData.uniq;
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
is(ss.getTabValue(tab, "uniq"), expectedData,
|
||||
"load " + loadCount + " - correct tab was reloaded");
|
||||
|
||||
if (loadCount == aState.windows[0].tabs.length) {
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(aCallback);
|
||||
} else {
|
||||
// reload the next tab
|
||||
window.gBrowser.selectTabAtIndex(loadCount);
|
||||
BrowserReloadOrDuplicate(fakeEvent);
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
|
||||
BrowserReloadOrDuplicate(fakeEvent);
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }
|
||||
], selected: 1 }] };
|
||||
|
||||
let expectedCounts = [
|
||||
[5, 1, 0],
|
||||
[4, 1, 1],
|
||||
[3, 1, 2],
|
||||
[2, 1, 3],
|
||||
[1, 1, 4],
|
||||
[0, 1, 5]
|
||||
];
|
||||
let tabOrder = [0, 5, 1, 4, 3, 2];
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
loadCount++;
|
||||
let expected = expectedCounts[loadCount - 1];
|
||||
|
||||
is(aNeedRestore, expected[0], "load " + loadCount + " - # tabs that need to be restored");
|
||||
is(aRestoring, expected[1], "load " + loadCount + " - # tabs that are restoring");
|
||||
is(aRestored, expected[2], "load " + loadCount + " - # tabs that has been restored");
|
||||
|
||||
if (loadCount < state.windows[0].tabs.length) {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = state.windows[0].tabs[tabOrder[loadCount - 1]].extData.uniq;
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
|
||||
is(ss.getTabValue(tab, "uniq"), expectedData,
|
||||
"load " + loadCount + " - correct tab was restored");
|
||||
|
||||
// select the next tab
|
||||
window.gBrowser.selectTabAtIndex(tabOrder[loadCount]);
|
||||
} else {
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
// We'll use 2 states so that we can make sure calling setWindowState doesn't
|
||||
// wipe out currently restoring data.
|
||||
let state1 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }] },
|
||||
{ entries: [{ url: "http://example.com#2" }] },
|
||||
{ entries: [{ url: "http://example.com#3" }] },
|
||||
{ entries: [{ url: "http://example.com#4" }] },
|
||||
{ entries: [{ url: "http://example.com#5" }] },
|
||||
] }] };
|
||||
let state2 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org#1" }] },
|
||||
{ entries: [{ url: "http://example.org#2" }] },
|
||||
{ entries: [{ url: "http://example.org#3" }] },
|
||||
{ entries: [{ url: "http://example.org#4" }] },
|
||||
{ entries: [{ url: "http://example.org#5" }] }
|
||||
] }] };
|
||||
let numTabs = state1.windows[0].tabs.length + state2.windows[0].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
// When loadCount == 2, we'll also restore state2 into the window
|
||||
if (++loadCount == 2) {
|
||||
ss.setWindowState(window, JSON.stringify(state2), false);
|
||||
}
|
||||
|
||||
if (loadCount < numTabs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs, "test_setWindowStateNoOverwrite: all tabs were restored");
|
||||
// window.__SS_tabsToRestore isn't decremented until after the progress
|
||||
// listener is called. Since we get in here before that, we still expect
|
||||
// the count to be 1.
|
||||
is(window.__SS_tabsToRestore, 1, "window doesn't think there are more tabs to restore");
|
||||
is(aNeedRestore, 0, "there are no tabs left needing restore");
|
||||
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
});
|
||||
|
||||
ss.setWindowState(window, JSON.stringify(state1), true);
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/* 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/. */
|
||||
|
||||
const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
|
||||
registerCleanupFunction(function () {
|
||||
Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
|
||||
});
|
||||
|
||||
// We'll use 2 states so that we can make sure calling setWindowState doesn't
|
||||
// wipe out currently restoring data.
|
||||
let state1 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com#1" }] },
|
||||
{ entries: [{ url: "http://example.com#2" }] },
|
||||
{ entries: [{ url: "http://example.com#3" }] },
|
||||
{ entries: [{ url: "http://example.com#4" }] },
|
||||
{ entries: [{ url: "http://example.com#5" }] },
|
||||
] }] };
|
||||
let state2 = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org#1" }] },
|
||||
{ entries: [{ url: "http://example.org#2" }] },
|
||||
{ entries: [{ url: "http://example.org#3" }] },
|
||||
{ entries: [{ url: "http://example.org#4" }] },
|
||||
{ entries: [{ url: "http://example.org#5" }] }
|
||||
] }] };
|
||||
let numTabs = 2 + state2.windows[0].tabs.length;
|
||||
|
||||
let loadCount = 0;
|
||||
gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
|
||||
// When loadCount == 2, we'll also restore state2 into the window
|
||||
if (++loadCount == 2) {
|
||||
ss.setWindowState(window, JSON.stringify(state2), true);
|
||||
}
|
||||
|
||||
if (loadCount < numTabs) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't actually care about load order in this test, just that they all
|
||||
// do load.
|
||||
is(loadCount, numTabs, "all tabs were restored");
|
||||
// window.__SS_tabsToRestore isn't decremented until after the progress
|
||||
// listener is called. Since we get in here before that, we still expect
|
||||
// the count to be 1.
|
||||
is(window.__SS_tabsToRestore, 1, "window doesn't think there are more tabs to restore");
|
||||
is(aNeedRestore, 0, "there are no tabs left needing restore");
|
||||
|
||||
gProgressListener.unsetCallback();
|
||||
executeSoon(function () {
|
||||
waitForBrowserState(JSON.parse(stateBackup), finish);
|
||||
});
|
||||
});
|
||||
|
||||
ss.setWindowState(window, JSON.stringify(state1), true);
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
let state = {windows:[{tabs:[
|
||||
{entries:[{url:"http://example.com#1"}]},
|
||||
{entries:[{url:"http://example.com#2"}]},
|
||||
|
@ -2,9 +2,6 @@
|
||||
* 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/. */
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function cleanup() {
|
||||
|
@ -2,9 +2,6 @@
|
||||
* 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/. */
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
function cleanup() {
|
||||
|
@ -1,9 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
let statePinned = {windows:[{tabs:[
|
||||
|
@ -1,8 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
|
||||
let tabState = {
|
||||
entries: [{url: "data:text/html,<input%20id='foo'>", formdata: { id: { "foo": "bar" } } }]
|
||||
};
|
||||
|
@ -2,6 +2,9 @@
|
||||
* 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/. */
|
||||
|
||||
const TAB_STATE_NEEDS_RESTORE = 1;
|
||||
const TAB_STATE_RESTORING = 2;
|
||||
|
||||
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
|
||||
|
||||
// Some tests here assume that all restored tabs are loaded without waiting for
|
||||
@ -200,3 +203,77 @@ var gUniqueCounter = 0;
|
||||
function r() {
|
||||
return Date.now() + "-" + (++gUniqueCounter);
|
||||
}
|
||||
|
||||
function BrowserWindowIterator() {
|
||||
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let currentWindow = windowsEnum.getNext();
|
||||
if (!currentWindow.closed) {
|
||||
yield currentWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let gProgressListener = {
|
||||
_callback: null,
|
||||
_checkRestoreState: true,
|
||||
|
||||
setCallback: function gProgressListener_setCallback(aCallback, aCheckRestoreState = true) {
|
||||
if (!this._callback) {
|
||||
window.gBrowser.addTabsProgressListener(this);
|
||||
}
|
||||
this._callback = aCallback;
|
||||
this._checkRestoreState = aCheckRestoreState;
|
||||
},
|
||||
|
||||
unsetCallback: function gProgressListener_unsetCallback() {
|
||||
if (this._callback) {
|
||||
this._callback = null;
|
||||
window.gBrowser.removeTabsProgressListener(this);
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange:
|
||||
function gProgressListener_onStateChange(aBrowser, aWebProgress, aRequest,
|
||||
aStateFlags, aStatus) {
|
||||
if ((!this._checkRestoreState ||
|
||||
aBrowser.__SS_restoreState == TAB_STATE_RESTORING) &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
||||
let args = [aBrowser].concat(this._countTabs());
|
||||
this._callback.apply(this, args);
|
||||
}
|
||||
},
|
||||
|
||||
_countTabs: function gProgressListener_countTabs() {
|
||||
let needsRestore = 0, isRestoring = 0, wasRestored = 0;
|
||||
|
||||
for (let win in BrowserWindowIterator()) {
|
||||
for (let i = 0; i < win.gBrowser.tabs.length; i++) {
|
||||
let browser = win.gBrowser.tabs[i].linkedBrowser;
|
||||
if (browser.__SS_restoreState == TAB_STATE_RESTORING)
|
||||
isRestoring++;
|
||||
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
|
||||
needsRestore++;
|
||||
else
|
||||
wasRestored++;
|
||||
}
|
||||
}
|
||||
return [needsRestore, isRestoring, wasRestored];
|
||||
}
|
||||
};
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
gProgressListener.unsetCallback();
|
||||
});
|
||||
|
||||
// Close everything but our primary window. We can't use waitForFocus()
|
||||
// because apparently it's buggy. See bug 599253.
|
||||
function closeAllButPrimaryWindow() {
|
||||
for (let win in BrowserWindowIterator()) {
|
||||
if (win != window) {
|
||||
win.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -445,27 +445,13 @@ let PageThumbsExpiration = {
|
||||
}
|
||||
},
|
||||
|
||||
expireThumbnails: function Expiration_expireThumbnails(aURLsToKeep) {
|
||||
let keep = {};
|
||||
|
||||
// Transform all these URLs into file names.
|
||||
for (let url of aURLsToKeep) {
|
||||
keep[PageThumbsStorage.getLeafNameForURL(url)] = true;
|
||||
}
|
||||
|
||||
let numFilesRemoved = 0;
|
||||
let dir = PageThumbsStorage.getDirectory().path;
|
||||
let msg = {type: "getFilesInDirectory", path: dir};
|
||||
|
||||
PageThumbsWorker.postMessage(msg, function (aData) {
|
||||
let files = [file for (file of aData.result) if (!(file in keep))];
|
||||
let maxFilesToRemove = Math.max(EXPIRATION_MIN_CHUNK_SIZE,
|
||||
Math.round(files.length / 2));
|
||||
|
||||
let fileNames = files.slice(0, maxFilesToRemove);
|
||||
let filePaths = [dir + "/" + fileName for (fileName of fileNames)];
|
||||
PageThumbsWorker.postMessage({type: "removeFiles", paths: filePaths});
|
||||
});
|
||||
expireThumbnails: function Expiration_expireThumbnails(aURLsToKeep, aCallback) {
|
||||
PageThumbsWorker.postMessage({
|
||||
type: "expireFilesInDirectory",
|
||||
minChunkSize: EXPIRATION_MIN_CHUNK_SIZE,
|
||||
path: PageThumbsStorage.getDirectory().path,
|
||||
filesToKeep: [PageThumbsStorage.getLeafNameForURL(url) for (url of aURLsToKeep)]
|
||||
}, aCallback);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -22,11 +22,8 @@ let PageThumbsWorker = {
|
||||
case "removeFile":
|
||||
data.result = this.removeFile(msg);
|
||||
break;
|
||||
case "removeFiles":
|
||||
data.result = this.removeFiles(msg);
|
||||
break;
|
||||
case "getFilesInDirectory":
|
||||
data.result = this.getFilesInDirectory(msg);
|
||||
case "expireFilesInDirectory":
|
||||
data.result = this.expireFilesInDirectory(msg);
|
||||
break;
|
||||
default:
|
||||
data.result = false;
|
||||
@ -37,20 +34,6 @@ let PageThumbsWorker = {
|
||||
self.postMessage(data);
|
||||
},
|
||||
|
||||
getFilesInDirectory: function Worker_getFilesInDirectory(msg) {
|
||||
let iter = new OS.File.DirectoryIterator(msg.path);
|
||||
let entries = [];
|
||||
|
||||
for (let entry in iter) {
|
||||
if (!entry.isDir && !entry.isSymLink) {
|
||||
entries.push(entry.name);
|
||||
}
|
||||
}
|
||||
|
||||
iter.close();
|
||||
return entries;
|
||||
},
|
||||
|
||||
removeFile: function Worker_removeFile(msg) {
|
||||
try {
|
||||
OS.File.remove(msg.path);
|
||||
@ -60,16 +43,30 @@ let PageThumbsWorker = {
|
||||
}
|
||||
},
|
||||
|
||||
removeFiles: function Worker_removeFiles(msg) {
|
||||
for (let file of msg.paths) {
|
||||
try {
|
||||
OS.File.remove(file);
|
||||
} catch (e) {
|
||||
// We couldn't remove the file for some reason.
|
||||
// Let's just continue with the next one.
|
||||
expireFilesInDirectory: function Worker_expireFilesInDirectory(msg) {
|
||||
let entries = this.getFileEntriesInDirectory(msg.path, msg.filesToKeep);
|
||||
let limit = Math.max(msg.minChunkSize, Math.round(entries.length / 2));
|
||||
|
||||
for (let entry of entries) {
|
||||
this.removeFile(entry);
|
||||
|
||||
// Check if we reached the limit of files to remove.
|
||||
if (--limit <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
getFileEntriesInDirectory:
|
||||
function Worker_getFileEntriesInDirectory(aPath, aSkipFiles) {
|
||||
let skip = new Set(aSkipFiles);
|
||||
let iter = new OS.File.DirectoryIterator(aPath);
|
||||
|
||||
return [entry
|
||||
for (entry in iter)
|
||||
if (!entry.isDir && !entry.isSymLink && !skip.has(entry.name))];
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -13,6 +13,7 @@ include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_BROWSER_FILES = \
|
||||
browser_thumbnails_capture.js \
|
||||
browser_thumbnails_expiration.js \
|
||||
browser_thumbnails_privacy.js \
|
||||
browser_thumbnails_redirect.js \
|
||||
browser_thumbnails_storage.js \
|
||||
|
@ -0,0 +1,78 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "http://mochi.test:8888/?t=" + Date.now();
|
||||
const URL1 = URL + "#1";
|
||||
const URL2 = URL + "#2";
|
||||
const URL3 = URL + "#3";
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
let tmp = {};
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript("resource:///modules/PageThumbs.jsm", tmp);
|
||||
|
||||
const {EXPIRATION_MIN_CHUNK_SIZE, PageThumbsExpiration} = tmp;
|
||||
|
||||
function runTests() {
|
||||
// Create three thumbnails.
|
||||
createDummyThumbnail(URL1);
|
||||
ok(thumbnailExists(URL1), "first thumbnail created");
|
||||
|
||||
createDummyThumbnail(URL2);
|
||||
ok(thumbnailExists(URL2), "second thumbnail created");
|
||||
|
||||
createDummyThumbnail(URL3);
|
||||
ok(thumbnailExists(URL3), "third thumbnail created");
|
||||
|
||||
// Remove the third thumbnail.
|
||||
yield expireThumbnails([URL1, URL2]);
|
||||
ok(thumbnailExists(URL1), "first thumbnail still exists");
|
||||
ok(thumbnailExists(URL2), "second thumbnail still exists");
|
||||
ok(!thumbnailExists(URL3), "third thumbnail has been removed");
|
||||
|
||||
// Remove the second thumbnail.
|
||||
yield expireThumbnails([URL1]);
|
||||
ok(thumbnailExists(URL1), "first thumbnail still exists");
|
||||
ok(!thumbnailExists(URL2), "second thumbnail has been removed");
|
||||
|
||||
// Remove all thumbnails.
|
||||
yield expireThumbnails([]);
|
||||
ok(!thumbnailExists(URL1), "all thumbnails have been removed");
|
||||
|
||||
// Create some more files than the min chunk size.
|
||||
let urls = [];
|
||||
for (let i = 0; i < EXPIRATION_MIN_CHUNK_SIZE + 10; i++) {
|
||||
urls.push(URL + "#dummy" + i);
|
||||
}
|
||||
|
||||
urls.forEach(createDummyThumbnail);
|
||||
ok(urls.every(thumbnailExists), "all dummy thumbnails created");
|
||||
|
||||
// Expire thumbnails and expect 10 remaining.
|
||||
yield expireThumbnails([]);
|
||||
let remainingURLs = [u for (u of urls) if (thumbnailExists(u))];
|
||||
is(remainingURLs.length, 10, "10 dummy thumbnails remaining");
|
||||
|
||||
// Expire thumbnails again. All should be gone by now.
|
||||
yield expireThumbnails([]);
|
||||
remainingURLs = [u for (u of remainingURLs) if (thumbnailExists(u))];
|
||||
is(remainingURLs.length, 0, "no dummy thumbnails remaining");
|
||||
}
|
||||
|
||||
function createDummyThumbnail(aURL) {
|
||||
let file = PageThumbsStorage.getFileForURL(aURL);
|
||||
let fos = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
let data = "dummy";
|
||||
fos.write(data, data.length);
|
||||
FileUtils.closeSafeFileOutputStream(fos);
|
||||
}
|
||||
|
||||
function expireThumbnails(aKeep) {
|
||||
PageThumbsExpiration.expireThumbnails(aKeep, function () {
|
||||
executeSoon(next);
|
||||
});
|
||||
}
|
@ -96,8 +96,7 @@ function createThumbnail() {
|
||||
|
||||
function whenFileExists(aCallback) {
|
||||
let callback;
|
||||
let file = PageThumbsStorage.getFileForURL(URL);
|
||||
if (file.exists() && file.fileSize) {
|
||||
if (thumbnailExists(URL)) {
|
||||
callback = aCallback;
|
||||
} else {
|
||||
callback = function () whenFileExists(aCallback);
|
||||
|
@ -146,3 +146,12 @@ function checkCanvasColor(aContext, aRed, aGreen, aBlue, aMessage) {
|
||||
let [r, g, b] = aContext.getImageData(0, 0, 1, 1).data;
|
||||
ok(r == aRed && g == aGreen && b == aBlue, aMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a thumbnail for the given URL exists.
|
||||
* @param aURL The url associated to the thumbnail.
|
||||
*/
|
||||
function thumbnailExists(aURL) {
|
||||
let file = PageThumbsStorage.getFileForURL(aURL);
|
||||
return file.exists() && file.fileSize;
|
||||
}
|
||||
|
@ -31,15 +31,6 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour
|
||||
var ArraySet = require('source-map/array-set').ArraySet;
|
||||
var base64VLQ = require('source-map/base64-vlq');
|
||||
|
||||
// TODO: bug 673487
|
||||
//
|
||||
// Sometime in the future, if we decide we need to be able to query where in
|
||||
// the generated source a piece of the original code came from, we may want to
|
||||
// add a slot `_originalMappings` which would be an object keyed by the
|
||||
// original source and whose value would be an array of mappings ordered by
|
||||
// original line/col rather than generated (which is what we have now in
|
||||
// `_generatedMappings`).
|
||||
|
||||
/**
|
||||
* A SourceMapConsumer instance represents a parsed source map which we can
|
||||
* query for information about the original file positions by giving it a file
|
||||
@ -72,7 +63,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour
|
||||
function SourceMapConsumer(aSourceMap) {
|
||||
var sourceMap = aSourceMap;
|
||||
if (typeof aSourceMap === 'string') {
|
||||
sourceMap = JSON.parse(aSourceMap);
|
||||
sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
|
||||
}
|
||||
|
||||
var version = util.getArg(sourceMap, 'version');
|
||||
@ -89,9 +80,9 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour
|
||||
this._names = ArraySet.fromArray(names);
|
||||
this._sources = ArraySet.fromArray(sources);
|
||||
|
||||
// `this._generatedMappings` hold the parsed mapping coordinates from the
|
||||
// source map's "mappings" attribute. Each object in the array is of the
|
||||
// form
|
||||
// `this._generatedMappings` and `this._originalMappings` hold the parsed
|
||||
// mapping coordinates from the source map's "mappings" attribute. Each
|
||||
// object in the array is of the form
|
||||
//
|
||||
// {
|
||||
// generatedLine: The line number in the generated code,
|
||||
@ -108,7 +99,12 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour
|
||||
//
|
||||
// All properties except for `generatedLine` and `generatedColumn` can be
|
||||
// `null`.
|
||||
//
|
||||
// `this._generatedMappings` is ordered by the generated positions.
|
||||
//
|
||||
// `this._originalMappings` is ordered by the original positions.
|
||||
this._generatedMappings = [];
|
||||
this._originalMappings = [];
|
||||
this._parseMappings(mappings, sourceRoot);
|
||||
}
|
||||
|
||||
@ -195,8 +191,65 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour
|
||||
}
|
||||
|
||||
this._generatedMappings.push(mapping);
|
||||
this._originalMappings.push(mapping);
|
||||
}
|
||||
}
|
||||
|
||||
this._originalMappings.sort(this._compareOriginalPositions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Comparator between two mappings where the original positions are compared.
|
||||
*/
|
||||
SourceMapConsumer.prototype._compareOriginalPositions =
|
||||
function SourceMapConsumer_compareOriginalPositions(mappingA, mappingB) {
|
||||
if (mappingA.source > mappingB.source) {
|
||||
return 1;
|
||||
}
|
||||
else if (mappingA.source < mappingB.source) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
var cmp = mappingA.originalLine - mappingB.originalLine;
|
||||
return cmp === 0
|
||||
? mappingA.originalColumn - mappingB.originalColumn
|
||||
: cmp;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Comparator between two mappings where the generated positions are compared.
|
||||
*/
|
||||
SourceMapConsumer.prototype._compareGeneratedPositions =
|
||||
function SourceMapConsumer_compareGeneratedPositions(mappingA, mappingB) {
|
||||
var cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
return cmp === 0
|
||||
? mappingA.generatedColumn - mappingB.generatedColumn
|
||||
: cmp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the mapping that best matches the hypothetical "needle" mapping that
|
||||
* we are searching for in the given "haystack" of mappings.
|
||||
*/
|
||||
SourceMapConsumer.prototype._findMapping =
|
||||
function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
|
||||
aColumnName, aComparator) {
|
||||
// To return the position we are searching for, we must first find the
|
||||
// mapping for the given position and then return the opposite position it
|
||||
// points to. Because the mappings are sorted, we can use binary search to
|
||||
// find the best mapping.
|
||||
|
||||
if (aNeedle[aLineName] <= 0) {
|
||||
throw new TypeError('Line must be greater than or equal to 1, got '
|
||||
+ aNeedle[aLineName]);
|
||||
}
|
||||
if (aNeedle[aColumnName] < 0) {
|
||||
throw new TypeError('Column must be greater than or equal to 0, got '
|
||||
+ aNeedle[aColumnName]);
|
||||
}
|
||||
|
||||
return binarySearch.search(aNeedle, aMappings, aComparator);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -216,35 +269,16 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour
|
||||
*/
|
||||
SourceMapConsumer.prototype.originalPositionFor =
|
||||
function SourceMapConsumer_originalPositionFor(aArgs) {
|
||||
// To return the original position, we must first find the mapping for the
|
||||
// given generated position and then return the original position it
|
||||
// points to. Because the mappings are sorted by generated line/column, we
|
||||
// can use binary search to find the best mapping.
|
||||
|
||||
// To perform a binary search on the mappings, we must be able to compare
|
||||
// two mappings.
|
||||
function compare(mappingA, mappingB) {
|
||||
var cmp = mappingA.generatedLine - mappingB.generatedLine;
|
||||
return cmp === 0
|
||||
? mappingA.generatedColumn - mappingB.generatedColumn
|
||||
: cmp;
|
||||
}
|
||||
|
||||
// This is the mock of the mapping we are looking for: the needle in the
|
||||
// haystack of mappings.
|
||||
var needle = {
|
||||
generatedLine: util.getArg(aArgs, 'line'),
|
||||
generatedColumn: util.getArg(aArgs, 'column')
|
||||
};
|
||||
|
||||
if (needle.generatedLine <= 0) {
|
||||
throw new TypeError('Line must be greater than or equal to 1.');
|
||||
}
|
||||
if (needle.generatedColumn < 0) {
|
||||
throw new TypeError('Column must be greater than or equal to 0.');
|
||||
}
|
||||
|
||||
var mapping = binarySearch.search(needle, this._generatedMappings, compare);
|
||||
var mapping = this._findMapping(needle,
|
||||
this._generatedMappings,
|
||||
"generatedLine",
|
||||
"generatedColumn",
|
||||
this._compareGeneratedPositions)
|
||||
|
||||
if (mapping) {
|
||||
return {
|
||||
@ -261,7 +295,47 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour
|
||||
column: null,
|
||||
name: null
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the generated line and column information for the original source,
|
||||
* line, and column positions provided. The only argument is an object with
|
||||
* the following properties:
|
||||
*
|
||||
* - source: The filename of the original source.
|
||||
* - line: The line number in the original source.
|
||||
* - column: The column number in the original source.
|
||||
*
|
||||
* and an object is returned with the following properties:
|
||||
*
|
||||
* - line: The line number in the generated source, or null.
|
||||
* - column: The column number in the generated source, or null.
|
||||
*/
|
||||
SourceMapConsumer.prototype.generatedPositionFor =
|
||||
function SourceMapConsumer_generatedPositionFor(aArgs) {
|
||||
var needle = {
|
||||
source: util.getArg(aArgs, 'source'),
|
||||
originalLine: util.getArg(aArgs, 'line'),
|
||||
originalColumn: util.getArg(aArgs, 'column')
|
||||
};
|
||||
|
||||
var mapping = this._findMapping(needle,
|
||||
this._originalMappings,
|
||||
"originalLine",
|
||||
"originalColumn",
|
||||
this._compareOriginalPositions)
|
||||
|
||||
if (mapping) {
|
||||
return {
|
||||
line: util.getArg(mapping, 'generatedLine', null),
|
||||
column: util.getArg(mapping, 'generatedColumn', null)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
line: null,
|
||||
column: null
|
||||
};
|
||||
};
|
||||
|
||||
exports.SourceMapConsumer = SourceMapConsumer;
|
||||
@ -885,7 +959,9 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/s
|
||||
}, this);
|
||||
}
|
||||
else if (aChunk instanceof SourceNode || typeof aChunk === "string") {
|
||||
this.children.push(aChunk);
|
||||
if (aChunk) {
|
||||
this.children.push(aChunk);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new TypeError(
|
||||
@ -973,7 +1049,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/s
|
||||
lastChild.replaceRight(aPattern, aReplacement);
|
||||
}
|
||||
else if (typeof lastChild === 'string') {
|
||||
this.children[this.children.lenth - 1] = lastChild.replace(aPattern, aReplacement);
|
||||
this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
|
||||
}
|
||||
else {
|
||||
this.children.push(''.replace(aPattern, aReplacement));
|
||||
|
@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
Components.utils.import('resource://gre/modules/devtools/Require.jsm');
|
||||
Components.utils.import('resource:///modules/devtools/SourceMap.jsm');
|
||||
Components.utils.import('resource://gre/modules/devtools/SourceMap.jsm');
|
||||
|
||||
let EXPORTED_SYMBOLS = [ "define", "runSourceMapTests" ];
|
||||
/* -*- Mode: js; js-indent-level: 2; -*- */
|
||||
@ -109,23 +109,40 @@ define('test/source-map/util', ['require', 'exports', 'module' ], function(requi
|
||||
};
|
||||
|
||||
function assertMapping(generatedLine, generatedColumn, originalSource,
|
||||
originalLine, originalColumn, name, map, assert) {
|
||||
var mapping = map.originalPositionFor({
|
||||
line: generatedLine,
|
||||
column: generatedColumn
|
||||
});
|
||||
assert.equal(mapping.name, name,
|
||||
'Incorrect name, expected ' + JSON.stringify(name)
|
||||
+ ', got ' + JSON.stringify(mapping.name));
|
||||
assert.equal(mapping.line, originalLine,
|
||||
'Incorrect line, expected ' + JSON.stringify(originalLine)
|
||||
+ ', got ' + JSON.stringify(mapping.line));
|
||||
assert.equal(mapping.column, originalColumn,
|
||||
'Incorrect column, expected ' + JSON.stringify(originalColumn)
|
||||
+ ', got ' + JSON.stringify(mapping.column));
|
||||
assert.equal(mapping.source, originalSource,
|
||||
'Incorrect source, expected ' + JSON.stringify(originalSource)
|
||||
+ ', got ' + JSON.stringify(mapping.source));
|
||||
originalLine, originalColumn, name, map, assert,
|
||||
dontTestGenerated, dontTestOriginal) {
|
||||
if (!dontTestOriginal) {
|
||||
var origMapping = map.originalPositionFor({
|
||||
line: generatedLine,
|
||||
column: generatedColumn
|
||||
});
|
||||
assert.equal(origMapping.name, name,
|
||||
'Incorrect name, expected ' + JSON.stringify(name)
|
||||
+ ', got ' + JSON.stringify(origMapping.name));
|
||||
assert.equal(origMapping.line, originalLine,
|
||||
'Incorrect line, expected ' + JSON.stringify(originalLine)
|
||||
+ ', got ' + JSON.stringify(origMapping.line));
|
||||
assert.equal(origMapping.column, originalColumn,
|
||||
'Incorrect column, expected ' + JSON.stringify(originalColumn)
|
||||
+ ', got ' + JSON.stringify(origMapping.column));
|
||||
assert.equal(origMapping.source, originalSource,
|
||||
'Incorrect source, expected ' + JSON.stringify(originalSource)
|
||||
+ ', got ' + JSON.stringify(origMapping.source));
|
||||
}
|
||||
|
||||
if (!dontTestGenerated) {
|
||||
var genMapping = map.generatedPositionFor({
|
||||
source: originalSource,
|
||||
line: originalLine,
|
||||
column: originalColumn
|
||||
});
|
||||
assert.equal(genMapping.line, generatedLine,
|
||||
'Incorrect line, expected ' + JSON.stringify(generatedLine)
|
||||
+ ', got ' + JSON.stringify(genMapping.line));
|
||||
assert.equal(genMapping.column, generatedColumn,
|
||||
'Incorrect column, expected ' + JSON.stringify(generatedColumn)
|
||||
+ ', got ' + JSON.stringify(genMapping.column));
|
||||
}
|
||||
}
|
||||
exports.assertMapping = assertMapping;
|
||||
|
||||
|
@ -56,14 +56,22 @@ define("test/source-map/test-dog-fooding", ["require", "exports", "module"], fun
|
||||
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, smc, assert);
|
||||
|
||||
// Fuzzy
|
||||
util.assertMapping(2, 0, null, null, null, null, smc, assert);
|
||||
util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert);
|
||||
util.assertMapping(3, 0, '/wu/tang/gza.coffee', 1, 0, null, smc, assert);
|
||||
util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert);
|
||||
util.assertMapping(4, 0, '/wu/tang/gza.coffee', 2, 0, null, smc, assert);
|
||||
util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert);
|
||||
util.assertMapping(5, 0, '/wu/tang/gza.coffee', 3, 0, null, smc, assert);
|
||||
util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert);
|
||||
|
||||
// Original to generated
|
||||
util.assertMapping(2, 0, null, null, null, null, smc, assert, true);
|
||||
util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
|
||||
util.assertMapping(3, 0, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
|
||||
util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
|
||||
util.assertMapping(4, 0, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
|
||||
util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
|
||||
util.assertMapping(5, 0, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
|
||||
util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert, true);
|
||||
|
||||
// Generated to original
|
||||
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, smc, assert, null, true);
|
||||
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, smc, assert, null, true);
|
||||
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, smc, assert, null, true);
|
||||
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, smc, assert, null, true);
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -61,12 +61,24 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul
|
||||
util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', map, assert);
|
||||
};
|
||||
|
||||
exports['test mapping tokens back fuzzy'] = function (assert, util) {
|
||||
exports['test mapping tokens fuzzy'] = function (assert, util) {
|
||||
var map = new SourceMapConsumer(util.testMap);
|
||||
|
||||
util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert);
|
||||
util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert);
|
||||
util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert);
|
||||
// Finding original positions
|
||||
util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert, true);
|
||||
util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert, true);
|
||||
util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert, true);
|
||||
|
||||
// Finding generated positions
|
||||
util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', map, assert, null, true);
|
||||
util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', map, assert, null, true);
|
||||
util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, map, assert, null, true);
|
||||
};
|
||||
|
||||
exports['test creating source map consumers with )]}\' prefix'] = function (assert, util) {
|
||||
assert.doesNotThrow(function () {
|
||||
var map = new SourceMapConsumer(")]}'" + JSON.stringify(util.testMap));
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -117,6 +117,22 @@ define("test/source-map/test-source-node", ["require", "exports", "module"], fun
|
||||
});
|
||||
};
|
||||
|
||||
exports['test .replaceRight'] = function (assert, util) {
|
||||
var node;
|
||||
|
||||
// Not nested
|
||||
node = new SourceNode(null, null, null, 'hello world');
|
||||
node.replaceRight(/world/, 'universe');
|
||||
assert.equal(node.toString(), 'hello universe');
|
||||
|
||||
// Nested
|
||||
node = new SourceNode(null, null, null,
|
||||
[new SourceNode(null, null, null, 'hey sexy mama, '),
|
||||
new SourceNode(null, null, null, 'want to kill all humans?')]);
|
||||
node.replaceRight(/kill all humans/, 'watch Futurama');
|
||||
assert.equal(node.toString(), 'hey sexy mama, want to watch Futurama?');
|
||||
};
|
||||
|
||||
exports['test .toStringWithSourceMap()'] = function (assert, util) {
|
||||
var node = new SourceNode(null, null, null,
|
||||
['(function () {\n',
|
||||
|
Loading…
Reference in New Issue
Block a user