1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/extensions/test/xpinstall/head.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,386 @@ 1.4 +const RELATIVE_DIR = "toolkit/mozapps/extensions/test/xpinstall/"; 1.5 + 1.6 +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; 1.7 +const TESTROOT2 = "http://example.org/browser/" + RELATIVE_DIR; 1.8 +const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; 1.9 +const PROMPT_URL = "chrome://global/content/commonDialog.xul"; 1.10 +const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xul"; 1.11 +const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; 1.12 +const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; 1.13 +const CHROME_NAME = "mochikit"; 1.14 + 1.15 +function getChromeRoot(path) { 1.16 + if (path === undefined) { 1.17 + return "chrome://" + CHROME_NAME + "/content/browser/" + RELATIVE_DIR 1.18 + } 1.19 + return getRootDirectory(path); 1.20 +} 1.21 + 1.22 +function extractChromeRoot(path) { 1.23 + var chromeRootPath = getChromeRoot(path); 1.24 + var jar = getJar(chromeRootPath); 1.25 + if (jar) { 1.26 + var tmpdir = extractJarToTmp(jar); 1.27 + return "file://" + tmpdir.path + "/"; 1.28 + } 1.29 + return chromeRootPath; 1.30 +} 1.31 + 1.32 +/** 1.33 + * This is a test harness designed to handle responding to UI during the process 1.34 + * of installing an XPI. A test can set callbacks to hear about specific parts 1.35 + * of the sequence. 1.36 + * Before use setup must be called and finish must be called afterwards. 1.37 + */ 1.38 +var Harness = { 1.39 + // If set then the callback is called when an install is attempted and 1.40 + // software installation is disabled. 1.41 + installDisabledCallback: null, 1.42 + // If set then the callback is called when an install is attempted and 1.43 + // then canceled. 1.44 + installCancelledCallback: null, 1.45 + // If set then the callback will be called when an install is blocked by the 1.46 + // whitelist. The callback should return true to continue with the install 1.47 + // anyway. 1.48 + installBlockedCallback: null, 1.49 + // If set will be called in the event of authentication being needed to get 1.50 + // the xpi. Should return a 2 element array of username and password, or 1.51 + // null to not authenticate. 1.52 + authenticationCallback: null, 1.53 + // If set this will be called to allow checking the contents of the xpinstall 1.54 + // confirmation dialog. The callback should return true to continue the install. 1.55 + installConfirmCallback: null, 1.56 + // If set will be called when downloading of an item has begun. 1.57 + downloadStartedCallback: null, 1.58 + // If set will be called during the download of an item. 1.59 + downloadProgressCallback: null, 1.60 + // If set will be called when an xpi fails to download. 1.61 + downloadFailedCallback: null, 1.62 + // If set will be called when an xpi download is cancelled. 1.63 + downloadCancelledCallback: null, 1.64 + // If set will be called when downloading of an item has ended. 1.65 + downloadEndedCallback: null, 1.66 + // If set will be called when installation by the extension manager of an xpi 1.67 + // item starts 1.68 + installStartedCallback: null, 1.69 + // If set will be called when an xpi fails to install. 1.70 + installFailedCallback: null, 1.71 + // If set will be called when each xpi item to be installed completes 1.72 + // installation. 1.73 + installEndedCallback: null, 1.74 + // If set will be called when all triggered items are installed or the install 1.75 + // is canceled. 1.76 + installsCompletedCallback: null, 1.77 + 1.78 + pendingCount: null, 1.79 + installCount: null, 1.80 + runningInstalls: null, 1.81 + 1.82 + waitingForFinish: false, 1.83 + 1.84 + // Setup and tear down functions 1.85 + setup: function() { 1.86 + if (!this.waitingForFinish) { 1.87 + waitForExplicitFinish(); 1.88 + this.waitingForFinish = true; 1.89 + 1.90 + Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); 1.91 + Services.obs.addObserver(this, "addon-install-started", false); 1.92 + Services.obs.addObserver(this, "addon-install-disabled", false); 1.93 + Services.obs.addObserver(this, "addon-install-blocked", false); 1.94 + Services.obs.addObserver(this, "addon-install-failed", false); 1.95 + Services.obs.addObserver(this, "addon-install-complete", false); 1.96 + 1.97 + AddonManager.addInstallListener(this); 1.98 + 1.99 + Services.wm.addListener(this); 1.100 + 1.101 + var self = this; 1.102 + registerCleanupFunction(function() { 1.103 + Services.prefs.clearUserPref(PREF_LOGGING_ENABLED); 1.104 + Services.obs.removeObserver(self, "addon-install-started"); 1.105 + Services.obs.removeObserver(self, "addon-install-disabled"); 1.106 + Services.obs.removeObserver(self, "addon-install-blocked"); 1.107 + Services.obs.removeObserver(self, "addon-install-failed"); 1.108 + Services.obs.removeObserver(self, "addon-install-complete"); 1.109 + 1.110 + AddonManager.removeInstallListener(self); 1.111 + 1.112 + Services.wm.removeListener(self); 1.113 + 1.114 + AddonManager.getAllInstalls(function(aInstalls) { 1.115 + is(aInstalls.length, 0, "Should be no active installs at the end of the test"); 1.116 + aInstalls.forEach(function(aInstall) { 1.117 + info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); 1.118 + aInstall.cancel(); 1.119 + }); 1.120 + }); 1.121 + }); 1.122 + } 1.123 + 1.124 + this.installCount = 0; 1.125 + this.pendingCount = 0; 1.126 + this.runningInstalls = []; 1.127 + }, 1.128 + 1.129 + finish: function() { 1.130 + finish(); 1.131 + }, 1.132 + 1.133 + endTest: function() { 1.134 + // Defer the final notification to allow things like the InstallTrigger 1.135 + // callback to complete 1.136 + var self = this; 1.137 + executeSoon(function() { 1.138 + let callback = self.installsCompletedCallback; 1.139 + let count = self.installCount; 1.140 + 1.141 + is(self.runningInstalls.length, 0, "Should be no running installs left"); 1.142 + self.runningInstalls.forEach(function(aInstall) { 1.143 + info("Install for " + aInstall.sourceURI + " is in state " + aInstall.state); 1.144 + }); 1.145 + 1.146 + self.installBlockedCallback = null; 1.147 + self.authenticationCallback = null; 1.148 + self.installConfirmCallback = null; 1.149 + self.downloadStartedCallback = null; 1.150 + self.downloadProgressCallback = null; 1.151 + self.downloadCancelledCallback = null; 1.152 + self.downloadFailedCallback = null; 1.153 + self.downloadEndedCallback = null; 1.154 + self.installStartedCallback = null; 1.155 + self.installFailedCallback = null; 1.156 + self.installEndedCallback = null; 1.157 + self.installsCompletedCallback = null; 1.158 + self.runningInstalls = null; 1.159 + 1.160 + if (callback) 1.161 + callback(count); 1.162 + }); 1.163 + }, 1.164 + 1.165 + // Window open handling 1.166 + windowReady: function(window) { 1.167 + if (window.document.location.href == XPINSTALL_URL) { 1.168 + if (this.installBlockedCallback) 1.169 + ok(false, "Should have been blocked by the whitelist"); 1.170 + this.pendingCount = window.document.getElementById("itemList").childNodes.length; 1.171 + 1.172 + // If there is a confirm callback then its return status determines whether 1.173 + // to install the items or not. If not the test is over. 1.174 + if (this.installConfirmCallback && !this.installConfirmCallback(window)) { 1.175 + window.document.documentElement.cancelDialog(); 1.176 + } 1.177 + else { 1.178 + // Initially the accept button is disabled on a countdown timer 1.179 + var button = window.document.documentElement.getButton("accept"); 1.180 + button.disabled = false; 1.181 + window.document.documentElement.acceptDialog(); 1.182 + } 1.183 + } 1.184 + else if (window.document.location.href == PROMPT_URL) { 1.185 + var promptType = window.args.promptType; 1.186 + switch (promptType) { 1.187 + case "alert": 1.188 + case "alertCheck": 1.189 + case "confirmCheck": 1.190 + case "confirm": 1.191 + case "confirmEx": 1.192 + window.document.documentElement.acceptDialog(); 1.193 + break; 1.194 + case "promptUserAndPass": 1.195 + // This is a login dialog, hopefully an authentication prompt 1.196 + // for the xpi. 1.197 + if (this.authenticationCallback) { 1.198 + var auth = this.authenticationCallback(); 1.199 + if (auth && auth.length == 2) { 1.200 + window.document.getElementById("loginTextbox").value = auth[0]; 1.201 + window.document.getElementById("password1Textbox").value = auth[1]; 1.202 + window.document.documentElement.acceptDialog(); 1.203 + } 1.204 + else { 1.205 + window.document.documentElement.cancelDialog(); 1.206 + } 1.207 + } 1.208 + else { 1.209 + window.document.documentElement.cancelDialog(); 1.210 + } 1.211 + break; 1.212 + default: 1.213 + ok(false, "prompt type " + promptType + " not handled in test."); 1.214 + break; 1.215 + } 1.216 + } 1.217 + }, 1.218 + 1.219 + // Install blocked handling 1.220 + 1.221 + installDisabled: function(installInfo) { 1.222 + ok(!!this.installDisabledCallback, "Installation shouldn't have been disabled"); 1.223 + if (this.installDisabledCallback) 1.224 + this.installDisabledCallback(installInfo); 1.225 + this.expectingCancelled = true; 1.226 + installInfo.installs.forEach(function(install) { 1.227 + install.cancel(); 1.228 + }); 1.229 + this.expectingCancelled = false; 1.230 + this.endTest(); 1.231 + }, 1.232 + 1.233 + installCancelled: function(installInfo) { 1.234 + if (this.expectingCancelled) 1.235 + return; 1.236 + 1.237 + ok(!!this.installCancelledCallback, "Installation shouldn't have been cancelled"); 1.238 + if (this.installCancelledCallback) 1.239 + this.installCancelledCallback(installInfo); 1.240 + this.endTest(); 1.241 + }, 1.242 + 1.243 + installBlocked: function(installInfo) { 1.244 + ok(!!this.installBlockedCallback, "Shouldn't have been blocked by the whitelist"); 1.245 + if (this.installBlockedCallback && this.installBlockedCallback(installInfo)) { 1.246 + this.installBlockedCallback = null; 1.247 + installInfo.install(); 1.248 + } 1.249 + else { 1.250 + this.expectingCancelled = true; 1.251 + installInfo.installs.forEach(function(install) { 1.252 + install.cancel(); 1.253 + }); 1.254 + this.expectingCancelled = false; 1.255 + this.endTest(); 1.256 + } 1.257 + }, 1.258 + 1.259 + // nsIWindowMediatorListener 1.260 + 1.261 + onWindowTitleChange: function(window, title) { 1.262 + }, 1.263 + 1.264 + onOpenWindow: function(window) { 1.265 + var domwindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) 1.266 + .getInterface(Components.interfaces.nsIDOMWindow); 1.267 + var self = this; 1.268 + waitForFocus(function() { 1.269 + self.windowReady(domwindow); 1.270 + }, domwindow); 1.271 + }, 1.272 + 1.273 + onCloseWindow: function(window) { 1.274 + }, 1.275 + 1.276 + // Addon Install Listener 1.277 + 1.278 + onNewInstall: function(install) { 1.279 + this.runningInstalls.push(install); 1.280 + }, 1.281 + 1.282 + onDownloadStarted: function(install) { 1.283 + this.pendingCount++; 1.284 + if (this.downloadStartedCallback) 1.285 + this.downloadStartedCallback(install); 1.286 + }, 1.287 + 1.288 + onDownloadProgress: function(install) { 1.289 + if (this.downloadProgressCallback) 1.290 + this.downloadProgressCallback(install); 1.291 + }, 1.292 + 1.293 + onDownloadEnded: function(install) { 1.294 + if (this.downloadEndedCallback) 1.295 + this.downloadEndedCallback(install); 1.296 + }, 1.297 + 1.298 + onDownloadCancelled: function(install) { 1.299 + isnot(this.runningInstalls.indexOf(install), -1, 1.300 + "Should only see cancelations for started installs"); 1.301 + this.runningInstalls.splice(this.runningInstalls.indexOf(install), 1); 1.302 + 1.303 + if (this.downloadCancelledCallback) 1.304 + this.downloadCancelledCallback(install); 1.305 + this.checkTestEnded(); 1.306 + }, 1.307 + 1.308 + onDownloadFailed: function(install) { 1.309 + if (this.downloadFailedCallback) 1.310 + this.downloadFailedCallback(install); 1.311 + this.checkTestEnded(); 1.312 + }, 1.313 + 1.314 + onInstallStarted: function(install) { 1.315 + if (this.installStartedCallback) 1.316 + this.installStartedCallback(install); 1.317 + }, 1.318 + 1.319 + onInstallEnded: function(install, addon) { 1.320 + if (this.installEndedCallback) 1.321 + this.installEndedCallback(install, addon); 1.322 + this.installCount++; 1.323 + this.checkTestEnded(); 1.324 + }, 1.325 + 1.326 + onInstallFailed: function(install) { 1.327 + if (this.installFailedCallback) 1.328 + this.installFailedCallback(install); 1.329 + this.checkTestEnded(); 1.330 + }, 1.331 + 1.332 + checkTestEnded: function() { 1.333 + if (--this.pendingCount == 0) 1.334 + this.endTest(); 1.335 + }, 1.336 + 1.337 + // nsIObserver 1.338 + 1.339 + observe: function(subject, topic, data) { 1.340 + var installInfo = subject.QueryInterface(Components.interfaces.amIWebInstallInfo); 1.341 + switch (topic) { 1.342 + case "addon-install-started": 1.343 + is(this.runningInstalls.length, installInfo.installs.length, 1.344 + "Should have seen the expected number of installs started"); 1.345 + break; 1.346 + case "addon-install-disabled": 1.347 + this.installDisabled(installInfo); 1.348 + break; 1.349 + case "addon-install-cancelled": 1.350 + this.installCancelled(installInfo); 1.351 + break; 1.352 + case "addon-install-blocked": 1.353 + this.installBlocked(installInfo); 1.354 + break; 1.355 + case "addon-install-failed": 1.356 + installInfo.installs.forEach(function(aInstall) { 1.357 + isnot(this.runningInstalls.indexOf(aInstall), -1, 1.358 + "Should only see failures for started installs"); 1.359 + 1.360 + ok(aInstall.error != 0 || aInstall.addon.appDisabled, 1.361 + "Failed installs should have an error or be appDisabled"); 1.362 + 1.363 + this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); 1.364 + }, this); 1.365 + break; 1.366 + case "addon-install-complete": 1.367 + installInfo.installs.forEach(function(aInstall) { 1.368 + isnot(this.runningInstalls.indexOf(aInstall), -1, 1.369 + "Should only see completed events for started installs"); 1.370 + 1.371 + is(aInstall.error, 0, "Completed installs should have no error"); 1.372 + ok(!aInstall.appDisabled, "Completed installs should not be appDisabled"); 1.373 + 1.374 + // Complete installs are either in the INSTALLED or CANCELLED state 1.375 + // since the test may cancel installs the moment they complete. 1.376 + ok(aInstall.state == AddonManager.STATE_INSTALLED || 1.377 + aInstall.state == AddonManager.STATE_CANCELLED, 1.378 + "Completed installs should be in the right state"); 1.379 + 1.380 + this.runningInstalls.splice(this.runningInstalls.indexOf(aInstall), 1); 1.381 + }, this); 1.382 + break; 1.383 + } 1.384 + }, 1.385 + 1.386 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, 1.387 + Ci.nsIWindowMediatorListener, 1.388 + Ci.nsISupports]) 1.389 +}