Detecting Load of <Link> Resources

Get loaded resource files list from android webview

You can get the list of resources the WebView is trying to load by overriding WebViewClient.shouldInterceptRequest like so:

class MyWebViewClient extends WebViewClient {

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
android.util.Log.i("MyWebViewClient", "attempting to load resource: " + url);
return null;
}

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
android.util.Log.i("MyWebViewClient", "attempting to load resource: " + request.getUrl());
return null;
}

}

Remember that shouldInterceptRequest is called on a background thread and so you need synchronize access to any shared data structures.

There is no Java API to find out whether the WebView has successfully loaded a given resource though.

Detect and log when external JavaScript or CSS resources fail to load

If your app/page is dependent on JS, you can load the content with JS, I know it's confusing. When loading these with JS, you can have callbacks that allow you to only have the functionality of the loaded content and not have to worry about what you didn't load.

var script = document.createElement("script");
script.type = "text/javascript";
script.src = 'http://domain.com/somefile.js';
script.onload = CallBackForAfterFileLoaded;
document.body.appendChild(script);
function CallBackForAfterFileLoaded (e) {
//Do your magic here...
}

I usually have this be a bit more complex by having arrays of JS and files that are dependent on each other, and if they don't load then I have an error state.

I forgot to mention, obviously I am just showing how to create a JS tag, you would have to create your own method for the other types of files you want to load.

Hope maybe that helps, cheers

How to load a resource file from url?

So if I'm understanding you, you need an input stream that reads your XML from a web server. That would look like this:

InputStream is = new URL("http://example.com/folder/configuration.xml").openConnection().getInputStream();

However there's much more to this, you can't do this on the main thread (see NetworkOnMainThreadException) and you have to be prepared that there is no internet connection or that it downloads slowly. So I don't know what you use this configuration for but for example you may have to have defaults and put a "please wait" message on the screen.

Detect resources loading in Angular

After a long long exchange of comments, I finally understood your problem, Romain, and here's what I came up with.

My solution involves adding a directive and a service. The directive will be attached to all <img> tags, and will subscribe to their load events, and the service will coordinate all the load events firing and maintain a running list of images that are still being loaded.

Here's the directive:

@Directive({
selector: 'img'
})
export class MyImgDirective {
constructor(private el: ElementRef, private imageService: ImageService) {
imageService.imageLoading(el.nativeElement);
}

@HostListener('load')
onLoad() {
this.imageService.imageLoadedOrError(this.el.nativeElement);
}

@HostListener('error')
onError() {
this.imageService.imageLoadedOrError(this.el.nativeElement);
}
}

Here's the service:

@Injectable({
providedIn: 'root'
})
export class ImageService {
private _imagesLoading = new Subject<number>();
private images: Map<HTMLElement, boolean> = new Map();
private imagesLoading = 0;

imagesLoading$ = this._imagesLoading.asObservable();

imageLoading(img: HTMLElement) {
if (!this.images.has(img) || this.images.get(img)) {
this.images.set(img, false);
this.imagesLoading++;
console.log('images loading', this.imagesLoading);
this._imagesLoading.next(this.imagesLoading);
}
}

imageLoadedOrError(img: HTMLElement) {
if (this.images.has(img) && !this.images.get(img)) {
this.images.set(img, true);
this.imagesLoading--;
console.log('images loading', this.imagesLoading);
this._imagesLoading.next(this.imagesLoading);
}
}
}

And here's how I would use it in your parallax directive:

  constructor(private imageService: ImageService) {}

ngOnInit() {
this.sub = imageService.imagesLoading$.pipe(filter(r => r === 0)).subscribe(_ => {
this.initParallax()
});
}

ngOnDestroy() {
this.sub.unsubscribe();
}

How does this work? The image directive gets attached to all images regardless of how deep they are in your component tree, registers the image with the service, and then tracks the loading progress by listening to the load event. Every time a new directive is created it means a new image is created, so a counter is incremented, and every time load fires, it means an image has finished loading, so the counter is decremented. We emit this counter in an observable, and so we can detect when all images are ready by waiting for the observable to emit the value 0.

Stackblitz demo

EDIT Added error handling in case an image points to a broken link

URL to load resources from the classpath in Java

Intro and basic Implementation

