~ubuntu-branches/ubuntu/gutsy/firefox/gutsy

« back to all changes in this revision

Viewing changes to browser/components/sessionstore/src/nsSessionStore.js

  • Committer: Bazaar Package Importer
  • Author(s): Ian Jackson
  • Date: 2006-10-10 18:49:32 UTC
  • mfrom: (1.1.8 upstream)
  • Revision ID: james.westby@ubuntu.com-20061010184932-da75izt7y0e59afq
Tags: 1.99+2.0rc2+dfsg-0ubuntu1
* New upstream version 2.0rc2.
* Fix/workaround for epiphany GtkSocket lifetype crash:
  apply patch id=241087 from Mozilla Bugzilla #241535 to fix LP #63814.
* Change application name to `Firefox', as requested by mdz.
  Files changed:
    - browser/locales/en-US/chrome/branding/brand.dtd
    - browser/locales/en-US/chrome/branding/brand.properties;
  New values:
    - brandShortName and brandFullName: `Bon Echo' => `Firefox'
    - vendorShortName: `Mozilla' => `Ubuntu'
* Make preferences dialogue fit again (bah!).

Show diffs side-by-side

added added

removed removed

Lines of Context:
42
42
 * Overview
43
43
 * This service keeps track of a user's session, storing the various bits
44
44
 * required to return the browser to it's current state. The relevant data is 
45
 
 * stored in memory, and is periodically saved to disk in an ini file in the 
46
 
 * profile directory. The service is started at first window load, in delayedStartup, and will restore
47
 
 * the session from the data received from the nsSessionStartup service.
 
45
 * stored in memory, and is periodically saved to disk in a file in the 
 
46
 * profile directory. The service is started at first window load, in
 
47
 * delayedStartup, and will restore the session from the data received from
 
48
 * the nsSessionStartup service.
48
49
 */
49
50
 
50
51
/* :::::::: Constants and Helpers ::::::::::::::: */
166
167
   * Initialize the component
167
168
   */
168
169
  init: function sss_init(aWindow) {
169
 
    if (!aWindow || this._loadState == STATE_RUNNING)
 
170
    if (!aWindow || this._loadState == STATE_RUNNING) {
 
171
      // make sure that all browser windows which try to initialize
 
172
      // SessionStore are really tracked by it
 
173
      if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
 
174
        this.onLoad(aWindow);
170
175
      return;
 
176
    }
171
177
 
172
178
    this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
173
179
                       getService(Ci.nsIPrefService).getBranch("browser.");
224
230
        }, this);
225
231
        delete this._initialState.windows[0].hidden;
226
232
      }
227
 
      catch (ex) { debug("The session file is invalid: " + ex); } // invalid .INI file - nothing can be restored
 
233
      catch (ex) { debug("The session file is invalid: " + ex); }
228
234
    }
229
235
    
230
236
    // if last session crashed, backup the session
231
237
    if (this._lastSessionCrashed) {
232
238
      try {
233
 
        this._writeFile(this._getSessionFile(true), iniString);
 
239
        this._writeFile(this._sessionFileBackup, iniString);
234
240
      }
235
241
      catch (ex) { } // nothing else we can do here
236
242
    }
385
391
   *        Window reference
386
392
   */
387
393
  onLoad: function sss_onLoad(aWindow) {
 
394
    // return if window has already been initialized
 
395
    if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
 
396
      return;
 
397
 
388
398
    var _this = this;
389
399
 
390
 
    // ignore non-browser windows
391
 
    if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser")
 
400
    // ignore non-browser windows and windows opened while shutting down
 
401
    if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
 
402
      this._loadState == STATE_QUITTING)
392
403
      return;
393
404
 
394
405
    // assign it a unique identifier (timestamp)
441
452
   *        Window reference
442
453
   */
443
454
  onClose: function sss_onClose(aWindow) {
444
 
    if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
 
455
    // ignore windows not tracked by SessionStore
 
456
    if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
445
457
      return;
446
458
    }
447
459
    
603
615
 
604
616
/* ........ nsISessionStore API .............. */
605
617
 
606
 
// This API is terribly rough. TODO:
607
 
// * figure out what functionality we really need
608
 
// * figure out how to name the functions
609
 
 
610
 
