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.

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

mercurial