| /* |
| * Copyright 2014 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. |
| */ |
| |
| // Export into our name space |
| // We do not consider any of these classes a public API and they will be changed as needed. |
| $namespace.lib = $namespace.lib || {}; |
| |
| StringHelper = {}; |
| |
| StringHelper.endsWith = function(str, suffix) { |
| return str.indexOf(suffix, str.length - suffix.length) !== -1; |
| }; |
| |
| StringHelper.removeQueryString = function(str) { |
| return str.split("#")[0].split("?")[0]; |
| }; |
| |
| $namespace.lib.StringHelper = StringHelper; |
| |
| /** |
| * A PropertySource computes the binding properties to use for calling the GWT compiler. |
| * |
| * @constructor |
| * @param {string} moduleName - the name of the gwt module |
| * @param {Function} propertyProvidersHolder - a function returning property providers and |
| * their values when invoked. |
| * @param {MetaTagParser} metaTagParser - an instance of {@link MetaTagParser} |
| */ |
| function PropertySource(moduleName, propertyProvidersHolder, metaTagParser) { |
| this.__moduleName = moduleName; |
| this.__metaTagParser = metaTagParser; |
| var props = this.__getProvidersAndValues(propertyProvidersHolder); |
| this.__propertyProviders = props.providers; |
| this.__propertyValues = props.values; |
| } |
| |
| /** |
| * Setup the environment that property providers need to run and initialize property providers |
| * with that environment. |
| * |
| * @returns an object containing property providers and possible values. |
| */ |
| PropertySource.prototype.__getProvidersAndValues = function(propertyProvidersHolder) { |
| // Setup scope for property providers |
| var that = this; |
| |
| //Used by LocalePropertyGenerator |
| var __gwt_getMetaProperty = function(name) { |
| return that.__metaTagParser.get()[name]; |
| }; |
| |
| // Used by LocalePropertyGenerator |
| var __gwt_isKnownPropertyValue = function(propName, propValue) { |
| return propValue in that.__propertyValues[propName]; |
| }; |
| |
| // This function is defined in recompile_template.js and parameters being passed here need to be |
| // kept in sync. |
| return propertyProvidersHolder(__gwt_getMetaProperty, __gwt_isKnownPropertyValue); |
| }; |
| |
| /** |
| * Compute the binding property value for a given property name. |
| * |
| * @param {string} propName - the binding property name |
| * @returns {string} the computed value of the binding property |
| */ |
| PropertySource.prototype.__computePropValue = function(propName) { |
| var val = this.__propertyProviders[propName](); |
| // sanity check |
| var allowedValuesMap = this.__propertyValues[propName]; |
| if (val in allowedValuesMap) { |
| return val; |
| } else { |
| // TODO(dankurka): trigger this error in the ui |
| // IE8 only has console defined if its dev tools have been opened before |
| if ($wnd.console && $wnd.console.log) { |
| $wnd.console.log("provider for " + propName |
| + " returned unexpected value: '" + val + "'"); |
| } |
| throw "can't compute binding property value for " + propName; |
| } |
| }; |
| |
| /** |
| * Compute all binding properties for a given module. |
| * @returns {Object} a map of from binding properties to their current values |
| */ |
| PropertySource.prototype.computeBindingProperties = function() { |
| var result = {}; |
| for (var key in this.__propertyValues) { |
| if (this.__propertyValues.hasOwnProperty(key)) { |
| result[key] = this.__computePropValue(key); |
| } |
| } |
| return result; |
| }; |
| |
| $namespace.lib.PropertySource = PropertySource; |
| |
| /** |
| * Create a dialog. |
| * @constructor |
| */ |
| function Dialog() { |
| var dialog = $doc.createElement('div'); |
| dialog.style.zIndex = 1000001; |
| dialog.style.position = 'fixed'; |
| dialog.style.top = '20pt'; |
| dialog.style.left = '20pt'; |
| dialog.style.right = '20pt'; |
| dialog.style.color = 'black'; |
| dialog.style.background = 'white'; |
| dialog.style.border = '4px solid #ccc'; |
| dialog.style.padding = '1em'; |
| dialog.style.borderRadius = '5px'; |
| dialog.style.wordWrap = 'break-word'; |
| this.__dialog = dialog; |
| |
| var overlay = $doc.createElement('div'); |
| overlay.style.zIndex = 1000000; |
| overlay.style.position = 'absolute'; |
| overlay.style.top = 0; |
| overlay.style.left = 0; |
| overlay.style.bottom = 0; |
| overlay.style.right = 0; |
| overlay.style.background = 'black'; // darken background |
| overlay.style.opacity = '0.5'; |
| this.__overlay = overlay; |
| } |
| |
| /** |
| * Create a DOM node with a given text. |
| * @returns {external:Node} the node with the given text. |
| */ |
| Dialog.prototype.createTextElement = function(tagName, fontSize, text) { |
| var elt = $doc.createElement(tagName); |
| elt.style.color = 'black'; |
| elt.style.background = 'white'; |
| elt.style.fontSize = fontSize; |
| elt.appendChild($doc.createTextNode(text)); |
| return elt; |
| }; |
| |
| /** |
| * Add a node to the dialog. |
| * @param {external:Node} node - the node to add to the dialog |
| */ |
| Dialog.prototype.add = function(node) { |
| this.__dialog.appendChild(node); |
| }; |
| |
| /** |
| * Remove all children from the dialog. |
| */ |
| Dialog.prototype.clear = function() { |
| while (this.__dialog.firstChild) { |
| this.__dialog.removeChild(this.__dialog.firstChild); |
| } |
| }; |
| |
| /** |
| * Show the dialog (add it to the body). |
| */ |
| Dialog.prototype.show = function() { |
| $doc.body.appendChild(this.__overlay); |
| $doc.body.appendChild(this.__dialog); |
| }; |
| |
| /** |
| * Hide the dialog (remove it from the body). |
| */ |
| Dialog.prototype.hide = function() { |
| $doc.body.removeChild(this.__overlay); |
| $doc.body.removeChild(this.__dialog); |
| }; |
| |
| //Export Dialog to namespace |
| $namespace.lib.Dialog = Dialog; |
| |
| /** |
| * Construct a Recompiler object. |
| * @constructor |
| * @param {string} moduleName - the name of the gwt module |
| * @param {Object} permutationProperties - A map of binding property names (string) to their values (string). |
| * @returns |
| */ |
| function Recompiler(moduleName, permutationProperties) { |
| if ($wnd.__gwt_sdm_globals) { |
| this.__globals = $wnd.__gwt_sdm_globals; |
| } else { |
| this.__globals = { |
| callbackCounter: new Date().getTime(), // avoid cache hits |
| callbacks: {} |
| }; |
| $wnd.__gwt_sdm_globals = this.__globals; |
| } |
| this.__moduleName = moduleName; |
| this.__permutationProperties = permutationProperties; |
| this.__compiling = false; |
| } |
| |
| /** |
| * Build the url that triggers a compile on the code server. |
| * @returns {string} the url |
| */ |
| Recompiler.prototype.__buildCompileUrl = function() { |
| var url = this.getCodeServerBaseUrl() + 'recompile/' + this.__moduleName + '?'; |
| var props = []; |
| for (var key in this.__permutationProperties) { |
| props.push($wnd.encodeURIComponent(key) + '=' + |
| $wnd.encodeURIComponent(this.__permutationProperties[key])); |
| } |
| return url + props.join('&'); |
| }; |
| |
| /** |
| * Issue a compile request for the module of this Recompiler class. |
| * @param {Function} finishCallback - the callback to invoke once compile finishes |
| */ |
| Recompiler.prototype.compile = function(finishCallback) { |
| var that = this; |
| this.__compiling = true; |
| var compileUrl = this.__buildCompileUrl(); |
| this.__jsonp(compileUrl, function(data) { |
| that.__compiling = false; |
| finishCallback(data); |
| }); |
| }; |
| |
| /** |
| * Issue a jsonp request. |
| * @param {string} url - the url to use |
| * @param {Function} callback - the callback to invoke |
| */ |
| Recompiler.prototype.__jsonp = function(url, callback) { |
| var that = this; |
| var callback_id = 'c' + this.__globals.callbackCounter++; |
| this.__globals.callbacks[callback_id] = function(json) { |
| delete that.__globals.callbacks[callback_id]; |
| callback(json); |
| }; |
| |
| var url = url + '&_callback=__gwt_sdm_globals.callbacks.' + callback_id; |
| this.__injectScriptTag(url); |
| }; |
| |
| Recompiler.prototype.__injectScriptTag = function(url) { |
| var script = $doc.createElement('script'); |
| script.src = url; |
| var $head = $doc.head || $doc.getElementsByTagName('head')[0]; |
| $head.appendChild(script); |
| }; |
| |
| /** |
| * Get protocol and host from a url. |
| * @param {string} url |
| */ |
| Recompiler.prototype.__getBaseUrl = function(url) { |
| var a = $doc.createElement('a'); |
| a.href = url; |
| return a.protocol + '//' + a.host + '/'; |
| }; |
| |
| /** |
| * Get the base url of the code server. |
| * @returns {string} the url of the code server |
| */ |
| Recompiler.prototype.getCodeServerBaseUrl = function() { |
| var scriptTagsToSearch = $doc.getElementsByTagName('script'); |
| var expectedSuffix = '/recompile-requester/' + this.__moduleName; |
| for (var i = 0; i < scriptTagsToSearch.length; i++) { |
| var candidate = StringHelper.removeQueryString(scriptTagsToSearch[i].src); |
| if (StringHelper.endsWith(candidate, expectedSuffix)) { |
| return this.__getBaseUrl(candidate); |
| } |
| } |
| throw "unable to find the script tag that includes /recompile-requester/" + moduleName ; |
| }; |
| |
| /** |
| * Load the compiled application |
| */ |
| Recompiler.prototype.loadApp = function() { |
| var url = this.getCodeServerBaseUrl() + this.__moduleName + '/' + this.__moduleName + '.nocache.js'; |
| var scriptTag = $doc.createElement('script'); |
| scriptTag.src = url; |
| var $head = $doc.head || $doc.getElementsByTagName('head')[0]; |
| $head.insertBefore(scriptTag, $head.firstElementChild || $head.children[0]); |
| }; |
| |
| Recompiler.prototype.getLogUrl = function() { |
| return this.getCodeServerBaseUrl() + 'log/' + this.__moduleName; |
| }; |
| |
| //Export Recompiler to namespace |
| $namespace.lib.Recompiler = Recompiler; |
| |
| function MetaTagParser(moduleName) { |
| this.__parsed = false; |
| this.__metaProperties = null; |
| this.__moduleName = moduleName; |
| } |
| |
| MetaTagParser.prototype.__getMetaTags = function() { |
| return $doc.getElementsByTagName('meta'); |
| }; |
| |
| MetaTagParser.prototype.__parse = function() { |
| var metaProps = {}; |
| var metas = this.__getMetaTags(); |
| |
| for (var i = 0, n = metas.length; i < n; ++i) { |
| var meta = metas[i]; |
| var name = meta.getAttribute('name'); |
| var content = meta.getAttribute('content'); |
| |
| if (name) { |
| name = name.replace(this.__moduleName + '::', ''); |
| if (name.indexOf('::') >= 0) { |
| // It's for a different module |
| continue; |
| } |
| } |
| if (name == 'gwt:property' && content) { |
| var value; |
| var eq = content.indexOf('='); |
| if (eq >= 0) { |
| name = content.substring(0, eq); |
| value = content.substring(eq+1); |
| } else { |
| name = content; |
| value = ''; |
| } |
| metaProps[name] = value; |
| } |
| } |
| return metaProps; |
| }; |
| |
| MetaTagParser.prototype.get = function() { |
| if (!this.__parsed) { |
| this.__metaProperties = this.__parse(); |
| this.__parsed = true; |
| } |
| return this.__metaProperties; |
| }; |
| |
| //Export MetaTagParser to namespace |
| $namespace.lib.MetaTagParser = MetaTagParser; |
| |
| /** |
| * BaseUrlProvider provides the url to the original server that the app has been loaded from. |
| * This is not the url of super dev mode. |
| * |
| * @constructor |
| * @param {string} moduleName - the module for which we should determine the base url. |
| */ |
| function BaseUrlProvider(moduleName, metaTagParser) { |
| this.__moduleName = moduleName; |
| this.__metaTagParser = metaTagParser; |
| } |
| |
| BaseUrlProvider.prototype.__getScriptTags = function() { |
| return $doc.getElementsByTagName('script'); |
| }; |
| |
| BaseUrlProvider.prototype.__stripHashQueryAndFileName = function(url) { |
| // Truncate starting at the first '?' or '#', whichever comes first. |
| var hashIndex = url.lastIndexOf('#'); |
| if (hashIndex == -1) { |
| hashIndex = url.length; |
| } |
| |
| var queryIndex = url.indexOf('?'); |
| if (queryIndex == -1) { |
| queryIndex = url.length; |
| } |
| |
| var slashIndex = url.lastIndexOf('/', Math.min(queryIndex, hashIndex)); |
| if (slashIndex < 0) { |
| throw new 'Cannot find slash in: ' + url; |
| } |
| |
| return url.substring(0, slashIndex + 1); |
| }; |
| |
| BaseUrlProvider.prototype.__maybeConvertToAbsoluteUrl = function(url) { |
| // test for valid protocol starts |
| if ((url.match(/^(?:(?:https)|(?:http)|(?:file)):\/\//))) { |
| return url; |
| } |
| |
| // Probably a relative URL; use magic to make the browser absolutify it. |
| // I wish there were a better way to do this, but this seems the only |
| // sure way! (A side benefit is it preloads clear.cache.gif) |
| // Note: this trick is harmless if the URL was really already absolute. |
| var img = $doc.createElement("img"); |
| img.src = url + 'clear.cache.gif'; |
| return this.__stripHashQueryAndFileName(img.src); |
| }; |
| |
| BaseUrlProvider.prototype.getBaseUrl = function() { |
| // This code follows the order in com/google/gwt/core/ext/linker/impl/computeScriptBase.js |
| // Try to get the url from a meta property first |
| var url = this.__getBaseUrlFromMetaTag(); |
| if (url) { |
| return this.__maybeConvertToAbsoluteUrl(url); |
| } |
| |
| // try the nocache next |
| var expectedSuffix = this.__moduleName + '.nocache.js'; |
| var scriptTags = this.__getScriptTags(); |
| for (var i = 0; i < scriptTags.length; i++) { |
| var candidate = StringHelper.removeQueryString(scriptTags[i].src); |
| if (StringHelper.endsWith(candidate, expectedSuffix)) { |
| var stripedUrl = this.__stripHashQueryAndFileName(candidate); |
| return this.__maybeConvertToAbsoluteUrl(stripedUrl); |
| } |
| } |
| |
| //try the base tag |
| var baseElements = this.__getBaseElements(); |
| if (baseElements.length > 0) { |
| // It's always the last parsed base tag that will apply to this script. |
| var baseElementUrl = baseElements[baseElements.length - 1].href; |
| return this.__maybeConvertToAbsoluteUrl(baseElementUrl); |
| } |
| |
| // Now we are getting desperate and as a last resort we try the current doc |
| var fallbackUrl = this.__stripHashQueryAndFileName($doc.location.href); |
| return this.__maybeConvertToAbsoluteUrl(fallbackUrl); |
| }; |
| |
| BaseUrlProvider.prototype.__getBaseElements = function() { |
| return $doc.getElementsByTagName('base'); |
| }; |
| |
| BaseUrlProvider.prototype.__getBaseUrlFromMetaTag = function() { |
| return this.__metaTagParser.get()['baseUrl']; |
| }; |
| |
| //Export BaseUrlProvider to namespace |
| $namespace.lib.BaseUrlProvider = BaseUrlProvider; |