browser_addons_reload.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /* Any copyright is dedicated to the Public Domain.
  2. http://creativecommons.org/publicdomain/zero/1.0/ */
  3. "use strict";
  4. const ADDON_ID = "test-devtools@mozilla.org";
  5. const ADDON_NAME = "test-devtools";
  6. /**
  7. * Returns a promise that resolves when the given add-on event is fired. The
  8. * resolved value is an array of arguments passed for the event.
  9. */
  10. function promiseAddonEvent(event) {
  11. return new Promise(resolve => {
  12. let listener = {
  13. [event]: function (...args) {
  14. AddonManager.removeAddonListener(listener);
  15. resolve(args);
  16. }
  17. };
  18. AddonManager.addAddonListener(listener);
  19. });
  20. }
  21. function* tearDownAddon(addon) {
  22. const onUninstalled = promiseAddonEvent("onUninstalled");
  23. addon.uninstall();
  24. const [uninstalledAddon] = yield onUninstalled;
  25. is(uninstalledAddon.id, addon.id,
  26. `Add-on was uninstalled: ${uninstalledAddon.id}`);
  27. }
  28. function getReloadButton(document, addonName) {
  29. const names = [...document.querySelectorAll("#addons .target-name")];
  30. const name = names.filter(element => element.textContent === addonName)[0];
  31. ok(name, `Found ${addonName} add-on in the list`);
  32. const targetElement = name.parentNode.parentNode;
  33. const reloadButton = targetElement.querySelector(".reload-button");
  34. info(`Found reload button for ${addonName}`);
  35. return reloadButton;
  36. }
  37. function installAddonWithManager(filePath) {
  38. return new Promise((resolve, reject) => {
  39. AddonManager.getInstallForFile(filePath, install => {
  40. if (!install) {
  41. throw new Error(`An install was not created for ${filePath}`);
  42. }
  43. install.addListener({
  44. onDownloadFailed: reject,
  45. onDownloadCancelled: reject,
  46. onInstallFailed: reject,
  47. onInstallCancelled: reject,
  48. onInstallEnded: resolve
  49. });
  50. install.install();
  51. });
  52. });
  53. }
  54. function getAddonByID(addonId) {
  55. return new Promise(resolve => {
  56. AddonManager.getAddonByID(addonId, addon => resolve(addon));
  57. });
  58. }
  59. /**
  60. * Creates a web extension from scratch in a temporary location.
  61. * The object must be removed when you're finished working with it.
  62. */
  63. class TempWebExt {
  64. constructor(addonId) {
  65. this.addonId = addonId;
  66. this.tmpDir = FileUtils.getDir("TmpD", ["browser_addons_reload"]);
  67. if (!this.tmpDir.exists()) {
  68. this.tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
  69. }
  70. this.sourceDir = this.tmpDir.clone();
  71. this.sourceDir.append(this.addonId);
  72. if (!this.sourceDir.exists()) {
  73. this.sourceDir.create(Ci.nsIFile.DIRECTORY_TYPE,
  74. FileUtils.PERMS_DIRECTORY);
  75. }
  76. }
  77. writeManifest(manifestData) {
  78. const manifest = this.sourceDir.clone();
  79. manifest.append("manifest.json");
  80. if (manifest.exists()) {
  81. manifest.remove(true);
  82. }
  83. const fos = Cc["@mozilla.org/network/file-output-stream;1"]
  84. .createInstance(Ci.nsIFileOutputStream);
  85. fos.init(manifest,
  86. FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
  87. FileUtils.MODE_TRUNCATE,
  88. FileUtils.PERMS_FILE, 0);
  89. const manifestString = JSON.stringify(manifestData);
  90. fos.write(manifestString, manifestString.length);
  91. fos.close();
  92. }
  93. remove() {
  94. return this.tmpDir.remove(true);
  95. }
  96. }
  97. add_task(function* reloadButtonReloadsAddon() {
  98. const { tab, document } = yield openAboutDebugging("addons");
  99. yield waitForInitialAddonList(document);
  100. yield installAddon({
  101. document,
  102. path: "addons/unpacked/install.rdf",
  103. name: ADDON_NAME,
  104. });
  105. const reloadButton = getReloadButton(document, ADDON_NAME);
  106. is(reloadButton.disabled, false, "Reload button should not be disabled");
  107. is(reloadButton.title, "", "Reload button should not have a tooltip");
  108. const onInstalled = promiseAddonEvent("onInstalled");
  109. const onBootstrapInstallCalled = new Promise(done => {
  110. Services.obs.addObserver(function listener() {
  111. Services.obs.removeObserver(listener, ADDON_NAME, false);
  112. info("Add-on was re-installed: " + ADDON_NAME);
  113. done();
  114. }, ADDON_NAME, false);
  115. });
  116. reloadButton.click();
  117. const [reloadedAddon] = yield onInstalled;
  118. is(reloadedAddon.name, ADDON_NAME,
  119. "Add-on was reloaded: " + reloadedAddon.name);
  120. yield onBootstrapInstallCalled;
  121. yield tearDownAddon(reloadedAddon);
  122. yield closeAboutDebugging(tab);
  123. });
  124. add_task(function* reloadButtonRefreshesMetadata() {
  125. const { tab, document } = yield openAboutDebugging("addons");
  126. yield waitForInitialAddonList(document);
  127. const manifestBase = {
  128. "manifest_version": 2,
  129. "name": "Temporary web extension",
  130. "version": "1.0",
  131. "applications": {
  132. "gecko": {
  133. "id": ADDON_ID
  134. }
  135. }
  136. };
  137. const tempExt = new TempWebExt(ADDON_ID);
  138. tempExt.writeManifest(manifestBase);
  139. const onAddonListUpdated = waitForMutation(getAddonList(document),
  140. { childList: true });
  141. const onInstalled = promiseAddonEvent("onInstalled");
  142. yield AddonManager.installTemporaryAddon(tempExt.sourceDir);
  143. const [addon] = yield onInstalled;
  144. info(`addon installed: ${addon.id}`);
  145. yield onAddonListUpdated;
  146. const newName = "Temporary web extension (updated)";
  147. tempExt.writeManifest(Object.assign({}, manifestBase, {name: newName}));
  148. // Wait for the add-on list to be updated with the reloaded name.
  149. const onReInstall = promiseAddonEvent("onInstalled");
  150. const onAddonReloaded = waitForContentMutation(getAddonList(document));
  151. const reloadButton = getReloadButton(document, manifestBase.name);
  152. reloadButton.click();
  153. yield onAddonReloaded;
  154. const [reloadedAddon] = yield onReInstall;
  155. // Make sure the name was updated correctly.
  156. const allAddons = [...document.querySelectorAll("#addons .target-name")]
  157. .map(element => element.textContent);
  158. const nameWasUpdated = allAddons.some(name => name === newName);
  159. ok(nameWasUpdated, `New name appeared in reloaded add-ons: ${allAddons}`);
  160. yield tearDownAddon(reloadedAddon);
  161. tempExt.remove();
  162. yield closeAboutDebugging(tab);
  163. });
  164. add_task(function* onlyTempInstalledAddonsCanBeReloaded() {
  165. const { tab, document } = yield openAboutDebugging("addons");
  166. yield waitForInitialAddonList(document);
  167. const onAddonListUpdated = waitForMutation(getAddonList(document),
  168. { childList: true });
  169. yield installAddonWithManager(getSupportsFile("addons/bug1273184.xpi").file);
  170. yield onAddonListUpdated;
  171. const addon = yield getAddonByID("bug1273184@tests");
  172. const reloadButton = getReloadButton(document, addon.name);
  173. ok(reloadButton, "Reload button exists");
  174. is(reloadButton.disabled, true, "Reload button should be disabled");
  175. ok(reloadButton.title, "Disabled reload button should have a tooltip");
  176. yield tearDownAddon(addon);
  177. yield closeAboutDebugging(tab);
  178. });