browser/components/sessionstore/content/aboutSessionRestore.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/sessionstore/content/aboutSessionRestore.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,310 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +const Cc = Components.classes;
     1.9 +const Ci = Components.interfaces;
    1.10 +const Cu = Components.utils;
    1.11 +
    1.12 +var gStateObject;
    1.13 +var gTreeData;
    1.14 +
    1.15 +// Page initialization
    1.16 +
    1.17 +window.onload = function() {
    1.18 +  // the crashed session state is kept inside a textbox so that SessionStore picks it up
    1.19 +  // (for when the tab is closed or the session crashes right again)
    1.20 +  var sessionData = document.getElementById("sessionData");
    1.21 +  if (!sessionData.value) {
    1.22 +    document.getElementById("errorTryAgain").disabled = true;
    1.23 +    return;
    1.24 +  }
    1.25 +
    1.26 +  // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0)
    1.27 +  if (sessionData.value.charAt(0) == '(')
    1.28 +    sessionData.value = sessionData.value.slice(1, -1);
    1.29 +  try {
    1.30 +    gStateObject = JSON.parse(sessionData.value);
    1.31 +  }
    1.32 +  catch (exJSON) {
    1.33 +    var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'});
    1.34 +    gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s);
    1.35 +    // If we couldn't parse the string with JSON.parse originally, make sure
    1.36 +    // that the value in the textbox will be parsable.
    1.37 +    sessionData.value = JSON.stringify(gStateObject);
    1.38 +  }
    1.39 +
    1.40 +  // make sure the data is tracked to be restored in case of a subsequent crash
    1.41 +  var event = document.createEvent("UIEvents");
    1.42 +  event.initUIEvent("input", true, true, window, 0);
    1.43 +  sessionData.dispatchEvent(event);
    1.44 +
    1.45 +  initTreeView();
    1.46 +
    1.47 +  document.getElementById("errorTryAgain").focus();
    1.48 +};
    1.49 +
    1.50 +function initTreeView() {
    1.51 +  var tabList = document.getElementById("tabList");
    1.52 +  var winLabel = tabList.getAttribute("_window_label");
    1.53 +
    1.54 +  gTreeData = [];
    1.55 +  gStateObject.windows.forEach(function(aWinData, aIx) {
    1.56 +    var winState = {
    1.57 +      label: winLabel.replace("%S", (aIx + 1)),
    1.58 +      open: true,
    1.59 +      checked: true,
    1.60 +      ix: aIx
    1.61 +    };
    1.62 +    winState.tabs = aWinData.tabs.map(function(aTabData) {
    1.63 +      var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" };
    1.64 +      var iconURL = aTabData.attributes && aTabData.attributes.image || null;
    1.65 +      // don't initiate a connection just to fetch a favicon (see bug 462863)
    1.66 +      if (/^https?:/.test(iconURL))
    1.67 +        iconURL = "moz-anno:favicon:" + iconURL;
    1.68 +      return {
    1.69 +        label: entry.title || entry.url,
    1.70 +        checked: true,
    1.71 +        src: iconURL,
    1.72 +        parent: winState
    1.73 +      };
    1.74 +    });
    1.75 +    gTreeData.push(winState);
    1.76 +    for each (var tab in winState.tabs)
    1.77 +      gTreeData.push(tab);
    1.78 +  }, this);
    1.79 +
    1.80 +  tabList.view = treeView;
    1.81 +  tabList.view.selection.select(0);
    1.82 +}
    1.83 +
    1.84 +// User actions
    1.85 +
    1.86 +function restoreSession() {
    1.87 +  document.getElementById("errorTryAgain").disabled = true;
    1.88 +
    1.89 +  // remove all unselected tabs from the state before restoring it
    1.90 +  var ix = gStateObject.windows.length - 1;
    1.91 +  for (var t = gTreeData.length - 1; t >= 0; t--) {
    1.92 +    if (treeView.isContainer(t)) {
    1.93 +      if (gTreeData[t].checked === 0)
    1.94 +        // this window will be restored partially
    1.95 +        gStateObject.windows[ix].tabs =
    1.96 +          gStateObject.windows[ix].tabs.filter(function(aTabData, aIx)
    1.97 +                                                 gTreeData[t].tabs[aIx].checked);
    1.98 +      else if (!gTreeData[t].checked)
    1.99 +        // this window won't be restored at all
   1.100 +        gStateObject.windows.splice(ix, 1);
   1.101 +      ix--;
   1.102 +    }
   1.103 +  }
   1.104 +  var stateString = JSON.stringify(gStateObject);
   1.105 +
   1.106 +  var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
   1.107 +  var top = getBrowserWindow();
   1.108 +
   1.109 +  // if there's only this page open, reuse the window for restoring the session
   1.110 +  if (top.gBrowser.tabs.length == 1) {
   1.111 +    ss.setWindowState(top, stateString, true);
   1.112 +    return;
   1.113 +  }
   1.114 +
   1.115 +  // restore the session into a new window and close the current tab
   1.116 +  var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all");
   1.117 +  newWindow.addEventListener("load", function() {
   1.118 +    newWindow.removeEventListener("load", arguments.callee, true);
   1.119 +    ss.setWindowState(newWindow, stateString, true);
   1.120 +
   1.121 +    var tabbrowser = top.gBrowser;
   1.122 +    var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
   1.123 +    tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
   1.124 +  }, true);
   1.125 +}
   1.126 +
   1.127 +function startNewSession() {
   1.128 +  var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   1.129 +  if (prefBranch.getIntPref("browser.startup.page") == 0)
   1.130 +    getBrowserWindow().gBrowser.loadURI("about:blank");
   1.131 +  else
   1.132 +    getBrowserWindow().BrowserHome();
   1.133 +}
   1.134 +
   1.135 +function onListClick(aEvent) {
   1.136 +  // don't react to right-clicks
   1.137 +  if (aEvent.button == 2)
   1.138 +    return;
   1.139 +
   1.140 +  var row = {}, col = {};
   1.141 +  treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY, row, col, {});
   1.142 +  if (col.value) {
   1.143 +    // Restore this specific tab in the same window for middle/double/accel clicking
   1.144 +    // on a tab's title.
   1.145 +#ifdef XP_MACOSX
   1.146 +    let accelKey = aEvent.metaKey;
   1.147 +#else
   1.148 +    let accelKey = aEvent.ctrlKey;
   1.149 +#endif
   1.150 +    if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) &&
   1.151 +        col.value.id == "title" &&
   1.152 +        !treeView.isContainer(row.value)) {
   1.153 +      restoreSingleTab(row.value, aEvent.shiftKey);
   1.154 +      aEvent.stopPropagation();
   1.155 +    }
   1.156 +    else if (col.value.id == "restore")
   1.157 +      toggleRowChecked(row.value);
   1.158 +  }
   1.159 +}
   1.160 +
   1.161 +function onListKeyDown(aEvent) {
   1.162 +  switch (aEvent.keyCode)
   1.163 +  {
   1.164 +  case KeyEvent.DOM_VK_SPACE:
   1.165 +    toggleRowChecked(document.getElementById("tabList").currentIndex);
   1.166 +    break;
   1.167 +  case KeyEvent.DOM_VK_RETURN:
   1.168 +    var ix = document.getElementById("tabList").currentIndex;
   1.169 +    if (aEvent.ctrlKey && !treeView.isContainer(ix))
   1.170 +      restoreSingleTab(ix, aEvent.shiftKey);
   1.171 +    break;
   1.172 +  }
   1.173 +}
   1.174 +
   1.175 +// Helper functions
   1.176 +
   1.177 +function getBrowserWindow() {
   1.178 +  return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
   1.179 +               .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
   1.180 +               .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
   1.181 +}
   1.182 +
   1.183 +function toggleRowChecked(aIx) {
   1.184 +  var item = gTreeData[aIx];
   1.185 +  item.checked = !item.checked;
   1.186 +  treeView.treeBox.invalidateRow(aIx);
   1.187 +
   1.188 +  function isChecked(aItem) aItem.checked;
   1.189 +
   1.190 +  if (treeView.isContainer(aIx)) {
   1.191 +    // (un)check all tabs of this window as well
   1.192 +    for each (var tab in item.tabs) {
   1.193 +      tab.checked = item.checked;
   1.194 +      treeView.treeBox.invalidateRow(gTreeData.indexOf(tab));
   1.195 +    }
   1.196 +  }
   1.197 +  else {
   1.198 +    // update the window's checkmark as well (0 means "partially checked")
   1.199 +    item.parent.checked = item.parent.tabs.every(isChecked) ? true :
   1.200 +                          item.parent.tabs.some(isChecked) ? 0 : false;
   1.201 +    treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
   1.202 +  }
   1.203 +
   1.204 +  document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
   1.205 +}
   1.206 +
   1.207 +function restoreSingleTab(aIx, aShifted) {
   1.208 +  var tabbrowser = getBrowserWindow().gBrowser;
   1.209 +  var newTab = tabbrowser.addTab();
   1.210 +  var item = gTreeData[aIx];
   1.211 +
   1.212 +  var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
   1.213 +  var tabState = gStateObject.windows[item.parent.ix]
   1.214 +                             .tabs[aIx - gTreeData.indexOf(item.parent) - 1];
   1.215 +  // ensure tab would be visible on the tabstrip.
   1.216 +  tabState.hidden = false;
   1.217 +  ss.setTabState(newTab, JSON.stringify(tabState));
   1.218 +
   1.219 +  // respect the preference as to whether to select the tab (the Shift key inverses)
   1.220 +  var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   1.221 +  if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)
   1.222 +    tabbrowser.selectedTab = newTab;
   1.223 +}
   1.224 +
   1.225 +// Tree controller
   1.226 +
   1.227 +var treeView = {
   1.228 +  treeBox: null,
   1.229 +  selection: null,
   1.230 +
   1.231 +  get rowCount()                     { return gTreeData.length; },
   1.232 +  setTree: function(treeBox)         { this.treeBox = treeBox; },
   1.233 +  getCellText: function(idx, column) { return gTreeData[idx].label; },
   1.234 +  isContainer: function(idx)         { return "open" in gTreeData[idx]; },
   1.235 +  getCellValue: function(idx, column){ return gTreeData[idx].checked; },
   1.236 +  isContainerOpen: function(idx)     { return gTreeData[idx].open; },
   1.237 +  isContainerEmpty: function(idx)    { return false; },
   1.238 +  isSeparator: function(idx)         { return false; },
   1.239 +  isSorted: function()               { return false; },
   1.240 +  isEditable: function(idx, column)  { return false; },
   1.241 +  canDrop: function(idx, orientation, dt) { return false; },
   1.242 +  getLevel: function(idx)            { return this.isContainer(idx) ? 0 : 1; },
   1.243 +
   1.244 +  getParentIndex: function(idx) {
   1.245 +    if (!this.isContainer(idx))
   1.246 +      for (var t = idx - 1; t >= 0 ; t--)
   1.247 +        if (this.isContainer(t))
   1.248 +          return t;
   1.249 +    return -1;
   1.250 +  },
   1.251 +
   1.252 +  hasNextSibling: function(idx, after) {
   1.253 +    var thisLevel = this.getLevel(idx);
   1.254 +    for (var t = after + 1; t < gTreeData.length; t++)
   1.255 +      if (this.getLevel(t) <= thisLevel)
   1.256 +        return this.getLevel(t) == thisLevel;
   1.257 +    return false;
   1.258 +  },
   1.259 +
   1.260 +  toggleOpenState: function(idx) {
   1.261 +    if (!this.isContainer(idx))
   1.262 +      return;
   1.263 +    var item = gTreeData[idx];
   1.264 +    if (item.open) {
   1.265 +      // remove this window's tab rows from the view
   1.266 +      var thisLevel = this.getLevel(idx);
   1.267 +      for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++);
   1.268 +      var deletecount = t - idx - 1;
   1.269 +      gTreeData.splice(idx + 1, deletecount);
   1.270 +      this.treeBox.rowCountChanged(idx + 1, -deletecount);
   1.271 +    }
   1.272 +    else {
   1.273 +      // add this window's tab rows to the view
   1.274 +      var toinsert = gTreeData[idx].tabs;
   1.275 +      for (var i = 0; i < toinsert.length; i++)
   1.276 +        gTreeData.splice(idx + i + 1, 0, toinsert[i]);
   1.277 +      this.treeBox.rowCountChanged(idx + 1, toinsert.length);
   1.278 +    }
   1.279 +    item.open = !item.open;
   1.280 +    this.treeBox.invalidateRow(idx);
   1.281 +  },
   1.282 +
   1.283 +  getCellProperties: function(idx, column) {
   1.284 +    if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0)
   1.285 +      return "partial";
   1.286 +    if (column.id == "title")
   1.287 +      return this.getImageSrc(idx, column) ? "icon" : "noicon";
   1.288 +
   1.289 +    return "";
   1.290 +  },
   1.291 +
   1.292 +  getRowProperties: function(idx) {
   1.293 +    var winState = gTreeData[idx].parent || gTreeData[idx];
   1.294 +    if (winState.ix % 2 != 0)
   1.295 +      return "alternate";
   1.296 +
   1.297 +    return "";
   1.298 +  },
   1.299 +
   1.300 +  getImageSrc: function(idx, column) {
   1.301 +    if (column.id == "title")
   1.302 +      return gTreeData[idx].src || null;
   1.303 +    return null;
   1.304 +  },
   1.305 +
   1.306 +  getProgressMode : function(idx, column) { },
   1.307 +  cycleHeader: function(column) { },
   1.308 +  cycleCell: function(idx, column) { },
   1.309 +  selectionChanged: function() { },
   1.310 +  performAction: function(action) { },
   1.311 +  performActionOnCell: function(action, index, column) { },
   1.312 +  getColumnProperties: function(column) { return ""; }
   1.313 +};

mercurial