Bug 742051 - Remove the backwards compatibility for the old formdata format r=zpao
authorAndres Hernandez [:andreshm] <andres@appcoast.com>
Mon, 14 May 2012 16:11:43 -0600
changeset 98524 aa466d084fe373be2b8160dfa8325f60ccbb7cb1
parent 98523 068b9453c49828d296f006c3186784a05d59363b
child 98525 985058b05905b2e3e3adbf1668f363b8ac3231e4
push id1116
push userlsblakk@mozilla.com
push dateMon, 16 Jul 2012 19:38:18 +0000
treeherdermozilla-beta@95f959a8b4dc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszpao
bugs742051
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 742051 - Remove the backwards compatibility for the old formdata format r=zpao
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser_456342.js
browser/components/sessionstore/test/browser_464199.js
browser/components/sessionstore/test/browser_467409-backslashplosion.js
browser/components/sessionstore/test/browser_581593.js
browser/components/sessionstore/test/browser_590563.js
browser/components/sessionstore/test/browser_739805.js
browser/components/sessionstore/test/browser_formdata_format.js
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -360,17 +360,20 @@ SessionStoreService.prototype = {
           if (lastSessionCrashed) {
             this._recentCrashes = (this._initialState.session &&
                                    this._initialState.session.recentCrashes || 0) + 1;
             
             if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
               // replace the crashed session with a restore-page-only session
               let pageData = {
                 url: "about:sessionrestore",
-                formdata: { "#sessionData": this._initialState }
+                formdata: {
+                  id: { "sessionData": this._initialState },
+                  xpath: {}
+                }
               };
               this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
             }
           }
 
           // Load the session start time from the previous state
           this._sessionStartTime = this._initialState.session &&
                                    this._initialState.session.startTime ||
