test_feathersManager.js
Go to the documentation of this file.
1 /*
2 //
3 // BEGIN SONGBIRD GPL
4 //
5 // This file is part of the Songbird web player.
6 //
7 // Copyright(c) 2005-2008 POTI, Inc.
8 // http://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 */
26 
31 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
32 
33 // Default layouts/skin, read in from the preferences.
34 // Used in |previousSkinName()| and |previousLayoutURL()|.
38 var gBundledSkins = []; // Empty, will get filled in testAddonMetadataReader
39 var gBundledLayouts = []; // Empty, will get filled in testAddonMetadataReader
40 
41 // These skins / Layouts are required to be registered
42 var gRequiredSkinInternalNames = [ "bluemonday" ];
43 var gRequiredLayoutURLs = [ "chrome://bluemonday/content/xul/mainplayer.xul",
44  "chrome://bluemonday/content/xul/miniplayer.xul" ];
45 
46 // Preference constants for default layout/skin/feather
47 // @see sbFeathersManager.js for information about each pref.
48 const PREF_DEFAULT_MAIN_LAYOUT = "songbird.feathers.default_main_layout";
49 const PREF_DEFAULT_SECONDARY_LAYOUT = "songbird.feathers.default_secondary_layout";
50 const PREF_DEFAULT_SKIN_INTERNALNAME = "songbird.feathers.default_skin_internalname";
51 
52 var feathersManager = Components.classes['@songbirdnest.com/songbird/feathersmanager;1']
53  .getService(Components.interfaces.sbIFeathersManager);
54 
55 // Needed to collect all feather-AddOns
56 Components.utils.import("resource://app/jsmodules/RDFHelper.jsm");
57 
58 
59 // List of skin descriptions used in the test cases
60 var skins = [];
61 
62 // List of layout descriptions used in the test cases
63 var layouts = [];
64 
65 
66 // Make dataremotes to tweak feathers settings
67 var createDataRemote = new Components.Constructor(
68  "@songbirdnest.com/Songbird/DataRemote;1",
69  Components.interfaces.sbIDataRemote, "init");
70 
71 var layoutDataRemote = createDataRemote("feathers.selectedLayout", null);
72 var skinDataRemote = createDataRemote("selectedSkin", "general.skins.");
73 var previousLayoutDataRemote = createDataRemote("feathers.previousLayout", null);
74 var previousSkinDataRemote = createDataRemote("feathers.previousSkin", null);
75 
77 
78 
79 
84 function saveDataRemotes() {
85  originalDataRemoteValues.push(layoutDataRemote.stringValue);
86  originalDataRemoteValues.push(skinDataRemote.stringValue);
87  originalDataRemoteValues.push(previousLayoutDataRemote.stringValue);
88  originalDataRemoteValues.push(previousSkinDataRemote.stringValue);
89 }
90 
96 function restoreDataRemotes() {
97  layoutDataRemote.stringValue = originalDataRemoteValues.shift();
98  skinDataRemote.stringValue = originalDataRemoteValues.shift();
99  previousLayoutDataRemote.stringValue = originalDataRemoteValues.shift();
100  previousSkinDataRemote.stringValue = originalDataRemoteValues.shift();
101 }
102 
103 
104 
109  // NOT a constructor because the feathers manager holds on to things for
110  // way too long; by carefully staying out of the scope, we reduce the set of
111  // objects being held via the XPCOM interface (in particular, the global)
112  var o = new Object();
113  o.wrappedJSObject = o;
114  o.QueryInterface = XPCOMUtils.generateQI([Ci.sbISkinDescription,
115  Ci.sbILayoutDescription]);
116  return o;
117 }
118 
119 
124 var feathersChangeListener = {
125 
126  // Count update notifications so that they can be confirmed
127  // with an assert.
128  updateCounter: 0,
129  onFeathersUpdate: function() {
130  this.updateCounter++;
131  },
132 
133  // Set skin and layout to be expected on next select,
134  // and reset when received.
135  expectLayout: null,
136  expectSkin: null,
137  onFeathersSelectRequest: function(layoutDesc, skinDesc) {
138  assertEqual(layoutDesc.wrappedJSObject, this.expectLayout);
139  assertEqual(skinDesc.wrappedJSObject, this.expectSkin);
140  },
141  onFeathersSelectComplete: function(layoutDesc, skinDesc) {
142  assertEqual(layoutDesc.wrappedJSObject, this.expectLayout);
143  assertEqual(skinDesc.wrappedJSObject, this.expectSkin);
144  this.expectLayout = null;
145  this.expectSkin = null;
146  testFinished();
147  },
148 
149  QueryInterface: function(iid) {
150  if (!iid.equals(Components.interfaces.sbIFeathersChangeListener))
151  throw Components.results.NS_ERROR_NO_INTERFACE;
152  return this;
153  }
154 }
155 
156 
160 function wrapEnumerator(enumerator, iface)
161 {
162  while (enumerator.hasMoreElements()) {
163  yield enumerator.getNext().QueryInterface(iface);
164  }
165 }
166 
167 
172 function assertEnumeratorEqualsArray(enumerator, list) {
173  list = list.concat([]); // Clone list before modifying
174  for (var item in enumerator) {
175  assertTrue(list.indexOf(item.wrappedJSObject) > -1);
176  list.splice(list.indexOf(item.wrappedJSObject), 1);
177  }
178  assertEqual(list.length, 0);
179 }
180 
181 
188 function assertEnumeratorMatchesFieldArray(enumerator, field, list) {
189  list = list.concat([]); // Clone list before modifying
190  for (var item in enumerator) {
191  assertTrue(list.indexOf(item[field]) > -1,
192  item[field] + " not found in list: '" + list + "'\n");
193  list.splice(list.indexOf(item[field]), 1);
194  }
195  assertEqual(list.length, 0);
196 }
197 
198 
199 
204 function testAddonMetadataReader()
205 {
206  // Get all AddOns
207  var addons = RDFHelper.help(
208  "rdf:addon-metadata",
209  "urn:songbird:addon:root",
210  RDFHelper.DEFAULT_RDF_NAMESPACES
211  );
212 
213  // Go through all AddOns and search for feathers and layouts,
214  // push them into gBundledSkins and gBundledLayouts
215  var deprecatedLayouts = []; // Will hold Gonzo layouts
216  for (var i = 0; i < addons.length; i++) {
217  var addon = addons[i];
218  if (addon.skin) {
219  log("FeatherAddon found: " + addon.id);
220  var skins = addon.skin;
221  for (var j = 0; j < skins.length; j++){
222  log(" > Feather: " + skins[j].name + " (" + skins[j].internalName + ")");
223  if (addon.id == "gonzo@songbirdnest.com"){
224  log(" ignored, it is the Gonzo feather");
225  continue; // Do not include Gonzo, it doesn't provide a feather
226  }
227  // Push the found feather into the Array of Feathers we will use
228  gBundledSkins.push(skins[j].internalName.toString());
229  }
230  var layouts = addon.layout;
231  for (var j = 0; j < layouts.length; j++){
232  log(" > Layout: " + layouts[j].name + " (" + layouts[j].url + ")");
233  // Push the found layout into the Array of layouts we will use
234  // Gonzo gets another array, which will get joined with the other
235  // in the end, so a valid feather with two connected layouts are
236  // first in list
237  if (addon.id == "gonzo@songbirdnest.com") {
238  deprecatedLayouts.push(layouts[j].url.toString());
239  } else {
240  gBundledLayouts.push(layouts[j].url.toString());
241  }
242  }
243  }
244  }
245  // Add Gonzo layouts to the array, so the length is correct but they can't
246  // be first
247  gBundledLayouts = gBundledLayouts.concat(deprecatedLayouts);
248 
249  // Verify all skins added properly
250  var skinNames = gBundledSkins;
251 
252  assertEqual(feathersManager.skinCount, skinNames.length);
253  var enumerator = wrapEnumerator(feathersManager.getSkinDescriptions(),
254  Components.interfaces.sbISkinDescription);
255  assertEnumeratorMatchesFieldArray(enumerator, "internalName", skinNames);
256 
257  // Verify all layouts added properly
258  var layoutURLs = gBundledLayouts;
259 
260  assertEqual(feathersManager.layoutCount, layoutURLs.length);
261  enumerator = wrapEnumerator(feathersManager.getLayoutDescriptions(),
262  Components.interfaces.sbILayoutDescription);
263  assertEnumeratorMatchesFieldArray(enumerator, "url", layoutURLs);
264 
265  // Verify mappings
266  enumerator = wrapEnumerator(feathersManager.getSkinsForLayout(layoutURLs[0]),
267  Components.interfaces.sbISkinDescription);
268  assertEnumeratorMatchesFieldArray(enumerator, "internalName", [gBundledSkins[0]]);
269  enumerator = wrapEnumerator(feathersManager.getSkinsForLayout(layoutURLs[1]),
270  Components.interfaces.sbISkinDescription);
271  assertEnumeratorMatchesFieldArray(enumerator, "internalName", [gBundledSkins[0]]);
272 
273  // Verify showChrome
274  // Chrome is only enabled on Mac OS X
275  var sysInfo = Components.classes["@mozilla.org/system-info;1"]
276  .getService(Components.interfaces.nsIPropertyBag2);
277  if (sysInfo.getProperty("name") == "Darwin")
278  assertEqual( feathersManager.isChromeEnabled(layoutURLs[0], skinNames[0]), true);
279  else
280  assertEqual( feathersManager.isChromeEnabled(layoutURLs[0], skinNames[1]), false);
281 
282  // Verify onTop
283  assertEqual( feathersManager.isOnTop(layoutURLs[0], skinNames[0]), false);
284  assertEqual( feathersManager.isOnTop(layoutURLs[1], skinNames[0]), false);
285 
286  // Verify we have the Coppery skin and the two Coppery layouts
287  var thing;
288  for each (thing in gRequiredSkinInternalNames)
289  assertTrue( feathersManager.getSkinDescription(thing) );
290  for each (thing in gRequiredLayoutURLs)
291  assertTrue( feathersManager.getLayoutDescription(thing) );
292 }
293 
294 
298 function testDefaultRevert() {
299  // Set feathers dataremotes to some junk value
300  previousLayoutDataRemote.stringValue = "somethingrandom";
301  layoutDataRemote.stringValue = "somethingelse";
302 
303  // Confirm that revert switches us to the primary fallback
304  feathersManager.switchFeathers(feathersManager.previousLayoutURL,
305  feathersManager.previousSkinName);
306 
307  testPending();
308 
309  assertEqual(skinDataRemote.stringValue, gDefaultSkinName);
310  assertEqual(layoutDataRemote.stringValue, gDefaultMainLayoutURL);
311 
312  // Now revert again, taking us to the secondary fallback
313  feathersManager.switchFeathers(feathersManager.previousLayoutURL,
314  feathersManager.previousSkinName);
315 
316  testPending();
317 
318  assertEqual(skinDataRemote.stringValue, gDefaultSkinName);
319  assertEqual(layoutDataRemote.stringValue, gDefaultSecondaryLayoutURL);
320 }
321 
322 
326 function submitFeathers()
327 {
328  // Make some skins
329  var skin = newFeathersDescription();
330  skin.name = "Blue Skin";
331  skin.internalName = "blue/1.0";
332  skins.push(skin);
333  feathersManager.registerSkin(skin);
334 
335  skin = newFeathersDescription();
336  skin.name = "Red Skin";
337  skin.internalName = "red/1.0";
338  skins.push(skin);
339  feathersManager.registerSkin(skin);
340 
341  skin = newFeathersDescription();
342  skin.name = "Orange Skin";
343  skin.internalName = "orange/1.0";
344  skins.push(skin);
345  feathersManager.registerSkin(skin);
346 
347  // Make some layouts
348  var layout = newFeathersDescription();
349  layout.name = "Big Layout";
350  layout.url = "chrome://biglayout/content/mainwin.xul";
351  layouts.push(layout);
352  feathersManager.registerLayout(layout);
353 
354  layout = newFeathersDescription();
355  layout.name = "Mini Layout";
356  layout.url = "chrome://minilayout/content/mini.xul";
357  layouts.push(layout);
358  feathersManager.registerLayout(layout);
359 
360  // Create some mappings
361  // Blue -> big/ontop, Red -> big, Orange -> mini with chrome
362  feathersManager.assertCompatibility(layouts[1].url, skins[0].internalName, false, true);
363  feathersManager.assertCompatibility(layouts[1].url, skins[1].internalName, false, false);
364  feathersManager.assertCompatibility(layouts[0].url, skins[2].internalName, true, false);
365 }
366 
367 
371 function teardown() {
372  skins.forEach(function(skin) {
373  feathersManager.unregisterSkin(skin);
374  });
375 
376  layouts.forEach(function(layout) {
377  feathersManager.unregisterLayout(layout);
378  });
379 
380  // Remove mappings
381  // Blue -> big, Red -> big, Orange -> mini
382  feathersManager.unassertCompatibility(layouts[1].url, skins[0].internalName);
383  feathersManager.unassertCompatibility(layouts[1].url, skins[1].internalName);
384 
385  // Remove change listener before final modification to confirm
386  // that it actually gets unhooked.
387  feathersManager.removeListener(feathersChangeListener);
388 
389  feathersManager.unassertCompatibility(layouts[0].url, skins[2].internalName);
390 }
391 
392 
393 
394 
395 
399 function runTest () {
400 
401  saveDataRemotes();
402 
403  // Read in the default layout and skin from prefs.
404  var AppPrefs = Cc["@mozilla.org/fuel/application;1"]
405  .getService(Ci.fuelIApplication).prefs;
406  gDefaultMainLayoutURL = AppPrefs.getValue(PREF_DEFAULT_MAIN_LAYOUT, "");
407  gDefaultSecondaryLayoutURL =
408  AppPrefs.getValue(PREF_DEFAULT_SECONDARY_LAYOUT, "");
409  gDefaultSkinName = AppPrefs.getValue(PREF_DEFAULT_SKIN_INTERNALNAME, "");
410 
411  layoutDataRemote.stringValue = "";
412  skinDataRemote.stringValue = "";
413  previousLayoutDataRemote.stringValue = "";
414  previousSkinDataRemote.stringValue = "";
415 
416  // Register for change callbacks
417  feathersManager.addListener(feathersChangeListener);
418 
420  // Test skins/layouts registered via extensions //
422 
423  testAddonMetadataReader();
424 
425  testDefaultRevert();
426 
427  // Now remove the extension descriptions in order to
428  // make testing the registration functions a bit easier
429  enumerator = wrapEnumerator(feathersManager.getSkinDescriptions(),
430  Components.interfaces.sbISkinDescription);
431  for (var item in enumerator) feathersManager.unregisterSkin(item);
432  enumerator = wrapEnumerator(feathersManager.getLayoutDescriptions(),
433  Components.interfaces.sbILayoutDescription);
434  for (var item in enumerator) feathersManager.unregisterLayout(item);
435 
436 
438  // Test registration functions //
440 
441  feathersChangeListener.updateCounter = 0;
442 
443  submitFeathers();
444 
445  // ------------------------
446  // Test change notification. We should have seen 8 update notifications
447  assertEqual(feathersChangeListener.updateCounter, 8);
448 
449  // ------------------------
450  // Test Getters / Make sure setup succeeded
451 
452  // Verify all skins added properly
453  assertEqual(feathersManager.skinCount, skins.length);
454  var enumerator = wrapEnumerator(feathersManager.getSkinDescriptions(),
455  Components.interfaces.sbISkinDescription);
456  assertEnumeratorEqualsArray(enumerator, skins);
457 
458  // Verify all layouts added properly
459  assertEqual(feathersManager.layoutCount, layouts.length);
460  enumerator = wrapEnumerator(feathersManager.getLayoutDescriptions(),
461  Components.interfaces.sbILayoutDescription);
462  assertEnumeratorEqualsArray(enumerator, layouts);
463 
464  // Test description getters
465  var desc = feathersManager.getSkinDescription(skins[1].internalName).wrappedJSObject;
466  assertEqual(desc, skins[1]);
467  desc = feathersManager.getLayoutDescription(layouts[1].url).wrappedJSObject;
468  assertEqual(desc, layouts[1]);
469 
470  // ------------------------
471  // Verify mappings
472  enumerator = wrapEnumerator(feathersManager.getSkinsForLayout(layouts[0].url),
473  Components.interfaces.sbISkinDescription);
474  assertEnumeratorEqualsArray(enumerator, [skins[2]]);
475  enumerator = wrapEnumerator(feathersManager.getSkinsForLayout(layouts[1].url),
476  Components.interfaces.sbISkinDescription);
477  assertEnumeratorEqualsArray(enumerator, [skins[0], skins[1]]);
478  enumerator = wrapEnumerator(feathersManager.getLayoutsForSkin(skins[0].internalName),
479  Components.interfaces.sbILayoutDescription);
480  assertEnumeratorEqualsArray(enumerator, [layouts[1]]);
481 
482  // ------------------------
483  // Verify showChrome
484  assertEqual( feathersManager.isChromeEnabled(layouts[1].url, skins[0].internalName), false);
485  assertEqual( feathersManager.isChromeEnabled(layouts[1].url, skins[1].internalName), false);
486  assertEqual( feathersManager.isChromeEnabled(layouts[0].url, skins[2].internalName), true);
487 
488  // ------------------------
489  // Verify onTop
490  assertEqual( feathersManager.isOnTop(layouts[1].url, skins[0].internalName), false);
491  assertEqual( feathersManager.isOnTop(layouts[1].url, skins[1].internalName), false);
492  assertEqual( feathersManager.isOnTop(layouts[0].url, skins[2].internalName), false);
493 
494 
495  // ------------------------
496  // Test feathers select and revert
497 
498  // Manually set the current feathers so that we can test revert
499  layoutDataRemote.stringValue = layouts[0].url;
500  skinDataRemote.stringValue = skins[2].internalName;
501 
502 
503  // First with an invalid pair
504  var failed = false;
505  try {
506  feathersManager.switchFeathers(layouts[0].url, skins[1].internalName);
507  } catch (e) {
508  failed = true;
509  }
510  assertEqual(failed, true);
511 
512 
513  // Then with a valid pair. Expect an onSelect callback.
514  feathersChangeListener.expectSkin = skins[0];
515  feathersChangeListener.expectLayout = layouts[1];
516  feathersManager.switchFeathers(layouts[1].url, skins[0].internalName);
517 
518  testPending();
519 
520  // Make sure onSelect callback occurred
521  assertEqual(feathersChangeListener.expectSkin, null);
522  assertEqual(feathersChangeListener.expectLayout, null);
523 
524 
525  // Now revert, expecting the values manually set above.
526  feathersChangeListener.expectSkin = skins[2];
527  feathersChangeListener.expectLayout = layouts[0];
528  feathersManager.switchFeathers(feathersManager.previousLayoutURL,
529  feathersManager.previousSkinName);
530 
531  testPending();
532 
533  // Make sure onSelect callback occurred
534  assertEqual(feathersChangeListener.expectSkin, null);
535  assertEqual(feathersChangeListener.expectLayout, null);
536 
537 
539  // Get rid of everything we added //
541 
542  // Reset update counter so we can test teardown
543  feathersChangeListener.updateCounter = 0;
544 
545  teardown();
546 
547  // ------------------------
548  // Test change notification. We should have seen 7 update notifications,
549  // as we unhooked before the last removeMapping call.
550  assertEqual(feathersChangeListener.updateCounter, 7);
551  feathersChangeListener.updateCounter = 0;
552 
553  // ------------------------
554  // Make sure teardown succeeded
555  assertEqual(feathersManager.skinCount, 0);
556  enumerator = feathersManager.getSkinDescriptions();
557  assertEqual(enumerator.hasMoreElements(), false);
558 
559  assertEqual(feathersManager.layoutCount, 0);
560  enumerator = feathersManager.getLayoutDescriptions();
561  assertEqual(enumerator.hasMoreElements(), false);
562 
563  // ------------------------
564  // Verify mappings are gone
565  enumerator = feathersManager.getSkinsForLayout(layouts[0].url);
566  assertEqual(enumerator.hasMoreElements(), false);
567  enumerator = feathersManager.getSkinsForLayout(layouts[1].url);
568  assertEqual(enumerator.hasMoreElements(), false);
569 
571 
572  return Components.results.NS_OK;
573 }
var layoutDataRemote
var skinDataRemote
function newFeathersDescription()
const PREF_DEFAULT_MAIN_LAYOUT
const Cc
function restoreDataRemotes()
function log(s)
function testFinished()
const PREF_DEFAULT_SECONDARY_LAYOUT
var originalDataRemoteValues
sbOSDControlService prototype QueryInterface
function assertTrue(aTest, aMessage)
function assertEqual(aExpected, aActual, aMessage)
function RDFHelper(aRdf, aDatasource, aResource, aNamespaces)
Definition: RDFHelper.jsm:61
var gDefaultSecondaryLayoutURL
var skins
var createDataRemote
var gDefaultSkinName
return null
Definition: FeedWriter.js:1143
var layouts
function saveDataRemotes()
function url(spec)
var previousLayoutDataRemote
var gRequiredLayoutURLs
var gDefaultMainLayoutURL
var feathersManager
var gRequiredSkinInternalNames
const Ci
var gBundledSkins
function runTest()
var failed
_getSelectedPageStyle s i
const PREF_DEFAULT_SKIN_INTERNALNAME
var previousSkinDataRemote
function testPending()
var gBundledLayouts