tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ExtensionPopups.sys.mjs (22630B)


      1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim: set sts=2 sw=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 const lazy = {};
      8 
      9 ChromeUtils.defineESModuleGetters(lazy, {
     10  CustomizableUI:
     11    "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs",
     12  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
     13  setTimeout: "resource://gre/modules/Timer.sys.mjs",
     14 });
     15 
     16 import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
     17 import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
     18 
     19 var { DefaultWeakMap, promiseEvent } = ExtensionUtils;
     20 
     21 const { makeWidgetId } = ExtensionCommon;
     22 
     23 const POPUP_LOAD_TIMEOUT_MS = 200;
     24 
     25 function promisePopupShown(popup) {
     26  return new Promise(resolve => {
     27    if (popup.state == "open") {
     28      resolve();
     29    } else {
     30      popup.addEventListener(
     31        "popupshown",
     32        function () {
     33          resolve();
     34        },
     35        { once: true }
     36      );
     37    }
     38  });
     39 }
     40 
     41 const REMOTE_PANEL_ID = "webextension-remote-preload-panel";
     42 
     43 export class BasePopup {
     44  constructor(
     45    extension,
     46    viewNode,
     47    popupURL,
     48    browserStyle,
     49    fixedWidth = false,
     50    blockParser = false
     51  ) {
     52    this.extension = extension;
     53    this.popupURL = popupURL;
     54    this.viewNode = viewNode;
     55    this.browserStyle = browserStyle;
     56    this.window = viewNode.ownerGlobal;
     57    this.destroyed = false;
     58    this.fixedWidth = fixedWidth;
     59    this.blockParser = blockParser;
     60 
     61    extension.callOnClose(this);
     62 
     63    this.contentReady = new Promise(resolve => {
     64      this._resolveContentReady = resolve;
     65    });
     66 
     67    this.window.addEventListener("unload", this);
     68    this.viewNode.addEventListener(this.DESTROY_EVENT, this);
     69    this.panel.addEventListener("popuppositioned", this, {
     70      once: true,
     71      capture: true,
     72    });
     73 
     74    this.browser = null;
     75    this.browserLoaded = new Promise((resolve, reject) => {
     76      this.browserLoadedDeferred = { resolve, reject };
     77    });
     78    this.browserReady = this.createBrowser(viewNode, popupURL);
     79 
     80    BasePopup.instances.get(this.window).set(extension, this);
     81  }
     82 
     83  static for(extension, window) {
     84    return BasePopup.instances.get(window).get(extension);
     85  }
     86 
     87  close() {
     88    this.closePopup();
     89  }
     90 
     91  destroy() {
     92    this.extension.forgetOnClose(this);
     93 
     94    this.window.removeEventListener("unload", this);
     95 
     96    this.destroyed = true;
     97    this.browserLoadedDeferred.reject(new Error("Popup destroyed"));
     98    // Ignore unhandled rejections if the "attach" method is not called.
     99    this.browserLoaded.catch(() => {});
    100 
    101    BasePopup.instances.get(this.window).delete(this.extension);
    102 
    103    return this.browserReady.then(() => {
    104      if (this.browser) {
    105        this.destroyBrowser(this.browser, true);
    106        this.browser.parentNode.remove();
    107      }
    108      if (this.stack) {
    109        this.stack.remove();
    110      }
    111 
    112      if (this.viewNode) {
    113        this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
    114        delete this.viewNode.customRectGetter;
    115      }
    116 
    117      let { panel } = this;
    118      if (panel) {
    119        panel.removeEventListener("popuppositioned", this, { capture: true });
    120      }
    121      if (panel && panel.id !== REMOTE_PANEL_ID) {
    122        panel.style.removeProperty("--arrowpanel-background");
    123        panel.style.removeProperty("--arrowpanel-border-color");
    124        panel.removeAttribute("remote");
    125      }
    126 
    127      this.browser = null;
    128      this.stack = null;
    129      this.viewNode = null;
    130    });
    131  }
    132 
    133  destroyBrowser(browser, finalize = false) {
    134    let mm = browser.messageManager;
    135    // If the browser has already been removed from the document, because the
    136    // popup was closed externally, there will be no message manager here, so
    137    // just replace our receiveMessage method with a stub.
    138    if (mm) {
    139      mm.removeMessageListener("Extension:BrowserBackgroundChanged", this);
    140      mm.removeMessageListener("Extension:BrowserContentLoaded", this);
    141      mm.removeMessageListener("Extension:BrowserResized", this);
    142    } else if (finalize) {
    143      this.receiveMessage = () => {};
    144    }
    145    browser.removeEventListener("pagetitlechanged", this);
    146    browser.removeEventListener("DOMWindowClose", this);
    147    browser.removeEventListener("DoZoomEnlargeBy10", this);
    148    browser.removeEventListener("DoZoomReduceBy10", this);
    149  }
    150 
    151  // Returns the name of the event fired on `viewNode` when the popup is being
    152  // destroyed. This must be implemented by every subclass.
    153  get DESTROY_EVENT() {
    154    throw new Error("Not implemented");
    155  }
    156 
    157  get STYLESHEETS() {
    158    let sheets = [];
    159 
    160    if (this.browserStyle) {
    161      sheets.push("chrome://browser/content/extension.css");
    162    }
    163    if (!this.fixedWidth) {
    164      sheets.push("chrome://browser/content/extension-popup-panel.css");
    165    }
    166 
    167    return sheets;
    168  }
    169 
    170  get panel() {
    171    let panel = this.viewNode;
    172    while (panel && panel.localName != "panel") {
    173      panel = panel.parentNode;
    174    }
    175    return panel;
    176  }
    177 
    178  receiveMessage({ name, data }) {
    179    switch (name) {
    180      case "Extension:BrowserBackgroundChanged":
    181        this.setBackground(data.background);
    182        break;
    183 
    184      case "Extension:BrowserContentLoaded":
    185        this.browserLoadedDeferred.resolve();
    186        break;
    187 
    188      case "Extension:BrowserResized":
    189        this._resolveContentReady();
    190        if (this.ignoreResizes) {
    191          this.dimensions = data;
    192        } else {
    193          this.resizeBrowser(data);
    194        }
    195        break;
    196    }
    197  }
    198 
    199  handleEvent(event) {
    200    switch (event.type) {
    201      case "unload":
    202      case this.DESTROY_EVENT:
    203        if (!this.destroyed) {
    204          this.destroy();
    205        }
    206        break;
    207      case "popuppositioned":
    208        if (!this.destroyed) {
    209          this.browserLoaded
    210            .then(() => {
    211              if (this.destroyed) {
    212                return;
    213              }
    214              // Wait the reflow before asking the popup panel to grab the focus, otherwise
    215              // `nsFocusManager::SetFocus` may ignore out request because the panel view
    216              // visibility is still set to `ViewVisibility::Hide` (waiting the document
    217              // to be fully flushed makes us sure that when the popup panel grabs the focus
    218              // nsMenuPopupFrame::LayoutPopup has already been colled and set the frame
    219              // visibility to `ViewVisibility::Show`).
    220              this.browser.ownerGlobal.promiseDocumentFlushed(() => {
    221                if (this.destroyed) {
    222                  return;
    223                }
    224                this.browser.messageManager.sendAsyncMessage(
    225                  "Extension:GrabFocus",
    226                  {}
    227                );
    228              });
    229            })
    230            .catch(() => {
    231              // If the panel closes too fast an exception is raised here and tests will fail.
    232            });
    233        }
    234        break;
    235 
    236      case "pagetitlechanged":
    237        this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
    238        break;
    239 
    240      case "DOMWindowClose":
    241        this.closePopup();
    242        break;
    243 
    244      case "DoZoomEnlargeBy10": {
    245        const browser = event.target;
    246        let { ZoomManager } = browser.ownerGlobal;
    247        let zoom = this.browser.fullZoom;
    248        zoom += 0.1;
    249        if (zoom > ZoomManager.MAX) {
    250          zoom = ZoomManager.MAX;
    251        }
    252        browser.fullZoom = zoom;
    253        break;
    254      }
    255 
    256      case "DoZoomReduceBy10": {
    257        const browser = event.target;
    258        let { ZoomManager } = browser.ownerGlobal;
    259        let zoom = browser.fullZoom;
    260        zoom -= 0.1;
    261        if (zoom < ZoomManager.MIN) {
    262          zoom = ZoomManager.MIN;
    263        }
    264        browser.fullZoom = zoom;
    265        break;
    266      }
    267    }
    268  }
    269 
    270  createBrowser(viewNode, popupURL = null) {
    271    let document = viewNode.ownerDocument;
    272 
    273    let stack = document.createXULElement("stack");
    274    stack.setAttribute("class", "webextension-popup-stack");
    275 
    276    let browser = document.createXULElement("browser");
    277    browser.setAttribute("type", "content");
    278    browser.setAttribute("disableglobalhistory", "true");
    279    browser.setAttribute("messagemanagergroup", "webext-browsers");
    280    browser.setAttribute("class", "webextension-popup-browser");
    281    browser.setAttribute("webextension-view-type", "popup");
    282    browser.setAttribute("tooltip", "aHTMLTooltip");
    283    browser.setAttribute("contextmenu", "contentAreaContextMenu");
    284    browser.setAttribute("autocompletepopup", "PopupAutoComplete");
    285    browser.setAttribute("constrainpopups", "false");
    286 
    287    // Ensure the browser will initially load in the same group as other
    288    // browsers from the same extension.
    289    browser.setAttribute(
    290      "initialBrowsingContextGroupId",
    291      this.extension.policy.browsingContextGroupId
    292    );
    293 
    294    if (this.extension.remote) {
    295      browser.setAttribute("remote", "true");
    296      browser.setAttribute("remoteType", this.extension.remoteType);
    297      browser.setAttribute("maychangeremoteness", "true");
    298    }
    299 
    300    // We only need flex sizing for the sake of the slide-in sub-views of the
    301    // main menu panel, so that the browser occupies the full width of the view,
    302    // and also takes up any extra height that's available to it.
    303    browser.setAttribute("flex", "1");
    304    stack.setAttribute("flex", "1");
    305 
    306    // Note: When using noautohide panels, the popup manager will add width and
    307    // height attributes to the panel, breaking our resize code, if the browser
    308    // starts out smaller than 30px by 10px. This isn't an issue now, but it
    309    // will be if and when we popup debugging.
    310 
    311    this.browser = browser;
    312    this.stack = stack;
    313 
    314    let readyPromise;
    315    if (this.extension.remote) {
    316      readyPromise = promiseEvent(browser, "XULFrameLoaderCreated");
    317    } else {
    318      readyPromise = promiseEvent(browser, "load");
    319    }
    320 
    321    stack.appendChild(browser);
    322    viewNode.appendChild(stack);
    323 
    324    if (!this.extension.remote) {
    325      // FIXME: bug 1494029 - this code used to rely on the browser binding
    326      // accessing browser.contentWindow. This is a stopgap to continue doing
    327      // that, but we should get rid of it in the long term.
    328      browser.contentWindow; // eslint-disable-line no-unused-expressions
    329    }
    330 
    331    let setupBrowser = browser => {
    332      let mm = browser.messageManager;
    333      mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
    334      mm.addMessageListener("Extension:BrowserContentLoaded", this);
    335      mm.addMessageListener("Extension:BrowserResized", this);
    336      browser.addEventListener("pagetitlechanged", this);
    337      browser.addEventListener("DOMWindowClose", this);
    338      browser.addEventListener("DoZoomEnlargeBy10", this, true); // eslint-disable-line mozilla/balanced-listeners
    339      browser.addEventListener("DoZoomReduceBy10", this, true); // eslint-disable-line mozilla/balanced-listeners
    340 
    341      lazy.ExtensionParent.apiManager.emit(
    342        "extension-browser-inserted",
    343        browser
    344      );
    345      return browser;
    346    };
    347 
    348    const initBrowser = () => {
    349      setupBrowser(browser);
    350      let mm = browser.messageManager;
    351 
    352      mm.loadFrameScript(
    353        "chrome://extensions/content/ext-browser-content.js",
    354        false,
    355        true
    356      );
    357 
    358      mm.sendAsyncMessage("Extension:InitBrowser", {
    359        allowScriptsToClose: true,
    360        blockParser: this.blockParser,
    361        fixedWidth: this.fixedWidth,
    362        maxWidth: 800,
    363        maxHeight: 600,
    364        stylesheets: this.STYLESHEETS,
    365      });
    366    };
    367 
    368    browser.addEventListener("DidChangeBrowserRemoteness", initBrowser); // eslint-disable-line mozilla/balanced-listeners
    369 
    370    if (!popupURL) {
    371      // For remote browsers, we can't do any setup until the frame loader is
    372      // created. Non-remote browsers get a message manager immediately, so
    373      // there's no need to wait for the load event.
    374      if (this.extension.remote) {
    375        return readyPromise.then(() => setupBrowser(browser));
    376      }
    377      return setupBrowser(browser);
    378    }
    379 
    380    return readyPromise.then(() => {
    381      initBrowser();
    382      browser.fixupAndLoadURIString(popupURL, {
    383        triggeringPrincipal: this.extension.principal,
    384      });
    385    });
    386  }
    387 
    388  unblockParser() {
    389    this.browserReady.then(() => {
    390      if (this.destroyed) {
    391        return;
    392      }
    393      // Only block the parser for the preloaded browser, initBrowser will be
    394      // called again when the browserAction popup is navigated and we should
    395      // not block the parser in that case, otherwise the navigating the popup
    396      // to another extension page will never complete and the popup will
    397      // stay stuck on the previous extension page. See Bug 1747813.
    398      this.blockParser = false;
    399      this.browser.messageManager.sendAsyncMessage("Extension:UnblockParser");
    400    });
    401  }
    402 
    403  resizeBrowser({ width, height, detail }) {
    404    if (this.fixedWidth) {
    405      // Figure out how much extra space we have on the side of the panel
    406      // opposite the arrow.
    407      let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
    408      let maxHeight = this.viewHeight + this.extraHeight[side];
    409 
    410      height = Math.min(height, maxHeight);
    411      this.browser.style.height = `${height}px`;
    412 
    413      // Used by the panelmultiview code to figure out sizing without reparenting
    414      // (which would destroy the browser and break us).
    415      this.lastCalculatedInViewHeight = Math.max(height, this.viewHeight);
    416    } else {
    417      this.browser.style.width = `${width}px`;
    418      this.browser.style.minWidth = `${width}px`;
    419      this.browser.style.height = `${height}px`;
    420      this.browser.style.minHeight = `${height}px`;
    421    }
    422 
    423    let event = new this.window.CustomEvent("WebExtPopupResized", { detail });
    424    this.browser.dispatchEvent(event);
    425  }
    426 
    427  setBackground(background) {
    428    // Panels inherit the applied theme (light, dark, etc) and there is a high
    429    // likelihood that most extension authors will not have tested with a dark theme.
    430    // If they have not set a background-color, we force it to white to ensure visibility
    431    // of the extension content. Passing `null` should be treated the same as no argument,
    432    // which is why we can't use default parameters here.
    433    if (!background) {
    434      background = "#fff";
    435    }
    436    if (this.panel.id != "widget-overflow") {
    437      this.panel.style.setProperty("--arrowpanel-background", background);
    438    }
    439    if (background == "#fff") {
    440      // Set a usable default color that work with the default background-color.
    441      this.panel.style.setProperty(
    442        "--arrowpanel-border-color",
    443        "hsla(210,4%,10%,.15)"
    444      );
    445    }
    446    this.background = background;
    447  }
    448 }
    449 
    450 /**
    451 * A map of active popups for a given browser window.
    452 *
    453 * WeakMap[window -> WeakMap[Extension -> BasePopup]]
    454 */
    455 BasePopup.instances = new DefaultWeakMap(() => new WeakMap());
    456 
    457 export class PanelPopup extends BasePopup {
    458  constructor(extension, document, popupURL, browserStyle) {
    459    let panel = document.createXULElement("panel");
    460    panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
    461    panel.setAttribute("class", "browser-extension-panel panel-no-padding");
    462    panel.setAttribute("tabspecific", "true");
    463    panel.setAttribute("type", "arrow");
    464    panel.setAttribute("role", "group");
    465    if (extension.remote) {
    466      panel.setAttribute("remote", "true");
    467    }
    468    panel.setAttribute("neverhidden", "true");
    469 
    470    document.getElementById("mainPopupSet").appendChild(panel);
    471 
    472    panel.addEventListener(
    473      "popupshowing",
    474      () => {
    475        let event = new this.window.CustomEvent("WebExtPopupLoaded", {
    476          bubbles: true,
    477          detail: { extension },
    478        });
    479        this.browser.dispatchEvent(event);
    480      },
    481      { once: true }
    482    );
    483 
    484    super(extension, panel, popupURL, browserStyle);
    485  }
    486 
    487  get DESTROY_EVENT() {
    488    return "popuphidden";
    489  }
    490 
    491  destroy() {
    492    super.destroy();
    493    this.viewNode.remove();
    494    this.viewNode = null;
    495  }
    496 
    497  closePopup() {
    498    promisePopupShown(this.viewNode).then(() => {
    499      // Make sure we're not already destroyed, or removed from the DOM.
    500      if (this.viewNode && this.viewNode.hidePopup) {
    501        this.viewNode.hidePopup();
    502      }
    503    });
    504  }
    505 }
    506 
    507 export class ViewPopup extends BasePopup {
    508  constructor(
    509    extension,
    510    window,
    511    popupURL,
    512    browserStyle,
    513    fixedWidth,
    514    blockParser
    515  ) {
    516    let document = window.document;
    517 
    518    let createPanel = remote => {
    519      let panel = document.createXULElement("panel");
    520      panel.setAttribute("type", "arrow");
    521      if (remote) {
    522        panel.setAttribute("remote", "true");
    523      }
    524      panel.setAttribute("neverhidden", "true");
    525 
    526      document.getElementById("mainPopupSet").appendChild(panel);
    527      return panel;
    528    };
    529 
    530    // Create a temporary panel to hold the browser while it pre-loads its
    531    // content. This panel will never be shown, but the browser's docShell will
    532    // be swapped with the browser in the real panel when it's ready. For remote
    533    // extensions, this popup is shared between all extensions.
    534    let panel;
    535    if (extension.remote) {
    536      panel = document.getElementById(REMOTE_PANEL_ID);
    537      if (!panel) {
    538        panel = createPanel(true);
    539        panel.id = REMOTE_PANEL_ID;
    540      }
    541    } else {
    542      panel = createPanel();
    543    }
    544 
    545    super(extension, panel, popupURL, browserStyle, fixedWidth, blockParser);
    546 
    547    this.ignoreResizes = true;
    548 
    549    this.attached = false;
    550    this.shown = false;
    551    this.tempPanel = panel;
    552    this.tempBrowser = this.browser;
    553 
    554    // NOTE: this class is added to the preload browser and never removed because
    555    // the preload browser is then switched with a new browser once we are about to
    556    // make the popup visible (this class is not actually used anywhere but it may
    557    // be useful to keep it around to be able to identify the preload buffer while
    558    // investigating issues).
    559    this.browser.classList.add("webextension-preload-browser");
    560  }
    561 
    562  /**
    563   * Attaches the pre-loaded browser to the given view node, and reserves a
    564   * promise which resolves when the browser is ready.
    565   *
    566   * @param {Element} viewNode
    567   *        The node to attach the browser to.
    568   * @returns {Promise<boolean>}
    569   *        Resolves when the browser is ready. Resolves to `false` if the
    570   *        browser was destroyed before it was fully loaded, and the popup
    571   *        should be closed, or `true` otherwise.
    572   */
    573  async attach(viewNode) {
    574    if (this.destroyed) {
    575      return false;
    576    }
    577    this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
    578    this.panel.removeEventListener("popuppositioned", this, {
    579      once: true,
    580      capture: true,
    581    });
    582 
    583    this.viewNode = viewNode;
    584    this.viewNode.addEventListener(this.DESTROY_EVENT, this);
    585    this.viewNode.setAttribute("closemenu", "none");
    586 
    587    this.panel.addEventListener("popuppositioned", this, {
    588      once: true,
    589      capture: true,
    590    });
    591    if (this.extension.remote) {
    592      this.panel.setAttribute("remote", "true");
    593    }
    594 
    595    // Wait until the browser element is fully initialized, and give it at least
    596    // a short grace period to finish loading its initial content, if necessary.
    597    //
    598    // In practice, the browser that was created by the mousdown handler should
    599    // nearly always be ready by this point.
    600    await Promise.all([
    601      this.browserReady,
    602      Promise.race([
    603        // This promise may be rejected if the popup calls window.close()
    604        // before it has fully loaded.
    605        this.browserLoaded.catch(() => {}),
    606        new Promise(resolve => lazy.setTimeout(resolve, POPUP_LOAD_TIMEOUT_MS)),
    607      ]),
    608    ]);
    609 
    610    const { panel } = this;
    611 
    612    if (!this.destroyed && !panel) {
    613      this.destroy();
    614    }
    615 
    616    if (this.destroyed) {
    617      lazy.CustomizableUI.hidePanelForNode(viewNode);
    618      return false;
    619    }
    620 
    621    this.attached = true;
    622 
    623    this.setBackground(this.background);
    624 
    625    let flushPromise = this.window.promiseDocumentFlushed(() => {
    626      let win = this.window;
    627 
    628      // Calculate the extra height available on the screen above and below the
    629      // menu panel. Use that to calculate the how much the sub-view may grow.
    630      let popupRect = panel.getBoundingClientRect();
    631      let screenBottom = win.screen.availTop + win.screen.availHeight;
    632      let popupBottom = win.mozInnerScreenY + popupRect.bottom;
    633      let popupTop = win.mozInnerScreenY + popupRect.top;
    634 
    635      // Store the initial height of the view, so that we never resize menu panel
    636      // sub-views smaller than the initial height of the menu.
    637      this.viewHeight = viewNode.getBoundingClientRect().height;
    638 
    639      this.extraHeight = {
    640        bottom: Math.max(0, screenBottom - popupBottom),
    641        top: Math.max(0, popupTop - win.screen.availTop),
    642      };
    643    });
    644 
    645    // Create a new browser in the real popup.
    646    let browser = this.browser;
    647    await this.createBrowser(this.viewNode);
    648 
    649    this.browser.swapDocShells(browser);
    650    this.destroyBrowser(browser);
    651 
    652    await flushPromise;
    653 
    654    // Check if the popup has been destroyed while we were waiting for the
    655    // document flush promise to be resolve.
    656    if (this.destroyed) {
    657      this.closePopup();
    658      this.destroy();
    659      return false;
    660    }
    661 
    662    if (this.dimensions) {
    663      if (this.fixedWidth) {
    664        delete this.dimensions.width;
    665      }
    666      this.resizeBrowser(this.dimensions);
    667    }
    668 
    669    this.ignoreResizes = false;
    670 
    671    this.viewNode.customRectGetter = () => {
    672      return { height: this.lastCalculatedInViewHeight || this.viewHeight };
    673    };
    674 
    675    this.removeTempPanel();
    676 
    677    this.shown = true;
    678 
    679    if (this.destroyed) {
    680      this.closePopup();
    681      this.destroy();
    682      return false;
    683    }
    684 
    685    let event = new this.window.CustomEvent("WebExtPopupLoaded", {
    686      bubbles: true,
    687      detail: { extension: this.extension },
    688    });
    689    this.browser.dispatchEvent(event);
    690 
    691    return true;
    692  }
    693 
    694  removeTempPanel() {
    695    if (this.tempPanel) {
    696      if (this.tempPanel.id !== REMOTE_PANEL_ID) {
    697        this.tempPanel.remove();
    698      }
    699      this.tempPanel = null;
    700    }
    701    if (this.tempBrowser) {
    702      this.tempBrowser.parentNode.remove();
    703      this.tempBrowser = null;
    704    }
    705  }
    706 
    707  destroy() {
    708    return super.destroy().then(() => {
    709      this.removeTempPanel();
    710    });
    711  }
    712 
    713  get DESTROY_EVENT() {
    714    return "ViewHiding";
    715  }
    716 
    717  closePopup() {
    718    if (this.shown) {
    719      lazy.CustomizableUI.hidePanelForNode(this.viewNode);
    720    } else if (this.attached) {
    721      this.destroyed = true;
    722    } else {
    723      this.destroy();
    724    }
    725  }
    726 }