@@ -2204,17 +2207,20 @@ SessionStoreService.prototype = {
     
     this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
                                           aTabData.entries[tabIndex],
                                           !aBrowser.__SS_formDataSaved, aFullData,
                                           !!aTabData.pinned);
     aBrowser.__SS_formDataSaved = true;
     if (aBrowser.currentURI.spec == "about:config")
       aTabData.entries[tabIndex].formdata = {
-        "#textbox": aBrowser.contentDocument.getElementById("textbox").value
+        id: {
+          "textbox": aBrowser.contentDocument.getElementById("textbox").value
+        },
+        xpath: {}
       };
   },
 
   /**
    * go through all subframes and store all form data, the current
    * scroll positions and innerHTML content of WYSIWYG editors
    * @param aWindow
    *        Window reference
@@ -2247,27 +2253,19 @@ SessionStoreService.prototype = {
 
         // 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.id["sessionData"] = JSON.parse(formData.id["sessionData"]);
         }
 
-        // For backwards compatibility in SessionStore, the structure of the
-        // stored object is modified slightly.
         if (Object.keys(formData.id).length ||
             Object.keys(formData.xpath).length) {
-          // The object returned from getFormData() is not shared, so we reuse
-          // the xpath sub-object for our storage.
-          aData.formdata = formData.xpath;
-
-          for each (let [k, v] in Iterator(formData.id)) {
-            aData.formdata["#" + k] = v;
-          }
+          aData.formdata = formData;
         } else if (aData.formdata) {
           delete aData.formdata;
         }
       }
       
       // designMode is undefined e.g. for XUL documents (as about:config)
       if ((aContent.document.designMode || "") == "on") {
         if (aData.innerHTML === undefined && !aFullData) {
@@ -3414,37 +3412,42 @@ SessionStoreService.prototype = {
       !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, "");
 
     let selectedPageStyle = aBrowser.__SS_restore_pageStyle;
     function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
       if (aData.formdata && hasExpectedURL(aContent.document, aData.url)) {
         let formdata = aData.formdata;
 
         // handle backwards compatibility
+        // this is a migration from pre-firefox 15. cf. bug 742051
         if (!("xpath" in formdata || "id" in formdata)) {
-          formdata = {xpath: {}, id: {}};
+          formdata = { xpath: {}, id: {} };
 
           for each (let [key, value] in Iterator(aData.formdata)) {
             if (key.charAt(0) == "#") {
               formdata.id[key.slice(1)] = value;
             } else {
               formdata.xpath[key] = value;
             }
           }
         }
 
         // for about:sessionrestore we saved the field as JSON to avoid
         // nested instances causing humongous sessionstore.js files.
         // cf. bug 467409
         if (aData.url == "about:sessionrestore" &&
+            "sessionData" in formdata.id &&
             typeof formdata.id["sessionData"] == "object") {
           formdata.id["sessionData"] =
             JSON.stringify(formdata.id["sessionData"]);
         }
 
+        // update the formdata
+        aData.formdata = formdata;
+        // merge the formdata
         DocumentUtils.mergeFormData(aContent.document, formdata);
       }
 
       if (aData.innerHTML) {
         aWindow.setTimeout(function() {
           if (aContent.document.designMode == "on" &&
               hasExpectedURL(aContent.document, aData.url)) {
             aContent.document.body.innerHTML = aData.innerHTML;
--- a/browser/components/sessionstore/test/browser_456342.js
+++ b/browser/components/sessionstore/test/browser_456342.js
@@ -56,23 +56,29 @@ function test() {
       formEls[i].value = expectedValue;
 
     gBrowser.removeTab(tab);
     
     let undoItems = JSON.parse(ss.getClosedTabData(window));
     let savedFormData = undoItems[0].state.entries[0].formdata;
     
     let countGood = 0, countBad = 0;
-    for each (let value in savedFormData) {
+    for each (let value in savedFormData.id) {
       if (value == expectedValue)
         countGood++;
       else
         countBad++;
     }
-    
+    for each (let value in savedFormData.xpath) {
+      if (value == expectedValue)
+        countGood++;
+      else
+        countBad++;
+    }
+
     is(countGood, 4, "Saved text for non-standard input fields");
     is(countBad,  0, "Didn't save text for ignored field types");
     
     // clean up
     if (gPrefService.prefHasUserValue("browser.sessionstore.privacy_level"))
       gPrefService.clearUserPref("browser.sessionstore.privacy_level");
     finish();
   }, true);
--- a/browser/components/sessionstore/test/browser_464199.js
+++ b/browser/components/sessionstore/test/browser_464199.js
@@ -55,17 +55,17 @@ function test() {
                              { url: "http://www.example.org:8080/frame2" }
                            ] }] }, title: REMEMBER },
     { state: { entries: [{ url: "http://www.example.org/frameset",
                            children: [
                              { url: "http://www.example.org/frame" },
                              { url: "http://www.example.net/frame" }
                            ] }] }, title: FORGET },
     { state: { entries: [{ url: "http://www.example.org/form",
-                           formdata: { "#url": "http://www.example.net/" }
+                           formdata: { id: { "url": "http://www.example.net/" } }
                          }] }, title: REMEMBER },
     { state: { entries: [{ url: "http://www.example.org/form" }],
                extData: { "setTabValue": "http://example.net:80" } }, title: REMEMBER }
   ] }] };
   let remember_count = 5;
 
   function countByTitle(aClosedTabList, aTitle)
     aClosedTabList.filter(function(aData) aData.title == aTitle).length;
--- a/browser/components/sessionstore/test/browser_467409-backslashplosion.js
+++ b/browser/components/sessionstore/test/browser_467409-backslashplosion.js
@@ -19,64 +19,64 @@
 function test() {
   waitForExplicitFinish();
   ignoreAllUncaughtExceptions();
 
   let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank" }] }]}]};
   let crashState = { windows: [{ tabs: [{ entries: [{ url: "about:mozilla" }] }]}]};
 
   let pagedata = { url: "about:sessionrestore",
-                   formdata: { "#sessionData": crashState } };
+                   formdata: { id: {"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 } };
+                      formdata: { id: { "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) } };
+                      formdata: { id: { "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");
+    ok(formdata.id["sessionData"], testName + ": we have form data for about:sessionrestore");
 
-    let sessionData_raw = JSON.stringify(formdata["#sessionData"]);
+    let sessionData_raw = JSON.stringify(formdata.id["sessionData"]);
     ok(!/\\/.test(sessionData_raw), testName + ": #sessionData contains no backslashes");
     info(sessionData_raw);
 
     let gotError = false;
     try {
-      JSON.parse(formdata["#sessionData"]);
+      JSON.parse(formdata.id["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
--- a/browser/components/sessionstore/test/browser_581593.js
+++ b/browser/components/sessionstore/test/browser_581593.js
@@ -40,17 +40,17 @@ let stateBackup = ss.getBrowserState();
 function test() {
   /** Test for bug 581593 **/
   waitForExplicitFinish();
   ignoreAllUncaughtExceptions();
 
   let oldState = { windows: [{ tabs: [{ entries: [{ url: "example.com" }] }] }]};
   let pageData = {
     url: "about:sessionrestore",
-    formdata: { "#sessionData": "(" + JSON.stringify(oldState) + ")" }
+    formdata: { id: { "sessionData": "(" + JSON.stringify(oldState) + ")" } }
   };
   let state = { windows: [{ tabs: [{ entries: [pageData] }] }] };
 
   // The form data will be restored before SSTabRestored, so we want to listen
   // for that on the currently selected tab (it will be reused)
   gBrowser.selectedTab.addEventListener("SSTabRestored", onSSTabRestored, true);
 
   ss.setBrowserState(JSON.stringify(state));
