Bug 1544950 - Part 1: Add-on warning messages for HTML about:addons r=rpl a=jcristau
authorMark Striemer <mstriemer@mozilla.com>
Thu, 13 Jun 2019 03:08:59 +0000
changeset 536931 591c4abc2dd073781bb75c61b05df3e9fc572350
parent 536930 1f82fe365272472c28fb2c988ce6124bdec1fd7c
child 536932 7a85d0459d6d449791e31f6d4ee1e108bab183a6
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrpl, jcristau
bugs1544950
milestone68.0
Bug 1544950 - Part 1: Add-on warning messages for HTML about:addons r=rpl a=jcristau Differential Revision: https://phabricator.services.mozilla.com/D34449
toolkit/mozapps/extensions/content/aboutaddons.css
toolkit/mozapps/extensions/content/aboutaddons.html
toolkit/mozapps/extensions/content/aboutaddons.js
toolkit/mozapps/extensions/content/aboutaddonsCommon.js
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/message-bar.css
toolkit/mozapps/extensions/test/browser/browser.ini
toolkit/mozapps/extensions/test/browser/browser_bug590347.js
toolkit/mozapps/extensions/test/browser/browser_html_warning_messages.js
toolkit/mozapps/extensions/test/browser/browser_langpack_signing.js
--- a/toolkit/mozapps/extensions/content/aboutaddons.css
+++ b/toolkit/mozapps/extensions/content/aboutaddons.css
@@ -67,16 +67,27 @@ addon-card[expanded] .addon.card {
 .addon-card-collapsed {
   display: flex;
 }
 
 addon-list addon-card > .addon.card {
   -moz-user-select: none;
 }
 
+.addon-card-message {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+  margin: 8px calc(var(--card-padding) * -1) calc(var(--card-padding) * -1);
+}
+
+addon-card[expanded] .addon-card-message {
+  border-radius: 0;
+  margin-bottom: 0;
+}
+
 /* Theme preview image. */
 .card-heading-image {
   /* If the width, height or aspect ratio changes, don't forget to update the
    * getScreenshotUrlForAddon function in aboutaddons.js */
   width: var(--section-width);
   /* Adjust height so that the image preserves the aspect ratio from AMO.
    * For details, see https://bugzilla.mozilla.org/show_bug.cgi?id=1546123 */
   height: calc(var(--section-width) * 92 / 680);
--- a/toolkit/mozapps/extensions/content/aboutaddons.html
+++ b/toolkit/mozapps/extensions/content/aboutaddons.html
@@ -65,16 +65,20 @@
                     hidden></div>
             </div>
             <span class="addon-description"></span>
           </div>
           <div class="more-options-menu">
             <button class="more-options-button ghost-button" action="more-options"></button>
           </div>
         </div>
+        <message-bar class="addon-card-message" align="center" hidden>
+          <span></span>
+          <button action="link"></button>
+        </message-bar>
       </div>
     </template>
 
     <template name="addon-name-container-in-disco-card">
       <div class="disco-card-head">
         <span class="disco-addon-name"></span>
         <span class="disco-addon-author"><a data-l10n-name="author" target="_blank"></a></span>
       </div>
--- a/toolkit/mozapps/extensions/content/aboutaddons.js
+++ b/toolkit/mozapps/extensions/content/aboutaddons.js
@@ -24,16 +24,20 @@ XPCOMUtils.defineLazyModuleGetters(this,
 XPCOMUtils.defineLazyGetter(this, "browserBundle", () => {
   return Services.strings.createBundle(
     "chrome://browser/locale/browser.properties");
 });
 XPCOMUtils.defineLazyGetter(this, "brandBundle", () => {
   return Services.strings.createBundle(
       "chrome://branding/locale/brand.properties");
 });
+XPCOMUtils.defineLazyGetter(this, "extBundle", function() {
+  return Services.strings.createBundle(
+    "chrome://mozapps/locale/extensions/extensions.properties");
+});
 XPCOMUtils.defineLazyGetter(this, "extensionStylesheets", () => {
   const {ExtensionParent} =
     ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
   return ExtensionParent.extensionStylesheets;
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this, "allowPrivateBrowsingByDefault",
@@ -148,16 +152,95 @@ function hasPermission(addon, permission
   return !!(addon.permissions & PERMISSION_MASKS[permission]);
 }
 
 function isPending(addon, action) {
   const amAction = AddonManager["PENDING_" + action.toUpperCase()];
   return !!(addon.pendingOperations & amAction);
 }
 
+async function getAddonMessageInfo(addon) {
+  const {name} = addon;
+  const appName = brandBundle.GetStringFromName("brandShortName");
+  const {
+    STATE_BLOCKED, STATE_OUTDATED, STATE_SOFTBLOCKED,
+    STATE_VULNERABLE_UPDATE_AVAILABLE, STATE_VULNERABLE_NO_UPDATE,
+  } = Ci.nsIBlocklistService;
+
+  const formatString = (name, args) =>
+    extBundle.formatStringFromName(
+      `details.notification.${name}`, args, args.length);
+  const getString = name =>
+    extBundle.GetStringFromName(`details.notification.${name}`);
+
+  if (addon.blocklistState === STATE_BLOCKED) {
+    return {
+      linkText: getString("blocked.link"),
+      linkUrl: await addon.getBlocklistURL(),
+      message: formatString("blocked", [name]),
+      type: "error",
+    };
+  } else if (isDisabledUnsigned(addon)) {
+    return {
+      linkText: getString("unsigned.link"),
+      linkUrl: SUPPORT_URL + "unsigned-addons",
+      message: formatString("unsignedAndDisabled", [name, appName]),
+      type: "error",
+    };
+  } else if (!addon.isCompatible && (AddonManager.checkCompatibility ||
+      addon.blocklistState !== STATE_SOFTBLOCKED)) {
+    return {
+      message: formatString(
+        "incompatible", [name, appName, Services.appinfo.version]),
+      type: "warning",
+    };
+  } else if (!isCorrectlySigned(addon)) {
+    return {
+      linkText: getString("unsigned.link"),
+      linkUrl: SUPPORT_URL + "unsigned-addons",
+      message: formatString("unsigned", [name, appName]),
+      type: "warning",
+    };
+  } else if (addon.blocklistState === STATE_SOFTBLOCKED) {
+    return {
+      linkText: getString("softblocked.link"),
+      linkUrl: await addon.getBlocklistURL(),
+      message: formatString("softblocked", [name]),
+      type: "warning",
+    };
+  } else if (addon.blocklistState === STATE_OUTDATED) {
+    return {
+      linkText: getString("outdated.link"),
+      linkUrl: await addon.getBlocklistURL(),
+      message: formatString("outdated", [name]),
+      type: "warning",
+    };
+  } else if (addon.blocklistState === STATE_VULNERABLE_UPDATE_AVAILABLE) {
+    return {
+      linkText: getString("vulnerableUpdatable.link"),
+      linkUrl: await addon.getBlocklistURL(),
+      message: formatString("vulnerableUpdatable", [name]),
+      type: "error",
+    };
+  } else if (addon.blocklistState === STATE_VULNERABLE_NO_UPDATE) {
+    return {
+      linkText: getString("vulnerableNoUpdate.link"),
+      linkUrl: await addon.getBlocklistURL(),
+      message: formatString("vulnerableNoUpdate", [name]),
+      type: "error",
+    };
+  } else if (addon.isGMPlugin && !addon.isInstalled && addon.isActive) {
+    return {
+      message: formatString("gmpPending", [name]),
+      type: "warning",
+    };
+  }
+  return {};
+}
+
 // Don't change how we handle this while the page is open.
 const INLINE_OPTIONS_ENABLED = Services.prefs.getBoolPref(
   "extensions.htmlaboutaddons.inline-options.enabled");
 const OPTIONS_TYPE_MAP = {
   [AddonManager.OPTIONS_TYPE_TAB]: "tab",
   [AddonManager.OPTIONS_TYPE_INLINE_BROWSER]:
     INLINE_OPTIONS_ENABLED ? "inline" : "tab",
 };
@@ -1462,16 +1545,22 @@ class AddonCard extends HTMLElement {
           if (e.mozInputSource == MouseEvent.MOZ_SOURCE_KEYBOARD) {
             this.panel.toggle(e);
           }
           break;
         case "report":
           this.panel.hide();
           openAbuseReport({addonId: addon.id, reportEntryPoint: "menu"});
           break;
+        case "link":
+          if (e.target.getAttribute("url")) {
+            windowRoot.ownerGlobal.openWebLinkIn(
+              e.target.getAttribute("url"), "tab");
+          }
+          break;
         default:
           // Handle a click on the card itself.
           if (!this.expanded) {
             loadViewFn("detail", this.addon.id);
           } else if (e.target.localName == "a" &&
                      e.target.getAttribute("data-telemetry-name")) {
             let value = e.target.getAttribute("data-telemetry-name");
             AMTelemetry.recordLinkEvent({
@@ -1637,24 +1726,47 @@ class AddonCard extends HTMLElement {
         card.querySelector(".addon-badge-private-browsing-allowed")
           .hidden = !isAllowed;
       });
     }
 
     // Update description.
     card.querySelector(".addon-description").textContent = addon.description;
 
+    this.updateMessage();
+
     // Update the details if they're shown.
     if (this.details) {
       this.details.update();
     }
 
     this.sendEvent("update");
   }
 
+  async updateMessage() {
+    let {addon, card} = this;
+    let messageBar = card.querySelector(".addon-card-message");
+    let link = messageBar.querySelector("button");
+
+    let {message, type = "", linkText, linkUrl} =
+      await getAddonMessageInfo(addon);
+
+    if (message) {
+      messageBar.querySelector("span").textContent = message;
+      messageBar.setAttribute("type", type);
+      if (linkText) {
+        link.textContent = linkText;
+        link.setAttribute("url", linkUrl);
+      }
+    }
+
+    messageBar.hidden = !message;
+    link.hidden = !linkText;
+  }
+
   showPrefs() {
     this.details.showPrefs();
   }
 
   expand() {
     if (this.children.length == 0) {
       this.expanded = true;
     } else {
--- a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
+++ b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
@@ -1,19 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* eslint max-len: ["error", 80] */
 
 "use strict";
 
-/* exported attachUpdateHandler, gBrowser, getBrowserElement, loadReleaseNotes,
-            openOptionsInTab, promiseEvent, shouldShowPermissionsPrompt,
-            showPermissionsPrompt */
+/* exported attachUpdateHandler, gBrowser, getBrowserElement, isCorrectlySigned,
+ *          isDisabledUnsigned, loadReleaseNotes, openOptionsInTab,
+ *          promiseEvent, shouldShowPermissionsPrompt, showPermissionsPrompt */
 
+const {AddonSettings} =
+  ChromeUtils.import("resource://gre/modules/addons/AddonSettings.jsm");
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(
   this, "WEBEXT_PERMISSION_PROMPTS",
   "extensions.webextPermissionPrompts", false);
 
 ChromeUtils.defineModuleGetter(this, "Extension",
@@ -165,8 +167,21 @@ var gBrowser = {
 
     if (parentWindow.gBrowser) {
       return parentWindow.gBrowser.getTabModalPromptBox(browser);
     }
 
     return null;
   },
 };
+
+function isCorrectlySigned(addon) {
+  // Add-ons without an "isCorrectlySigned" property are correctly signed as
+  // they aren't the correct type for signing.
+  return addon.isCorrectlySigned !== false;
+}
+
+function isDisabledUnsigned(addon) {
+  let signingRequired = (addon.type == "locale") ?
+                        AddonSettings.LANGPACKS_REQUIRE_SIGNING :
+                        AddonSettings.REQUIRE_SIGNING;
+  return signingRequired && !isCorrectlySigned(addon);
+}
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -7,17 +7,16 @@
 /* import-globals-from ../../../content/contentAreaUtils.js */
 /* import-globals-from aboutaddonsCommon.js */
 /* globals ProcessingInstruction */
 /* exported gBrowser, loadView */
 
 const {DeferredTask} = ChromeUtils.import("resource://gre/modules/DeferredTask.jsm");
 const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 const {AddonRepository} = ChromeUtils.import("resource://gre/modules/addons/AddonRepository.jsm");
-const {AddonSettings} = ChromeUtils.import("resource://gre/modules/addons/AddonSettings.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AMTelemetry",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "E10SUtils", "resource://gre/modules/E10SUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ExtensionParent",
                                "resource://gre/modules/ExtensionParent.jsm");
 ChromeUtils.defineModuleGetter(this, "ExtensionPermissions",
                                "resource://gre/modules/ExtensionPermissions.jsm");
@@ -298,29 +297,16 @@ function loadView(aViewId) {
     // should be the initial history entry
 
     gViewController.loadInitialView(aViewId);
   } else {
     gViewController.loadView(aViewId);
   }
 }
 
-function isCorrectlySigned(aAddon) {
-  // Add-ons without an "isCorrectlySigned" property are correctly signed as
-  // they aren't the correct type for signing.
-  return aAddon.isCorrectlySigned !== false;
-}
-
-function isDisabledUnsigned(addon) {
-  let signingRequired = (addon.type == "locale") ?
-                        AddonSettings.LANGPACKS_REQUIRE_SIGNING :
-                        AddonSettings.REQUIRE_SIGNING;
-  return signingRequired && !isCorrectlySigned(addon);
-}
-
 function isLegacyExtension(addon) {
   let legacy = false;
   if (addon.type == "extension" && !addon.isWebExtension) {
     legacy = true;
   }
   if (addon.type == "theme") {
     legacy = false;
   }
--- a/toolkit/mozapps/extensions/content/message-bar.css
+++ b/toolkit/mozapps/extensions/content/message-bar.css
@@ -77,28 +77,31 @@
   font-weight: 400;
   line-height: 1.4;
 
   display: flex;
   /* Ensure that the message bar shadow dom elements are vertically aligned. */
   align-items: center;
 }
 
+:host([align="center"]) .container {
+  justify-content: center;
+}
+
 :host([dismissable]) .container {
   /* Add padding on the end of the container when the bar is dismissable. */
   padding-inline-end: 4px;
 }
 
 .icon {
   flex-shrink: 0;
 }
 
 .content {
   margin-inline-end: 4px;
-  flex-grow: 1;
   display: flex;
   /* Ensure that the message bar content is vertically aligned. */
   align-items: center;
   /* Ensure that the message bar content is wrapped. */
   word-break: break-word;
 }
 
 button.close {
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -88,16 +88,17 @@ skip-if = os == 'linux' && !debug # Bug 
 [browser_html_message_bar.js]
 [browser_html_named_deck.js]
 [browser_html_options_ui.js]
 [browser_html_options_ui_in_tab.js]
 [browser_html_plugins.js]
 skip-if = (os == 'win' && processor == 'aarch64') # aarch64 has no plugin support, bug 1525174 and 1547495
 [browser_html_recent_updates.js]
 [browser_html_updates.js]
+[browser_html_warning_messages.js]
 [browser_inlinesettings_browser.js]
 skip-if = os == 'mac' || os == 'linux' # Bug 1483347
 [browser_installssl.js]
 skip-if = verify
 [browser_interaction_telemetry.js]
 [browser_langpack_signing.js]
 [browser_legacy.js]
 [browser_legacy_pre57.js]
--- a/toolkit/mozapps/extensions/test/browser/browser_bug590347.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug590347.js
@@ -8,17 +8,17 @@
 
 var gProvider;
 var gManagerWindow;
 var gCategoryUtilities;
 
 var gApp = document.getElementById("bundle_brand").getString("brandShortName");
 var gVersion = Services.appinfo.version;
 
-// Not implemented in HTML about:addons (Bug 1544950)
+// Tested in browser_html_warning_messages.js for HTML.
 SpecialPowers.pushPrefEnv({
   set: [["extensions.htmlaboutaddons.enabled", false]],
 });
 
 // Opens the details view of an add-on
 async function open_details(aId, aType, aCallback) {
   requestLongerTimeout(2);
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_warning_messages.js
@@ -0,0 +1,305 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint max-len: ["error", 80] */
+
+"use strict";
+
+let gProvider;
+const {
+  STATE_BLOCKED, STATE_OUTDATED, STATE_SOFTBLOCKED, STATE_VULNERABLE_NO_UPDATE,
+  STATE_VULNERABLE_UPDATE_AVAILABLE,
+} = Ci.nsIBlocklistService;
+
+const brandBundle =
+  Services.strings.createBundle("chrome://branding/locale/brand.properties");
+const appName = brandBundle.GetStringFromName("brandShortName");
+const appVersion = Services.appinfo.version;
+const SUPPORT_URL = Services.urlFormatter.formatURL(
+  Services.prefs.getStringPref("app.support.baseURL"));
+
+add_task(async function setup() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.htmlaboutaddons.enabled", true]],
+  });
+  gProvider = new MockProvider();
+});
+
+async function checkMessageState(id, addonType, expected) {
+  async function checkAddonCard() {
+    let card = doc.querySelector(`addon-card[addon-id="${id}"]`);
+    let messageBar = card.querySelector(".addon-card-message");
+
+    if (!expected) {
+      ok(messageBar.hidden, "message is hidden");
+    } else {
+      let {linkText, linkUrl, text, type} = expected;
+
+      ok(!messageBar.hidden, "message is visible");
+      is(messageBar.getAttribute("type"), type, "message has the right type");
+      is(messageBar.querySelector("span").textContent,
+         text, "message has the right text");
+
+      let link = messageBar.querySelector("button");
+      if (linkUrl) {
+        ok(!link.hidden, "link is visible");
+        is(link.textContent, linkText, "link text is correct");
+        let newTab = BrowserTestUtils.waitForNewTab(gBrowser, linkUrl);
+        link.click();
+        BrowserTestUtils.removeTab(await newTab);
+      } else {
+        ok(link.hidden, "link is hidden");
+      }
+    }
+
+    return card;
+  }
+
+  let win = await loadInitialView(addonType);
+  let doc = win.document;
+
+  // Check the list view.
+  ok(doc.querySelector("addon-list"), "this is a list view");
+  let card = await checkAddonCard();
+
+  // Load the detail view.
+  let loaded = waitForViewLoad(win);
+  card.querySelector('[action="expand"]').click();
+  await loaded;
+
+  // Check the detail view.
+  ok(!doc.querySelector("addon-list"), "this isn't a list view");
+  await checkAddonCard();
+
+  await closeView(win);
+}
+
+add_task(async function testNoMessageExtension() {
+  let id = "no-message@mochi.test";
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {applications: {gecko: {id}}},
+    useAddonManager: "temporary",
+  });
+  await extension.startup();
+
+  await checkMessageState(id, "extension", null);
+
+  await extension.unload();
+});
+
+add_task(async function testNoMessageLangpack() {
+  let id = "no-message@mochi.test";
+  gProvider.createAddons([{
+    appDisabled: true,
+    id,
+    name: "Signed Langpack",
+    signedState: AddonManager.SIGNEDSTATE_SIGNED,
+    type: "locale",
+  }]);
+
+  await checkMessageState(id, "locale", null);
+});
+
+add_task(async function testBlocked() {
+  let id = "blocked@mochi.test";
+  let linkUrl = "https://example.com/addon-blocked";
+  gProvider.createAddons([{
+    appDisabled: true,
+    blocklistState: STATE_BLOCKED,
+    blocklistURL: linkUrl,
+    id,
+    isActive: false,
+    name: "Blocked",
+  }]);
+  await checkMessageState(id, "extension", {
+    linkText: "More Information",
+    linkUrl,
+    text: "Blocked has been disabled due to security or stability issues.",
+    type: "error",
+  });
+});
+
+add_task(async function testUnsignedDisabled() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["xpinstall.signatures.required", true]],
+  });
+
+  let id = "unsigned@mochi.test";
+  gProvider.createAddons([{
+    appDisabled: true,
+    id,
+    name: "Unsigned",
+    signedState: AddonManager.SIGNEDSTATE_MISSING,
+  }]);
+  await checkMessageState(id, "extension", {
+    linkText: "More Information",
+    linkUrl: SUPPORT_URL + "unsigned-addons",
+    text:
+      "Unsigned could not be verified for use in " + appName +
+      " and has been disabled.",
+    type: "error",
+  });
+
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function testUnsignedLangpackDisabled() {
+  let id = "unsigned-langpack@mochi.test";
+  gProvider.createAddons([{
+    appDisabled: true,
+    id,
+    name: "Unsigned",
+    signedState: AddonManager.SIGNEDSTATE_MISSING,
+    type: "locale",
+  }]);
+  await checkMessageState(id, "locale", {
+    linkText: "More Information",
+    linkUrl: SUPPORT_URL + "unsigned-addons",
+    text:
+      "Unsigned could not be verified for use in " + appName +
+      " and has been disabled.",
+    type: "error",
+  });
+});
+
+add_task(async function testIncompatible() {
+  let id = "incompatible@mochi.test";
+  gProvider.createAddons([{
+    appDisabled: true,
+    id,
+    isActive: false,
+    isCompatible: false,
+    name: "Incompatible",
+  }]);
+  await checkMessageState(id, "extension", {
+    text:
+      "Incompatible is incompatible with " + appName + " " + appVersion + ".",
+    type: "warning",
+  });
+});
+
+add_task(async function testUnsignedEnabled() {
+  let id = "unsigned-allowed@mochi.test";
+  gProvider.createAddons([{
+    id,
+    name: "Unsigned",
+    signedState: AddonManager.SIGNEDSTATE_MISSING,
+  }]);
+  await checkMessageState(id, "extension", {
+    linkText: "More Information",
+    linkUrl: SUPPORT_URL + "unsigned-addons",
+    text:
+      "Unsigned could not be verified for use in " + appName +
+      ". Proceed with caution.",
+    type: "warning",
+  });
+});
+
+add_task(async function testUnsignedLangpackEnabled() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.langpacks.signatures.required", false]],
+  });
+
+  let id = "unsigned-allowed-langpack@mochi.test";
+  gProvider.createAddons([{
+    id,
+    name: "Unsigned Langpack",
+    signedState: AddonManager.SIGNEDSTATE_MISSING,
+    type: "locale",
+  }]);
+  await checkMessageState(id, "locale", {
+    linkText: "More Information",
+    linkUrl: SUPPORT_URL + "unsigned-addons",
+    text:
+      "Unsigned Langpack could not be verified for use in " + appName +
+      ". Proceed with caution.",
+    type: "warning",
+  });
+
+  await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function testSoftBlocked() {
+  let id = "softblocked@mochi.test";
+  let linkUrl = "https://example.com/addon-blocked";
+  gProvider.createAddons([{
+    appDisabled: true,
+    blocklistState: STATE_SOFTBLOCKED,
+    blocklistURL: linkUrl,
+    id,
+    isActive: false,
+    name: "Soft Blocked",
+  }]);
+  await checkMessageState(id, "extension", {
+    linkText: "More Information",
+    linkUrl,
+    text: "Soft Blocked is known to cause security or stability issues.",
+    type: "warning",
+  });
+});
+
+add_task(async function testOutdated() {
+  let id = "outdated@mochi.test";
+  let linkUrl = "https://example.com/addon-blocked";
+  gProvider.createAddons([{
+    blocklistState: STATE_OUTDATED,
+    blocklistURL: linkUrl,
+    id,
+    name: "Outdated",
+  }]);
+  await checkMessageState(id, "extension", {
+    linkText: "Update Now",
+    linkUrl,
+    text: "An important update is available for Outdated.",
+    type: "warning",
+  });
+});
+
+add_task(async function testVulnerableUpdate() {
+  let id = "vulnerable-update@mochi.test";
+  let linkUrl = "https://example.com/addon-blocked";
+  gProvider.createAddons([{
+    blocklistState: STATE_VULNERABLE_UPDATE_AVAILABLE,
+    blocklistURL: linkUrl,
+    id,
+    name: "Vulnerable Update",
+  }]);
+  await checkMessageState(id, "extension", {
+    linkText: "Update Now",
+    linkUrl,
+    text: "Vulnerable Update is known to be vulnerable and should be updated.",
+    type: "error",
+  });
+});
+
+add_task(async function testVulnerableNoUpdate() {
+  let id = "vulnerable-no-update@mochi.test";
+  let linkUrl = "https://example.com/addon-blocked";
+  gProvider.createAddons([{
+    blocklistState: STATE_VULNERABLE_NO_UPDATE,
+    blocklistURL: linkUrl,
+    id,
+    name: "Vulnerable No Update",
+  }]);
+  await checkMessageState(id, "extension", {
+    linkText: "More Information",
+    linkUrl,
+    text: "Vulnerable No Update is known to be vulnerable. Use with caution.",
+    type: "error",
+  });
+});
+
+add_task(async function testPluginInstalling() {
+  let id = "plugin-installing@mochi.test";
+  gProvider.createAddons([{
+    id,
+    isActive: true,
+    isGMPlugin: true,
+    isInstalled: false,
+    name: "Plugin Installing",
+    type: "plugin",
+  }]);
+  await checkMessageState(id, "plugin", {
+    text: "Plugin Installing will be installed shortly.",
+    type: "warning",
+  });
+});
--- a/toolkit/mozapps/extensions/test/browser/browser_langpack_signing.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_langpack_signing.js
@@ -1,14 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-// This test is testing a warning message that is not yet supported
-// by the HTML about:addons views (tracked by Bug 1544950).
+// The HTML tests are in browser_html_warning_messages.js.
 SpecialPowers.pushPrefEnv({
   set: [["extensions.htmlaboutaddons.enabled", false]],
 });
 
 // Tests that signed and unsigned language packs show up correctly in
 // the Languages tab based on the langpack signing preference.
 add_task(async function() {
   const PREF = "extensions.langpacks.signatures.required";