|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ |
|
3 */ |
|
4 |
|
5 Components.utils.import("resource://gre/modules/NetUtil.jsm"); |
|
6 |
|
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; |
|
13 |
|
14 var pathParts = gTestPath.split("/"); |
|
15 // Drop the test filename |
|
16 pathParts.splice(pathParts.length - 1, pathParts.length); |
|
17 |
|
18 var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]); |
|
19 |
|
20 // Drop the UI type |
|
21 if (gTestInWindow) { |
|
22 pathParts.splice(pathParts.length - 1, pathParts.length); |
|
23 } |
|
24 |
|
25 const RELATIVE_DIR = pathParts.slice(4).join("/") + "/"; |
|
26 |
|
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"; |
|
35 |
|
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"; |
|
41 |
|
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 })(); |
|
57 |
|
58 var gPendingTests = []; |
|
59 var gTestsRun = 0; |
|
60 var gTestStart = null; |
|
61 |
|
62 var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window); |
|
63 |
|
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}]; |
|
80 |
|
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 } |
|
94 |
|
95 // Turn logging on for all tests |
|
96 Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); |
|
97 |
|
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 } |
|
112 |
|
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 } |
|
125 |
|
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"); |
|
130 |
|
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; |
|
136 |
|
137 ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state); |
|
138 install.cancel(); |
|
139 } |
|
140 }); |
|
141 }); |
|
142 |
|
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 } |
|
152 |
|
153 function log_callback(aPromise, aCallback) { |
|
154 aPromise.then(aCallback) |
|
155 .then(null, e => info("Exception thrown: " + e)); |
|
156 return aPromise; |
|
157 } |
|
158 |
|
159 function add_test(test) { |
|
160 gPendingTests.push(test); |
|
161 } |
|
162 |
|
163 function run_next_test() { |
|
164 if (gTestsRun > 0) |
|
165 info("Test " + gTestsRun + " took " + (Date.now() - gTestStart) + "ms"); |
|
166 |
|
167 if (gPendingTests.length == 0) { |
|
168 end_test(); |
|
169 return; |
|
170 } |
|
171 |
|
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); |
|
178 |
|
179 gTestStart = Date.now(); |
|
180 log_exceptions(test); |
|
181 } |
|
182 |
|
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); |
|
193 |
|
194 return Services.io.newFileURI(tmpDir).QueryInterface(Ci.nsIFileURL); |
|
195 } |
|
196 } |
|
197 |
|
198 function get_test_items_in_list(aManager) { |
|
199 var tests = "@tests.mozilla.org"; |
|
200 |
|
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 = []; |
|
205 |
|
206 while (item) { |
|
207 if (item.localName != "richlistitem") { |
|
208 item = item.nextSibling; |
|
209 continue; |
|
210 } |
|
211 |
|
212 if (!item.mAddon || item.mAddon.id.substring(item.mAddon.id.length - tests.length) == tests) |
|
213 items.push(item); |
|
214 item = item.nextSibling; |
|
215 } |
|
216 |
|
217 return items; |
|
218 } |
|
219 |
|
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); |
|
225 |
|
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 } |
|
233 |
|
234 for (let id of aIds) { |
|
235 if (inlist.indexOf(id) == -1) |
|
236 ok(false, "Should find " + id + " in the list"); |
|
237 } |
|
238 |
|
239 if (aIgnoreExtras) |
|
240 return; |
|
241 |
|
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 } |
|
247 |
|
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); |
|
257 |
|
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 } |
|
266 |
|
267 function wait_for_view_load(aManagerWindow, aCallback, aForceWait, aLongerTimeout) { |
|
268 requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); |
|
269 |
|
270 if (!aForceWait && !aManagerWindow.gViewController.isLoading) { |
|
271 log_exceptions(aCallback, aManagerWindow); |
|
272 return; |
|
273 } |
|
274 |
|
275 aManagerWindow.document.addEventListener("ViewChanged", function() { |
|
276 aManagerWindow.document.removeEventListener("ViewChanged", arguments.callee, false); |
|
277 log_exceptions(aCallback, aManagerWindow); |
|
278 }, false); |
|
279 } |
|
280 |
|
281 function wait_for_manager_load(aManagerWindow, aCallback) { |
|
282 if (!aManagerWindow.gIsInitializing) { |
|
283 log_exceptions(aCallback, aManagerWindow); |
|
284 return; |
|
285 } |
|
286 |
|
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 } |
|
293 |
|
294 function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) { |
|
295 let p = new Promise((resolve, reject) => { |
|
296 |
|
297 function setup_manager(aManagerWindow) { |
|
298 if (aLoadCallback) |
|
299 log_exceptions(aLoadCallback, aManagerWindow); |
|
300 |
|
301 if (aView) |
|
302 aManagerWindow.loadView(aView); |
|
303 |
|
304 ok(aManagerWindow != null, "Should have an add-ons manager window"); |
|
305 is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI"); |
|
306 |
|
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 } |
|
317 |
|
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); |
|
328 |
|
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); |
|
337 |
|
338 openDialog(MANAGER_URI); |
|
339 } |
|
340 }); |
|
341 |
|
342 // The promise resolves with the manager window, so it is passed to the callback |
|
343 return log_callback(p, aCallback); |
|
344 } |
|
345 |
|
346 function close_manager(aManagerWindow, aCallback, aLongerTimeout) { |
|
347 let p = new Promise((resolve, reject) => { |
|
348 requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); |
|
349 |
|
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"); |
|
352 |
|
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 }); |
|
363 |
|
364 info("Telling manager window to close"); |
|
365 aManagerWindow.close(); |
|
366 info("Manager window close() call returned"); |
|
367 |
|
368 return log_callback(p, aCallback); |
|
369 } |
|
370 |
|
371 function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) { |
|
372 if (!aManagerWindow) { |
|
373 return open_manager(aView, aCallback, aLoadCallback); |
|
374 } |
|
375 |
|
376 return close_manager(aManagerWindow) |
|
377 .then(() => open_manager(aView, aCallback, aLoadCallback)); |
|
378 } |
|
379 |
|
380 function wait_for_window_open(aCallback) { |
|
381 Services.wm.addListener({ |
|
382 onOpenWindow: function(aWindow) { |
|
383 Services.wm.removeListener(this); |
|
384 |
|
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 }, |
|
394 |
|
395 onCloseWindow: function(aWindow) { |
|
396 }, |
|
397 |
|
398 onWindowTitleChange: function(aWindow, aTitle) { |
|
399 } |
|
400 }); |
|
401 } |
|
402 |
|
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 } |
|
409 |
|
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 } |
|
420 |
|
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; |
|
427 |
|
428 // Hiding a parent element will hide all its children |
|
429 if (aElement.parentNode != aElement.ownerDocument) |
|
430 return is_hidden(aElement.parentNode); |
|
431 |
|
432 return false; |
|
433 } |
|
434 |
|
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 } |
|
439 |
|
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 } |
|
444 |
|
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 }); |
|
456 |
|
457 install.install(); |
|
458 }, "application/x-xpinstall"); |
|
459 }); |
|
460 |
|
461 return log_callback(p, cb); |
|
462 } |
|
463 |
|
464 function CategoryUtilities(aManagerWindow) { |
|
465 this.window = aManagerWindow; |
|
466 |
|
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 } |
|
473 |
|
474 CategoryUtilities.prototype = { |
|
475 window: null, |
|
476 |
|
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 }, |
|
484 |
|
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"); |
|
488 |
|
489 var viewId = "addons://list/" + aCategoryType; |
|
490 var items = categories.getElementsByAttribute("value", viewId); |
|
491 if (items.length) |
|
492 return items[0]; |
|
493 |
|
494 viewId = "addons://" + aCategoryType + "/"; |
|
495 items = categories.getElementsByAttribute("value", viewId); |
|
496 if (items.length) |
|
497 return items[0]; |
|
498 |
|
499 if (!aAllowMissing) |
|
500 ok(false, "Should have found a category with type " + aCategoryType); |
|
501 return null; |
|
502 }, |
|
503 |
|
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 }, |
|
508 |
|
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; |
|
514 |
|
515 return !is_hidden(aCategory); |
|
516 }, |
|
517 |
|
518 isTypeVisible: function(aCategoryType) { |
|
519 return this.isVisible(this.get(aCategoryType)); |
|
520 }, |
|
521 |
|
522 open: function(aCategory, aCallback) { |
|
523 |
|
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"); |
|
526 |
|
527 EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window); |
|
528 let p = new Promise((resolve, reject) => wait_for_view_load(this.window, resolve)); |
|
529 |
|
530 return log_callback(p, aCallback); |
|
531 }, |
|
532 |
|
533 openType: function(aCategoryType, aCallback) { |
|
534 return this.open(this.get(aCategoryType), aCallback); |
|
535 } |
|
536 } |
|
537 |
|
538 function CertOverrideListener(host, bits) { |
|
539 this.host = host; |
|
540 this.bits = bits; |
|
541 } |
|
542 |
|
543 CertOverrideListener.prototype = { |
|
544 host: null, |
|
545 bits: null, |
|
546 |
|
547 getInterface: function (aIID) { |
|
548 return this.QueryInterface(aIID); |
|
549 }, |
|
550 |
|
551 QueryInterface: function(aIID) { |
|
552 if (aIID.equals(Ci.nsIBadCertListener2) || |
|
553 aIID.equals(Ci.nsIInterfaceRequestor) || |
|
554 aIID.equals(Ci.nsISupports)) |
|
555 return this; |
|
556 |
|
557 throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE); |
|
558 }, |
|
559 |
|
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 } |
|
569 |
|
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 } |
|
582 |
|
583 /***** Mock Provider *****/ |
|
584 |
|
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; |
|
597 |
|
598 var self = this; |
|
599 registerCleanupFunction(function() { |
|
600 if (self.started) |
|
601 self.unregister(); |
|
602 }); |
|
603 |
|
604 this.register(); |
|
605 } |
|
606 |
|
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, |
|
616 |
|
617 /***** Utility functions *****/ |
|
618 |
|
619 /** |
|
620 * Register this provider with the AddonManager |
|
621 */ |
|
622 register: function MP_register() { |
|
623 AddonManagerPrivate.registerProvider(this, this.types); |
|
624 }, |
|
625 |
|
626 /** |
|
627 * Unregister this provider with the AddonManager |
|
628 */ |
|
629 unregister: function MP_unregister() { |
|
630 AddonManagerPrivate.unregisterProvider(this); |
|
631 }, |
|
632 |
|
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; |
|
643 |
|
644 this.addons = this.addons.filter(function(aOldAddon) aOldAddon.id != aAddon.id); |
|
645 |
|
646 this.addons.push(aAddon); |
|
647 aAddon._provider = this; |
|
648 |
|
649 if (!this.started) |
|
650 return; |
|
651 |
|
652 let requiresRestart = (aAddon.operationsRequiringRestart & |
|
653 AddonManager.OP_NEEDS_RESTART_INSTALL) != 0; |
|
654 AddonManagerPrivate.callInstallListeners("onExternalInstall", null, aAddon, |
|
655 oldAddon, requiresRestart) |
|
656 }, |
|
657 |
|
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 } |
|
671 |
|
672 this.addons.splice(pos, 1); |
|
673 |
|
674 if (!this.started) |
|
675 return; |
|
676 |
|
677 AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon); |
|
678 }, |
|
679 |
|
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; |
|
690 |
|
691 if (!this.started) |
|
692 return; |
|
693 |
|
694 aInstall.callListeners("onNewInstall"); |
|
695 }, |
|
696 |
|
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 } |
|
703 |
|
704 this.installs.splice(pos, 1); |
|
705 }, |
|
706 |
|
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; |
|
734 |
|
735 // Make sure the active state matches the passed in properties |
|
736 addon.isActive = addon.shouldBeActive; |
|
737 |
|
738 this.addAddon(addon); |
|
739 newAddons.push(addon); |
|
740 } |
|
741 |
|
742 return newAddons; |
|
743 }, |
|
744 |
|
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 } |
|
774 |
|
775 return newInstalls; |
|
776 }, |
|
777 |
|
778 /***** AddonProvider implementation *****/ |
|
779 |
|
780 /** |
|
781 * Called to initialize the provider. |
|
782 */ |
|
783 startup: function MP_startup() { |
|
784 this.started = true; |
|
785 }, |
|
786 |
|
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; |
|
808 |
|
809 this.started = false; |
|
810 }, |
|
811 |
|
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 } |
|
827 |
|
828 aCallback(null); |
|
829 }, |
|
830 |
|
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 }, |
|
847 |
|
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 }, |
|
864 |
|
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; |
|
878 |
|
879 if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1) |
|
880 return false; |
|
881 |
|
882 return true; |
|
883 }); |
|
884 this._delayCallback(aCallback, installs); |
|
885 }, |
|
886 |
|
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 }, |
|
902 |
|
903 /** |
|
904 * Update the appDisabled property for all add-ons. |
|
905 */ |
|
906 updateAddonAppDisabledStates: function MP_updateAddonAppDisabledStates() { |
|
907 // Not needed |
|
908 }, |
|
909 |
|
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 }, |
|
932 |
|
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 }, |
|
944 |
|
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 }, |
|
953 |
|
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 }, |
|
965 |
|
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 }, |
|
976 |
|
977 |
|
978 /***** Internal functions *****/ |
|
979 |
|
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 } |
|
993 |
|
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); |
|
1007 } |
|
1008 this.timerLocations.delete(timer); |
|
1009 aCallback(...aArgs); |
|
1010 }, this.apiDelay, timer.TYPE_ONE_SHOT); |
|
1011 } |
|
1012 }; |
|
1013 |
|
1014 /***** Mock Addon object for the Mock Provider *****/ |
|
1015 |
|
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); |
|
1042 } |
|
1043 |
|
1044 MockAddon.prototype = { |
|
1045 get shouldBeActive() { |
|
1046 return !this.appDisabled && !this._userDisabled; |
|
1047 }, |
|
1048 |
|
1049 get appDisabled() { |
|
1050 return this._appDisabled; |
|
1051 }, |
|
1052 |
|
1053 set appDisabled(val) { |
|
1054 if (val == this._appDisabled) |
|
1055 return val; |
|
1056 |
|
1057 AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["appDisabled"]); |
|
1058 |
|
1059 var currentActive = this.shouldBeActive; |
|
1060 this._appDisabled = val; |
|
1061 var newActive = this.shouldBeActive; |
|
1062 this._updateActiveState(currentActive, newActive); |
|
1063 |
|
1064 return val; |
|
1065 }, |
|
1066 |
|
1067 get userDisabled() { |
|
1068 return this._userDisabled; |
|
1069 }, |
|
1070 |
|
1071 set userDisabled(val) { |
|
1072 if (val == this._userDisabled) |
|
1073 return val; |
|
1074 |
|
1075 var currentActive = this.shouldBeActive; |
|
1076 this._userDisabled = val; |
|
1077 var newActive = this.shouldBeActive; |
|
1078 this._updateActiveState(currentActive, newActive); |
|
1079 |
|
1080 return val; |
|
1081 }, |
|
1082 |
|
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 }, |
|
1091 |
|
1092 set permissions(val) { |
|
1093 return this._permissions = val; |
|
1094 }, |
|
1095 |
|
1096 get applyBackgroundUpdates() { |
|
1097 return this._applyBackgroundUpdates; |
|
1098 }, |
|
1099 |
|
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); |
|
1105 } |
|
1106 this._applyBackgroundUpdates = val; |
|
1107 AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); |
|
1108 }, |
|
1109 |
|
1110 isCompatibleWith: function(aAppVersion, aPlatformVersion) { |
|
1111 return true; |
|
1112 }, |
|
1113 |
|
1114 findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) { |
|
1115 // Tests can implement this if they need to |
|
1116 }, |
|
1117 |
|
1118 uninstall: function() { |
|
1119 if (this.pendingOperations & AddonManager.PENDING_UNINSTALL) |
|
1120 throw Components.Exception("Add-on is already pending uninstall"); |
|
1121 |
|
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); |
|
1128 } |
|
1129 }, |
|
1130 |
|
1131 cancelUninstall: function() { |
|
1132 if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL)) |
|
1133 throw Components.Exception("Add-on is not pending uninstall"); |
|
1134 |
|
1135 this.pendingOperations -= AddonManager.PENDING_UNINSTALL; |
|
1136 AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); |
|
1137 }, |
|
1138 |
|
1139 _updateActiveState: function(currentActive, newActive) { |
|
1140 if (currentActive == newActive) |
|
1141 return; |
|
1142 |
|
1143 if (newActive == this.isActive) { |
|
1144 this.pendingOperations -= (newActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE); |
|
1145 AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); |
|
1146 } |
|
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); |
|
1155 } |
|
1156 } |
|
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); |
|
1165 } |
|
1166 } |
|
1167 } |
|
1168 }; |
|
1169 |
|
1170 /***** Mock AddonInstall object for the Mock Provider *****/ |
|
1171 |
|
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 = []; |
|
1192 |
|
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 = []; |
|
1196 } |
|
1197 |
|
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; |
|
1207 } |
|
1208 |
|
1209 this.type = this._type; |
|
1210 |
|
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; |
|
1218 } |
|
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; |
|
1225 } |
|
1226 |
|
1227 this.state = AddonManager.STATE_DOWNLOADED; |
|
1228 this.callListeners("onDownloadEnded"); |
|
1229 |
|
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; |
|
1236 } |
|
1237 |
|
1238 AddonManagerPrivate.callAddonListeners("onInstalling", this.addon); |
|
1239 |
|
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); |
|
1250 } |
|
1251 }, |
|
1252 |
|
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); |
|
1266 } |
|
1267 }, |
|
1268 |
|
1269 |
|
1270 addListener: function(aListener) { |
|
1271 if (!this.listeners.some(function(i) i == aListener)) |
|
1272 this.listeners.push(aListener); |
|
1273 }, |
|
1274 |
|
1275 removeListener: function(aListener) { |
|
1276 this.listeners = this.listeners.filter(function(i) i != aListener); |
|
1277 }, |
|
1278 |
|
1279 addTestListener: function(aListener) { |
|
1280 if (!this.testListeners.some(function(i) i == aListener)) |
|
1281 this.testListeners.push(aListener); |
|
1282 }, |
|
1283 |
|
1284 removeTestListener: function(aListener) { |
|
1285 this.testListeners = this.testListeners.filter(function(i) i != aListener); |
|
1286 }, |
|
1287 |
|
1288 callListeners: function(aMethod) { |
|
1289 var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners, |
|
1290 this, this.addon); |
|
1291 |
|
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; |
|
1299 } |
|
1300 catch (e) { |
|
1301 ok(false, "Test listener threw exception: " + e); |
|
1302 } |
|
1303 } |
|
1304 |
|
1305 return result; |
|
1306 } |
|
1307 }; |
|
1308 |
|
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(); |
|
1315 } |
|
1316 var conditionPassed; |
|
1317 try { |
|
1318 conditionPassed = condition(); |
|
1319 } catch (e) { |
|
1320 ok(false, e + "\n" + e.stack); |
|
1321 conditionPassed = false; |
|
1322 } |
|
1323 if (conditionPassed) { |
|
1324 moveOn(); |
|
1325 } |
|
1326 tries++; |
|
1327 }, 100); |
|
1328 let moveOn = function() { clearInterval(interval); nextTest(); }; |
|
1329 } |
|
1330 |
|
1331 function getTestPluginTag() { |
|
1332 let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
|
1333 let tags = ph.getPluginTags(); |
|
1334 |
|
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]; |
|
1339 } |
|
1340 ok(false, "Unable to find plugin"); |
|
1341 return null; |
|
1342 } |