1// Copyright 2006 Google Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14// 15// This startup script should be included in host pages either just after 16// <body> or inside the <head> after module <meta> tags. 17// 18 19////////////////////////////////////////////////////////////////////////////// 20// DynamicResources 21// 22 23function DynamicResources() { 24 this.pendingElemsBySrc_ = {}; 25 this.pendingScriptElems_ = new Array(); 26} 27DynamicResources.prototype = {}; 28 29// The array is set up such that, pairwise, the entries are (src, readyFnStr). 30// Called once for each module that is attached to the host page. 31// It is theoretically possible that addScripts() could be called reentrantly 32// if the browser event loop is pumped during this function and an iframe loads; 33// we may want to enhance this method in the future to support that case. 34DynamicResources.prototype.addScripts = function(scriptArray, insertBeforeElem) { 35 var wasEmpty = (this.pendingScriptElems_.length == 0); 36 var anyAdded = false; 37 for (var i = 0, n = scriptArray.length; i < n; i += 2) { 38 var src = scriptArray[i]; 39 if (this.pendingElemsBySrc_[src]) { 40 // Don't load the same script twice. 41 continue; 42 } 43 // Set up the element but don't add it to the DOM until its turn. 44 anyAdded = true; 45 var e = document.createElement("script"); 46 this.pendingElemsBySrc_[src] = e; 47 var readyFn; 48 eval("readyFn = " + scriptArray[i+1]); 49 e.__readyFn = readyFn; 50 e.type = "text/javascript"; 51 e.src = src; 52 e.__insertBeforeElem = insertBeforeElem; 53 this.pendingScriptElems_ = this.pendingScriptElems_.concat(e); 54 } 55 56 if (wasEmpty && anyAdded) { 57 // Kickstart. 58 this.injectScript(this.pendingScriptElems_[0]); 59 } 60} 61 62DynamicResources.prototype.injectScript = function(scriptElem) { 63 var parentElem = scriptElem.__insertBeforeElem.parentNode; 64 parentElem.insertBefore(scriptElem, scriptElem.__insertBeforeElem); 65} 66 67DynamicResources.prototype.addStyles = function(styleSrcArray, insertBeforeElem) { 68 var parent = insertBeforeElem.parentNode; 69 for (var i = 0, n = styleSrcArray.length; i < n; ++i) { 70 var src = styleSrcArray[i]; 71 if (this.pendingElemsBySrc_[src]) 72 continue; 73 var e = document.createElement("link"); 74 this.pendingElemsBySrc_[src] = e; 75 e.type = "text/css"; 76 e.rel = "stylesheet"; 77 e.href = src; 78 parent.insertBefore(e, insertBeforeElem); 79 } 80} 81 82DynamicResources.prototype.isReady = function() { 83 var elems = this.pendingScriptElems_; 84 if (elems.length > 0) { 85 var e = elems[0]; 86 if (!e.__readyFn()) { 87 // The pending script isn't ready yet. 88 return false; 89 } 90 91 // The pending script has now finished loading. Enqueue the next, if any. 92 e.__readyFn = null; 93 elems.shift(); 94 if (elems.length > 0) { 95 // There is another script. 96 this.injectScript(elems[0]); 97 return false; 98 } 99 } 100 101 // There are no more pending scripts. 102 return true; 103} 104 105////////////////////////////////////////////////////////////////////////////// 106// ModuleControlBlock 107// 108function ModuleControlBlock(metaElem, rawName) { 109 var parts = ["", rawName]; 110 var i = rawName.lastIndexOf("="); 111 if (i != -1) { 112 parts[0] = rawName.substring(0, i) + '/'; 113 parts[1] = rawName.substring(i+1); 114 } 115 116 this.metaElem_ = metaElem; 117 this.baseUrl_ = parts[0]; 118 this.name_ = parts[1]; 119 this.compilationLoaded_ = false; 120 this.frameWnd_ = null; 121} 122ModuleControlBlock.prototype = {}; 123 124/** 125 * Determines whether this module is fully loaded and ready to run. 126 */ 127ModuleControlBlock.prototype.isReady = function() { 128 return this.compilationLoaded_; 129}; 130 131/** 132 * Called when the compilation for this module is loaded. 133 */ 134ModuleControlBlock.prototype.compilationLoaded = function(frameWnd) { 135 this.frameWnd_ = frameWnd; 136 this.compilationLoaded_ = true; 137} 138 139/** 140 * Gets the logical module name, not including a base url prefix if one was 141 * specified. 142 */ 143ModuleControlBlock.prototype.getName = function() { 144 return this.name_; 145} 146 147/** 148 * Gets the base URL of the module, guaranteed to end with a slash. 149 */ 150ModuleControlBlock.prototype.getBaseURL = function() { 151 return this.baseUrl_; 152} 153 154/** 155 * Gets the window of the module's frame. 156 */ 157ModuleControlBlock.prototype.getModuleFrameWindow = function() { 158 return this.frameWnd_; 159} 160 161/** 162 * Injects a set of dynamic scripts. 163 * The array is set up such that, pairwise, the entries are (src, readyFnStr). 164 */ 165ModuleControlBlock.prototype.addScripts = function(scriptSrcArray) { 166 return ModuleControlBlocks.dynamicResources_.addScripts(scriptSrcArray, this.metaElem_); 167} 168 169/** 170 * Injects a set of dynamic styles. 171 */ 172ModuleControlBlock.prototype.addStyles = function(styleSrcArray) { 173 return ModuleControlBlocks.dynamicResources_.addStyles(styleSrcArray, this.metaElem_); 174} 175 176////////////////////////////////////////////////////////////////////////////// 177// ModuleControlBlocks 178// 179function ModuleControlBlocks() { 180 this.blocks_ = []; 181} 182ModuleControlBlocks.dynamicResources_ = new DynamicResources(); // "static" 183ModuleControlBlocks.prototype = {}; 184 185/** 186 * Adds a module control control block for the named module. 187 * @param metaElem the meta element that caused the module to be added 188 * @param name the name of the module being added, optionally preceded by 189 * an alternate base url of the form "_path_=_module_". 190 */ 191ModuleControlBlocks.prototype.add = function(metaElem, name) { 192 var mcb = new ModuleControlBlock(metaElem, name); 193 this.blocks_ = this.blocks_.concat(mcb); 194}; 195 196/** 197 * Determines whether all the modules are loaded and ready to run. 198 */ 199ModuleControlBlocks.prototype.isReady = function() { 200 for (var i = 0, n = this.blocks_.length; i < n; ++i) { 201 var mcb = this.blocks_[i]; 202 if (!mcb.isReady()) { 203 return false; 204 } 205 } 206 207 // Are there any pending dynamic resources (e.g. styles, scripts)? 208 if (!ModuleControlBlocks.dynamicResources_.isReady()) { 209 // No, we're still waiting on one or more dynamic resources. 210 return false; 211 } 212 213 return true; 214} 215 216/** 217 * Determines whether there are any module control blocks. 218 */ 219ModuleControlBlocks.prototype.isEmpty = function() { 220 return this.blocks_.length == 0; 221} 222 223/** 224 * Gets the module control block at the specified index. 225 */ 226ModuleControlBlocks.prototype.get = function(index) { 227 return this.blocks_[index]; 228} 229 230/** 231 * Injects an iframe for each module. 232 */ 233ModuleControlBlocks.prototype.injectFrames = function() { 234 for (var i = 0, n = this.blocks_.length; i < n; ++i) { 235 var mcb = this.blocks_[i]; 236 237 // Insert an iframe for the module 238 var iframe = document.createElement("iframe"); 239 var selectorUrl = mcb.getBaseURL() + mcb.getName() + ".nocache.html"; 240 selectorUrl += "?" + (__gwt_isHosted() ? "h&" : "" ) + i; 241 var unique = new Date().getTime(); 242 selectorUrl += "&" + unique; 243 iframe.style.border = '0px'; 244 iframe.style.width = '0px'; 245 iframe.style.height = '0px'; 246 247 // Fragile browser-specific ordering issues below 248 249/*@cc_on 250 // prevent extra clicky noises on IE 251 iframe.src = selectorUrl; 252@*/ 253 254 if (document.body.firstChild) { 255 document.body.insertBefore(iframe, document.body.firstChild); 256 } else { 257 document.body.appendChild(iframe); 258 } 259 260/*@cc_on 261 // prevent extra clicky noises on IE 262 return; 263@*/ 264 265 if (iframe.contentWindow) { 266 // Older Mozilla has a caching bug for the iframe and won't reload the nocache. 267 iframe.contentWindow.location.replace(selectorUrl); 268 } else { 269 // Older Safari doesn't have a contentWindow. 270 iframe.src = selectorUrl; 271 } 272 } 273} 274 275/** 276 * Runs the entry point for each module. 277 */ 278ModuleControlBlocks.prototype.run = function() { 279 for (var i = 0, n = this.blocks_.length; i < n; ++i) { 280 var mcb = this.blocks_[i]; 281 var name = mcb.getName(); 282 var frameWnd = mcb.getModuleFrameWindow(); 283 if (__gwt_isHosted()) { 284 if (!window.external.gwtOnLoad(frameWnd, name)) { 285 // Module failed to load. 286 if (__gwt_onLoadError) { 287 __gwt_onLoadError(name); 288 } else { 289 window.alert("Failed to load module '" + name + 290 "'.\nPlease see the log in the development shell for details."); 291 } 292 } 293 } else { 294 // The compilation itself handles calling the error function. 295 frameWnd.gwtOnLoad(__gwt_onLoadError, name); 296 } 297 } 298} 299 300////////////////////////////////////////////////////////////////////////////// 301// Globals 302// 303 304var __gwt_retryWaitMillis = 10; 305var __gwt_isHostPageLoaded = false; 306var __gwt_metaProps = {}; 307var __gwt_onPropertyError = null; 308var __gwt_onLoadError = null; 309var __gwt_moduleControlBlocks = new ModuleControlBlocks(); 310 311////////////////////////////////////////////////////////////////////////////// 312// Common 313// 314 315/** 316 * Determines whether or not the page is being loaded in the GWT hosted browser. 317 */ 318function __gwt_isHosted() { 319 if (window.external && window.external.gwtOnLoad) { 320 // gwt.hybrid makes the hosted browser pretend not to be 321 if (document.location.href.indexOf("gwt.hybrid") == -1) { 322 return true; 323 } 324 } 325 return false; 326} 327 328/** 329 * Tries to get a module control block based on a query string passed in from 330 * the caller. Used by iframes to get references back to their mcbs. 331 * @param queryString the entire query string as returned by location.search, 332 * which notably includes the leading '?' if one is specified 333 * @return the relevant module control block, or <code>null</code> if it cannot 334 * be derived based on <code>queryString</code> 335 */ 336function __gwt_tryGetModuleControlBlock(queryString) { 337 if (queryString.length > 0) { 338 // The pattern is ?[h&]<index>[&<unique>] 339 var queryString = queryString.substring(1); 340 if (queryString.indexOf("h&") == 0) { 341 // Ignore the hosted mode flag here; only GWTShellServlet cares about it. 342 queryString = queryString.substring(2); 343 } 344 var pos = queryString.indexOf("&"); 345 if (pos >= 0) { 346 queryString = queryString.substring(0, pos); 347 } 348 var mcbIndex = parseInt(queryString); 349 if (!isNaN(mcbIndex)) { 350 var mcb = __gwt_moduleControlBlocks.get(mcbIndex); 351 return mcb; 352 } 353 // Ignore the unique number that remains on the query string. 354 } 355 return null; 356} 357 358/** 359 * Parses meta tags from the host html. 360 * 361 * <meta name="gwt:module" content="_module-name_"> 362 * causes the specified module to be loaded 363 * 364 * <meta name="gwt:property" content="_name_=_value_"> 365 * statically defines a deferred binding client property 366 * 367 * <meta name="gwt:onPropertyErrorFn" content="_fnName_"> 368 * specifies the name of a function to call if a client property is set to 369 * an invalid value (meaning that no matching compilation will be found) 370 * 371 * <meta name="gwt:onLoadErrorFn" content="_fnName_"> 372 * specifies the name of a function to call if an exception happens during 373 * bootstrapping or if a module throws an exception out of onModuleLoad(); 374 * the function should take a message parameter 375 */ 376function __gwt_processMetas() { 377 var metas = document.getElementsByTagName("meta"); 378 for (var i = 0, n = metas.length; i < n; ++i) { 379 var meta = metas[i]; 380 var name = meta.getAttribute("name"); 381 if (name) { 382 if (name == "gwt:module") { 383 var moduleName = meta.getAttribute("content"); 384 if (moduleName) { 385 __gwt_moduleControlBlocks.add(meta, moduleName); 386 } 387 } else if (name == "gwt:property") { 388 var content = meta.getAttribute("content"); 389 if (content) { 390 var name = content, value = ""; 391 var eq = content.indexOf("="); 392 if (eq != -1) { 393 name = content.substring(0, eq); 394 value = content.substring(eq+1); 395 } 396 __gwt_metaProps[name] = value; 397 } 398 } else if (name == "gwt:onPropertyErrorFn") { 399 var content = meta.getAttribute("content"); 400 if (content) { 401 try { 402 __gwt_onPropertyError = eval(content); 403 } catch (e) { 404 window.alert("Bad handler \"" + content + 405 "\" for \"gwt:onPropertyErrorFn\""); 406 } 407 } 408 } else if (name == "gwt:onLoadErrorFn") { 409 var content = meta.getAttribute("content"); 410 if (content) { 411 try { 412 __gwt_onLoadError = eval(content); 413 } catch (e) { 414 window.alert("Bad handler \"" + content + 415 "\" for \"gwt:onLoadErrorFn\""); 416 } 417 } 418 } 419 } 420 } 421} 422 423/** 424 * Determines the value of a deferred binding client property specified 425 * statically in host html. 426 */ 427function __gwt_getMetaProperty(name) { 428 var value = __gwt_metaProps[name]; 429 if (value) { 430 return value; 431 } else { 432 return null; 433 } 434} 435 436/** 437 * Determines whether or not a particular property value is allowed. 438 * @param wnd the caller's window object (not $wnd!) 439 * @param propName the name of the property being checked 440 * @param propValue the property value being tested 441 */ 442function __gwt_isKnownPropertyValue(wnd, propName, propValue) { 443 return propValue in wnd["values$" + propName]; 444} 445 446/** 447 * Called by the selection script when a property has a bad value or is missing. 448 * 'allowedValues' is an array of strings. Can be hooked in the host page using 449 * gwt:onPropertyErrorFn. 450 */ 451function __gwt_onBadProperty(moduleName, propName, allowedValues, badValue) { 452 if (__gwt_onPropertyError) { 453 __gwt_onPropertyError(moduleName, propName, allowedValues, badValue); 454 return; 455 } else { 456 var msg = "While attempting to load module \"" + moduleName + "\", "; 457 if (badValue != null) { 458 msg += "property \"" + propName + "\" was set to the unexpected value \"" 459 + badValue + "\""; 460 } else { 461 msg += "property \"" + propName + "\" was not specified"; 462 } 463 464 msg += "\n\nAllowed values: " + allowedValues; 465 466 window.alert(msg); 467 } 468} 469 470/** 471 * Called directly from compiled code. 472 */ 473function __gwt_initHandlers(resize, beforeunload, unload) { 474 var oldOnResize = window.onresize; 475 window.onresize = function() { 476 resize(); 477 if (oldOnResize) 478 oldOnResize(); 479 }; 480 481 var oldOnBeforeUnload = window.onbeforeunload; 482 window.onbeforeunload = function() { 483 var ret = beforeunload(); 484 485 var oldRet; 486 if (oldOnBeforeUnload) 487 oldRet = oldOnBeforeUnload(); 488 489 if (ret !== null) 490 return ret; 491 return oldRet; 492 }; 493 494 var oldOnUnload = window.onunload; 495 window.onunload = function() { 496 unload(); 497 if (oldOnUnload) 498 oldOnUnload(); 499 }; 500} 501 502////////////////////////////////////////////////////////////////////////////// 503// Hosted Mode 504// 505function __gwt_onUnloadHostedMode() { 506 window.external.gwtOnLoad(null, null); 507 if (__gwt_onUnloadHostedMode.oldUnloadHandler) { 508 __gwt_onUnloadHostedMode.oldUnloadHandler(); 509 } 510} 511 512////////////////////////////////////////////////////////////////////////////// 513// Bootstrap 514// 515 516/** 517 * Waits until all startup preconditions are satisfied, then launches the 518 * user-defined startup code for each module. 519 */ 520function __gwt_latchAndLaunch() { 521 var ready = true; 522 523 // Are there any compilations still pending? 524 if (ready && !__gwt_moduleControlBlocks.isReady()) { 525 // Yes, we're still waiting on one or more compilations. 526 ready = false; 527 } 528 529 // Has the host html onload event fired? 530 if (ready && !__gwt_isHostPageLoaded) { 531 // No, the host html page hasn't fully loaded. 532 ready = false; 533 } 534 535 // Are we ready to run user code? 536 if (ready) { 537 // Yes: run entry points. 538 __gwt_moduleControlBlocks.run(); 539 } else { 540 // No: try again soon. 541 window.setTimeout(__gwt_latchAndLaunch, __gwt_retryWaitMillis); 542 } 543} 544 545/** 546 * Starts the module-loading sequence after meta tags have been processed and 547 * the body element exists. 548 */ 549function __gwt_loadModules() { 550 // Make sure the body element exists before starting. 551 if (!document.body) { 552 // Try again soon. 553 window.setTimeout(__gwt_loadModules, __gwt_retryWaitMillis); 554 return; 555 } 556 557 // Inject a frame for each module. 558 __gwt_moduleControlBlocks.injectFrames(); 559 560 // Try to launch module entry points once everything is ready. 561 __gwt_latchAndLaunch(); 562} 563 564/** 565 * The very first thing to run, and it runs exactly once unconditionally. 566 */ 567function __gwt_bootstrap() { 568 // Hook onunload for hosted mode. 569 if (__gwt_isHosted()) { 570 __gwt_onUnloadHostedMode.oldUnloadHandler = window.onunload; 571 window.onunload = __gwt_onUnloadHostedMode; 572 } 573 574 // Hook the current window onload handler. 575 var oldHandler = window.onload; 576 window.onload = function() { 577 __gwt_isHostPageLoaded = true; 578 if (oldHandler) { 579 oldHandler(); 580 } 581 }; 582 583 // Parse meta tags from host html. 584 __gwt_processMetas(); 585 586 // Load any modules. 587 __gwt_loadModules(); 588} 589 590// Go. 591__gwt_bootstrap(); 592