      sendAsyncMessage("Extension:BrowserContentLoaded", {
            url: content.location.href,
          });

          if (this.needsResize) {
            this.handleDOMChange(true);
          }
        }
        break;

      case "load":
        if (event.target.contentWindow === content) {
          // For about:addons inline <browser>s, we currently receive a load
          // event on the <browser> element, but no load or DOMContentLoaded
          // events from the content window.

          // Inline browsers don't receive the "DOMWindowCreated" event, so this
          // is a workaround to load the stylesheets.
          if (this.isInline) {
            this.loadStylesheets();
          }
          sendAsyncMessage("Extension:BrowserContentLoaded", {
            url: content.location.href,
          });
        } else if (event.target !== content.document) {
          break;
        }

        if (!this.needsResize) {
          break;
        }

        // We use a capturing listener, so we get this event earlier than any
        // load listeners in the content page. Resizing after a timeout ensures
        // that we calculate the size after the entire event cycle has completed
        // (unless someone spins the event loop, anyway), and hopefully after
        // the content has made any modifications.
        Promise.resolve().then(() => {
          this.handleDOMChange(true);
        });

        // Mutation observer to make sure the panel shrinks when the content does.
        new content.MutationObserver(this.handleDOMChange.bind(this)).observe(
          content.document.documentElement,
          {
            attributes: true,
            characterData: true,
            childList: true,
            subtree: true,
          }
        );
        break;

      case "MozScrolledAreaChanged":
        if (this.needsResize) {
          this.handleDOMChange();
        }
        break;
    }
  },

  // Resizes the browser to match the preferred size of the content (debounced).
  handleDOMChange(ignoreThrottling = false) {
    if (ignoreThrottling && this.resizeTimeout) {
      clearTimeout(this.resizeTimeout);
      this.resizeTimeout = null;
    }

    if (this.resizeTimeout == null) {
      this.resizeTimeout = setTimeout(() => {
        try {
          if (content) {
            this._handleDOMChange("delayed");
          }
        } finally {
          this.resizeTimeout = null;
        }
      }, RESIZE_TIMEOUT);

      this._handleDOMChange();
    }
  },

  _handleDOMChange(detail) {
    let doc = content.document;

    let body = doc.body;
    if (!body || doc.compatMode === "BackCompat") {
      // In quirks mode, the root element is used as the scroll frame, and the
      // body lies about its scroll geometry, and returns the values for the
      // root instead.
      body = doc.documentElement;
    }

    let result;
    const zoom = content.browsingContext.fullZoom;
    if (this.fixedWidth) {
      // If we're in a fixed-width area (namely a slide-in subview of the main
      // menu panel), we need to calculate the view height based on the
      // preferred height of the content document's root scrollable element at the
      // current width, rather than the complete preferred dimensions of the
      // content window.

      // Compensate for any offsets (margin, padding, ...) between the scroll
      // area of the body and the outer height of the document.
      // This calculation is hard to get right for all cases, so take the lower
      // number of the combination of all padding and margins of the document
      // and body elements, or the difference between their heights.
      let getHeight = elem => elem.getBoundingClientRect(elem).height;
      let bodyPadding = getHeight(doc.documentElement) - getHeight(body);

      if (body !== doc.documentElement) {
        let bs = content.getComputedStyle(body);
        let ds = content.getComputedStyle(doc.documentElement);

        let p =
          parseFloat(bs.marginTop) +
          parseFloat(bs.marginBottom) +
          parseFloat(ds.marginTop) +
          parseFloat(ds.marginBottom) +
          parseFloat(ds.paddingTop) +
          parseFloat(ds.paddingBottom);
        bodyPadding = Math.min(p, bodyPadding);
      }

      let height = Math.ceil((body.scrollHeight + bodyPadding) * zoom);

      result = { height, detail };
    } else {
      let background = content.windowUtils.canvasBackgroundColor;
      if (background !== this.oldBackground) {
        sendAsyncMessage("Extension:BrowserBackgroundChanged", { background });
      }
      this.oldBackground = background;

      // Adjust the size of the browser based on its content's preferred size.
      let w = {},
        h = {};
      docShell.docViewer.getContentSize(
        this.maxWidth,
        this.maxHeight,
        /* prefWidth = */ 0,
        w,
        h
      );

      let width = Math.ceil(w.value * zoom);
      let height = Math.ceil(h.value * zoom);
      result = { width, height, detail };
    }

    sendAsyncMessage("Extension:BrowserResized", result);
  },
};

addMessageListener("Extension:InitBrowser", BrowserListener);
addMessageListener("Extension:UnblockParser", BrowserListener);
addMessageListener("Extension:GrabFocus", BrowserListener);

// This is a temporary hack to prevent regressions (bug 1471327).
void content;
PK