/*
* Copyright 2011 cruxframework.org.
*
* 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.
*/
package org.cruxframework.crux.core.client.utils;
import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.ScriptElement;
/**
*
* @author Thiago da Rosa de Bustamante
*
*/
public class ScriptTagHandler
{
private static ScriptInjector injector = null;
private static ArrayList<Element> scripts;
/**
* Evaluates any script inserted on the given element using element.innerHTML.
* @param element
*/
public static void evaluateScripts(Element element, ScriptLoadCallback callback)
{
if (scripts == null)
{
scripts = new ArrayList<Element>();
}
NodeList<Element> scriptElements = element.getElementsByTagName("script");
if (scriptElements != null)
{
for (int i = 0; i < scriptElements.getLength(); i++)
{
scripts.add(scriptElements.getItem(i));
}
}
processNextScript(callback);
}
private static void processNextScript(final ScriptLoadCallback callback)
{
if (scripts.size() > 0)
{
final ScriptElement script = scripts.remove(0).cast();
final ScriptElement cloneScript = cloneScript(script); // if cloned using node.cloneNode(), browser does not evaluate the
// inner javascript when tag is attached
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
@Override
public void execute()
{
processScriptTag(script, cloneScript, callback);
}
});
}
else if (callback != null)
{
callback.onLoaded();
}
}
private static void processScriptTag(final ScriptElement script, final ScriptElement cloneScript, final ScriptLoadCallback callback)
{
handleDocWriteFunction();
String src = script.getSrc();
ScriptLoadCallback tagCallback = new ScriptLoadCallback()
{
@Override
public void onLoaded()
{
String content = getDocWrittenContent();
restoreDocWriteFunction();
processNextScript(callback);
final DivElement wrapperElement = Document.get().createDivElement();
wrapperElement.setInnerHTML(content);
cloneScript.getParentElement().insertAfter(wrapperElement, cloneScript);
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
@Override
public void execute()
{
evaluateScripts(wrapperElement, null);
}
});
}
};
// If it is a cross domain script, the content loading is assynchronous
boolean xDomain = isXDomain(src);
if (xDomain)
{
handleXDomainScriptLoad(cloneScript, tagCallback);
}
script.getParentElement().replaceChild(cloneScript, script);
if (!xDomain)
{
tagCallback.onLoaded();
}
}
private static native boolean isXDomain(String src)/*-{
if (!src)
{
return false;
}
var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec(src);
return parts && ( parts[1] && parts[1] != location.protocol || parts[2] != location.host );
}-*/;
private static ScriptElement cloneScript(ScriptElement script)
{
Document doc = Document.get();
ScriptElement cloneScript = doc.createScriptElement();
cloneScript.setType("text/javascript");
cloneScript.setLang("javascript");
if (script.hasAttribute("src"))
{
cloneScript.setSrc(script.getSrc());
}
else
{
getScriptInjector().copyScriptContent(script, cloneScript);
}
return cloneScript;
}
private static native void handleDocWriteFunction()/*-{
$wnd.__crux_content = '';
$wnd.__crux_originalWrite = $doc.write;
$doc.write = function(s) {
$wnd.__crux_content += s;
};
}-*/;
private static native String getDocWrittenContent()/*-{
return $wnd.__crux_content;
}-*/;
private static native void restoreDocWriteFunction()/*-{
$wnd.__crux_content = '';
$doc.write = $wnd.__crux_originalWrite;
}-*/;
private static native void handleXDomainScriptLoad(ScriptElement script, ScriptLoadCallback callback)/*-{
script.onload = script.onreadystatechange = function(){
if (!this.readyState || this.readyState == "loaded" || this.readyState == "complete") {
if (callback)
{
callback.@org.cruxframework.crux.core.client.utils.ScriptTagHandler.ScriptLoadCallback::onLoaded()();
}
script.onload = script.onreadystatechange = null;
script.parentNode.removeChild( script );
}
};
}-*/;
private static ScriptInjector getScriptInjector()
{
if (injector == null)
{
injector = GWT.create(ScriptInjector.class);
}
return injector;
}
public static interface ScriptLoadCallback
{
void onLoaded();
}
static class ScriptInjector
{
protected void copyScriptContent(ScriptElement script, ScriptElement cloneScript)
{
String text = script.getInnerHTML();
if (StringUtils.isEmpty(text))
{
text = script.getText();
}
cloneScript.appendChild(Document.get().createTextNode(text));
}
}
static class IEScriptInjector extends ScriptInjector
{
protected void copyScriptContent(ScriptElement script, ScriptElement cloneScript)
{
cloneScript.setText(script.getText());
}
}
}