Bug 861409 - Use a content script to listen for pageshow events; r=yoric,jaws

This commit is contained in:
Tim Taubert 2013-04-20 10:05:20 +02:00
parent 13d8b090f4
commit 6123af3720
7 changed files with 171 additions and 29 deletions

View File

@ -760,6 +760,7 @@ var gBrowserInit = {
window.addEventListener("AppCommand", HandleAppCommandEvent, true);
messageManager.loadFrameScript("chrome://browser/content/content.js", true);
messageManager.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
// initialize observers and listeners
// and give C++ access to gBrowser

View File

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function debug(msg) {
Services.console.logStringMessage("SessionStoreContent: " + msg);
}
/**
* Listens for and handles content events that we need for the
* session store service to be notified of state changes in content.
*/
let EventListener = {
init: function () {
addEventListener("pageshow", this, true);
},
handleEvent: function (event) {
switch (event.type) {
case "pageshow":
if (event.persisted)
sendAsyncMessage("SessionStore:pageshow");
break;
default:
debug("received unknown event '" + event.type + "'");
break;
}
}
};
EventListener.init();

View File

@ -5,3 +5,4 @@
browser.jar:
* content/browser/aboutSessionRestore.xhtml (content/aboutSessionRestore.xhtml)
* content/browser/aboutSessionRestore.js (content/aboutSessionRestore.js)
content/browser/content-sessionStore.js (content/content-sessionStore.js)

View File

@ -609,6 +609,26 @@ let SessionStoreInternal = {
}
},
/**
* This method handles incoming messages sent by the session store content
* script and thus enables communication with OOP tabs.
*/
receiveMessage: function ssi_receiveMessage(aMessage) {
var browser = aMessage.target;
var win = browser.ownerDocument.defaultView;
switch (aMessage.name) {
case "SessionStore:pageshow":
this.onTabLoad(win, browser);
break;
default:
debug("received unknown message '" + aMessage.name + "'");
break;
}
this._clearRestoringWindows();
},
/* ........ Window Event Handlers .............. */
/**
@ -621,11 +641,10 @@ let SessionStoreInternal = {
// If __SS_restore_data is set, then we need to restore the document
// (form data, scrolling, etc.). This will only happen when a tab is
// first restored.
if (aEvent.currentTarget.__SS_restore_data)
this.restoreDocument(win, aEvent.currentTarget, aEvent);
// We still need to call onTabLoad, so fall through to "pageshow" case.
case "pageshow":
this.onTabLoad(win, aEvent.currentTarget, aEvent);
let browser = aEvent.currentTarget;
if (browser.__SS_restore_data)
this.restoreDocument(win, browser, aEvent);
this.onTabLoad(win, browser);
break;
case "change":
case "input":
@ -1193,11 +1212,12 @@ let SessionStoreInternal = {
onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser;
browser.addEventListener("load", this, true);
browser.addEventListener("pageshow", this, true);
browser.addEventListener("change", this, true);
browser.addEventListener("input", this, true);
browser.addEventListener("DOMAutoComplete", this, true);
browser.messageManager.addMessageListener("SessionStore:pageshow", this);
if (!aNoNotification) {
this.saveStateDelayed(aWindow);
}
@ -1217,11 +1237,12 @@ let SessionStoreInternal = {
onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser;
browser.removeEventListener("load", this, true);
browser.removeEventListener("pageshow", this, true);
browser.removeEventListener("change", this, true);
browser.removeEventListener("input", this, true);
browser.removeEventListener("DOMAutoComplete", this, true);
browser.messageManager.removeMessageListener("SessionStore:pageshow", this);
delete browser.__SS_data;
delete browser.__SS_tabStillLoading;
delete browser.__SS_formDataSaved;
@ -1289,17 +1310,14 @@ let SessionStoreInternal = {
* Window reference
* @param aBrowser
* Browser reference
* @param aEvent
* Event obj
*/
onTabLoad: function ssi_onTabLoad(aWindow, aBrowser, aEvent) {
onTabLoad: function ssi_onTabLoad(aWindow, aBrowser) {
// react on "load" and solitary "pageshow" events (the first "pageshow"
// following "load" is too late for deleting the data caches)
// It's possible to get a load event after calling stop on a browser (when
// overwriting tabs). We want to return early if the tab hasn't been restored yet.
if ((aEvent.type != "load" && !aEvent.persisted) ||
(aBrowser.__SS_restoreState &&
aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)) {
if (aBrowser.__SS_restoreState &&
aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
return;
}

View File

@ -25,6 +25,7 @@ MOCHITEST_BROWSER_FILES = \
browser_form_restore_events_sample.html \
browser_formdata_format.js \
browser_formdata_format_sample.html \
browser_pageshow.js \
browser_248970_b_perwindowpb.js \
browser_248970_b_sample.html \
browser_339445.js \

View File

@ -88,11 +88,11 @@ function test_2() {
forceWriteState(function(state) {
is(state.windows.length, 1,
"sessionstore state: 1 windows in data being writted to disk");
"sessionstore state: 1 windows in data being written to disk");
is (state.selectedWindow, 1,
"Selected window is updated to match one of the saved windows");
is(state._closedWindows.length, 0,
"sessionstore state: no closed windows in data being writted to disk");
"sessionstore state: no closed windows in data being written to disk");
runNextTest();
});
});
@ -114,20 +114,25 @@ function test_3() {
is(curState.selectedWindow, 4, "Last window opened is the one selected");
waitForWindowClose(normalWindow, function() {
forceWriteState(function(state) {
is(state.windows.length, 2,
"sessionstore state: 2 windows in data being writted to disk");
is(state.selectedWindow, 2,
"Selected window is updated to match one of the saved windows");
state.windows.forEach(function(win) {
is(!win.isPrivate, true, "Saved window is not private");
// Load another tab before checking the written state so that
// the list of restoring windows gets cleared. Otherwise the
// window we just closed would be marked as not closed.
waitForTabLoad(aWindow, "http://www.example.com/", function() {
forceWriteState(function(state) {
is(state.windows.length, 2,
"sessionstore state: 2 windows in data being written to disk");
is(state.selectedWindow, 2,
"Selected window is updated to match one of the saved windows");
state.windows.forEach(function(win) {
is(!win.isPrivate, true, "Saved window is not private");
});
is(state._closedWindows.length, 1,
"sessionstore state: 1 closed window in data being written to disk");
state._closedWindows.forEach(function(win) {
is(!win.isPrivate, true, "Closed window is not private");
});
runNextTest();
});
is(state._closedWindows.length, 1,
"sessionstore state: 1 closed window in data being writted to disk");
state._closedWindows.forEach(function(win) {
is(!win.isPrivate, true, "Closed window is not private");
});
runNextTest();
});
});
});
@ -178,7 +183,7 @@ function testOnWindow(aIsPrivate, aCallback) {
function waitForTabLoad(aWin, aURL, aCallback) {
aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
aCallback();
executeSoon(aCallback);
}, true);
aWin.gBrowser.selectedBrowser.loadURI(aURL);
}

View File

@ -0,0 +1,84 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
TestRunner.run();
}
/**
* This test ensures that loading a page from bfcache (by going back or forward
* in history) marks the window as dirty and causes data about the tab that
* changed to be re-collected.
*
* We will do this by creating a tab with two history entries and going back
* to the first. When we now request the current browser state from the
* session store service the first history entry must be selected.
*/
function runTests() {
// Create a dummy window that is regarded as active. We need to do this
// because we always collect data for tabs of active windows no matter if
// the window is dirty or not.
let win = OpenBrowserWindow();
yield waitForLoad(win);
// Create a tab with two history entries.
let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
yield loadURI("about:robots");
yield loadURI("about:mozilla");
// All windows currently marked as dirty will be written to disk
// and thus marked clean afterwards.
yield forceWriteState();
// Go back to 'about:robots' - which is loaded from the bfcache and thus
// will not fire a 'load' event but a 'pageshow' event with persisted=true.
waitForPageShow();
yield gBrowser.selectedBrowser.goBack();
is(tab.linkedBrowser.currentURI.spec, "about:robots", "url is about:robots");
// If by receiving the 'pageshow' event the first window has correctly
// been marked as dirty, getBrowserState() should return the tab we created
// with the right history entry (about:robots) selected.
let state = JSON.parse(ss.getBrowserState());
is(state.windows[0].tabs[1].index, 1, "first history entry is selected");
// Clean up after ourselves.
gBrowser.removeTab(tab);
win.close();
}
function forceWriteState() {
const PREF = "browser.sessionstore.interval";
const TOPIC = "sessionstore-state-write";
Services.obs.addObserver(function observe() {
Services.obs.removeObserver(observe, TOPIC);
Services.prefs.clearUserPref(PREF);
executeSoon(next);
}, TOPIC, false);
Services.prefs.setIntPref(PREF, 0);
}
function loadURI(aURI) {
let browser = gBrowser.selectedBrowser;
waitForLoad(browser);
browser.loadURI(aURI);
}
function waitForLoad(aElement) {
aElement.addEventListener("load", function onLoad() {
aElement.removeEventListener("load", onLoad, true);
executeSoon(next);
}, true);
}
function waitForPageShow() {
let mm = gBrowser.selectedBrowser.messageManager;
mm.addMessageListener("SessionStore:pageshow", function onPageShow() {
mm.removeMessageListener("SessionStore:pageshow", onPageShow);
executeSoon(next);
});
}