// * get/set the (opaque) state of all windows
611
 
 
612
 
  get opaqueState() {
613
 
    return this._getCurrentState();
 
618
  getBrowserState: function sss_getBrowserState() {
 
619
    return this._toJSONString(this._getCurrentState());
614
620
  },
615
621
 
616
 
  set opaqueState(aState) {
 
622
  setBrowserState: function sss_setBrowserState(aState) {
617
623
    var window = this._getMostRecentBrowserWindow();
618
624
    if (!window) {
619
 
      this._openWindowWithState(aState);
 
625
      this._openWindowWithState("(" + aState + ")");
620
626
      return;
621
627
    }
622
 
    
 
628
 
623
629
    // close all other browser windows
624
630
    this._forEachBrowserWindow(function(aWindow) {
625
631
      if (aWindow != window) {
628
634
    });
629
635
 
630
636
    // restore to the given state
631
 
    this.restoreWindow(window, aState, true);
632
 
  },
633
 
 
634
 
// * get/set the opaque state of a closed window (for persisting it over sessions)
635
 
 
636
 
  getOpaqueWindowState: function sss_getOpaqueWindowState(aWindow) {
637
 
    return this._getWindowState(aWindow);
638
 
  },
639
 
 
640
 
  setOpaqueWindowState: function sss_setOpaqueWindowState(aWindow, aState) {
641
 
    this.restoreWindow(aWindow, aState, true);
642
 
  },
643
 
 
644
 
// * allow to reopen closed tabs
645
 
// * get/set the opaque state of a closed tab (for persisting it over sessions)
 
637
    this.restoreWindow(window, "(" + aState + ")", true);
 
638
  },
 
639
 
 
640
  getWindowState: function sss_getWindowState(aWindow) {
 
641
    return this._toJSONString(this._getWindowState(aWindow));
 
642
  },
 
643
 
 
644
  setWindowState: function sss_setWindowState(aWindow, aState, aOverwrite) {
 
645
    this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite);
 
646
  },
646
647
 
647
648
  getClosedTabCount: function sss_getClosedTabCount(aWindow) {
648
649
    return this._windows[aWindow.__SSi]._closedTabs.length;
654
655
    return aIx in tabs ? tabs[aIx].title : null;
655
656
  },
656
657
 
657
 
  /**
658
 
   * Returns nsIDictionary
659
 
   */
660
658
  getClosedTabData: function sss_getClosedTabDataAt(aWindow) {
661
 
    try {
662
 
      var wrappedData = Cc["@mozilla.org/dictionary;1"].createInstance(Ci.nsIDictionary);
663
 
      var closedTabData = this._windows[aWindow.__SSi]._closedTabs;
664
 
      if (closedTabData.length == 0)
665
 
        return wrappedData;
666
 
 
667
 
      // wrap it
668
 
      for (var i = 0; i < closedTabData.length; i++)
669
 
        wrappedData.setValue(i, { wrappedJSObject: closedTabData[i] });
670
 
 
671
 
      return wrappedData;
672
 
    } catch(ex) { debug("getClosedTabData: " + ex); }
673
 
  },
674
 
 
675
 
  setClosedTabData: function sss_setClosedTabDataAt(aWindow, aData) {
676
 
    this._windows[aWindow.__SSi]._closedTabs = aData;
 
659
    return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
677
660
  },
678
661
 
679
662
  undoCloseTab: function sss_undoCloseTab(aWindow, aIndex) {
703
686
    }
704
687
  },
705
688
 
706
 
