Skip to content

Instantly share code, notes, and snippets.

@mems
Last active March 30, 2019 09:57
Show Gist options
  • Save mems/e4d3f234992b6fbbe871dae1fc2e091f to your computer and use it in GitHub Desktop.
Save mems/e4d3f234992b6fbbe871dae1fc2e091f to your computer and use it in GitHub Desktop.
Webpage resources graph (read HAR)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webpage resources graph</title>
<script>
function readFileAsText(file){
const reader = new FileReader();
reader.readAsText(file);
return new Promise((resolve, reject) => {
reader.addEventListener("loadend", event => resolve(reader.result));
reader.addEventListener("error", event => reject(event.detail));
});
}
/**
* Search for the first matching resource by URL or create a new one if not exist (for cases of evaluated scripts)
*/
function getResourceByURL(resources, url, type = "unknown"){
let resource = resources.find(resource => resource.url === url);
if(!resource){
resource = new Resource(url, type);
resources.push(resource);
}
return resource;
}
let readingDataTransferItems = false;
async function readDataTransferItems(items){
readingDataTransferItems = true;
const fileItem = Array.from(items).find(item => item.kind === "file" && (/^application\/(?:[a-z.-]+\+)?json(?=>$|;)/i.test(item.type) || /\.(json|har)$/i.test(item.getAsFile().name)));
if(fileItem){
try{
const data = JSON.parse(await readFileAsText(fileItem.getAsFile())).log;
const allResources = [];
const allDependencies = [];
for(const {id: pageID} of data.pages){
const resources = [];
const dependencies = [];
for(const {request: {url}, _initiator: initiatorDetails, pageref} of data.entries){
if(pageref !== pageID){
continue;
}
const resource = new Resource(url, "");//TODO get type
let initiator;
switch(initiatorDetails.type){
case "other":
dependencies.push(new Dependency(
resource,
resources[0] || null,// root
));
break;
case "script":
let stack = initiatorDetails.stack;
let stackDependencies = new Set();
// From top to bottom of the stack
do{
for(const {url, scriptId, lineNumber, columnNumber} of stack.callFrames){
const dependency = getResourceByURL(resources, url || `eval:///${scriptId}`, "script");// generate an URL based on script ID for evaluated scripts (Chrome use sourceURL if provided)
// Skip already registered dependencies
if(stackDependencies.has(dependency)){
continue;
}
dependencies.push(new Dependency(resource, dependency, lineNumber, columnNumber));
stackDependencies.add(dependency);
}
}while(stack = stack.parent)
break;
case "parser":
// could be parser HTML (images)
// or CSS background or font (an element use it)
dependencies.push(new Dependency(
resource,
getResourceByURL(resources, initiatorDetails.url),
initiatorDetails.lineNumber,
initiatorDetails.columnNumber
));
break;
default:
console.warn(`Unknown initiator type ${initiatorDetails.type}`);
}
resources.push(resource);
}
allResources.push(...resources);
allDependencies.push(...dependencies);
}
console.log(allResources);
console.log(allDependencies);
}catch(error){
console.log(error);
}
}
readingDataTransferItems = false;
}
class Dependency{
constructor(dependency, resource, line = 0, column = 0){
this.dependency = dependency;
this.resource = resource;
this.line = line;
this.column = column;
}
}
class Resource{
constructor(url, type = "unknown"){
this.url = url;
this.type = type;
}
}
document.addEventListener("dragover", event => {
// prevent default to allow drop
event.preventDefault();
});
document.addEventListener("drop", event => {
event.preventDefault();
if(readingDataTransferItems){
return;
}
readDataTransferItems(event.dataTransfer.items);
});
</script>
</head>
<body>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment