Skip to content

Instantly share code, notes, and snippets.

@mems
Last active March 30, 2019 09:57
Show Gist options
  • Select an option

  • Save mems/e4d3f234992b6fbbe871dae1fc2e091f to your computer and use it in GitHub Desktop.

Select an option

Save mems/e4d3f234992b6fbbe871dae1fc2e091f to your computer and use it in GitHub Desktop.

Revisions

  1. mems revised this gist Mar 30, 2019. No changes.
  2. mems created this gist Nov 18, 2018.
    141 changes: 141 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,141 @@
    <!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>