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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/mozapps/extensions/test/browser/head.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1342 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 + * http://creativecommons.org/publicdomain/zero/1.0/
     1.6 + */
     1.7 +
     1.8 +Components.utils.import("resource://gre/modules/NetUtil.jsm");
     1.9 +
    1.10 +let tmp = {};
    1.11 +Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
    1.12 +Components.utils.import("resource://gre/modules/Log.jsm", tmp);
    1.13 +let AddonManager = tmp.AddonManager;
    1.14 +let AddonManagerPrivate = tmp.AddonManagerPrivate;
    1.15 +let Log = tmp.Log;
    1.16 +
    1.17 +var pathParts = gTestPath.split("/");
    1.18 +// Drop the test filename
    1.19 +pathParts.splice(pathParts.length - 1, pathParts.length);
    1.20 +
    1.21 +var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]);
    1.22 +
    1.23 +// Drop the UI type
    1.24 +if (gTestInWindow) {
    1.25 +  pathParts.splice(pathParts.length - 1, pathParts.length);
    1.26 +}
    1.27 +
    1.28 +const RELATIVE_DIR = pathParts.slice(4).join("/") + "/";
    1.29 +
    1.30 +const TESTROOT = "http://example.com/" + RELATIVE_DIR;
    1.31 +const TESTROOT2 = "http://example.org/" + RELATIVE_DIR;
    1.32 +const CHROMEROOT = pathParts.join("/") + "/";
    1.33 +const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
    1.34 +const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
    1.35 +const PREF_XPI_ENABLED = "xpinstall.enabled";
    1.36 +const PREF_UPDATEURL = "extensions.update.url";
    1.37 +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
    1.38 +
    1.39 +const MANAGER_URI = "about:addons";
    1.40 +const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
    1.41 +const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
    1.42 +const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults";
    1.43 +const PREF_STRICT_COMPAT = "extensions.strictCompatibility";
    1.44 +
    1.45 +var PREF_CHECK_COMPATIBILITY;
    1.46 +(function() {
    1.47 +  var channel = "default";
    1.48 +  try {
    1.49 +    channel = Services.prefs.getCharPref("app.update.channel");
    1.50 +  } catch (e) { }
    1.51 +  if (channel != "aurora" &&
    1.52 +    channel != "beta" &&
    1.53 +    channel != "release") {
    1.54 +    var version = "nightly";
    1.55 +  } else {
    1.56 +    version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1");
    1.57 +  }
    1.58 +  PREF_CHECK_COMPATIBILITY = "extensions.checkCompatibility." + version;
    1.59 +})();
    1.60 +
    1.61 +var gPendingTests = [];
    1.62 +var gTestsRun = 0;
    1.63 +var gTestStart = null;
    1.64 +
    1.65 +var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window);
    1.66 +
    1.67 +var gRestorePrefs = [{name: PREF_LOGGING_ENABLED},
    1.68 +                     {name: "extensions.webservice.discoverURL"},
    1.69 +                     {name: "extensions.update.url"},
    1.70 +                     {name: "extensions.update.background.url"},
    1.71 +                     {name: "extensions.update.enabled"},
    1.72 +                     {name: "extensions.update.autoUpdateDefault"},
    1.73 +                     {name: "extensions.getAddons.get.url"},
    1.74 +                     {name: "extensions.getAddons.getWithPerformance.url"},
    1.75 +                     {name: "extensions.getAddons.search.browseURL"},
    1.76 +                     {name: "extensions.getAddons.search.url"},
    1.77 +                     {name: "extensions.getAddons.cache.enabled"},
    1.78 +                     {name: "devtools.chrome.enabled"},
    1.79 +                     {name: "devtools.debugger.remote-enabled"},
    1.80 +                     {name: PREF_SEARCH_MAXRESULTS},
    1.81 +                     {name: PREF_STRICT_COMPAT},
    1.82 +                     {name: PREF_CHECK_COMPATIBILITY}];
    1.83 +
    1.84 +for (let pref of gRestorePrefs) {
    1.85 +  if (!Services.prefs.prefHasUserValue(pref.name)) {
    1.86 +    pref.type = "clear";
    1.87 +    continue;
    1.88 +  }
    1.89 +  pref.type = Services.prefs.getPrefType(pref.name);
    1.90 +  if (pref.type == Services.prefs.PREF_BOOL)
    1.91 +    pref.value = Services.prefs.getBoolPref(pref.name);
    1.92 +  else if (pref.type == Services.prefs.PREF_INT)
    1.93 +    pref.value = Services.prefs.getIntPref(pref.name);
    1.94 +  else if (pref.type == Services.prefs.PREF_STRING)
    1.95 +    pref.value = Services.prefs.getCharPref(pref.name);
    1.96 +}
    1.97 +
    1.98 +// Turn logging on for all tests
    1.99 +Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true);
   1.100 +
   1.101 +// Helper to register test failures and close windows if any are left open
   1.102 +function checkOpenWindows(aWindowID) {
   1.103 +  let windows = Services.wm.getEnumerator(aWindowID);
   1.104 +  let found = false;
   1.105 +  while (windows.hasMoreElements()) {
   1.106 +    let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
   1.107 +    if (!win.closed) {
   1.108 +      found = true;
   1.109 +      win.close();
   1.110 +    }
   1.111 +  }
   1.112 +  if (found)
   1.113 +    ok(false, "Found unexpected " + aWindowID + " window still open");
   1.114 +}
   1.115 +
   1.116 +registerCleanupFunction(function() {
   1.117 +  // Restore prefs
   1.118 +  for (let pref of gRestorePrefs) {
   1.119 +    if (pref.type == "clear")
   1.120 +      Services.prefs.clearUserPref(pref.name);
   1.121 +    else if (pref.type == Services.prefs.PREF_BOOL)
   1.122 +      Services.prefs.setBoolPref(pref.name, pref.value);
   1.123 +    else if (pref.type == Services.prefs.PREF_INT)
   1.124 +      Services.prefs.setIntPref(pref.name, pref.value);
   1.125 +    else if (pref.type == Services.prefs.PREF_STRING)
   1.126 +      Services.prefs.setCharPref(pref.name, pref.value);
   1.127 +  }
   1.128 +
   1.129 +  // Throw an error if the add-ons manager window is open anywhere
   1.130 +  checkOpenWindows("Addons:Manager");
   1.131 +  checkOpenWindows("Addons:Compatibility");
   1.132 +  checkOpenWindows("Addons:Install");
   1.133 +
   1.134 +  return new Promise((resolve, reject) => AddonManager.getAllInstalls(resolve))
   1.135 +    .then(aInstalls => {
   1.136 +      for (let install of aInstalls) {
   1.137 +        if (install instanceof MockInstall)
   1.138 +          continue;
   1.139 +
   1.140 +        ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state);
   1.141 +        install.cancel();
   1.142 +      }
   1.143 +    });
   1.144 +});
   1.145 +
   1.146 +function log_exceptions(aCallback, ...aArgs) {
   1.147 +  try {
   1.148 +    return aCallback.apply(null, aArgs);
   1.149 +  }
   1.150 +  catch (e) {
   1.151 +    info("Exception thrown: " + e);
   1.152 +    throw e;
   1.153 +  }
   1.154 +}
   1.155 +
   1.156 +function log_callback(aPromise, aCallback) {
   1.157 +  aPromise.then(aCallback)
   1.158 +    .then(null, e => info("Exception thrown: " + e));
   1.159 +  return aPromise;
   1.160 +}
   1.161 +
   1.162 +function add_test(test) {
   1.163 +  gPendingTests.push(test);
   1.164 +}
   1.165 +
   1.166 +function run_next_test() {
   1.167 +  if (gTestsRun > 0)
   1.168 +    info("Test " + gTestsRun + " took " + (Date.now() - gTestStart) + "ms");
   1.169 +
   1.170 +  if (gPendingTests.length == 0) {
   1.171 +    end_test();
   1.172 +    return;
   1.173 +  }
   1.174 +
   1.175 +  gTestsRun++;
   1.176 +  var test = gPendingTests.shift();
   1.177 +  if (test.name)
   1.178 +    info("Running test " + gTestsRun + " (" + test.name + ")");
   1.179 +  else
   1.180 +    info("Running test " + gTestsRun);
   1.181 +
   1.182 +  gTestStart = Date.now();
   1.183 +  log_exceptions(test);
   1.184 +}
   1.185 +
   1.186 +function get_addon_file_url(aFilename) {
   1.187 +  try {
   1.188 +    var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
   1.189 +             getService(Ci.nsIChromeRegistry);
   1.190 +    var fileurl = cr.convertChromeURL(makeURI(CHROMEROOT + "addons/" + aFilename));
   1.191 +    return fileurl.QueryInterface(Ci.nsIFileURL);
   1.192 +  } catch(ex) {
   1.193 +    var jar = getJar(CHROMEROOT + "addons/" + aFilename);
   1.194 +    var tmpDir = extractJarToTmp(jar);
   1.195 +    tmpDir.append(aFilename);
   1.196 +
   1.197 +    return Services.io.newFileURI(tmpDir).QueryInterface(Ci.nsIFileURL);
   1.198 +  }
   1.199 +}
   1.200 +
   1.201 +function get_test_items_in_list(aManager) {
   1.202 +  var tests = "@tests.mozilla.org";
   1.203 +
   1.204 +  let view = aManager.document.getElementById("view-port").selectedPanel;
   1.205 +  let listid = view.id == "search-view" ? "search-list" : "addon-list";
   1.206 +  let item = aManager.document.getElementById(listid).firstChild;
   1.207 +  let items = [];
   1.208 +
   1.209 +  while (item) {
   1.210 +    if (item.localName != "richlistitem") {
   1.211 +      item = item.nextSibling;
   1.212 +      continue;
   1.213 +    }
   1.214 +
   1.215 +    if (!item.mAddon || item.mAddon.id.substring(item.mAddon.id.length - tests.length) == tests)
   1.216 +      items.push(item);
   1.217 +    item = item.nextSibling;
   1.218 +  }
   1.219 +
   1.220 +  return items;
   1.221 +}
   1.222 +
   1.223 +function check_all_in_list(aManager, aIds, aIgnoreExtras) {
   1.224 +  var doc = aManager.document;
   1.225 +  var view = doc.getElementById("view-port").selectedPanel;
   1.226 +  var listid = view.id == "search-view" ? "search-list" : "addon-list";
   1.227 +  var list = doc.getElementById(listid);
   1.228 +
   1.229 +  var inlist = [];
   1.230 +  var node = list.firstChild;
   1.231 +  while (node) {
   1.232 +    if (node.value)
   1.233 +      inlist.push(node.value);
   1.234 +    node = node.nextSibling;
   1.235 +  }
   1.236 +
   1.237 +  for (let id of aIds) {
   1.238 +    if (inlist.indexOf(id) == -1)
   1.239 +      ok(false, "Should find " + id + " in the list");
   1.240 +  }
   1.241 +
   1.242 +  if (aIgnoreExtras)
   1.243 +    return;
   1.244 +
   1.245 +  for (let inlistItem of inlist) {
   1.246 +    if (aIds.indexOf(inlistItem) == -1)
   1.247 +      ok(false, "Shouldn't have seen " + inlistItem + " in the list");
   1.248 +  }
   1.249 +}
   1.250 +
   1.251 +function get_addon_element(aManager, aId) {
   1.252 +  var doc = aManager.document;
   1.253 +  var view = doc.getElementById("view-port").selectedPanel;
   1.254 +  var listid = "addon-list";
   1.255 +  if (view.id == "search-view")
   1.256 +    listid = "search-list";
   1.257 +  else if (view.id == "updates-view")
   1.258 +    listid = "updates-list";
   1.259 +  var list = doc.getElementById(listid);
   1.260 +
   1.261 +  var node = list.firstChild;
   1.262 +  while (node) {
   1.263 +    if (node.value == aId)
   1.264 +      return node;
   1.265 +    node = node.nextSibling;
   1.266 +  }
   1.267 +  return null;
   1.268 +}
   1.269 +
   1.270 +function wait_for_view_load(aManagerWindow, aCallback, aForceWait, aLongerTimeout) {
   1.271 +  requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2);
   1.272 +
   1.273 +  if (!aForceWait && !aManagerWindow.gViewController.isLoading) {
   1.274 +    log_exceptions(aCallback, aManagerWindow);
   1.275 +    return;
   1.276 +  }
   1.277 +
   1.278 +  aManagerWindow.document.addEventListener("ViewChanged", function() {
   1.279 +    aManagerWindow.document.removeEventListener("ViewChanged", arguments.callee, false);
   1.280 +    log_exceptions(aCallback, aManagerWindow);
   1.281 +  }, false);
   1.282 +}
   1.283 +
   1.284 +function wait_for_manager_load(aManagerWindow, aCallback) {
   1.285 +  if (!aManagerWindow.gIsInitializing) {
   1.286 +    log_exceptions(aCallback, aManagerWindow);
   1.287 +    return;
   1.288 +  }
   1.289 +
   1.290 +  info("Waiting for initialization");
   1.291 +  aManagerWindow.document.addEventListener("Initialized", function() {
   1.292 +    aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false);
   1.293 +    log_exceptions(aCallback, aManagerWindow);
   1.294 +  }, false);
   1.295 +}
   1.296 +
   1.297 +function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) {
   1.298 +  let p = new Promise((resolve, reject) => {
   1.299 +
   1.300 +    function setup_manager(aManagerWindow) {
   1.301 +      if (aLoadCallback)
   1.302 +        log_exceptions(aLoadCallback, aManagerWindow);
   1.303 +
   1.304 +      if (aView)
   1.305 +        aManagerWindow.loadView(aView);
   1.306 +
   1.307 +      ok(aManagerWindow != null, "Should have an add-ons manager window");
   1.308 +      is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI");
   1.309 +
   1.310 +      waitForFocus(function() {
   1.311 +        info("window has focus, waiting for manager load");
   1.312 +        wait_for_manager_load(aManagerWindow, function() {
   1.313 +          info("Manager waiting for view load");
   1.314 +          wait_for_view_load(aManagerWindow, function() {
   1.315 +            resolve(aManagerWindow);
   1.316 +          }, null, aLongerTimeout);
   1.317 +        });
   1.318 +      }, aManagerWindow);
   1.319 +    }
   1.320 +
   1.321 +    if (gUseInContentUI) {
   1.322 +      info("Loading manager window in tab");
   1.323 +      Services.obs.addObserver(function (aSubject, aTopic, aData) {
   1.324 +        Services.obs.removeObserver(arguments.callee, aTopic);
   1.325 +        if (aSubject.location.href != MANAGER_URI) {
   1.326 +          info("Ignoring load event for " + aSubject.location.href);
   1.327 +          return;
   1.328 +        }
   1.329 +        setup_manager(aSubject);
   1.330 +      }, "EM-loaded", false);
   1.331 +
   1.332 +      gBrowser.selectedTab = gBrowser.addTab();
   1.333 +      switchToTabHavingURI(MANAGER_URI, true);
   1.334 +    } else {
   1.335 +      info("Loading manager window in dialog");
   1.336 +      Services.obs.addObserver(function (aSubject, aTopic, aData) {
   1.337 +        Services.obs.removeObserver(arguments.callee, aTopic);
   1.338 +        setup_manager(aSubject);
   1.339 +      }, "EM-loaded", false);
   1.340 +
   1.341 +      openDialog(MANAGER_URI);
   1.342 +    }
   1.343 +  });
   1.344 +
   1.345 +  // The promise resolves with the manager window, so it is passed to the callback
   1.346 +  return log_callback(p, aCallback);
   1.347 +}
   1.348 +
   1.349 +function close_manager(aManagerWindow, aCallback, aLongerTimeout) {
   1.350 +  let p = new Promise((resolve, reject) => {
   1.351 +    requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2);
   1.352 +
   1.353 +    ok(aManagerWindow != null, "Should have an add-ons manager window to close");
   1.354 +    is(aManagerWindow.location, MANAGER_URI, "Should be closing window with correct URI");
   1.355 +
   1.356 +    aManagerWindow.addEventListener("unload", function() {
   1.357 +      try {
   1.358 +        dump("Manager window unload handler");
   1.359 +        this.removeEventListener("unload", arguments.callee, false);
   1.360 +        resolve();
   1.361 +      } catch(e) {
   1.362 +        reject(e);
   1.363 +      }
   1.364 +    }, false);
   1.365 +  });
   1.366 +
   1.367 +  info("Telling manager window to close");
   1.368 +  aManagerWindow.close();
   1.369 +  info("Manager window close() call returned");
   1.370 +
   1.371 +  return log_callback(p, aCallback);
   1.372 +}
   1.373 +
   1.374 +function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) {
   1.375 +  if (!aManagerWindow) {
   1.376 +    return open_manager(aView, aCallback, aLoadCallback);
   1.377 +  }
   1.378 +
   1.379 +  return close_manager(aManagerWindow)
   1.380 +    .then(() => open_manager(aView, aCallback, aLoadCallback));
   1.381 +}
   1.382 +
   1.383 +function wait_for_window_open(aCallback) {
   1.384 +  Services.wm.addListener({
   1.385 +    onOpenWindow: function(aWindow) {
   1.386 +      Services.wm.removeListener(this);
   1.387 +
   1.388 +      let domwindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
   1.389 +                             .getInterface(Ci.nsIDOMWindow);
   1.390 +      domwindow.addEventListener("load", function() {
   1.391 +        domwindow.removeEventListener("load", arguments.callee, false);
   1.392 +        executeSoon(function() {
   1.393 +          aCallback(domwindow);
   1.394 +        });
   1.395 +      }, false);
   1.396 +    },
   1.397 +
   1.398 +    onCloseWindow: function(aWindow) {
   1.399 +    },
   1.400 +
   1.401 +    onWindowTitleChange: function(aWindow, aTitle) {
   1.402 +    }
   1.403 +  });
   1.404 +}
   1.405 +
   1.406 +function get_string(aName, ...aArgs) {
   1.407 +  var bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/extensions.properties");
   1.408 +  if (aArgs.length == 0)
   1.409 +    return bundle.GetStringFromName(aName);
   1.410 +  return bundle.formatStringFromName(aName, aArgs, aArgs.length);
   1.411 +}
   1.412 +
   1.413 +function formatDate(aDate) {
   1.414 +  return Cc["@mozilla.org/intl/scriptabledateformat;1"]
   1.415 +           .getService(Ci.nsIScriptableDateFormat)
   1.416 +           .FormatDate("",
   1.417 +                       Ci.nsIScriptableDateFormat.dateFormatLong,
   1.418 +                       aDate.getFullYear(),
   1.419 +                       aDate.getMonth() + 1,
   1.420 +                       aDate.getDate()
   1.421 +                       );
   1.422 +}
   1.423 +
   1.424 +function is_hidden(aElement) {
   1.425 +  var style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, "");
   1.426 +  if (style.display == "none")
   1.427 +    return true;
   1.428 +  if (style.visibility != "visible")
   1.429 +    return true;
   1.430 +
   1.431 +  // Hiding a parent element will hide all its children
   1.432 +  if (aElement.parentNode != aElement.ownerDocument)
   1.433 +    return is_hidden(aElement.parentNode);
   1.434 +
   1.435 +  return false;
   1.436 +}
   1.437 +
   1.438 +function is_element_visible(aElement, aMsg) {
   1.439 +  isnot(aElement, null, "Element should not be null, when checking visibility");
   1.440 +  ok(!is_hidden(aElement), aMsg);
   1.441 +}
   1.442 +
   1.443 +function is_element_hidden(aElement, aMsg) {
   1.444 +  isnot(aElement, null, "Element should not be null, when checking visibility");
   1.445 +  ok(is_hidden(aElement), aMsg);
   1.446 +}
   1.447 +
   1.448 +/**
   1.449 + * Install an add-on and call a callback when complete.
   1.450 + *
   1.451 + * The callback will receive the Addon for the installed add-on.
   1.452 + */
   1.453 +function install_addon(path, cb, pathPrefix=TESTROOT) {
   1.454 +  let p = new Promise((resolve, reject) => {
   1.455 +    AddonManager.getInstallForURL(pathPrefix + path, (install) => {
   1.456 +      install.addListener({
   1.457 +        onInstallEnded: () => resolve(install.addon),
   1.458 +      });
   1.459 +
   1.460 +      install.install();
   1.461 +    }, "application/x-xpinstall");
   1.462 +  });
   1.463 +
   1.464 +  return log_callback(p, cb);
   1.465 +}
   1.466 +
   1.467 +function CategoryUtilities(aManagerWindow) {
   1.468 +  this.window = aManagerWindow;
   1.469 +
   1.470 +  var self = this;
   1.471 +  this.window.addEventListener("unload", function() {
   1.472 +    self.window.removeEventListener("unload", arguments.callee, false);
   1.473 +    self.window = null;
   1.474 +  }, false);
   1.475 +}
   1.476 +
   1.477 +CategoryUtilities.prototype = {
   1.478 +  window: null,
   1.479 +
   1.480 +  get selectedCategory() {
   1.481 +    isnot(this.window, null, "Should not get selected category when manager window is not loaded");
   1.482 +    var selectedItem = this.window.document.getElementById("categories").selectedItem;
   1.483 +    isnot(selectedItem, null, "A category should be selected");
   1.484 +    var view = this.window.gViewController.parseViewId(selectedItem.value);
   1.485 +    return (view.type == "list") ? view.param : view.type;
   1.486 +  },
   1.487 +
   1.488 +  get: function(aCategoryType, aAllowMissing) {
   1.489 +    isnot(this.window, null, "Should not get category when manager window is not loaded");
   1.490 +    var categories = this.window.document.getElementById("categories");
   1.491 +
   1.492 +    var viewId = "addons://list/" + aCategoryType;
   1.493 +    var items = categories.getElementsByAttribute("value", viewId);
   1.494 +    if (items.length)
   1.495 +      return items[0];
   1.496 +
   1.497 +    viewId = "addons://" + aCategoryType + "/";
   1.498 +    items = categories.getElementsByAttribute("value", viewId);
   1.499 +    if (items.length)
   1.500 +      return items[0];
   1.501 +
   1.502 +    if (!aAllowMissing)
   1.503 +      ok(false, "Should have found a category with type " + aCategoryType);
   1.504 +    return null;
   1.505 +  },
   1.506 +
   1.507 +  getViewId: function(aCategoryType) {
   1.508 +    isnot(this.window, null, "Should not get view id when manager window is not loaded");
   1.509 +    return this.get(aCategoryType).value;
   1.510 +  },
   1.511 +
   1.512 +  isVisible: function(aCategory) {
   1.513 +    isnot(this.window, null, "Should not check visible state when manager window is not loaded");
   1.514 +    if (aCategory.hasAttribute("disabled") &&
   1.515 +        aCategory.getAttribute("disabled") == "true")
   1.516 +      return false;
   1.517 +
   1.518 +    return !is_hidden(aCategory);
   1.519 +  },
   1.520 +
   1.521 +  isTypeVisible: function(aCategoryType) {
   1.522 +    return this.isVisible(this.get(aCategoryType));
   1.523 +  },
   1.524 +
   1.525 +  open: function(aCategory, aCallback) {
   1.526 +
   1.527 +    isnot(this.window, null, "Should not open category when manager window is not loaded");
   1.528 +    ok(this.isVisible(aCategory), "Category should be visible if attempting to open it");
   1.529 +
   1.530 +    EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window);
   1.531 +    let p = new Promise((resolve, reject) => wait_for_view_load(this.window, resolve));
   1.532 +
   1.533 +    return log_callback(p, aCallback);
   1.534 +  },
   1.535 +
   1.536 +  openType: function(aCategoryType, aCallback) {
   1.537 +    return this.open(this.get(aCategoryType), aCallback);
   1.538 +  }
   1.539 +}
   1.540 +
   1.541 +function CertOverrideListener(host, bits) {
   1.542 +  this.host = host;
   1.543 +  this.bits = bits;
   1.544 +}
   1.545 +
   1.546 +CertOverrideListener.prototype = {
   1.547 +  host: null,
   1.548 +  bits: null,
   1.549 +
   1.550 +  getInterface: function (aIID) {
   1.551 +    return this.QueryInterface(aIID);
   1.552 +  },
   1.553 +
   1.554 +  QueryInterface: function(aIID) {
   1.555 +    if (aIID.equals(Ci.nsIBadCertListener2) ||
   1.556 +        aIID.equals(Ci.nsIInterfaceRequestor) ||
   1.557 +        aIID.equals(Ci.nsISupports))
   1.558 +      return this;
   1.559 +
   1.560 +    throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE);
   1.561 +  },
   1.562 +
   1.563 +  notifyCertProblem: function (socketInfo, sslStatus, targetHost) {
   1.564 +    var cert = sslStatus.QueryInterface(Components.interfaces.nsISSLStatus)
   1.565 +                        .serverCert;
   1.566 +    var cos = Cc["@mozilla.org/security/certoverride;1"].
   1.567 +              getService(Ci.nsICertOverrideService);
   1.568 +    cos.rememberValidityOverride(this.host, -1, cert, this.bits, false);
   1.569 +    return true;
   1.570 +  }
   1.571 +}
   1.572 +
   1.573 +// Add overrides for the bad certificates
   1.574 +function addCertOverride(host, bits) {
   1.575 +  var req = new XMLHttpRequest();
   1.576 +  try {
   1.577 +    req.open("GET", "https://" + host + "/", false);
   1.578 +    req.channel.notificationCallbacks = new CertOverrideListener(host, bits);
   1.579 +    req.send(null);
   1.580 +  }
   1.581 +  catch (e) {
   1.582 +    // This request will fail since the SSL server is not trusted yet
   1.583 +  }
   1.584 +}
   1.585 +
   1.586 +/***** Mock Provider *****/
   1.587 +
   1.588 +function MockProvider(aUseAsyncCallbacks, aTypes) {
   1.589 +  this.addons = [];
   1.590 +  this.installs = [];
   1.591 +  this.callbackTimers = [];
   1.592 +  this.timerLocations = new Map();
   1.593 +  this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks;
   1.594 +  this.types = (aTypes === undefined) ? [{
   1.595 +    id: "extension",
   1.596 +    name: "Extensions",
   1.597 +    uiPriority: 4000,
   1.598 +    flags: AddonManager.TYPE_UI_VIEW_LIST
   1.599 +  }] : aTypes;
   1.600 +
   1.601 +  var self = this;
   1.602 +  registerCleanupFunction(function() {
   1.603 +    if (self.started)
   1.604 +      self.unregister();
   1.605 +  });
   1.606 +
   1.607 +  this.register();
   1.608 +}
   1.609 +
   1.610 +MockProvider.prototype = {
   1.611 +  addons: null,
   1.612 +  installs: null,
   1.613 +  started: null,
   1.614 +  apiDelay: 10,
   1.615 +  callbackTimers: null,
   1.616 +  timerLocations: null,
   1.617 +  useAsyncCallbacks: null,
   1.618 +  types: null,
   1.619 +
   1.620 +  /***** Utility functions *****/
   1.621 +
   1.622 +  /**
   1.623 +   * Register this provider with the AddonManager
   1.624 +   */
   1.625 +  register: function MP_register() {
   1.626 +    AddonManagerPrivate.registerProvider(this, this.types);
   1.627 +  },
   1.628 +
   1.629 +  /**
   1.630 +   * Unregister this provider with the AddonManager
   1.631 +   */
   1.632 +  unregister: function MP_unregister() {
   1.633 +    AddonManagerPrivate.unregisterProvider(this);
   1.634 +  },
   1.635 +
   1.636 +  /**
   1.637 +   * Adds an add-on to the list of add-ons that this provider exposes to the
   1.638 +   * AddonManager, dispatching appropriate events in the process.
   1.639 +   *
   1.640 +   * @param  aAddon
   1.641 +   *         The add-on to add
   1.642 +   */
   1.643 +  addAddon: function MP_addAddon(aAddon) {
   1.644 +    var oldAddons = this.addons.filter(function(aOldAddon) aOldAddon.id == aAddon.id);
   1.645 +    var oldAddon = oldAddons.length > 0 ? oldAddons[0] : null;
   1.646 +
   1.647 +    this.addons = this.addons.filter(function(aOldAddon) aOldAddon.id != aAddon.id);
   1.648 +
   1.649 +    this.addons.push(aAddon);
   1.650 +    aAddon._provider = this;
   1.651 +
   1.652 +    if (!this.started)
   1.653 +      return;
   1.654 +
   1.655 +    let requiresRestart = (aAddon.operationsRequiringRestart &
   1.656 +                           AddonManager.OP_NEEDS_RESTART_INSTALL) != 0;
   1.657 +    AddonManagerPrivate.callInstallListeners("onExternalInstall", null, aAddon,
   1.658 +                                             oldAddon, requiresRestart)
   1.659 +  },
   1.660 +
   1.661 +  /**
   1.662 +   * Removes an add-on from the list of add-ons that this provider exposes to
   1.663 +   * the AddonManager, dispatching the onUninstalled event in the process.
   1.664 +   *
   1.665 +   * @param  aAddon
   1.666 +   *         The add-on to add
   1.667 +   */
   1.668 +  removeAddon: function MP_removeAddon(aAddon) {
   1.669 +    var pos = this.addons.indexOf(aAddon);
   1.670 +    if (pos == -1) {
   1.671 +      ok(false, "Tried to remove an add-on that wasn't registered with the mock provider");
   1.672 +      return;
   1.673 +    }
   1.674 +
   1.675 +    this.addons.splice(pos, 1);
   1.676 +
   1.677 +    if (!this.started)
   1.678 +      return;
   1.679 +
   1.680 +    AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon);
   1.681 +  },
   1.682 +
   1.683 +  /**
   1.684 +   * Adds an add-on install to the list of installs that this provider exposes
   1.685 +   * to the AddonManager, dispatching appropriate events in the process.
   1.686 +   *
   1.687 +   * @param  aInstall
   1.688 +   *         The add-on install to add
   1.689 +   */
   1.690 +  addInstall: function MP_addInstall(aInstall) {
   1.691 +    this.installs.push(aInstall);
   1.692 +    aInstall._provider = this;
   1.693 +
   1.694 +    if (!this.started)
   1.695 +      return;
   1.696 +
   1.697 +    aInstall.callListeners("onNewInstall");
   1.698 +  },
   1.699 +
   1.700 +  removeInstall: function MP_removeInstall(aInstall) {
   1.701 +    var pos = this.installs.indexOf(aInstall);
   1.702 +    if (pos == -1) {
   1.703 +      ok(false, "Tried to remove an install that wasn't registered with the mock provider");
   1.704 +      return;
   1.705 +    }
   1.706 +
   1.707 +    this.installs.splice(pos, 1);
   1.708 +  },
   1.709 +
   1.710 +  /**
   1.711 +   * Creates a set of mock add-on objects and adds them to the list of add-ons
   1.712 +   * managed by this provider.
   1.713 +   *
   1.714 +   * @param  aAddonProperties
   1.715 +   *         An array of objects containing properties describing the add-ons
   1.716 +   * @return Array of the new MockAddons
   1.717 +   */
   1.718 +  createAddons: function MP_createAddons(aAddonProperties) {
   1.719 +    var newAddons = [];
   1.720 +    for (let addonProp of aAddonProperties) {
   1.721 +      let addon = new MockAddon(addonProp.id);
   1.722 +      for (let prop in addonProp) {
   1.723 +        if (prop == "id")
   1.724 +          continue;
   1.725 +        if (prop == "applyBackgroundUpdates") {
   1.726 +          addon._applyBackgroundUpdates = addonProp[prop];
   1.727 +          continue;
   1.728 +        }
   1.729 +        if (prop == "appDisabled") {
   1.730 +          addon._appDisabled = addonProp[prop];
   1.731 +          continue;
   1.732 +        }
   1.733 +        addon[prop] = addonProp[prop];
   1.734 +      }
   1.735 +      if (!addon.optionsType && !!addon.optionsURL)
   1.736 +        addon.optionsType = AddonManager.OPTIONS_TYPE_DIALOG;
   1.737 +
   1.738 +      // Make sure the active state matches the passed in properties
   1.739 +      addon.isActive = addon.shouldBeActive;
   1.740 +
   1.741 +      this.addAddon(addon);
   1.742 +      newAddons.push(addon);
   1.743 +    }
   1.744 +
   1.745 +    return newAddons;
   1.746 +  },
   1.747 +
   1.748 +  /**
   1.749 +   * Creates a set of mock add-on install objects and adds them to the list
   1.750 +   * of installs managed by this provider.
   1.751 +   *
   1.752 +   * @param  aInstallProperties
   1.753 +   *         An array of objects containing properties describing the installs
   1.754 +   * @return Array of the new MockInstalls
   1.755 +   */
   1.756 +  createInstalls: function MP_createInstalls(aInstallProperties) {
   1.757 +    var newInstalls = [];
   1.758 +    for (let installProp of aInstallProperties) {
   1.759 +      let install = new MockInstall(installProp.name || null,
   1.760 +                                    installProp.type || null,
   1.761 +                                    null);
   1.762 +      for (let prop in installProp) {
   1.763 +        switch (prop) {
   1.764 +          case "name":
   1.765 +          case "type":
   1.766 +            break;
   1.767 +          case "sourceURI":
   1.768 +            install[prop] = NetUtil.newURI(installProp[prop]);
   1.769 +            break;
   1.770 +          default:
   1.771 +            install[prop] = installProp[prop];
   1.772 +        }
   1.773 +      }
   1.774 +      this.addInstall(install);
   1.775 +      newInstalls.push(install);
   1.776 +    }
   1.777 +
   1.778 +    return newInstalls;
   1.779 +  },
   1.780 +
   1.781 +  /***** AddonProvider implementation *****/
   1.782 +
   1.783 +  /**
   1.784 +   * Called to initialize the provider.
   1.785 +   */
   1.786 +  startup: function MP_startup() {
   1.787 +    this.started = true;
   1.788 +  },
   1.789 +
   1.790 +  /**
   1.791 +   * Called when the provider should shutdown.
   1.792 +   */
   1.793 +  shutdown: function MP_shutdown() {
   1.794 +    if (this.callbackTimers.length) {
   1.795 +      info("MockProvider: pending callbacks at shutdown(): calling immediately");
   1.796 +    }
   1.797 +    while (this.callbackTimers.length > 0) {
   1.798 +      // When we notify the callback timer, it removes itself from our array
   1.799 +      let timer = this.callbackTimers[0];
   1.800 +      try {
   1.801 +        let setAt = this.timerLocations.get(timer);
   1.802 +        info("Notifying timer set at " + (setAt || "unknown location"));
   1.803 +        timer.callback.notify(timer);
   1.804 +        timer.cancel();
   1.805 +      } catch(e) {
   1.806 +        info("Timer notify failed: " + e);
   1.807 +      }
   1.808 +    }
   1.809 +    this.callbackTimers = [];
   1.810 +    this.timerLocations = null;
   1.811 +
   1.812 +    this.started = false;
   1.813 +  },
   1.814 +
   1.815 +  /**
   1.816 +   * Called to get an Addon with a particular ID.
   1.817 +   *
   1.818 +   * @param  aId
   1.819 +   *         The ID of the add-on to retrieve
   1.820 +   * @param  aCallback
   1.821 +   *         A callback to pass the Addon to
   1.822 +   */
   1.823 +  getAddonByID: function MP_getAddon(aId, aCallback) {
   1.824 +    for (let addon of this.addons) {
   1.825 +      if (addon.id == aId) {
   1.826 +        this._delayCallback(aCallback, addon);
   1.827 +        return;
   1.828 +      }
   1.829 +    }
   1.830 +
   1.831 +    aCallback(null);
   1.832 +  },
   1.833 +
   1.834 +  /**
   1.835 +   * Called to get Addons of a particular type.
   1.836 +   *
   1.837 +   * @param  aTypes
   1.838 +   *         An array of types to fetch. Can be null to get all types.
   1.839 +   * @param  callback
   1.840 +   *         A callback to pass an array of Addons to
   1.841 +   */
   1.842 +  getAddonsByTypes: function MP_getAddonsByTypes(aTypes, aCallback) {
   1.843 +    var addons = this.addons.filter(function(aAddon) {
   1.844 +      if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1)
   1.845 +        return false;
   1.846 +      return true;
   1.847 +    });
   1.848 +    this._delayCallback(aCallback, addons);
   1.849 +  },
   1.850 +
   1.851 +  /**
   1.852 +   * Called to get Addons that have pending operations.
   1.853 +   *
   1.854 +   * @param  aTypes
   1.855 +   *         An array of types to fetch. Can be null to get all types
   1.856 +   * @param  aCallback
   1.857 +   *         A callback to pass an array of Addons to
   1.858 +   */
   1.859 +  getAddonsWithOperationsByTypes: function MP_getAddonsWithOperationsByTypes(aTypes, aCallback) {
   1.860 +    var addons = this.addons.filter(function(aAddon) {
   1.861 +      if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1)
   1.862 +        return false;
   1.863 +      return aAddon.pendingOperations != 0;
   1.864 +    });
   1.865 +    this._delayCallback(aCallback, addons);
   1.866 +  },
   1.867 +
   1.868 +  /**
   1.869 +   * Called to get the current AddonInstalls, optionally restricting by type.
   1.870 +   *
   1.871 +   * @param  aTypes
   1.872 +   *         An array of types or null to get all types
   1.873 +   * @param  aCallback
   1.874 +   *         A callback to pass the array of AddonInstalls to
   1.875 +   */
   1.876 +  getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) {
   1.877 +    var installs = this.installs.filter(function(aInstall) {
   1.878 +      // Appear to have actually removed cancelled installs from the provider
   1.879 +      if (aInstall.state == AddonManager.STATE_CANCELLED)
   1.880 +        return false;
   1.881 +
   1.882 +      if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1)
   1.883 +        return false;
   1.884 +
   1.885 +      return true;
   1.886 +    });
   1.887 +    this._delayCallback(aCallback, installs);
   1.888 +  },
   1.889 +
   1.890 +  /**
   1.891 +   * Called when a new add-on has been enabled when only one add-on of that type
   1.892 +   * can be enabled.
   1.893 +   *
   1.894 +   * @param  aId
   1.895 +   *         The ID of the newly enabled add-on
   1.896 +   * @param  aType
   1.897 +   *         The type of the newly enabled add-on
   1.898 +   * @param  aPendingRestart
   1.899 +   *         true if the newly enabled add-on will only become enabled after a
   1.900 +   *         restart
   1.901 +   */
   1.902 +  addonChanged: function MP_addonChanged(aId, aType, aPendingRestart) {
   1.903 +    // Not implemented
   1.904 +  },
   1.905 +
   1.906 +  /**
   1.907 +   * Update the appDisabled property for all add-ons.
   1.908 +   */
   1.909 +  updateAddonAppDisabledStates: function MP_updateAddonAppDisabledStates() {
   1.910 +    // Not needed
   1.911 +  },
   1.912 +
   1.913 +  /**
   1.914 +   * Called to get an AddonInstall to download and install an add-on from a URL.
   1.915 +   *
   1.916 +   * @param  aUrl
   1.917 +   *         The URL to be installed
   1.918 +   * @param  aHash
   1.919 +   *         A hash for the install
   1.920 +   * @param  aName
   1.921 +   *         A name for the install
   1.922 +   * @param  aIconURL
   1.923 +   *         An icon URL for the install
   1.924 +   * @param  aVersion
   1.925 +   *         A version for the install
   1.926 +   * @param  aLoadGroup
   1.927 +   *         An nsILoadGroup to associate requests with
   1.928 +   * @param  aCallback
   1.929 +   *         A callback to pass the AddonInstall to
   1.930 +   */
   1.931 +  getInstallForURL: function MP_getInstallForURL(aUrl, aHash, aName, aIconURL,
   1.932 +                                                  aVersion, aLoadGroup, aCallback) {
   1.933 +    // Not yet implemented
   1.934 +  },
   1.935 +
   1.936 +  /**
   1.937 +   * Called to get an AddonInstall to install an add-on from a local file.
   1.938 +   *
   1.939 +   * @param  aFile
   1.940 +   *         The file to be installed
   1.941 +   * @param  aCallback
   1.942 +   *         A callback to pass the AddonInstall to
   1.943 +   */
   1.944 +  getInstallForFile: function MP_getInstallForFile(aFile, aCallback) {
   1.945 +    // Not yet implemented
   1.946 +  },
   1.947 +
   1.948 +  /**
   1.949 +   * Called to test whether installing add-ons is enabled.
   1.950 +   *
   1.951 +   * @return true if installing is enabled
   1.952 +   */
   1.953 +  isInstallEnabled: function MP_isInstallEnabled() {
   1.954 +    return false;
   1.955 +  },
   1.956 +
   1.957 +  /**
   1.958 +   * Called to test whether this provider supports installing a particular
   1.959 +   * mimetype.
   1.960 +   *
   1.961 +   * @param  aMimetype
   1.962 +   *         The mimetype to check for
   1.963 +   * @return true if the mimetype is supported
   1.964 +   */
   1.965 +  supportsMimetype: function MP_supportsMimetype(aMimetype) {
   1.966 +    return false;
   1.967 +  },
   1.968 +
   1.969 +  /**
   1.970 +   * Called to test whether installing add-ons from a URI is allowed.
   1.971 +   *
   1.972 +   * @param  aUri
   1.973 +   *         The URI being installed from
   1.974 +   * @return true if installing is allowed
   1.975 +   */
   1.976 +  isInstallAllowed: function MP_isInstallAllowed(aUri) {
   1.977 +    return false;
   1.978 +  },
   1.979 +
   1.980 +
   1.981 +  /***** Internal functions *****/
   1.982 +
   1.983 +  /**
   1.984 +   * Delay calling a callback to fake a time-consuming async operation.
   1.985 +   * The delay is specified by the apiDelay property, in milliseconds.
   1.986 +   * Parameters to send to the callback should be specified as arguments after
   1.987 +   * the aCallback argument.
   1.988 +   *
   1.989 +   * @param aCallback Callback to eventually call
   1.990 +   */
   1.991 +  _delayCallback: function MP_delayCallback(aCallback, ...aArgs) {
   1.992 +    if (!this.useAsyncCallbacks) {
   1.993 +      aCallback(...aArgs);
   1.994 +      return;
   1.995 +    }
   1.996 +
   1.997 +    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.998 +    // Need to keep a reference to the timer, so it doesn't get GC'ed
   1.999 +    this.callbackTimers.push(timer);
  1.1000 +    // Capture a stack trace where the timer was set
  1.1001 +    // needs the 'new Error' hack until bug 1007656
  1.1002 +    this.timerLocations.set(timer, Log.stackTrace(new Error("dummy")));
  1.1003 +    timer.initWithCallback(() => {
  1.1004 +      let idx = this.callbackTimers.indexOf(timer);
  1.1005 +      if (idx == -1) {
  1.1006 +        dump("MockProvider._delayCallback lost track of timer set at "
  1.1007 +             + (this.timerLocations.get(timer) || "unknown location") + "\n");
  1.1008 +      } else {
  1.1009 +        this.callbackTimers.splice(idx, 1);
  1.1010 +      }
  1.1011 +      this.timerLocations.delete(timer);
  1.1012 +      aCallback(...aArgs);
  1.1013 +    }, this.apiDelay, timer.TYPE_ONE_SHOT);
  1.1014 +  }
  1.1015 +};
  1.1016 +
  1.1017 +/***** Mock Addon object for the Mock Provider *****/
  1.1018 +
  1.1019 +function MockAddon(aId, aName, aType, aOperationsRequiringRestart) {
  1.1020 +  // Only set required attributes.
  1.1021 +  this.id = aId || "";
  1.1022 +  this.name = aName || "";
  1.1023 +  this.type = aType || "extension";
  1.1024 +  this.version = "";
  1.1025 +  this.isCompatible = true;
  1.1026 +  this.isDebuggable = false;
  1.1027 +  this.providesUpdatesSecurely = true;
  1.1028 +  this.blocklistState = 0;
  1.1029 +  this._appDisabled = false;
  1.1030 +  this._userDisabled = false;
  1.1031 +  this._applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE;
  1.1032 +  this.scope = AddonManager.SCOPE_PROFILE;
  1.1033 +  this.isActive = true;
  1.1034 +  this.creator = "";
  1.1035 +  this.pendingOperations = 0;
  1.1036 +  this._permissions = AddonManager.PERM_CAN_UNINSTALL |
  1.1037 +                      AddonManager.PERM_CAN_ENABLE |
  1.1038 +                      AddonManager.PERM_CAN_DISABLE |
  1.1039 +                      AddonManager.PERM_CAN_UPGRADE;
  1.1040 +  this.operationsRequiringRestart = aOperationsRequiringRestart ||
  1.1041 +    (AddonManager.OP_NEEDS_RESTART_INSTALL |
  1.1042 +     AddonManager.OP_NEEDS_RESTART_UNINSTALL |
  1.1043 +     AddonManager.OP_NEEDS_RESTART_ENABLE |
  1.1044 +     AddonManager.OP_NEEDS_RESTART_DISABLE);
  1.1045 +}
  1.1046 +
  1.1047 +MockAddon.prototype = {
  1.1048 +  get shouldBeActive() {
  1.1049 +    return !this.appDisabled && !this._userDisabled;
  1.1050 +  },
  1.1051 +
  1.1052 +  get appDisabled() {
  1.1053 +    return this._appDisabled;
  1.1054 +  },
  1.1055 +
  1.1056 +  set appDisabled(val) {
  1.1057 +    if (val == this._appDisabled)
  1.1058 +      return val;
  1.1059 +
  1.1060 +    AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["appDisabled"]);
  1.1061 +
  1.1062 +    var currentActive = this.shouldBeActive;
  1.1063 +    this._appDisabled = val;
  1.1064 +    var newActive = this.shouldBeActive;
  1.1065 +    this._updateActiveState(currentActive, newActive);
  1.1066 +
  1.1067 +    return val;
  1.1068 +  },
  1.1069 +
  1.1070 +  get userDisabled() {
  1.1071 +    return this._userDisabled;
  1.1072 +  },
  1.1073 +
  1.1074 +  set userDisabled(val) {
  1.1075 +    if (val == this._userDisabled)
  1.1076 +      return val;
  1.1077 +
  1.1078 +    var currentActive = this.shouldBeActive;
  1.1079 +    this._userDisabled = val;
  1.1080 +    var newActive = this.shouldBeActive;
  1.1081 +    this._updateActiveState(currentActive, newActive);
  1.1082 +
  1.1083 +    return val;
  1.1084 +  },
  1.1085 +
  1.1086 +  get permissions() {
  1.1087 +    let permissions = this._permissions;
  1.1088 +    if (this.appDisabled || !this._userDisabled)
  1.1089 +      permissions &= ~AddonManager.PERM_CAN_ENABLE;
  1.1090 +    if (this.appDisabled || this._userDisabled)
  1.1091 +      permissions &= ~AddonManager.PERM_CAN_DISABLE;
  1.1092 +    return permissions;
  1.1093 +  },
  1.1094 +
  1.1095 +  set permissions(val) {
  1.1096 +    return this._permissions = val;
  1.1097 +  },
  1.1098 +
  1.1099 +  get applyBackgroundUpdates() {
  1.1100 +    return this._applyBackgroundUpdates;
  1.1101 +  },
  1.1102 +  
  1.1103 +  set applyBackgroundUpdates(val) {
  1.1104 +    if (val != AddonManager.AUTOUPDATE_DEFAULT &&
  1.1105 +        val != AddonManager.AUTOUPDATE_DISABLE &&
  1.1106 +        val != AddonManager.AUTOUPDATE_ENABLE) {
  1.1107 +      ok(false, "addon.applyBackgroundUpdates set to an invalid value: " + val);
  1.1108 +    }
  1.1109 +    this._applyBackgroundUpdates = val;
  1.1110 +    AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]);
  1.1111 +  },
  1.1112 +
  1.1113 +  isCompatibleWith: function(aAppVersion, aPlatformVersion) {
  1.1114 +    return true;
  1.1115 +  },
  1.1116 +
  1.1117 +  findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
  1.1118 +    // Tests can implement this if they need to
  1.1119 +  },
  1.1120 +
  1.1121 +  uninstall: function() {
  1.1122 +    if (this.pendingOperations & AddonManager.PENDING_UNINSTALL)
  1.1123 +      throw Components.Exception("Add-on is already pending uninstall");
  1.1124 +
  1.1125 +    var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL);
  1.1126 +    this.pendingOperations |= AddonManager.PENDING_UNINSTALL;
  1.1127 +    AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart);
  1.1128 +    if (!needsRestart) {
  1.1129 +      this.pendingOperations -= AddonManager.PENDING_UNINSTALL;
  1.1130 +      this._provider.removeAddon(this);
  1.1131 +    }
  1.1132 +  },
  1.1133 +
  1.1134 +  cancelUninstall: function() {
  1.1135 +    if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL))
  1.1136 +      throw Components.Exception("Add-on is not pending uninstall");
  1.1137 +
  1.1138 +    this.pendingOperations -= AddonManager.PENDING_UNINSTALL;
  1.1139 +    AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
  1.1140 +  },
  1.1141 +
  1.1142 +  _updateActiveState: function(currentActive, newActive) {
  1.1143 +    if (currentActive == newActive)
  1.1144 +      return;
  1.1145 +
  1.1146 +    if (newActive == this.isActive) {
  1.1147 +      this.pendingOperations -= (newActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE);
  1.1148 +      AddonManagerPrivate.callAddonListeners("onOperationCancelled", this);
  1.1149 +    }
  1.1150 +    else if (newActive) {
  1.1151 +      var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE);
  1.1152 +      this.pendingOperations |= AddonManager.PENDING_ENABLE;
  1.1153 +      AddonManagerPrivate.callAddonListeners("onEnabling", this, needsRestart);
  1.1154 +      if (!needsRestart) {
  1.1155 +        this.isActive = newActive;
  1.1156 +        this.pendingOperations -= AddonManager.PENDING_ENABLE;
  1.1157 +        AddonManagerPrivate.callAddonListeners("onEnabled", this);
  1.1158 +      }
  1.1159 +    }
  1.1160 +    else {
  1.1161 +      var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE);
  1.1162 +      this.pendingOperations |= AddonManager.PENDING_DISABLE;
  1.1163 +      AddonManagerPrivate.callAddonListeners("onDisabling", this, needsRestart);
  1.1164 +      if (!needsRestart) {
  1.1165 +        this.isActive = newActive;
  1.1166 +        this.pendingOperations -= AddonManager.PENDING_DISABLE;
  1.1167 +        AddonManagerPrivate.callAddonListeners("onDisabled", this);
  1.1168 +      }
  1.1169 +    }
  1.1170 +  }
  1.1171 +};
  1.1172 +
  1.1173 +/***** Mock AddonInstall object for the Mock Provider *****/
  1.1174 +
  1.1175 +function MockInstall(aName, aType, aAddonToInstall) {
  1.1176 +  this.name = aName || "";
  1.1177 +  // Don't expose type until download completed
  1.1178 +  this._type = aType || "extension";
  1.1179 +  this.type = null;
  1.1180 +  this.version = "1.0";
  1.1181 +  this.iconURL = "";
  1.1182 +  this.infoURL = "";
  1.1183 +  this.state = AddonManager.STATE_AVAILABLE;
  1.1184 +  this.error = 0;
  1.1185 +  this.sourceURI = null;
  1.1186 +  this.file = null;
  1.1187 +  this.progress = 0;
  1.1188 +  this.maxProgress = -1;
  1.1189 +  this.certificate = null;
  1.1190 +  this.certName = "";
  1.1191 +  this.existingAddon = null;
  1.1192 +  this.addon = null;
  1.1193 +  this._addonToInstall = aAddonToInstall;
  1.1194 +  this.listeners = [];
  1.1195 +
  1.1196 +  // Another type of install listener for tests that want to check the results
  1.1197 +  // of code run from standard install listeners
  1.1198 +  this.testListeners = [];
  1.1199 +}
  1.1200 +
  1.1201 +MockInstall.prototype = {
  1.1202 +  install: function() {
  1.1203 +    switch (this.state) {
  1.1204 +      case AddonManager.STATE_AVAILABLE:
  1.1205 +        this.state = AddonManager.STATE_DOWNLOADING;
  1.1206 +        if (!this.callListeners("onDownloadStarted")) {
  1.1207 +          this.state = AddonManager.STATE_CANCELLED;
  1.1208 +          this.callListeners("onDownloadCancelled");
  1.1209 +          return;
  1.1210 +        }
  1.1211 +
  1.1212 +        this.type = this._type;
  1.1213 +
  1.1214 +        // Adding addon to MockProvider to be implemented when needed
  1.1215 +        if (this._addonToInstall)
  1.1216 +          this.addon = this._addonToInstall;
  1.1217 +        else {
  1.1218 +          this.addon = new MockAddon("", this.name, this.type);
  1.1219 +          this.addon.version = this.version;
  1.1220 +          this.addon.pendingOperations = AddonManager.PENDING_INSTALL;
  1.1221 +        }
  1.1222 +        this.addon.install = this;
  1.1223 +        if (this.existingAddon) {
  1.1224 +          if (!this.addon.id)
  1.1225 +            this.addon.id = this.existingAddon.id;
  1.1226 +          this.existingAddon.pendingUpgrade = this.addon;
  1.1227 +          this.existingAddon.pendingOperations |= AddonManager.PENDING_UPGRADE;
  1.1228 +        }
  1.1229 +
  1.1230 +        this.state = AddonManager.STATE_DOWNLOADED;
  1.1231 +        this.callListeners("onDownloadEnded");
  1.1232 +
  1.1233 +      case AddonManager.STATE_DOWNLOADED:
  1.1234 +        this.state = AddonManager.STATE_INSTALLING;
  1.1235 +        if (!this.callListeners("onInstallStarted")) {
  1.1236 +          this.state = AddonManager.STATE_CANCELLED;
  1.1237 +          this.callListeners("onInstallCancelled");
  1.1238 +          return;
  1.1239 +        }
  1.1240 +
  1.1241 +        AddonManagerPrivate.callAddonListeners("onInstalling", this.addon);
  1.1242 +
  1.1243 +        this.state = AddonManager.STATE_INSTALLED;
  1.1244 +        this.callListeners("onInstallEnded");
  1.1245 +        break;
  1.1246 +      case AddonManager.STATE_DOWNLOADING:
  1.1247 +      case AddonManager.STATE_CHECKING:
  1.1248 +      case AddonManger.STATE_INSTALLING:
  1.1249 +        // Installation is already running
  1.1250 +        return;
  1.1251 +      default:
  1.1252 +        ok(false, "Cannot start installing when state = " + this.state);
  1.1253 +    }
  1.1254 +  },
  1.1255 +
  1.1256 +  cancel: function() {
  1.1257 +    switch (this.state) {
  1.1258 +      case AddonManager.STATE_AVAILABLE:
  1.1259 +        this.state = AddonManager.STATE_CANCELLED;
  1.1260 +        break;
  1.1261 +      case AddonManager.STATE_INSTALLED:
  1.1262 +        this.state = AddonManager.STATE_CANCELLED;
  1.1263 +        this._provider.removeInstall(this);
  1.1264 +        this.callListeners("onInstallCancelled");
  1.1265 +        break;
  1.1266 +      default:
  1.1267 +        // Handling cancelling when downloading to be implemented when needed
  1.1268 +        ok(false, "Cannot cancel when state = " + this.state);
  1.1269 +    }
  1.1270 +  },
  1.1271 +
  1.1272 +
  1.1273 +  addListener: function(aListener) {
  1.1274 +    if (!this.listeners.some(function(i) i == aListener))
  1.1275 +      this.listeners.push(aListener);
  1.1276 +  },
  1.1277 +
  1.1278 +  removeListener: function(aListener) {
  1.1279 +    this.listeners = this.listeners.filter(function(i) i != aListener);
  1.1280 +  },
  1.1281 +
  1.1282 +  addTestListener: function(aListener) {
  1.1283 +    if (!this.testListeners.some(function(i) i == aListener))
  1.1284 +      this.testListeners.push(aListener);
  1.1285 +  },
  1.1286 +
  1.1287 +  removeTestListener: function(aListener) {
  1.1288 +    this.testListeners = this.testListeners.filter(function(i) i != aListener);
  1.1289 +  },
  1.1290 +
  1.1291 +  callListeners: function(aMethod) {
  1.1292 +    var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners,
  1.1293 +                                                          this, this.addon);
  1.1294 +
  1.1295 +    // Call test listeners after standard listeners to remove race condition
  1.1296 +    // between standard and test listeners
  1.1297 +    for (let listener of this.testListeners) {
  1.1298 +      try {
  1.1299 +        if (aMethod in listener)
  1.1300 +          if (listener[aMethod].call(listener, this, this.addon) === false)
  1.1301 +            result = false;
  1.1302 +      }
  1.1303 +      catch (e) {
  1.1304 +        ok(false, "Test listener threw exception: " + e);
  1.1305 +      }
  1.1306 +    }
  1.1307 +
  1.1308 +    return result;
  1.1309 +  }
  1.1310 +};
  1.1311 +
  1.1312 +function waitForCondition(condition, nextTest, errorMsg) {
  1.1313 +  let tries = 0;
  1.1314 +  let interval = setInterval(function() {
  1.1315 +    if (tries >= 30) {
  1.1316 +      ok(false, errorMsg);
  1.1317 +      moveOn();
  1.1318 +    }
  1.1319 +    var conditionPassed;
  1.1320 +    try {
  1.1321 +      conditionPassed = condition();
  1.1322 +    } catch (e) {
  1.1323 +      ok(false, e + "\n" + e.stack);
  1.1324 +      conditionPassed = false;
  1.1325 +    }
  1.1326 +    if (conditionPassed) {
  1.1327 +      moveOn();
  1.1328 +    }
  1.1329 +    tries++;
  1.1330 +  }, 100);
  1.1331 +  let moveOn = function() { clearInterval(interval); nextTest(); };
  1.1332 +}
  1.1333 +
  1.1334 +function getTestPluginTag() {
  1.1335 +  let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
  1.1336 +  let tags = ph.getPluginTags();
  1.1337 +
  1.1338 +  // Find the test plugin
  1.1339 +  for (let i = 0; i < tags.length; i++) {
  1.1340 +    if (tags[i].name == "Test Plug-in")
  1.1341 +      return tags[i];
  1.1342 +  }
  1.1343 +  ok(false, "Unable to find plugin");
  1.1344 +  return null;
  1.1345 +}

mercurial