LibWeb+LibWebView+WebContent: Remove the built-in Inspector

This commit is contained in:
Timothy Flynn 2025-03-14 16:22:16 -04:00 committed by Alexander Kalenik
parent 1c696e7893
commit 810d04b3f4
Notes: github-actions[bot] 2025-03-15 18:10:57 +00:00
34 changed files with 24 additions and 3082 deletions

View file

@ -1,383 +0,0 @@
:root {
--code-font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
@media (prefers-color-scheme: dark) {
:root {
--background: rgb(23, 23, 23);
--separator: dimgray;
--separator-accent: rgb(57, 57, 57);
--tab-controls: rgb(57, 57, 57);
--tab-button-background: rgb(43, 42, 50);
--text-color: white;
--tab-button-active-background: rgb(22 100 219);
--tab-button-active-color: var(--text-color);
--tab-button-border: rgb(96, 96, 96);
--hoverable-background: #31383e;
--selected-border: cyan;
--console-prompt-color: cyan;
--console-message-color: lightskyblue;
--console-warning-color: orange;
--console-input-color: rgb(57, 57, 57);
--console-input-focus-color: cyan;
--console-table-row-odd: rgb(57, 57, 57);
--console-table-row-hover: rgb(80, 79, 79);
--console-table-border: gray;
--property-table-head: rgb(57, 57, 57);
--property-table-row: rgb(45, 45, 45);
}
}
@media (prefers-color-scheme: light) {
:root {
--background: white;
--separator: lightgray;
--separator-accent: white;
--tab-controls: rgb(229, 229, 229);
--tab-button-background: white;
--text-color: black;
--tab-button-active: rgb(22 100 219);
--tab-button-border: rgb(242, 242, 242);
--hoverable-background: rgb(236, 236, 236);
--selected-border: blue;
--console-prompt-color: blue;
--console-message-color: blue;
--console-warning-color: darkorange;
--console-input-color: rgb(229, 229, 229);
--console-input-focus-color: blue;
--console-table-row-odd: rgb(229, 229, 229);
--console-table-row-hover: rgb(199, 198, 198);
--console-table-border: gray;
--property-table-head: rgb(229, 229, 229);
--property-table-row: rgb(240, 240, 240);
}
}
html {
background-color: var(--background);
}
body {
font-family: system-ui, sans-serif;
font-size: 10pt;
margin: 0;
}
.split-view {
width: 100vw;
height: 100vh;
overflow: hidden;
}
.split-view-container {
max-height: calc(100% - 40px);
min-height: 40px;
}
.split-view-separator {
background-color: var(--separator);
width: 100%;
height: 5px;
display: flex;
justify-content: center;
cursor: row-resize;
user-select: none;
z-index: 100;
}
.split-view-separator circle {
fill: var(--separator-accent);
}
.tab-controls-container {
background-color: var(--tab-controls);
width: 100%;
padding: 4px;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 100;
}
.tab-controls {
overflow: hidden;
flex-shrink: 0;
}
.tab-controls button {
color: var(--text-color);
background-color: var(--tab-button-background);
font-size: 12px;
font-weight: 600;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 4px 8px;
}
.tab-controls :first-child {
border-radius: 0.5rem 0 0 0.5rem;
}
.tab-controls :last-child {
border-radius: 0 0.5rem 0.5rem 0;
}
.tab-controls button.active {
background-color: var(--tab-button-active-background);
color: var(--tab-button-active-color);
}
.tab-controls button + button {
border-left: 1px solid var(--tab-button-border);
}
.global-controls {
margin: 0 8px 0 8px;
}
.global-controls button {
width: 24px;
height: 24px;
border: none;
outline: none;
cursor: pointer;
}
#export-inspector-button {
background-image: url("resource://icons/16x16/download.png");
background-position: center;
background-repeat: no-repeat;
background-color: var(--tab-controls);
}
#export-inspector-button:hover {
background-color: var(--tab-button-background);
}
.tab-content {
height: calc(100% - 40px);
display: none;
padding: 8px 0px 0px 4px;
overflow: auto scroll;
}
.tab-header {
position: sticky;
top: 2px; /* FIXME: Remove this when https://github.com/LadybirdBrowser/ladybird/issues/1245 is resolved. */
left: 0;
right: 0;
background-color: var(--tab-controls);
border-top: 2px solid var(--background);
display: flex;
padding: 0.5em;
}
details > :not(:first-child) {
display: list-item;
list-style: none inside;
margin-left: 1em;
}
.hoverable {
display: block;
padding: 1px;
}
.dom-editor {
width: fit-content;
outline: none;
}
.hoverable:hover {
background-color: var(--hoverable-background);
}
.selected {
border: 1px dashed var(--selected-border);
padding: 0;
}
#console {
overflow: unset;
}
.console {
font-family: var(--code-font-family);
width: 100%;
height: 100%;
}
.console-output {
height: calc(100% - 32px);
overflow: auto scroll;
}
.console-input {
background-color: var(--console-input-color);
width: 100%;
height: 24px;
padding: 4px;
position: absolute;
bottom: 0;
left: 0;
}
.console-input input {
width: calc(100% - 60px);
}
.console-input input:focus {
outline: 1px dashed var(--console-input-focus-color);
}
.console-prompt {
color: var(--console-prompt-color);
}
.console-message {
color: var(--console-message-color);
}
.console-warning {
color: var(--console-warning-color);
}
.property-filter {
display: block;
width: calc(100% - 10px);
height: 20px;
padding: 4px;
position: sticky;
top: 0;
border: 1px solid var(--console-table-border);
background-color: var(--console-input-color);
color: var(--text-color);
}
.property-table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
.property-table th {
background-color: var(--property-table-head);
position: sticky;
top: 30px;
}
.property-table th,
.property-table td {
padding: 4px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
}
.hidden-row {
display: none;
}
.property-table tr:nth-child(even of :not(.hidden-row)) {
background-color: var(--property-table-row);
}
#fonts {
display: flex;
flex-direction: row;
}
#fonts-list {
display: flex;
flex-direction: column;
}
#fonts-list .font {
display: flex;
flex-direction: row;
}
#fonts-list .font div {
padding: 4px;
}
#fonts-list .font div.name {
background-color: var(--property-table-head);
font-weight: bold;
padding-left: 10px;
padding-right: 10px;
}
.console-log-table {
width: 100%;
padding: 0 10px;
box-sizing: border-box;
}
.console-log-table table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
border: 1px solid var(--console-table-border);
}
.console-log-table thead {
border-bottom: 1px solid var(--console-table-border);
}
.console-log-table th {
position: sticky;
top: 0px;
border: 1px solid var(--console-table-border);
}
.console-log-table td {
border-left: 1px solid var(--console-table-border);
border-right: 1px solid var(--console-table-border);
}
.console-log-table tbody tr:nth-of-type(2n + 1) {
background-color: var(--console-table-row-odd);
}
.console-log-table tbody tr:hover {
background-color: var(--console-table-row-hover);
}
.console-log-table th,
.console-log-table td {
padding: 4px;
text-align: left;
}
#style-sheet-picker {
flex-grow: 1;
}
#style-sheet-source {
font-size: 10pt;
font-family: var(--code-font-family);
white-space: pre;
padding: 0.5em;
}

View file

@ -1,114 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="color-scheme" content="dark light">
<title>Inspector</title>
<style type="text/css">@INSPECTOR_STYLE@</style>
<link href="@INSPECTOR_CSS@" rel="stylesheet" />
</head>
<body>
<div class="split-view">
<div id="inspector-top" class="split-view-container" style="height: 60%">
<div class="tab-controls-container">
<div class="global-controls"></div>
<div class="tab-controls">
<button id="dom-tree-button" onclick="selectTopTab(this, 'dom-tree')">DOM Tree</button>
<button id="accessibility-tree-button" onclick="selectTopTab(this, 'accessibility-tree')">Accessibility Tree</button>
<button id="storage-button" onclick="selectTopTab(this, 'storage')">Storage</button>
<button id="style-sheets-button" onclick="selectTopTab(this, 'style-sheets')">Style Sheets</button>
</div>
<div class="global-controls">
<button id="export-inspector-button" title="Export the Inspector to an HTML file" onclick="inspector.exportInspector()"></button>
</div>
</div>
<div id="dom-tree" class="tab-content html"></div>
<div id="accessibility-tree" class="tab-content"></div>
<div id="storage" class="tab-content" style="padding: 0">
<div class="tab-header">
<select id="storage-picker">
<option value="cookies" selected>Cookies</option>
</select>
</div>
<div id="cookie-storage">
<table class="property-table">
<thead>
<tr>
<th style="width: 10%">Name</th>
<th style="width: 15%">Value</th>
<th style="width: 10%">Domain</th>
<th style="width: 5%">Path</th>
<th style="width: 20%">Created</th>
<th style="width: 20%">Last Accessed</th>
<th style="width: 20%">Expires</th>
</tr>
</thead>
<tbody id="cookie-table">
</tbody>
</table>
</div>
</div>
<div id="style-sheets" class="tab-content" style="padding: 0">
<div class="tab-header">
<select id="style-sheet-picker" disabled onchange="loadStyleSheet()">
<option value="." selected>No style sheets found</option>
</select>
</div>
<div id="style-sheet-source"></div>
</div>
</div>
<div id="inspector-separator" class="split-view-separator">
<svg viewBox="0 0 16 5" xmlns="http://www.w3.org/2000/svg">
<circle cx="2" cy="2.5" r="2" />
<circle cx="8" cy="2.5" r="2" />
<circle cx="14" cy="2.5" r="2" />
</svg>
</div>
<div id="inspector-bottom" class="split-view-container" style="height: calc(40% - 5px)">
<div class="tab-controls-container">
<div class="global-controls"></div>
<div class="tab-controls">
<button id="console-button" onclick="selectBottomTab(this, 'console')">Console</button>
<button id="computed-style-button" onclick="selectBottomTab(this, 'computed-style')">Computed Style</button>
<button id="resolved-style-button" onclick="selectBottomTab(this, 'resolved-style')">Resolved Style</button>
<button id="custom-properties-button" onclick="selectBottomTab(this, 'custom-properties')">Custom Properties</button>
<button id="font-button" onclick="selectBottomTab(this, 'fonts')">Fonts</button>
</div>
<div class="global-controls"></div>
</div>
<div id="console" class="tab-content">
<div class="console">
<div id="console-output" class="console-output"></div>
<div class="console-input">
<label for="console-input" class="console-prompt">&gt;&gt;</label>
<input id="console-input" type="text" placeholder="Enter statement to execute">
<button id="console-clear" title="Clear the console output" onclick="inspector.clearConsoleOutput()">X</button>
</div>
</div>
</div>
@COMPUTED_STYLE@
@RESOVLED_STYLE@
@CUSTOM_PROPERTIES@
<div id="fonts" class="tab-content">
<div id="fonts-list"></div>
</div>
</div>
</div>
<script type="text/javascript" src="@INSPECTOR_JS@"></script>
</body>
</html>

View file

