// 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();