mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
Embed default resources in Servo applications using a servo-default-resources crate (#43182)
This PR considers the following constraints: - Resources must be available when building servo via a published crates.io package (i.e. no `../../../resources/<file>` file references). - Minimal setup when writing tests (`nextest` spawns each test in its own process, so we don't want to explicitly initialize the resource handler for every `#[test]` fn) - Use local resources when developing locally - Support loading the resources from a proper resource directory if the embedder wishes so, including via a custom mechanism, not necessarily as files (File) Resources that are only accessed from servoshell are out of scope of this PR, since it mainly focusses on unblocking publishing `libservo` to crates.io. Baking the resources into the binary by default simplifies the setup a lot. We already supported that before, but only for testing purposes and explicitly not for production builds. Using [`inventory`](https://crates.io/crates/inventory) adds a simple way for the embedder to replace the default baked in resources, while also keeping the test usage of baked in resources simple. rippy.png is also referenced from image_cache - We simply duplicate it, since the image is small, to avoid adding unnecessarily complex solutions like adding a dedicated crate. Testing: Covered by existing tests. [mach try full](https://github.com/jschwe/servo/actions/runs/23811669469) Fixes: Part of #43145 --------- Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
committed by
GitHub
parent
05946163f2
commit
f4877c190e
223
components/default-resources/resources/about-memory.html
Normal file
223
components/default-resources/resources/about-memory.html
Normal file
@@ -0,0 +1,223 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>about:memory</title>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", start);
|
||||
|
||||
function insertNode(root, report) {
|
||||
let currentNode = root;
|
||||
for (let path of report.path) {
|
||||
if (!currentNode[path]) {
|
||||
currentNode[path] = { total: 0, container: true };
|
||||
}
|
||||
currentNode = currentNode[path];
|
||||
currentNode.total += report.size;
|
||||
}
|
||||
currentNode.size = report.size;
|
||||
currentNode.container = false;
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes < 1024) {
|
||||
return bytes + " B";
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return (bytes / 1024).toFixed(2) + " KiB";
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return (bytes / (1024 * 1024)).toFixed(2) + " MiB";
|
||||
} else {
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GiB";
|
||||
}
|
||||
}
|
||||
|
||||
function formattedSize(size) {
|
||||
// Use enough padding to take into account the "MiB" part.
|
||||
return formatBytes(size).padStart(10);
|
||||
}
|
||||
|
||||
function convertNodeToDOM(node, name = null) {
|
||||
let result = document.createDocumentFragment();
|
||||
|
||||
if (node.container) {
|
||||
let details = document.createElement("details");
|
||||
let summary = document.createElement("summary");
|
||||
summary.textContent = `${formattedSize(node.total)} -- ${name}`;
|
||||
details.append(summary);
|
||||
result.append(details);
|
||||
|
||||
// Add the children in descending order of total size.
|
||||
let entries = Object.entries(node)
|
||||
.filter((item) => {
|
||||
return !["total", "size", "container"].includes(item[0]);
|
||||
})
|
||||
.sort((a, b) => b[1].total - a[1].total)
|
||||
.forEach((item) =>
|
||||
details.append(convertNodeToDOM(item[1], item[0]))
|
||||
);
|
||||
} else {
|
||||
let inner = document.createElement("div");
|
||||
inner.textContent = `${formattedSize(node.size)} -- ${name}`;
|
||||
result.append(inner);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function reportsForProcess(processReport) {
|
||||
let explicitRoot = {};
|
||||
let nonExplicitRoot = {};
|
||||
|
||||
let jemallocHeapReportedSize = 0;
|
||||
let systemHeapReportedSize = 0;
|
||||
|
||||
let jemallocHeapAllocatedSize = NaN;
|
||||
let systemHeapAllocatedSize = NaN;
|
||||
|
||||
// In content processes, get the list of urls.
|
||||
let urls = new Set();
|
||||
|
||||
processReport.reports.forEach((report) => {
|
||||
if (report.path[0].startsWith("url(")) {
|
||||
// This can be a list of urls.
|
||||
let path_urls = report.path[0].slice(4, -1).split(", ");
|
||||
path_urls.forEach((url) => urls.add(url));
|
||||
}
|
||||
|
||||
// Add "explicit" to the start of the path, when appropriate.
|
||||
if (report.kind.startsWith("Explicit")) {
|
||||
report.path.unshift("explicit");
|
||||
}
|
||||
|
||||
// Update the reported fractions of the heaps, when appropriate.
|
||||
if (report.kind == "ExplicitJemallocHeapSize") {
|
||||
jemallocHeapReportedSize += report.size;
|
||||
} else if (report.kind == "ExplicitSystemHeapSize") {
|
||||
systemHeapReportedSize += report.size;
|
||||
}
|
||||
|
||||
// Record total size of the heaps, when we see them.
|
||||
if (report.path.length == 1) {
|
||||
if (report.path[0] == "jemalloc-heap-allocated") {
|
||||
jemallocHeapAllocatedSize = report.size;
|
||||
} else if (report.path[0] == "system-heap-allocated") {
|
||||
systemHeapAllocatedSize = report.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert this report at the proper position.
|
||||
insertNode(
|
||||
report.kind.startsWith("Explicit") ? explicitRoot : nonExplicitRoot,
|
||||
report
|
||||
);
|
||||
});
|
||||
|
||||
// Compute and insert the heap-unclassified values.
|
||||
if (!isNaN(jemallocHeapAllocatedSize)) {
|
||||
insertNode(explicitRoot, {
|
||||
path: ["explicit", "jemalloc-heap-unclassified"],
|
||||
size: jemallocHeapAllocatedSize - jemallocHeapReportedSize,
|
||||
});
|
||||
}
|
||||
if (!isNaN(systemHeapAllocatedSize)) {
|
||||
insertNode(explicitRoot, {
|
||||
path: ["explicit", "system-heap-unclassified"],
|
||||
size: systemHeapAllocatedSize - systemHeapReportedSize,
|
||||
});
|
||||
}
|
||||
|
||||
// Create the DOM structure for each process report:
|
||||
// <div class=process> <h4>...<h4> <pre> ...</pre> </div>
|
||||
let container = document.createElement("div");
|
||||
container.classList.add("process");
|
||||
let reportTitle = document.createElement("h4");
|
||||
reportTitle.textContent = `${
|
||||
processReport.isMainProcess ? "Main Process" : "Content Process"
|
||||
} (pid ${processReport.pid}) ${[...urls.values()].join(", ")}`;
|
||||
|
||||
container.append(reportTitle);
|
||||
let reportNode = document.createElement("pre");
|
||||
reportNode.classList.add("report");
|
||||
container.append(reportNode);
|
||||
|
||||
reportNode.append(convertNodeToDOM(explicitRoot.explicit, "explicit"));
|
||||
|
||||
for (let prop in nonExplicitRoot) {
|
||||
reportNode.append(convertNodeToDOM(nonExplicitRoot[prop], prop));
|
||||
}
|
||||
|
||||
// Make sure we always put the main process first.
|
||||
if (processReport.isMainProcess) {
|
||||
window.reports.prepend(container);
|
||||
} else {
|
||||
window.reports.append(container);
|
||||
}
|
||||
}
|
||||
|
||||
function start() {
|
||||
window.gcButton.onclick = async () => {
|
||||
window.gcButton.disabled = true;
|
||||
navigator.servo.garbageCollectAllContexts();
|
||||
window.gcButton.disabled = false;
|
||||
};
|
||||
|
||||
window.startButton.onclick = async () => {
|
||||
let content = await navigator.servo.reportMemory();
|
||||
let reports = JSON.parse(content);
|
||||
if (reports.error) {
|
||||
console.error(reports.error);
|
||||
return;
|
||||
}
|
||||
window.reports.innerHTML = "";
|
||||
window.reports.classList.remove("hidden");
|
||||
|
||||
if (!Array.isArray(reports)) {
|
||||
console.error("Unexpected memory report format!");
|
||||
return;
|
||||
}
|
||||
|
||||
reports.forEach(reportsForProcess);
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
details,
|
||||
details div {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
summary:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.process {
|
||||
margin: 0.5em;
|
||||
border: 2px solid gray;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
background-color: lightgray;
|
||||
}
|
||||
|
||||
.report {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.report > details {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Memory Reports</h2>
|
||||
<button id="startButton">Measure</button>
|
||||
<button id="gcButton">Force GC</button>
|
||||
<div id="reports" class="hidden"></div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user