-
Notifications
You must be signed in to change notification settings - Fork 371
/
Impl.java
351 lines (310 loc) · 11.4 KB
/
Impl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.core.client.impl;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
/**
* Private implementation class for GWT core. This API is should not be
* considered public or stable.
*/
public final class Impl {
static {
if (GWT.isClient() && StackTraceCreator.collector != null) {
// Just enforces loading of StackTraceCreator early on, nothing else to do here...
}
}
private static final int WATCHDOG_ENTRY_DEPTH_CHECK_INTERVAL_MS = 2000;
/**
* Used by {@link #entry0(Object, Object)} to handle reentrancy.
*/
private static int entryDepth = 0;
/**
* TimeStamp indicating last scheduling of the entry depth watchdog.
*/
private static double watchdogEntryDepthLastScheduled;
/**
* Timer id of the entry depth watchdog. -1 if not scheduled.
*/
private static int watchdogEntryDepthTimerId = -1;
/**
* This method should be used whenever GWT code is entered from a JS context
* and there is no GWT code in the same module on the call stack. Examples
* include event handlers, exported methods, and module initialization.
* <p>
* The GWT compiler and Development Mode will provide a module-scoped
* variable, <code>$entry</code>, which is an alias for this method.
* <p>
* This method can be called reentrantly, which will simply delegate to the
* function.
* <p>
* The function passed to this method will be invoked via
* <code>Function.apply()</code> with the current <code>this</code> value and
* the invocation arguments passed to <code>$entry</code>.
*
* @param jsFunction a JS function to invoke, which is typically a JSNI
* reference to a static Java method
* @return the value returned when <code>jsFunction</code> is invoked, or
* <code>undefined</code> if the UncaughtExceptionHandler catches an
* exception raised by <code>jsFunction</code>
*/
public static native JavaScriptObject entry(JavaScriptObject jsFunction) /*-{
return function() {
if (@com.google.gwt.core.client.GWT::isScript()()) {
return @Impl::entry0(*)(jsFunction, this, arguments);
} else {
var _ = @Impl::entry0(*)(jsFunction, this, arguments);
if (_ != null) {
// Unwraps for Development Mode (see #apply())
_ = _.val;
}
return _;
}
};
}-*/;
public static native String getHostPageBaseURL() /*-{
var s = $doc.location.href;
// Pull off any hash.
var i = s.indexOf('#');
if (i != -1)
s = s.substring(0, i);
// Pull off any query string.
i = s.indexOf('?');
if (i != -1)
s = s.substring(0, i);
// Rip off everything after the last slash.
i = s.lastIndexOf('/');
if (i != -1)
s = s.substring(0, i);
// Ensure a final slash if non-empty.
return s.length > 0 ? s + "/" : "";
}-*/;
public static native String getModuleBaseURL() /*-{
// Check to see if DevModeRedirectHook has set an alternate value.
// The key should match DevModeRedirectHook.js.
var key = "__gwtDevModeHook:" + $moduleName + ":moduleBase";
var global = $wnd || self;
return global[key] || $moduleBase;
}-*/;
public static native String getModuleBaseURLForStaticFiles() /*-{
return $moduleBase;
}-*/;
public static native String getModuleName() /*-{
return $moduleName;
}-*/;
/**
* Returns the obfuscated name of members in the compiled output. This is a thin wrapper around
* JNameOf AST nodes and is therefore meaningless to implement in Development Mode.
* If the requested member is a method, the method will not be devirtualized, inlined or prunned.
*
* @param jsniIdent a string literal specifying a type, field, or method. Raw
* type names may also be used to obtain the name of the type's seed
* function.
* @return the name by which the named member can be accessed at runtime, or
* <code>null</code> if the requested member has been pruned from the
* output.
*/
public static String getNameOf(String jsniIdent) {
/*
* In Production Mode, the compiler directly replaces calls to this method
* with a string literal expression.
*/
assert !GWT.isScript() : "ReplaceRebinds failed to replace this method";
throw new UnsupportedOperationException(
"Impl.getNameOf() is unimplemented in Development Mode");
}
public static native String getPermutationStrongName() /*-{
return $strongName;
}-*/;
/**
* UncaughtExceptionHandler that is used by unit tests to spy on uncaught
* exceptions.
*/
private static UncaughtExceptionHandler uncaughtExceptionHandlerForTest;
/**
* Set an uncaught exception handler to spy on uncaught exceptions in unit
* tests.
* <p>
* Setting this method will not interfere with any exception handling logic;
* i.e. {@link GWT#getUncaughtExceptionHandler()} will still return null if a
* handler is not set via {@link GWT#setUncaughtExceptionHandler}.
*/
public static void setUncaughtExceptionHandlerForTest(
UncaughtExceptionHandler handler) {
uncaughtExceptionHandlerForTest = handler;
}
public static void reportUncaughtException(Throwable e) {
if (Impl.uncaughtExceptionHandlerForTest != null) {
Impl.uncaughtExceptionHandlerForTest.onUncaughtException(e);
}
UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
if (handler != null) {
if (handler == Impl.uncaughtExceptionHandlerForTest) {
return; // Already reported so we're done.
}
// TODO(goktug): Handler might throw an exception but catching and reporting it to browser
// here breaks assumptions of some existing hybrid apps that uses UCE for exception
// conversion. We don't have an alternative functionality (yet) and it is too risky to include
// the change in the release at last minute.
handler.onUncaughtException(e);
return; // Done.
}
// Make sure that the exception is not swallowed
if (GWT.isClient()) {
reportToBrowser(e);
} else {
System.err.print("Uncaught exception ");
e.printStackTrace(System.err);
}
}
private static void reportToBrowser(Throwable e) {
reportToBrowser(e instanceof JavaScriptException ? ((JavaScriptException) e).getThrown() : e);
}
private static native void reportToBrowser(Object e) /*-{
$wnd.setTimeout(function () {
throw e;
}, 0);
}-*/;
/**
* Indicates if <code>$entry</code> has been called.
*/
public static boolean isEntryOnStack() {
return entryDepth > 0;
}
/**
* Indicates if <code>$entry</code> is present on the stack more than once.
*/
public static boolean isNestedEntry() {
return entryDepth > 1;
}
/**
* Implicitly called by JavaToJavaScriptCompiler.findEntryPoints().
*/
public static native JavaScriptObject registerEntry() /*-{
if (@com.google.gwt.core.client.GWT::isScript()()) {
// Assignment to $entry is done by the compiler
return @Impl::entry(*);
} else {
// But we have to do in in Development Mode
return $entry = @Impl::entry(*);
}
}-*/;
private static native Object apply(Object jsFunction, Object thisObj,
Object args) /*-{
if (@com.google.gwt.core.client.GWT::isScript()()) {
return jsFunction.apply(thisObj, args);
} else {
var _ = jsFunction.apply(thisObj, args);
if (_ != null) {
// Wrap for Development Mode (unwrapped in #entry())
_ = { val: _ };
}
return _;
}
}-*/;
/**
* Called by ModuleSpace in Development Mode when running onModuleLoads.
*/
private static boolean enter() {
assert entryDepth >= 0 : "Negative entryDepth value at entry " + entryDepth;
if (GWT.isScript() && entryDepth != 0) {
double now = Duration.currentTimeMillis();
if (now - watchdogEntryDepthLastScheduled > WATCHDOG_ENTRY_DEPTH_CHECK_INTERVAL_MS) {
watchdogEntryDepthLastScheduled = now;
watchdogEntryDepthTimerId = watchdogEntryDepthSchedule();
}
}
// We want to disable some actions in the reentrant case
if (entryDepth++ == 0) {
SchedulerImpl.INSTANCE.flushEntryCommands();
return true;
}
return false;
}
/**
* Implements {@link #entry(JavaScriptObject)}.
*/
private static Object entry0(Object jsFunction, Object thisObj, Object args) throws Throwable {
boolean initialEntry = enter();
try {
/*
* Always invoke the UCE if we have one so that the exception never
* percolates up to the browser's event loop, even in a reentrant
* situation.
*/
if (GWT.getUncaughtExceptionHandler() != null) {
/*
* This try block is guarded by the if statement so that we don't molest
* the exception object traveling up the stack unless we're capable of
* doing something useful with it.
*/
try {
return apply(jsFunction, thisObj, args);
} catch (Throwable t) {
reportUncaughtException(t);
return undefined();
}
} else {
// Can't handle any exceptions, let them percolate normally
return apply(jsFunction, thisObj, args);
}
/*
* DO NOT ADD catch(Throwable t) here, it would always wrap the thrown
* value. Instead, entry() has a general catch-all block.
*/
} finally {
exit(initialEntry);
}
}
/**
* Called by ModuleSpace in Development Mode when running onModuleLoads.
*/
private static void exit(boolean initialEntry) {
if (initialEntry) {
SchedulerImpl.INSTANCE.flushFinallyCommands();
}
// Decrement after we call flush
entryDepth--;
assert entryDepth >= 0 : "Negative entryDepth value at exit " + entryDepth;
if (initialEntry) {
assert entryDepth == 0 : "Depth not 0" + entryDepth;
if (GWT.isScript() && watchdogEntryDepthTimerId != -1) {
watchdogEntryDepthCancel(watchdogEntryDepthTimerId);
watchdogEntryDepthTimerId = -1;
}
}
}
private static native Object undefined() /*-{
// Intentionally not returning a value
return;
}-*/;
private static native void watchdogEntryDepthCancel(int timerId) /*-{
$wnd.clearTimeout(timerId);
}-*/;
private static void watchdogEntryDepthRun() {
// Note: this must NEVER be called nested in a $entry() call.
// This method is call from a "setTimeout": entryDepth should be set to 0.
if (GWT.isScript() && entryDepth != 0) {
entryDepth = 0;
}
watchdogEntryDepthTimerId = -1; // Timer has run.
}
private static native int watchdogEntryDepthSchedule() /*-{
return $wnd.setTimeout(@Impl::watchdogEntryDepthRun(), 10);
}-*/;
}