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;