Preserve newlines in exception messages.

We accomplish this by replacing them with \u200b\n and updating StackTraceCreator to recognize that sequence as part of the message, not the stack trace.

Change-Id: I2ad76c853c2810d377b4244b9999e76cb69b2560
diff --git a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
index 0222af7..658fc10 100644
--- a/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
+++ b/user/src/com/google/gwt/core/client/impl/StackTraceCreator.java
@@ -373,8 +373,21 @@
     return (match && match[1]) || @StackTraceCreator::ANONYMOUS;
   }-*/;
 
+  /*
+   * u200b is a special character inserted by our Throwable.initializeBackingError before any
+   * newlines in the Throwable _message_ so that we can distinguish those newlines from the newlines
+   * that separate lines of the _stack trace_.
+   *
+   * Why replace()+split() instead of a regex with lookbehind? Because lookbehind isn't available
+   * until ECMAScript 2018.
+   *
+   * Why not have Throwable generate \n\u200b instead of \u200b\n so that we can use lookahead?
+   * Because u200b, despite being a "zero-width space," does take up space when it appears at the
+   * _beginning_ of a line, at least in some terminals, including xterm. Ditto for u2060 (word
+   * joiner) and probably other "zero-width" characters.
+   */
   private static native JsArrayString split(Object t) /*-{
     var e = t.@Throwable::backingJsObject;
-    return (e && e.stack) ? e.stack.split('\n') : [];
+    return (e && e.stack) ? e.stack.replace('\u200b\n', ' ').split('\n') : [];
   }-*/;
 }
diff --git a/user/super/com/google/gwt/emul/java/lang/Throwable.java b/user/super/com/google/gwt/emul/java/lang/Throwable.java
index 09558a8..25fbef3 100644
--- a/user/super/com/google/gwt/emul/java/lang/Throwable.java
+++ b/user/super/com/google/gwt/emul/java/lang/Throwable.java
@@ -105,10 +105,13 @@
   }
 
   private void initializeBackingError() {
-    // Replace newlines with spaces so that we don't confuse the parser
-    // below which splits on newlines, and will otherwise try to parse
-    // the error message as part of the stack trace.
-    String message = detailMessage == null ? null : detailMessage.nativeReplaceAll("\n", " ");
+    /*
+     * Prefix newlines in the _message_ with u200b so that StackTraceCreator can distinguish them
+     * from the newlines in the JavaScript Error _stack trace_. (StackTraceCreator's input is a
+     * string that often contains both the message and the stack trace.)
+     */
+    String message =
+        detailMessage == null ? null : detailMessage.nativeReplaceAll("\n", "\u200b\n");
     String errorMessage = toString(message);
 
     setBackingJsObject(fixIE(createError(errorMessage)));
diff --git a/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java b/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java
index 8e72a6f..34b7da0 100644
--- a/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/ThrowableTest.java
@@ -96,6 +96,15 @@
     assertTrue(caughtNative.toString().contains(e.getClass().getName()));
   }
 
+  public void testCatchNativeWithNewlineInMesssage() {
+    if (TestUtils.isJvm()) {
+      return;
+    }
+    Throwable e = new Throwable("my\nmsg");
+    Object caughtNative = catchNative(createThrower(e));
+    assertTrue(caughtNative.toString().contains("my\u200b\nmsg"));
+  }
+
   public void testJavaNativeJavaSandwichCatch() {
     if (TestUtils.isJvm()) {
       return;