blob: 24b5cdda95d905c4e3a613e08600ab22dc98bd9d [file] [log] [blame]
// 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();