Bug 862442 - Use a content script to listen for input and change events; r=yoric

This commit is contained in:
Tim Taubert 2013-04-20 10:05:20 +02:00
parent 3d11af6229
commit 268d8cd42f
5 changed files with 170 additions and 23 deletions

View File

@ -12,8 +12,12 @@ function debug(msg) {
*/ */
let EventListener = { let EventListener = {
DOM_EVENTS: [
"pageshow", "change", "input"
],
init: function () { init: function () {
addEventListener("pageshow", this, true); this.DOM_EVENTS.forEach(e => addEventListener(e, this, true));
}, },
handleEvent: function (event) { handleEvent: function (event) {
@ -22,6 +26,10 @@ let EventListener = {
if (event.persisted) if (event.persisted)
sendAsyncMessage("SessionStore:pageshow"); sendAsyncMessage("SessionStore:pageshow");
break; break;
case "input":
case "change":
sendAsyncMessage("SessionStore:input");
break;
default: default:
debug("received unknown event '" + event.type + "'"); debug("received unknown event '" + event.type + "'");
break; break;

View File

@ -62,6 +62,18 @@ const CAPABILITIES = [
"DNSPrefetch", "Auth", "WindowControl" "DNSPrefetch", "Auth", "WindowControl"
]; ];
const MESSAGES = [
// The content script tells us that its form data (or that of one of its
// subframes) might have changed. This can be the contents or values of
// standard form fields or of ContentEditables.
"SessionStore:input",
// The content script has received a pageshow event. This happens when a
// page is loaded from bfcache without any network activity, i.e. when
// clicking the back or forward button.
"SessionStore:pageshow"
];
// These are tab events that we listen to. // These are tab events that we listen to.
const TAB_EVENTS = [ const TAB_EVENTS = [
"TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned", "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
@ -621,6 +633,9 @@ let SessionStoreInternal = {
case "SessionStore:pageshow": case "SessionStore:pageshow":
this.onTabLoad(win, browser); this.onTabLoad(win, browser);
break; break;
case "SessionStore:input":
this.onTabInput(win, browser);
break;
default: default:
debug("received unknown message '" + aMessage.name + "'"); debug("received unknown message '" + aMessage.name + "'");
break; break;
@ -646,11 +661,6 @@ let SessionStoreInternal = {
this.restoreDocument(win, browser, aEvent); this.restoreDocument(win, browser, aEvent);
this.onTabLoad(win, browser); this.onTabLoad(win, browser);
break; break;
case "change":
case "input":
case "DOMAutoComplete":
this.onTabInput(win, aEvent.currentTarget);
break;
case "TabOpen": case "TabOpen":
this.onTabAdd(win, aEvent.originalTarget); this.onTabAdd(win, aEvent.originalTarget);
break; break;
@ -1212,11 +1222,9 @@ let SessionStoreInternal = {
onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) { onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser; let browser = aTab.linkedBrowser;
browser.addEventListener("load", this, true); browser.addEventListener("load", this, true);
browser.addEventListener("change", this, true);
browser.addEventListener("input", this, true);
browser.addEventListener("DOMAutoComplete", this, true);
browser.messageManager.addMessageListener("SessionStore:pageshow", this); let mm = browser.messageManager;
MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
if (!aNoNotification) { if (!aNoNotification) {
this.saveStateDelayed(aWindow); this.saveStateDelayed(aWindow);
@ -1237,11 +1245,9 @@ let SessionStoreInternal = {
onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) { onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser; let browser = aTab.linkedBrowser;
browser.removeEventListener("load", this, true); browser.removeEventListener("load", this, true);
browser.removeEventListener("change", this, true);
browser.removeEventListener("input", this, true);
browser.removeEventListener("DOMAutoComplete", this, true);
browser.messageManager.removeMessageListener("SessionStore:pageshow", this); let mm = browser.messageManager;
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
delete browser.__SS_data; delete browser.__SS_data;
delete browser.__SS_tabStillLoading; delete browser.__SS_tabStillLoading;
@ -2254,16 +2260,8 @@ let SessionStoreInternal = {
} }
// designMode is undefined e.g. for XUL documents (as about:config) // designMode is undefined e.g. for XUL documents (as about:config)
if ((aContent.document.designMode || "") == "on" && aContent.document.body) { if ((aContent.document.designMode || "") == "on" && aContent.document.body)
if (aData.innerHTML === undefined && !aFullData) {
// we get no "input" events from iframes - listen for keypress here
let _this = this;
aContent.addEventListener("keypress", function(aEvent) {
_this.saveStateDelayed(aWindow, 3000);
}, true);
}
aData.innerHTML = aContent.document.body.innerHTML; aData.innerHTML = aContent.document.body.innerHTML;
}
} }
// get scroll position from nsIDOMWindowUtils, since it allows avoiding a // get scroll position from nsIDOMWindowUtils, since it allows avoiding a

View File

@ -25,6 +25,8 @@ MOCHITEST_BROWSER_FILES = \
browser_form_restore_events_sample.html \ browser_form_restore_events_sample.html \
browser_formdata_format.js \ browser_formdata_format.js \
browser_formdata_format_sample.html \ browser_formdata_format_sample.html \
browser_input.js \
browser_input_sample.html \
browser_pageshow.js \ browser_pageshow.js \
browser_248970_b_perwindowpb.js \ browser_248970_b_perwindowpb.js \
browser_248970_b_sample.html \ browser_248970_b_sample.html \

View File

@ -0,0 +1,121 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const URL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_input_sample.html";
function test() {
TestRunner.run();
}
/**
* This test ensures that modifying form input fields on a web page marks the
* window as dirty and causes the corresponding form data of the tab that
* changed to be re-collected.
*/
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 some form fields.
let tab = gBrowser.selectedTab = gBrowser.addTab(URL);
let browser = gBrowser.selectedBrowser;
yield waitForLoad(browser);
// All windows currently marked as dirty will be written to disk
// and thus marked clean afterwards.
yield forceWriteState();
// Modify the checkbox field's state.
let chk = browser.contentDocument.getElementById("chk");
EventUtils.sendMouseEvent({type: "click"}, chk);
yield waitForInput();
// Check that we'll save the form data state correctly.
let state = JSON.parse(ss.getBrowserState());
let {formdata} = state.windows[0].tabs[1].entries[0];
is(formdata.id.chk, true, "chk's value is correct");
// Clear dirty state of all windows again.
yield forceWriteState();
// Modify the text input field's state.
browser.contentDocument.getElementById("txt").focus();
EventUtils.synthesizeKey("m", {});
yield waitForInput();
// Check that we'll save the form data state correctly.
let state = JSON.parse(ss.getBrowserState());
let {formdata} = state.windows[0].tabs[1].entries[0];
is(formdata.id.chk, true, "chk's value is correct");
is(formdata.id.txt, "m", "txt's value is correct");
// Clear dirty state of all windows again.
yield forceWriteState();
// Modify the state of the checkbox contained in the iframe.
let cdoc = browser.contentDocument.getElementById("ifr").contentDocument;
EventUtils.sendMouseEvent({type: "click"}, cdoc.getElementById("chk"));
yield waitForInput();
// Modify the state of text field contained in the iframe.
cdoc.getElementById("txt").focus();
EventUtils.synthesizeKey("m", {});
yield waitForInput();
// Check that we'll save the iframe's form data correctly.
let state = JSON.parse(ss.getBrowserState());
let {formdata} = state.windows[0].tabs[1].entries[0].children[0];
is(formdata.id.chk, true, "iframe chk's value is correct");
is(formdata.id.txt, "m", "iframe txt's value is correct");
// Clear dirty state of all windows again.
yield forceWriteState();
// Modify the content editable's state.
browser.contentDocument.getElementById("ced").focus();
EventUtils.synthesizeKey("m", {});
yield waitForInput();
// Check the we'll correctly save the content editable's state.
let state = JSON.parse(ss.getBrowserState());
let {innerHTML} = state.windows[0].tabs[1].entries[0].children[1];
is(innerHTML, "m", "content editable's value is correct");
// 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 waitForLoad(aElement) {
aElement.addEventListener("load", function onLoad() {
aElement.removeEventListener("load", onLoad, true);
executeSoon(next);
}, true);
}
function waitForInput() {
let mm = gBrowser.selectedBrowser.messageManager;
mm.addMessageListener("SessionStore:input", function onPageShow() {
mm.removeMessageListener("SessionStore:input", onPageShow);
executeSoon(next);
});
}

View File

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html dir="ltr" xml:lang="en-US" lang="en-US">
<head>
<meta charset="utf-8">
<title>sessionstore input test</title>
</head>
<body>
<input id="chk" type="checkbox" /><input id="txt" />
<iframe id="ifr" src="data:text/html;charset=utf-8,<input id=chk type=checkbox /><input id=txt />"></iframe>
<iframe id="ced"></iframe>
<script type="text/javascript">
addEventListener("load", () => {
document.getElementById("ced").contentDocument.designMode = "on";
});
</script>
</body>
</html>