// * get/set persistent properties on individual tabs or windows (for extensions)
707
 
 
708
689
  getWindowValue: function sss_getWindowValue(aWindow, aKey) {
709
690
    if (aWindow.__SSi) {
710
691
      var data = this._windows[aWindow.__SSi].extData || {};
967
948
    Array.forEach(aWindow.getBrowser().browsers, function(aBrowser, aIx) {
968
949
      try {
969
950
        var tabData = this._windows[aWindow.__SSi].tabs[aIx];
 
951
        if (tabData.entries.length == 0)
 
952
          return; // ignore incompletely initialized tabs
970
953
        
971
954
        var text = [];
972
955
        if (aBrowser.parentNode.__SS_text && this._checkPrivacyLevel(aBrowser.currentURI.schemeIs("https"))) {
980
963
          }
981
964
        }
982
965
        if (aBrowser.currentURI.spec == "about:config") {
983
 
          text = ["#textbox=" + encodeURI(aBrowser.contentDocument.getElementById("textbox").value)];
 
966
          text = ["#textbox=" + encodeURI(aBrowser.contentDocument.getElementById("textbox").wrappedJSObject.value)];
984
967
        }
985
968
        tabData.text = text.join(" ");
986
969
        
1165
1148
   * @param aWindow
1166
1149
   *        Window reference
1167
1150
   * @param aState
1168
 
   *        Ini formatted string, or object
 
1151
   *        JS object or its eval'able source
1169
1152
   * @param aOverwriteTabs
1170
1153
   *        bool overwrite existing tabs w/ new ones
1171
1154
   */
1172
1155
  restoreWindow: function sss_restoreWindow(aWindow, aState, aOverwriteTabs) {
 
1156
    // initialize window if necessary
 
1157
    if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
 
1158
      this.onLoad(aWindow);
 
1159
 
1173
1160
    try {
1174
1161
      var root = typeof aState == "string" ? this._safeEval(aState) : aState;
1175
1162
      if (!root.windows[0]) {
1176
1163
        return; // nothing to restore
1177
1164
      }
1178
1165
    }
1179
 
    catch (ex) { // invalid .INI file - don't restore anything 
 
1166
    catch (ex) { // invalid state object - don't restore anything 
1180
1167
      debug(ex);
1181
1168
      return;
1182
1169
    }
1183
1170
    
 
1171
    var winData;
1184
1172
    // open new windows for all further window entries of a multi-window session
 
1173
    // (unless they don't contain any tab data)
1185
1174
    for (var w = 1; w < root.windows.length; w++) {
1186
 
      this._openWindowWithState({ windows: [root.windows[w]], opener: aWindow });
 
1175
      winData = root.windows[w];
 
1176
      if (winData && winData.tabs && winData.tabs[0]) {
 
1177
        this._openWindowWithState({ windows: [winData], opener: aWindow });
 
1178
      }
1187
1179
    }
1188
1180
    
1189
 
    var winData = root.windows[0];
 
1181
    winData = root.windows[0];
1190
1182
    if (!winData.tabs) {
1191
1183
      winData.tabs = [];
1192
1184
    }
1222
1214
        this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
1223
1215
      }
1224
1216
    }
 
1217
    if (winData._closedTabs && (root._firstTabs || aOverwriteTabs)) {
 
1218
      //XXXzeniko remove the slice call as soon as _closedTabs instanceof Array
 
1219
      this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs.slice();
 
1220
    }
1225
1221
    
1226
1222
    this.restoreHistoryPrecursor(aWindow, winData.tabs, (aOverwriteTabs ?
1227
1223
      (parseInt(winData.selected) || 1) : 0), 0, 0);
1431
1427
    }
1432
1428
    
1433
1429
    var textArray = this.__SS_restore_text ? this.__SS_restore_text.split(" ") : [];
1434
 
    function restoreTextData(aContent) {
 
1430
    function restoreTextData(aContent, aPrefix) {
1435
1431
      textArray.forEach(function(aEntry) {
1436
1432
        if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry) && (!RegExp.$1 || RegExp.$1 == aPrefix)) {
1437
1433
          var document = aContent.document;
1464
1460
    
1465
1461
    var content = XPCNativeWrapper(aEvent.originalTarget).defaultView;
1466
1462
    if (this.currentURI.spec == "about:config") {
1467
 
      //XXXzeniko why ever this doesn't work with an XPCNativeWrapper...
1468
 
      content = aEvent.originalTarget.defaultView;
 
1463
      // unwrap the document for about:config because otherwise the properties
 
1464
      // of the XBL bindings - as the textbox - aren't accessible (see bug 350718)
 
1465
      content = content.wrappedJSObject;
1469
1466
    }
1470
1467
    restoreTextDataAndScrolling(content, this.__SS_restore_data, "");
1471
1468
    
1594
1591
      return;
1595
1592
    }
1596
1593
    
1597
 
    var downloads = rdfContainer.GetElements();
1598
 
    
1599
1594
    // iterate through all downloads currently available in the RDF store
1600
1595
    // and restart the ones which were in progress before the crash
 
1596
    var downloads = rdfContainer.GetElements();
1601
1597
    while (downloads.hasMoreElements()) {
1602
1598
      var download = downloads.getNext().QueryInterface(Ci.nsIRDFResource);
1603
 
      
1604
 
      var node = datasource.GetTarget(rdfService.GetResource(download.Value), rdfService.GetResource("http://home.netscape.com/NC-rdf#DownloadState"), true);
 
1599
 
 
1600
      // restart only if the download's in progress
 
1601
      var node = datasource.GetTarget(download, rdfService.GetResource("http://home.netscape.com/NC-rdf#DownloadState"), true);
1605
1602
      if (node) {
1606
1603
        node.QueryInterface(Ci.nsIRDFInt);
1607
1604
      }
1608
1605
      if (!node || node.Value != Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING) {
1609
1606
        continue;
1610
1607
      }
1611
 
      
1612
 
      node = datasource.GetTarget(rdfService.GetResource(download.Value), rdfService.GetResource("http://home.netscape.com/NC-rdf#URL"), true);
1613
 
      node.QueryInterface(Ci.nsIRDFResource);
1614
 
      
1615
 
      var url = node.Value;
1616
 
      
1617
 
      node = datasource.GetTarget(rdfService.GetResource(download.Value), rdfService.GetResource("http://home.netscape.com/NC-rdf#File"), true);
1618
 
      node.QueryInterface(Ci.nsIRDFResource);
1619
 
      
 
1608
 
 
1609
      // URL being downloaded
 
1610
      node = datasource.GetTarget(download, rdfService.GetResource("http://home.netscape.com/NC-rdf#URL"), true);
 
1611
      var url = node.QueryInterface(Ci.nsIRDFResource).Value;
 
1612
      
 
1613
      // location where download's being saved
 
1614
      node = datasource.GetTarget(download, rdfService.GetResource("http://home.netscape.com/NC-rdf#File"), true);
 
1615
 
 
1616
      // nsIRDFResource.Value is a string that's a URI; the downloads.rdf from
 
1617
      // which this was created will have a string in one of the following two
 
1618
      // forms, depending on platform:
 
1619
      //
 
1620
      //    /home/lumpy/dogtreat.txt
 
1621
      //    C:\lumpy\dogtreat.txt
 
1622
      //
 
1623
      // During RDF loading, the string *appears* to be converted to a URL if
 
1624
      // necessary.  Strings in the first form are not URLs and are converted to
 
1625
      // file: URLs; strings in the latter form seem to be treated as if they
 
1626
      // already are URLs and thus are not modified.  Consequently, on platforms
 
1627
      // where paths aren't URLs, we need to extract the path from the file:
 
1628
      // URL.
 
1629
      //
 
1630
      // See also bug 335725, bug 239948, and bug 349971.
 
1631
      var savedTo = node.QueryInterface(Ci.nsIRDFResource).Value;
 
1632
      try {
 
1633
        var savedToURI = Cc["@mozilla.org/network/io-service;1"].
 
1634
                         getService(Ci.nsIIOService).
 
1635
                         newURI(savedTo, null, null);
 
1636
        if (savedToURI.schemeIs("file"))
 
1637
          savedTo = savedToURI.path;
 
1638
      }
 
1639
      catch (e) { /* not a URI, assume it was a string of form #1 */ }
 
1640
 
1620
1641
      var linkChecker = Cc["@mozilla.org/network/urichecker;1"].
1621
1642
                        createInstance(Ci.nsIURIChecker);
1622
 
      
1623
1643
      linkChecker.init(ioService.newURI(url, null, null));
1624
1644
      linkChecker.loadFlags = Ci.nsIRequest.LOAD_BACKGROUND;
1625
 
      linkChecker.asyncCheck(new AutoDownloader(url, node.Value, aWindow), null);
 
1645
      linkChecker.asyncCheck(new AutoDownloader(url, savedTo, aWindow), null);
1626
1646
    }
1627
1647
  },
1628
1648
 
1666
1686
    this._dirty = aUpdateAll;
1667
1687
    var oState = this._getCurrentState();
1668
1688
    oState.session = { state: ((this._loadState == STATE_RUNNING) ? STATE_RUNNING_STR : STATE_STOPPED_STR) };
1669
 
    this._writeFile(this._getSessionFile(), oState.toSource());
 
1689
    this._writeFile(this._sessionFile, oState.toSource());
1670
1690
    this._lastSaveTime = Date.now();
1671
1691
  },
1672
1692
 
1674
1694
   * delete session datafile and backup
1675
1695
   */
1676
1696
  _clearDisk: function sss_clearDisk() {
1677
 
    var file = this._getSessionFile();
1678
 
 
1679
 
    if (file.exists()) {
1680
 
      try {
1681
 
        file.remove(false);
1682
 
      }
1683
 
      catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
1684
 
    }
1685
 
 
1686
 
    if (!this._lastSessionCrashed)
1687
 
      return;
1688
 
 
1689
 
    try {
1690
 
      this._getSessionFile(true).remove(false);
1691
 
    }
1692
 
    catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
1693
 
  },
1694
 
 
1695
 
  /**
1696
 
   * get session datafile (or its backup)
1697
 
   * @returns nsIFile 
1698
 
   */
1699
 
  _getSessionFile: function sss_getSessionFile(aBackup) {
1700
 
    return aBackup ? this._sessionFileBackup : this._sessionFile;
 
1697
    if (this._sessionFile.exists()) {
 
1698
      try {
 
1699
        this._sessionFile.remove(false);
 
1700
      }
 
1701
      catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
 
1702
    }
 
1703
    if (this._sessionFileBackup.exists()) {
 
1704
      try {
 
1705
        this._sessionFileBackup.remove(false);
 
1706
      }
 
1707
      catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
 
1708
    }
1701
1709
  },
1702
1710
 
1703
1711
/* ........ Auxiliary Functions .............. */
1862
1870
   * safe eval'ing
1863
1871
   */
1864
1872
  _safeEval: function sss_safeEval(aStr) {
1865
 
    var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
1866
 
    var uri = ioService.newFileURI(this._sessionFile, null, null);
1867
 
    var s = new Components.utils.Sandbox(uri.spec);
 
1873
    var s = new Components.utils.Sandbox("about:blank");
1868
1874
    return Components.utils.evalInSandbox(aStr, s);
1869
1875
  },
1870
1876
 
 
1877
  /**
 
1878
   * Converts a JavaScript object into a JSON string
 
1879
   * (see http://www.json.org/ for the full grammar).
 
1880
   *
 
1881
   * The inverse operation consists of eval("(" + JSON_string + ")");
 
1882
   * and should be provably safe.
 
1883
   *
 
1884
   * @param aJSObject is the object to be converted
 
1885
   * @return the object's JSON representation
 
1886
   */
 
1887
  _toJSONString: function sss_toJSONString(aJSObject) {
 
1888
    // these characters have a special escape notation
 
1889
    const charMap = { "\b": "\\b", "\t": "\\t", "\n": "\\n", "\f": "\\f",
 
1890
                      "\r": "\\r", '"': '\\"', "\\": "\\\\" };
 
1891
    // we use a single string builder for efficiency reasons
 
1892
    var parts = [];
 
1893
    
 
1894
    // this recursive function walks through all objects and appends their
 
1895
    // JSON representation to the string builder
 
1896
    function jsonIfy(aObj) {
 
1897
      if (typeof aObj == "boolean") {
 
1898
        parts.push(aObj ? "true" : "false");
 
1899
      }
 
1900
      else if (typeof aObj == "number" && isFinite(aObj)) {
 
1901
        // there is no representation for infinite numbers or for NaN!
 
1902
        parts.push(aObj.toString());
 
1903
      }
 
1904
      else if (typeof aObj == "string") {
 
1905
        aObj = aObj.replace(/[\\"\x00-\x1F\u0080-\uFFFF]/g, function($0) {
 
1906
          // use the special escape notation if one exists, otherwise
 
1907
          // produce a general unicode escape sequence
 
1908
          return charMap[$0] ||
 
1909
            "\\u" + ("0000" + $0.charCodeAt(0).toString(16)).slice(-4);
 
1910
        });
 
1911
        parts.push('"' + aObj + '"')
 
1912
      }
 
1913
      else if (aObj == null) {
 
1914
        parts.push("null");
 
1915
      }
 
1916
      else if (aObj instanceof Array) {
 
1917
        parts.push("[");
 
1918
        for (var i = 0; i < aObj.length; i++) {
 
1919
          jsonIfy(aObj[i]);
 
1920
          parts.push(",");
 
1921
        }
 
1922
        if (parts[parts.length - 1] == ",")
 
1923
          parts.pop(); // drop the trailing colon
 
1924
        parts.push("]");
 
1925
      }
 
1926
      else if (typeof aObj == "object") {
 
1927
        parts.push("{");
 
1928
        for (var key in aObj) {
 
1929
          jsonIfy(key.toString());
 
1930
          parts.push(":");
 
1931
          jsonIfy(aObj[key]);
 
1932
          parts.push(",");
 
1933
        }
 
1934
        if (parts[parts.length - 1] == ",")
 
1935
          parts.pop(); // drop the trailing colon
 
1936
        parts.push("}");
 
1937
      }
 
1938
      else {
 
1939
        throw new Error("No JSON representation for this object!");
 
1940
      }
 
1941
    }
 
1942
    jsonIfy(aJSObject);
 
1943
    
 
1944
    var newJSONString = parts.join(" ");
 
1945
    // sanity check - so that API consumers can just eval this string
 
1946
    if (/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
 
1947
      newJSONString.replace(/"(\\.|[^"\\])*"/g, "")
 
1948
    ))
 
1949
      throw new Error("JSON conversion failed unexpectedly!");
 
1950
    
 
1951
    return newJSONString;
 
1952
  },
 
1953
 
1871
1954
/* ........ Storage API .............. */
1872
1955
 
1873
1956
  /**