Wed, 31 Dec 2014 06:09:35 +0100
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, "&")
563 .replace(/"/g, """)
564 .replace(/</g, "<")
565 .replace(/>/g, ">");
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);
1036 }
1037 };
1039 function hasFlag(aBits, aFlag) {
1040 return (aBits & aFlag) != 0;
1041 }
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;
1051 }
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;
1064 }
1066 for (let id in gExpectedEvents) {
1067 if (gExpectedEvents[id].length > 0)
1068 return undefined;
1069 }
1071 return gNext.apply(null, aArgs);
1072 }
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);
1079 }
1080 gExpectedEvents = {};
1081 if (gExpectedInstalls)
1082 do_check_eq(gExpectedInstalls.length, 0);
1083 }
1085 /**
1086 * A helper method to install an array of AddonInstall to completion and then
1087 * call a provided callback.
1088 *
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;
1100 }
1102 function installCompleted(aInstall) {
1103 aInstall.removeListener(listener);
1105 if (--count == 0)
1106 do_execute_soon(aCallback);
1107 }
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 });
1121 }
1123 /**
1124 * A helper method to install an array of files and call a callback after the
1125 * installs are completed.
1126 *
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();
1141 }
1142 }
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 });
1156 }
1158 function promiseInstallAllFiles(aFiles, aIgnoreIncompatible) {
1159 let deferred = Promise.defer();
1160 installAllFiles(aFiles, deferred.resolve, aIgnoreIncompatible);
1161 return deferred.promise;
1163 }
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;
1182 }
1183 },
1185 setValue: function(aRoot, aPath, aName, aValue) {
1186 let rootKey = MockRegistry.getRoot(aRoot);
1188 if (!(aPath in rootKey)) {
1189 rootKey[aPath] = [];
1190 }
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;
1199 }
1200 }
1201 }
1203 if (aValue === null)
1204 return;
1206 rootKey[aPath].push({
1207 name: aName,
1208 value: aValue
1209 });
1210 }
1211 };
1213 /**
1214 * This is a mock nsIWindowsRegistry implementation. It only implements the
1215 * methods that the extension manager requires.
1216 */
1217 function MockWindowsRegKey() {
1218 }
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;
1255 }
1256 return null;
1257 }
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);
1267 }
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);
1274 }
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();
1349 }
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();
1359 }
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");
1368 }
1369 }
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);
1381 }
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);
1430 }
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
1438 }
1439 }
1441 /**
1442 * Sets up a path handler for the given URL and saves the
1443 * corresponding file in the global url -> file map.
1444 *
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;
1453 }
1455 function mapFile(path, server) {
1456 mapUrlToFile(path, do_get_file(path), server);
1457 }
1459 /**
1460 * Take out the port number in an URL
1461 *
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;
1469 }
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);
1475 }
1476 catch(e) {
1477 do_report_unexpected_exception(e);
1478 }
1479 };
1480 }
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);
1489 }
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);
1508 }
1509 cstream.close();
1510 return data;
1511 }
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));
1520 }
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);
1538 }
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");
1548 }
1549 }
1551 /**
1552 * A promise-based variant of AddonManager.getAddonsByIDs.
1553 *
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;
1563 }