Bug 1412561 - Block addon installation prompts in fullscreen mode. r=johannh,aswan
authorPaul Zuehlcke <pzuhlcke@mozilla.com>
Fri, 03 May 2019 21:44:24 +0000
changeset 531455 2268e6a9359ed9ddcf3a0325853984c32f93930e
parent 531454 e99ec6c4418fd17cba92ea8a8b7d5160f4cf3285
child 531456 a0a023dac829933a76c7ab83ceb578878a086238
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh, aswan
bugs1412561
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1412561 - Block addon installation prompts in fullscreen mode. r=johannh,aswan Differential Revision: https://phabricator.services.mozilla.com/D27734
browser/base/content/browser-addons.js
browser/base/content/browser-fullScreenAndPointerLock.js
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/test/xpinstall/browser.ini
toolkit/mozapps/extensions/test/xpinstall/browser_block_fullscreen_prompt.js
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -344,16 +344,41 @@ var gXPInstallObserver = {
 
     removeNotificationOnEnd(popup, installInfo.installs);
 
     Services.telemetry
             .getHistogramById("SECURITY_UI")
             .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
   },
 
+  // IDs of addon install related notifications
+  NOTIFICATION_IDS: [
+    "addon-install-blocked",
+    "addon-install-blocked-silent",
+    "addon-install-complete",
+    "addon-install-confirmation",
+    "addon-install-disabled",
+    "addon-install-failed",
+    "addon-install-origin-blocked",
+    "addon-install-started",
+    "addon-progress",
+    "addon-webext-permissions",
+    "xpinstall-disabled",
+  ],
+
+  // Remove all opened addon installation notifications
+  removeAllNotifications(browser) {
+    this.NOTIFICATION_IDS.forEach((id) => {
+      let notification = PopupNotifications.getNotification(id, browser);
+      if (notification) {
+        PopupNotifications.remove(notification);
+      }
+    });
+  },
+
   observe(aSubject, aTopic, aData) {
     var brandBundle = document.getElementById("bundle_brand");
     var installInfo = aSubject.wrappedJSObject;
     var browser = installInfo.browser;
 
     // Make sure the browser is still alive.
     if (!browser || !gBrowser.browsers.includes(browser))
       return;
--- a/browser/base/content/browser-fullScreenAndPointerLock.js
+++ b/browser/base/content/browser-fullScreenAndPointerLock.js
@@ -368,16 +368,22 @@ var FullScreen = {
         // request was initiated from an in-process browser, we need
         // to get its corresponding browser here.
         let browser;
         if (event.target.ownerGlobal == window) {
           browser = event.target;
         } else {
           browser = event.target.ownerGlobal.docShell.chromeEventHandler;
         }
+
+        // Addon installation should be cancelled when entering fullscreen for security and usability reasons.
+        // Installation prompts in fullscreen can trick the user into installing unwanted addons.
+        // In fullscreen the notification box does not have a clear visual association with its parent anymore.
+        gXPInstallObserver.removeAllNotifications(browser);
+
         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
         this.enterDomFullscreen(browser);
         break;
       }
       case "MozDOMFullscreen:Exited":
         TelemetryStopwatch.start("FULLSCREEN_CHANGE_MS");
         this.cleanupDomFullscreen();
         break;
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -1801,16 +1801,25 @@ var AddonManagerInternal = {
                                     aInstallingPrincipal.URI, aInstall);
         return;
       } else if (aInstallingPrincipal.isNullPrincipal || !aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
         aInstall.cancel();
 
         this.installNotifyObservers("addon-install-origin-blocked", topBrowser,
                                     aInstallingPrincipal.URI, aInstall);
         return;
+      } else if (topBrowser.ownerGlobal.fullScreen) {
+        // Addon installation and the resulting notifications should be blocked in fullscreen for security and usability reasons.
+        // Installation prompts in fullscreen can trick the user into installing unwanted addons.
+        // In fullscreen the notification box does not have a clear visual association with its parent anymore.
+        aInstall.cancel();
+
+        this.installNotifyObservers("addon-install-blocked-silent", topBrowser,
+                                    aInstallingPrincipal.URI, aInstall);
+        return;
       }
 
       // The install may start now depending on the web install listener,
       // listen for the browser navigating to a new origin and cancel the
       // install in that case.
       new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
 
       let startInstall = (source) => {
--- a/toolkit/mozapps/extensions/test/xpinstall/browser.ini
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser.ini
@@ -28,16 +28,17 @@ support-files =
 [browser_auth.js]
 [browser_auth2.js]
 [browser_auth3.js]
 [browser_auth4.js]
 [browser_badargs.js]
 [browser_badargs2.js]
 [browser_badhash.js]
 [browser_badhashtype.js]
+[browser_block_fullscreen_prompt.js]
 [browser_bug540558.js]
 [browser_bug611242.js]
 [browser_bug638292.js]
 [browser_bug645699.js]
 [browser_bug672485.js]
 skip-if = true # disabled due to a leak. See bug 682410.
 [browser_containers.js]
 [browser_cookies.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_block_fullscreen_prompt.js
@@ -0,0 +1,109 @@
+/* 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/. */
+
+"use strict";
+
+// This test tends to trigger a race in the fullscreen time telemetry,
+// where the fullscreen enter and fullscreen exit events (which use the
+// same histogram ID) overlap. That causes TelemetryStopwatch to log an
+// error.
+SimpleTest.ignoreAllUncaughtExceptions(true);
+
+const ADDON_FILE_URI = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/amosigned.xpi";
+
+const ADDON_EVENTS = [
+  "addon-install-blocked",
+  "addon-install-blocked-silent",
+  "addon-install-complete",
+  "addon-install-confirmation",
+  "addon-install-disabled",
+  "addon-install-failed",
+  "addon-install-origin-blocked",
+  "addon-install-started",
+  "addon-progress",
+  "addon-webext-permissions",
+  "xpinstall-disabled",
+];
+
+/**
+ * Registers observers for addon installation events and resolves promise on first matching event
+ */
+function waitForNextAddonEvent() {
+  return Promise.race(ADDON_EVENTS.map( async (eventStr) => {
+    await TestUtils.topicObserved(eventStr);
+    return eventStr;
+  }));
+}
+
+/**
+ * Check if an addon installation prompt is visible
+ */
+function addonPromptVisible(browser) {
+  if (!PopupNotifications.isPanelOpen) return false;
+  if (ADDON_EVENTS.some(id => PopupNotifications.getNotification(id, browser) != null)) return true;
+  return false;
+}
+
+/**
+ * Spawns content task in browser to enter / leave fullscreen
+ * @param browser - Browser to use for JS fullscreen requests
+ * @param {Boolean} fullscreenState - true to enter fullscreen, false to leave
+ */
+function changeFullscreen(browser, fullscreenState) {
+  return ContentTask.spawn(browser, fullscreenState, async function(state) {
+    if (state) {
+      await content.document.body.requestFullscreen();
+    } else {
+      await content.document.exitFullscreen();
+    }
+  });
+}
+
+// This tests if addon installation is blocked when requested in fullscreen
+add_task(async function testFullscreenBlockAddonInstallPrompt() {
+  // Open example.com
+  await BrowserTestUtils.withNewTab("http://example.com", async function(browser) {
+    await changeFullscreen(browser, true);
+
+    // Navigate to addon file path
+    BrowserTestUtils.loadURI(browser, ADDON_FILE_URI);
+
+    // Wait for addon manager event and check if installation has been blocked
+    let eventStr = await waitForNextAddonEvent();
+
+    Assert.equal(eventStr, "addon-install-blocked-silent", "Addon installation was blocked");
+
+    // Test if addon installation prompt has been blocked
+    Assert.ok(!addonPromptVisible(), "Addon installation prompt not opened");
+
+    await changeFullscreen(browser, false);
+  });
+});
+
+
+
+// This tests if the addon install prompt is closed when entering fullscreen
+add_task(async function testFullscreenCloseAddonInstallPrompt() {
+  // Open example.com
+  await BrowserTestUtils.withNewTab("http://example.com", async function(browser) {
+    // Navigate to addon file path
+    BrowserTestUtils.loadURI(browser, ADDON_FILE_URI);
+
+    // Test if addon installation started
+    let eventStr = await waitForNextAddonEvent();
+
+    Assert.ok(eventStr === "addon-install-started", "Addon installation started");
+
+    // Test if addon installation prompt is visible
+    Assert.ok(addonPromptVisible(), "Addon installation prompt opened");
+
+    // Switch to fullscreen
+    await changeFullscreen(browser, true);
+
+    // Test if addon installation prompt has been closed
+    Assert.ok(!addonPromptVisible(), "Addon installation prompt closed for fullscreen");
+
+    await changeFullscreen(browser, false);
+  });
+});