blob: 8f605ca73e3c54e6519cf2f623e68ea1cdd28f6f [file] [log] [blame]
/*
* 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 {
public static boolean moduleUnloaded = false;
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;
private static int sNextHashId = 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;
private static UnloadSupport unloadSupport = GWT.isScript() ?
(UnloadSupport) GWT.create(UnloadSupport.class) : new UnloadSupport();
static {
exportUnloadModule();
}
public static void clearInterval(int timerId) {
unloadSupport.clearInterval(timerId);
}
public static void clearTimeout(int timerId) {
unloadSupport.clearTimeout(timerId);
}
public static void dispose(Disposable d) {
unloadSupport.dispose(d);
}
/**
* 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() {
try {
if (@com.google.gwt.core.client.GWT::isScript()()) {
return @com.google.gwt.core.client.impl.Impl::entry0(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)(jsFunction, this, arguments);
} else {
var _ = @com.google.gwt.core.client.impl.Impl::entry0(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)(jsFunction, this, arguments);
if (_ != null) {
// Unwraps for Development Mode (see #apply())
_ = _.val;
}
return _;
}
} catch (e) {
// This catch block is here to ensure that the finally block in entry0
// will be executed correctly on IE6/7. We can't put a catch Throwable
// in entry0 because this would always cause the unhandled exception to
// be wrapped in a JavaScriptException type.
throw e;
}
};
}-*/;
public static void exportUnloadModule() {
unloadSupport.exportUnloadModule();
}
/**
* Gets an identity-based hash code on the passed-in Object by adding an
* expando. This method should not be used with <code>null</code> or any
* String. The former will crash and the later will produce unstable results
* when called repeatedly with a String primitive.
* <p>
* The sequence of hashcodes generated by this method are a
* monotonically-increasing sequence.
*/
public static native int getHashCode(Object o) /*-{
return o.$H || (o.$H = @com.google.gwt.core.client.impl.Impl::getNextHashId()());
}-*/;
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.
*
* @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.
* @see com.google.gwt.core.client.impl.ArtificialRescue
*/
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 and let the browser handle it
reportToBrowser(e);
}
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;
}
public static boolean isModuleUnloaded() {
return moduleUnloaded;
}
/**
* 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 @com.google.gwt.core.client.impl.Impl::entry(Lcom/google/gwt/core/client/JavaScriptObject;);
} else {
// But we have to do in in Development Mode
return $entry = @com.google.gwt.core.client.impl.Impl::entry(Lcom/google/gwt/core/client/JavaScriptObject;);
}
}-*/;
public static void scheduleDispose(Disposable d) {
unloadSupport.scheduleDispose(d);
}
public static int setInterval(JavaScriptObject func, int time) {
return unloadSupport.setInterval(func, time);
}
public static int setTimeout(JavaScriptObject func, int time) {
return unloadSupport.setTimeout(func, time);
}
public static void unloadModule() {
if (unloadSupport.isUnloadSupported()) {
moduleUnloaded = true;
unloadSupport.disposeAll();
}
}
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 {
// if module is unloaded, don't run anything
if (unloadSupport.isUnloadSupported() && Impl.isModuleUnloaded()) {
return null;
}
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;
}
}
}
/**
* Called from JSNI. Do not change this implementation without updating:
* <ul>
* <li>{@link com.google.gwt.user.client.rpc.impl.SerializerBase}</li>
* </ul>
*/
private static int getNextHashId() {
return ++sNextHashId;
}
private static native Object undefined() /*-{
// Intentionally not returning a value
return;
}-*/;
private static native void watchdogEntryDepthCancel(int timerId) /*-{
@com.google.gwt.core.client.impl.Impl::clearTimeout(I)(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 @com.google.gwt.core.client.impl.Impl::setTimeout(Lcom/google/gwt/core/client/JavaScriptObject;I)(function() {
@com.google.gwt.core.client.impl.Impl::watchdogEntryDepthRun()();
}, 10);
}-*/;
}