Browse Source

Add error handling to ResourceLoader (#9044)

tags/7.0.0.alpha3
Leif Åstrand 12 years ago
parent
commit
41ba62d059

+ 15
- 2
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java View File

@@ -1624,9 +1624,16 @@ public class ApplicationConnection {
private static void loadStyleDependencies(JsArrayString dependencies) {
// Assuming no reason to interpret in a defined order
ResourceLoadListener resourceLoadListener = new ResourceLoadListener() {
public void onResourceLoad(ResourceLoadEvent event) {
public void onLoad(ResourceLoadEvent event) {
ApplicationConfiguration.endDependencyLoading();
}

public void onError(ResourceLoadEvent event) {
VConsole.error(event.getResourceUrl()
+ " could not be loaded, or the load detection failed because the stylesheet is empty.");
// The show must go on
onLoad(event);
}
};
ResourceLoader loader = ResourceLoader.get();
for (int i = 0; i < dependencies.length(); i++) {
@@ -1642,7 +1649,7 @@ public class ApplicationConnection {

// Listener that loads the next when one is completed
ResourceLoadListener resourceLoadListener = new ResourceLoadListener() {
public void onResourceLoad(ResourceLoadEvent event) {
public void onLoad(ResourceLoadEvent event) {
if (dependencies.length() != 0) {
ApplicationConfiguration.startDependencyLoading();
// Load next in chain (hopefully already preloaded)
@@ -1652,6 +1659,12 @@ public class ApplicationConnection {
// Call start for next before calling end for current
ApplicationConfiguration.endDependencyLoading();
}

public void onError(ResourceLoadEvent event) {
VConsole.error(event.getResourceUrl() + " could not be loaded.");
// The show must go on
onLoad(event);
}
};

ResourceLoader loader = ResourceLoader.get();

+ 167
- 39
src/com/vaadin/terminal/gwt/client/ResourceLoader.java View File

@@ -10,6 +10,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
@@ -20,6 +21,7 @@ import com.google.gwt.dom.client.LinkElement;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.ObjectElement;
import com.google.gwt.dom.client.ScriptElement;
import com.google.gwt.user.client.Timer;

/**
* ResourceLoader lets you dynamically include external scripts and styles on
@@ -97,7 +99,9 @@ public class ResourceLoader {
*/
public interface ResourceLoadListener {
/**
* Notified this ResourceLoadListener that a resource has been loaded
* Notifies this ResourceLoadListener that a resource has been loaded.
* Some browsers do not support any way of detecting load errors. In
* these cases, onLoad will be called regardless of the status.
*
* @see ResourceLoadEvent
*
@@ -105,7 +109,22 @@ public class ResourceLoader {
* a resource load event with information about the loaded
* resource
*/
public void onResourceLoad(ResourceLoadEvent event);
public void onLoad(ResourceLoadEvent event);

/**
* Notifies this ResourceLoadListener that a resource could not be
* loaded, e.g. because the file could not be found or because the
* server did not respond. Some browsers do not support any way of
* detecting load errors. In these cases, onLoad will be called
* regardless of the status.
*
* @see ResourceLoadEvent
*
* @param event
* a resource load event with information about the resource
* that could not be loaded.
*/
public void onError(ResourceLoadEvent event);
}

private static final ResourceLoader INSTANCE = GWT
@@ -176,19 +195,27 @@ public class ResourceLoader {
public void loadScript(final String scriptUrl,
final ResourceLoadListener resourceLoadListener) {
final String url = getAbsoluteUrl(scriptUrl);
ResourceLoadEvent event = new ResourceLoadEvent(this, url, false);
if (loadedResources.contains(url)) {
if (resourceLoadListener != null) {
resourceLoadListener.onResourceLoad(new ResourceLoadEvent(this,
url, false));
resourceLoadListener.onLoad(event);
}
return;
}

if (preloadListeners.containsKey(url)) {
// Preload going on, continue when preloaded
preloadResource(url, new ResourceLoadListener() {
public void onResourceLoad(ResourceLoadEvent event) {
public void onLoad(ResourceLoadEvent event) {
loadScript(url, resourceLoadListener);
}

public void onError(ResourceLoadEvent event) {
// Preload failed -> signal error to own listener
if (resourceLoadListener != null) {
resourceLoadListener.onError(event);
}
}
});
return;
}
@@ -197,7 +224,15 @@ public class ResourceLoader {
ScriptElement scriptTag = Document.get().createScriptElement();
scriptTag.setSrc(url);
scriptTag.setType("text/javascript");
addOnloadHandler(scriptTag, url, false);
addOnloadHandler(scriptTag, new ResourceLoadListener() {
public void onLoad(ResourceLoadEvent event) {
fireLoad(event);
}

public void onError(ResourceLoadEvent event) {
fireError(event);
}
}, event);
head.appendChild(scriptTag);
}
}
@@ -229,10 +264,11 @@ public class ResourceLoader {
public void preloadResource(String url,
ResourceLoadListener resourceLoadListener) {
url = getAbsoluteUrl(url);
ResourceLoadEvent event = new ResourceLoadEvent(this, url, true);
if (loadedResources.contains(url) || preloadedResources.contains(url)) {
// Already loaded or preloaded -> just fire listener
if (resourceLoadListener != null) {
resourceLoadListener.onResourceLoad(new ResourceLoadEvent(this,
url, !loadedResources.contains(url)));
resourceLoadListener.onLoad(event);
}
return;
}
@@ -243,7 +279,15 @@ public class ResourceLoader {
// AND the resources isn't already being loaded in the normal way

Element element = getPreloadElement(url);
addOnloadHandler(element, url, true);
addOnloadHandler(element, new ResourceLoadListener() {
public void onLoad(ResourceLoadEvent event) {
fireLoad(event);
}

public void onError(ResourceLoadEvent event) {
fireError(event);
}
}, event);

// TODO Remove object when loaded (without causing spinner in FF)
Document.get().getBody().appendChild(element);
@@ -266,24 +310,24 @@ public class ResourceLoader {
}
}

private native void addOnloadHandler(Element element, String url,
boolean preload)
private native void addOnloadHandler(Element element,
ResourceLoadListener listener, ResourceLoadEvent event)
/*-{
var self = this;
var done = $entry(function() {
element.onloadDone = true;
element.onload = $entry(function() {
element.onload = null;
element.onerror = null;
element.onreadystatechange = null;
self.@com.vaadin.terminal.gwt.client.ResourceLoader::onResourceLoad(Ljava/lang/String;Z)(url, preload);
listener.@com.vaadin.terminal.gwt.client.ResourceLoader.ResourceLoadListener::onLoad(Lcom/vaadin/terminal/gwt/client/ResourceLoader$ResourceLoadEvent;)(event);
});
element.onerror = $entry(function() {
element.onload = null;
element.onerror = null;
element.onreadystatechange = null;
listener.@com.vaadin.terminal.gwt.client.ResourceLoader.ResourceLoadListener::onError(Lcom/vaadin/terminal/gwt/client/ResourceLoader$ResourceLoadEvent;)(event);
});
element.onload = function() {
if (!element.onloadDone) {
done();
}
};
element.onreadystatechange = function() {
if (("loaded" === element.readyState || "complete" === element.readyState) && !element.onloadDone ) {
done();
if ("loaded" === element.readyState || "complete" === element.readyState ) {
element.onload(arguments[0]);
}
};
}-*/;
@@ -303,19 +347,27 @@ public class ResourceLoader {
public void loadStylesheet(final String stylesheetUrl,
final ResourceLoadListener resourceLoadListener) {
final String url = getAbsoluteUrl(stylesheetUrl);
final ResourceLoadEvent event = new ResourceLoadEvent(this, url, false);
if (loadedResources.contains(url)) {
if (resourceLoadListener != null) {
resourceLoadListener.onResourceLoad(new ResourceLoadEvent(this,
url, false));
resourceLoadListener.onLoad(event);
}
return;
}

if (preloadListeners.containsKey(url)) {
// Preload going on, continue when preloaded
preloadResource(url, new ResourceLoadListener() {
public void onResourceLoad(ResourceLoadEvent event) {
public void onLoad(ResourceLoadEvent event) {
loadStylesheet(url, resourceLoadListener);
}

public void onError(ResourceLoadEvent event) {
// Preload failed -> signal error to own listener
if (resourceLoadListener != null) {
resourceLoadListener.onError(event);
}
}
});
return;
}
@@ -327,35 +379,92 @@ public class ResourceLoader {
linkElement.setHref(url);

if (BrowserInfo.get().isSafari()) {
// Safari doesn't fire onload events for link elements
// Safari doesn't fire any events for link elements
// See http://www.phpied.com/when-is-a-stylesheet-really-loaded/
// TODO Stop checking after some timeout
Scheduler.get().scheduleFixedPeriod(new RepeatingCommand() {
private final Duration duration = new Duration();

public boolean execute() {
if (isStyleSheetPresent(url)) {
onResourceLoad(url, false);
int styleSheetLength = getStyleSheetLength(url);
if (getStyleSheetLength(url) > 0) {
fireLoad(event);
return false; // Stop repeating
} else if (styleSheetLength == 0) {
// "Loaded" empty sheet -> most likely 404 error
fireError(event);
return true;
} else if (duration.elapsedMillis() > 60 * 1000) {
fireError(event);
return false;
} else {
return true; // Continue repeating
}
}
}, 10);
} else {
addOnloadHandler(linkElement, url, false);
addOnloadHandler(linkElement, new ResourceLoadListener() {
public void onLoad(ResourceLoadEvent event) {
// Chrome && IE fires load for errors, must check
// stylesheet data
if (BrowserInfo.get().isChrome()
|| BrowserInfo.get().isIE()) {
int styleSheetLength = getStyleSheetLength(url);
// Error if there's an empty stylesheet
if (styleSheetLength == 0) {
fireError(event);
return;
}
}
fireLoad(event);
}

public void onError(ResourceLoadEvent event) {
fireError(event);
}
}, event);
if (BrowserInfo.get().isOpera()) {
// Opera onerror never fired, assume error if no onload in x
// seconds
new Timer() {
@Override
public void run() {
if (!loadedResources.contains(url)) {
fireError(event);
}
}
}.schedule(5 * 1000);
}
}

head.appendChild(linkElement);
}
}

private static native boolean isStyleSheetPresent(String url)
private static native int getStyleSheetLength(String url)
/*-{
for(var i = 0; i < $doc.styleSheets.length; i++) {
if ($doc.styleSheets[i].href === url) {
return true;
var sheet = $doc.styleSheets[i];
try {
var rules = sheet.cssRules
if (rules === undefined) {
rules = sheet.rules;
}
if (rules === null) {
// Style sheet loaded, but can't access length because of XSS -> assume there's something there
return 1;
}
// Return length so we can distinguish 0 (probably 404 error) from normal case.
return rules.length;
} catch (err) {
return 1;
}
}
}
return false;
// No matching stylesheet found -> not yet loaded
return -1;
}-*/;

private static boolean addListener(String url,
@@ -373,26 +482,45 @@ public class ResourceLoader {
}
}

private void onResourceLoad(String resource, boolean preload) {
private void fireError(ResourceLoadEvent event) {
String resource = event.getResourceUrl();

Collection<ResourceLoadListener> listeners;
if (event.isPreload()) {
// Also fire error for load listeners
fireError(new ResourceLoadEvent(this, resource, false));
listeners = preloadListeners.remove(resource);
} else {
listeners = loadListeners.remove(resource);
}
if (listeners != null && !listeners.isEmpty()) {
for (ResourceLoadListener listener : listeners) {
if (listener != null) {
listener.onError(event);
}
}
}
}

private void fireLoad(ResourceLoadEvent event) {
String resource = event.getResourceUrl();
Collection<ResourceLoadListener> listeners;
if (preload) {
if (event.isPreload()) {
preloadedResources.add(resource);
listeners = preloadListeners.remove(resource);
} else {
if (preloadListeners.containsKey(resource)) {
// Also fire preload events for potential listeners
onResourceLoad(resource, true);
fireLoad(new ResourceLoadEvent(this, resource, true));
}
preloadedResources.remove(resource);
loadedResources.add(resource);
listeners = loadListeners.remove(resource);
}
if (listeners != null && !listeners.isEmpty()) {
ResourceLoadEvent event = new ResourceLoadEvent(this, resource,
preload);
for (ResourceLoadListener listener : listeners) {
if (listener != null) {
listener.onResourceLoad(event);
listener.onLoad(event);
}
}
}

Loading…
Cancel
Save