Skip to content

Commit

Permalink
Import chrome extension source code to container-timing (#10)
Browse files Browse the repository at this point in the history
* Add demo chrome extension

Copy contents of the Chrome Extension from jdapena/container-timing-extension
moving them to the main container-timing repository.

* Fix paths for building chrome extension

* Remove package-lock.json

* chrome-extension: updated README.md

Removed references to the old way to use the extension, in an independent
repository.
  • Loading branch information
jdapena authored Dec 20, 2024
1 parent 3159fdb commit bb4ed42
Show file tree
Hide file tree
Showing 16 changed files with 337 additions and 0 deletions.
12 changes: 12 additions & 0 deletions chrome-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# production
/build

# misc
.DS_Store

npm-debug.log*
38 changes: 38 additions & 0 deletions chrome-extension/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"label": "npm: build (Production)",
"detail": "node ./build.mjs --production"
},
{
"type": "npm",
"script": "build:container-timing-polyfill",
"group": {
"kind": "build",
"isDefault": "container-timing/**"
},
"problemMatcher": [],
"label": "npm: build container timing polyfill",
"detail": "npm build:container-timing-polyfill"
},
{
"type": "npm",
"script": "watch",
"group": {
"kind": "build",
"isDefault": false
},
"problemMatcher": [],
"label": "npm: watch",
"detail": "node ./build.mjs --watch"
}
]
}
28 changes: 28 additions & 0 deletions chrome-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# <img src="public/icons/icon_48.png" width="45" align="left"> Container Timing

Chrome extension that sets the attribute `containertiming` to the HTML node of
the loaded documents, and registers an observer that dumps the `container` entries
to `console.log`.


## Prerequisites

You will need a version of node.js on you machine, preferably v20+.

## Install

The steps to setup this project are:

