SBSessionStore.js
Go to the documentation of this file.
1 // vim: set sw=2 :miv
2 /*
3  *=BEGIN SONGBIRD GPL
4  *
5  * This file is part of the Songbird web player.
6  *
7  * Copyright(c) 2005-2010 POTI, Inc.
8  * http://www.songbirdnest.com
9  *
10  * This file may be licensed under the terms of of the
11  * GNU General Public License Version 2 (the ``GPL'').
12  *
13  * Software distributed under the License is distributed
14  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
15  * express or implied. See the GPL for the specific language
16  * governing rights and limitations.
17  *
18  * You should have received a copy of the GPL along with this
19  * program. If not, go to http://www.gnu.org/licenses/gpl.html
20  * or write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  *
23  *=END SONGBIRD GPL
24  */
25 
36 const EXPORTED_SYMBOLS = ["SBSessionStore"];
37 
38 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
39 Components.utils.import("resource://app/jsmodules/sbLibraryUtils.jsm");
40 
41 const Cc = Components.classes;
42 const Ci = Components.interfaces;
43 
44 const Application = Cc["@mozilla.org/fuel/application;1"].getService();
45 const JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
46 
47 const PREF_TAB_STATE = "songbird.browser.tab_state";
48 const PREF_FIRSTRUN = "songbird.firstrun.tabs.restore";
49 const PREF_FIRSTRUN_URL = "songbird.url.firstrunpage";
50 const PLACEHOLDER_URL = "chrome://songbird/content/mediapages/firstrun.xul";
51 // this pref marks the current session as being firstrun. Used to cooperate
52 // with things like file scan.
53 const PREF_FIRSTRUN_SESSION = "songbird.firstrun.is_session";
54 
55 const PREF_UPDATE_VERSION = "nightingale.update.version";
56 const PREF_UPDATE_URL = "nightingale.update.url";
57 
58 function LOG(str) {
59  // var environment = Cc["@mozilla.org/process/environment;1"]
60  // .createInstance(Ci.nsIEnvironment);
61  // var level = ("," + environment.get("NSPR_LOG_MODULES") + ",")
62  // .match(/,(?:sbSessionStore|all):(\d+),/);
63  // if (!level || level[1] < 3) {
64  // // don't log
65  // return;
66  // }
67  // var file = (new Error).stack.split("\n").reverse()[1];
68  // dump(file + "" + str + "\n");
69 
70  // dump("\n\n\n");
71  // dump(str);
72  // dump("\n\n\n");
73  // var consoleService = Cc['@mozilla.org/consoleservice;1']
74  // .getService(Ci.nsIConsoleService);
75  // consoleService.logStringMessage(str);
76 }
77 
78 __defineGetter__("_tabState", function() {
79  var state = Application.prefs.getValue(PREF_TAB_STATE, null);
80  if (state === null)
81  return null;
82  try {
83  return JSON.decode(state);
84  } catch(e) {
85  Components.utils.reportError("Error restoring tab state: invalid JSON\n" +
86  state);
87  return null;
88  }
89 });
90 
91 __defineSetter__("_tabState", function(aValue) {
92  Application.prefs.setValue(PREF_TAB_STATE, JSON.encode(aValue));
93 });
94 
96  saveTabState: function saveTabState(aTabBrowser)
97  {
98  var tab = aTabBrowser.mTabs[0];
99  var urls = [];
100  while (tab) {
101  try {
102  // For media pages, store the list GUIDs
103  // so that the page can be restored correctly
104  if (tab.mediaPage) {
105  if (tab.mediaPage.mediaListView) {
106  var view = tab.mediaPage.mediaListView;
107  var mediaList = view.mediaList;
108  var data = {
109  listGUID: mediaList.guid,
110  libraryGUID: mediaList.library.guid,
111  pageURL: tab.linkedBrowser.currentURI.spec,
112  isOnlyView: tab.mediaPage.isOnlyView
113  };
114  if (view instanceof Ci.sbIFilterableMediaListView) {
115  // Clear the cascade filter set so that the filter constraints
116  // only include the original constraints on the view, not those
117  // imposed by the CFS. We do this on a copy of the view to avoid
118  // changing the state of the original.
119  var cloneView = view.clone();
120  cloneView.cascadeFilterSet.clearAll();
121 
122  data.constraints = [];
123  for (var group in ArrayConverter.JSEnum(cloneView.filterConstraint.groups)) {
124  if (!(group instanceof Ci.sbILibraryConstraintGroup)) {
125  // this shouldn't happen... but let's be nice and not die
126  continue;
127  }
128  let propArray = [];
129  for (let prop in ArrayConverter.JSEnum(group.properties)) {
130  propArray.push([prop,
131  ArrayConverter.JSArray(group.getValues(prop))]);
132  }
133  data.constraints.push(propArray);
134  }
135  if (!data.constraints.length) {
136  // no constraints; this shouldn't ever happen either (we should
137  // have the standard not hidden one), but let's not die
138  delete data.constraints;
139  }
140  }
141  urls.push(data);
142  }
143  // For all other pages, just keep the URI
144  } else {
145  // Check to see if the url has a prefix that matches a service pane
146  // node, and store the node url instead of the actual loaded page.
147  // This allows service pane nodes to direct session restore to a
148  // useful page instead of something we don't want to restore like
149  // an error page.
150  let url = tab.linkedBrowser.currentURI.spec;
151  let sps = Cc["@songbirdnest.com/servicepane/service;1"]
152  .getService(Ci.sbIServicePaneService);
153  let node =
154  sps.getNodeForURL(url, Ci.sbIServicePaneService.URL_MATCH_PREFIX);
155 
156  // Set the node url if we found a node. Otherwise just use url of the
157  // page that loaded.
158  if (node) {
159  url = node.url;
160  }
161  urls.push(url);
162  }
163  } catch (e) {
164  Components.utils.reportError(e);
165  }
166  tab = tab.nextSibling;
167  }
168 
169  _tabState = {
170  selectedTabIndex: aTabBrowser.tabContainer.selectedIndex,
171  urlList: urls
172  };
173  },
174 
175  restoreTabState: function restoreTabState(aTabBrowser)
176  {
177  var tabObject = _tabState;
178  var tabs = [];
179  var selectedIndex = 0, selectedTab;
180 
181  LOG("restoring tab state");
182 
183  if (Application.prefs.has(PREF_FIRSTRUN_SESSION))
184  Application.prefs.get(PREF_FIRSTRUN_SESSION).reset();
185 
186  if (tabObject && "urlList" in tabObject) {
187  // v2 of the tab state object (with selectedTabIndex)
188  tabs = tabObject.urlList;
189  selectedIndex = tabObject.selectedTabIndex || 0;
190  } else {
191  // v1 of the tab state object (no selected tab index, only urls)
192  tabs = tabObject;
193  }
194 
195  var isFirstTab = true;
196  if ( !tabs || !tabs.length ) {
197  if (!Application.prefs.getValue(PREF_FIRSTRUN, false)) {
198  LOG("no saved tabs, first run - using defaults");
199  // First run, load the dummy page in the first tab, and the welcome
200  // page in the second. The dummy page will get replaced in mainWinInit.js
201  // when media scan is done / skipped.
202  aTabBrowser.loadURI(PLACEHOLDER_URL, null, null, null, '_media');
203 
204  var loadMLInBackground =
205  Application.prefs.getValue("songbird.firstrun.load_ml_in_background",
206  false);
207  var firstrunURL = Application.prefs.getValue(PREF_FIRSTRUN_URL, null);
208  LOG(PREF_FIRSTRUN_URL + ": " + firstrunURL);
209  if (firstrunURL) {
210  // If the pref to load the medialist in the background is true, then
211  // we want to load the firstrun page in the foreground
212  selectedTab = aTabBrowser.loadOneTab(firstrunURL, null, null, null,
213  !loadMLInBackground);
214  }
215  Application.prefs.setValue(PREF_FIRSTRUN, true);
216  Application.prefs.setValue(PREF_FIRSTRUN_SESSION, true);
217  Application.prefs.setValue(PREF_UPDATE_VERSION, Application.version);
218  }
219  } else {
220  LOG("saved tabs found: " + uneval(tabs));
221 
222  // check whether ngale has been upgraded, if the pref is not present we upgraded from <1.12.1
223  if(!Application.prefs.has(PREF_UPDATE_VERSION) || Application.prefs.get(PREF_UPDATE_VERSION).value != Application.version) {
224  LOG("first launch after an update");
225 
226  var updateURL = Application.prefs.getValue(PREF_UPDATE_URL, null);
227  if(updateURL) {
228  LOG("opening update tab");
229  aTabBrowser.loadOneTab(updateURL, null, null, null, false);
230 
231  Application.prefs.setValue(PREF_UPDATE_VERSION, Application.version);
232  }
233  }
234 
235  // check if this is an invalid chrome url
236  var chromeReg = Cc['@mozilla.org/chrome/chrome-registry;1']
237  .getService(Ci.nsIChromeRegistry);
238  var ios = Cc["@mozilla.org/network/io-service;1"]
239  .getService(Ci.nsIIOService);
240  function isInvalidChromeURL(url) {
241  var uri;
242  // parse the URL
243  try {
244  uri = ios.newURI(url, null, null);
245  } catch (e) {
246  // can't parse the url, that will be handled elsewhere
247  return false;
248  }
249  // if it's not chrome then we don't care
250  if (uri.scheme != 'chrome') {
251  return false;
252  }
253  // resolve the chrome url with the registry
254  try {
255  uri = chromeReg.convertChromeURL(uri);
256  } catch (e) {
257  // an exception here means that this chrome URL isn't valid
258  return true;
259  }
260  // if the scheme *is* chrome, then something's wrong
261  // (recursive chrome mapping)
262  if (uri.scheme == 'chrome') {
263  return true;
264  }
265  // ok - things look fine
266  return false;
267  }
268 
269  // Otherwise, just restore whatever was there, previously.
270  var tab, location;
271  for (var i = 0; i < tabs.length; i++) {
272  tab = tabs[i];
273  var newTab = null;
274 
275  var url = (tab.pageURL ? tab.pageURL : tab);
276  if (isInvalidChromeURL(url)) {
277  // we don't want to restore invalid chrome URLs
278  continue;
279  }
280  if (url == "about:blank") {
281  // skip restoring blank tabs
282  continue;
283  }
284 
285  // If the tab had a media page, restore it by reloading
286  // the media list
287  if (tab.listGUID) {
288 
289  // HACK! Add a random param to the querystring in order to avoid
290  // the XUL cache. This is a work around for the following bugs:
291  //
292  // Bug 7896 - Media pages do not initialize when loaded from tab restore
293  // BMO 420815 - XUL Cache interferes with onload when loading multiple
294  // instances of the same XUL file
295  if (url.indexOf("&bypassXULCache") == -1) {
296  url += "&bypassXULCache="+ Math.random();
297  }
298 
299  if (isFirstTab) {
300  // restore the first tab into the media tab, if available
301  location = "_media";
302  } else {
303  location = "_blank";
304  }
305 
306  try {
307  var list = LibraryUtils.getMediaListByGUID(tab.libraryGUID,
308  tab.listGUID);
309  }
310  catch (e if e.result == Components.results.NS_ERROR_NOT_AVAILABLE) {
311  // not available, just go to the library.
312  var list = LibraryUtils.mainLibrary;
313  }
314 
315  let view = LibraryUtils.createStandardMediaListView(list);
316  if ("constraints" in tab) {
317  view.filterConstraint = LibraryUtils.createConstraint(tab.constraints);
318  }
319  newTab = aTabBrowser.loadMediaList(list,
320  null,
321  location,
322  view,
323  url,
324  tab.isOnlyView);
325 
326  // Otherwise just reload the URL
327  } else {
328  LOG("loading plain url: " + url);
329  if (isFirstTab) {
330  if (aTabBrowser.mediaTab) {
331  // let the placeholder URL load in the media tab (again).
332  if ((PLACEHOLDER_URL == url) ||
333  LibraryUtils.isMediaTabURL(url, aTabBrowser))
334  {
335  LOG("this is the placeholder tab or media url");
336  // this is the first tab, and is a media-ish url
337  location = "_media";
338  } else {
339  LOG(<>not a media-esque tab ({firstrunURL} vs {url}: {(firstrunURL == url)})</>);
340  // this is the first tab, but this is unsuitable for the media tab
341  location = "_top";
342  // Load the default page
343  newTab = this.loadDefault(aTabBrowser);
344  }
345  } else {
346  // no media tab, but this is the first tab
347  LOG("no media tab found, just use first tab");
348  location = "_top";
349  }
350  } else {
351  LOG("not first tab, always _blank");
352  location = "_blank";
353  }
354 
355  // don't load device pages for a device that isn't mounted
356  var uri = ios.newURI(url, null, null);
357  if (uri.scheme == 'chrome' && (uri.path.indexOf("?device-id") >= 0)) {
358  var deviceId = uri.path.match(/\?device-id=\{([0-9a-z\-]+)\}/);
359  if (deviceId) {
360  deviceId = deviceId[1];
361 
362  var deviceMgr = Cc["@songbirdnest.com/Songbird/DeviceManager;2"]
363  .getService(Ci.sbIDeviceManager2);
364  try {
365  deviceMgr.getDevice(Components.ID(deviceId));
366  // It's a valid device, so go ahead and open up the chrome page
367  newTab = aTabBrowser.loadURI(url, null, null, null, location);
368  } catch (e) {
369  // This is an invalid device -- load the default
370  newTab = this.loadDefault(aTabBrowser);
371  }
372  }
373  } else {
374  // It's not a device page, so go ahead and load it like normal
375  newTab = aTabBrowser.loadURI(url, null, null, null, location);
376  }
377  }
378 
379  // Load the first url into the current tab and subsequent
380  // urls into new tabs
381  isFirstTab = false;
382 
383  if (i == selectedIndex) {
384  // note that this might not match the actual tab index, if there are
385  // any saved tabs that are now at an invalid chrome URL. That's fine,
386  // because we just want to selected the same content, not the index.
387  selectedTab = newTab;
388  }
389  }
390  }
391 
392  // Let's just go to the main library when:
393  // - there was only one tab and that tab is an invalid chrome url
394  // (isInvalidChromeURL says true)
395  // - or no saved tabs, not first run, and tab state pref is missing/corrupt.
396  if (isFirstTab) {
397  // if skip_load_default_page has been set then something else is going
398  // to load the media page so that we don't need to, such as the first
399  // run import.
400  var skipDefault =
401  Application.prefs.getValue("songbird.firstrun.skip_load_default_page",
402  false);
403  if (!skipDefault) {
404  Application.prefs.setValue("songbird.firstrun.skip_load_default_page",
405  false);
406  this.loadDefault(aTabBrowser);
407  }
408  }
409 
410  // Select the selected tab from the previous session (or the first one if
411  // we don't know which one that is)
412  // use delayedSelectTab because sbTabBrowser::loadURI uses a timeout to
413  // force the new tab to be selected :(
414  if (!selectedTab && aTabBrowser.mediaTab) {
415  selectedTab = aTabBrowser.mediaTab;
416  }
417  aTabBrowser.delayedSelectTab(selectedTab);
418 
419  this.tabStateRestored = true;
420 
421  // tell the tab browser we switched tabs so it can update state correctly
422  var selectEvent = document.createEvent("Events");
423  selectEvent.initEvent("select", true, true);
424  aTabBrowser.tabStrip.dispatchEvent(selectEvent);
425  },
426 
427  loadDefault: function(aTabBrowser) {
428  var mainLib = LibraryUtils.mainLibrary;
429  var view = LibraryUtils.createStandardMediaListView(mainLib);
430  var constraints = [[["http://songbirdnest.com/data/1.0#isList",["0"]]],
431  [["http://songbirdnest.com/data/1.0#hidden",["0"]]],
432  [["http://songbirdnest.com/data/1.0#contentType",["audio"]]]]
433  view.filterConstraint = LibraryUtils.createConstraint(constraints);
434  return aTabBrowser.loadMediaList(mainLib, null, "_media", view, null, false);
435  },
436 
437  tabStateRestored: false
438 };
__defineGetter__("_tabState", function(){var state=Application.prefs.getValue(PREF_TAB_STATE, null);if(state===null) return null;try{return JSON.decode(state);}catch(e){Components.utils.reportError("Error restoring tab state: invalid JSON\n"+state);return null;}})
const Cc
const PREF_FIRSTRUN_URL
onPageChanged aValue
Definition: FeedWriter.js:1395
const JSON
const PREF_FIRSTRUN
var SBSessionStore
var tab
const PLACEHOLDER_URL
const PREF_UPDATE_URL
const Application
var tabs
const PREF_UPDATE_VERSION
return null
Definition: FeedWriter.js:1143
let node
var uri
Definition: FeedWriter.js:1135
function url(spec)
const EXPORTED_SYMBOLS
Javascript wrappers for common library tasks.
var ios
Definition: head_feeds.js:5
__defineSetter__("_tabState", function(aValue){Application.prefs.setValue(PREF_TAB_STATE, JSON.encode(aValue));})
observe data
Definition: FeedWriter.js:1329
_getSelectedPageStyle s i
const PREF_FIRSTRUN_SESSION
const PREF_TAB_STATE
function LOG(str)
var group
const Ci