diff --git a/Base/res/html/inspector/inspector.css b/Base/res/html/inspector/inspector.css index b753072ea9e..65757f98057 100644 --- a/Base/res/html/inspector/inspector.css +++ b/Base/res/html/inspector/inspector.css @@ -137,6 +137,11 @@ details > :not(:first-child) { padding: 1px; } +.dom-editor { + width: fit-content; + outline: none; +} + @media (prefers-color-scheme: dark) { .hoverable:hover { background-color: #31383e; diff --git a/Base/res/html/inspector/inspector.js b/Base/res/html/inspector/inspector.js index 3c00040c53e..44f4bcd15a3 100644 --- a/Base/res/html/inspector/inspector.js +++ b/Base/res/html/inspector/inspector.js @@ -96,6 +96,15 @@ inspector.loadDOMTree = tree => { event.preventDefault(); }); } + + domNodes = domTree.querySelectorAll(".editable"); + + for (let domNode of domNodes) { + domNode.addEventListener("dblclick", event => { + editDOMNode(domNode); + event.preventDefault(); + }); + } }; inspector.loadAccessibilityTree = tree => { @@ -166,6 +175,64 @@ const inspectDOMNode = domNode => { inspector.inspectDOMNode(domNode.dataset.id, domNode.dataset.pseudoElement); }; +const editDOMNode = domNode => { + if (selectedDOMNode === null) { + return; + } + + const domNodeID = selectedDOMNode.dataset.id; + const type = domNode.dataset.nodeType; + + selectedDOMNode.classList.remove("selected"); + + let input = document.createElement("input"); + input.classList.add("dom-editor"); + input.classList.add("selected"); + input.value = domNode.innerText; + + const handleChange = () => { + input.removeEventListener("change", handleChange); + input.removeEventListener("blur", cancelChange); + + if (type === "text" || type === "comment") { + inspector.setDOMNodeText(domNodeID, input.value); + } else if (type === "tag") { + try { + const element = document.createElement(input.value); + inspector.setDOMNodeTag(domNodeID, input.value); + } catch { + cancelChange(); + } + } else if (type === "attribute") { + let element = document.createElement("div"); + element.innerHTML = `
`; + + inspector.replaceDOMNodeAttribute( + domNodeID, + domNode.dataset.attributeName, + element.children[0].attributes + ); + } + }; + + const cancelChange = () => { + selectedDOMNode.classList.add("selected"); + input.parentNode.replaceChild(domNode, input); + }; + + input.addEventListener("change", handleChange); + input.addEventListener("blur", cancelChange); + + domNode.parentNode.replaceChild(input, domNode); + + setTimeout(() => { + input.focus(); + + // FIXME: Invoke `select` when it isn't just stubbed out. + // input.select(); + }); +}; + const executeConsoleScript = consoleInput => { const script = consoleInput.value; diff --git a/Userland/Libraries/LibWebView/InspectorClient.cpp b/Userland/Libraries/LibWebView/InspectorClient.cpp index e2b0ed82e1b..6cd5c382656 100644 --- a/Userland/Libraries/LibWebView/InspectorClient.cpp +++ b/Userland/Libraries/LibWebView/InspectorClient.cpp @@ -109,6 +109,25 @@ InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImple m_inspector_web_view.run_javascript(builder.string_view()); }; + 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_pending_selection = node_id; + inspect(); + }; + + m_inspector_web_view.on_inspector_set_dom_node_tag = [this](auto node_id, auto const& tag) { + m_pending_selection = m_content_web_view.set_dom_node_tag(node_id, tag); + inspect(); + }; + + m_inspector_web_view.on_inspector_replaced_dom_node_attribute = [this](auto node_id, auto const& name, auto const& replacement_attributes) { + m_content_web_view.replace_dom_node_attribute(node_id, name, replacement_attributes); + + m_pending_selection = node_id; + inspect(); + }; + m_inspector_web_view.on_inspector_executed_console_script = [this](auto const& script) { append_console_source(script); @@ -128,6 +147,7 @@ InspectorClient::~InspectorClient() void InspectorClient::inspect() { + m_dom_tree_loaded = false; m_content_web_view.inspect_dom_tree(); m_content_web_view.inspect_accessibility_tree(); } @@ -326,7 +346,7 @@ String InspectorClient::generate_dom_tree(JsonObject const& dom_tree) builder.append(name); builder.append(""sv); } else { - builder.appendff("", data_attributes.string_view()); + builder.appendff("", data_attributes.string_view()); builder.append(text); builder.append(""sv); } @@ -338,7 +358,7 @@ String InspectorClient::generate_dom_tree(JsonObject const& dom_tree) auto comment = node.get_deprecated_string("data"sv).release_value(); comment = escape_html_entities(comment); - builder.appendff("", data_attributes.string_view()); + builder.appendff("", data_attributes.string_view()); builder.appendff("<!--{}-->", comment); builder.append(""sv); return; @@ -365,14 +385,16 @@ String InspectorClient::generate_dom_tree(JsonObject const& dom_tree) builder.appendff("", data_attributes.string_view()); builder.append("<"sv); - builder.appendff("{}", name.to_lowercase()); + builder.appendff("{}", name.to_lowercase()); if (auto attributes = node.get_object("attributes"sv); attributes.has_value()) { attributes->for_each_member([&builder](auto const& name, auto const& value) { builder.append(" "sv); + builder.appendff("", name); builder.appendff("{}", name); builder.append('='); builder.appendff("\"{}\"", value); + builder.append(""sv); }); }