@ -1,866 +0,0 @@
let selectedTopTab = null;
let selectedTopTabButton = null;
let selectedBottomTab = null;
let selectedBottomTabButton = null;
let selectedDOMNode = null;
let pendingEditDOMNode = null;
let visibleDOMNodes = [];
let consoleGroupStack = [];
let consoleGroupNextID = 0;
let consoleHistory = [];
let consoleHistoryIndex = 0;
const decodeBase64 = encoded => {
return new TextDecoder().decode(Uint8Array.from(atob(encoded), c => c.charCodeAt(0)));
};
const beginSplitViewDrag = () => {
let inspectorTop = document.getElementById("inspector-top");
let inspectorBottom = document.getElementById("inspector-bottom");
let inspectorSeparator = document.getElementById("inspector-separator");
const windowHeight = window.innerHeight;
const separatorHeight = inspectorSeparator.clientHeight;
const updateSplitView = event => {
let position = Math.min(event.clientY, windowHeight - separatorHeight);
position = Math.max(position, 0);
inspectorTop.style.height = `${position}px`;
inspectorBottom.style.height = `${windowHeight - position - separatorHeight}px`;
event.preventDefault();
};
const endSplitViewDrag = () => {
document.removeEventListener("mousemove", updateSplitView);
document.removeEventListener("mouseup", endSplitViewDrag);
document.body.style.cursor = "";
};
document.addEventListener("mousemove", updateSplitView);
document.addEventListener("mouseup", endSplitViewDrag);
document.body.style.cursor = "row-resize";
};
const selectTab = (tabButton, tabID, selectedTab, selectedTabButton) => {
let tab = document.getElementById(tabID);
if (selectedTab === tab) {
return selectedTab;
}
if (selectedTab !== null) {
selectedTab.style.display = "none";
selectedTabButton.classList.remove("active");
}
tab.style.display = "block";
tabButton.classList.add("active");
// Apply any filtering if we have it
let filterInput = tab.querySelector(".property-filter");
let propertyTable = tab.querySelector(".property-table");
if (filterInput && propertyTable) {
filterInput.value = inspector.propertyFilterText || "";
filterInput.dispatchEvent(new InputEvent("input"));
}
return tab;
};
const selectTopTab = (tabButton, tabID) => {
selectedTopTab = selectTab(tabButton, tabID, selectedTopTab, selectedTopTabButton);
selectedTopTabButton = tabButton;
};
const selectBottomTab = (tabButton, tabID) => {
selectedBottomTab = selectTab(tabButton, tabID, selectedBottomTab, selectedBottomTabButton);
selectedBottomTabButton = tabButton;
};
let initialTopTabButton = document.getElementById("dom-tree-button");
selectTopTab(initialTopTabButton, "dom-tree");
let initialBottomTabButton = document.getElementById("console-button");
selectBottomTab(initialBottomTabButton, "console");
const scrollToElement = element => {
// Include an offset to prevent the element being placed behind the fixed `tab-controls` header.
const offset = 45;
let position = element.getBoundingClientRect().top;
position += window.pageYOffset - offset;
window.scrollTo(0, position);
};
inspector.exportInspector = () => {
const html = `<!DOCTYPE ${document.doctype.name}>\n${document.documentElement.outerHTML}`;
inspector.exportInspectorHTML(html);
};
inspector.reset = () => {
let domTree = document.getElementById("dom-tree");
domTree.innerHTML = "";
let accessibilityTree = document.getElementById("accessibility-tree");
accessibilityTree.innerHTML = "";
let cookieTable = document.getElementById("cookie-table");
cookieTable.innerHTML = "";
let styleSheetPicker = document.getElementById("style-sheet-picker");
styleSheetPicker.replaceChildren();
let styleSheetSource = document.getElementById("style-sheet-source");
styleSheetSource.innerHTML = "";
let fontsList = document.getElementById("fonts-list");
fontsList.innerHTML = "";
selectedDOMNode = null;
pendingEditDOMNode = null;
inspector.clearConsoleOutput();
};
inspector.loadDOMTree = tree => {
let domTree = document.getElementById("dom-tree");
domTree.innerHTML = decodeBase64(tree);
let domNodes = domTree.querySelectorAll(".hoverable");
for (let domNode of domNodes) {
domNode.addEventListener("click", event => {
inspectDOMNode(domNode);
event.preventDefault();
});
}
domNodes = domTree.querySelectorAll(".editable");
for (let domNode of domNodes) {
domNode.addEventListener("dblclick", event => {
const type = domNode.dataset.nodeType;
const text = event.target.innerText;
if (type === "attribute" && event.target.classList.contains("attribute-value")) {
text = text.substring(1, text.length - 1);
}
editDOMNode(domNode, text);
event.preventDefault();
});
}
domNodes = domTree.querySelectorAll("details");
for (let domNode of domNodes) {
domNode.addEventListener("toggle", event => {
updateVisibleDOMNodes();
});
}
updateVisibleDOMNodes();
};
inspector.loadAccessibilityTree = tree => {
let accessibilityTree = document.getElementById("accessibility-tree");
accessibilityTree.innerHTML = decodeBase64(tree);
};
inspector.inspectDOMNodeID = nodeID => {
let domNodes = document.querySelectorAll(`[data-id="${nodeID}"]`);
if (domNodes.length !== 1) {
return;
}
for (let domNode = domNodes[0]; domNode; domNode = domNode.parentNode) {
if (domNode.tagName === "DETAILS") {
domNode.setAttribute("open", "");
}
}
inspectDOMNode(domNodes[0]);
scrollToElement(selectedDOMNode);
};
inspector.clearInspectedDOMNode = () => {
if (selectedDOMNode !== null) {
selectedDOMNode.classList.remove("selected");
selectedDOMNode = null;
}
};
inspector.editDOMNodeID = nodeID => {
if (pendingEditDOMNode === null) {
return;
}
inspector.inspectDOMNodeID(nodeID);
editDOMNode(pendingEditDOMNode);
pendingEditDOMNode = null;
};
inspector.addAttributeToDOMNodeID = nodeID => {
if (pendingEditDOMNode === null) {
return;
}
inspector.inspectDOMNodeID(nodeID);
addAttributeToDOMNode(pendingEditDOMNode);
pendingEditDOMNode = null;
};
inspector.setCookies = cookies => {
let oldTable = document.getElementById("cookie-table");
let newTable = document.createElement("tbody");
newTable.setAttribute("id", oldTable.id);
const addColumn = (row, value) => {
let column = row.insertCell();
column.innerText = value;
column.title = value;
};
cookies
.sort((lhs, rhs) => lhs.name.localeCompare(rhs.name))
.forEach(cookie => {
let row = newTable.insertRow();
addColumn(row, cookie.name);
addColumn(row, cookie.value);
addColumn(row, cookie.domain);
addColumn(row, cookie.path);
addColumn(row, new Date(cookie.creationTime).toLocaleString());
addColumn(row, new Date(cookie.lastAccessTime).toLocaleString());
addColumn(row, new Date(cookie.expiryTime).toLocaleString());
row.addEventListener("contextmenu", event => {
inspector.requestCookieContextMenu(cookie.index, event.clientX, event.clientY);
event.preventDefault();
});
});
oldTable.parentNode.replaceChild(newTable, oldTable);
};
inspector.setStyleSheets = styleSheets => {
const styleSheetPicker = document.getElementById("style-sheet-picker");
const styleSheetSource = document.getElementById("style-sheet-source");
styleSheetPicker.replaceChildren();
styleSheetSource.innerHTML = "";
function addOption(styleSheet, text) {
const option = document.createElement("option");
option.innerText = text;
if (styleSheet.type) {
option.dataset["type"] = styleSheet.type;
}
if (styleSheet.domNodeId) {
option.dataset["domNodeId"] = styleSheet.domNodeId;
}
if (styleSheet.url) {
option.dataset["url"] = styleSheet.url;
}
styleSheetPicker.add(option);
}
if (styleSheets.length > 0) {
let styleElementIndex = 1;
for (const styleSheet of styleSheets) {
switch (styleSheet.type) {
case "StyleElement":
addOption(styleSheet, `Style element #${styleElementIndex++}`);
break;
case "LinkElement":
addOption(styleSheet, styleSheet.url);
break;
case "ImportRule":
addOption(styleSheet, styleSheet.url);
break;
case "UserAgent":
addOption(styleSheet, `User agent: ${styleSheet.url}`);
break;
case "UserStyle":
addOption(styleSheet, "User style");
break;
}
}
styleSheetPicker.disabled = false;
} else {
addOption({}, "No style sheets found");
styleSheetPicker.disabled = true;
}
styleSheetPicker.selectedIndex = 0;
if (!styleSheetPicker.disabled) {
loadStyleSheet();
}
};
const loadStyleSheet = () => {
const styleSheetPicker = document.getElementById("style-sheet-picker");
const styleSheetSource = document.getElementById("style-sheet-source");
const selectedOption = styleSheetPicker.selectedOptions[0];
styleSheetSource.innerHTML = "Loading...";
inspector.requestStyleSheetSource(
selectedOption.dataset["type"],
selectedOption.dataset["domNodeId"],
selectedOption.dataset["url"]
);
};
inspector.setStyleSheetSource = (identifier, sourceBase64) => {
const styleSheetPicker = document.getElementById("style-sheet-picker");
const styleSheetSource = document.getElementById("style-sheet-source");
const selectedOption = styleSheetPicker.selectedOptions[0];
// Make sure this is the source for the currently-selected style sheet.
// NOTE: These are != not !== intentionally.
if (
identifier.type != selectedOption.dataset["type"] ||
identifier.domNodeId != selectedOption.dataset["domNodeId"] ||
identifier.url != selectedOption.dataset["url"]
) {
console.log(
JSON.stringify(identifier),
"doesn't match",
JSON.stringify(selectedOption.dataset)
);
return;
}
styleSheetSource.innerHTML = decodeBase64(sourceBase64);
};
const applyPropertyFilter = (row, searchText) => {
let matches = false;
if (searchText) {
for (let cell of row.cells) {
if (cell.textContent.toLowerCase().includes(searchText)) {
matches = true;
break;
}
}
} else {
// Empty searchText matches everything, so skip the checks.
matches = true;
}
if (matches) {
row.classList.remove("hidden-row");
} else {
row.classList.add("hidden-row");
}
};
const setupPropertyFilter = inputId => {
const filterInput = document.getElementById(`${inputId}-filter`);
filterInput.addEventListener("input", event => {
inspector.propertyFilterText = event.target.value.toLowerCase();
const tbody = document.getElementById(`${inputId}-table`);
const rows = tbody.getElementsByTagName("tr");
for (let row of rows) {
applyPropertyFilter(row, inspector.propertyFilterText);
}
});
};
inspector.createPropertyTables = (computedStyle, resolvedStyle, customProperties) => {
const createPropertyTable = (tableID, properties) => {
let oldTable = document.getElementById(tableID);
let newTable = document.createElement("tbody");
newTable.setAttribute("id", tableID);
Object.keys(properties)
.sort((a, b) => {
let baseResult = a.localeCompare(b);
// Manually move vendor-prefixed items after non-prefixed ones.
if (a[0] === "-") {
if (b[0] === "-") {
return baseResult;
}
return 1;
}
if (b[0] === "-") {
return -1;
}
return baseResult;
})
.forEach(name => {
let row = newTable.insertRow();
let nameColumn = row.insertCell();
nameColumn.innerText = name;
let valueColumn = row.insertCell();
valueColumn.innerText = properties[name];
if (inspector.propertyFilterText) {
applyPropertyFilter(row, inspector.propertyFilterText);
}
});
oldTable.parentNode.replaceChild(newTable, oldTable);
};
createPropertyTable("computed-style-table", JSON.parse(computedStyle));
createPropertyTable("resolved-style-table", JSON.parse(resolvedStyle));
createPropertyTable("custom-properties-table", JSON.parse(customProperties));
};
inspector.createFontList = fonts => {
let fontsJSON = JSON.parse(fonts);
if (!Array.isArray(fontsJSON)) return;
const listId = "fonts-list";
let oldList = document.getElementById(listId);
let newList = document.createElement("div");
newList.setAttribute("id", listId);
const createFontEntry = (listContainer, font) => {
let fontEntry = document.createElement("div");
fontEntry.classList.add("font");
let fontName = document.createElement("div");
fontName.classList.add("name");
fontName.innerText = font.name;
fontEntry.appendChild(fontName);
let fontSize = document.createElement("div");
fontSize.classList.add("size");
fontSize.innerText = font.size;
fontEntry.appendChild(fontSize);
let fontWeight = document.createElement("div");
fontWeight.classList.add("Weight");
fontWeight.innerText = font.weight;
fontEntry.appendChild(fontWeight);
let fontVariant = document.createElement("div");
fontVariant.classList.add("Variant");
fontVariant.innerText = font.variant;
fontEntry.appendChild(fontVariant);
listContainer.appendChild(fontEntry);
};
for (let font of fontsJSON) createFontEntry(newList, font);
oldList.parentNode.replaceChild(newList, oldList);
};
const inspectDOMNode = domNode => {
if (selectedDOMNode === domNode) {
return;
}
inspector.clearInspectedDOMNode();
domNode.classList.add("selected");
selectedDOMNode = domNode;
inspector.inspectDOMNode(domNode.dataset.id, domNode.dataset.pseudoElement);
};
const createDOMEditor = (onHandleChange, onCancelChange) => {
selectedDOMNode.classList.remove("selected");
let input = document.createElement("input");
input.classList.add("dom-editor");
input.classList.add("selected");
const handleChange = () => {
input.removeEventListener("change", handleChange);
input.removeEventListener("blur", cancelChange);
input.removeEventListener("keydown", handleInput);
try {
onHandleChange(input.value);
} catch {
cancelChange();
}
};
const cancelChange = () => {
input.removeEventListener("change", handleChange);
input.removeEventListener("blur", cancelChange);
input.removeEventListener("keydown", handleInput);
selectedDOMNode.classList.add("selected");
onCancelChange(input);
};
const handleInput = event => {
const ESCAPE_KEYCODE = 27;
if (event.keyCode === ESCAPE_KEYCODE) {
cancelChange();
event.preventDefault();
}
};
input.addEventListener("change", handleChange);
input.addEventListener("blur", cancelChange);
input.addEventListener("keydown", handleInput);
setTimeout(() => {
input.focus();
});
return input;
};
const parseDOMAttributes = value => {
let element = document.createElement("div");
element.innerHTML = `<div ${value}></div>`;
return element.children[0].attributes;
};
const editDOMNode = (domNode, textToSelect) => {
if (selectedDOMNode === null) {
return;
}
const domNodeID = selectedDOMNode.dataset.id;
const type = domNode.dataset.nodeType;
const handleChange = value => {
if (type === "text" || type === "comment") {
inspector.setDOMNodeText(domNodeID, value);
} else if (type === "tag") {
const element = document.createElement(value);
inspector.setDOMNodeTag(domNodeID, value);
} else if (type === "attribute") {
const attributeIndex = domNode.dataset.attributeIndex;
const attributes = parseDOMAttributes(value);
inspector.replaceDOMNodeAttribute(domNodeID, attributeIndex, attributes);
}
};
const cancelChange = editor => {
editor.parentNode.replaceChild(domNode, editor);
};
let editor = createDOMEditor(handleChange, cancelChange);
if (type === "text") {
let emptyTextSpan = domNode.querySelector(".internal");
if (emptyTextSpan === null) {
editor.value = domNode.innerText;
}
} else {
editor.value = domNode.innerText;
}
setTimeout(() => {
if (typeof textToSelect !== "undefined") {
const index = editor.value.indexOf(textToSelect);
if (index !== -1) {
editor.setSelectionRange(index, index + textToSelect.length);
return;
}
}
editor.select();
});
domNode.parentNode.replaceChild(editor, domNode);
};
const addAttributeToDOMNode = domNode => {
if (selectedDOMNode === null) {
return;
}
const domNodeID = selectedDOMNode.dataset.id;
const handleChange = value => {
const attributes = parseDOMAttributes(value);
inspector.addDOMNodeAttributes(domNodeID, attributes);
};
const cancelChange = () => {
container.remove();
};
let editor = createDOMEditor(handleChange, cancelChange);
editor.placeholder = 'name="value"';
let nbsp = document.createElement("span");
nbsp.innerHTML = "&nbsp;";
let container = document.createElement("span");
container.appendChild(nbsp);
container.appendChild(editor);
domNode.parentNode.insertBefore(container, domNode.parentNode.lastChild);
};
const updateVisibleDOMNodes = () => {
let domTree = document.getElementById("dom-tree");
visibleDOMNodes = [];
function recurseDOMNodes(node) {
for (let child of node.children) {
if (child.classList.contains("hoverable")) {
visibleDOMNodes.push(child);
}
if (child.tagName === "DIV") {
if (node.open) {
recurseDOMNodes(child);
}
} else {
recurseDOMNodes(child);
}
}
}
recurseDOMNodes(domTree);
};
const requestContextMenu = (clientX, clientY, domNode) => {
pendingEditDOMNode = null;
if (typeof domNode.dataset.nodeType === "undefined") {
if (domNode.parentNode !== null) {
domNode = domNode.parentNode;
}
}
const domNodeID = domNode.closest(".hoverable")?.dataset.id;
const type = domNode.dataset.nodeType;
if (typeof domNodeID === "undefined" || typeof type === "undefined") {
return;
}
let tag = null;
let attributeIndex = null;
if (type === "tag") {
tag = domNode.dataset.tag;
} else if (type === "attribute") {
tag = domNode.dataset.tag;
attributeIndex = domNode.dataset.attributeIndex;
}
pendingEditDOMNode = domNode;
inspector.requestDOMTreeContextMenu(domNodeID, clientX, clientY, type, tag, attributeIndex);
};
const executeConsoleScript = consoleInput => {
const script = consoleInput.value;
if (!/\S/.test(script)) {
return;
}
if (consoleHistory.length === 0 || consoleHistory[consoleHistory.length - 1] !== script) {
consoleHistory.push(script);
}
consoleHistoryIndex = consoleHistory.length;
inspector.executeConsoleScript(script);
consoleInput.value = "";
};
const setConsoleInputToPreviousHistoryItem = consoleInput => {
if (consoleHistoryIndex === 0) {
return;
}
--consoleHistoryIndex;
const script = consoleHistory[consoleHistoryIndex];
consoleInput.value = script;
};
const setConsoleInputToNextHistoryItem = consoleInput => {
if (consoleHistory.length === 0) {
return;
}
const lastIndex = consoleHistory.length - 1;
if (consoleHistoryIndex < lastIndex) {
++consoleHistoryIndex;
consoleInput.value = consoleHistory[consoleHistoryIndex];
return;
}
if (consoleHistoryIndex === lastIndex) {
++consoleHistoryIndex;
consoleInput.value = "";
return;
}
};
const consoleParentGroup = () => {
if (consoleGroupStack.length === 0) {
return document.getElementById("console-output");
}
const lastConsoleGroup = consoleGroupStack[consoleGroupStack.length - 1];
return document.getElementById(`console-group-${lastConsoleGroup.id}`);
};
const scrollConsoleToBottom = () => {
let consoleOutput = document.getElementById("console-output");
// FIXME: It should be sufficient to scrollTo a y value of document.documentElement.offsetHeight,
// but due to an unknown bug offsetHeight seems to not be properly updated after spamming
// a lot of document changes.
//
// The setTimeout makes the scrollTo async and allows the DOM to be updated.
setTimeout(function () {
consoleOutput.scrollTo(0, 1_000_000_000);
}, 0);
};
inspector.appendConsoleOutput = output => {
let parent = consoleParentGroup();
let element = document.createElement("p");
element.innerHTML = decodeBase64(output);
parent.appendChild(element);
scrollConsoleToBottom();
};
inspector.clearConsoleOutput = () => {
let consoleOutput = document.getElementById("console-output");
consoleOutput.innerHTML = "";
consoleGroupStack = [];
};
inspector.beginConsoleGroup = (label, startExpanded) => {
let parent = consoleParentGroup();
const group = {
id: ++consoleGroupNextID,
label: label,
};
consoleGroupStack.push(group);
let details = document.createElement("details");
details.id = `console-group-${group.id}`;
details.open = startExpanded;
let summary = document.createElement("summary");
summary.innerHTML = decodeBase64(label);
details.appendChild(summary);
parent.appendChild(details);
scrollConsoleToBottom();
};
inspector.endConsoleGroup = () => {
consoleGroupStack.pop();
};
document.addEventListener("DOMContentLoaded", () => {
let inspectorSeparator = document.getElementById("inspector-separator");
inspectorSeparator.addEventListener("mousedown", beginSplitViewDrag);
let consoleInput = document.getElementById("console-input");
consoleInput.focus();
consoleInput.addEventListener("keydown", event => {
const UP_ARROW_KEYCODE = 38;
const DOWN_ARROW_KEYCODE = 40;
const RETURN_KEYCODE = 13;
if (event.keyCode === UP_ARROW_KEYCODE) {
setConsoleInputToPreviousHistoryItem(consoleInput);
event.preventDefault();
} else if (event.keyCode === DOWN_ARROW_KEYCODE) {
setConsoleInputToNextHistoryItem(consoleInput);
event.preventDefault();
} else if (event.keyCode === RETURN_KEYCODE) {
executeConsoleScript(consoleInput);
event.preventDefault();
}
});
document.addEventListener("contextmenu", event => {
requestContextMenu(event.clientX, event.clientY, event.target);
event.preventDefault();
});
document.addEventListener("keydown", event => {
const UP_ARROW_KEYCODE = 38;
const DOWN_ARROW_KEYCODE = 40;
const RIGHT_ARROW_KEYCODE = 39;
const LEFT_ARROW_KEYCODE = 37;
const RETURN_KEYCODE = 13;
const SPACE_KEYCODE = 32;
const move = delta => {
let selectedIndex = visibleDOMNodes.indexOf(selectedDOMNode);
if (selectedIndex < 0) {
return;
}
let newIndex = selectedIndex + delta;
if (visibleDOMNodes[newIndex]) {
inspectDOMNode(visibleDOMNodes[newIndex]);
}
};
if (document.activeElement.tagName !== "INPUT") {
const isSummary = selectedDOMNode.parentNode.tagName === "SUMMARY";
const isDiv = selectedDOMNode.parentNode.tagName === "DIV";
if (event.keyCode == UP_ARROW_KEYCODE) {
move(-1);
} else if (event.keyCode == DOWN_ARROW_KEYCODE) {
move(1);
} else if (event.keyCode == RETURN_KEYCODE || event.keyCode == SPACE_KEYCODE) {
if (isSummary) {
selectedDOMNode.parentNode.click();
}
} else if (event.keyCode == RIGHT_ARROW_KEYCODE) {
if (isSummary && selectedDOMNode.parentNode.parentNode.open === false) {
selectedDOMNode.parentNode.click();
} else if (selectedDOMNode.parentNode.parentNode.open === true && !isDiv) {
move(1);
}
} else if (event.keyCode == LEFT_ARROW_KEYCODE) {
if (isSummary && selectedDOMNode.parentNode.parentNode.open === true) {
selectedDOMNode.parentNode.click();
} else if (selectedDOMNode.parentNode.parentNode.open === false || isDiv) {
move(-1);
}
}
}
});
// Setup filters for property tables
["computed-style", "resolved-style", "custom-properties"].forEach(setupPropertyFilter);
inspector.inspectorLoaded();
});