1. `npm i`
1. `npm run install-container-timing-polyfill`
1. `npm run build`.
1. Run Chromium with `--enable-blink-features=ContainerTiming --load-extension=PATH_TO_EXTENSION/build/

## Contribution

Suggestions and pull requests are welcomed!.

---

This folder was bootstrapped with [Chrome Extension CLI](https://github.com/dutiyesh/chrome-extension-cli)

46 changes: 46 additions & 0 deletions chrome-extension/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import esbuild from "esbuild";
import { copy } from "esbuild-plugin-copy";
const watch = process.argv[2] === "--watch";
const production = process.argv[2] === "--production";

const context = await esbuild
.context({
entryPoints: ["./src/content.js", "./src/content.css"],
bundle: true,
outdir: "build",
format: "iife",
sourcemap: !production,
minify: production,
platform: "browser",
target: "ES2022",
plugins: [
copy({
resolveFrom: 'cwd',
assets: [{
from: ["./public/**/*"],
to: ["./build"]
}, {
from: ["../polyfill/polyfill.js"],
to: ["./build"]
},
{
from: ["./src/setVars.js"],
to: ["./build"]
}
],
watch

})
]
})
.catch((e) => {
console.error(e);
process.exit(1);
});

if (watch) {
await context.watch();
} else {
context.rebuild();
context.dispose();
}
16 changes: 16 additions & 0 deletions chrome-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "container-timing",
"version": "0.1.0",
"description": "My Chrome Extension",
"private": true,
"scripts": {
"watch": "node ./build.mjs --watch",
"build": "npm run build:container-timing-polyfill && node ./build.mjs --production",
"build:container-timing-polyfill": "npm run build --prefix ../polyfill",
"install-container-timing-polyfill": "npm i --prefix ../polyfill"
},
"devDependencies": {
"esbuild": "^0.24.0",
"esbuild-plugin-copy": "^2.1.1"
}
}
Binary file added chrome-extension/public/icons/icon_128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added chrome-extension/public/icons/icon_16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added chrome-extension/public/icons/icon_32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added chrome-extension/public/icons/icon_48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions chrome-extension/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"manifest_version": 3,
"name": "Container Timing demo",
"$schema": "https://json.schemastore.org/chrome-manifest.json",
"version": "0.1.0",
"description": "Use Container Timing to provide hints of when first paints happen in a specific web page subtree",
"icons": {
"16": "icons/icon_16.png",
"32": "icons/icon_32.png",
"48": "icons/icon_48.png",
"128": "icons/icon_128.png"
},
"permissions": [
"storage"
],
"options_page": "options.html",
"content_scripts": [
{
"matches": [
"*://*/*"
],
"css": [
"content.css"
],
"js": [
"setVars.js",
"polyfill.js",
"content.js"
],
"run_at": "document_start",
"all_frames": true
}
]
}
17 changes: 17 additions & 0 deletions chrome-extension/public/options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>Container Timing: Options</title>
</head>
<body>
<label>
<input type="text" id="selector" />
selector (defaults to html)
</label>

<div id="status"></div>
<button id="save">Save</button>

<script src="options.js"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions chrome-extension/public/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Saves options to chrome.storage
const saveOptions = () => {
const selector = document.getElementById('selector').value ?? 'html';
console.log(selector);

chrome.storage.sync.set(
{ selector },
() => {
// Update status to let user know options were saved.
const status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(() => {
status.textContent = '';
}, 1000);
}
);
};

// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
const restoreOptions = () => {
chrome.storage.sync.get(
{selector: 'html'},
(items) => {
console.log(items);
// When selector hasn't been set it's not false or empty, but just an empty object (I don't know why)
document.getElementById('selector').value = items.selector;
}
);
};

document.addEventListener('DOMContentLoaded', restoreOptions);
document.getElementById('save').addEventListener('click', saveOptions);
24 changes: 24 additions & 0 deletions chrome-extension/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Welcome to your Chrome Extension

## What's in this directory
* `public/`: HTML files for the override page.
* `manifest.json`: Extension [configuration](https://developer.chrome.com/docs/extensions/mv2/manifest/).
* `src/`: Source files for the override page. See [chrome docs](https://developer.chrome.com/docs/extensions/mv3/override/#manifest) for more details.
* `.gitignore`: Lists files to be ignored in your Git repo.
* `package.json`: Contains project configuration, scripts, and dependencies.

## Test the extension
1. `npm run watch`
2. Open [chrome://extensions](chrome://extensions).
3. Enable developer mode (top right of page).
4. Click "Load unpacked extension" (top left page).
5. Select this directory.

## Bundle the extension
To package the source code into static files for the Chrome webstore, execute `npm run build`.

## Documentation
Refer to [the Chrome developer documentation](https://developer.chrome.com/docs/extensions/mv3/getstarted/) to get started.

## VSCode developer tools
Refer to [github.com/gadhagod/vscode-chrome-extension-developer-tools/blob/master/README.md#commands](https://github.com/gadhagod/vscode-chrome-extension-developer-tools/blob/master/README.md#commands).
9 changes: 9 additions & 0 deletions chrome-extension/src/content.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.overlay {
position:absolute;
background-color: #0000FF64;
}

.boundingRect {
position: absolute;
border: 3px dashed #ff0000a8;
}
79 changes: 79 additions & 0 deletions chrome-extension/src/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Don't re-run operation after selector has been seen
let flag = false;
// Default selector
let selector = 'html';
// Container Timing element we've found
let elm;

// Pull the selector out of the settings
chrome.storage.sync.get(
{ selector: 'html' },
(items) => {
selector = items.selector || 'html';
}
);


const observer = new MutationObserver(() => {
if ((elm = document.querySelector(selector)) && !flag) {
startPerformanceObserve(elm)
}
});

observer.observe(document.documentElement, { attributes: false, childList: true, characterData: false, subtree: true });

function startPerformanceObserve(elm) {
const href = document.location.href
const nativeObserver = new PerformanceObserver((list) => {
console.log("Container timing entries from " + href)
list.getEntries().forEach((list) => {
clearRects();
showRectsOnScreen(list.damagedRects);
showBoundingRect(list.intersectionRect);
clearRects(true)
})
// Now hide the rects so they're not in the way

});
nativeObserver.observe({ type: "container", buffered: true });
console.debug("Registered observer for " + href)

elm.setAttribute("containertiming", "")
console.debug("Added containertiming attribute")
flag = true;
}

function showRectsOnScreen(rects) {
// TODO We may want to batch these DOM updates
rects.forEach((rect) => {
const div = document.createElement('div');
div.classList.add('overlay');
div.style.left = `${rect.left}px`;
div.style.top = `${rect.top}px`;
div.style.width = `${rect.width}px`;
div.style.height = `${rect.height}px`;
document.body.appendChild(div);
});
}

function showBoundingRect(rect) {
const div = document.createElement('div');
div.classList.add('boundingRect');
div.style.left = `${rect.left}px`;
div.style.top = `${rect.top}px`;
div.style.width = `${rect.width}px`;
div.style.height = `${rect.height}px`;
document.body.appendChild(div);
}

function clearRects(withDelay = false) {
if (withDelay) {
return setTimeout(() => {
document.querySelectorAll('.overlay').forEach(elm => elm.remove());
document.querySelectorAll('.boundingRect').forEach(elm => elm.remove());
}, 5000);
}

document.querySelectorAll('.overlay').forEach(elm => elm.remove());
document.querySelectorAll('.boundingRect').forEach(elm => elm.remove());
}
1 change: 1 addition & 0 deletions chrome-extension/src/setVars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
window.ctDebug = true;

0 comments on commit bb4ed42

Please sign in to comment.