From e872907b50cfd9b76a47a7ea0e9116fccc4767e6 Mon Sep 17 00:00:00 2001
From: Dan Abramov <dan.abramov@gmail.com>
Date: Sun, 25 Sep 2016 12:33:43 +0100
Subject: [PATCH] Fix error overlay in Firefox (#749)

---
 .../react-dev-utils/webpackHotDevClient.js    | 135 ++++++++++++------
 1 file changed, 95 insertions(+), 40 deletions(-)

diff --git a/packages/react-dev-utils/webpackHotDevClient.js b/packages/react-dev-utils/webpackHotDevClient.js
index d0aa54adb..120b8680a 100644
--- a/packages/react-dev-utils/webpackHotDevClient.js
+++ b/packages/react-dev-utils/webpackHotDevClient.js
@@ -39,13 +39,10 @@ var colors = {
 };
 ansiHTML.setColors(colors);
 
-function showErrorOverlay(message) {
-  // Use an iframe so that document styles don't mess up the overlay.
-  var iframeID = 'react-dev-utils-webpack-hot-dev-client-overlay';
-  var iframe =
-    document.getElementById(iframeID) ||
-    document.createElement('iframe');
-  iframe.id = iframeID;
+function createOverlayIframe(onIframeLoad) {
+  var iframe = document.createElement('iframe');
+  iframe.id = 'react-dev-utils-webpack-hot-dev-client-overlay';
+  iframe.src = 'about:blank';
   iframe.style.position = 'fixed';
   iframe.style.left = 0;
   iframe.style.top = 0;
@@ -55,39 +52,74 @@ function showErrorOverlay(message) {
   iframe.style.height = '100vh';
   iframe.style.border = 'none';
   iframe.style.zIndex = 9999999999;
-  document.body.appendChild(iframe);
-
-  // Inside, make a div.
-  var overlayID = 'react-dev-utils-webpack-hot-dev-client-overlay-div';
-  var overlay =
-    iframe.contentDocument.getElementById(overlayID) ||
-    iframe.contentDocument.createElement('div');
-  overlay.id = overlayID;
-  overlay.style.position = 'fixed';
-  overlay.style.left = 0;
-  overlay.style.top = 0;
-  overlay.style.right = 0;
-  overlay.style.bottom = 0;
-  overlay.style.width = '100vw';
-  overlay.style.height = '100vh';
-  overlay.style.backgroundColor = 'black';
-  overlay.style.color = '#E8E8E8';
-  overlay.style.fontFamily = 'Menlo, Consolas, monospace';
-  overlay.style.fontSize = 'large';
-  overlay.style.padding = '2rem';
-  overlay.style.lineHeight = '1.2';
-  overlay.style.whiteSpace = 'pre-wrap';
-  overlay.style.overflow = 'auto';
-
-  // Make it look similar to our terminal.
-  overlay.innerHTML =
-    '<span style="color: #' +
-    colors.red +
-    '">Failed to compile.</span><br><br>' +
-    ansiHTML(entities.encode(message));
-
-  // Render!
-  iframe.contentDocument.body.appendChild(overlay);
+  iframe.onload = onIframeLoad;
+  return iframe;
+}
+
+function addOverlayDivTo(iframe) {
+  var div =  iframe.contentDocument.createElement('div');
+  div.id = 'react-dev-utils-webpack-hot-dev-client-overlay-div';
+  div.style.position = 'fixed';
+  div.style.left = 0;
+  div.style.top = 0;
+  div.style.right = 0;
+  div.style.bottom = 0;
+  div.style.width = '100vw';
+  div.style.height = '100vh';
+  div.style.backgroundColor = 'black';
+  div.style.color = '#E8E8E8';
+  div.style.fontFamily = 'Menlo, Consolas, monospace';
+  div.style.fontSize = 'large';
+  div.style.padding = '2rem';
+  div.style.lineHeight = '1.2';
+  div.style.whiteSpace = 'pre-wrap';
+  div.style.overflow = 'auto';
+  iframe.contentDocument.body.appendChild(div);
+  return div;
+}
+
+var overlayIframe = null;
+var overlayDiv = null;
+var lastOnOverlayDivReady = null;
+
+function ensureOverlayDivExists(onOverlayDivReady) {
+  if (overlayDiv) {
+    // Everything is ready, call the callback right away.
+    onOverlayDivReady(overlayDiv);
+    return;
+  }
+
+  // Creating an iframe may be asynchronous so we'll schedule the callback.
+  // In case of multiple calls, last callback wins.
+  lastOnOverlayDivReady = onOverlayDivReady;
+
+  if (overlayIframe) {
+    // We're already creating it.
+    return;
+  }
+
+  // Create iframe and, when it is ready, a div inside it.
+  overlayIframe = createOverlayIframe(function onIframeLoad() {
+    overlayDiv = addOverlayDivTo(overlayIframe);
+    // Now we can talk!
+    lastOnOverlayDivReady(overlayDiv);
+  });
+
+  // Zalgo alert: onIframeLoad() will be called either synchronouly
+  // or asynchronously depending on the browser.
+  // We delay adding it so `overlayIframe` is set when `onIframeLoad` fires.
+  document.body.appendChild(overlayIframe);
+}
+
+function showErrorOverlay(message) {
+  ensureOverlayDivExists(function onOverlayDivReady(overlayDiv) {
+    // Make it look similar to our terminal.
+    overlayDiv.innerHTML =
+      '<span style="color: #' +
+      colors.red +
+      '">Failed to compile.</span><br><br>' +
+      ansiHTML(entities.encode(message));
+  });
 }
 
 // Connect to WebpackDevServer via a socket.
@@ -105,11 +137,22 @@ var connection = new SockJS(url.format({
 // Remember some state related to hot module replacement.
 var isFirstCompilation = true;
 var mostRecentCompilationHash = null;
+var hasCompileErrors = false;
+
+function clearOutdatedErrors() {
+  // Clean up outdated compile errors, if any.
+  if (hasCompileErrors && typeof console.clear === 'function') {
+    console.clear();
+  }
+}
 
 // Successful compilation.
 function handleSuccess() {
+  clearOutdatedErrors();
+
   var isHotUpdate = !isFirstCompilation;
   isFirstCompilation = false;
+  hasCompileErrors = false;
 
   // Attempt to apply hot updates or reload.
   if (isHotUpdate) {
@@ -119,8 +162,11 @@ function handleSuccess() {
 
 // Compilation with warnings (e.g. ESLint).
 function handleWarnings(warnings) {
+  clearOutdatedErrors();
+
   var isHotUpdate = !isFirstCompilation;
   isFirstCompilation = false;
+  hasCompileErrors = false;
 
   function printWarnings() {
     // Print warnings to the console.
@@ -144,7 +190,10 @@ function handleWarnings(warnings) {
 
 // Compilation with errors (e.g. syntax error or missing modules).
 function handleErrors(errors) {
+  clearOutdatedErrors();
+
   isFirstCompilation = false;
+  hasCompileErrors = true;
 
   // "Massage" webpack messages.
   var formatted = formatWebpackMessages({
@@ -154,6 +203,12 @@ function handleErrors(errors) {
 
   // Only show the first error.
   showErrorOverlay(formatted.errors[0]);
+
+  // Also log them to the console.
+  for (var i = 0; i < formatted.errors.length; i++) {
+    console.error(stripAnsi(formatted.errors[i]));
+  }
+
   // Do not attempt to reload now.
   // We will reload on next success instead.
 }
-- 
GitLab