View file

@ -555,7 +555,6 @@ set(SOURCES
IndexedDB/Internal/Database.cpp
IndexedDB/Internal/Key.cpp
IndexedDB/Internal/RequestList.cpp
Internals/Inspector.cpp
Internals/InternalAnimationTimeline.cpp
Internals/Internals.cpp
IntersectionObserver/IntersectionObserver.cpp

View file

@ -621,7 +621,6 @@ class RequestList;
}
namespace Web::Internals {
class Inspector;
class Internals;
}

View file

@ -60,7 +60,6 @@
#include <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Internals/Inspector.h>
#include <LibWeb/Internals/Internals.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Page/Page.h>
@ -716,14 +715,8 @@ Vector<GC::Ref<MimeType>> Window::pdf_viewer_mime_type_objects()
return m_pdf_viewer_mime_type_objects;
}
static bool s_inspector_object_exposed = false;
static bool s_internals_object_exposed = false;
void Window::set_inspector_object_exposed(bool exposed)
{
s_inspector_object_exposed = exposed;
}
void Window::set_internals_object_exposed(bool exposed)
{
s_internals_object_exposed = exposed;
@ -739,8 +732,6 @@ WebIDL::ExceptionOr<void> Window::initialize_web_interfaces(Badge<WindowEnvironm
Bindings::WindowGlobalMixin::initialize(realm, *this);
WindowOrWorkerGlobalScopeMixin::initialize(realm);
if (s_inspector_object_exposed)
define_direct_property("inspector", realm.create<Internals::Inspector>(realm), JS::default_attributes);
if (s_internals_object_exposed)
define_direct_property("internals", realm.create<Internals::Internals>(realm), JS::default_attributes);

View file

@ -247,7 +247,6 @@ public:
void consume_history_action_user_activation();
static void set_inspector_object_exposed(bool);
static void set_internals_object_exposed(bool);
[[nodiscard]] OrderedHashMap<FlyString, GC::Ref<Navigable>> document_tree_child_navigable_target_name_property_set();

View file

@ -1,108 +0,0 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/InspectorPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/DOM/NamedNodeMap.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Internals/Inspector.h>
#include <LibWeb/Page/Page.h>
namespace Web::Internals {
GC_DEFINE_ALLOCATOR(Inspector);
Inspector::Inspector(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
Inspector::~Inspector() = default;
void Inspector::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(Inspector);
}
PageClient& Inspector::inspector_page_client() const
{
return as<HTML::Window>(HTML::relevant_global_object(*this)).page().client();
}
void Inspector::inspector_loaded()
{
inspector_page_client().inspector_did_load();
}
void Inspector::inspect_dom_node(i64 node_id, Optional<i32> const& pseudo_element)
{
inspector_page_client().inspector_did_select_dom_node(node_id, pseudo_element.map([](auto value) {
VERIFY(value < to_underlying(Web::CSS::Selector::PseudoElement::Type::KnownPseudoElementCount));
return static_cast<Web::CSS::Selector::PseudoElement::Type>(value);
}));
}
void Inspector::set_dom_node_text(i64 node_id, String const& text)
{
inspector_page_client().inspector_did_set_dom_node_text(node_id, text);
}
void Inspector::set_dom_node_tag(i64 node_id, String const& tag)
{
inspector_page_client().inspector_did_set_dom_node_tag(node_id, tag);
}
void Inspector::add_dom_node_attributes(i64 node_id, GC::Ref<DOM::NamedNodeMap> attributes)
{
inspector_page_client().inspector_did_add_dom_node_attributes(node_id, attributes);
}
void Inspector::replace_dom_node_attribute(i64 node_id, WebIDL::UnsignedLongLong attribute_index, GC::Ref<DOM::NamedNodeMap> replacement_attributes)
{
inspector_page_client().inspector_did_replace_dom_node_attribute(node_id, static_cast<size_t>(attribute_index), replacement_attributes);
}
void Inspector::request_dom_tree_context_menu(i64 node_id, i32 client_x, i32 client_y, String const& type, Optional<String> const& tag, Optional<WebIDL::UnsignedLongLong> const& attribute_index)
{
inspector_page_client().inspector_did_request_dom_tree_context_menu(node_id, { client_x, client_y }, type, tag, attribute_index.map([](auto index) { return static_cast<size_t>(index); }));
}
void Inspector::request_cookie_context_menu(WebIDL::UnsignedLongLong cookie_index, i32 client_x, i32 client_y)
{
inspector_page_client().inspector_did_request_cookie_context_menu(cookie_index, { client_x, client_y });
}
void Inspector::request_style_sheet_source(String const& type_string, Optional<i64> const& dom_node_unique_id, Optional<String> const& url)
{
auto type = CSS::style_sheet_identifier_type_from_string(type_string);
VERIFY(type.has_value());
Optional<UniqueNodeID> dom_node_unique_id_opt;
if (dom_node_unique_id.has_value())
dom_node_unique_id_opt = dom_node_unique_id.value();
inspector_page_client().inspector_did_request_style_sheet_source({
.type = type.value(),
.dom_element_unique_id = dom_node_unique_id_opt,
.url = url,
});
}
void Inspector::execute_console_script(String const& script)
{
inspector_page_client().inspector_did_execute_console_script(script);
}
void Inspector::export_inspector_html(String const& html)
{
inspector_page_client().inspector_did_export_inspector_html(html);
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/WebIDL/Types.h>
namespace Web::Internals {
class Inspector final : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Inspector, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Inspector);
public:
virtual ~Inspector() override;
void inspector_loaded();
void inspect_dom_node(i64 node_id, Optional<i32> const& pseudo_element);
void set_dom_node_text(i64 node_id, String const& text);
void set_dom_node_tag(i64 node_id, String const& tag);
void add_dom_node_attributes(i64 node_id, GC::Ref<DOM::NamedNodeMap> attributes);
void replace_dom_node_attribute(i64 node_id, WebIDL::UnsignedLongLong attribute_index, GC::Ref<DOM::NamedNodeMap> replacement_attributes);
void request_dom_tree_context_menu(i64 node_id, i32 client_x, i32 client_y, String const& type, Optional<String> const& tag, Optional<WebIDL::UnsignedLongLong> const& attribute_index);
void request_cookie_context_menu(WebIDL::UnsignedLongLong cookie_index, i32 client_x, i32 client_y);
void request_style_sheet_source(String const& type, Optional<i64> const& dom_node_unique_id, Optional<String> const& url);
void execute_console_script(String const& script);
void export_inspector_html(String const& html);
private:
explicit Inspector(JS::Realm&);
PageClient& inspector_page_client() const;
virtual void initialize(JS::Realm&) override;
};
}

View file

@ -1,23 +0,0 @@
#import <DOM/NamedNodeMap.idl>
[Exposed=Nobody] interface Inspector {
undefined inspectorLoaded();
undefined inspectDOMNode(long long nodeID, optional long pseudoElement);
undefined setDOMNodeText(long long nodeID, DOMString text);
undefined setDOMNodeTag(long long nodeID, DOMString tag);
undefined addDOMNodeAttributes(long long nodeID, NamedNodeMap attributes);
undefined replaceDOMNodeAttribute(long long nodeID, unsigned long long attributeIndex, NamedNodeMap replacementAttributes);
undefined requestDOMTreeContextMenu(long long nodeID, long clientX, long clientY, DOMString type, DOMString? tag, unsigned long long? attributeIndex);
undefined requestCookieContextMenu(unsigned long long cookieIndex, long clientX, long clientY);
undefined requestStyleSheetSource(DOMString type, long long? domNodeID, DOMString? url);
undefined executeConsoleScript(DOMString script);
undefined exportInspectorHTML(DOMString html);
};

View file

@ -9,12 +9,7 @@
#pragma once
#include <AK/Noncopyable.h>
#include <AK/OwnPtr.h>
#include <AK/RefPtr.h>
#include <AK/WeakPtr.h>
#include <AK/Weakable.h>
#include <LibGC/Heap.h>
#include <LibGC/Root.h>
#include <LibGfx/Cursor.h>
#include <LibGfx/Forward.h>
@ -28,8 +23,6 @@
#include <LibWeb/CSS/PreferredColorScheme.h>
#include <LibWeb/CSS/PreferredContrast.h>
#include <LibWeb/CSS/PreferredMotion.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/Cookie/Cookie.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/ActivateTab.h>
@ -407,18 +400,6 @@ public:
virtual void page_did_mutate_dom([[maybe_unused]] FlyString const& type, [[maybe_unused]] DOM::Node const& target, [[maybe_unused]] DOM::NodeList& added_nodes, [[maybe_unused]] DOM::NodeList& removed_nodes, [[maybe_unused]] GC::Ptr<DOM::Node> previous_sibling, [[maybe_unused]] GC::Ptr<DOM::Node> next_sibling, [[maybe_unused]] Optional<String> const& attribute_name) { }
virtual void inspector_did_load() { }
virtual void inspector_did_select_dom_node([[maybe_unused]] UniqueNodeID node_id, [[maybe_unused]] Optional<CSS::Selector::PseudoElement::Type> const& pseudo_element) { }
virtual void inspector_did_set_dom_node_text([[maybe_unused]] UniqueNodeID node_id, [[maybe_unused]] String const& text) { }
virtual void inspector_did_set_dom_node_tag([[maybe_unused]] UniqueNodeID node_id, [[maybe_unused]] String const& tag) { }
virtual void inspector_did_add_dom_node_attributes([[maybe_unused]] UniqueNodeID node_id, [[maybe_unused]] GC::Ref<DOM::NamedNodeMap> attributes) { }
virtual void inspector_did_replace_dom_node_attribute([[maybe_unused]] UniqueNodeID node_id, [[maybe_unused]] size_t attribute_index, [[maybe_unused]] GC::Ref<DOM::NamedNodeMap> replacement_attributes) { }
virtual void inspector_did_request_dom_tree_context_menu([[maybe_unused]] UniqueNodeID node_id, [[maybe_unused]] CSSPixelPoint position, [[maybe_unused]] String const& type, [[maybe_unused]] Optional<String> const& tag, [[maybe_unused]] Optional<size_t> const& attribute_index) { }
virtual void inspector_did_request_cookie_context_menu([[maybe_unused]] size_t cookie_index, [[maybe_unused]] CSSPixelPoint position) { }
virtual void inspector_did_request_style_sheet_source([[maybe_unused]] CSS::StyleSheetIdentifier const& identifier) { }
virtual void inspector_did_execute_console_script([[maybe_unused]] String const& script) { }
virtual void inspector_did_export_inspector_html([[maybe_unused]] String const& html) { }
virtual bool is_ready_to_paint() const = 0;
virtual DisplayListPlayerType display_list_player_type() const = 0;

View file

@ -262,7 +262,6 @@ libweb_js_bindings(IndexedDB/IDBOpenDBRequest)
libweb_js_bindings(IndexedDB/IDBRequest)
libweb_js_bindings(IndexedDB/IDBTransaction)
libweb_js_bindings(IndexedDB/IDBVersionChangeEvent)
libweb_js_bindings(Internals/Inspector)
libweb_js_bindings(Internals/InternalAnimationTimeline)
libweb_js_bindings(Internals/Internals)
libweb_js_bindings(IntersectionObserver/IntersectionObserver)

View file

@ -8,7 +8,6 @@ set(SOURCES
CookieJar.cpp
Database.cpp
HelperProcess.cpp
InspectorClient.cpp
Mutation.cpp
Plugins/FontPlugin.cpp
Plugins/ImageCodecPlugin.cpp

View file

@ -13,7 +13,6 @@ namespace WebView {
class Application;
class CookieJar;
class Database;
class InspectorClient;
class OutOfProcessWebView;
class ProcessManager;
class ViewImplementation;

View file

@ -85,7 +85,6 @@ static ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_proc
Optional<IPC::File> request_server_socket,
ClientArguments&&... client_arguments)
{
auto const& chrome_options = WebView::Application::chrome_options();
auto const& web_content_options = WebView::Application::web_content_options();
Vector<ByteString> arguments {
@ -95,8 +94,6 @@ static ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_proc
web_content_options.executable_path.to_byte_string(),
};
if (chrome_options.devtools_port.has_value())
arguments.append("--devtools"sv);
if (web_content_options.config_path.has_value()) {
arguments.append("--config-path"sv);
arguments.append(web_content_options.config_path.value());

View file

@ -1,809 +0,0 @@
/*
* Copyright (c) 2023-2024, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Base64.h>
#include <AK/Enumerate.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/LexicalPath.h>
#include <AK/SourceGenerator.h>
#include <AK/StringBuilder.h>
#include <LibCore/Directory.h>
#include <LibCore/File.h>
#include <LibCore/Resource.h>
#include <LibJS/MarkupGenerator.h>
#include <LibURL/Parser.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/Namespace.h>
#include <LibWebView/Application.h>
#include <LibWebView/CookieJar.h>
#include <LibWebView/InspectorClient.h>
#include <LibWebView/SourceHighlighter.h>
namespace WebView {
static constexpr auto INSPECTOR_HTML = "resource://ladybird/inspector.html"sv;
static constexpr auto INSPECTOR_CSS = "resource://ladybird/inspector.css"sv;
static constexpr auto INSPECTOR_JS = "resource://ladybird/inspector.js"sv;
static String style_sheet_identifier_to_json(Web::CSS::StyleSheetIdentifier const& identifier)
{
return MUST(String::formatted("{{ type: '{}', domNodeId: {}, url: '{}' }}"sv,
Web::CSS::style_sheet_identifier_type_to_string(identifier.type),
identifier.dom_element_unique_id.map([](auto& it) { return String::number(it.value()); }).value_or("undefined"_string),
identifier.url.value_or("undefined"_string)));
}
InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImplementation& inspector_web_view)
: m_content_web_view(content_web_view)
, m_inspector_web_view(inspector_web_view)
{
m_content_web_view.on_received_dom_tree = [this](auto const& dom_tree) {
auto dom_tree_html = generate_dom_tree(dom_tree);
auto dom_tree_base64 = MUST(encode_base64(dom_tree_html.bytes()));
auto script = MUST(String::formatted("inspector.loadDOMTree(\"{}\");", dom_tree_base64));
m_inspector_web_view.run_javascript(script);
m_dom_tree_loaded = true;
if (m_pending_selection.has_value())
select_node(m_pending_selection.release_value());
else
select_default_node();
};
m_content_web_view.on_received_dom_node_properties = [this](auto const& properties) {
StringBuilder builder;
// FIXME: Support box model metrics and ARIA properties.
builder.append("inspector.createPropertyTables(\""sv);
builder.append_escaped_for_json(properties.computed_style.serialized());
builder.append("\", \""sv);
builder.append_escaped_for_json(properties.resolved_style.serialized());
builder.append("\", \""sv);
builder.append_escaped_for_json(properties.custom_properties.serialized());
builder.append("\");"sv);
builder.append("inspector.createFontList(\""sv);
builder.append_escaped_for_json(properties.fonts.serialized());
builder.append("\");"sv);
m_inspector_web_view.run_javascript(MUST(builder.to_string()));
};
m_content_web_view.on_received_accessibility_tree = [this](auto const& accessibility_tree) {
auto accessibility_tree_html = generate_accessibility_tree(accessibility_tree);
auto accessibility_tree_base64 = MUST(encode_base64(accessibility_tree_html.bytes()));
auto script = MUST(String::formatted("inspector.loadAccessibilityTree(\"{}\");", accessibility_tree_base64));
m_inspector_web_view.run_javascript(script);
};
m_content_web_view.on_received_hovered_node_id = [this](auto node_id) {
select_node(node_id);
};
m_content_web_view.on_received_style_sheet_list = [this](auto const& style_sheets) {
StringBuilder builder;
builder.append("inspector.setStyleSheets(["sv);
for (auto& style_sheet : style_sheets) {
builder.appendff("{}, "sv, style_sheet_identifier_to_json(style_sheet));
}
builder.append("]);"sv);
m_inspector_web_view.run_javascript(MUST(builder.to_string()));
};
m_content_web_view.on_received_style_sheet_source = [this](Web::CSS::StyleSheetIdentifier const& identifier, URL::URL const& base_url, String const& source) {
auto html = highlight_source(URL::Parser::basic_parse(identifier.url.value_or({})), base_url, source, Syntax::Language::CSS, HighlightOutputMode::SourceOnly);
auto script = MUST(String::formatted("inspector.setStyleSheetSource({}, \"{}\");",
style_sheet_identifier_to_json(identifier),
MUST(encode_base64(html.bytes()))));
m_inspector_web_view.run_javascript(script);
};
m_content_web_view.on_finshed_editing_dom_node = [this](auto const& node_id) {
m_pending_selection = node_id;
m_dom_tree_loaded = false;
m_dom_node_attributes.clear();
inspect();
};
m_content_web_view.on_received_dom_node_html = [this](auto const& html) {
if (m_content_web_view.on_insert_clipboard_entry)
m_content_web_view.on_insert_clipboard_entry(html, "unspecified"_string, "text/plain"_string);
};
m_content_web_view.on_console_message_available = [this](auto message_index) {
console_message_available(message_index);
};
m_content_web_view.on_received_styled_console_messages = [this](auto start_index, auto const& message_types, auto const& messages) {
console_messages_received(start_index, message_types, messages);
};
m_inspector_web_view.enable_inspector_prototype();
m_inspector_web_view.use_native_user_style_sheet();
m_inspector_web_view.on_inspector_loaded = [this]() {
m_inspector_loaded = true;
inspect();
m_content_web_view.js_console_request_messages(0);
};
m_inspector_web_view.on_inspector_requested_dom_tree_context_menu = [this](auto node_id, auto position, auto const& type, auto const& tag, auto const& attribute_index) {
Optional<Attribute> attribute;
if (attribute_index.has_value())
attribute = m_dom_node_attributes.get(node_id)->at(*attribute_index);
m_context_menu_data = ContextMenuData { node_id, tag, attribute };
if (type.is_one_of("text"sv, "comment"sv)) {
if (on_requested_dom_node_text_context_menu)
on_requested_dom_node_text_context_menu(position);
} else if (type == "tag"sv) {
VERIFY(tag.has_value());
if (on_requested_dom_node_tag_context_menu)
on_requested_dom_node_tag_context_menu(position, *tag);
} else if (type == "attribute"sv) {
VERIFY(tag.has_value());
VERIFY(attribute.has_value());
if (on_requested_dom_node_attribute_context_menu)
on_requested_dom_node_attribute_context_menu(position, *tag, *attribute);
}
};
m_inspector_web_view.on_inspector_selected_dom_node = [this](auto node_id, auto const& pseudo_element) {
m_content_web_view.highlight_dom_node(node_id, pseudo_element);
m_content_web_view.inspect_dom_node(node_id, pseudo_element);
};
m_inspector_web_view.on_inspector_set_dom_node_text = [this](auto node_id, auto const& text) {
m_content_web_view.set_dom_node_text(node_id, text);
};
m_inspector_web_view.on_inspector_set_dom_node_tag = [this](auto node_id, auto const& tag) {
m_content_web_view.set_dom_node_tag(node_id, tag);
};
m_inspector_web_view.on_inspector_added_dom_node_attributes = [this](auto node_id, auto const& attributes) {
m_content_web_view.add_dom_node_attributes(node_id, attributes);
};
m_inspector_web_view.on_inspector_replaced_dom_node_attribute = [this](auto node_id, u32 attribute_index, auto const& replacement_attributes) {
auto const& attribute = m_dom_node_attributes.get(node_id)->at(attribute_index);
m_content_web_view.replace_dom_node_attribute(node_id, attribute.name, replacement_attributes);
};
m_inspector_web_view.on_inspector_requested_cookie_context_menu = [this](auto cookie_index, auto position) {
if (cookie_index >= m_cookies.size())
return;
m_cookie_context_menu_index = cookie_index;
if (on_requested_cookie_context_menu)
on_requested_cookie_context_menu(position, m_cookies[cookie_index]);
};
m_inspector_web_view.on_inspector_requested_style_sheet_source = [this](auto const& identifier) {
m_content_web_view.request_style_sheet_source(identifier);
};
m_inspector_web_view.on_inspector_executed_console_script = [this](auto const& script) {
append_console_source(script);
m_content_web_view.js_console_input(script);
};
m_inspector_web_view.on_inspector_exported_inspector_html = [this](String const& html) {
auto maybe_inspector_path = Application::the().path_for_downloaded_file("inspector"sv);
if (maybe_inspector_path.is_error()) {
append_console_warning(MUST(String::formatted("Unable to select a download location: {}", maybe_inspector_path.error())));
return;
}
auto inspector_path = maybe_inspector_path.release_value();
if (auto result = Core::Directory::create(inspector_path.string(), Core::Directory::CreateDirectories::Yes); result.is_error()) {
append_console_warning(MUST(String::formatted("Unable to create {}: {}", inspector_path, result.error())));
return;
}
auto export_file = [&](auto name, auto const& contents) {
auto path = inspector_path.append(name);
auto file = Core::File::open(path.string(), Core::File::OpenMode::Write);
if (file.is_error()) {
append_console_warning(MUST(String::formatted("Unable to open {}: {}", path, file.error())));
return false;
}
if (auto result = file.value()->write_until_depleted(contents); result.is_error()) {
append_console_warning(MUST(String::formatted("Unable to save {}: {}", path, result.error())));
return false;
}
return true;
};
auto inspector_css = MUST(Core::Resource::load_from_uri(INSPECTOR_CSS));
auto inspector_js = MUST(Core::Resource::load_from_uri(INSPECTOR_JS));
auto inspector_html = MUST(html.replace(INSPECTOR_CSS, "inspector.css"sv, ReplaceMode::All));
inspector_html = MUST(inspector_html.replace(INSPECTOR_JS, "inspector.js"sv, ReplaceMode::All));
if (!export_file("inspector.html"sv, inspector_html))
return;
if (!export_file("inspector.css"sv, inspector_css->data()))
return;
if (!export_file("inspector.js"sv, inspector_js->data()))
return;
append_console_message(MUST(String::formatted("Exported Inspector files to {}", inspector_path)));
};
load_inspector();
}
InspectorClient::~InspectorClient()
{
m_content_web_view.on_finshed_editing_dom_node = nullptr;
m_content_web_view.on_received_accessibility_tree = nullptr;
m_content_web_view.on_console_message_available = nullptr;
m_content_web_view.on_received_styled_console_messages = nullptr;
m_content_web_view.on_received_dom_node_html = nullptr;
m_content_web_view.on_received_dom_node_properties = nullptr;
m_content_web_view.on_received_dom_tree = nullptr;
m_content_web_view.on_received_hovered_node_id = nullptr;
m_content_web_view.on_received_style_sheet_list = nullptr;
m_content_web_view.on_inspector_requested_style_sheet_source = nullptr;
}
void InspectorClient::inspect()
{
if (!m_inspector_loaded)
return;
m_content_web_view.inspect_dom_tree();
m_content_web_view.inspect_accessibility_tree();
m_content_web_view.list_style_sheets();
load_cookies();
}
void InspectorClient::reset()
{
static auto script = "inspector.reset();"_string;
m_inspector_web_view.run_javascript(script);
m_body_or_frameset_node_id.clear();
m_pending_selection.clear();
m_dom_tree_loaded = false;
m_dom_node_attributes.clear();
m_highest_notified_message_index = -1;
m_highest_received_message_index = -1;
m_waiting_for_messages = false;
}
void InspectorClient::select_hovered_node()
{
m_content_web_view.get_hovered_node_id();
}
void InspectorClient::select_default_node()
{
if (m_body_or_frameset_node_id.has_value())
select_node(*m_body_or_frameset_node_id);
}
void InspectorClient::clear_selection()
{
m_content_web_view.clear_highlighted_dom_node();
m_content_web_view.clear_inspected_dom_node();
static auto script = "inspector.clearInspectedDOMNode();"_string;
m_inspector_web_view.run_javascript(script);
}
void InspectorClient::select_node(Web::UniqueNodeID node_id)
{
if (!m_dom_tree_loaded) {
m_pending_selection = node_id;
return;
}
auto script = MUST(String::formatted("inspector.inspectDOMNodeID({});", node_id.value()));
m_inspector_web_view.run_javascript(script);
}
void InspectorClient::load_cookies()
{
m_cookies = Application::cookie_jar().get_all_cookies(m_content_web_view.url());
JsonArray json_cookies;
for (auto const& [index, cookie] : enumerate(m_cookies)) {
JsonObject json_cookie;
json_cookie.set("index"sv, JsonValue { index });
json_cookie.set("name"sv, JsonValue { cookie.name });
json_cookie.set("value"sv, JsonValue { cookie.value });
json_cookie.set("domain"sv, JsonValue { cookie.domain });
json_cookie.set("path"sv, JsonValue { cookie.path });
json_cookie.set("creationTime"sv, JsonValue { cookie.creation_time.milliseconds_since_epoch() });
json_cookie.set("lastAccessTime"sv, JsonValue { cookie.last_access_time.milliseconds_since_epoch() });
json_cookie.set("expiryTime"sv, JsonValue { cookie.expiry_time.milliseconds_since_epoch() });
MUST(json_cookies.append(move(json_cookie)));
}
StringBuilder builder;
builder.append("inspector.setCookies("sv);
json_cookies.serialize(builder);
builder.append(");"sv);
m_inspector_web_view.run_javascript(MUST(builder.to_string()));
}
void InspectorClient::context_menu_edit_dom_node()
{
VERIFY(m_context_menu_data.has_value());
auto script = MUST(String::formatted("inspector.editDOMNodeID({});", m_context_menu_data->dom_node_id));
m_inspector_web_view.run_javascript(script);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_copy_dom_node()
{
VERIFY(m_context_menu_data.has_value());
m_content_web_view.get_dom_node_outer_html(m_context_menu_data->dom_node_id);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_screenshot_dom_node()
{
VERIFY(m_context_menu_data.has_value());
m_content_web_view.take_dom_node_screenshot(m_context_menu_data->dom_node_id)
->when_resolved([this](auto const& path) {
append_console_message(MUST(String::formatted("Screenshot saved to: {}", path)));
})
.when_rejected([this](auto const& error) {
append_console_warning(MUST(String::formatted("Warning: {}", error)));
});
m_context_menu_data.clear();
}
void InspectorClient::context_menu_create_child_element()
{
VERIFY(m_context_menu_data.has_value());
m_content_web_view.create_child_element(m_context_menu_data->dom_node_id);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_create_child_text_node()
{
VERIFY(m_context_menu_data.has_value());
m_content_web_view.create_child_text_node(m_context_menu_data->dom_node_id);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_clone_dom_node()
{
VERIFY(m_context_menu_data.has_value());
m_content_web_view.clone_dom_node(m_context_menu_data->dom_node_id);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_remove_dom_node()
{
VERIFY(m_context_menu_data.has_value());
m_content_web_view.remove_dom_node(m_context_menu_data->dom_node_id);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_add_dom_node_attribute()
{
VERIFY(m_context_menu_data.has_value());
auto script = MUST(String::formatted("inspector.addAttributeToDOMNodeID({});", m_context_menu_data->dom_node_id));
m_inspector_web_view.run_javascript(script);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_remove_dom_node_attribute()
{
VERIFY(m_context_menu_data.has_value());
VERIFY(m_context_menu_data->attribute.has_value());
m_content_web_view.replace_dom_node_attribute(m_context_menu_data->dom_node_id, m_context_menu_data->attribute->name, {});
m_context_menu_data.clear();
}
void InspectorClient::context_menu_copy_dom_node_attribute_value()
{
VERIFY(m_context_menu_data.has_value());
VERIFY(m_context_menu_data->attribute.has_value());
if (m_content_web_view.on_insert_clipboard_entry)
m_content_web_view.on_insert_clipboard_entry(m_context_menu_data->attribute->value, "unspecified"_string, "text/plain"_string);
m_context_menu_data.clear();
}
void InspectorClient::context_menu_delete_cookie()
{
VERIFY(m_cookie_context_menu_index.has_value());
VERIFY(*m_cookie_context_menu_index < m_cookies.size());
auto& cookie = m_cookies[*m_cookie_context_menu_index];
cookie.expiry_time = UnixDateTime::earliest();
Application::cookie_jar().update_cookie(move(cookie));
load_cookies();
m_cookie_context_menu_index.clear();
}
void InspectorClient::context_menu_delete_all_cookies()
{
for (auto& cookie : m_cookies) {
cookie.expiry_time = UnixDateTime::earliest();
Application::cookie_jar().update_cookie(move(cookie));
}
load_cookies();
m_cookie_context_menu_index.clear();
}
void InspectorClient::load_inspector()
{
auto inspector_html = MUST(Core::Resource::load_from_uri(INSPECTOR_HTML));
auto generate_property_table = [&](auto name) {
return MUST(String::formatted(R"~~~(
<div id="{0}" class="tab-content">
<input class="property-filter" id="{0}-filter" placeholder="Filter properties" />
<table class="property-table">
<thead>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
</thead>
<tbody id="{0}-table">
</tbody>
</table>
</div>
)~~~",
name));
};
StringBuilder builder;
SourceGenerator generator { builder };
generator.set("INSPECTOR_CSS"sv, INSPECTOR_CSS);
generator.set("INSPECTOR_JS"sv, INSPECTOR_JS);
generator.set("INSPECTOR_STYLE"sv, HTML_HIGHLIGHTER_STYLE);
generator.set("COMPUTED_STYLE"sv, generate_property_table("computed-style"sv));
generator.set("RESOVLED_STYLE"sv, generate_property_table("resolved-style"sv));
generator.set("CUSTOM_PROPERTIES"sv, generate_property_table("custom-properties"sv));
generator.append(inspector_html->data());
m_inspector_web_view.load_html(generator.as_string_view());
}
template<typename Generator>
static void generate_tree(StringBuilder& builder, JsonObject const& node, Generator&& generator)
{
if (auto children = node.get_array("children"sv); children.has_value() && !children->is_empty()) {
auto name = node.get_string("name"sv).value_or({});
builder.append("<details>"sv);
builder.append("<summary>"sv);
generator(node);
builder.append("</summary>"sv);
children->for_each([&](auto const& child) {
builder.append("<div>"sv);
generate_tree(builder, child.as_object(), generator);
builder.append("</div>"sv);
});
builder.append("</details>"sv);
} else {
generator(node);
}
}
String InspectorClient::generate_dom_tree(JsonObject const& dom_tree)
{
StringBuilder builder;
generate_tree(builder, dom_tree, [&](JsonObject const& node) {
auto type = node.get_string("type"sv).value_or("unknown"_string);
auto name = node.get_string("name"sv).value_or({});
StringBuilder data_attributes;
auto append_data_attribute = [&](auto name, auto value) {
if (!data_attributes.is_empty())
data_attributes.append(' ');
data_attributes.appendff("data-{}=\"{}\"", name, value);
};
i32 node_id = 0;
if (auto pseudo_element = node.get_integer<i32>("pseudo-element"sv); pseudo_element.has_value()) {
node_id = node.get_integer<i32>("parent-id"sv).value();
append_data_attribute("pseudo-element"sv, *pseudo_element);
} else {
node_id = node.get_integer<i32>("id"sv).value();
}
append_data_attribute("id"sv, node_id);
if (type == "text"sv) {
auto deprecated_text = escape_html_entities(*node.get_string("text"sv));
auto text = MUST(Web::Infra::strip_and_collapse_whitespace(deprecated_text));
builder.appendff("<span data-node-type=\"text\" class=\"hoverable editable\" {}>", data_attributes.string_view());
if (text.is_empty())
builder.appendff("<span class=\"internal\">{}</span>", name);
else
builder.append(text);
builder.append("</span>"sv);
return;
}
if (type == "comment"sv) {
auto comment = escape_html_entities(*node.get_string("data"sv));
builder.appendff("<span class=\"hoverable comment\" {}>", data_attributes.string_view());
builder.append("<span>&lt;!--</span>"sv);
builder.appendff("<span data-node-type=\"comment\" class=\"editable\">{}</span>", comment);
builder.append("<span>--&gt;</span>"sv);
builder.append("</span>"sv);
return;
}
if (type == "shadow-root"sv) {
auto mode = node.get_string("mode"sv).release_value();
builder.appendff("<span class=\"hoverable internal\" {}>", data_attributes.string_view());
builder.appendff("{} ({})", name, mode);
builder.append("</span>"sv);
return;
}
if (type != "element"sv) {
builder.appendff("<span class=\"hoverable internal\" {}>", data_attributes.string_view());
builder.appendff(name);
} else {
if (name.equals_ignoring_ascii_case("BODY"sv) || name.equals_ignoring_ascii_case("FRAMESET"sv))
m_body_or_frameset_node_id = node_id;
auto tag = name;
if (node.get_string("namespace"sv) == Web::Namespace::HTML.bytes_as_string_view())
tag = MUST(tag.to_lowercase());
builder.appendff("<span class=\"hoverable\" {}>", data_attributes.string_view());
builder.append("<span>&lt;</span>"sv);
builder.appendff("<span data-node-type=\"tag\" data-tag=\"{0}\" class=\"editable tag\">{0}</span>", tag);
if (auto attributes = node.get_object("attributes"sv); attributes.has_value()) {
attributes->for_each_member([&](auto const& name, auto const& value) {
auto& dom_node_attributes = m_dom_node_attributes.ensure(node_id);
auto value_string = value.as_string();
builder.append("&nbsp;"sv);
builder.appendff("<span data-node-type=\"attribute\" data-tag=\"{}\" data-attribute-index={} class=\"editable\">", tag, dom_node_attributes.size());
builder.appendff("<span class=\"attribute-name\">{}</span>", escape_html_entities(name));
builder.append('=');
builder.appendff("<span class=\"attribute-value\">\"{}\"</span>", escape_html_entities(value_string));
builder.append("</span>"sv);
dom_node_attributes.empend(name, value_string);
});
}
builder.append("<span>&gt;</span>"sv);
}
// display miscellaneous extra bits of info about the element
Vector<String> extra;
if (node.get_bool("scrollable"sv).value_or(false)) {
extra.append("scrollable"_string);
}
if (node.get_bool("invisible"sv).value_or(false)) {
extra.append("invisible"_string);
}
if (node.get_bool("stackingContext"sv).value_or(false)) {
extra.append("isolated"_string);
}
if (!extra.is_empty()) {
builder.append(" <span>("sv);
builder.append(extra[0]);
for (size_t i = 1; i < extra.size(); i++) {
builder.appendff(", {}", extra[i]);
}
builder.append(")</span>"sv);
}
builder.append("</span>"sv);
});
return MUST(builder.to_string());
}
String InspectorClient::generate_accessibility_tree(JsonObject const& accessibility_tree)
{
StringBuilder builder;
generate_tree(builder, accessibility_tree, [&](JsonObject const& node) {
auto type = node.get_string("type"sv).value_or("unknown"_string);
auto role = node.get_string("role"sv).value_or({});
if (type == "text"sv) {
auto text = escape_html_entities(*node.get_string("text"sv));
builder.appendff("<span class=\"hoverable\">");
builder.append(MUST(Web::Infra::strip_and_collapse_whitespace(text)));
builder.append("</span>"sv);
return;
}
if (type != "element"sv) {
builder.appendff("<span class=\"hoverable internal\">");
builder.appendff(MUST(role.to_lowercase()));
builder.append("</span>"sv);
return;
}
auto name = node.get_string("name"sv).value_or({});
auto description = node.get_string("description"sv).value_or({});
builder.appendff("<span class=\"hoverable\">");
builder.append(MUST(role.to_lowercase()));
builder.appendff(" name: \"{}\", description: \"{}\"", name, description);
builder.append("</span>"sv);
});
return MUST(builder.to_string());
}
void InspectorClient::request_console_messages()
{
VERIFY(!m_waiting_for_messages);
m_content_web_view.js_console_request_messages(m_highest_received_message_index + 1);
m_waiting_for_messages = true;
}
void InspectorClient::console_message_available(i32 message_index)
{
if (message_index <= m_highest_received_message_index) {
dbgln("Notified about console message we already have");
return;
}
if (message_index <= m_highest_notified_message_index) {
dbgln("Notified about console message we're already aware of");
return;
}
m_highest_notified_message_index = message_index;
if (!m_waiting_for_messages)
request_console_messages();
}
void InspectorClient::console_messages_received(i32 start_index, ReadonlySpan<String> message_types, ReadonlySpan<String> messages)
{
auto end_index = start_index + static_cast<i32>(message_types.size()) - 1;
if (end_index <= m_highest_received_message_index) {
dbgln("Received old console messages");
return;
}
for (size_t i = 0; i < message_types.size(); ++i) {
auto const& type = message_types[i];
auto const& message = messages[i];
if (type == "html"sv)
append_console_output(message);
else if (type == "clear"sv)
clear_console_output();
else if (type == "group"sv)
begin_console_group(message, true);
else if (type == "groupCollapsed"sv)
begin_console_group(message, false);
else if (type == "groupEnd"sv)
end_console_group();
else
VERIFY_NOT_REACHED();
}
m_highest_received_message_index = end_index;
m_waiting_for_messages = false;
if (m_highest_received_message_index < m_highest_notified_message_index)
request_console_messages();
}
void InspectorClient::append_console_source(StringView source)
{
StringBuilder builder;
builder.append("<span class=\"console-prompt\">&gt;&nbsp;</span>"sv);
builder.append(MUST(JS::MarkupGenerator::html_from_source(source)));
append_console_output(builder.string_view());
}
void InspectorClient::append_console_message(StringView message)
{
StringBuilder builder;
builder.append("<span class=\"console-prompt\">#&nbsp;</span>"sv);
builder.appendff("<span class=\"console-message\">{}</span>", message);
append_console_output(builder.string_view());
}
void InspectorClient::append_console_warning(StringView warning)
{
StringBuilder builder;
builder.append("<span class=\"console-prompt\">#&nbsp;</span>"sv);
builder.appendff("<span class=\"console-warning\">{}</span>", warning);
append_console_output(builder.string_view());
}
void InspectorClient::append_console_output(StringView html)
{
auto html_base64 = MUST(encode_base64(html.bytes()));
auto script = MUST(String::formatted("inspector.appendConsoleOutput(\"{}\");", html_base64));
m_inspector_web_view.run_javascript(script);
}
void InspectorClient::clear_console_output()
{
static auto script = "inspector.clearConsoleOutput();"_string;
m_inspector_web_view.run_javascript(script);
}
void InspectorClient::begin_console_group(StringView label, bool start_expanded)
{
auto label_base64 = MUST(encode_base64(label.bytes()));
auto script = MUST(String::formatted("inspector.beginConsoleGroup(\"{}\", {});", label_base64, start_expanded));
m_inspector_web_view.run_javascript(script);
}
void InspectorClient::end_console_group()
{
static auto script = "inspector.endConsoleGroup();"_string;
m_inspector_web_view.run_javascript(script);
}
}

View file

@ -1,98 +0,0 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/JsonValue.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibGfx/Point.h>
#include <LibWebView/Attribute.h>
#include <LibWebView/ViewImplementation.h>
#pragma once
namespace WebView {
class InspectorClient {
public:
InspectorClient(ViewImplementation& content_web_view, ViewImplementation& inspector_web_view);
~InspectorClient();
void inspect();
void reset();
void select_hovered_node();
void select_default_node();
void clear_selection();
void context_menu_edit_dom_node();
void context_menu_copy_dom_node();
void context_menu_screenshot_dom_node();
void context_menu_create_child_element();
void context_menu_create_child_text_node();
void context_menu_clone_dom_node();
void context_menu_remove_dom_node();
void context_menu_add_dom_node_attribute();
void context_menu_remove_dom_node_attribute();
void context_menu_copy_dom_node_attribute_value();
void context_menu_delete_cookie();
void context_menu_delete_all_cookies();
Function<void(Gfx::IntPoint)> on_requested_dom_node_text_context_menu;
Function<void(Gfx::IntPoint, String const&)> on_requested_dom_node_tag_context_menu;
Function<void(Gfx::IntPoint, String const&, Attribute const&)> on_requested_dom_node_attribute_context_menu;
Function<void(Gfx::IntPoint, Web::Cookie::Cookie const&)> on_requested_cookie_context_menu;
private:
void load_inspector();
String generate_dom_tree(JsonObject const&);
String generate_accessibility_tree(JsonObject const&);
void select_node(Web::UniqueNodeID);
void load_cookies();
void request_console_messages();
void console_message_available(i32 message_index);
void console_messages_received(i32 start_index, ReadonlySpan<String> message_types, ReadonlySpan<String> messages);
void append_console_source(StringView);
void append_console_message(StringView);
void append_console_warning(StringView);
void append_console_output(StringView);
void clear_console_output();
void begin_console_group(StringView label, bool start_expanded);
void end_console_group();
ViewImplementation& m_content_web_view;
ViewImplementation& m_inspector_web_view;
Optional<Web::UniqueNodeID> m_body_or_frameset_node_id;
Optional<Web::UniqueNodeID> m_pending_selection;
bool m_inspector_loaded { false };
bool m_dom_tree_loaded { false };
struct ContextMenuData {
Web::UniqueNodeID dom_node_id;
Optional<String> tag;
Optional<Attribute> attribute;
};
Optional<ContextMenuData> m_context_menu_data;
HashMap<Web::UniqueNodeID, Vector<Attribute>> m_dom_node_attributes;
Vector<Web::Cookie::Cookie> m_cookies;
Optional<size_t> m_cookie_context_menu_index;
i32 m_highest_notified_message_index { -1 };
i32 m_highest_received_message_index { -1 };
bool m_waiting_for_messages { false };
};
}

View file

@ -779,9 +779,4 @@ void ViewImplementation::use_native_user_style_sheet()
set_user_style_sheet(native_stylesheet_source);
}
void ViewImplementation::enable_inspector_prototype()
{
client().async_enable_inspector_prototype(page_id());
}
}

View file

@ -180,8 +180,6 @@ public:
// native GUI widgets as possible.
void use_native_user_style_sheet();
void enable_inspector_prototype();
Function<void()> on_ready_to_paint;
Function<String(Web::HTML::ActivateTab, Web::HTML::WebViewHints, Optional<u64>)> on_new_web_view;
Function<void()> on_activate_tab;
@ -215,13 +213,12 @@ public:
Function<void(JsonObject)> on_received_dom_tree;
Function<void(DOMNodeProperties)> on_received_dom_node_properties;
Function<void(JsonObject)> on_received_accessibility_tree;
Function<void(Vector<Web::CSS::StyleSheetIdentifier>)> on_received_style_sheet_list;
Function<void(Web::CSS::StyleSheetIdentifier const&)> on_inspector_requested_style_sheet_source;
Function<void(Web::CSS::StyleSheetIdentifier const&, URL::URL const&, String const&)> on_received_style_sheet_source;
Function<void(Web::UniqueNodeID)> on_received_hovered_node_id;
Function<void(Mutation)> on_dom_mutation_received;
Function<void(Optional<Web::UniqueNodeID> const& node_id)> on_finshed_editing_dom_node;
Function<void(String)> on_received_dom_node_html;
Function<void(Vector<Web::CSS::StyleSheetIdentifier>)> on_received_style_sheet_list;
Function<void(Web::CSS::StyleSheetIdentifier const&, URL::URL const&, String const&)> on_received_style_sheet_source;
Function<void(JsonValue)> on_received_js_console_result;
Function<void(i32 message_id)> on_console_message_available;
Function<void(i32 start_index, Vector<String> const& message_types, Vector<String> const& messages)> on_received_styled_console_messages;
@ -246,16 +243,6 @@ public:
Function<void(String const&, String const&, String const&)> on_insert_clipboard_entry;
Function<void(Web::HTML::AudioPlayState)> on_audio_play_state_changed;
Function<void(bool, bool)> on_navigation_buttons_state_changed;
Function<void()> on_inspector_loaded;
Function<void(Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type> const&)> on_inspector_selected_dom_node;
Function<void(Web::UniqueNodeID, String const&)> on_inspector_set_dom_node_text;
Function<void(Web::UniqueNodeID, String const&)> on_inspector_set_dom_node_tag;
Function<void(Web::UniqueNodeID, Vector<Attribute> const&)> on_inspector_added_dom_node_attributes;
Function<void(Web::UniqueNodeID, size_t, Vector<Attribute> const&)> on_inspector_replaced_dom_node_attribute;
Function<void(Web::UniqueNodeID, Gfx::IntPoint, String const&, Optional<String> const&, Optional<size_t> const&)> on_inspector_requested_dom_tree_context_menu;
Function<void(size_t, Gfx::IntPoint)> on_inspector_requested_cookie_context_menu;
Function<void(String const&)> on_inspector_executed_console_script;
Function<void(String const&)> on_inspector_exported_inspector_html;
Function<void()> on_web_content_crashed;
virtual Web::DevicePixelSize viewport_size() const = 0;

View file

@ -375,6 +375,22 @@ void WebContentClient::did_get_dom_node_html(u64 page_id, String html)
}
}
void WebContentClient::did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> stylesheets)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_style_sheet_list)
view->on_received_style_sheet_list(stylesheets);
}
}
void WebContentClient::did_get_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier, URL::URL base_url, String source)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_style_sheet_source)
view->on_received_style_sheet_source(identifier, base_url, source);
}
}
void WebContentClient::did_take_screenshot(u64 page_id, Gfx::ShareableBitmap screenshot)
{
if (auto view = view_for_page_id(page_id); view.has_value())
@ -664,86 +680,6 @@ void WebContentClient::did_allocate_backing_stores(u64 page_id, i32 front_bitmap
view->did_allocate_backing_stores({}, front_bitmap_id, front_bitmap, back_bitmap_id, back_bitmap);
}
void WebContentClient::inspector_did_load(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_loaded)
view->on_inspector_loaded();
}
}
void WebContentClient::inspector_did_select_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_selected_dom_node)
view->on_inspector_selected_dom_node(node_id, pseudo_element);
}
}
void WebContentClient::inspector_did_set_dom_node_text(u64 page_id, Web::UniqueNodeID node_id, String text)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_set_dom_node_text)
view->on_inspector_set_dom_node_text(node_id, text);
}
}
void WebContentClient::inspector_did_set_dom_node_tag(u64 page_id, Web::UniqueNodeID node_id, String tag)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_set_dom_node_tag)
view->on_inspector_set_dom_node_tag(node_id, tag);
}
}
void WebContentClient::inspector_did_add_dom_node_attributes(u64 page_id, Web::UniqueNodeID node_id, Vector<Attribute> attributes)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_added_dom_node_attributes)
view->on_inspector_added_dom_node_attributes(node_id, attributes);
}
}
void WebContentClient::inspector_did_replace_dom_node_attribute(u64 page_id, Web::UniqueNodeID node_id, size_t attribute_index, Vector<Attribute> replacement_attributes)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_replaced_dom_node_attribute)
view->on_inspector_replaced_dom_node_attribute(node_id, attribute_index, replacement_attributes);
}
}
void WebContentClient::inspector_did_request_dom_tree_context_menu(u64 page_id, Web::UniqueNodeID node_id, Gfx::IntPoint position, String type, Optional<String> tag, Optional<size_t> attribute_index)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_requested_dom_tree_context_menu)
view->on_inspector_requested_dom_tree_context_menu(node_id, view->to_widget_position(position), type, tag, attribute_index);
}
}
void WebContentClient::inspector_did_request_cookie_context_menu(u64 page_id, size_t cookie_index, Gfx::IntPoint position)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_requested_cookie_context_menu)
view->on_inspector_requested_cookie_context_menu(cookie_index, view->to_widget_position(position));
}
}
void WebContentClient::inspector_did_execute_console_script(u64 page_id, String script)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_executed_console_script)
view->on_inspector_executed_console_script(script);
}
}
void WebContentClient::inspector_did_export_inspector_html(u64 page_id, String html)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_exported_inspector_html)
view->on_inspector_exported_inspector_html(html);
}
}
Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request_worker_agent(u64 page_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
@ -754,30 +690,6 @@ Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request
return IPC::File {};
}
void WebContentClient::inspector_did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> stylesheets)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_style_sheet_list)
view->on_received_style_sheet_list(stylesheets);
}
}
void WebContentClient::inspector_did_request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_requested_style_sheet_source)
view->on_inspector_requested_style_sheet_source(identifier);
}
}
void WebContentClient::did_get_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier, URL::URL base_url, String source)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_style_sheet_source)
view->on_received_style_sheet_source(identifier, base_url, source);
}
}
Optional<ViewImplementation&> WebContentClient::view_for_page_id(u64 page_id, SourceLocation location)
{
// Don't bother logging anything for the spare WebContent process. It will only receive a load notification for about:blank.

View file

@ -83,6 +83,8 @@ private:
virtual void did_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id) override;
virtual void did_mutate_dom(u64 page_id, Mutation) override;
virtual void did_get_dom_node_html(u64 page_id, String html) override;
virtual void did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> stylesheets) override;
virtual void did_get_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier, URL::URL, String source) override;
virtual void did_take_screenshot(u64 page_id, Gfx::ShareableBitmap screenshot) override;
virtual void did_get_internal_page_info(u64 page_id, PageInfoType, String) override;
virtual void did_execute_js_console_input(u64 page_id, JsonValue) override;
@ -126,20 +128,7 @@ private:
virtual void did_change_audio_play_state(u64 page_id, Web::HTML::AudioPlayState) override;
virtual void did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) override;
virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap, i32 back_bitmap_id, Gfx::ShareableBitmap) override;
virtual void inspector_did_load(u64 page_id) override;
virtual void inspector_did_select_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) override;
virtual void inspector_did_set_dom_node_text(u64 page_id, Web::UniqueNodeID node_id, String text) override;
virtual void inspector_did_set_dom_node_tag(u64 page_id, Web::UniqueNodeID node_id, String tag) override;
virtual void inspector_did_add_dom_node_attributes(u64 page_id, Web::UniqueNodeID node_id, Vector<Attribute> attributes) override;
virtual void inspector_did_replace_dom_node_attribute(u64 page_id, Web::UniqueNodeID node_id, size_t attribute_index, Vector<Attribute> replacement_attributes) override;
virtual void inspector_did_request_dom_tree_context_menu(u64 page_id, Web::UniqueNodeID node_id, Gfx::IntPoint position, String type, Optional<String> tag, Optional<size_t> attribute_index) override;
virtual void inspector_did_request_cookie_context_menu(u64 page_id, size_t cookie_index, Gfx::IntPoint position) override;
virtual void inspector_did_execute_console_script(u64 page_id, String script) override;
virtual void inspector_did_export_inspector_html(u64 page_id, String html) override;
virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id) override;
virtual void inspector_did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> stylesheets) override;
virtual void inspector_did_request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier) override;
virtual void did_get_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier, URL::URL, String source) override;
Optional<ViewImplementation&> view_for_page_id(u64, SourceLocation = SourceLocation::current());

View file

@ -261,7 +261,6 @@ standard_idl_files = [
"//Userland/Libraries/LibWeb/IndexedDB/IDBFactory.idl",
"//Userland/Libraries/LibWeb/IndexedDB/IDBOpenDBRequest.idl",
"//Userland/Libraries/LibWeb/IndexedDB/IDBRequest.idl",
"//Userland/Libraries/LibWeb/Internals/Inspector.idl",
"//Userland/Libraries/LibWeb/Internals/InternalAnimationTimeline.idl",
"//Userland/Libraries/LibWeb/Internals/Internals.idl",
"//Userland/Libraries/LibWeb/IntersectionObserver/IntersectionObserver.idl",

View file

@ -5,7 +5,6 @@ set(SOURCES
ConnectionFromClient.cpp
ConsoleGlobalEnvironmentExtensions.cpp
DevToolsConsoleClient.cpp
InspectorConsoleClient.cpp
PageClient.cpp
PageHost.cpp
WebContentConsoleClient.cpp

View file

@ -652,7 +652,7 @@ void ConnectionFromClient::list_style_sheets(u64 page_id)
if (!page.has_value())
return;
async_inspector_did_list_style_sheets(page_id, page->list_style_sheets());
async_did_list_style_sheets(page_id, page->list_style_sheets());
}
void ConnectionFromClient::request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier)
@ -1323,11 +1323,6 @@ void ConnectionFromClient::set_user_style(u64 page_id, String source)
page->page().set_user_style(move(source));
}
void ConnectionFromClient::enable_inspector_prototype(u64)
{
Web::HTML::Window::set_inspector_object_exposed(true);
}
void ConnectionFromClient::system_time_zone_changed()
{
JS::clear_system_time_zone_cache();

View file

@ -135,8 +135,6 @@ private:
virtual void set_user_style(u64 page_id, String) override;
virtual void enable_inspector_prototype(u64 page_id) override;
virtual void take_document_screenshot(u64 page_id) override;
virtual void take_dom_node_screenshot(u64 page_id, Web::UniqueNodeID node_id) override;

View file

@ -11,7 +11,6 @@ namespace WebContent {
class ConnectionFromClient;
class ConsoleGlobalEnvironmentExtensions;
class DevToolsConsoleClient;
class InspectorConsoleClient;
class PageHost;
class PageClient;
class WebContentConsoleClient;

View file

@ -1,252 +0,0 @@
/*
* Copyright (c) 2021, Brandon Scott <xeon.productions@gmail.com>
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
* Copyright (c) 2024, Gasim Gasimzada <gasim@gasimzada.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/MemoryStream.h>
#include <LibJS/MarkupGenerator.h>
#include <LibJS/Print.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/HTML/Window.h>
#include <WebContent/ConsoleGlobalEnvironmentExtensions.h>
#include <WebContent/InspectorConsoleClient.h>
#include <WebContent/PageClient.h>
namespace WebContent {
GC_DEFINE_ALLOCATOR(InspectorConsoleClient);
GC::Ref<InspectorConsoleClient> InspectorConsoleClient::create(JS::Realm& realm, JS::Console& console, PageClient& client)
{
auto& window = as<Web::HTML::Window>(realm.global_object());
auto console_global_environment_extensions = realm.create<ConsoleGlobalEnvironmentExtensions>(realm, window);
return realm.heap().allocate<InspectorConsoleClient>(realm, console, client, console_global_environment_extensions);
}
InspectorConsoleClient::InspectorConsoleClient(JS::Realm& realm, JS::Console& console, PageClient& client, ConsoleGlobalEnvironmentExtensions& console_global_environment_extensions)
: WebContentConsoleClient(realm, console, client, console_global_environment_extensions)
{
}
InspectorConsoleClient::~InspectorConsoleClient() = default;
void InspectorConsoleClient::handle_result(JS::Value result)
{
print_html(JS::MarkupGenerator::html_from_value(result).release_value_but_fixme_should_propagate_errors());
}
void InspectorConsoleClient::report_exception(JS::Error const& exception, bool in_promise)
{
print_html(JS::MarkupGenerator::html_from_error(exception, in_promise).release_value_but_fixme_should_propagate_errors());
}
void InspectorConsoleClient::begin_group(String const& label, bool start_expanded)
{
m_message_log.append({ .type = start_expanded ? ConsoleOutput::Type::BeginGroup : ConsoleOutput::Type::BeginGroupCollapsed, .data = label });
m_client->did_output_js_console_message(m_message_log.size() - 1);
}
void InspectorConsoleClient::end_group()
{
m_message_log.append({ .type = ConsoleOutput::Type::EndGroup, .data = String {} });
m_client->did_output_js_console_message(m_message_log.size() - 1);
}
void InspectorConsoleClient::clear()
{
m_message_log.append({ .type = ConsoleOutput::Type::Clear, .data = String {} });
m_client->did_output_js_console_message(m_message_log.size() - 1);
}
void InspectorConsoleClient::print_html(String const& line)
{
m_message_log.append({ .type = ConsoleOutput::Type::HTML, .data = line });
m_client->did_output_js_console_message(m_message_log.size() - 1);
}
void InspectorConsoleClient::send_messages(i32 start_index)
{
auto messages_to_send = m_message_log.size() - start_index;
if (messages_to_send < 1) {
// When the console is first created, it requests any messages that happened before then, by requesting with
// start_index=0. If we don't have any messages at all, that is still a valid request, and we can just ignore it.
if (start_index != 0)
m_client->console_peer_did_misbehave("Requested non-existent console message index");
return;
}
// FIXME: Replace with a single Vector of message structs
Vector<String> message_types;
Vector<String> messages;
message_types.ensure_capacity(messages_to_send);
messages.ensure_capacity(messages_to_send);
for (size_t i = start_index; i < m_message_log.size(); i++) {
auto& message = m_message_log[i];
switch (message.type) {
case ConsoleOutput::Type::HTML:
message_types.append("html"_string);
break;
case ConsoleOutput::Type::Clear:
message_types.append("clear"_string);
break;
case ConsoleOutput::Type::BeginGroup:
message_types.append("group"_string);
break;
case ConsoleOutput::Type::BeginGroupCollapsed:
message_types.append("groupCollapsed"_string);
break;
case ConsoleOutput::Type::EndGroup:
message_types.append("groupEnd"_string);
break;
}
messages.append(message.data);
}
m_client->did_get_styled_js_console_messages(start_index, message_types, messages);
}
// 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
JS::ThrowCompletionOr<JS::Value> InspectorConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments)
{
auto styling = escape_html_entities(m_current_message_style.string_view());
m_current_message_style.clear();
if (log_level == JS::Console::LogLevel::Table) {
auto& vm = m_console->realm().vm();
auto table_args = arguments.get<GC::RootVector<JS::Value>>();
auto& table = table_args.at(0).as_object();
auto& columns = TRY(table.get(vm.names.columns)).as_array().indexed_properties();
auto& rows = TRY(table.get(vm.names.rows)).as_array().indexed_properties();
StringBuilder html;
html.appendff("<div class=\"console-log-table\">");
html.appendff("<table>");
html.appendff("<thead>");
html.appendff("<tr>");
for (auto const& col : columns) {
auto index = col.index();
auto value = columns.storage()->get(index).value().value;
html.appendff("<td>{}</td>", value);
}
html.appendff("</tr>");
html.appendff("</thead>");
html.appendff("<tbody>");
for (auto const& row : rows) {
auto row_index = row.index();
auto& row_obj = rows.storage()->get(row_index).value().value.as_object();
html.appendff("<tr>");
for (auto const& col : columns) {
auto col_index = col.index();
auto col_name = columns.storage()->get(col_index).value().value;
auto property_key = TRY(JS::PropertyKey::from_value(vm, col_name));
auto cell = TRY(row_obj.get(property_key));
html.appendff("<td>");
if (TRY(cell.is_array(vm))) {
AllocatingMemoryStream stream;
JS::PrintContext ctx { vm, stream, true };
TRY_OR_THROW_OOM(vm, stream.write_until_depleted(" "sv.bytes()));
TRY_OR_THROW_OOM(vm, JS::print(cell, ctx));
auto output = TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
auto size = cell.as_array().indexed_properties().array_like_size();
html.appendff("<details><summary>Array({})</summary>{}</details>", size, output);
} else if (cell.is_object()) {
AllocatingMemoryStream stream;
JS::PrintContext ctx { vm, stream, true };
TRY_OR_THROW_OOM(vm, stream.write_until_depleted(" "sv.bytes()));
TRY_OR_THROW_OOM(vm, JS::print(cell, ctx));
auto output = TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
html.appendff("<details><summary>Object({{...}})</summary>{}</details>", output);
} else if (cell.is_function() || cell.is_constructor()) {
html.appendff("ƒ");
} else if (!cell.is_undefined()) {
html.appendff("{}", cell);
}
html.appendff("</td>");
}
html.appendff("</tr>");
}
html.appendff("</tbody>");
html.appendff("</table>");
html.appendff("</div>");
print_html(MUST(html.to_string()));
auto output = TRY(generically_format_values(table_args));
m_console->output_debug_message(log_level, output);
return JS::js_undefined();
}
if (log_level == JS::Console::LogLevel::Trace) {
auto trace = arguments.get<JS::Console::Trace>();
StringBuilder html;
if (!trace.label.is_empty())
html.appendff("<span class='title' style='{}'>{}</span><br>", styling, escape_html_entities(trace.label));
html.append("<span class='trace'>"sv);
for (auto& function_name : trace.stack)
html.appendff("-> {}<br>", escape_html_entities(function_name));
html.append("</span>"sv);
print_html(MUST(html.to_string()));
return JS::js_undefined();
}
if (log_level == JS::Console::LogLevel::Group || log_level == JS::Console::LogLevel::GroupCollapsed) {
auto group = arguments.get<JS::Console::Group>();
begin_group(MUST(String::formatted("<span style='{}'>{}</span>", styling, escape_html_entities(group.label))), log_level == JS::Console::LogLevel::Group);
return JS::js_undefined();
}
auto output = TRY(generically_format_values(arguments.get<GC::RootVector<JS::Value>>()));
m_console->output_debug_message(log_level, output);
StringBuilder html;
switch (log_level) {
case JS::Console::LogLevel::Debug:
html.appendff("<span class=\"debug\" style=\"{}\">(d) "sv, styling);
break;
case JS::Console::LogLevel::Error:
html.appendff("<span class=\"error\" style=\"{}\">(e) "sv, styling);
break;
case JS::Console::LogLevel::Info:
html.appendff("<span class=\"info\" style=\"{}\">(i) "sv, styling);
break;
case JS::Console::LogLevel::Log:
html.appendff("<span class=\"log\" style=\"{}\"> "sv, styling);
break;
case JS::Console::LogLevel::Warn:
case JS::Console::LogLevel::CountReset:
html.appendff("<span class=\"warn\" style=\"{}\">(w) "sv, styling);
break;
default:
html.appendff("<span style=\"{}\">"sv, styling);
break;
}
html.append(escape_html_entities(output));
html.append("</span>"sv);
print_html(MUST(html.to_string()));
return JS::js_undefined();
}
}

View file

@ -1,65 +0,0 @@
/*
* Copyright (c) 2021, Brandon Scott <xeon.productions@gmail.com>
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
* Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/String.h>
#include <AK/StringBuilder.h>
#include <AK/Vector.h>
#include <LibWeb/Forward.h>
#include <WebContent/Forward.h>
#include <WebContent/WebContentConsoleClient.h>
namespace WebContent {
class InspectorConsoleClient final : public WebContentConsoleClient {
GC_CELL(InspectorConsoleClient, WebContentConsoleClient);
GC_DECLARE_ALLOCATOR(InspectorConsoleClient);
public:
static GC::Ref<InspectorConsoleClient> create(JS::Realm&, JS::Console&, PageClient&);
virtual ~InspectorConsoleClient() override;
private:
InspectorConsoleClient(JS::Realm&, JS::Console&, PageClient&, ConsoleGlobalEnvironmentExtensions&);
virtual void handle_result(JS::Value) override;
virtual void report_exception(JS::Error const&, bool) override;
void begin_group(String const& label, bool start_expanded);
virtual void end_group() override;
virtual void clear() override;
void print_html(String const& line);
virtual void send_messages(i32 start_index) override;
virtual JS::ThrowCompletionOr<JS::Value> printer(JS::Console::LogLevel log_level, PrinterArguments) override;
virtual void add_css_style_to_current_message(StringView style) override
{
m_current_message_style.append(style);
m_current_message_style.append(';');
}
struct ConsoleOutput {
enum class Type {
HTML,
Clear,
BeginGroup,
BeginGroupCollapsed,
EndGroup,
};
Type type;
String data;
};
Vector<ConsoleOutput> m_message_log;
StringBuilder m_current_message_style;
};
}

View file

@ -9,30 +9,25 @@
#include <AK/JsonObjectSerializer.h>
#include <AK/JsonValue.h>
#include <LibCore/Timer.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibJS/Console.h>
#include <LibJS/Runtime/ConsoleObject.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/CSS/CSSImportRule.h>
#include <LibWeb/Cookie/ParsedCookie.h>
#include <LibWeb/DOM/Attr.h>
#include <LibWeb/DOM/CharacterData.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/MutationType.h>
#include <LibWeb/DOM/NamedNodeMap.h>
#include <LibWeb/DOM/NodeList.h>
#include <LibWeb/HTML/HTMLLinkElement.h>
#include <LibWeb/HTML/HTMLStyleElement.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWebView/Attribute.h>
#include <LibWebView/SiteIsolation.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/DevToolsConsoleClient.h>
#include <WebContent/InspectorConsoleClient.h>
#include <WebContent/PageClient.h>
#include <WebContent/PageHost.h>
#include <WebContent/WebContentClientEndpoint.h>
@ -42,7 +37,6 @@ namespace WebContent {
static PageClient::UseSkiaPainter s_use_skia_painter = PageClient::UseSkiaPainter::GPUBackendIfAvailable;
static bool s_is_headless { false };
static bool s_devtools_enabled { false };
GC_DEFINE_ALLOCATOR(PageClient);
@ -61,11 +55,6 @@ void PageClient::set_is_headless(bool is_headless)
s_is_headless = is_headless;
}
void PageClient::set_devtools_enabled(bool devtools_enabled)
{
s_devtools_enabled = devtools_enabled;
}
GC::Ref<PageClient> PageClient::create(JS::VM& vm, PageHost& page_host, u64 id)
{
return vm.heap().allocate<PageClient>(page_host, id);
@ -710,76 +699,6 @@ void PageClient::page_did_mutate_dom(FlyString const& type, Web::DOM::Node const
client().async_did_mutate_dom(m_id, { type.to_string(), target.unique_id(), move(serialized_target), mutation.release_value() });
}
void PageClient::inspector_did_load()
{
client().async_inspector_did_load(m_id);
}
void PageClient::inspector_did_select_dom_node(Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> const& pseudo_element)
{
client().async_inspector_did_select_dom_node(m_id, node_id, pseudo_element);
}
void PageClient::inspector_did_set_dom_node_text(Web::UniqueNodeID node_id, String const& text)
{
client().async_inspector_did_set_dom_node_text(m_id, node_id, text);
}
void PageClient::inspector_did_set_dom_node_tag(Web::UniqueNodeID node_id, String const& tag)
{
client().async_inspector_did_set_dom_node_tag(m_id, node_id, tag);
}
static Vector<WebView::Attribute> named_node_map_to_vector(GC::Ref<Web::DOM::NamedNodeMap> map)
{
Vector<WebView::Attribute> attributes;
attributes.ensure_capacity(map->length());
for (size_t i = 0; i < map->length(); ++i) {
auto const* attribute = map->item(i);
VERIFY(attribute);
attributes.empend(attribute->name().to_string(), attribute->value());
}
return attributes;
}
void PageClient::inspector_did_add_dom_node_attributes(Web::UniqueNodeID node_id, GC::Ref<Web::DOM::NamedNodeMap> attributes)
{
client().async_inspector_did_add_dom_node_attributes(m_id, node_id, named_node_map_to_vector(attributes));
}
void PageClient::inspector_did_replace_dom_node_attribute(Web::UniqueNodeID node_id, size_t attribute_index, GC::Ref<Web::DOM::NamedNodeMap> replacement_attributes)
{
client().async_inspector_did_replace_dom_node_attribute(m_id, node_id, attribute_index, named_node_map_to_vector(replacement_attributes));
}
void PageClient::inspector_did_request_dom_tree_context_menu(Web::UniqueNodeID node_id, Web::CSSPixelPoint position, String const& type, Optional<String> const& tag, Optional<size_t> const& attribute_index)
{
client().async_inspector_did_request_dom_tree_context_menu(m_id, node_id, page().css_to_device_point(position).to_type<int>(), type, tag, attribute_index);
}
void PageClient::inspector_did_request_cookie_context_menu(size_t cookie_index, Web::CSSPixelPoint position)
{
client().async_inspector_did_request_cookie_context_menu(m_id, cookie_index, page().css_to_device_point(position).to_type<int>());
}
void PageClient::inspector_did_request_style_sheet_source(Web::CSS::StyleSheetIdentifier const& identifier)
{
client().async_inspector_did_request_style_sheet_source(m_id, identifier);
}
void PageClient::inspector_did_execute_console_script(String const& script)
{
client().async_inspector_did_execute_console_script(m_id, script);
}
void PageClient::inspector_did_export_inspector_html(String const& html)
{
client().async_inspector_did_export_inspector_html(m_id, html);
}
ErrorOr<void> PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path)
{
VERIFY(!m_webdriver);
@ -796,12 +715,7 @@ void PageClient::initialize_js_console(Web::DOM::Document& document)
auto& realm = document.realm();
auto console_object = realm.intrinsics().console_object();
GC::Ptr<JS::ConsoleClient> console_client;
if (s_devtools_enabled)
console_client = DevToolsConsoleClient::create(document.realm(), console_object->console(), *this);
else
console_client = InspectorConsoleClient::create(document.realm(), console_object->console(), *this);
auto console_client = DevToolsConsoleClient::create(document.realm(), console_object->console(), *this);
document.set_console_client(console_client);
}

View file

@ -38,8 +38,6 @@ public:
virtual bool is_headless() const override;
static void set_is_headless(bool);
static void set_devtools_enabled(bool);
virtual bool is_ready_to_paint() const override;
virtual Web::Page& page() override { return *m_page; }
@ -176,17 +174,6 @@ private:
virtual void page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) override;
virtual IPC::File request_worker_agent() override;
virtual void page_did_mutate_dom(FlyString const& type, Web::DOM::Node const& target, Web::DOM::NodeList& added_nodes, Web::DOM::NodeList& removed_nodes, GC::Ptr<Web::DOM::Node> previous_sibling, GC::Ptr<Web::DOM::Node> next_sibling, Optional<String> const& attribute_name) override;
virtual void inspector_did_load() override;
virtual void inspector_did_select_dom_node(Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type> const& pseudo_element) override;
virtual void inspector_did_set_dom_node_text(Web::UniqueNodeID, String const& text) override;
virtual void inspector_did_set_dom_node_tag(Web::UniqueNodeID, String const& tag) override;
virtual void inspector_did_add_dom_node_attributes(Web::UniqueNodeID, GC::Ref<Web::DOM::NamedNodeMap> attributes) override;
virtual void inspector_did_replace_dom_node_attribute(Web::UniqueNodeID, size_t attribute_index, GC::Ref<Web::DOM::NamedNodeMap> replacement_attributes) override;
virtual void inspector_did_request_dom_tree_context_menu(Web::UniqueNodeID, Web::CSSPixelPoint position, String const& type, Optional<String> const& tag, Optional<size_t> const& attribute_index) override;
virtual void inspector_did_request_cookie_context_menu(size_t cookie_index, Web::CSSPixelPoint position) override;
virtual void inspector_did_request_style_sheet_source(Web::CSS::StyleSheetIdentifier const& stylesheet_source) override;
virtual void inspector_did_execute_console_script(String const& script) override;
virtual void inspector_did_export_inspector_html(String const& script) override;
Web::Layout::Viewport* layout_root();
void setup_palette();

View file

@ -59,8 +59,7 @@ endpoint WebContentClient
did_mutate_dom(u64 page_id, WebView::Mutation mutation) =|
did_get_dom_node_html(u64 page_id, String html) =|
inspector_did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> style_sheets) =|
inspector_did_request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier) =|
did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> style_sheets) =|
did_get_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier, URL::URL base_url, String source) =|
did_take_screenshot(u64 page_id, Gfx::ShareableBitmap screenshot) =|
@ -109,16 +108,4 @@ endpoint WebContentClient
did_find_in_page(u64 page_id, size_t current_match_index, Optional<size_t> total_match_count) =|
request_worker_agent(u64 page_id) => (IPC::File socket) // FIXME: Add required attributes to select a SharedWorker Agent
inspector_did_load(u64 page_id) =|
inspector_did_select_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) =|
inspector_did_set_dom_node_text(u64 page_id, Web::UniqueNodeID node_id, String text) =|
inspector_did_set_dom_node_tag(u64 page_id, Web::UniqueNodeID node_id, String tag) =|
inspector_did_add_dom_node_attributes(u64 page_id, Web::UniqueNodeID node_id, Vector<WebView::Attribute> attributes) =|
inspector_did_replace_dom_node_attribute(u64 page_id, Web::UniqueNodeID node_id, size_t attribute_index, Vector<WebView::Attribute> replacement_attributes) =|
inspector_did_request_dom_tree_context_menu(u64 page_id, Web::UniqueNodeID node_id, Gfx::IntPoint position, String type, Optional<String> tag, Optional<size_t> attribute_index) =|
inspector_did_request_cookie_context_menu(u64 page_id, size_t cookie_index, Gfx::IntPoint position) =|
inspector_did_execute_console_script(u64 page_id, String script) =|
inspector_did_export_inspector_html(u64 page_id, String html) =|
}

View file

@ -123,7 +123,5 @@ endpoint WebContentServer
set_user_style(u64 page_id, String source) =|
enable_inspector_prototype(u64 page_id) =|
system_time_zone_changed() =|
}

View file

@ -109,7 +109,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
bool collect_garbage_on_every_allocation = false;
bool is_headless = false;
bool disable_scrollbar_painting = false;
bool devtools = false;
StringView echo_server_port_string_view {};
Core::ArgsParser args_parser;
@ -133,7 +132,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
args_parser.add_option(disable_scrollbar_painting, "Don't paint horizontal or vertical viewport scrollbars", "disable-scrollbar-painting");
args_parser.add_option(echo_server_port_string_view, "Echo server port used in test internals", "echo-server-port", 0, "echo_server_port");
args_parser.add_option(is_headless, "Report that the browser is running in headless mode", "headless");
args_parser.add_option(devtools, "Report that the browser is running with Firefox DevTools support", "devtools");
args_parser.parse(arguments);
@ -160,7 +158,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
WebContent::PageClient::set_use_skia_painter(force_cpu_painting ? WebContent::PageClient::UseSkiaPainter::CPUBackend : WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable);
WebContent::PageClient::set_is_headless(is_headless);
WebContent::PageClient::set_devtools_enabled(devtools);
if (disable_site_isolation)
WebView::disable_site_isolation();

View file

@ -61,11 +61,6 @@ list(TRANSFORM 48x48_ICONS PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/icons/48x48/
list(TRANSFORM 128x128_ICONS PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/icons/128x128/")
list(TRANSFORM BROWSER_ICONS PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/icons/browser/")
set(WEB_RESOURCES
inspector.css
inspector.html
inspector.js
)
set(ABOUT_PAGES
about.html
newtab.html
@ -75,7 +70,6 @@ set(WEB_TEMPLATES
error.html
version.html
)
list(TRANSFORM WEB_RESOURCES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/")
list(TRANSFORM ABOUT_PAGES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/about-pages/")
list(TRANSFORM WEB_TEMPLATES PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/ladybird/templates/")
@ -161,10 +155,6 @@ function(copy_resources_to_build base_directory bundle_target)
DESTINATION ${base_directory} TARGET ${bundle_target}
)
copy_resource_set(ladybird RESOURCES ${WEB_RESOURCES}
DESTINATION ${base_directory} TARGET ${bundle_target}
)
copy_resource_set(ladybird/about-pages RESOURCES ${ABOUT_PAGES}
DESTINATION ${base_directory} TARGET ${bundle_target}
)
@ -191,7 +181,6 @@ function(install_ladybird_resources destination component)
install(FILES ${128x128_ICONS} DESTINATION "${destination}/icons/128x128" COMPONENT ${component})
install(FILES ${BROWSER_ICONS} DESTINATION "${destination}/icons/browser" COMPONENT ${component})
install(FILES ${THEMES} DESTINATION "${destination}/themes" COMPONENT ${component})
install(FILES ${WEB_RESOURCES} DESTINATION "${destination}/ladybird" COMPONENT ${component})
install(FILES ${ABOUT_PAGES} DESTINATION "${destination}/ladybird/about-pages" COMPONENT ${component})
install(FILES ${WEB_TEMPLATES} DESTINATION "${destination}/ladybird/templates" COMPONENT ${component})
install(FILES ${CONFIG_RESOURCES} DESTINATION "${destination}/ladybird/default-config" COMPONENT ${component})