toolkit/mozapps/extensions/test/xpcshell/head_addons.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* Any copyright is dedicated to the Public Domain.
     2  * http://creativecommons.org/publicdomain/zero/1.0/
     3  */
     5 const AM_Cc = Components.classes;
     6 const AM_Ci = Components.interfaces;
     8 const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
     9 const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
    11 const PREF_EM_CHECK_UPDATE_SECURITY   = "extensions.checkUpdateSecurity";
    12 const PREF_EM_STRICT_COMPATIBILITY    = "extensions.strictCompatibility";
    13 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
    14 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
    15 const PREF_GETADDONS_BYIDS               = "extensions.getAddons.get.url";
    16 const PREF_GETADDONS_BYIDS_PERFORMANCE   = "extensions.getAddons.getWithPerformance.url";
    18 // Forcibly end the test if it runs longer than 15 minutes
    19 const TIMEOUT_MS = 900000;
    21 Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm");
    22 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    23 Components.utils.import("resource://gre/modules/FileUtils.jsm");
    24 Components.utils.import("resource://gre/modules/Services.jsm");
    25 Components.utils.import("resource://gre/modules/NetUtil.jsm");
    26 Components.utils.import("resource://gre/modules/Promise.jsm");
    27 Components.utils.import("resource://gre/modules/Task.jsm");
    28 Components.utils.import("resource://gre/modules/osfile.jsm");
    30 Services.prefs.setBoolPref("toolkit.osfile.log", true);
    32 // We need some internal bits of AddonManager
    33 let AMscope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
    34 let AddonManager = AMscope.AddonManager;
    35 let AddonManagerInternal = AMscope.AddonManagerInternal;
    36 // Mock out AddonManager's reference to the AsyncShutdown module so we can shut
    37 // down AddonManager from the test
    38 let MockAsyncShutdown = {
    39   hook: null,
    40   profileBeforeChange: {
    41     addBlocker: function(aName, aBlocker) {
    42       do_print("Mock profileBeforeChange blocker for '" + aName + "'");
    43       MockAsyncShutdown.hook = aBlocker;
    44     }
    45   }
    46 };
    47 AMscope.AsyncShutdown = MockAsyncShutdown;
    49 var gInternalManager = null;
    50 var gAppInfo = null;
    51 var gAddonsList;
    53 var gPort = null;
    54 var gUrlToFileMap = {};
    56 var TEST_UNPACKED = false;
    58 function isNightlyChannel() {
    59   var channel = "default";
    60   try {
    61     channel = Services.prefs.getCharPref("app.update.channel");
    62   }
    63   catch (e) { }
    65   return channel != "aurora" && channel != "beta" && channel != "release" && channel != "esr";
    66 }
    68 function createAppInfo(id, name, version, platformVersion) {
    69   gAppInfo = {
    70     // nsIXULAppInfo
    71     vendor: "Mozilla",
    72     name: name,
    73     ID: id,
    74     version: version,
    75     appBuildID: "2007010101",
    76     platformVersion: platformVersion ? platformVersion : "1.0",
    77     platformBuildID: "2007010101",
    79     // nsIXULRuntime
    80     inSafeMode: false,
    81     logConsoleErrors: true,
    82     OS: "XPCShell",
    83     XPCOMABI: "noarch-spidermonkey",
    84     invalidateCachesOnRestart: function invalidateCachesOnRestart() {
    85       // Do nothing
    86     },
    88     // nsICrashReporter
    89     annotations: {},
    91     annotateCrashReport: function(key, data) {
    92       this.annotations[key] = data;
    93     },
    95     QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIXULAppInfo,
    96                                            AM_Ci.nsIXULRuntime,
    97                                            AM_Ci.nsICrashReporter,
    98                                            AM_Ci.nsISupports])
    99   };
   101   var XULAppInfoFactory = {
   102     createInstance: function (outer, iid) {
   103       if (outer != null)
   104         throw Components.results.NS_ERROR_NO_AGGREGATION;
   105       return gAppInfo.QueryInterface(iid);
   106     }
   107   };
   108   var registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar);
   109   registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
   110                             XULAPPINFO_CONTRACTID, XULAppInfoFactory);
   111 }
   113 /**
   114  * Tests that an add-on does appear in the crash report annotations, if
   115  * crash reporting is enabled. The test will fail if the add-on is not in the
   116  * annotation.
   117  * @param  aId
   118  *         The ID of the add-on
   119  * @param  aVersion
   120  *         The version of the add-on
   121  */
   122 function do_check_in_crash_annotation(aId, aVersion) {
   123   if (!("nsICrashReporter" in AM_Ci))
   124     return;
   126   if (!("Add-ons" in gAppInfo.annotations)) {
   127     do_check_false(true);
   128     return;
   129   }
   131   let addons = gAppInfo.annotations["Add-ons"].split(",");
   132   do_check_false(addons.indexOf(encodeURIComponent(aId) + ":" +
   133                                 encodeURIComponent(aVersion)) < 0);
   134 }
   136 /**
   137  * Tests that an add-on does not appear in the crash report annotations, if
   138  * crash reporting is enabled. The test will fail if the add-on is in the
   139  * annotation.
   140  * @param  aId
   141  *         The ID of the add-on
   142  * @param  aVersion
   143  *         The version of the add-on
   144  */
   145 function do_check_not_in_crash_annotation(aId, aVersion) {
   146   if (!("nsICrashReporter" in AM_Ci))
   147     return;
   149   if (!("Add-ons" in gAppInfo.annotations)) {
   150     do_check_true(true);
   151     return;
   152   }
   154   let addons = gAppInfo.annotations["Add-ons"].split(",");
   155   do_check_true(addons.indexOf(encodeURIComponent(aId) + ":" +
   156                                encodeURIComponent(aVersion)) < 0);
   157 }
   159 /**
   160  * Returns a testcase xpi
   161  *
   162  * @param  aName
   163  *         The name of the testcase (without extension)
   164  * @return an nsIFile pointing to the testcase xpi
   165  */
   166 function do_get_addon(aName) {
   167   return do_get_file("addons/" + aName + ".xpi");
   168 }
   170 function do_get_addon_hash(aName, aAlgorithm) {
   171   if (!aAlgorithm)
   172     aAlgorithm = "sha1";
   174   let file = do_get_addon(aName);
   176   let crypto = AM_Cc["@mozilla.org/security/hash;1"].
   177                createInstance(AM_Ci.nsICryptoHash);
   178   crypto.initWithString(aAlgorithm);
   179   let fis = AM_Cc["@mozilla.org/network/file-input-stream;1"].
   180             createInstance(AM_Ci.nsIFileInputStream);
   181   fis.init(file, -1, -1, false);
   182   crypto.updateFromStream(fis, file.fileSize);
   184   // return the two-digit hexadecimal code for a byte
   185   function toHexString(charCode)
   186     ("0" + charCode.toString(16)).slice(-2);
   188   let binary = crypto.finish(false);
   189   return aAlgorithm + ":" + [toHexString(binary.charCodeAt(i)) for (i in binary)].join("")
   190 }
   192 /**
   193  * Returns an extension uri spec
   194  *
   195  * @param  aProfileDir
   196  *         The extension install directory
   197  * @return a uri spec pointing to the root of the extension
   198  */
   199 function do_get_addon_root_uri(aProfileDir, aId) {
   200   let path = aProfileDir.clone();
   201   path.append(aId);
   202   if (!path.exists()) {
   203     path.leafName += ".xpi";
   204     return "jar:" + Services.io.newFileURI(path).spec + "!/";
   205   }
   206   else {
   207     return Services.io.newFileURI(path).spec;
   208   }
   209 }
   211 function do_get_expected_addon_name(aId) {
   212   if (TEST_UNPACKED)
   213     return aId;
   214   return aId + ".xpi";
   215 }
   217 /**
   218  * Check that an array of actual add-ons is the same as an array of
   219  * expected add-ons.
   220  *
   221  * @param  aActualAddons
   222  *         The array of actual add-ons to check.
   223  * @param  aExpectedAddons
   224  *         The array of expected add-ons to check against.
   225  * @param  aProperties
   226  *         An array of properties to check.
   227  */
   228 function do_check_addons(aActualAddons, aExpectedAddons, aProperties) {
   229   do_check_neq(aActualAddons, null);
   230   do_check_eq(aActualAddons.length, aExpectedAddons.length);
   231   for (let i = 0; i < aActualAddons.length; i++)
   232     do_check_addon(aActualAddons[i], aExpectedAddons[i], aProperties);
   233 }
   235 /**
   236  * Check that the actual add-on is the same as the expected add-on.
   237  *
   238  * @param  aActualAddon
   239  *         The actual add-on to check.
   240  * @param  aExpectedAddon
   241  *         The expected add-on to check against.
   242  * @param  aProperties
   243  *         An array of properties to check.
   244  */
   245 function do_check_addon(aActualAddon, aExpectedAddon, aProperties) {
   246   do_check_neq(aActualAddon, null);
   248   aProperties.forEach(function(aProperty) {
   249     let actualValue = aActualAddon[aProperty];
   250     let expectedValue = aExpectedAddon[aProperty];
   252     // Check that all undefined expected properties are null on actual add-on
   253     if (!(aProperty in aExpectedAddon)) {
   254       if (actualValue !== undefined && actualValue !== null) {
   255         do_throw("Unexpected defined/non-null property for add-on " +
   256                  aExpectedAddon.id + " (addon[" + aProperty + "] = " +
   257                  actualValue.toSource() + ")");
   258       }
   260       return;
   261     }
   262     else if (expectedValue && !actualValue) {
   263       do_throw("Missing property for add-on " + aExpectedAddon.id +
   264         ": expected addon[" + aProperty + "] = " + expectedValue);
   265       return;
   266     }
   268     switch (aProperty) {
   269       case "creator":
   270         do_check_author(actualValue, expectedValue);
   271         break;
   273       case "developers":
   274       case "translators":
   275       case "contributors":
   276         do_check_eq(actualValue.length, expectedValue.length);
   277         for (let i = 0; i < actualValue.length; i++)
   278           do_check_author(actualValue[i], expectedValue[i]);
   279         break;
   281       case "screenshots":
   282         do_check_eq(actualValue.length, expectedValue.length);
   283         for (let i = 0; i < actualValue.length; i++)
   284           do_check_screenshot(actualValue[i], expectedValue[i]);
   285         break;
   287       case "sourceURI":
   288         do_check_eq(actualValue.spec, expectedValue);
   289         break;
   291       case "updateDate":
   292         do_check_eq(actualValue.getTime(), expectedValue.getTime());
   293         break;
   295       case "compatibilityOverrides":
   296         do_check_eq(actualValue.length, expectedValue.length);
   297         for (let i = 0; i < actualValue.length; i++)
   298           do_check_compatibilityoverride(actualValue[i], expectedValue[i]);
   299         break;
   301       case "icons":
   302         do_check_icons(actualValue, expectedValue);
   303         break;
   305       default:
   306         if (remove_port(actualValue) !== remove_port(expectedValue))
   307           do_throw("Failed for " + aProperty + " for add-on " + aExpectedAddon.id +
   308                    " (" + actualValue + " === " + expectedValue + ")");
   309     }
   310   });
   311 }
   313 /**
   314  * Check that the actual author is the same as the expected author.
   315  *
   316  * @param  aActual
   317  *         The actual author to check.
   318  * @param  aExpected
   319  *         The expected author to check against.
   320  */
   321 function do_check_author(aActual, aExpected) {
   322   do_check_eq(aActual.toString(), aExpected.name);
   323   do_check_eq(aActual.name, aExpected.name);
   324   do_check_eq(aActual.url, aExpected.url);
   325 }
   327 /**
   328  * Check that the actual screenshot is the same as the expected screenshot.
   329  *
   330  * @param  aActual
   331  *         The actual screenshot to check.
   332  * @param  aExpected
   333  *         The expected screenshot to check against.
   334  */
   335 function do_check_screenshot(aActual, aExpected) {
   336   do_check_eq(aActual.toString(), aExpected.url);
   337   do_check_eq(aActual.url, aExpected.url);
   338   do_check_eq(aActual.width, aExpected.width);
   339   do_check_eq(aActual.height, aExpected.height);
   340   do_check_eq(aActual.thumbnailURL, aExpected.thumbnailURL);
   341   do_check_eq(aActual.thumbnailWidth, aExpected.thumbnailWidth);
   342   do_check_eq(aActual.thumbnailHeight, aExpected.thumbnailHeight);
   343   do_check_eq(aActual.caption, aExpected.caption);
   344 }
   346 /**
   347  * Check that the actual compatibility override is the same as the expected
   348  * compatibility override.
   349  *
   350  * @param  aAction
   351  *         The actual compatibility override to check.
   352  * @param  aExpected
   353  *         The expected compatibility override to check against.
   354  */
   355 function do_check_compatibilityoverride(aActual, aExpected) {
   356   do_check_eq(aActual.type, aExpected.type);
   357   do_check_eq(aActual.minVersion, aExpected.minVersion);
   358   do_check_eq(aActual.maxVersion, aExpected.maxVersion);
   359   do_check_eq(aActual.appID, aExpected.appID);
   360   do_check_eq(aActual.appMinVersion, aExpected.appMinVersion);
   361   do_check_eq(aActual.appMaxVersion, aExpected.appMaxVersion);
   362 }
   364 function do_check_icons(aActual, aExpected) {
   365   for (var size in aExpected) {
   366     do_check_eq(remove_port(aActual[size]), remove_port(aExpected[size]));
   367   }
   368 }
   370 // Record the error (if any) from trying to save the XPI
   371 // database at shutdown time
   372 let gXPISaveError = null;
   374 /**
   375  * Starts up the add-on manager as if it was started by the application.
   376  *
   377  * @param  aAppChanged
   378  *         An optional boolean parameter to simulate the case where the
   379  *         application has changed version since the last run. If not passed it
   380  *         defaults to true
   381  */
   382 function startupManager(aAppChanged) {
   383   if (gInternalManager)
   384     do_throw("Test attempt to startup manager that was already started.");
   386   if (aAppChanged || aAppChanged === undefined) {
   387     if (gExtensionsINI.exists())
   388       gExtensionsINI.remove(true);
   389   }
   391   gInternalManager = AM_Cc["@mozilla.org/addons/integration;1"].
   392                      getService(AM_Ci.nsIObserver).
   393                      QueryInterface(AM_Ci.nsITimerCallback);
   395   gInternalManager.observe(null, "addons-startup", null);
   397   // Load the add-ons list as it was after extension registration
   398   loadAddonsList();
   399 }
   401 /**
   402  * Helper to spin the event loop until a promise resolves or rejects
   403  */
   404 function loopUntilPromise(aPromise) {
   405   let done = false;
   406   aPromise.then(
   407     () => done = true,
   408     err => {
   409       do_report_unexpected_exception(err);
   410       done = true;
   411     });
   413   let thr = Services.tm.mainThread;
   415   while (!done) {
   416     thr.processNextEvent(true);
   417   }
   418 }
   420 /**
   421  * Restarts the add-on manager as if the host application was restarted.
   422  *
   423  * @param  aNewVersion
   424  *         An optional new version to use for the application. Passing this
   425  *         will change nsIXULAppInfo.version and make the startup appear as if
   426  *         the application version has changed.
   427  */
   428 function restartManager(aNewVersion) {
   429   loopUntilPromise(promiseRestartManager(aNewVersion));
   430 }
   432 function promiseRestartManager(aNewVersion) {
   433   return promiseShutdownManager()
   434     .then(null, err => do_report_unexpected_exception(err))
   435     .then(() => {
   436       if (aNewVersion) {
   437         gAppInfo.version = aNewVersion;
   438         startupManager(true);
   439       }
   440       else {
   441         startupManager(false);
   442       }
   443     });
   444 }
   446 function shutdownManager() {
   447   loopUntilPromise(promiseShutdownManager());
   448 }
   450 function promiseShutdownManager() {
   451   if (!gInternalManager) {
   452     return Promise.resolve(false);
   453   }
   455   let hookErr = null;
   456   Services.obs.notifyObservers(null, "quit-application-granted", null);
   457   return MockAsyncShutdown.hook()
   458     .then(null, err => hookErr = err)
   459     .then( () => {
   460       gInternalManager = null;
   462       // Load the add-ons list as it was after application shutdown
   463       loadAddonsList();
   465       // Clear any crash report annotations
   466       gAppInfo.annotations = {};
   468       // Force the XPIProvider provider to reload to better
   469       // simulate real-world usage.
   470       let XPIscope = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm");
   471       // This would be cleaner if I could get it as the rejection reason from
   472       // the AddonManagerInternal.shutdown() promise
   473       gXPISaveError = XPIscope.XPIProvider._shutdownError;
   474       do_print("gXPISaveError set to: " + gXPISaveError);
   475       AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
   476       Components.utils.unload("resource://gre/modules/addons/XPIProvider.jsm");
   477       if (hookErr) {
   478         throw hookErr;
   479       }
   480     });
   481 }
   483 function loadAddonsList() {
   484   function readDirectories(aSection) {
   485     var dirs = [];
   486     var keys = parser.getKeys(aSection);
   487     while (keys.hasMore()) {
   488       let descriptor = parser.getString(aSection, keys.getNext());
   489       try {
   490         let file = AM_Cc["@mozilla.org/file/local;1"].
   491                    createInstance(AM_Ci.nsIFile);
   492         file.persistentDescriptor = descriptor;
   493         dirs.push(file);
   494       }
   495       catch (e) {
   496         // Throws if the directory doesn't exist, we can ignore this since the
   497         // platform will too.
   498       }
   499     }
   500     return dirs;
   501   }
   503   gAddonsList = {
   504     extensions: [],
   505     themes: []
   506   };
   508   if (!gExtensionsINI.exists())
   509     return;
   511   var factory = AM_Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
   512                 getService(AM_Ci.nsIINIParserFactory);
   513   var parser = factory.createINIParser(gExtensionsINI);
   514   gAddonsList.extensions = readDirectories("ExtensionDirs");
   515   gAddonsList.themes = readDirectories("ThemeDirs");
   516 }
   518 function isItemInAddonsList(aType, aDir, aId) {
   519   var path = aDir.clone();
   520   path.append(aId);
   521   var xpiPath = aDir.clone();
   522   xpiPath.append(aId + ".xpi");
   523   for (var i = 0; i < gAddonsList[aType].length; i++) {
   524     let file = gAddonsList[aType][i];
   525     if (!file.exists())
   526       do_throw("Non-existant path found in extensions.ini: " + file.path)
   527     if (file.isDirectory() && file.equals(path))
   528       return true;
   529     if (file.isFile() && file.equals(xpiPath))
   530       return true;
   531   }
   532   return false;
   533 }
   535 function isThemeInAddonsList(aDir, aId) {
   536   return isItemInAddonsList("themes", aDir, aId);
   537 }
   539 function isExtensionInAddonsList(aDir, aId) {
   540   return isItemInAddonsList("extensions", aDir, aId);
   541 }
   543 function check_startup_changes(aType, aIds) {
   544   var ids = aIds.slice(0);
   545   ids.sort();
   546   var changes = AddonManager.getStartupChanges(aType);
   547   changes = changes.filter(function(aEl) /@tests.mozilla.org$/.test(aEl));
   548   changes.sort();
   550   do_check_eq(JSON.stringify(ids), JSON.stringify(changes));
   551 }
   553 /**
   554  * Escapes any occurances of &, ", < or > with XML entities.
   555  *
   556  * @param   str
   557  *          The string to escape
   558  * @return  The escaped string
   559  */
   560 function escapeXML(aStr) {
   561   return aStr.toString()
   562              .replace(/&/g, "&amp;")
   563              .replace(/"/g, "&quot;")
   564              .replace(/</g, "&lt;")
   565              .replace(/>/g, "&gt;");
   566 }
   568 function writeLocaleStrings(aData) {
   569   let rdf = "";
   570   ["name", "description", "creator", "homepageURL"].forEach(function(aProp) {
   571     if (aProp in aData)
   572       rdf += "<em:" + aProp + ">" + escapeXML(aData[aProp]) + "</em:" + aProp + ">\n";
   573   });
   575   ["developer", "translator", "contributor"].forEach(function(aProp) {
   576     if (aProp in aData) {
   577       aData[aProp].forEach(function(aValue) {
   578         rdf += "<em:" + aProp + ">" + escapeXML(aValue) + "</em:" + aProp + ">\n";
   579       });
   580     }
   581   });
   582   return rdf;
   583 }
   585 function createInstallRDF(aData) {
   586   var rdf = '<?xml version="1.0"?>\n';
   587   rdf += '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' +
   588          '     xmlns:em="http://www.mozilla.org/2004/em-rdf#">\n';
   589   rdf += '<Description about="urn:mozilla:install-manifest">\n';
   591   ["id", "version", "type", "internalName", "updateURL", "updateKey",
   592    "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL",
   593    "skinnable", "bootstrap", "strictCompatibility"].forEach(function(aProp) {
   594     if (aProp in aData)
   595       rdf += "<em:" + aProp + ">" + escapeXML(aData[aProp]) + "</em:" + aProp + ">\n";
   596   });
   598   rdf += writeLocaleStrings(aData);
   600   if ("targetPlatforms" in aData) {
   601     aData.targetPlatforms.forEach(function(aPlatform) {
   602       rdf += "<em:targetPlatform>" + escapeXML(aPlatform) + "</em:targetPlatform>\n";
   603     });
   604   }
   606   if ("targetApplications" in aData) {
   607     aData.targetApplications.forEach(function(aApp) {
   608       rdf += "<em:targetApplication><Description>\n";
   609       ["id", "minVersion", "maxVersion"].forEach(function(aProp) {
   610         if (aProp in aApp)
   611           rdf += "<em:" + aProp + ">" + escapeXML(aApp[aProp]) + "</em:" + aProp + ">\n";
   612       });
   613       rdf += "</Description></em:targetApplication>\n";
   614     });
   615   }
   617   if ("localized" in aData) {
   618     aData.localized.forEach(function(aLocalized) {
   619       rdf += "<em:localized><Description>\n";
   620       if ("locale" in aLocalized) {
   621         aLocalized.locale.forEach(function(aLocaleName) {
   622           rdf += "<em:locale>" + escapeXML(aLocaleName) + "</em:locale>\n";
   623         });
   624       }
   625       rdf += writeLocaleStrings(aLocalized);
   626       rdf += "</Description></em:localized>\n";
   627     });
   628   }
   630   rdf += "</Description>\n</RDF>\n";
   631   return rdf;
   632 }
   634 /**
   635  * Writes an install.rdf manifest into a directory using the properties passed
   636  * in a JS object. The objects should contain a property for each property to
   637  * appear in the RDFThe object may contain an array of objects with id,
   638  * minVersion and maxVersion in the targetApplications property to give target
   639  * application compatibility.
   640  *
   641  * @param   aData
   642  *          The object holding data about the add-on
   643  * @param   aDir
   644  *          The directory to add the install.rdf to
   645  * @param   aExtraFile
   646  *          An optional dummy file to create in the directory
   647  */
   648 function writeInstallRDFToDir(aData, aDir, aExtraFile) {
   649   var rdf = createInstallRDF(aData);
   650   if (!aDir.exists())
   651     aDir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   652   var file = aDir.clone();
   653   file.append("install.rdf");
   654   if (file.exists())
   655     file.remove(true);
   656   var fos = AM_Cc["@mozilla.org/network/file-output-stream;1"].
   657             createInstance(AM_Ci.nsIFileOutputStream);
   658   fos.init(file,
   659            FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
   660            FileUtils.PERMS_FILE, 0);
   661   fos.write(rdf, rdf.length);
   662   fos.close();
   664   if (!aExtraFile)
   665     return;
   667   file = aDir.clone();
   668   file.append(aExtraFile);
   669   file.create(AM_Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
   670 }
   672 /**
   673  * Writes an install.rdf manifest into an extension using the properties passed
   674  * in a JS object. The objects should contain a property for each property to
   675  * appear in the RDFThe object may contain an array of objects with id,
   676  * minVersion and maxVersion in the targetApplications property to give target
   677  * application compatibility.
   678  *
   679  * @param   aData
   680  *          The object holding data about the add-on
   681  * @param   aDir
   682  *          The install directory to add the extension to
   683  * @param   aId
   684  *          An optional string to override the default installation aId
   685  * @param   aExtraFile
   686  *          An optional dummy file to create in the extension
   687  * @return  A file pointing to where the extension was installed
   688  */
   689 function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
   690   var id = aId ? aId : aData.id
   692   var dir = aDir.clone();
   694   if (TEST_UNPACKED) {
   695     dir.append(id);
   696     writeInstallRDFToDir(aData, dir, aExtraFile);
   697     return dir;
   698   }
   700   if (!dir.exists())
   701     dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   702   dir.append(id + ".xpi");
   703   var rdf = createInstallRDF(aData);
   704   var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
   705                createInstance(AM_Ci.nsIStringInputStream);
   706   stream.setData(rdf, -1);
   707   var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
   708              createInstance(AM_Ci.nsIZipWriter);
   709   zipW.open(dir, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
   710   zipW.addEntryStream("install.rdf", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
   711                       stream, false);
   712   if (aExtraFile)
   713     zipW.addEntryStream(aExtraFile, 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
   714                         stream, false);
   715   zipW.close();
   716   return dir;
   717 }
   719 /**
   720  * Sets the last modified time of the extension, usually to trigger an update
   721  * of its metadata. If the extension is unpacked, this function assumes that
   722  * the extension contains only the install.rdf file.
   723  *
   724  * @param aExt   a file pointing to either the packed extension or its unpacked directory.
   725  * @param aTime  the time to which we set the lastModifiedTime of the extension
   726  *
   727  * @deprecated Please use promiseSetExtensionModifiedTime instead
   728  */
   729 function setExtensionModifiedTime(aExt, aTime) {
   730   aExt.lastModifiedTime = aTime;
   731   if (aExt.isDirectory()) {
   732     let entries = aExt.directoryEntries
   733                       .QueryInterface(AM_Ci.nsIDirectoryEnumerator);
   734     while (entries.hasMoreElements())
   735       setExtensionModifiedTime(entries.nextFile, aTime);
   736     entries.close();
   737   }
   738 }
   739 function promiseSetExtensionModifiedTime(aPath, aTime) {
   740   return Task.spawn(function* () {
   741     yield OS.File.setDates(aPath, aTime, aTime);
   742     let entries, iterator;
   743     try {
   744       let iterator = new OS.File.DirectoryIterator(aPath);
   745       entries = yield iterator.nextBatch();
   746     } catch (ex if ex instanceof OS.File.Error) {
   747       return;
   748     } finally {
   749       if (iterator) {
   750         iterator.close();
   751       }
   752     }
   753     for (let entry of entries) {
   754       yield promiseSetExtensionModifiedTime(entry.path, aTime);
   755     }
   756   });
   757 }
   759 /**
   760  * Manually installs an XPI file into an install location by either copying the
   761  * XPI there or extracting it depending on whether unpacking is being tested
   762  * or not.
   763  *
   764  * @param aXPIFile
   765  *        The XPI file to install.
   766  * @param aInstallLocation
   767  *        The install location (an nsIFile) to install into.
   768  * @param aID
   769  *        The ID to install as.
   770  */
   771 function manuallyInstall(aXPIFile, aInstallLocation, aID) {
   772   if (TEST_UNPACKED) {
   773     let dir = aInstallLocation.clone();
   774     dir.append(aID);
   775     dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   776     let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
   777               createInstance(AM_Ci.nsIZipReader);
   778     zip.open(aXPIFile);
   779     let entries = zip.findEntries(null);
   780     while (entries.hasMore()) {
   781       let entry = entries.getNext();
   782       let target = dir.clone();
   783       entry.split("/").forEach(function(aPart) {
   784         target.append(aPart);
   785       });
   786       zip.extract(entry, target);
   787     }
   788     zip.close();
   790     return dir;
   791   }
   792   else {
   793     let target = aInstallLocation.clone();
   794     target.append(aID + ".xpi");
   795     aXPIFile.copyTo(target.parent, target.leafName);
   796     return target;
   797   }
   798 }
   800 /**
   801  * Manually uninstalls an add-on by removing its files from the install
   802  * location.
   803  *
   804  * @param aInstallLocation
   805  *        The nsIFile of the install location to remove from.
   806  * @param aID
   807  *        The ID of the add-on to remove.
   808  */
   809 function manuallyUninstall(aInstallLocation, aID) {
   810   let file = getFileForAddon(aInstallLocation, aID);
   812   // In reality because the app is restarted a flush isn't necessary for XPIs
   813   // removed outside the app, but for testing we must flush manually.
   814   if (file.isFile())
   815     Services.obs.notifyObservers(file, "flush-cache-entry", null);
   817   file.remove(true);
   818 }
   820 /**
   821  * Gets the nsIFile for where an add-on is installed. It may point to a file or
   822  * a directory depending on whether add-ons are being installed unpacked or not.
   823  *
   824  * @param  aDir
   825  *         The nsIFile for the install location
   826  * @param  aId
   827  *         The ID of the add-on
   828  * @return an nsIFile
   829  */
   830 function getFileForAddon(aDir, aId) {
   831   var dir = aDir.clone();
   832   dir.append(do_get_expected_addon_name(aId));
   833   return dir;
   834 }
   836 function registerDirectory(aKey, aDir) {
   837   var dirProvider = {
   838     getFile: function(aProp, aPersistent) {
   839       aPersistent.value = true;
   840       if (aProp == aKey)
   841         return aDir.clone();
   842       return null;
   843     },
   845     QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIDirectoryServiceProvider,
   846                                            AM_Ci.nsISupports])
   847   };
   848   Services.dirsvc.registerProvider(dirProvider);
   849 }
   851 var gExpectedEvents = {};
   852 var gExpectedInstalls = [];
   853 var gNext = null;
   855 function getExpectedEvent(aId) {
   856   if (!(aId in gExpectedEvents))
   857     do_throw("Wasn't expecting events for " + aId);
   858   if (gExpectedEvents[aId].length == 0)
   859     do_throw("Too many events for " + aId);
   860   let event = gExpectedEvents[aId].shift();
   861   if (event instanceof Array)
   862     return event;
   863   return [event, true];
   864 }
   866 function getExpectedInstall(aAddon) {
   867   if (gExpectedInstalls instanceof Array)
   868     return gExpectedInstalls.shift();
   869   if (!aAddon || !aAddon.id)
   870     return gExpectedInstalls["NO_ID"].shift();
   871   let id = aAddon.id;
   872   if (!(id in gExpectedInstalls) || !(gExpectedInstalls[id] instanceof Array))
   873     do_throw("Wasn't expecting events for " + id);
   874   if (gExpectedInstalls[id].length == 0)
   875     do_throw("Too many events for " + id);
   876   return gExpectedInstalls[id].shift();
   877 }
   879 const AddonListener = {
   880   onPropertyChanged: function(aAddon, aProperties) {
   881     let [event, properties] = getExpectedEvent(aAddon.id);
   882     do_check_eq("onPropertyChanged", event);
   883     do_check_eq(aProperties.length, properties.length);
   884     properties.forEach(function(aProperty) {
   885       // Only test that the expected properties are listed, having additional
   886       // properties listed is not necessary a problem
   887       if (aProperties.indexOf(aProperty) == -1)
   888         do_throw("Did not see property change for " + aProperty);
   889     });
   890     return check_test_completed(arguments);
   891   },
   893   onEnabling: function(aAddon, aRequiresRestart) {
   894     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   895     do_check_eq("onEnabling", event);
   896     do_check_eq(aRequiresRestart, expectedRestart);
   897     if (expectedRestart)
   898       do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_ENABLE));
   899     do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE));
   900     return check_test_completed(arguments);
   901   },
   903   onEnabled: function(aAddon) {
   904     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   905     do_check_eq("onEnabled", event);
   906     do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE));
   907     return check_test_completed(arguments);
   908   },
   910   onDisabling: function(aAddon, aRequiresRestart) {
   911     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   912     do_check_eq("onDisabling", event);
   913     do_check_eq(aRequiresRestart, expectedRestart);
   914     if (expectedRestart)
   915       do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_DISABLE));
   916     do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE));
   917     return check_test_completed(arguments);
   918   },
   920   onDisabled: function(aAddon) {
   921     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   922     do_check_eq("onDisabled", event);
   923     do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE));
   924     return check_test_completed(arguments);
   925   },
   927   onInstalling: function(aAddon, aRequiresRestart) {
   928     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   929     do_check_eq("onInstalling", event);
   930     do_check_eq(aRequiresRestart, expectedRestart);
   931     if (expectedRestart)
   932       do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_INSTALL));
   933     return check_test_completed(arguments);
   934   },
   936   onInstalled: function(aAddon) {
   937     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   938     do_check_eq("onInstalled", event);
   939     return check_test_completed(arguments);
   940   },
   942   onUninstalling: function(aAddon, aRequiresRestart) {
   943     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   944     do_check_eq("onUninstalling", event);
   945     do_check_eq(aRequiresRestart, expectedRestart);
   946     if (expectedRestart)
   947       do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_UNINSTALL));
   948     return check_test_completed(arguments);
   949   },
   951   onUninstalled: function(aAddon) {
   952     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   953     do_check_eq("onUninstalled", event);
   954     return check_test_completed(arguments);
   955   },
   957   onOperationCancelled: function(aAddon) {
   958     let [event, expectedRestart] = getExpectedEvent(aAddon.id);
   959     do_check_eq("onOperationCancelled", event);
   960     return check_test_completed(arguments);
   961   }
   962 };
   964 const InstallListener = {
   965   onNewInstall: function(install) {
   966     if (install.state != AddonManager.STATE_DOWNLOADED &&
   967         install.state != AddonManager.STATE_AVAILABLE)
   968       do_throw("Bad install state " + install.state);
   969     do_check_eq(install.error, 0);
   970     do_check_eq("onNewInstall", getExpectedInstall());
   971     return check_test_completed(arguments);
   972   },
   974   onDownloadStarted: function(install) {
   975     do_check_eq(install.state, AddonManager.STATE_DOWNLOADING);
   976     do_check_eq(install.error, 0);
   977     do_check_eq("onDownloadStarted", getExpectedInstall());
   978     return check_test_completed(arguments);
   979   },
   981   onDownloadEnded: function(install) {
   982     do_check_eq(install.state, AddonManager.STATE_DOWNLOADED);
   983     do_check_eq(install.error, 0);
   984     do_check_eq("onDownloadEnded", getExpectedInstall());
   985     return check_test_completed(arguments);
   986   },
   988   onDownloadFailed: function(install) {
   989     do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
   990     do_check_eq("onDownloadFailed", getExpectedInstall());
   991     return check_test_completed(arguments);
   992   },
   994   onDownloadCancelled: function(install) {
   995     do_check_eq(install.state, AddonManager.STATE_CANCELLED);
   996     do_check_eq(install.error, 0);
   997     do_check_eq("onDownloadCancelled", getExpectedInstall());
   998     return check_test_completed(arguments);
   999   },
  1001   onInstallStarted: function(install) {
  1002     do_check_eq(install.state, AddonManager.STATE_INSTALLING);
  1003     do_check_eq(install.error, 0);
  1004     do_check_eq("onInstallStarted", getExpectedInstall(install.addon));
  1005     return check_test_completed(arguments);
  1006   },
  1008   onInstallEnded: function(install, newAddon) {
  1009     do_check_eq(install.state, AddonManager.STATE_INSTALLED);
  1010     do_check_eq(install.error, 0);
  1011     do_check_eq("onInstallEnded", getExpectedInstall(install.addon));
  1012     return check_test_completed(arguments);
  1013   },
  1015   onInstallFailed: function(install) {
  1016     do_check_eq(install.state, AddonManager.STATE_INSTALL_FAILED);
  1017     do_check_eq("onInstallFailed", getExpectedInstall(install.addon));
  1018     return check_test_completed(arguments);
  1019   },
  1021   onInstallCancelled: function(install) {
  1022     // If the install was cancelled by a listener returning false from
  1023     // onInstallStarted, then the state will revert to STATE_DOWNLOADED.
  1024     let possibleStates = [AddonManager.STATE_CANCELLED,
  1025                           AddonManager.STATE_DOWNLOADED];
  1026     do_check_true(possibleStates.indexOf(install.state) != -1);
  1027     do_check_eq(install.error, 0);
  1028     do_check_eq("onInstallCancelled", getExpectedInstall(install.addon));
  1029     return check_test_completed(arguments);
  1030   },
  1032   onExternalInstall: function(aAddon, existingAddon, aRequiresRestart) {
  1033     do_check_eq("onExternalInstall", getExpectedInstall(aAddon));
  1034     do_check_false(aRequiresRestart);
  1035     return check_test_completed(arguments);
  1037 };
  1039 function hasFlag(aBits, aFlag) {
  1040   return (aBits & aFlag) != 0;
  1043 // Just a wrapper around setting the expected events
  1044 function prepare_test(aExpectedEvents, aExpectedInstalls, aNext) {
  1045   AddonManager.addAddonListener(AddonListener);
  1046   AddonManager.addInstallListener(InstallListener);
  1048   gExpectedInstalls = aExpectedInstalls;
  1049   gExpectedEvents = aExpectedEvents;
  1050   gNext = aNext;
  1053 // Checks if all expected events have been seen and if so calls the callback
  1054 function check_test_completed(aArgs) {
  1055   if (!gNext)
  1056     return undefined;
  1058   if (gExpectedInstalls instanceof Array &&
  1059       gExpectedInstalls.length > 0)
  1060     return undefined;
  1061   else for each (let installList in gExpectedInstalls) {
  1062     if (installList.length > 0)
  1063       return undefined;
  1066   for (let id in gExpectedEvents) {
  1067     if (gExpectedEvents[id].length > 0)
  1068       return undefined;
  1071   return gNext.apply(null, aArgs);
  1074 // Verifies that all the expected events for all add-ons were seen
  1075 function ensure_test_completed() {
  1076   for (let i in gExpectedEvents) {
  1077     if (gExpectedEvents[i].length > 0)
  1078       do_throw("Didn't see all the expected events for " + i);
  1080   gExpectedEvents = {};
  1081   if (gExpectedInstalls)
  1082     do_check_eq(gExpectedInstalls.length, 0);
  1085 /**
  1086  * A helper method to install an array of AddonInstall to completion and then
  1087  * call a provided callback.
  1089  * @param   aInstalls
  1090  *          The array of AddonInstalls to install
  1091  * @param   aCallback
  1092  *          The callback to call when all installs have finished
  1093  */
  1094 function completeAllInstalls(aInstalls, aCallback) {
  1095   let count = aInstalls.length;
  1097   if (count == 0) {
  1098     aCallback();
  1099     return;
  1102   function installCompleted(aInstall) {
  1103     aInstall.removeListener(listener);
  1105     if (--count == 0)
  1106       do_execute_soon(aCallback);
  1109   let listener = {
  1110     onDownloadFailed: installCompleted,
  1111     onDownloadCancelled: installCompleted,
  1112     onInstallFailed: installCompleted,
  1113     onInstallCancelled: installCompleted,
  1114     onInstallEnded: installCompleted
  1115   };
  1117   aInstalls.forEach(function(aInstall) {
  1118     aInstall.addListener(listener);
  1119     aInstall.install();
  1120   });
  1123 /**
  1124  * A helper method to install an array of files and call a callback after the
  1125  * installs are completed.
  1127  * @param   aFiles
  1128  *          The array of files to install
  1129  * @param   aCallback
  1130  *          The callback to call when all installs have finished
  1131  * @param   aIgnoreIncompatible
  1132  *          Optional parameter to ignore add-ons that are incompatible in
  1133  *          aome way with the application
  1134  */
  1135 function installAllFiles(aFiles, aCallback, aIgnoreIncompatible) {
  1136   let count = aFiles.length;
  1137   let installs = [];
  1138   function callback() {
  1139     if (aCallback) {
  1140       aCallback();
  1143   aFiles.forEach(function(aFile) {
  1144     AddonManager.getInstallForFile(aFile, function(aInstall) {
  1145       if (!aInstall)
  1146         do_throw("No AddonInstall created for " + aFile.path);
  1147       do_check_eq(aInstall.state, AddonManager.STATE_DOWNLOADED);
  1149       if (!aIgnoreIncompatible || !aInstall.addon.appDisabled)
  1150         installs.push(aInstall);
  1152       if (--count == 0)
  1153         completeAllInstalls(installs, callback);
  1154     });
  1155   });
  1158 function promiseInstallAllFiles(aFiles, aIgnoreIncompatible) {
  1159   let deferred = Promise.defer();
  1160   installAllFiles(aFiles, deferred.resolve, aIgnoreIncompatible);
  1161   return deferred.promise;
  1165 if ("nsIWindowsRegKey" in AM_Ci) {
  1166   var MockRegistry = {
  1167     LOCAL_MACHINE: {},
  1168     CURRENT_USER: {},
  1169     CLASSES_ROOT: {},
  1171     getRoot: function(aRoot) {
  1172       switch (aRoot) {
  1173       case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE:
  1174         return MockRegistry.LOCAL_MACHINE;
  1175       case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER:
  1176         return MockRegistry.CURRENT_USER;
  1177       case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT:
  1178         return MockRegistry.CLASSES_ROOT;
  1179       default:
  1180         do_throw("Unknown root " + aRootKey);
  1181         return null;
  1183     },
  1185     setValue: function(aRoot, aPath, aName, aValue) {
  1186       let rootKey = MockRegistry.getRoot(aRoot);
  1188       if (!(aPath in rootKey)) {
  1189         rootKey[aPath] = [];
  1191       else {
  1192         for (let i = 0; i < rootKey[aPath].length; i++) {
  1193           if (rootKey[aPath][i].name == aName) {
  1194             if (aValue === null)
  1195               rootKey[aPath].splice(i, 1);
  1196             else
  1197               rootKey[aPath][i].value = aValue;
  1198             return;
  1203       if (aValue === null)
  1204         return;
  1206       rootKey[aPath].push({
  1207         name: aName,
  1208         value: aValue
  1209       });
  1211   };
  1213   /**
  1214    * This is a mock nsIWindowsRegistry implementation. It only implements the
  1215    * methods that the extension manager requires.
  1216    */
  1217   function MockWindowsRegKey() {
  1220   MockWindowsRegKey.prototype = {
  1221     values: null,
  1223     // --- Overridden nsISupports interface functions ---
  1224     QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIWindowsRegKey]),
  1226     // --- Overridden nsIWindowsRegKey interface functions ---
  1227     open: function(aRootKey, aRelPath, aMode) {
  1228       let rootKey = MockRegistry.getRoot(aRootKey);
  1230       if (!(aRelPath in rootKey))
  1231         rootKey[aRelPath] = [];
  1232       this.values = rootKey[aRelPath];
  1233     },
  1235     close: function() {
  1236       this.values = null;
  1237     },
  1239     get valueCount() {
  1240       if (!this.values)
  1241         throw Components.results.NS_ERROR_FAILURE;
  1242       return this.values.length;
  1243     },
  1245     getValueName: function(aIndex) {
  1246       if (!this.values || aIndex >= this.values.length)
  1247         throw Components.results.NS_ERROR_FAILURE;
  1248       return this.values[aIndex].name;
  1249     },
  1251     readStringValue: function(aName) {
  1252       for (let value of this.values) {
  1253         if (value.name == aName)
  1254           return value.value;
  1256       return null;
  1258   };
  1260   var WinRegFactory = {
  1261     createInstance: function(aOuter, aIid) {
  1262       if (aOuter != null)
  1263         throw Components.results.NS_ERROR_NO_AGGREGATION;
  1265       var key = new MockWindowsRegKey();
  1266       return key.QueryInterface(aIid);
  1268   };
  1270   var registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar);
  1271   registrar.registerFactory(Components.ID("{0478de5b-0f38-4edb-851d-4c99f1ed8eba}"),
  1272                             "Mock Windows Registry Implementation",
  1273                             "@mozilla.org/windows-registry-key;1", WinRegFactory);
  1276 // Get the profile directory for tests to use.
  1277 const gProfD = do_get_profile();
  1279 const EXTENSIONS_DB = "extensions.json";
  1280 let gExtensionsJSON = gProfD.clone();
  1281 gExtensionsJSON.append(EXTENSIONS_DB);
  1283 const EXTENSIONS_INI = "extensions.ini";
  1284 let gExtensionsINI = gProfD.clone();
  1285 gExtensionsINI.append(EXTENSIONS_INI);
  1287 // Enable more extensive EM logging
  1288 Services.prefs.setBoolPref("extensions.logging.enabled", true);
  1290 // By default only load extensions from the profile install location
  1291 Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_PROFILE);
  1293 // By default don't disable add-ons from any scope
  1294 Services.prefs.setIntPref("extensions.autoDisableScopes", 0);
  1296 // By default, don't cache add-ons in AddonRepository.jsm
  1297 Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false);
  1299 // Disable the compatibility updates window by default
  1300 Services.prefs.setBoolPref("extensions.showMismatchUI", false);
  1302 // Point update checks to the local machine for fast failures
  1303 Services.prefs.setCharPref("extensions.update.url", "http://127.0.0.1/updateURL");
  1304 Services.prefs.setCharPref("extensions.update.background.url", "http://127.0.0.1/updateBackgroundURL");
  1305 Services.prefs.setCharPref("extensions.blocklist.url", "http://127.0.0.1/blocklistURL");
  1307 // By default ignore bundled add-ons
  1308 Services.prefs.setBoolPref("extensions.installDistroAddons", false);
  1310 // By default use strict compatibility
  1311 Services.prefs.setBoolPref("extensions.strictCompatibility", true);
  1313 // By default don't check for hotfixes
  1314 Services.prefs.setCharPref("extensions.hotfix.id", "");
  1316 // By default, set min compatible versions to 0
  1317 Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0");
  1318 Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0");
  1320 // Register a temporary directory for the tests.
  1321 const gTmpD = gProfD.clone();
  1322 gTmpD.append("temp");
  1323 gTmpD.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
  1324 registerDirectory("TmpD", gTmpD);
  1326 // Write out an empty blocklist.xml file to the profile to ensure nothing
  1327 // is blocklisted by default
  1328 var blockFile = gProfD.clone();
  1329 blockFile.append("blocklist.xml");
  1330 var stream = AM_Cc["@mozilla.org/network/file-output-stream;1"].
  1331              createInstance(AM_Ci.nsIFileOutputStream);
  1332 stream.init(blockFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE,
  1333             FileUtils.PERMS_FILE, 0);
  1335 var data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
  1336            "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">\n" +
  1337            "</blocklist>\n";
  1338 stream.write(data, data.length);
  1339 stream.close();
  1341 // Copies blocklistFile (an nsIFile) to gProfD/blocklist.xml.
  1342 function copyBlocklistToProfile(blocklistFile) {
  1343   var dest = gProfD.clone();
  1344   dest.append("blocklist.xml");
  1345   if (dest.exists())
  1346     dest.remove(false);
  1347   blocklistFile.copyTo(gProfD, "blocklist.xml");
  1348   dest.lastModifiedTime = Date.now();
  1351 // Throw a failure and attempt to abandon the test if it looks like it is going
  1352 // to timeout
  1353 function timeout() {
  1354   timer = null;
  1355   do_throw("Test ran longer than " + TIMEOUT_MS + "ms");
  1357   // Attempt to bail out of the test
  1358   do_test_finished();
  1361 var timer = AM_Cc["@mozilla.org/timer;1"].createInstance(AM_Ci.nsITimer);
  1362 timer.init(timeout, TIMEOUT_MS, AM_Ci.nsITimer.TYPE_ONE_SHOT);
  1364 // Make sure that a given path does not exist
  1365 function pathShouldntExist(aPath) {
  1366   if (aPath.exists()) {
  1367     do_throw("Test cleanup: path " + aPath.path + " exists when it should not");
  1371 do_register_cleanup(function addon_cleanup() {
  1372   if (timer)
  1373     timer.cancel();
  1375   // Check that the temporary directory is empty
  1376   var dirEntries = gTmpD.directoryEntries
  1377                         .QueryInterface(AM_Ci.nsIDirectoryEnumerator);
  1378   var entry;
  1379   while ((entry = dirEntries.nextFile)) {
  1380     do_throw("Found unexpected file in temporary directory: " + entry.leafName);
  1382   dirEntries.close();
  1384   var testDir = gProfD.clone();
  1385   testDir.append("extensions");
  1386   testDir.append("trash");
  1387   pathShouldntExist(testDir);
  1389   testDir.leafName = "staged";
  1390   pathShouldntExist(testDir);
  1392   testDir.leafName = "staged-xpis";
  1393   pathShouldntExist(testDir);
  1395   shutdownManager();
  1397   // Clear commonly set prefs.
  1398   try {
  1399     Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
  1400   } catch (e) {}
  1401   try {
  1402     Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY);
  1403   } catch (e) {}
  1404 });
  1406 /**
  1407  * Handler function that responds with the interpolated
  1408  * static file associated to the URL specified by request.path.
  1409  * This replaces the %PORT% entries in the file with the actual
  1410  * value of the running server's port (stored in gPort).
  1411  */
  1412 function interpolateAndServeFile(request, response) {
  1413   try {
  1414     let file = gUrlToFileMap[request.path];
  1415     var data = "";
  1416     var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
  1417     createInstance(Components.interfaces.nsIFileInputStream);
  1418     var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
  1419     createInstance(Components.interfaces.nsIConverterInputStream);
  1420     fstream.init(file, -1, 0, 0);
  1421     cstream.init(fstream, "UTF-8", 0, 0);
  1423     let (str = {}) {
  1424       let read = 0;
  1425       do {
  1426         // read as much as we can and put it in str.value
  1427         read = cstream.readString(0xffffffff, str);
  1428         data += str.value;
  1429       } while (read != 0);
  1431     data = data.replace(/%PORT%/g, gPort);
  1433     response.write(data);
  1434   } catch (e) {
  1435     do_throw("Exception while serving interpolated file.");
  1436   } finally {
  1437     cstream.close(); // this closes fstream as well
  1441 /**
  1442  * Sets up a path handler for the given URL and saves the
  1443  * corresponding file in the global url -> file map.
  1445  * @param  url
  1446  *         the actual URL
  1447  * @param  file
  1448  *         nsILocalFile representing a static file
  1449  */
  1450 function mapUrlToFile(url, file, server) {
  1451   server.registerPathHandler(url, interpolateAndServeFile);
  1452   gUrlToFileMap[url] = file;
  1455 function mapFile(path, server) {
  1456   mapUrlToFile(path, do_get_file(path), server);
  1459 /**
  1460  * Take out the port number in an URL
  1462  * @param url
  1463  *        String that represents an URL with a port number in it
  1464  */
  1465 function remove_port(url) {
  1466   if (typeof url === "string")
  1467     return url.replace(/:\d+/, "");
  1468   return url;
  1470 // Wrap a function (typically a callback) to catch and report exceptions
  1471 function do_exception_wrap(func) {
  1472   return function() {
  1473     try {
  1474       func.apply(null, arguments);
  1476     catch(e) {
  1477       do_report_unexpected_exception(e);
  1479   };
  1482 /**
  1483  * Change the schema version of the JSON extensions database
  1484  */
  1485 function changeXPIDBVersion(aNewVersion) {
  1486   let jData = loadJSON(gExtensionsJSON);
  1487   jData.schemaVersion = aNewVersion;
  1488   saveJSON(jData, gExtensionsJSON);
  1491 /**
  1492  * Load a file into a string
  1493  */
  1494 function loadFile(aFile) {
  1495   let data = "";
  1496   let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
  1497           createInstance(Components.interfaces.nsIFileInputStream);
  1498   let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
  1499           createInstance(Components.interfaces.nsIConverterInputStream);
  1500   fstream.init(aFile, -1, 0, 0);
  1501   cstream.init(fstream, "UTF-8", 0, 0);
  1502   let (str = {}) {
  1503     let read = 0;
  1504     do {
  1505       read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
  1506       data += str.value;
  1507     } while (read != 0);
  1509   cstream.close();
  1510   return data;
  1513 /**
  1514  * Raw load of a JSON file
  1515  */
  1516 function loadJSON(aFile) {
  1517   let data = loadFile(aFile);
  1518   do_print("Loaded JSON file " + aFile.path);
  1519   return(JSON.parse(data));
  1522 /**
  1523  * Raw save of a JSON blob to file
  1524  */
  1525 function saveJSON(aData, aFile) {
  1526   do_print("Starting to save JSON file " + aFile.path);
  1527   let stream = FileUtils.openSafeFileOutputStream(aFile);
  1528   let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"].
  1529     createInstance(AM_Ci.nsIConverterOutputStream);
  1530   converter.init(stream, "UTF-8", 0, 0x0000);
  1531   // XXX pretty print the JSON while debugging
  1532   converter.writeString(JSON.stringify(aData, null, 2));
  1533   converter.flush();
  1534   // nsConverterOutputStream doesn't finish() safe output streams on close()
  1535   FileUtils.closeSafeFileOutputStream(stream);
  1536   converter.close();
  1537   do_print("Done saving JSON file " + aFile.path);
  1540 /**
  1541  * Create a callback function that calls do_execute_soon on an actual callback and arguments
  1542  */
  1543 function callback_soon(aFunction) {
  1544   return function(...args) {
  1545     do_execute_soon(function() {
  1546       aFunction.apply(null, args);
  1547     }, aFunction.name ? "delayed callback " + aFunction.name : "delayed callback");
  1551 /**
  1552  * A promise-based variant of AddonManager.getAddonsByIDs.
  1554  * @param {array} list As the first argument of AddonManager.getAddonsByIDs
  1555  * @return {promise}
  1556  * @resolve {array} The list of add-ons sent by AddonManaget.getAddonsByIDs to
  1557  * its callback.
  1558  */
  1559 function promiseAddonsByIDs(list) {
  1560   let deferred = Promise.defer();
  1561   AddonManager.getAddonsByIDs(list, deferred.resolve);
  1562   return deferred.promise;

mercurial