blob: 1269914c0e2fc0fa4fffaf5d93515d2ce0b65504 [file] [edit]
// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
(function() {
const protoSchema = document.getElementById('blastradius-proto').textContent;
const summariesData = JSON.parse(document.getElementById('blastradius-data').textContent);
const tableBody = document.getElementById('summary-table-body');
const headers = document.querySelectorAll('th[data-sort]');
const root = protobuf.parse(protoSchema).root;
const BlastRadiusSummary = root.lookupType("com.android.tools.r8.blastradius.proto.BlastRadiusSummary");
let summaries = [];
let currentSort = {
key: null,
desc: true
};
summariesData.forEach(summaryBase64 => {
try {
const binary = atob(summaryBase64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
const message = BlastRadiusSummary.decode(bytes);
const summary = BlastRadiusSummary.toObject(message, {
defaults: true,
enums: String
});
summaries.push(summary);
} catch (e) {
console.error("Failed to decode summary:", e);
}
});
function renderTable() {
tableBody.innerHTML = '';
summaries.forEach(renderRow);
}
function renderRow(summary) {
// Name row (Full width)
const nameTr = document.createElement('tr');
nameTr.className = 'name-row';
const nameTd = document.createElement('td');
nameTd.colSpan = 6;
const link = document.createElement('a');
link.className = 'report-link';
link.href = summary.link || summary.name.replace('.pb', '.html');
link.textContent = summary.name;
nameTd.appendChild(link);
nameTr.appendChild(nameTd);
tableBody.appendChild(nameTr);
// Data row
const dataTr = document.createElement('tr');
dataTr.className = 'data-row';
const rulesTd = document.createElement('td');
rulesTd.textContent = summary.keepRuleCount || "0";
if (summary.keepRulePackageWideCount && summary.keepRulePackageWideCount !== 0) {
const span = document.createElement('span');
span.className = 'stat-badge warn';
span.textContent = `${summary.keepRulePackageWideCount} package-wide`;
span.title = "Number of keep rules that keep an entire package";
rulesTd.appendChild(document.createElement('br'));
rulesTd.appendChild(span);
}
dataTr.appendChild(rulesTd);
const itemsTd = document.createElement('td');
const totalKept = (summary.keptClasses?.itemCount || 0) +
(summary.keptMethods?.itemCount || 0) +
(summary.keptFields?.itemCount || 0);
const totalItems = (summary.classCount || 0) + (summary.methodCount || 0) + (summary.fieldCount || 0);
itemsTd.textContent = `${totalKept} / ${totalItems}`;
dataTr.appendChild(itemsTd);
const classesTd = document.createElement('td');
classesTd.innerHTML = formatKeepInfo(summary.keptClasses, summary.classCount);
dataTr.appendChild(classesTd);
const methodsTd = document.createElement('td');
methodsTd.innerHTML = formatKeepInfo(summary.keptMethods, summary.methodCount);
dataTr.appendChild(methodsTd);
const fieldsTd = document.createElement('td');
fieldsTd.innerHTML = formatKeepInfo(summary.keptFields, summary.fieldCount);
dataTr.appendChild(fieldsTd);
const problematicTd = document.createElement('td');
if (summary.keepRuleBlastRadius && summary.keepRuleBlastRadius.length > 0) {
const ul = document.createElement('ul');
ul.style.margin = '0';
ul.style.paddingLeft = '0';
ul.style.listStyleType = 'none';
ul.style.fontSize = '0.9rem';
summary.keepRuleBlastRadius.forEach(rule => {
const li = document.createElement('li');
li.style.marginBottom = '4px';
const badge = document.createElement('span');
badge.className = 'stat-badge warn';
badge.textContent = rule.itemCount;
badge.title = "Number of items kept by the rule.";
badge.style.marginRight = '8px';
li.appendChild(badge);
const code = document.createElement('span');
code.style.fontFamily = 'monospace';
code.style.fontSize = '0.85rem';
code.textContent = rule.source;
li.appendChild(code);
ul.appendChild(li);
});
problematicTd.appendChild(ul);
} else {
problematicTd.textContent = "-";
}
dataTr.appendChild(problematicTd);
tableBody.appendChild(dataTr);
}
function formatKeepInfo(keepInfo, total) {
if (!keepInfo) return "0 / " + (total || 0);
const kept = keepInfo.itemCount || 0;
let badges = "";
if (keepInfo.noObfuscationCount > 0) {
badges += `<span class="stat-badge" title="No obfuscation">${keepInfo.noObfuscationCount || 0} OBF</span>`;
}
if (keepInfo.noOptimizationCount > 0) {
badges += `<span class="stat-badge warn" title="No optimization">${keepInfo.noOptimizationCount || 0} OPT</span>`;
}
if (keepInfo.noShrinkingCount > 0) {
badges += `<span class="stat-badge error" title="No shrinking">${keepInfo.noShrinkingCount || 0} SHR</span>`;
}
return `${kept} / ${total || 0}<br>${badges}`;
}
function sortSummaries(key) {
if (currentSort.key === key) {
currentSort.desc = !currentSort.desc;
} else {
currentSort.key = key;
currentSort.desc = true;
}
summaries.sort((a, b) => {
let valA, valB;
switch (key) {
case 'rules':
valA = a.keepRuleCount || 0;
valB = b.keepRuleCount || 0;
break;
case 'items':
valA = (a.keptClasses?.itemCount || 0) + (a.keptMethods?.itemCount || 0) + (a.keptFields?.itemCount || 0);
valB = (b.keptClasses?.itemCount || 0) + (b.keptMethods?.itemCount || 0) + (b.keptFields?.itemCount || 0);
break;
case 'classes':
valA = (a.keptClasses && a.keptClasses.itemCount) || 0;
valB = (b.keptClasses && b.keptClasses.itemCount) || 0;
break;
case 'methods':
valA = (a.keptMethods && a.keptMethods.itemCount) || 0;
valB = (b.keptMethods && b.keptMethods.itemCount) || 0;
break;
case 'fields':
valA = (a.keptFields && a.keptFields.itemCount) || 0;
valB = (b.keptFields && b.keptFields.itemCount) || 0;
break;
default:
return 0;
}
return currentSort.desc ? valB - valA : valA - valB;
});
renderTable();
updateHeaderStyles();
}
function updateHeaderStyles() {
headers.forEach(th => {
const key = th.getAttribute('data-sort');
th.innerHTML = th.textContent.replace(/ [▲▼]/, '');
if (key === currentSort.key) {
th.innerHTML += currentSort.desc ? ' ▼' : ' ▲';
}
});
}
headers.forEach(th => {
th.addEventListener('click', () => {
sortSummaries(th.getAttribute('data-sort'));
});
});
sortSummaries('items');
})();