Bug 1646780 - use a template to wrap the identity popup while it's not needed, r=johannh
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Tue, 23 Jun 2020 14:04:49 +0000
changeset 536916 31af559a8ba6cb77cdb34d1b744e3600ac962617
parent 536915 c7af87ca39c8a28c008fd62cbc0c2debe6dc3080
child 536917 f9830e6ef75620e1e6c31b79747587983006a937
push id37533
push userdluca@mozilla.com
push dateTue, 23 Jun 2020 21:38:40 +0000
treeherdermozilla-central@d48aa0f0aa0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1646780
milestone79.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 1646780 - use a template to wrap the identity popup while it's not needed, r=johannh Differential Revision: https://phabricator.services.mozilla.com/D78894
accessible/tests/browser/mac/browser_app.js
browser/base/content/browser-siteIdentity.js
browser/base/content/browser-siteProtections.js
browser/base/content/browser.js
browser/base/content/tabbrowser.js
browser/base/content/test/fullscreen/browser_bug1557041.js
browser/base/content/test/general/browser_addCertException.js
browser/base/content/test/permissions/browser_autoplay_blocked.js
browser/base/content/test/permissions/browser_permissions.js
browser/base/content/test/popups/browser_popup_blocker_identity_block.js
browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
browser/base/content/test/protectionsUI/head.js
browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js
browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData.js
browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js
browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
browser/base/content/test/siteIdentity/browser_identity_UI.js
browser/base/content/test/siteIdentity/head.js
browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
browser/base/content/test/webrtc/browser_devices_get_user_media.js
browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
browser/base/content/test/webrtc/head.js
browser/components/controlcenter/content/identityPanel.inc.xhtml
browser/components/extensions/test/browser/browser_ext_persistent_storage_permission_indication.js
browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
--- a/accessible/tests/browser/mac/browser_app.js
+++ b/accessible/tests/browser/mac/browser_app.js
@@ -138,18 +138,18 @@ add_task(async () => {
       // Close context menu
       EventUtils.synthesizeKey("KEY_Escape");
       await BrowserTestUtils.waitForPopupEvent(menu, "hidden");
 
       // We're back to 5
       is(rootChildCount(), 5, "Root has 5 children");
 
       // Open site identity popup
+      document.getElementById("identity-box").click();
       const identityPopup = document.getElementById("identity-popup");
-      document.getElementById("identity-box").click();
       await BrowserTestUtils.waitForPopupEvent(identityPopup, "shown");
 
       // Now root has 6 children
       is(rootChildCount(), 6, "Root has 6 children");
 
       // Close popup
       EventUtils.synthesizeKey("KEY_Escape");
       await BrowserTestUtils.waitForPopupEvent(identityPopup, "hidden");
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -143,18 +143,36 @@ var gIdentityHandler = {
     return (
       !this._isBrokenConnection &&
       (this._isSecureContext ||
         (gBrowser.selectedBrowser.documentURI &&
           gBrowser.selectedBrowser.documentURI.scheme == "chrome"))
     );
   },
 
+  _popupInitialized: false,
+  _initializePopup() {
+    if (!this._popupInitialized) {
+      let wrapper = document.getElementById("template-identity-popup");
+      wrapper.replaceWith(wrapper.content);
+      this._popupInitialized = true;
+    }
+  },
+
+  hidePopup() {
+    if (this._popupInitialized) {
+      PanelMultiView.hidePopup(this._identityPopup);
+    }
+  },
+
   // smart getters
   get _identityPopup() {
+    if (!this._popupInitialized) {
+      return null;
+    }
     delete this._identityPopup;
     return (this._identityPopup = document.getElementById("identity-popup"));
   },
   get _identityBox() {
     delete this._identityBox;
     return (this._identityBox = document.getElementById("identity-box"));
   },
   get _identityPopupMultiView() {
@@ -411,41 +429,47 @@ var gIdentityHandler = {
     // Use telemetry to measure how often unblocking happens
     const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
     let histogram = Services.telemetry.getHistogramById(
       "MIXED_CONTENT_UNBLOCK_COUNTER"
     );
     histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
     // Reload the page with the content unblocked
     BrowserReloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
-    PanelMultiView.hidePopup(this._identityPopup);
+    if (this._popupInitialized) {
+      PanelMultiView.hidePopup(this._identityPopup);
+    }
   },
 
   enableMixedContentProtection() {
     gBrowser.selectedBrowser.sendMessageToActor(
       "MixedContent:ReenableProtection",
       {},
       "BrowserTab"
     );
     BrowserReload();
-    PanelMultiView.hidePopup(this._identityPopup);
+    if (this._popupInitialized) {
+      PanelMultiView.hidePopup(this._identityPopup);
+    }
   },
 
   removeCertException() {
     if (!this._uriHasHost) {
       Cu.reportError(
         "Trying to revoke a cert exception on a URI without a host?"
       );
       return;
     }
     let host = this._uri.host;
     let port = this._uri.port > 0 ? this._uri.port : 443;
     this._overrideService.clearValidityOverride(host, port);
     BrowserReloadSkipCache();
-    PanelMultiView.hidePopup(this._identityPopup);
+    if (this._popupInitialized) {
+      PanelMultiView.hidePopup(this._identityPopup);
+    }
   },
 
   /**
    * Helper to parse out the important parts of _secInfo (of the SSL cert in
    * particular) for use in constructing identity UI strings
    */
   getIdentityData() {
     var result = {};
@@ -496,17 +520,17 @@ var gIdentityHandler = {
     this.setURI(uri);
     this._secInfo = gBrowser.securityUI.secInfo;
     this._isSecureContext = gBrowser.securityUI.isSecureContext;
 
     // Then, update the user interface with the available data.
     this.refreshIdentityBlock();
     // Handle a location change while the Control Center is focused
     // by closing the popup (bug 1207542)
-    if (shouldHidePopup) {
+    if (shouldHidePopup && this._popupInitialized) {
       PanelMultiView.hidePopup(this._identityPopup);
     }
 
     // NOTE: We do NOT update the identity popup (the control center) when
     // we receive a new security state on the existing page (i.e. from a
     // subframe). If the user opened the popup and looks at the provided
     // information we don't want to suddenly change the panel contents.
 
@@ -562,17 +586,17 @@ var gIdentityHandler = {
       if (this._sharingState.geo) {
         this._geoSharingIcon.setAttribute("sharing", this._sharingState.geo);
       }
       if (this._sharingState.xr) {
         this._xrSharingIcon.setAttribute("sharing", this._sharingState.xr);
       }
     }
 
-    if (this._identityPopup.state == "open") {
+    if (this._popupInitialized && this._identityPopup.state != "closed") {
       this.updateSitePermissions();
       PanelView.forNode(
         this._identityPopupMainView
       ).descriptionHeightWorkaround();
     }
   },
 
   /**
@@ -1102,32 +1126,32 @@ var gIdentityHandler = {
       );
       document.exitFullscreen();
       return;
     }
     this._openPopup(event);
   },
 
   _openPopup(event) {
-    // Make sure that the display:none style we set in xul is removed now that
-    // the popup is actually needed
-    this._identityPopup.hidden = false;
+    // Make the popup available.
+    this._initializePopup();
 
     // Remove the reload hint that we show after a user has cleared a permission.
     this._permissionReloadHint.setAttribute("hidden", "true");
 
     // Update the popup strings
     this.refreshIdentityPopup();
 
     // Add the "open" attribute to the identity box for styling
     this._identityBox.setAttribute("open", "true");
 
-    // Check the panel state of the protections panel. Hide it if needed.
-    if (gProtectionsHandler._protectionsPopup.state != "closed") {
-      PanelMultiView.hidePopup(gProtectionsHandler._protectionsPopup);
+    // Check the panel state of other panels. Hide them if needed.
+    let openPanels = Array.from(document.querySelectorAll("panel[openpanel]"));
+    for (let panel of openPanels) {
+      PanelMultiView.hidePopup(panel);
     }
 
     // Now open the popup, anchored off the primary chrome element
     PanelMultiView.openPopup(this._identityPopup, this._identityIcon, {
       position: "bottomcenter topleft",
       triggerEvent: event,
     }).catch(Cu.reportError);
   },
@@ -1258,20 +1282,22 @@ var gIdentityHandler = {
     dt.setData("text/html", htmlString);
     dt.setDragImage(canvas, 16, 16);
 
     // Don't cover potential drop targets on the toolbars or in content.
     gURLBar.view.close();
   },
 
   onLocationChange() {
-    this._permissionReloadHint.setAttribute("hidden", "true");
+    if (this._popupInitialized && this._identityPopup.state != "closed") {
+      this._permissionReloadHint.setAttribute("hidden", "true");
 
-    if (!this._permissionList.hasChildNodes()) {
-      this._permissionEmptyHint.removeAttribute("hidden");
+      if (!this._permissionList.hasChildNodes()) {
+        this._permissionEmptyHint.removeAttribute("hidden");
+      }
     }
   },
 
   _updateAttribute(elem, attr, value) {
     if (value) {
       elem.setAttribute(attr, value);
     } else {
       elem.removeAttribute(attr);
--- a/browser/base/content/browser-siteProtections.js
+++ b/browser/base/content/browser-siteProtections.js
@@ -2186,19 +2186,20 @@ var gProtectionsHandler = {
         { once: true }
       );
     }
 
     // Add the "open" attribute to the tracking protection icon container
     // for styling.
     this._trackingProtectionIconContainer.setAttribute("open", "true");
 
-    // Check the panel state of the identity panel. Hide it if needed.
-    if (gIdentityHandler._identityPopup.state != "closed") {
-      PanelMultiView.hidePopup(gIdentityHandler._identityPopup);
+    // Check the panel state of other panels. Hide them if needed.
+    let openPanels = Array.from(document.querySelectorAll("panel[openpanel]"));
+    for (let panel of openPanels) {
+      PanelMultiView.hidePopup(panel);
     }
 
     // Now open the popup, anchored off the primary chrome element
     PanelMultiView.openPopup(
       this._protectionsPopup,
       this._trackingProtectionIconContainer,
       {
         position: "bottomcenter topleft",
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3545,17 +3545,17 @@ function BrowserReloadWithFlags(reloadFl
   // Reset temporary permissions on the remaining tabs to reload.
   // This is done here because we only want to reset
   // permissions on user reload.
   for (let tab of unchangedRemoteness) {
     SitePermissions.clearTemporaryPermissions(tab.linkedBrowser);
     // Also reset DOS mitigations for the basic auth prompt on reload.
     delete tab.linkedBrowser.authPromptAbuseCounter;
   }
-  PanelMultiView.hidePopup(gIdentityHandler._identityPopup);
+  gIdentityHandler.hidePopup();
 
   let handlingUserInput = window.windowUtils.isHandlingUserInput;
 
   for (let tab of unchangedRemoteness) {
     if (tab.linkedPanel) {
       sendReloadMessage(tab);
     } else {
       // Shift to fully loaded browser and make
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -4096,17 +4096,17 @@
 
     reloadTab(aTab) {
       let browser = this.getBrowserForTab(aTab);
       // Reset temporary permissions on the current tab. This is done here
       // because we only want to reset permissions on user reload.
       SitePermissions.clearTemporaryPermissions(browser);
       // Also reset DOS mitigations for the basic auth prompt on reload.
       delete browser.authPromptAbuseCounter;
-      PanelMultiView.hidePopup(gIdentityHandler._identityPopup);
+      gIdentityHandler.hidePopup();
       browser.reload();
     },
 
     addProgressListener(aListener) {
       if (arguments.length != 1) {
         Cu.reportError(
           "gBrowser.addProgressListener was " +
             "called with a second argument, " +
--- a/browser/base/content/test/fullscreen/browser_bug1557041.js
+++ b/browser/base/content/test/fullscreen/browser_bug1557041.js
@@ -17,32 +17,33 @@ add_task(async function test_identityPop
   let url = "https://example.com/";
 
   await BrowserTestUtils.withNewTab("about:blank", async browser => {
     let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
     BrowserTestUtils.loadURI(browser, url);
     await loaded;
 
     let identityBox = document.getElementById("identity-box");
-    let identityPopup = document.getElementById("identity-popup");
 
     info("Entering DOM fullscreen");
     await changeFullscreen(browser, true);
 
     let popupShown = BrowserTestUtils.waitForEvent(
-      identityPopup,
+      window,
       "popupshown",
-      true
+      true,
+      event => event.target == document.getElementById("identity-popup")
     );
     let fsExit = waitForFullScreenState(browser, false);
 
     identityBox.click();
 
     info("Waiting for fullscreen exit and identity popup to show");
     await Promise.all([fsExit, popupShown]);
 
+    let identityPopup = document.getElementById("identity-popup");
     ok(
       identityPopup.hasAttribute("panelopen"),
       "Identity popup should be open"
     );
     ok(!window.fullScreen, "Should not be in full-screen");
   });
 });
--- a/browser/base/content/test/general/browser_addCertException.js
+++ b/browser/base/content/test/general/browser_addCertException.js
@@ -10,18 +10,20 @@
 // dialog, using that to add an exception, and finally successfully visiting
 // the site, including showing the right identity box and control center icons.
 add_task(async function() {
   await BrowserTestUtils.openNewForegroundTab(gBrowser);
   await loadBadCertPage("https://expired.example.com");
 
   let { gIdentityHandler } = gBrowser.ownerGlobal;
   let promisePanelOpen = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    gBrowser.ownerGlobal,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   await promisePanelOpen;
 
   let promiseViewShown = BrowserTestUtils.waitForEvent(
     gIdentityHandler._identityPopup,
     "ViewShown"
   );
@@ -60,16 +62,16 @@ add_task(async function() {
     "Using expected icon image in the Control Center main view"
   );
   is(
     securityContentBG,
     'url("chrome://browser/skin/connection-mixed-passive-loaded.svg")',
     "Using expected icon image in the Control Center subview"
   );
 
-  gIdentityHandler._identityPopup.hidden = true;
+  gIdentityHandler._identityPopup.hidePopup();
 
   let certOverrideService = Cc[
     "@mozilla.org/security/certoverride;1"
   ].getService(Ci.nsICertOverrideService);
   certOverrideService.clearValidityOverride("expired.example.com", -1);
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/base/content/test/permissions/browser_autoplay_blocked.js
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.js
@@ -20,18 +20,20 @@ const MUTED_AUTOPLAY_PAGE =
     "https://example.com"
   ) + "browser_autoplay_muted.html";
 
 const AUTOPLAY_PREF = "media.autoplay.default";
 const AUTOPLAY_PERM = "autoplay-media";
 
 function openIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    gBrowser.ownerGlobal,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   return promise;
 }
 
 function closeIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
     gIdentityHandler._identityPopup,
--- a/browser/base/content/test/permissions/browser_permissions.js
+++ b/browser/base/content/test/permissions/browser_permissions.js
@@ -8,41 +8,42 @@ const PERMISSIONS_PAGE =
     "https://example.com"
   ) + "permissions.html";
 const kStrictKeyPressEvents = SpecialPowers.getBoolPref(
   "dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content"
 );
 
 function openIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    window,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   return promise;
 }
 
 function closeIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
     gIdentityHandler._identityPopup,
     "popuphidden"
   );
   gIdentityHandler._identityPopup.hidePopup();
   return promise;
 }
 
 add_task(async function testMainViewVisible() {
   await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function() {
+    await openIdentityPopup();
+
     let permissionsList = document.getElementById(
       "identity-popup-permission-list"
     );
     let emptyLabel = permissionsList.nextElementSibling.nextElementSibling;
-
-    await openIdentityPopup();
-
     ok(!BrowserTestUtils.is_hidden(emptyLabel), "List of permissions is empty");
 
     await closeIdentityPopup();
 
     PermissionTestUtils.add(
       gBrowser.currentURI,
       "camera",
       Services.perms.ALLOW_ACTION
--- a/browser/base/content/test/popups/browser_popup_blocker_identity_block.js
+++ b/browser/base/content/test/popups/browser_popup_blocker_identity_block.js
@@ -19,18 +19,20 @@ const URL = baseURL + "popup_blocker2.ht
 const URI = Services.io.newURI(URL);
 const PRINCIPAL = Services.scriptSecurityManager.createContentPrincipal(
   URI,
   {}
 );
 
 function openIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    window,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   return promise;
 }
 
 function closeIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
     gIdentityHandler._identityPopup,
--- a/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
+++ b/browser/base/content/test/protectionsUI/browser_protectionsUI_open_preferences.js
@@ -5,21 +5,19 @@
 
 const TP_PREF = "privacy.trackingprotection.enabled";
 const TPC_PREF = "network.cookie.cookieBehavior";
 const TRACKING_PAGE =
   "http://tracking.example.org/browser/browser/base/content/test/protectionsUI/trackingPage.html";
 const COOKIE_PAGE =
   "http://tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html";
 
-async function waitAndAssertPreferencesShown(_spotlight, identityPopup) {
+async function waitAndAssertPreferencesShown(_spotlight) {
   await BrowserTestUtils.waitForEvent(
-    identityPopup
-      ? gIdentityHandler._identityPopup
-      : gProtectionsHandler._protectionsPopup,
+    gProtectionsHandler._protectionsPopup,
     "popuphidden"
   );
   await TestUtils.waitForCondition(
     () => gBrowser.currentURI.spec == "about:preferences#privacy",
     "Should open about:preferences."
   );
 
   await SpecialPowers.spawn(
--- a/browser/base/content/test/protectionsUI/head.js
+++ b/browser/base/content/test/protectionsUI/head.js
@@ -163,23 +163,16 @@ function promiseTabLoadEvent(tab, url) {
 
   if (url) {
     BrowserTestUtils.loadURI(tab.linkedBrowser, url);
   }
 
   return loaded;
 }
 
-function openIdentityPopup() {
-  let mainView = document.getElementById("identity-popup-mainView");
-  let viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
-  gIdentityHandler._identityBox.click();
-  return viewShown;
-}
-
 function waitForSecurityChange(numChanges = 1, win = null) {
   if (!win) {
     win = window;
   }
   return new Promise(resolve => {
     let n = 0;
     let listener = {
       onSecurityChange() {
--- a/browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js
+++ b/browser/base/content/test/siteIdentity/browser_deprecatedTLSVersions.js
@@ -9,23 +9,16 @@ const HTTPS_TLS1_0 = "https://tls1.examp
 const HTTPS_TLS1_1 = "https://tls11.example.com";
 const HTTPS_TLS1_2 = "https://tls12.example.com";
 const HTTPS_TLS1_3 = "https://tls13.example.com";
 
 function getIdentityMode(aWindow = window) {
   return aWindow.document.getElementById("identity-box").className;
 }
 
-function openIdentityPopup() {
-  let mainView = document.getElementById("identity-popup-mainView");
-  let viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
-  gIdentityHandler._identityBox.click();
-  return viewShown;
-}
-
 function closeIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
     gIdentityHandler._identityPopup,
     "popuphidden"
   );
   gIdentityHandler._identityPopup.hidePopup();
   return promise;
 }
--- a/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData.js
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_clearSiteData.js
@@ -46,18 +46,20 @@ async function testClearing(
 
       usage = await SiteDataTestUtils.getQuotaUsage(subOrigin);
       Assert.greater(usage, 0, "Should have data for the sub origin.");
     }
 
     // Open the identity popup.
     let { gIdentityHandler } = gBrowser.ownerGlobal;
     let promisePanelOpen = BrowserTestUtils.waitForEvent(
-      gIdentityHandler._identityPopup,
-      "popupshown"
+      gBrowser.ownerGlobal,
+      "popupshown",
+      true,
+      event => event.target == gIdentityHandler._identityPopup
     );
     gIdentityHandler._identityBox.click();
     await promisePanelOpen;
 
     let clearFooter = document.getElementById(
       "identity-popup-clear-sitedata-footer"
     );
     let clearButton = document.getElementById(
--- a/browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_custom_roots.js
@@ -10,19 +10,22 @@ const TEST_PATH = getRootDirectory(gTest
 );
 
 // This test is incredibly simple, because our test framework already
 // imports root certificates by default, so we just visit example.com
 // and verify that the custom root certificates UI is visible.
 add_task(async function test_https() {
   await BrowserTestUtils.withNewTab("https://example.com", async function() {
     let promisePanelOpen = BrowserTestUtils.waitForEvent(
-      gIdentityHandler._identityPopup,
-      "popupshown"
+      window,
+      "popupshown",
+      true,
+      event => event.target == gIdentityHandler._identityPopup
     );
+
     gIdentityHandler._identityBox.click();
     await promisePanelOpen;
     let customRootWarning = document.getElementById(
       "identity-popup-security-decription-custom-root"
     );
     ok(
       BrowserTestUtils.is_visible(customRootWarning),
       "custom root warning is visible"
@@ -42,18 +45,20 @@ add_task(async function test_https() {
     );
   });
 });
 
 // Also check that there are conditions where this isn't shown.
 add_task(async function test_http() {
   await BrowserTestUtils.withNewTab("http://example.com", async function() {
     let promisePanelOpen = BrowserTestUtils.waitForEvent(
-      gIdentityHandler._identityPopup,
-      "popupshown"
+      window,
+      "popupshown",
+      true,
+      event => event.target == gIdentityHandler._identityPopup
     );
     gIdentityHandler._identityBox.click();
     await promisePanelOpen;
     let customRootWarning = document.getElementById(
       "identity-popup-security-decription-custom-root"
     );
     ok(
       BrowserTestUtils.is_hidden(customRootWarning),
--- a/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
+++ b/browser/base/content/test/siteIdentity/browser_identityPopup_focus.js
@@ -14,36 +14,40 @@ async function focusIdentityBox() {
   await focused;
 }
 
 // Access the identity popup via mouseclick. Focus should not be moved inside.
 add_task(async function testIdentityPopupFocusClick() {
   await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] });
   await BrowserTestUtils.withNewTab("https://example.com", async function() {
     let shown = BrowserTestUtils.waitForEvent(
-      gIdentityHandler._identityPopup,
-      "popupshown"
+      window,
+      "popupshown",
+      true,
+      event => event.target == gIdentityHandler._identityPopup
     );
     EventUtils.synthesizeMouseAtCenter(gIdentityHandler._identityBox, {});
     await shown;
     isnot(
       Services.focus.focusedElement,
       document.getElementById("identity-popup-security-expander")
     );
   });
 });
 
 // Access the identity popup via keyboard. Focus should be moved inside.
 add_task(async function testIdentityPopupFocusKeyboard() {
   await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] });
   await BrowserTestUtils.withNewTab("https://example.com", async function() {
     await focusIdentityBox();
     let shown = BrowserTestUtils.waitForEvent(
-      gIdentityHandler._identityPopup,
-      "popupshown"
+      window,
+      "popupshown",
+      true,
+      event => event.target == gIdentityHandler._identityPopup
     );
     EventUtils.sendString(" ");
     await shown;
     is(
       Services.focus.focusedElement,
       document.getElementById("identity-popup-security-expander")
     );
   });
@@ -52,18 +56,20 @@ add_task(async function testIdentityPopu
 // Access the Site Security panel, then move focus with the tab key.
 // Tabbing should be able to reach the More Information button.
 add_task(async function testSiteSecurityTabOrder() {
   await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] });
   await BrowserTestUtils.withNewTab("https://example.com", async function() {
     // 1. Access the identity popup.
     await focusIdentityBox();
     let shown = BrowserTestUtils.waitForEvent(
-      gIdentityHandler._identityPopup,
-      "popupshown"
+      window,
+      "popupshown",
+      true,
+      event => event.target == gIdentityHandler._identityPopup
     );
     EventUtils.sendString(" ");
     await shown;
     is(
       Services.focus.focusedElement,
       document.getElementById("identity-popup-security-expander")
     );
 
--- a/browser/base/content/test/siteIdentity/browser_identity_UI.js
+++ b/browser/base/content/test/siteIdentity/browser_identity_UI.js
@@ -106,17 +106,18 @@ async function runTest(i, forward) {
     gBrowser.selectedBrowser,
     false,
     currentTest.location
   );
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, currentTest.location);
   await loaded;
   await popupHidden;
   ok(
-    BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup),
+    !gIdentityHandler._identityPopup ||
+      BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup),
     "Control Center is hidden"
   );
 
   // Sanity check other values, and the value of gIdentityHandler.getHostForDisplay()
   is(
     gIdentityHandler._uri.spec,
     currentTest.newURI || currentTest.location,
     "location matches for test " + testDesc
@@ -127,18 +128,20 @@ async function runTest(i, forward) {
       gIdentityHandler.getHostForDisplay(),
       currentTest.hostForDisplay,
       "hostForDisplay matches for test " + testDesc
     );
   }
 
   // Open the Control Center and make sure it closes after nav (Bug 1207542).
   let popupShown = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    window,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   info("Waiting for the Control Center to be shown");
   await popupShown;
   ok(
     !BrowserTestUtils.is_hidden(gIdentityHandler._identityPopup),
     "Control Center is visible"
   );
--- a/browser/base/content/test/siteIdentity/head.js
+++ b/browser/base/content/test/siteIdentity/head.js
@@ -1,13 +1,14 @@
 var { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 function openIdentityPopup() {
+  gIdentityHandler._initializePopup();
   let mainView = document.getElementById("identity-popup-mainView");
   let viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
   gIdentityHandler._identityBox.click();
   return viewShown;
 }
 
 /**
  * Waits for a load (or custom) event to finish in a given tab. If provided
@@ -236,18 +237,20 @@ async function assertMixedContentBlockin
         'url("chrome://browser/skin/connection-mixed-passive-loaded.svg")',
         "Using active blocked and passive loaded icon"
       );
     }
   }
 
   // Make sure the identity popup has the correct mixedcontent states
   let promisePanelOpen = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    tabbrowser.ownerGlobal,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   await promisePanelOpen;
   let popupAttr = doc
     .getElementById("identity-popup")
     .getAttribute("mixedcontent");
   let bodyAttr = doc
     .getElementById("identity-popup-securityView-body")
--- a/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
+++ b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
@@ -73,18 +73,20 @@ add_task(async function() {
   is(
     Services.perms.ALLOW_ACTION,
     PermissionTestUtils.testPermission(pageWithAlert, "focus-tab-by-prompt"),
     "Tab switching should now be allowed"
   );
 
   // Check if the control center shows the correct permission.
   let shown = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    window,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   await shown;
   let labelText = SitePermissions.getPermissionLabel("focus-tab-by-prompt");
   let permissionsList = document.getElementById(
     "identity-popup-permission-list"
   );
   let label = permissionsList.querySelector(".identity-popup-permission-label");
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -738,46 +738,41 @@ var gTests = [
         await getMediaCaptureState(),
         { video: true },
         "expected camera to be shared"
       );
 
       await indicator;
       await checkSharingUI({ video: true });
 
-      ok(
-        gIdentityHandler._identityPopup.hidden,
-        "control center should be hidden"
-      );
+      ok(identityPopupHidden(), "control center should be hidden");
       if (USING_LEGACY_INDICATOR && IS_MAC) {
         let activeStreams = webrtcUI.getActiveStreams(true, false, false);
         webrtcUI.showSharingDoorhanger(activeStreams[0]);
       } else {
         let win = Services.wm.getMostRecentWindow(
           "Browser:WebRTCGlobalIndicator"
         );
 
         // The legacy indicator uses a different button ID when sharing
         // your camera.
         let buttonID = USING_LEGACY_INDICATOR
           ? "audioVideoButton"
           : "camera-button";
 
         let elt = win.document.getElementById(buttonID);
         EventUtils.synthesizeMouseAtCenter(elt, {}, win);
-        await TestUtils.waitForCondition(
-          () => !gIdentityHandler._identityPopup.hidden
-        );
       }
-      ok(
-        !gIdentityHandler._identityPopup.hidden,
-        "control center should be open"
+      await TestUtils.waitForCondition(
+        () => !identityPopupHidden(),
+        "wait for control center to open"
       );
+      ok(!identityPopupHidden(), "control center should be open");
 
-      gIdentityHandler._identityPopup.hidden = true;
+      gIdentityHandler._identityPopup.hidePopup();
 
       await closeStream();
     },
   },
 
   {
     desc: "'Always Allow' disabled on http pages",
     run: async function checkNoAlwaysOnHttp() {
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_screen.js
@@ -659,39 +659,34 @@ var gTests = [
       Assert.deepEqual(
         await getMediaCaptureState(),
         { screen: "Screen" },
         "expected screen to be shared"
       );
       await indicator;
       await checkSharingUI({ screen: "Screen" });
 
-      ok(
-        gIdentityHandler._identityPopup.hidden,
-        "control center should be hidden"
-      );
+      ok(identityPopupHidden(), "control center should be hidden");
       if (IS_MAC) {
         let activeStreams = webrtcUI.getActiveStreams(false, false, true);
         webrtcUI.showSharingDoorhanger(activeStreams[0]);
       } else {
         let win = Services.wm.getMostRecentWindow(
           "Browser:WebRTCGlobalIndicator"
         );
         let elt = win.document.getElementById("screenShareButton");
         EventUtils.synthesizeMouseAtCenter(elt, {}, win);
-        await TestUtils.waitForCondition(
-          () => !gIdentityHandler._identityPopup.hidden
-        );
       }
-      ok(
-        !gIdentityHandler._identityPopup.hidden,
-        "control center should be open"
+      await TestUtils.waitForCondition(
+        () => !identityPopupHidden(),
+        "wait for control center to open"
       );
+      ok(!identityPopupHidden(), "control center should be open");
 
-      gIdentityHandler._identityPopup.hidden = true;
+      gIdentityHandler._identityPopup.hidePopup();
 
       await closeStream();
     },
   },
 
   {
     desc: "Only persistent block is possible for screen sharing",
     run: async function checkPersistentPermissions() {
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media_tear_off_tab.js
@@ -43,31 +43,33 @@ var gTests = [
       let win = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
       await whenDelayedStartupFinished(win);
       await checkSharingUI({ audio: true, video: true }, win);
 
       await enableObserverVerification(win.gBrowser.selectedBrowser);
 
       // Clicking the global sharing indicator should open the control center in
       // the second window.
-      ok(
-        win.gIdentityHandler._identityPopup.hidden,
-        "control center should be hidden"
-      );
+      ok(identityPopupHidden(win), "control center should be hidden");
       let activeStreams = webrtcUI.getActiveStreams(true, false, false);
       webrtcUI.showSharingDoorhanger(activeStreams[0], "Devices");
+      // If the popup gets hidden before being shown, by stray focus/activate
+      // events, don't bother failing the test. It's enough to know that we
+      // started showing the popup.
+      let popup = win.gIdentityHandler._identityPopup;
+      let hiddenEvent = BrowserTestUtils.waitForEvent(popup, "popuphidden");
+      let shownEvent = BrowserTestUtils.waitForEvent(popup, "popupshown");
+      let ev = await Promise.race([hiddenEvent, shownEvent]);
+      ok(ev.type, "Tried to show popup");
+      win.gIdentityHandler._identityPopup.hidePopup();
+
       ok(
-        !win.gIdentityHandler._identityPopup.hidden,
-        "control center should be open in the second window"
-      );
-      ok(
-        gIdentityHandler._identityPopup.hidden,
+        identityPopupHidden(window),
         "control center should be hidden in the first window"
       );
-      win.gIdentityHandler._identityPopup.hidden = true;
 
       await disableObserverVerification();
 
       // Closing the new window should remove all sharing indicators.
       let promises = [
         expectObserverCalledOnClose(
           "recording-device-events",
           1,
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -83,18 +83,20 @@ function promiseIndicatorWindow() {
 
 async function assertWebRTCIndicatorStatus(expected) {
   let ui = ChromeUtils.import("resource:///modules/webrtcUI.jsm", {}).webrtcUI;
   let expectedState = expected ? "visible" : "hidden";
   let msg = "WebRTC indicator " + expectedState;
   if (!expected && ui.showGlobalIndicator) {
     // It seems the global indicator is not always removed synchronously
     // in some cases.
-    info("waiting for the global indicator to be hidden");
-    await TestUtils.waitForCondition(() => !ui.showGlobalIndicator);
+    await TestUtils.waitForCondition(
+      () => !ui.showGlobalIndicator,
+      "waiting for the global indicator to be hidden"
+    );
   }
   is(ui.showGlobalIndicator, !!expected, msg);
 
   let expectVideo = false,
     expectAudio = false,
     expectScreen = false;
   if (expected) {
     if (expected.video) {
@@ -519,16 +521,23 @@ async function stopSharing(
   aWindow = window
 ) {
   let promiseRecordingEvent = expectObserverCalled(
     "recording-device-events",
     1,
     aFrameBC
   );
   aWindow.gIdentityHandler._identityBox.click();
+  let popup = aWindow.gIdentityHandler._identityPopup;
+  // If the popup gets hidden before being shown, by stray focus/activate
+  // events, don't bother failing the test. It's enough to know that we
+  // started showing the popup.
+  let hiddenEvent = BrowserTestUtils.waitForEvent(popup, "popuphidden");
+  let shownEvent = BrowserTestUtils.waitForEvent(popup, "popupshown");
+  await Promise.race([hiddenEvent, shownEvent]);
   let doc = aWindow.document;
   let permissions = doc.getElementById("identity-popup-permission-list");
   let cancelButton = permissions.querySelector(
     ".identity-popup-permission-icon." +
       aType +
       "-icon ~ " +
       ".identity-popup-permission-remove-button"
   );
@@ -545,17 +554,17 @@ async function stopSharing(
     observerPromise2 = expectObserverCalled(
       "recording-window-ended",
       1,
       aFrameBC
     );
   }
 
   cancelButton.click();
-  aWindow.gIdentityHandler._identityPopup.hidden = true;
+  popup.hidePopup();
 
   await promiseRecordingEvent;
   await observerPromise1;
   await observerPromise2;
 
   if (!aShouldKeepSharing) {
     await checkNotSharing();
   }
@@ -720,16 +729,23 @@ async function checkSharingUI(
   is(
     webrtcSharingIcon.hasAttribute("paused"),
     allStreamsPaused,
     "sharing icon(s) should be in paused state when paused"
   );
 
   // Then check the sharing indicators inside the control center panel.
   identityBox.click();
+  let popup = aWin.gIdentityHandler._identityPopup;
+  // If the popup gets hidden before being shown, by stray focus/activate
+  // events, don't bother failing the test. It's enough to know that we
+  // started showing the popup.
+  let hiddenEvent = BrowserTestUtils.waitForEvent(popup, "popuphidden");
+  let shownEvent = BrowserTestUtils.waitForEvent(popup, "popupshown");
+  await Promise.race([hiddenEvent, shownEvent]);
   let permissions = doc.getElementById("identity-popup-permission-list");
   for (let id of ["microphone", "camera", "screen"]) {
     let convertId = idToConvert => {
       if (idToConvert == "camera") {
         return "video";
       }
       if (idToConvert == "microphone") {
         return "audio";
@@ -758,17 +774,21 @@ async function checkSharingUI(
       // This will happen if there are persistent permissions set.
       ok(
         !icon[0].classList.contains("in-use"),
         "if shown, the " + id + " icon should not have the in-use class"
       );
       is(icon.length, 1, "should not show more than 1 " + id + " icon");
     }
   }
-  aWin.gIdentityHandler._identityPopup.hidden = true;
+  aWin.gIdentityHandler._identityPopup.hidePopup();
+  await TestUtils.waitForCondition(
+    () => identityPopupHidden(aWin),
+    "identity popup should be hidden"
+  );
 
   // Check the global indicators.
   await assertWebRTCIndicatorStatus(aExpectedGlobal || aExpected);
 }
 
 async function checkNotSharing() {
   Assert.deepEqual(
     await getMediaCaptureState(),
@@ -869,26 +889,31 @@ async function disableObserverVerificati
     await BrowserTestUtils.stopObservingTopics(bc, observerTopics).catch(
       reason => {
         ok(false, "Failed " + reason);
       }
     );
   }
 }
 
+function identityPopupHidden(win = window) {
+  let popup = win.gIdentityHandler._identityPopup;
+  return !popup || popup.state == "closed";
+}
+
 async function runTests(tests, options = {}) {
   let browser = await openNewTestTab(options.relativeURI);
 
   is(
     PopupNotifications._currentNotifications.length,
     0,
     "should start the test without any prior popup notification"
   );
   ok(
-    gIdentityHandler._identityPopup.hidden,
+    identityPopupHidden(),
     "should start the test with the control center hidden"
   );
 
   // Set prefs so that permissions prompts are shown and loopback devices
   // are not used. To test the chrome we want prompts to be shown, and
   // these tests are flakey when using loopback devices (though it would
   // be desirable to make them work with loopback in future). See bug 1643711.
   let prefs = [
--- a/browser/components/controlcenter/content/identityPanel.inc.xhtml
+++ b/browser/components/controlcenter/content/identityPanel.inc.xhtml
@@ -1,16 +1,16 @@
 <!-- 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/. -->
 
+<html:template id="template-identity-popup">
 <panel id="identity-popup"
        class="panel-no-padding"
        type="arrow"
-       hidden="true"
        role="alertdialog"
        noautofocus="true"
        aria-labelledby="identity-popup-mainView-panel-header-span"
        onpopupshown="gIdentityHandler.onPopupShown(event);"
        onpopuphidden="gIdentityHandler.onPopupHidden(event);"
        orient="vertical">
 
   <panelmultiview id="identity-popup-multiView"
@@ -172,8 +172,9 @@
         <button id="identity-popup-more-info"
                 data-l10n-id="identity-more-info-link-text"
                 oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
       </vbox>
 
     </panelview>
   </panelmultiview>
 </panel>
+</html:template>
--- a/browser/components/extensions/test/browser/browser_ext_persistent_storage_permission_indication.js
+++ b/browser/components/extensions/test/browser/browser_ext_persistent_storage_permission_indication.js
@@ -3,18 +3,20 @@
 "use strict";
 
 const { PermissionTestUtils } = ChromeUtils.import(
   "resource://testing-common/PermissionTestUtils.jsm"
 );
 
 function openIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    window,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   return promise;
 }
 
 function closeIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
     gIdentityHandler._identityPopup,
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
@@ -292,16 +292,18 @@ async function loadPage(url) {
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
   await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, url);
 }
 
 async function openIdentityPopup(expand) {
   let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
   let gBrowser = browserWindow.gBrowser;
   let { gIdentityHandler } = gBrowser.ownerGlobal;
+  // Ensure the popup is available, if it's never been opened.
+  gIdentityHandler._initializePopup();
   gIdentityHandler._identityPopup.hidePopup();
   // Disable the popup shadow on OSX until we have figured out bug 1425253.
   if (AppConstants.platform == "macosx") {
     gIdentityHandler._identityPopup.classList.add("no-shadow");
   }
   gIdentityHandler._identityBox.querySelector("#identity-icon").click();
   if (expand) {
     // give some time for opening to avoid weird style issues
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_arrowpanels.js
@@ -1,14 +1,16 @@
 "use strict";
 
 function openIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
-    gIdentityHandler._identityPopup,
-    "popupshown"
+    window,
+    "popupshown",
+    true,
+    event => event.target == gIdentityHandler._identityPopup
   );
   gIdentityHandler._identityBox.click();
   return promise;
 }
 
 function closeIdentityPopup() {
   let promise = BrowserTestUtils.waitForEvent(
     gIdentityHandler._identityPopup,