toolkit/mozapps/extensions/test/browser/head.js

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

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

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

     1 /* Any copyright is dedicated to the Public Domain.
     2  * http://creativecommons.org/publicdomain/zero/1.0/
     3  */
     5 Components.utils.import("resource://gre/modules/NetUtil.jsm");
     7 let tmp = {};
     8 Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
     9 Components.utils.import("resource://gre/modules/Log.jsm", tmp);
    10 let AddonManager = tmp.AddonManager;
    11 let AddonManagerPrivate = tmp.AddonManagerPrivate;
    12 let Log = tmp.Log;
    14 var pathParts = gTestPath.split("/");
    15 // Drop the test filename
    16 pathParts.splice(pathParts.length - 1, pathParts.length);
    18 var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]);
    20 // Drop the UI type
    21 if (gTestInWindow) {
    22   pathParts.splice(pathParts.length - 1, pathParts.length);
    23 }
    25 const RELATIVE_DIR = pathParts.slice(4).join("/") + "/";
    27 const TESTROOT = "http://example.com/" + RELATIVE_DIR;
    28 const TESTROOT2 = "http://example.org/" + RELATIVE_DIR;
    29 const CHROMEROOT = pathParts.join("/") + "/";
    30 const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
    31 const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
    32 const PREF_XPI_ENABLED = "xpinstall.enabled";
    33 const PREF_UPDATEURL = "extensions.update.url";
    34 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
    36 const MANAGER_URI = "about:addons";
    37 const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
    38 const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
    39 const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults";
    40 const PREF_STRICT_COMPAT = "extensions.strictCompatibility";
    42 var PREF_CHECK_COMPATIBILITY;
    43 (function() {
    44   var channel = "default";
    45   try {
    46     channel = Services.prefs.getCharPref("app.update.channel");
    47   } catch (e) { }
    48   if (channel != "aurora" &&
    49     channel != "beta" &&
    50     channel != "release") {
    51     var version = "nightly";
    52   } else {
    53     version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1");
    54   }
    55   PREF_CHECK_COMPATIBILITY = "extensions.checkCompatibility." + version;
    56 })();
    58 var gPendingTests = [];
    59 var gTestsRun = 0;
    60 var gTestStart = null;
    62 var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window);
    64 var gRestorePrefs = [{name: PREF_LOGGING_ENABLED},
    65                      {name: "extensions.webservice.discoverURL"},
    66                      {name: "extensions.update.url"},
    67                      {name: "extensions.update.background.url"},
    68                      {name: "extensions.update.enabled"},
    69                      {name: "extensions.update.autoUpdateDefault"},
    70                      {name: "extensions.getAddons.get.url"},
    71                      {name: "extensions.getAddons.getWithPerformance.url"},
    72                      {name: "extensions.getAddons.search.browseURL"},
    73                      {name: "extensions.getAddons.search.url"},
    74                      {name: "extensions.getAddons.cache.enabled"},
    75                      {name: "devtools.chrome.enabled"},
    76                      {name: "devtools.debugger.remote-enabled"},
    77                      {name: PREF_SEARCH_MAXRESULTS},
    78                      {name: PREF_STRICT_COMPAT},
    79                      {name: PREF_CHECK_COMPATIBILITY}];
    81 for (let pref of gRestorePrefs) {
    82   if (!Services.prefs.prefHasUserValue(pref.name)) {
    83     pref.type = "clear";
    84     continue;
    85   }
    86   pref.type = Services.prefs.getPrefType(pref.name);
    87   if (pref.type == Services.prefs.PREF_BOOL)
    88     pref.value = Services.prefs.getBoolPref(pref.name);
    89   else if (pref.type == Services.prefs.PREF_INT)
    90     pref.value = Services.prefs.getIntPref(pref.name);
    91   else if (pref.type == Services.prefs.PREF_STRING)
    92     pref.value = Services.prefs.getCharPref(pref.name);
    93 }
    95 // Turn logging on for all tests
    96 Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true);
    98 // Helper to register test failures and close windows if any are left open
    99 function checkOpenWindows(aWindowID) {
   100   let windows = Services.wm.getEnumerator(aWindowID);
   101   let found = false;
   102   while (windows.hasMoreElements()) {
   103     let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
   104     if (!win.closed) {
   105       found = true;
   106       win.close();
   107     }
   108   }
   109   if (found)
   110     ok(false, "Found unexpected " + aWindowID + " window still open");
   111 }
   113 registerCleanupFunction(function() {
   114   // Restore prefs
   115   for (let pref of gRestorePrefs) {
   116     if (pref.type == "clear")
   117       Services.prefs.clearUserPref(pref.name);
   118     else if (pref.type == Services.prefs.PREF_BOOL)
   119       Services.prefs.setBoolPref(pref.name, pref.value);
   120     else if (pref.type == Services.prefs.PREF_INT)
   121       Services.prefs.setIntPref(pref.name, pref.value);
   122     else if (pref.type == Services.prefs.PREF_STRING)
   123       Services.prefs.setCharPref(pref.name, pref.value);
   124   }
   126   // Throw an error if the add-ons manager window is open anywhere
   127   checkOpenWindows("Addons:Manager");
   128   checkOpenWindows("Addons:Compatibility");
   129   checkOpenWindows("Addons:Install");
   131   return new Promise((resolve, reject) => AddonManager.getAllInstalls(resolve))
   132     .then(aInstalls => {
   133       for (let install of aInstalls) {
   134         if (install instanceof MockInstall)
   135           continue;
   137         ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state);
   138         install.cancel();
   139       }
   140     });
   141 });
   143 function log_exceptions(aCallback, ...aArgs) {
   144   try {
   145     return aCallback.apply(null, aArgs);
   146   }
   147   catch (e) {
   148     info("Exception thrown: " + e);
   149     throw e;
   150   }
   151 }
   153 function log_callback(aPromise, aCallback) {
   154   aPromise.then(aCallback)
   155     .then(null, e => info("Exception thrown: " + e));
   156   return aPromise;
   157 }
   159 function add_test(test) {
   160   gPendingTests.push(test);
   161 }
   163 function run_next_test() {
   164   if (gTestsRun > 0)
   165     info("Test " + gTestsRun + " took " + (Date.now() - gTestStart) + "ms");
   167   if (gPendingTests.length == 0) {
   168     end_test();
   169     return;
   170   }
   172   gTestsRun++;
   173   var test = gPendingTests.shift();
   174   if (test.name)
   175     info("Running test " + gTestsRun + " (" + test.name + ")");
   176   else
   177     info("Running test " + gTestsRun);
   179   gTestStart = Date.now();
   180   log_exceptions(test);
   181 }
   183 function get_addon_file_url(aFilename) {
   184   try {
   185     var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
   186              getService(Ci.nsIChromeRegistry);
   187     var fileurl = cr.convertChromeURL(makeURI(CHROMEROOT + "addons/" + aFilename));
   188     return fileurl.QueryInterface(Ci.nsIFileURL);
   189   } catch(ex) {
   190     var jar = getJar(CHROMEROOT + "addons/" + aFilename);
   191     var tmpDir = extractJarToTmp(jar);
   192     tmpDir.append(aFilename);
   194     return Services.io.newFileURI(tmpDir).QueryInterface(Ci.nsIFileURL);
   195   }
   196 }
   198 function get_test_items_in_list(aManager) {
   199   var tests = "@tests.mozilla.org";
   201   let view = aManager.document.getElementById("view-port").selectedPanel;
   202   let listid = view.id == "search-view" ? "search-list" : "addon-list";
   203   let item = aManager.document.getElementById(listid).firstChild;
   204   let items = [];
   206   while (item) {
   207     if (item.localName != "richlistitem") {
   208       item = item.nextSibling;
   209       continue;
   210     }
   212     if (!item.mAddon || item.mAddon.id.substring(item.mAddon.id.length - tests.length) == tests)
   213       items.push(item);
   214     item = item.nextSibling;
   215   }
   217   return items;
   218 }
   220 function check_all_in_list(aManager, aIds, aIgnoreExtras) {
   221   var doc = aManager.document;
   222   var view = doc.getElementById("view-port").selectedPanel;
   223   var listid = view.id == "search-view" ? "search-list" : "addon-list";
   224   var list = doc.getElementById(listid);
   226   var inlist = [];
   227   var node = list.firstChild;
   228   while (node) {
   229     if (node.value)
   230       inlist.push(node.value);
   231     node = node.nextSibling;
   232   }
   234   for (let id of aIds) {
   235     if (inlist.indexOf(id) == -1)
   236       ok(false, "Should find " + id + " in the list");
   237   }
   239   if (aIgnoreExtras)
   240     return;
   242   for (let inlistItem of inlist) {
   243     if (aIds.indexOf(inlistItem) == -1)
   244       ok(false, "Shouldn't have seen " + inlistItem + " in the list");
   245   }
   246 }
   248 function get_addon_element(aManager, aId) {
   249   var doc = aManager.document;
   250   var view = doc.getElementById("view-port").selectedPanel;
   251   var listid = "addon-list";
   252   if (view.id == "search-view")
   253     listid = "search-list";
   254   else if (view.id == "updates-view")
   255     listid = "updates-list";
   256   var list = doc.getElementById(listid);
   258   var node = list.firstChild;
   259   while (node) {
   260     if (node.value == aId)
   261       return node;
   262     node = node.nextSibling;
   263   }
   264   return null;
   265 }
   267 function wait_for_view_load(aManagerWindow, aCallback, aForceWait, aLongerTimeout) {
   268   requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2);
   270   if (!aForceWait && !aManagerWindow.gViewController.isLoading) {
   271     log_exceptions(aCallback, aManagerWindow);
   272     return;
   273   }
   275   aManagerWindow.document.addEventListener("ViewChanged", function() {
   276     aManagerWindow.document.removeEventListener("ViewChanged", arguments.callee, false);
   277     log_exceptions(aCallback, aManagerWindow);
   278   }, false);
   279 }
   281 function wait_for_manager_load(aManagerWindow, aCallback) {
   282   if (!aManagerWindow.gIsInitializing) {
   283     log_exceptions(aCallback, aManagerWindow);
   284     return;
   285   }
   287   info("Waiting for initialization");
   288   aManagerWindow.document.addEventListener("Initialized", function() {
   289     aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false);
   290     log_exceptions(aCallback, aManagerWindow);
   291   }, false);
   292 }
   294 function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) {
   295   let p = new Promise((resolve, reject) => {
   297     function setup_manager(aManagerWindow) {
   298       if (aLoadCallback)
   299         log_exceptions(aLoadCallback, aManagerWindow);
   301       if (aView)
   302         aManagerWindow.loadView(aView);
   304       ok(aManagerWindow != null, "Should have an add-ons manager window");
   305       is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI");
   307       waitForFocus(function() {
   308         info("window has focus, waiting for manager load");
   309         wait_for_manager_load(aManagerWindow, function() {
   310           info("Manager waiting for view load");
   311           wait_for_view_load(aManagerWindow, function() {
   312             resolve(aManagerWindow);
   313           }, null, aLongerTimeout);
   314         });
   315       }, aManagerWindow);
   316     }
   318     if (gUseInContentUI) {
   319       info("Loading manager window in tab");
   320       Services.obs.addObserver(function (aSubject, aTopic, aData) {
   321         Services.obs.removeObserver(arguments.callee, aTopic);
   322         if (aSubject.location.href != MANAGER_URI) {
   323           info("Ignoring load event for " + aSubject.location.href);
   324           return;
   325         }
   326         setup_manager(aSubject);
   327       }, "EM-loaded", false);
   329       gBrowser.selectedTab = gBrowser.addTab();
   330       switchToTabHavingURI(MANAGER_URI, true);
   331     } else {
   332       info("Loading manager window in dialog");
   333       Services.obs.addObserver(function (aSubject, aTopic, aData) {
   334         Services.obs.removeObserver(arguments.callee, aTopic);
   335         setup_manager(aSubject);
   336       }, "EM-loaded", false);
   338       openDialog(MANAGER_URI);
   339     }
   340   });
   342   // The promise resolves with the manager window, so it is passed to the callback
   343   return log_callback(p, aCallback);
   344 }
   346 function close_manager(aManagerWindow, aCallback, aLongerTimeout) {
   347   let p = new Promise((resolve, reject) => {
   348     requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2);
   350     ok(aManagerWindow != null, "Should have an add-ons manager window to close");
   351     is(aManagerWindow.location, MANAGER_URI, "Should be closing window with correct URI");
   353     aManagerWindow.addEventListener("unload", function() {
   354       try {
   355         dump("Manager window unload handler");
   356         this.removeEventListener("unload", arguments.callee, false);
   357         resolve();
   358       } catch(e) {
   359         reject(e);
   360       }
   361     }, false);
   362   });
   364   info("Telling manager window to close");
   365   aManagerWindow.close();
   366   info("Manager window close() call returned");
   368   return log_callback(p, aCallback);
   369 }
   371 function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) {
   372   if (!aManagerWindow) {
   373     return open_manager(aView, aCallback, aLoadCallback);
   374   }
   376   return close_manager(aManagerWindow)
   377     .then(() => open_manager(aView, aCallback, aLoadCallback));
   378 }
   380 function wait_for_window_open(aCallback) {
   381   Services.wm.addListener({
   382     onOpenWindow: function(aWindow) {
   383       Services.wm.removeListener(this);
   385       let domwindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   386                              .getInterface(Ci.nsIDOMWindow);
   387       domwindow.addEventListener("load", function() {
   388         domwindow.removeEventListener("load", arguments.callee, false);
   389         executeSoon(function() {
   390           aCallback(domwindow);
   391         });
   392       }, false);
   393     },
   395     onCloseWindow: function(aWindow) {
   396     },
   398     onWindowTitleChange: function(aWindow, aTitle) {
   399     }
   400   });
   401 }
   403 function get_string(aName, ...aArgs) {
   404   var bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/extensions.properties");
   405   if (aArgs.length == 0)
   406     return bundle.GetStringFromName(aName);
   407   return bundle.formatStringFromName(aName, aArgs, aArgs.length);
   408 }
   410 function formatDate(aDate) {
   411   return Cc["@mozilla.org/intl/scriptabledateformat;1"]
   412            .getService(Ci.nsIScriptableDateFormat)
   413            .FormatDate("",
   414                        Ci.nsIScriptableDateFormat.dateFormatLong,
   415                        aDate.getFullYear(),
   416                        aDate.getMonth() + 1,
   417                        aDate.getDate()
   418                        );
   419 }
   421 function is_hidden(aElement) {
   422   var style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, "");
   423   if (style.display == "none")
   424     return true;
   425   if (style.visibility != "visible")
   426     return true;
   428   // Hiding a parent element will hide all its children
   429   if (aElement.parentNode != aElement.ownerDocument)
   430     return is_hidden(aElement.parentNode);
   432   return false;
   433 }
   435 function is_element_visible(aElement, aMsg) {
   436   isnot(aElement, null, "Element should not be null, when checking visibility");
   437   ok(!is_hidden(aElement), aMsg);
   438 }
   440 function is_element_hidden(aElement, aMsg) {
   441   isnot(aElement, null, "Element should not be null, when checking visibility");
   442   ok(is_hidden(aElement), aMsg);
   443 }
   445 /**
   446  * Install an add-on and call a callback when complete.
   447  *
   448  * The callback will receive the Addon for the installed add-on.
   449  */
   450 function install_addon(path, cb, pathPrefix=TESTROOT) {
   451   let p = new Promise((resolve, reject) => {
   452     AddonManager.getInstallForURL(pathPrefix + path, (install) => {
   453       install.addListener({
   454         onInstallEnded: () => resolve(install.addon),
   455       });
   457       install.install();
   458     }, "application/x-xpinstall");
   459   });
   461   return log_callback(p, cb);
   462 }
   464 function CategoryUtilities(aManagerWindow) {
   465   this.window = aManagerWindow;
   467   var self = this;
   468   this.window.addEventListener("unload", function() {
   469     self.window.removeEventListener("unload", arguments.callee, false);
   470     self.window = null;
   471   }, false);
   472 }
   474 CategoryUtilities.prototype = {
   475   window: null,
   477   get selectedCategory() {
   478     isnot(this.window, null, "Should not get selected category when manager window is not loaded");
   479     var selectedItem = this.window.document.getElementById("categories").selectedItem;
   480     isnot(selectedItem, null, "A category should be selected");
   481     var view = this.window.gViewController.parseViewId(selectedItem.value);
   482     return (view.type == "list") ? view.param : view.type;
   483   },
   485   get: function(aCategoryType, aAllowMissing) {
   486     isnot(this.window, null, "Should not get category when manager window is not loaded");
   487     var categories = this.window.document.getElementById("categories");
   489     var viewId = "addons://list/" + aCategoryType;
   490     var items = categories.getElementsByAttribute("value", viewId);
   491     if (items.length)
   492       return items[0];
   494     viewId = "addons://" + aCategoryType + "/";
   495     items = categories.getElementsByAttribute("value", viewId);
   496     if (items.length)
   497       return items[0];
   499     if (!aAllowMissing)
   500       ok(false, "Should have found a category with type " + aCategoryType);
   501     return null;
   502   },
   504   getViewId: function(aCategoryType) {
   505     isnot(this.window, null, "Should not get view id when manager window is not loaded");
   506     return this.get(aCategoryType).value;
   507   },
   509   isVisible: function(aCategory) {
   510     isnot(this.window, null, "Should not check visible state when manager window is not loaded");
   511     if (aCategory.hasAttribute("disabled") &&
   512         aCategory.getAttribute("disabled") == "true")
   513       return false;
   515     return !is_hidden(aCategory);
   516   },
   518   isTypeVisible: function(aCategoryType) {
   519     return this.isVisible(this.get(aCategoryType));
   520   },
   522   open: function(aCategory, aCallback) {
   524     isnot(this.window, null, "Should not open category when manager window is not loaded");
   525     ok(this.isVisible(aCategory), "Category should be visible if attempting to open it");
   527     EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window);
   528     let p = new Promise((resolve, reject) => wait_for_view_load(this.window, resolve));
   530     return log_callback(p, aCallback);
   531   },
   533   openType: function(aCategoryType, aCallback) {
   534     return this.open(this.get(aCategoryType), aCallback);
   535   }
   536 }
   538 function CertOverrideListener(host, bits) {
   539   this.host = host;
   540   this.bits = bits;
   541 }
   543 CertOverrideListener.prototype = {
   544   host: null,
   545   bits: null,
   547   getInterface: function (aIID) {
   548     return this.QueryInterface(aIID);
   549   },
   551   QueryInterface: function(aIID) {
   552     if (aIID.equals(Ci.nsIBadCertListener2) ||
   553         aIID.equals(Ci.nsIInterfaceRequestor) ||
   554         aIID.equals(Ci.nsISupports))
   555       return this;
   557     throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE);
   558   },
   560   notifyCertProblem: function (socketInfo, sslStatus, targetHost) {
   561     var cert = sslStatus.QueryInterface(Components.interfaces.nsISSLStatus)
   562                         .serverCert;
   563     var cos = Cc["@mozilla.org/security/certoverride;1"].
   564               getService(Ci.nsICertOverrideService);
   565     cos.rememberValidityOverride(this.host, -1, cert, this.bits, false);
   566     return true;
   567   }
   568 }
   570 // Add overrides for the bad certificates
   571 function addCertOverride(host, bits) {
   572   var req = new XMLHttpRequest();
   573   try {
   574     req.open("GET", "https://" + host + "/", false);
   575     req.channel.notificationCallbacks = new CertOverrideListener(host, bits);
   576     req.send(null);
   577   }
   578   catch (e) {
   579     // This request will fail since the SSL server is not trusted yet
   580   }
   581 }
   583 /***** Mock Provider *****/
   585 function MockProvider(aUseAsyncCallbacks, aTypes) {
   586   this.addons = [];
   587   this.installs = [];
   588   this.callbackTimers = [];
   589   this.timerLocations = new Map();
   590   this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks;
   591   this.types = (aTypes === undefined) ? [{
   592     id: "extension",
   593     name: "Extensions",
   594     uiPriority: 4000,
   595     flags: AddonManager.TYPE_UI_VIEW_LIST
   596   }] : aTypes;
   598   var self = this;
   599   registerCleanupFunction(function() {
   600     if (self.started)
   601       self.unregister();
   602   });
   604   this.register();
   605 }
   607 MockProvider.prototype = {
   608   addons: null,
   609   installs: null,
   610   started: null,
   611   apiDelay: 10,
   612   callbackTimers: null,
   613   timerLocations: null,
   614   useAsyncCallbacks: null,
   615   types: null,
   617   /***** Utility functions *****/
   619   /**
   620    * Register this provider with the AddonManager
   621    */
   622   register: function MP_register() {
   623     AddonManagerPrivate.registerProvider(this, this.types);
   624   },
   626   /**
   627    * Unregister this provider with the AddonManager
   628    */
   629   unregister: function MP_unregister() {
   630     AddonManagerPrivate.unregisterProvider(this);
   631   },
   633   /**
   634    * Adds an add-on to the list of add-ons that this provider exposes to the
   635    * AddonManager, dispatching appropriate events in the process.
   636    *
   637    * @param  aAddon
   638    *         The add-on to add
   639    */
   640   addAddon: function MP_addAddon(aAddon) {
   641     var oldAddons = this.addons.filter(function(aOldAddon) aOldAddon.id == aAddon.id);
   642     var oldAddon = oldAddons.length > 0 ? oldAddons[0] : null;
   644     this.addons = this.addons.filter(function(aOldAddon) aOldAddon.id != aAddon.id);
   646     this.addons.push(aAddon);
   647     aAddon._provider = this;
   649     if (!this.started)
   650       return;
   652     let requiresRestart = (aAddon.operationsRequiringRestart &
   653                            AddonManager.OP_NEEDS_RESTART_INSTALL) != 0;
   654     AddonManagerPrivate.callInstallListeners("onExternalInstall", null, aAddon,
   655                                              oldAddon, requiresRestart)
   656   },
   658   /**
   659    * Removes an add-on from the list of add-ons that this provider exposes to
   660    * the AddonManager, dispatching the onUninstalled event in the process.
   661    *
   662    * @param  aAddon
   663    *         The add-on to add
   664    */
   665   removeAddon: function MP_removeAddon(aAddon) {
   666     var pos = this.addons.indexOf(aAddon);
   667     if (pos == -1) {
   668       ok(false, "Tried to remove an add-on that wasn't registered with the mock provider");
   669       return;
   670     }
   672     this.addons.splice(pos, 1);
   674     if (!this.started)
   675       return;
   677     AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
   678   },
   680   /**
   681    * Adds an add-on install to the list of installs that this provider exposes
   682    * to the AddonManager, dispatching appropriate events in the process.
   683    *
   684    * @param  aInstall
   685    *         The add-on install to add
   686    */
   687   addInstall: function MP_addInstall(aInstall) {
   688     this.installs.push(aInstall);
   689     aInstall._provider = this;
   691     if (!this.started)
   692       return;
   694     aInstall.callListeners("onNewInstall");
   695   },
   697   removeInstall: function MP_removeInstall(aInstall) {
   698     var pos = this.installs.indexOf(aInstall);
   699     if (pos == -1) {
   700       ok(false, "Tried to remove an install that wasn't registered with the mock provider");
   701       return;
   702     }
   704     this.installs.splice(pos, 1);
   705   },
   707   /**
   708    * Creates a set of mock add-on objects and adds them to the list of add-ons
   709    * managed by this provider.
   710    *
   711    * @param  aAddonProperties
   712    *         An array of objects containing properties describing the add-ons
   713    * @return Array of the new MockAddons
   714    */
   715   createAddons: function MP_createAddons(aAddonProperties) {
   716     var newAddons = [];
   717     for (let addonProp of aAddonProperties) {
   718       let addon = new MockAddon(addonProp.id);
   719       for (let prop in addonProp) {
   720         if (prop == "id")
   721           continue;
   722         if (prop == "applyBackgroundUpdates") {
   723           addon._applyBackgroundUpdates = addonProp[prop];
   724           continue;
   725         }
   726         if (prop == "appDisabled") {
   727           addon._appDisabled = addonProp[prop];
   728           continue;
   729         }
   730         addon[prop] = addonProp[prop];
   731       }
   732       if (!addon.optionsType && !!addon.optionsURL)
   733         addon.optionsType = AddonManager.OPTIONS_TYPE_DIALOG;
   735       // Make sure the active state matches the passed in properties
   736       addon.isActive = addon.shouldBeActive;
   738       this.addAddon(addon);
   739       newAddons.push(addon);
   740     }
   742     return newAddons;
   743   },
   745   /**
   746    * Creates a set of mock add-on install objects and adds them to the list
   747    * of installs managed by this provider.
   748    *
   749    * @param  aInstallProperties
   750    *         An array of objects containing properties describing the installs
   751    * @return Array of the new MockInstalls
   752    */
   753   createInstalls: function MP_createInstalls(aInstallProperties) {
   754     var newInstalls = [];
   755     for (let installProp of aInstallProperties) {
   756       let install = new MockInstall(installProp.name || null,
   757                                     installProp.type || null,
   758                                     null);
   759       for (let prop in installProp) {
   760         switch (prop) {
   761           case "name":
   762           case "type":
   763             break;
   764           case "sourceURI":
   765             install[prop] = NetUtil.newURI(installProp[prop]);
   766             break;
   767           default:
   768             install[prop] = installProp[prop];
   769         }
   770       }
   771       this.addInstall(install);
   772       newInstalls.push(install);
   773     }
   775     return newInstalls;
   776   },
   778   /***** AddonProvider implementation *****/
   780   /**
   781    * Called to initialize the provider.
   782    */
   783   startup: function MP_startup() {
   784     this.started = true;
   785   },
   787   /**
   788    * Called when the provider should shutdown.
   789    */
   790   shutdown: function MP_shutdown() {
   791     if (this.callbackTimers.length) {
   792       info("MockProvider: pending callbacks at shutdown(): calling immediately");
   793     }
   794     while (this.callbackTimers.length > 0) {
   795       // When we notify the callback timer, it removes itself from our array
   796       let timer = this.callbackTimers[0];
   797       try {
   798         let setAt = this.timerLocations.get(timer);
   799         info("Notifying timer set at " + (setAt || "unknown location"));
   800         timer.callback.notify(timer);
   801         timer.cancel();
   802       } catch(e) {
   803         info("Timer notify failed: " + e);
   804       }
   805     }
   806     this.callbackTimers = [];
   807     this.timerLocations = null;
   809     this.started = false;
   810   },
   812   /**
   813    * Called to get an Addon with a particular ID.
   814    *
   815    * @param  aId
   816    *         The ID of the add-on to retrieve
   817    * @param  aCallback
   818    *         A callback to pass the Addon to
   819    */
   820   getAddonByID: function MP_getAddon(aId, aCallback) {
   821     for (let addon of this.addons) {
   822       if (addon.id == aId) {
   823         this._delayCallback(aCallback, addon);
   824         return;
   825       }
   826     }
   828     aCallback(null);
   829   },
   831   /**
   832    * Called to get Addons of a particular type.
   833    *
   834    * @param  aTypes
   835    *         An array of types to fetch. Can be null to get all types.
   836    * @param  callback
   837    *         A callback to pass an array of Addons to
   838    */
   839   getAddonsByTypes: function MP_getAddonsByTypes(aTypes, aCallback) {
   840     var addons = this.addons.filter(function(aAddon) {
   841       if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1)
   842         return false;
   843       return true;
   844     });
   845     this._delayCallback(aCallback, addons);
   846   },
   848   /**
   849    * Called to get Addons that have pending operations.
   850    *
   851    * @param  aTypes
   852    *         An array of types to fetch. Can be null to get all types
   853    * @param  aCallback
   854    *         A callback to pass an array of Addons to
   855    */
   856   getAddonsWithOperationsByTypes: function MP_getAddonsWithOperationsByTypes(aTypes, aCallback) {
   857     var addons = this.addons.filter(function(aAddon) {
   858       if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1)
   859         return false;
   860       return aAddon.pendingOperations != 0;
   861     });
   862     this._delayCallback(aCallback, addons);
   863   },
   865   /**
   866    * Called to get the current AddonInstalls, optionally restricting by type.
   867    *
   868    * @param  aTypes
   869    *         An array of types or null to get all types
   870    * @param  aCallback
   871    *         A callback to pass the array of AddonInstalls to
   872    */
   873   getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) {
   874     var installs = this.installs.filter(function(aInstall) {
   875       // Appear to have actually removed cancelled installs from the provider
   876       if (aInstall.state == AddonManager.STATE_CANCELLED)
   877         return false;
   879       if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1)
   880         return false;
   882       return true;
   883     });
   884     this._delayCallback(aCallback, installs);
   885   },
   887   /**
   888    * Called when a new add-on has been enabled when only one add-on of that type
   889    * can be enabled.
   890    *
   891    * @param  aId
   892    *         The ID of the newly enabled add-on
   893    * @param  aType
   894    *         The type of the newly enabled add-on
   895    * @param  aPendingRestart
   896    *         true if the newly enabled add-on will only become enabled after a
   897    *         restart
   898    */
   899   addonChanged: function MP_addonChanged(aId, aType, aPendingRestart) {
   900     // Not implemented
   901   },
   903   /**
   904    * Update the appDisabled property for all add-ons.
   905    */
   906   updateAddonAppDisabledStates: function MP_updateAddonAppDisabledStates() {
   907     // Not needed
   908   },
   910   /**
   911    * Called to get an AddonInstall to download and install an add-on from a URL.
   912    *
   913    * @param  aUrl
   914    *         The URL to be installed
   915    * @param  aHash
   916    *         A hash for the install
   917    * @param  aName
   918    *         A name for the install
   919    * @param  aIconURL
   920    *         An icon URL for the install
   921    * @param  aVersion
   922    *         A version for the install
   923    * @param  aLoadGroup
   924    *         An nsILoadGroup to associate requests with
   925    * @param  aCallback
   926    *         A callback to pass the AddonInstall to
   927    */
   928   getInstallForURL: function MP_getInstallForURL(aUrl, aHash, aName, aIconURL,
   929                                                   aVersion, aLoadGroup, aCallback) {
   930     // Not yet implemented
   931   },
   933   /**
   934    * Called to get an AddonInstall to install an add-on from a local file.
   935    *
   936    * @param  aFile
   937    *         The file to be installed
   938    * @param  aCallback
   939    *         A callback to pass the AddonInstall to
   940    */
   941   getInstallForFile: function MP_getInstallForFile(aFile, aCallback) {
   942     // Not yet implemented
   943   },
   945   /**
   946    * Called to test whether installing add-ons is enabled.
   947    *
   948    * @return true if installing is enabled
   949    */
   950   isInstallEnabled: function MP_isInstallEnabled() {
   951     return false;
   952   },
   954   /**
   955    * Called to test whether this provider supports installing a particular
   956    * mimetype.
   957    *
   958    * @param  aMimetype
   959    *         The mimetype to check for
   960    * @return true if the mimetype is supported
   961    */
   962   supportsMimetype: function MP_supportsMimetype(aMimetype) {
   963     return false;
   964   },
   966   /**
   967    * Called to test whether installing add-ons from a URI is allowed.
   968    *
   969    * @param  aUri
   970    *         The URI being installed from
   971    * @return true if installing is allowed
   972    */
   973   isInstallAllowed: function MP_isInstallAllowed(aUri) {
   974     return false;
   975   },
   978   /***** Internal functions *****/
   980   /**
   981    * Delay calling a callback to fake a time-consuming async operation.
   982    * The delay is specified by the apiDelay property, in milliseconds.
   983    * Parameters to send to the callback should be specified as arguments after
   984    * the aCallback argument.
   985    *
   986    * @param aCallback Callback to eventually call
   987    */
   988   _delayCallback: function MP_delayCallback(aCallback, ...aArgs) {
   989     if (!this.useAsyncCallbacks) {
   990       aCallback(...aArgs);
   991       return;
   992     }
   994     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   995     // Need to keep a reference to the timer, so it doesn't get GC'ed
   996     this.callbackTimers.push(timer);
   997     // Capture a stack trace where the timer was set
   998     // needs the 'new Error' hack until bug 1007656
   999     this.timerLocations.set(timer, Log.stackTrace(new Error("dummy")));
  1000     timer.initWithCallback(() => {
  1001       let idx = this.callbackTimers.indexOf(timer);
  1002       if (idx == -1) {
  1003         dump("MockProvider._delayCallback lost track of timer set at "
  1004              + (this.timerLocations.get(timer) || "unknown location") + "\n");
  1005       } else {
  1006         this.callbackTimers.splice(idx, 1);
  1008       this.timerLocations.delete(timer);
  1009       aCallback(...aArgs);
  1010     }, this.apiDelay, timer.TYPE_ONE_SHOT);
  1012 };
  1014 /***** Mock Addon object for the Mock Provider *****/
  1016 function MockAddon(aId, aName, aType, aOperationsRequiringRestart) {
  1017   // Only set required attributes.
  1018   this.id = aId || "";
  1019   this.name = aName || "";
  1020   this.type = aType || "extension";
  1021   this.version = "";
  1022   this.isCompatible = true;
  1023   this.isDebuggable = false;
  1024   this.providesUpdatesSecurely = true;
  1025   this.blocklistState = 0;
  1026   this._appDisabled = false;
  1027   this._userDisabled = false;
  1028   this._applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
  1029   this.scope = AddonManager.SCOPE_PROFILE;
  1030   this.isActive = true;
  1031   this.creator = "";
  1032   this.pendingOperations = 0;
  1033   this._permissions = AddonManager.PERM_CAN_UNINSTALL |
  1034                       AddonManager.PERM_CAN_ENABLE |
  1035                       AddonManager.PERM_CAN_DISABLE |
  1036                       AddonManager.PERM_CAN_UPGRADE;
  1037   this.operationsRequiringRestart = aOperationsRequiringRestart ||
  1038     (AddonManager.OP_NEEDS_RESTART_INSTALL |
  1039      AddonManager.OP_NEEDS_RESTART_UNINSTALL |
  1040      AddonManager.OP_NEEDS_RESTART_ENABLE |
  1041      AddonManager.OP_NEEDS_RESTART_DISABLE);
  1044 MockAddon.prototype = {
  1045   get shouldBeActive() {
  1046     return !this.appDisabled && !this._userDisabled;
  1047   },
  1049   get appDisabled() {
  1050     return this._appDisabled;
  1051   },
  1053   set appDisabled(val) {
  1054     if (val == this._appDisabled)
  1055       return val;
  1057     AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["appDisabled"]);
  1059     var currentActive = this.shouldBeActive;
  1060     this._appDisabled = val;
  1061     var newActive = this.shouldBeActive;
  1062     this._updateActiveState(currentActive, newActive);
  1064     return val;
  1065   },
  1067   get userDisabled() {
  1068     return this._userDisabled;
  1069   },
  1071   set userDisabled(val) {
  1072     if (val == this._userDisabled)
  1073       return val;
  1075     var currentActive = this.shouldBeActive;
  1076     this._userDisabled = val;
  1077     var newActive = this.shouldBeActive;
  1078     this._updateActiveState(currentActive, newActive);
  1080     return val;
  1081   },
  1083   get permissions() {
  1084     let permissions = this._permissions;
  1085     if (this.appDisabled || !this._userDisabled)
  1086       permissions &= ~AddonManager.PERM_CAN_ENABLE;
  1087     if (this.appDisabled || this._userDisabled)
  1088       permissions &= ~AddonManager.PERM_CAN_DISABLE;
  1089     return permissions;
  1090   },
  1092   set permissions(val) {
  1093     return this._permissions = val;
  1094   },
  1096   get applyBackgroundUpdates() {
  1097     return this._applyBackgroundUpdates;
  1098   },
  1100   set applyBackgroundUpdates(val) {
  1101     if (val != AddonManager.AUTOUPDATE_DEFAULT &&
  1102         val != AddonManager.AUTOUPDATE_DISABLE &&
  1103         val != AddonManager.AUTOUPDATE_ENABLE) {
  1104       ok(false, "addon.applyBackgroundUpdates set to an invalid value: " + val);
  1106     this._applyBackgroundUpdates = val;
  1107     AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]);
  1108   },
  1110   isCompatibleWith: function(aAppVersion, aPlatformVersion) {
  1111     return true;
  1112   },
  1114   findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
  1115     // Tests can implement this if they need to
  1116   },
  1118   uninstall: function() {
  1119     if (this.pendingOperations & AddonManager.PENDING_UNINSTALL)
  1120       throw Components.Exception("Add-on is already pending uninstall");
  1122     var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL);
  1123     this.pendingOperations |= AddonManager.PENDING_UNINSTALL;
  1124     AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart);
  1125     if (!needsRestart) {
  1126       this.pendingOperations -= AddonManager.PENDING_UNINSTALL;
  1127       this._provider.removeAddon(this);
  1129   },
  1131   cancelUninstall: function() {
  1132     if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL))
  1133       throw Components.Exception("Add-on is not pending uninstall");
  1135     this.pendingOperations -= AddonManager.PENDING_UNINSTALL;
  1136     AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
  1137   },
  1139   _updateActiveState: function(currentActive, newActive) {
  1140     if (currentActive == newActive)
  1141       return;
  1143     if (newActive == this.isActive) {
  1144       this.pendingOperations -= (newActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE);
  1145       AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
  1147     else if (newActive) {
  1148       var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE);
  1149       this.pendingOperations |= AddonManager.PENDING_ENABLE;
  1150       AddonManagerPrivate.callAddonListeners("onEnabling", this, needsRestart);
  1151       if (!needsRestart) {
  1152         this.isActive = newActive;
  1153         this.pendingOperations -= AddonManager.PENDING_ENABLE;
  1154         AddonManagerPrivate.callAddonListeners("onEnabled", this);
  1157     else {
  1158       var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE);
  1159       this.pendingOperations |= AddonManager.PENDING_DISABLE;
  1160       AddonManagerPrivate.callAddonListeners("onDisabling", this, needsRestart);
  1161       if (!needsRestart) {
  1162         this.isActive = newActive;
  1163         this.pendingOperations -= AddonManager.PENDING_DISABLE;
  1164         AddonManagerPrivate.callAddonListeners("onDisabled", this);
  1168 };
  1170 /***** Mock AddonInstall object for the Mock Provider *****/
  1172 function MockInstall(aName, aType, aAddonToInstall) {
  1173   this.name = aName || "";
  1174   // Don't expose type until download completed
  1175   this._type = aType || "extension";
  1176   this.type = null;
  1177   this.version = "1.0";
  1178   this.iconURL = "";
  1179   this.infoURL = "";
  1180   this.state = AddonManager.STATE_AVAILABLE;
  1181   this.error = 0;
  1182   this.sourceURI = null;
  1183   this.file = null;
  1184   this.progress = 0;
  1185   this.maxProgress = -1;
  1186   this.certificate = null;
  1187   this.certName = "";
  1188   this.existingAddon = null;
  1189   this.addon = null;
  1190   this._addonToInstall = aAddonToInstall;
  1191   this.listeners = [];
  1193   // Another type of install listener for tests that want to check the results
  1194   // of code run from standard install listeners
  1195   this.testListeners = [];
  1198 MockInstall.prototype = {
  1199   install: function() {
  1200     switch (this.state) {
  1201       case AddonManager.STATE_AVAILABLE:
  1202         this.state = AddonManager.STATE_DOWNLOADING;
  1203         if (!this.callListeners("onDownloadStarted")) {
  1204           this.state = AddonManager.STATE_CANCELLED;
  1205           this.callListeners("onDownloadCancelled");
  1206           return;
  1209         this.type = this._type;
  1211         // Adding addon to MockProvider to be implemented when needed
  1212         if (this._addonToInstall)
  1213           this.addon = this._addonToInstall;
  1214         else {
  1215           this.addon = new MockAddon("", this.name, this.type);
  1216           this.addon.version = this.version;
  1217           this.addon.pendingOperations = AddonManager.PENDING_INSTALL;
  1219         this.addon.install = this;
  1220         if (this.existingAddon) {
  1221           if (!this.addon.id)
  1222             this.addon.id = this.existingAddon.id;
  1223           this.existingAddon.pendingUpgrade = this.addon;
  1224           this.existingAddon.pendingOperations |= AddonManager.PENDING_UPGRADE;
  1227         this.state = AddonManager.STATE_DOWNLOADED;
  1228         this.callListeners("onDownloadEnded");
  1230       case AddonManager.STATE_DOWNLOADED:
  1231         this.state = AddonManager.STATE_INSTALLING;
  1232         if (!this.callListeners("onInstallStarted")) {
  1233           this.state = AddonManager.STATE_CANCELLED;
  1234           this.callListeners("onInstallCancelled");
  1235           return;
  1238         AddonManagerPrivate.callAddonListeners("onInstalling", this.addon);
  1240         this.state = AddonManager.STATE_INSTALLED;
  1241         this.callListeners("onInstallEnded");
  1242         break;
  1243       case AddonManager.STATE_DOWNLOADING:
  1244       case AddonManager.STATE_CHECKING:
  1245       case AddonManger.STATE_INSTALLING:
  1246         // Installation is already running
  1247         return;
  1248       default:
  1249         ok(false, "Cannot start installing when state = " + this.state);
  1251   },
  1253   cancel: function() {
  1254     switch (this.state) {
  1255       case AddonManager.STATE_AVAILABLE:
  1256         this.state = AddonManager.STATE_CANCELLED;
  1257         break;
  1258       case AddonManager.STATE_INSTALLED:
  1259         this.state = AddonManager.STATE_CANCELLED;
  1260         this._provider.removeInstall(this);
  1261         this.callListeners("onInstallCancelled");
  1262         break;
  1263       default:
  1264         // Handling cancelling when downloading to be implemented when needed
  1265         ok(false, "Cannot cancel when state = " + this.state);
  1267   },
  1270   addListener: function(aListener) {
  1271     if (!this.listeners.some(function(i) i == aListener))
  1272       this.listeners.push(aListener);
  1273   },
  1275   removeListener: function(aListener) {
  1276     this.listeners = this.listeners.filter(function(i) i != aListener);
  1277   },
  1279   addTestListener: function(aListener) {
  1280     if (!this.testListeners.some(function(i) i == aListener))
  1281       this.testListeners.push(aListener);
  1282   },
  1284   removeTestListener: function(aListener) {
  1285     this.testListeners = this.testListeners.filter(function(i) i != aListener);
  1286   },
  1288   callListeners: function(aMethod) {
  1289     var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners,
  1290                                                           this, this.addon);
  1292     // Call test listeners after standard listeners to remove race condition
  1293     // between standard and test listeners
  1294     for (let listener of this.testListeners) {
  1295       try {
  1296         if (aMethod in listener)
  1297           if (listener[aMethod].call(listener, this, this.addon) === false)
  1298             result = false;
  1300       catch (e) {
  1301         ok(false, "Test listener threw exception: " + e);
  1305     return result;
  1307 };
  1309 function waitForCondition(condition, nextTest, errorMsg) {
  1310   let tries = 0;
  1311   let interval = setInterval(function() {
  1312     if (tries >= 30) {
  1313       ok(false, errorMsg);
  1314       moveOn();
  1316     var conditionPassed;
  1317     try {
  1318       conditionPassed = condition();
  1319     } catch (e) {
  1320       ok(false, e + "\n" + e.stack);
  1321       conditionPassed = false;
  1323     if (conditionPassed) {
  1324       moveOn();
  1326     tries++;
  1327   }, 100);
  1328   let moveOn = function() { clearInterval(interval); nextTest(); };
  1331 function getTestPluginTag() {
  1332   let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
  1333   let tags = ph.getPluginTags();
  1335   // Find the test plugin
  1336   for (let i = 0; i < tags.length; i++) {
  1337     if (tags[i].name == "Test Plug-in")
  1338       return tags[i];
  1340   ok(false, "Unable to find plugin");
  1341   return null;

mercurial