--- a/browser/components/sessionstore/test/browser_590563.js
+++ b/browser/components/sessionstore/test/browser_590563.js
@@ -7,17 +7,17 @@ function test() {
       tabs: [
         { entries: [{ url: "about:mozilla" }], hidden: true },
         { entries: [{ url: "about:blank" }], hidden: false }
       ]
     }]
   };
   let pageData = {
     url: "about:sessionrestore",
-    formdata: { "#sessionData": oldState }
+    formdata: { id: { "sessionData": oldState } } 
   };
   let state = { windows: [{ tabs: [{ entries: [pageData] }] }] };
 
   waitForExplicitFinish();
 
   newWindowWithState(state, function (win) {
     registerCleanupFunction(function () win.close());
 
--- a/browser/components/sessionstore/test/browser_739805.js
+++ b/browser/components/sessionstore/test/browser_739805.js
@@ -1,15 +1,15 @@
 /* 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: {"#foo": "bar"}}]
+  entries: [{url: "data:text/html,<input%20id='foo'>", formdata: { id: { "foo": "bar" } } }]
 };
 
 function test() {
   waitForExplicitFinish();
   Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   registerCleanupFunction(function () {
     if (gBrowser.tabs.length > 1)
@@ -23,17 +23,17 @@ function test() {
   whenBrowserLoaded(browser, function () {
     isnot(gBrowser.selectedTab, tab, "newly created tab is not selected");
 
     ss.setTabState(tab, JSON.stringify(tabState));
     is(browser.__SS_restoreState, TAB_STATE_NEEDS_RESTORE, "tab needs restoring");
 
     let state = JSON.parse(ss.getTabState(tab));
     let formdata = state.entries[0].formdata;
-    is(formdata && formdata["#foo"], "bar", "tab state's formdata is valid");
+    is(formdata && formdata.id["foo"], "bar", "tab state's formdata is valid");
 
     whenTabRestored(tab, function () {
       let input = browser.contentDocument.getElementById("foo");
       is(input.value, "bar", "formdata has been restored correctly");
       finish();
     });
 
     // Restore the tab by selecting it.
--- a/browser/components/sessionstore/test/browser_formdata_format.js
+++ b/browser/components/sessionstore/test/browser_formdata_format.js
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   /** Tests formdata format **/
   waitForExplicitFinish();
 
-  let testTabCount = 0;
   let formData = [
     { },
     // old format
     { "#input1" : "value0" },
     { "#input1" : "value1", "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value2" },
     { "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value3" },
     // new format
     { id: { "input1" : "value4" } },
@@ -47,50 +46,70 @@ function test() {
     [ "", "value16" ],
     [ "value18", "" ],
     [ "value20", "value21" ],
     [ "", "value23" ],
     [ "value26", "" ],
     [ "value29", "value30" ],
     [ "", "value33" ]
   ];
+  let testTabCount = 0;
   let callback = function() {
     testTabCount--;
     if (testTabCount == 0) {
       finish();
     }
   };
 
   for (let i = 0; i < formData.length; i++) {
     testTabCount++;
     testTabRestoreData(formData[i], expectedValues[i], callback);
   }
 }
 
-function testTabRestoreData(aFormData, aExpectedValues, aCallback) {
+function testTabRestoreData(aFormData, aExpectedValue, aCallback) {
   let testURL =
     getRootDirectory(gTestPath) + "browser_formdata_format_sample.html";
   let tab = gBrowser.addTab(testURL);
   let tabState = { entries: [{ url: testURL, formdata: aFormData}] };
 
-  tab.linkedBrowser.addEventListener("load", function(aEvent) {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-    ss.setTabState(tab, JSON.stringify(tabState));
-
-    tab.addEventListener("SSTabRestored", function(aEvent) {
-      tab.removeEventListener("SSTabRestored", arguments.callee);
+  let browserLoadedCallback = function(aEvent) {
+    let tabStateCallback = function(aEvent) {
       let doc = tab.linkedBrowser.contentDocument;
-      let value1 = doc.getElementById("input1").value;
-      let value2 = doc.querySelector("input[name=input2]").value;
+      let input1 = doc.getElementById("input1");
+      let input2 = doc.querySelector("input[name=input2]");
+      let saveStateCallback = function(aEvent) {
+        let restoredTabState = JSON.parse(ss.getTabState(tab));
+        let restoredFormData = restoredTabState.entries[0].formdata;
 
-      // test id
-      is(value1, aExpectedValues[0],
-        "FormData by 'id' has been restored correctly");
-      // test xpath
-      is(value2, aExpectedValues[1],
-        "FormData by 'xpath' has been restored correctly");
+        if (restoredFormData) {
+          // test format
+          ok("id" in restoredFormData && "xpath" in restoredFormData,
+            "FormData format is valid: " + restoredFormData);
+          // validate that there are no old keys
+          is(Object.keys(restoredFormData).length, 2,
+            "FormData key length is valid");
+          // test id
+          is(input1.value, aExpectedValue[0],
+            "FormData by 'id' has been restored correctly");
+          // test xpath
+          is(input2.value, aExpectedValue[1],
+            "FormData by 'xpath' has been restored correctly");
+        }
 
-      // clean up
-      gBrowser.removeTab(tab);
-      aCallback();
-    });
-  }, true);
+        // clean up
+        gBrowser.removeTab(tab);
+        aCallback();
+      };
+
+      waitForSaveState(saveStateCallback);
+
+      // force a change event to recollect the formdata
+      let changeEvent = document.createEvent("Events");
+      changeEvent.initEvent("change", true, true);
+      input.dispatchEvent(changeEvent);
+    };
+
+    waitForTabState(tab, tabState, tabStateCallback);
+  };
+
+  whenBrowserLoaded(tab.linkedBrowser, browserLoadedCallback);
 }