First up, you're going to need at least a URLStreamHandler. This will actually open the connection to a given URL. Notice that this is simply called Handler; this allows you to specify java -Djava.protocol.handler.pkgs=org.my.protocols and it will automatically be picked up, using the "simple" package name as the supported protocol (in this case "classpath").

Usage

new URL("classpath:org/my/package/resource.extension").openConnection();

Code

package org.my.protocols.classpath;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

/** A {@link URLStreamHandler} that handles resources on the classpath. */
public class Handler extends URLStreamHandler {
/** The classloader to find resources from. */
private final ClassLoader classLoader;

public Handler() {
this.classLoader = getClass().getClassLoader();
}

public Handler(ClassLoader classLoader) {
this.classLoader = classLoader;
}

@Override
protected URLConnection openConnection(URL u) throws IOException {
final URL resourceUrl = classLoader.getResource(u.getPath());
return resourceUrl.openConnection();
}
}

Launch issues

If you're anything like me, you don't want to rely on a property being set in the launch to get you somewhere (in my case, I like to keep my options open like Java WebStart - which is why I need all this).

Workarounds/Enhancements

Manual code Handler specification

If you control the code, you can do

new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))

and this will use your handler to open the connection.

But again, this is less than satisfactory, as you don't need a URL to do this - you want to do this because some lib you can't (or don't want to) control wants urls...

JVM Handler registration

The ultimate option is to register a URLStreamHandlerFactory that will handle all urls across the jvm:

package my.org.url;

import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.HashMap;
import java.util.Map;

class ConfigurableStreamHandlerFactory implements URLStreamHandlerFactory {
private final Map<String, URLStreamHandler> protocolHandlers;

public ConfigurableStreamHandlerFactory(String protocol, URLStreamHandler urlHandler) {
protocolHandlers = new HashMap<String, URLStreamHandler>();
addHandler(protocol, urlHandler);
}

public void addHandler(String protocol, URLStreamHandler urlHandler) {
protocolHandlers.put(protocol, urlHandler);
}

public URLStreamHandler createURLStreamHandler(String protocol) {
return protocolHandlers.get(protocol);
}
}

To register the handler, call URL.setURLStreamHandlerFactory() with your configured factory. Then do new URL("classpath:org/my/package/resource.extension") like the first example and away you go.

JVM Handler Registration Issue

Note that this method may only be called once per JVM, and note well that Tomcat will use this method to register a JNDI handler (AFAIK). Try Jetty (I will be); at worst, you can use the method first and then it has to work around you!

License

I release this to the public domain, and ask that if you wish to modify that you start a OSS project somewhere and comment here with the details. A better implementation would be to have a URLStreamHandlerFactory that uses ThreadLocals to store URLStreamHandlers for each Thread.currentThread().getContextClassLoader(). I'll even give you my modifications and test classes.

How to get not loaded resources?

Using the classes Resources and Errors above, I solved the problem by taking all the resources of the page and then removing the working ones. The result is an array of not loaded resources.

This is the code:

     var res = performance.getEntriesByType("resource");
res.forEach(function add(item){
resources.push(new Resource(item.name,
item.initiatorType,
item.startTime,
item.responseEnd,
item.duration));
})

var flag = true;
var imgs = document.getElementsByTagName('img');
for(var i=0;i<imgs.length;i++){
for(var j=0;j<resources.length;j++){
if(imgs[i].src == resources[j].name){
flag = false;
}
}
if(flag && imgs[i].src){
errors.push(new Errors("RES NOT FOUND",imgs[i].src,null));
}
flag = true;
}

var scripts = document.getElementsByTagName('script');
for(var i=0;i<scripts.length;i++){
for(var j=0;j<resources.length;j++){
if(scripts[i].src == resources[j].name){
flag = false;
}
}
if(flag && scripts[i].src){
errors.push(new Errors("RES NOT FOUND",scripts[i].src,null));
}
flag = true;
}

var links = document.getElementsByTagName('link');
for(var i=0;i<links.length;i++){
for(var j=0;j<resources.length;j++){
if(links[i].src == resources[j].name){
flag = false;
}
}
if(flag && links[i].href){
errors.push(new Errors("RES NOT FOUND",links[i].href,null));
}
flag = true;
}

console.log(resources);
console.log(errors);


Related Topics



Leave a reply



Submit