diff --git a/browser/components/sessionstore/src/nsSessionStore.js b/browser/components/sessionstore/src/nsSessionStore.js index ca43ce973f2..6f3e4ede9ab 100644 --- a/browser/components/sessionstore/src/nsSessionStore.js +++ b/browser/components/sessionstore/src/nsSessionStore.js @@ -359,7 +359,7 @@ SessionStoreService.prototype = { // replace the crashed session with a restore-page-only session let pageData = { url: "about:sessionrestore", - formdata: { "#sessionData": JSON.stringify(this._initialState) } + formdata: { "#sessionData": this._initialState } }; this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] }; } @@ -2140,10 +2140,17 @@ SessionStoreService.prototype = { } var isHTTPS = this._getURIFromString((aContent.parent || aContent). document.location.href).schemeIs("https"); - if (aFullData || this._checkPrivacyLevel(isHTTPS, aIsPinned) || - aContent.top.document.location.href == "about:sessionrestore") { + let isAboutSR = aContent.top.document.location.href == "about:sessionrestore"; + if (aFullData || this._checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) { if (aFullData || aUpdateFormData) { let formData = this._collectFormDataForFrame(aContent.document); + + // We want to avoid saving data for about:sessionrestore as a string. + // Since it's stored in the form as stringified JSON, stringifying further + // causes an explosion of escape characters. cf. bug 467409 + if (formData && isAboutSR) + formData["#sessionData"] = JSON.parse(formData["#sessionData"]); + if (formData) aData.formdata = formData; else if (aData.formdata) @@ -3380,6 +3387,13 @@ SessionStoreService.prototype = { let eventType; let value = aData[key]; + + // for about:sessionrestore we saved the field as JSON to avoid nested + // instances causing humongous sessionstore.js files. cf. bug 467409 + if (aURL == "about:sessionrestore" && typeof value == "object") { + value = JSON.stringify(value); + } + if (typeof value == "string" && node.type != "file") { if (node.value == value) continue; // don't dispatch an input event for no change diff --git a/browser/components/sessionstore/test/browser/Makefile.in b/browser/components/sessionstore/test/browser/Makefile.in index 722796c2d06..3e3445b99c0 100644 --- a/browser/components/sessionstore/test/browser/Makefile.in +++ b/browser/components/sessionstore/test/browser/Makefile.in @@ -100,6 +100,7 @@ _BROWSER_TEST_FILES = \ browser_465223.js \ browser_466937.js \ browser_466937_sample.html \ + browser_467409-backslashplosion.js \ browser_477657.js \ browser_480148.js \ browser_480893.js \ diff --git a/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js b/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js new file mode 100644 index 00000000000..4a5c5622f25 --- /dev/null +++ b/browser/components/sessionstore/test/browser/browser_467409-backslashplosion.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test Summary: +// 1. Open about:sessionrestore via setBrowserState where formdata is a JS object, not a string +// 1a. Check that #sessionData on the page is readable after JSON.parse (skipped, checking formdata is sufficient) +// 1b. Check that there are no backslashes in the formdata +// 1c. Check that formdata (via getBrowserState) doesn't require JSON.parse +// +// 2. Use the current state (currently about:sessionrestore with data) and then open than in a new instance of about:sessionrestore +// 2a. Check that there are no backslashes in the formdata +// 2b. Check that formdata (via getBrowserState) doesn't require JSON.parse +// +// 3. [backwards compat] Use a stringified state as formdata when opening about:sessionrestore +// 3a. Make sure there are nodes in the tree on about:sessionrestore (skipped, checking formdata is sufficient) +// 3b. Check that there are no backslashes in the formdata +// 3c. Check that formdata (via getBrowserState) doesn't require JSON.parse + +function test() { + waitForExplicitFinish(); + + let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]}; + let crashState = { windows: [{ tabs: [{ entries: [{ url: "about:mozilla" }] }]}]}; + + let pagedata = { url: "about:sessionrestore", + formdata: { "#sessionData": crashState } }; + let state = { windows: [{ tabs: [{ entries: [pagedata] }] }] }; + + // test1 calls test2 calls test3 calls finish + test1(state); + + + function test1(aState) { + waitForBrowserState(aState, function() { + checkState("test1", test2); + }); + } + + function test2(aState) { + let pagedata2 = { url: "about:sessionrestore", + formdata: { "#sessionData": aState } }; + let state2 = { windows: [{ tabs: [{ entries: [pagedata2] }] }] }; + + waitForBrowserState(state2, function() { + checkState("test2", test3); + }); + } + + function test3(aState) { + let pagedata3 = { url: "about:sessionrestore", + formdata: { "#sessionData": JSON.stringify(crashState) } }; + let state3 = { windows: [{ tabs: [{ entries: [pagedata3] }] }] }; + waitForBrowserState(state3, function() { + // In theory we should do inspection of the treeview on about:sessionrestore, + // but we don't actually need to. If we fail tests in checkState then + // about:sessionrestore won't be able to turn the form value into a usable page. + checkState("test3", function() waitForBrowserState(blankState, finish)); + }); + } + + function checkState(testName, callback) { + let curState = JSON.parse(ss.getBrowserState()); + let formdata = curState.windows[0].tabs[0].entries[0].formdata; + + ok(formdata["#sessionData"], testName + ": we have form data for about:sessionrestore"); + + let sessionData_raw = JSON.stringify(formdata["#sessionData"]); + ok(!/\\/.test(sessionData_raw), testName + ": #sessionData contains no backslashes"); + info(sessionData_raw); + + let gotError = false; + try { + JSON.parse(formdata["#sessionData"]); + } + catch (e) { + info(testName + ": got error: " + e); + gotError = true; + } + ok(gotError, testName + ": attempting to JSON.parse form data threw error"); + + // Panorama sticks JSON into extData, which we stringify causing the + // naive backslash check to fail. extData doesn't matter in the grand + // scheme here, so we'll delete the extData so doesn't end up in future states. + delete curState.windows[0].extData; + delete curState.windows[0].tabs[0].extData; + callback(curState); + } + +} + diff --git a/browser/components/sessionstore/test/browser/browser_590563.js b/browser/components/sessionstore/test/browser/browser_590563.js index 1fefc4c63d6..cf1bf0a35f9 100644 --- a/browser/components/sessionstore/test/browser/browser_590563.js +++ b/browser/components/sessionstore/test/browser/browser_590563.js @@ -12,7 +12,7 @@ function test() { }; let pageData = { url: "about:sessionrestore", - formdata: { "#sessionData": "(" + JSON.stringify(oldState) + ")" } + formdata: { "#sessionData": oldState } }; let state = { windows: [{ tabs: [{ entries: [pageData] }] }] };