diff --git a/Base/res/html/inspector/inspector.css b/Base/res/html/inspector/inspector.css new file mode 100644 index 0000000000..5774c4d5ca --- /dev/null +++ b/Base/res/html/inspector/inspector.css @@ -0,0 +1,169 @@ +body { + font-family: system-ui, sans-serif; + font-size: 10pt; + + margin: 0; +} + +.split-view { + width: 100vw; + height: 100vh; +} + +.split-view-container { + overflow: scroll; +} + +.tab-controls-container { + position: absolute; + width: 100%; + + padding: 4px; + + display: flex; + align-items: center; + justify-content: center; + + z-index: 100; +} + +.tab-controls { + overflow: hidden; + flex-shrink: 0; +} + +.tab-controls button { + 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-content { + height: 100%; + + display: none; + border-radius: 0.5rem; + + margin-top: 30px; + padding: 8px; +} + +@media (prefers-color-scheme: dark) { + html { + background-color: rgb(23, 23, 23); + } + + .tab-controls-container { + background-color: rgb(57, 57, 57); + } + + .tab-controls button { + color: white; + background-color: rgb(43, 42, 50); + } + + .tab-controls button.active { + background-color: rgb(22 100 219); + } + + .tab-controls button + button { + border-left: 1px solid rgb(96, 96, 96); + } +} + +@media (prefers-color-scheme: light) { + .tab-controls-container { + background-color: rgb(229, 229, 229); + } + + .tab-controls button { + color: black; + background-color: white; + } + + .tab-controls button.active { + color: white; + background-color: rgb(28, 138, 255); + } + + .tab-controls button + button { + border-left: 1px solid rgb(242, 242, 242); + } +} + +details > :not(:first-child) { + display: list-item; + list-style: none inside; + margin-left: 1em; +} + +.hoverable { + display: block; + padding: 1px; +} + +@media (prefers-color-scheme: dark) { + .hoverable:hover { + background-color: #31383e; + } + + .selected { + border: 1px dashed cyan; + padding: 0; + } +} + +@media (prefers-color-scheme: light) { + .hoverable:hover { + background-color: rgb(236, 236, 236); + } + + .selected { + border: 1px dashed blue; + padding: 0; + } +} + +.property-table { + width: 100%; + + table-layout: fixed; + border-collapse: collapse; +} + +.property-table th { + position: sticky; + top: 0px; +} + +.property-table th, +.property-table td { + padding: 4px; + text-align: left; +} + +@media (prefers-color-scheme: dark) { + .property-table th { + background-color: rgb(57, 57, 57); + } +} + +@media (prefers-color-scheme: light) { + .property-table th { + background-color: rgb(229, 229, 229); + } +} diff --git a/Base/res/html/inspector/inspector.js b/Base/res/html/inspector/inspector.js new file mode 100644 index 0000000000..b948cd0ec3 --- /dev/null +++ b/Base/res/html/inspector/inspector.js @@ -0,0 +1,136 @@ +let selectedTopTab = null; +let selectedTopTabButton = null; + +let selectedBottomTab = null; +let selectedBottomTabButton = null; + +let selectedDOMNode = null; + +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"); + + 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("computed-style-button"); +selectBottomTab(initialBottomTabButton, "computed-style"); + +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.loadDOMTree = tree => { + let domTree = document.getElementById("dom-tree"); + domTree.innerHTML = atob(tree); + + let domNodes = domTree.getElementsByClassName("hoverable"); + + for (let domNode of domNodes) { + domNode.addEventListener("click", event => { + inspectDOMNode(domNode); + event.preventDefault(); + }); + } +}; + +inspector.loadAccessibilityTree = tree => { + let accessibilityTree = document.getElementById("accessibility-tree"); + accessibilityTree.innerHTML = atob(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.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() + .forEach(name => { + let row = newTable.insertRow(); + + let nameColumn = row.insertCell(); + nameColumn.innerText = name; + + let valueColumn = row.insertCell(); + valueColumn.innerText = properties[name]; + }); + + 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)); +}; + +const inspectDOMNode = domNode => { + if (selectedDOMNode === domNode) { + return; + } + + inspector.clearInspectedDOMNode(); + + domNode.classList.add("selected"); + selectedDOMNode = domNode; + + inspector.inspectDOMNode(domNode.dataset.id, domNode.dataset.pseudoElement); +}; + +document.addEventListener("DOMContentLoaded", () => { + inspector.inspectorLoaded(); +}); diff --git a/Userland/Libraries/LibWebView/InspectorClient.cpp b/Userland/Libraries/LibWebView/InspectorClient.cpp index fb802f6344..0c47de1739 100644 --- a/Userland/Libraries/LibWebView/InspectorClient.cpp +++ b/Userland/Libraries/LibWebView/InspectorClient.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,10 @@ void InspectorClient::load_inspector() { StringBuilder builder; + // FIXME: Teach LibWeb how to load resource:// URIs instead of needing to read these files here. + auto inspector_css = MUST(Core::Resource::load_from_uri("resource://html/inspector/inspector.css"sv)); + auto inspector_js = MUST(Core::Resource::load_from_uri("resource://html/inspector/inspector.js"sv)); + builder.append(R"~~~( @@ -164,177 +169,9 @@ void InspectorClient::load_inspector() )~~~"sv); builder.append(HTML_HIGHLIGHTER_STYLE); + builder.append(inspector_css->data()); builder.append(R"~~~( - body { - font-family: system-ui, sans-serif; - font-size: 10pt; - - margin: 0; - } - - .split-view { - width: 100vw; - height: 100vh; - } - - .split-view-container { - overflow: scroll; - } - - .tab-controls-container { - position: absolute; - width: 100%; - - padding: 4px; - - display: flex; - align-items: center; - justify-content: center; - - z-index: 100; - } - - .tab-controls { - overflow: hidden; - flex-shrink: 0; - } - - .tab-controls button { - 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-content { - height: 100%; - - display: none; - border-radius: 0.5rem; - - margin-top: 30px; - padding: 8px; - } - - @media (prefers-color-scheme: dark) { - html { - background-color: rgb(23, 23, 23); - } - - .tab-controls-container { - background-color: rgb(57, 57, 57); - } - - .tab-controls button { - color: white; - background-color: rgb(43, 42, 50); - } - - .tab-controls button.active { - background-color: rgb(22 100 219); - } - - .tab-controls button + button { - border-left: 1px solid rgb(96, 96, 96); - } - } - - @media (prefers-color-scheme: light) { - .tab-controls-container { - background-color: rgb(229, 229, 229); - } - - .tab-controls button { - color: black; - background-color: white; - } - - .tab-controls button.active { - color: white; - background-color: rgb(28, 138, 255); - } - - .tab-controls button + button { - border-left: 1px solid rgb(242, 242, 242); - } - } - - details > :not(:first-child) { - display: list-item; - list-style: none inside; - margin-left: 1em; - } - - .hoverable { - display: block; - padding: 1px; - } - - @media (prefers-color-scheme: dark) { - .hoverable:hover { - background-color: #31383e; - } - - .selected { - border: 1px dashed cyan; - padding: 0; - } - } - - @media (prefers-color-scheme: light) { - .hoverable:hover { - background-color: rgb(236, 236, 236); - } - - .selected { - border: 1px dashed blue; - padding: 0; - } - } - - .property-table { - width: 100%; - - table-layout: fixed; - border-collapse: collapse; - } - - .property-table th { - position: sticky; - top: 0px; - } - - .property-table th, - .property-table td { - padding: 4px; - text-align: left; - } - - @media (prefers-color-scheme: dark) { - .property-table th { - background-color: rgb(57, 57, 57); - } - } - - @media (prefers-color-scheme: light) { - .property-table th { - background-color: rgb(229, 229, 229); - } - } @@ -386,140 +223,11 @@ void InspectorClient::load_inspector()