579 lines
17 KiB
JavaScript
579 lines
17 KiB
JavaScript
|
// Copyright 2006 Google Inc. All Rights Reserved.
|
||
|
// This startup script should be included in host pages either just after
|
||
|
// <body> or inside the <head> after module <meta> tags.
|
||
|
//
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// DynamicResources
|
||
|
//
|
||
|
|
||
|
function DynamicResources() {
|
||
|
this.pendingElemsBySrc_ = {};
|
||
|
this.pendingScriptElems_ = new Array();
|
||
|
}
|
||
|
DynamicResources.prototype = {};
|
||
|
|
||
|
// The array is set up such that, pairwise, the entries are (src, readyFnStr).
|
||
|
// Called once for each module that is attached to the host page.
|
||
|
// It is theoretically possible that addScripts() could be called reentrantly
|
||
|
// if the browser event loop is pumped during this function and an iframe loads;
|
||
|
// we may want to enhance this method in the future to support that case.
|
||
|
DynamicResources.prototype.addScripts = function(scriptArray, insertBeforeElem) {
|
||
|
var wasEmpty = (this.pendingScriptElems_.length == 0);
|
||
|
var anyAdded = false;
|
||
|
for (var i = 0, n = scriptArray.length; i < n; i += 2) {
|
||
|
var src = scriptArray[i];
|
||
|
if (this.pendingElemsBySrc_[src]) {
|
||
|
// Don't load the same script twice.
|
||
|
continue;
|
||
|
}
|
||
|
// Set up the element but don't add it to the DOM until its turn.
|
||
|
anyAdded = true;
|
||
|
var e = document.createElement("script");
|
||
|
this.pendingElemsBySrc_[src] = e;
|
||
|
var readyFn;
|
||
|
eval("readyFn = " + scriptArray[i+1]);
|
||
|
e.__readyFn = readyFn;
|
||
|
e.type = "text/javascript";
|
||
|
e.src = src;
|
||
|
e.__insertBeforeElem = insertBeforeElem;
|
||
|
this.pendingScriptElems_ = this.pendingScriptElems_.concat(e);
|
||
|
}
|
||
|
|
||
|
if (wasEmpty && anyAdded) {
|
||
|
// Kickstart.
|
||
|
this.injectScript(this.pendingScriptElems_[0]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DynamicResources.prototype.injectScript = function(scriptElem) {
|
||
|
var parentElem = scriptElem.__insertBeforeElem.parentNode;
|
||
|
parentElem.insertBefore(scriptElem, scriptElem.__insertBeforeElem);
|
||
|
}
|
||
|
|
||
|
DynamicResources.prototype.addStyles = function(styleSrcArray, insertBeforeElem) {
|
||
|
var parent = insertBeforeElem.parentNode;
|
||
|
for (var i = 0, n = styleSrcArray.length; i < n; ++i) {
|
||
|
var src = styleSrcArray[i];
|
||
|
if (this.pendingElemsBySrc_[src])
|
||
|
continue;
|
||
|
var e = document.createElement("link");
|
||
|
this.pendingElemsBySrc_[src] = e;
|
||
|
e.type = "text/css";
|
||
|
e.rel = "stylesheet";
|
||
|
e.href = src;
|
||
|
parent.insertBefore(e, insertBeforeElem);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DynamicResources.prototype.isReady = function() {
|
||
|
var elems = this.pendingScriptElems_;
|
||
|
if (elems.length > 0) {
|
||
|
var e = elems[0];
|
||
|
if (!e.__readyFn()) {
|
||
|
// The pending script isn't ready yet.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// The pending script has now finished loading. Enqueue the next, if any.
|
||
|
e.__readyFn = null;
|
||
|
elems.shift();
|
||
|
if (elems.length > 0) {
|
||
|
// There is another script.
|
||
|
this.injectScript(elems[0]);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// There are no more pending scripts.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// ModuleControlBlock
|
||
|
//
|
||
|
function ModuleControlBlock(metaElem, rawName) {
|
||
|
var parts = ["", rawName];
|
||
|
var i = rawName.lastIndexOf("=");
|
||
|
if (i != -1) {
|
||
|
parts[0] = rawName.substring(0, i) + '/';
|
||
|
parts[1] = rawName.substring(i+1);
|
||
|
}
|
||
|
|
||
|
this.metaElem_ = metaElem;
|
||
|
this.baseUrl_ = parts[0];
|
||
|
this.name_ = parts[1];
|
||
|
this.compilationLoaded_ = false;
|
||
|
this.frameWnd_ = null;
|
||
|
}
|
||
|
ModuleControlBlock.prototype = {};
|
||
|
|
||
|
/**
|
||
|
* Determines whether this module is fully loaded and ready to run.
|
||
|
*/
|
||
|
ModuleControlBlock.prototype.isReady = function() {
|
||
|
return this.compilationLoaded_;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Called when the compilation for this module is loaded.
|
||
|
*/
|
||
|
ModuleControlBlock.prototype.compilationLoaded = function(frameWnd) {
|
||
|
this.frameWnd_ = frameWnd;
|
||
|
this.compilationLoaded_ = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the logical module name, not including a base url prefix if one was
|
||
|
* specified.
|
||
|
*/
|
||
|
ModuleControlBlock.prototype.getName = function() {
|
||
|
return this.name_;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the base URL of the module, guaranteed to end with a slash.
|
||
|
*/
|
||
|
ModuleControlBlock.prototype.getBaseURL = function() {
|
||
|
return this.baseUrl_;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the window of the module's frame.
|
||
|
*/
|
||
|
ModuleControlBlock.prototype.getModuleFrameWindow = function() {
|
||
|
return this.frameWnd_;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Injects a set of dynamic scripts.
|
||
|
* The array is set up such that, pairwise, the entries are (src, readyFnStr).
|
||
|
*/
|
||
|
ModuleControlBlock.prototype.addScripts = function(scriptSrcArray) {
|
||
|
return ModuleControlBlocks.dynamicResources_.addScripts(scriptSrcArray, this.metaElem_);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Injects a set of dynamic styles.
|
||
|
*/
|
||
|
ModuleControlBlock.prototype.addStyles = function(styleSrcArray) {
|
||
|
return ModuleControlBlocks.dynamicResources_.addStyles(styleSrcArray, this.metaElem_);
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// ModuleControlBlocks
|
||
|
//
|
||
|
function ModuleControlBlocks() {
|
||
|
this.blocks_ = [];
|
||
|
}
|
||
|
ModuleControlBlocks.dynamicResources_ = new DynamicResources(); // "static"
|
||
|
ModuleControlBlocks.prototype = {};
|
||
|
|
||
|
/**
|
||
|
* Adds a module control control block for the named module.
|
||
|
* @param metaElem the meta element that caused the module to be added
|
||
|
* @param name the name of the module being added, optionally preceded by
|
||
|
* an alternate base url of the form "_path_=_module_".
|
||
|
*/
|
||
|
ModuleControlBlocks.prototype.add = function(metaElem, name) {
|
||
|
var mcb = new ModuleControlBlock(metaElem, name);
|
||
|
this.blocks_ = this.blocks_.concat(mcb);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Determines whether all the modules are loaded and ready to run.
|
||
|
*/
|
||
|
ModuleControlBlocks.prototype.isReady = function() {
|
||
|
for (var i = 0, n = this.blocks_.length; i < n; ++i) {
|
||
|
var mcb = this.blocks_[i];
|
||
|
if (!mcb.isReady()) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Are there any pending dynamic resources (e.g. styles, scripts)?
|
||
|
if (!ModuleControlBlocks.dynamicResources_.isReady()) {
|
||
|
// No, we're still waiting on one or more dynamic resources.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines whether there are any module control blocks.
|
||
|
*/
|
||
|
ModuleControlBlocks.prototype.isEmpty = function() {
|
||
|
return this.blocks_.length == 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the module control block at the specified index.
|
||
|
*/
|
||
|
ModuleControlBlocks.prototype.get = function(index) {
|
||
|
return this.blocks_[index];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Injects an iframe for each module.
|
||
|
*/
|
||
|
ModuleControlBlocks.prototype.injectFrames = function() {
|
||
|
for (var i = 0, n = this.blocks_.length; i < n; ++i) {
|
||
|
var mcb = this.blocks_[i];
|
||
|
|
||
|
// Insert an iframe for the module
|
||
|
var iframe = document.createElement("iframe");
|
||
|
var selectorUrl = mcb.getBaseURL() + mcb.getName() + ".nocache.html";
|
||
|
selectorUrl += "?" + (__gwt_isHosted() ? "h&" : "" ) + i;
|
||
|
var unique = new Date().getTime();
|
||
|
selectorUrl += "&" + unique;
|
||
|
iframe.style.border = '0px';
|
||
|
iframe.style.width = '0px';
|
||
|
iframe.style.height = '0px';
|
||
|
|
||
|
// Fragile browser-specific ordering issues below
|
||
|
|
||
|
/*@cc_on
|
||
|
// prevent extra clicky noises on IE
|
||
|
iframe.src = selectorUrl;
|
||
|
@*/
|
||
|
|
||
|
if (document.body.firstChild) {
|
||
|
document.body.insertBefore(iframe, document.body.firstChild);
|
||
|
} else {
|
||
|
document.body.appendChild(iframe);
|
||
|
}
|
||
|
|
||
|
/*@cc_on
|
||
|
// prevent extra clicky noises on IE
|
||
|
return;
|
||
|
@*/
|
||
|
|
||
|
if (iframe.contentWindow) {
|
||
|
// Older Mozilla has a caching bug for the iframe and won't reload the nocache.
|
||
|
iframe.contentWindow.location.replace(selectorUrl);
|
||
|
} else {
|
||
|
// Older Safari doesn't have a contentWindow.
|
||
|
iframe.src = selectorUrl;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Runs the entry point for each module.
|
||
|
*/
|
||
|
ModuleControlBlocks.prototype.run = function() {
|
||
|
for (var i = 0, n = this.blocks_.length; i < n; ++i) {
|
||
|
var mcb = this.blocks_[i];
|
||
|
var name = mcb.getName();
|
||
|
var frameWnd = mcb.getModuleFrameWindow();
|
||
|
if (__gwt_isHosted()) {
|
||
|
if (!window.external.gwtOnLoad(frameWnd, name)) {
|
||
|
// Module failed to load.
|
||
|
if (__gwt_onLoadError) {
|
||
|
__gwt_onLoadError(name);
|
||
|
} else {
|
||
|
window.alert("Failed to load module '" + name +
|
||
|
"'.\nPlease see the log in the development shell for details.");
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// The compilation itself handles calling the error function.
|
||
|
frameWnd.gwtOnLoad(__gwt_onLoadError, name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Globals
|
||
|
//
|
||
|
|
||
|
var __gwt_retryWaitMillis = 10;
|
||
|
var __gwt_isHostPageLoaded = false;
|
||
|
var __gwt_metaProps = {};
|
||
|
var __gwt_onPropertyError = null;
|
||
|
var __gwt_onLoadError = null;
|
||
|
var __gwt_moduleControlBlocks = new ModuleControlBlocks();
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Common
|
||
|
//
|
||
|
|
||
|
/**
|
||
|
* Determines whether or not the page is being loaded in the GWT hosted browser.
|
||
|
*/
|
||
|
function __gwt_isHosted() {
|
||
|
if (window.external && window.external.gwtOnLoad) {
|
||
|
// gwt.hybrid makes the hosted browser pretend not to be
|
||
|
if (document.location.href.indexOf("gwt.hybrid") == -1) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Tries to get a module control block based on a query string passed in from
|
||
|
* the caller. Used by iframes to get references back to their mcbs.
|
||
|
* @param queryString the entire query string as returned by location.search,
|
||
|
* which notably includes the leading '?' if one is specified
|
||
|
* @return the relevant module control block, or <code>null</code> if it cannot
|
||
|
* be derived based on <code>queryString</code>
|
||
|
*/
|
||
|
function __gwt_tryGetModuleControlBlock(queryString) {
|
||
|
if (queryString.length > 0) {
|
||
|
// The pattern is ?[h&]<index>[&<unique>]
|
||
|
var queryString = queryString.substring(1);
|
||
|
if (queryString.indexOf("h&") == 0) {
|
||
|
// Ignore the hosted mode flag here; only GWTShellServlet cares about it.
|
||
|
queryString = queryString.substring(2);
|
||
|
}
|
||
|
var pos = queryString.indexOf("&");
|
||
|
if (pos >= 0) {
|
||
|
queryString = queryString.substring(0, pos);
|
||
|
}
|
||
|
var mcbIndex = parseInt(queryString);
|
||
|
if (!isNaN(mcbIndex)) {
|
||
|
var mcb = __gwt_moduleControlBlocks.get(mcbIndex);
|
||
|
return mcb;
|
||
|
}
|
||
|
// Ignore the unique number that remains on the query string.
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parses meta tags from the host html.
|
||
|
*
|
||
|
* <meta name="gwt:module" content="_module-name_">
|
||
|
* causes the specified module to be loaded
|
||
|
*
|
||
|
* <meta name="gwt:property" content="_name_=_value_">
|
||
|
* statically defines a deferred binding client property
|
||
|
*
|
||
|
* <meta name="gwt:onPropertyErrorFn" content="_fnName_">
|
||
|
* specifies the name of a function to call if a client property is set to
|
||
|
* an invalid value (meaning that no matching compilation will be found)
|
||
|
*
|
||
|
* <meta name="gwt:onLoadErrorFn" content="_fnName_">
|
||
|
* specifies the name of a function to call if an exception happens during
|
||
|
* bootstrapping or if a module throws an exception out of onModuleLoad();
|
||
|
* the function should take a message parameter
|
||
|
*/
|
||
|
function __gwt_processMetas() {
|
||
|
var metas = document.getElementsByTagName("meta");
|
||
|
for (var i = 0, n = metas.length; i < n; ++i) {
|
||
|
var meta = metas[i];
|
||
|
var name = meta.getAttribute("name");
|
||
|
if (name) {
|
||
|
if (name == "gwt:module") {
|
||
|
var moduleName = meta.getAttribute("content");
|
||
|
if (moduleName) {
|
||
|
__gwt_moduleControlBlocks.add(meta, moduleName);
|
||
|
}
|
||
|
} else if (name == "gwt:property") {
|
||
|
var content = meta.getAttribute("content");
|
||
|
if (content) {
|
||
|
var name = content, value = "";
|
||
|
var eq = content.indexOf("=");
|
||
|
if (eq != -1) {
|
||
|
name = content.substring(0, eq);
|
||
|
value = content.substring(eq+1);
|
||
|
}
|
||
|
__gwt_metaProps[name] = value;
|
||
|
}
|
||
|
} else if (name == "gwt:onPropertyErrorFn") {
|
||
|
var content = meta.getAttribute("content");
|
||
|
if (content) {
|
||
|
try {
|
||
|
__gwt_onPropertyError = eval(content);
|
||
|
} catch (e) {
|
||
|
window.alert("Bad handler \"" + content +
|
||
|
"\" for \"gwt:onPropertyErrorFn\"");
|
||
|
}
|
||
|
}
|
||
|
} else if (name == "gwt:onLoadErrorFn") {
|
||
|
var content = meta.getAttribute("content");
|
||
|
if (content) {
|
||
|
try {
|
||
|
__gwt_onLoadError = eval(content);
|
||
|
} catch (e) {
|
||
|
window.alert("Bad handler \"" + content +
|
||
|
"\" for \"gwt:onLoadErrorFn\"");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines the value of a deferred binding client property specified
|
||
|
* statically in host html.
|
||
|
*/
|
||
|
function __gwt_getMetaProperty(name) {
|
||
|
var value = __gwt_metaProps[name];
|
||
|
if (value) {
|
||
|
return value;
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines whether or not a particular property value is allowed.
|
||
|
* @param wnd the caller's window object (not $wnd!)
|
||
|
* @param propName the name of the property being checked
|
||
|
* @param propValue the property value being tested
|
||
|
*/
|
||
|
function __gwt_isKnownPropertyValue(wnd, propName, propValue) {
|
||
|
return propValue in wnd["values$" + propName];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called by the selection script when a property has a bad value or is missing.
|
||
|
* 'allowedValues' is an array of strings. Can be hooked in the host page using
|
||
|
* gwt:onPropertyErrorFn.
|
||
|
*/
|
||
|
function __gwt_onBadProperty(moduleName, propName, allowedValues, badValue) {
|
||
|
if (__gwt_onPropertyError) {
|
||
|
__gwt_onPropertyError(moduleName, propName, allowedValues, badValue);
|
||
|
return;
|
||
|
} else {
|
||
|
var msg = "While attempting to load module \"" + moduleName + "\", ";
|
||
|
if (badValue != null) {
|
||
|
msg += "property \"" + propName + "\" was set to the unexpected value \""
|
||
|
+ badValue + "\"";
|
||
|
} else {
|
||
|
msg += "property \"" + propName + "\" was not specified";
|
||
|
}
|
||
|
|
||
|
msg += "\n\nAllowed values: " + allowedValues;
|
||
|
|
||
|
window.alert(msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called directly from compiled code.
|
||
|
*/
|
||
|
function __gwt_initHandlers(resize, beforeunload, unload) {
|
||
|
var oldOnResize = window.onresize;
|
||
|
window.onresize = function() {
|
||
|
resize();
|
||
|
if (oldOnResize)
|
||
|
oldOnResize();
|
||
|
};
|
||
|
|
||
|
var oldOnBeforeUnload = window.onbeforeunload;
|
||
|
window.onbeforeunload = function() {
|
||
|
var ret = beforeunload();
|
||
|
|
||
|
var oldRet;
|
||
|
if (oldOnBeforeUnload)
|
||
|
oldRet = oldOnBeforeUnload();
|
||
|
|
||
|
if (ret !== null)
|
||
|
return ret;
|
||
|
return oldRet;
|
||
|
};
|
||
|
|
||
|
var oldOnUnload = window.onunload;
|
||
|
window.onunload = function() {
|
||
|
unload();
|
||
|
if (oldOnUnload)
|
||
|
oldOnUnload();
|
||
|
};
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Hosted Mode
|
||
|
//
|
||
|
function __gwt_onUnloadHostedMode() {
|
||
|
window.external.gwtOnLoad(null, null);
|
||
|
if (__gwt_onUnloadHostedMode.oldUnloadHandler) {
|
||
|
__gwt_onUnloadHostedMode.oldUnloadHandler();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Bootstrap
|
||
|
//
|
||
|
|
||
|
/**
|
||
|
* Waits until all startup preconditions are satisfied, then launches the
|
||
|
* user-defined startup code for each module.
|
||
|
*/
|
||
|
function __gwt_latchAndLaunch() {
|
||
|
var ready = true;
|
||
|
|
||
|
// Are there any compilations still pending?
|
||
|
if (ready && !__gwt_moduleControlBlocks.isReady()) {
|
||
|
// Yes, we're still waiting on one or more compilations.
|
||
|
ready = false;
|
||
|
}
|
||
|
|
||
|
// Has the host html onload event fired?
|
||
|
if (ready && !__gwt_isHostPageLoaded) {
|
||
|
// No, the host html page hasn't fully loaded.
|
||
|
ready = false;
|
||
|
}
|
||
|
|
||
|
// Are we ready to run user code?
|
||
|
if (ready) {
|
||
|
// Yes: run entry points.
|
||
|
__gwt_moduleControlBlocks.run();
|
||
|
} else {
|
||
|
// No: try again soon.
|
||
|
window.setTimeout(__gwt_latchAndLaunch, __gwt_retryWaitMillis);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Starts the module-loading sequence after meta tags have been processed and
|
||
|
* the body element exists.
|
||
|
*/
|
||
|
function __gwt_loadModules() {
|
||
|
// Make sure the body element exists before starting.
|
||
|
if (!document.body) {
|
||
|
// Try again soon.
|
||
|
window.setTimeout(__gwt_loadModules, __gwt_retryWaitMillis);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Inject a frame for each module.
|
||
|
__gwt_moduleControlBlocks.injectFrames();
|
||
|
|
||
|
// Try to launch module entry points once everything is ready.
|
||
|
__gwt_latchAndLaunch();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The very first thing to run, and it runs exactly once unconditionally.
|
||
|
*/
|
||
|
function __gwt_bootstrap() {
|
||
|
// Hook onunload for hosted mode.
|
||
|
if (__gwt_isHosted()) {
|
||
|
__gwt_onUnloadHostedMode.oldUnloadHandler = window.onunload;
|
||
|
window.onunload = __gwt_onUnloadHostedMode;
|
||
|
}
|
||
|
|
||
|
// Hook the current window onload handler.
|
||
|
var oldHandler = window.onload;
|
||
|
window.onload = function() {
|
||
|
__gwt_isHostPageLoaded = true;
|
||
|
if (oldHandler) {
|
||
|
oldHandler();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Parse meta tags from host html.
|
||
|
__gwt_processMetas();
|
||
|
|
||
|
// Load any modules.
|
||
|
__gwt_loadModules();
|
||
|
}
|
||
|
|
||
|
// Go.
|
||
|
__gwt_bootstrap();
|