diff --git a/Ladybird/AppKit/UI/Inspector.mm b/Ladybird/AppKit/UI/Inspector.mm index cabaad4dd7..22ccaed02d 100644 --- a/Ladybird/AppKit/UI/Inspector.mm +++ b/Ladybird/AppKit/UI/Inspector.mm @@ -23,6 +23,7 @@ static constexpr CGFloat const WINDOW_HEIGHT = 825; static constexpr NSInteger CONTEXT_MENU_EDIT_NODE_TAG = 1; static constexpr NSInteger CONTEXT_MENU_REMOVE_ATTRIBUTE_TAG = 2; +static constexpr NSInteger CONTEXT_MENU_COPY_ATTRIBUTE_VALUE_TAG = 3; @interface Inspector () { @@ -98,8 +99,13 @@ static constexpr NSInteger CONTEXT_MENU_REMOVE_ATTRIBUTE_TAG = 2; return; } + static constexpr size_t MAX_ATTRIBUTE_VALUE_LENGTH = 32; + auto edit_attribute_text = MUST(String::formatted("Edit attribute \"{}\"", attribute.name)); auto remove_attribute_text = MUST(String::formatted("Remove attribute \"{}\"", attribute.name)); + auto copy_attribute_value_text = MUST(String::formatted("Copy attribute value \"{:.{}}{}\"", + attribute.value, MAX_ATTRIBUTE_VALUE_LENGTH, + attribute.value.bytes_as_string_view().length() > MAX_ATTRIBUTE_VALUE_LENGTH ? "..."sv : ""sv)); auto* edit_node_menu_item = [strong_self.dom_node_attribute_context_menu itemWithTag:CONTEXT_MENU_EDIT_NODE_TAG]; [edit_node_menu_item setTitle:Ladybird::string_to_ns_string(edit_attribute_text)]; @@ -107,6 +113,9 @@ static constexpr NSInteger CONTEXT_MENU_REMOVE_ATTRIBUTE_TAG = 2; auto* remove_attribute_menu_item = [strong_self.dom_node_attribute_context_menu itemWithTag:CONTEXT_MENU_REMOVE_ATTRIBUTE_TAG]; [remove_attribute_menu_item setTitle:Ladybird::string_to_ns_string(remove_attribute_text)]; + auto* copy_attribute_value_menu_item = [strong_self.dom_node_attribute_context_menu itemWithTag:CONTEXT_MENU_COPY_ATTRIBUTE_VALUE_TAG]; + [copy_attribute_value_menu_item setTitle:Ladybird::string_to_ns_string(copy_attribute_value_text)]; + auto* event = Ladybird::create_context_menu_mouse_event(strong_self.web_view, position); [NSMenu popUpContextMenu:strong_self.dom_node_attribute_context_menu withEvent:event forView:strong_self.web_view]; }; @@ -172,6 +181,11 @@ static constexpr NSInteger CONTEXT_MENU_REMOVE_ATTRIBUTE_TAG = 2; m_inspector_client->context_menu_remove_dom_node_attribute(); } +- (void)copyDOMAttributeValue:(id)sender +{ + m_inspector_client->context_menu_copy_dom_node_attribute_value(); +} + #pragma mark - Properties - (NSMenu*)dom_node_text_context_menu @@ -234,6 +248,12 @@ static constexpr NSInteger CONTEXT_MENU_REMOVE_ATTRIBUTE_TAG = 2; [remove_attribute_menu_item setTag:CONTEXT_MENU_REMOVE_ATTRIBUTE_TAG]; [_dom_node_attribute_context_menu addItem:remove_attribute_menu_item]; + auto* copy_attribute_value_menu_item = [[NSMenuItem alloc] initWithTitle:@"Copy attribute value" + action:@selector(copyDOMAttributeValue:) + keyEquivalent:@""]; + [copy_attribute_value_menu_item setTag:CONTEXT_MENU_COPY_ATTRIBUTE_VALUE_TAG]; + [_dom_node_attribute_context_menu addItem:copy_attribute_value_menu_item]; + [_dom_node_attribute_context_menu addItem:[NSMenuItem separatorItem]]; [_dom_node_attribute_context_menu addItem:[[NSMenuItem alloc] initWithTitle:@"Add attribute" diff --git a/Ladybird/Qt/InspectorWidget.cpp b/Ladybird/Qt/InspectorWidget.cpp index aae1cf2336..6077889999 100644 --- a/Ladybird/Qt/InspectorWidget.cpp +++ b/Ladybird/Qt/InspectorWidget.cpp @@ -39,6 +39,9 @@ InspectorWidget::InspectorWidget(QWidget* tab, WebContentView& content_view) m_remove_attribute_action = new QAction("&Remove attribute", this); connect(m_remove_attribute_action, &QAction::triggered, [this]() { m_inspector_client->context_menu_remove_dom_node_attribute(); }); + m_copy_attribute_value_action = new QAction("Copy attribute &value", this); + connect(m_copy_attribute_value_action, &QAction::triggered, [this]() { m_inspector_client->context_menu_copy_dom_node_attribute_value(); }); + m_dom_node_text_context_menu = new QMenu("DOM text context menu", this); m_dom_node_text_context_menu->addAction(m_edit_node_action); m_dom_node_text_context_menu->addSeparator(); @@ -52,6 +55,7 @@ InspectorWidget::InspectorWidget(QWidget* tab, WebContentView& content_view) m_dom_node_attribute_context_menu = new QMenu("DOM attribute context menu", this); m_dom_node_attribute_context_menu->addAction(m_edit_node_action); + m_dom_node_attribute_context_menu->addAction(m_copy_attribute_value_action); m_dom_node_attribute_context_menu->addAction(m_remove_attribute_action); m_dom_node_attribute_context_menu->addSeparator(); m_dom_node_attribute_context_menu->addAction(m_add_attribute_action); @@ -69,9 +73,14 @@ InspectorWidget::InspectorWidget(QWidget* tab, WebContentView& content_view) m_dom_node_tag_context_menu->exec(to_widget_position(position)); }; - m_inspector_client->on_requested_dom_node_attribute_context_menu = [this](auto position, auto const&, auto const& attribute) { + m_inspector_client->on_requested_dom_node_attribute_context_menu = [this](auto position, auto const&, WebView::Attribute const& attribute) { + static constexpr size_t MAX_ATTRIBUTE_VALUE_LENGTH = 32; + m_edit_node_action->setText(qstring_from_ak_string(MUST(String::formatted("&Edit attribute \"{}\"", attribute.name)))); m_remove_attribute_action->setText(qstring_from_ak_string(MUST(String::formatted("&Remove attribute \"{}\"", attribute.name)))); + m_copy_attribute_value_action->setText(qstring_from_ak_string(MUST(String::formatted("Copy attribute &value \"{:.{}}{}\"", + attribute.value, MAX_ATTRIBUTE_VALUE_LENGTH, + attribute.value.bytes_as_string_view().length() > MAX_ATTRIBUTE_VALUE_LENGTH ? "..."sv : ""sv)))); m_dom_node_attribute_context_menu->exec(to_widget_position(position)); }; diff --git a/Ladybird/Qt/InspectorWidget.h b/Ladybird/Qt/InspectorWidget.h index 1951be8aa8..b3f476e966 100644 --- a/Ladybird/Qt/InspectorWidget.h +++ b/Ladybird/Qt/InspectorWidget.h @@ -47,6 +47,7 @@ private: QAction* m_delete_node_action { nullptr }; QAction* m_add_attribute_action { nullptr }; QAction* m_remove_attribute_action { nullptr }; + QAction* m_copy_attribute_value_action { nullptr }; }; } diff --git a/Userland/Applications/Browser/InspectorWidget.cpp b/Userland/Applications/Browser/InspectorWidget.cpp index 122db4b1e1..e03d2ab74c 100644 --- a/Userland/Applications/Browser/InspectorWidget.cpp +++ b/Userland/Applications/Browser/InspectorWidget.cpp @@ -33,6 +33,7 @@ InspectorWidget::InspectorWidget(WebView::OutOfProcessWebView& content_view) m_delete_node_action = GUI::Action::create("&Delete node"sv, [this](auto&) { m_inspector_client->context_menu_remove_dom_node(); }); m_add_attribute_action = GUI::Action::create("&Add attribute"sv, [this](auto&) { m_inspector_client->context_menu_add_dom_node_attribute(); }); m_remove_attribute_action = GUI::Action::create("&Remove attribute"sv, [this](auto&) { m_inspector_client->context_menu_remove_dom_node_attribute(); }); + m_copy_attribute_value_action = GUI::Action::create("Copy attribute &value"sv, [this](auto&) { m_inspector_client->context_menu_copy_dom_node_attribute_value(); }); m_dom_node_text_context_menu = GUI::Menu::construct(); m_dom_node_text_context_menu->add_action(*m_edit_node_action); @@ -47,6 +48,7 @@ InspectorWidget::InspectorWidget(WebView::OutOfProcessWebView& content_view) m_dom_node_attribute_context_menu = GUI::Menu::construct(); m_dom_node_attribute_context_menu->add_action(*m_edit_node_action); + m_dom_node_attribute_context_menu->add_action(*m_copy_attribute_value_action); m_dom_node_attribute_context_menu->add_action(*m_remove_attribute_action); m_dom_node_attribute_context_menu->add_separator(); m_dom_node_attribute_context_menu->add_action(*m_add_attribute_action); @@ -65,8 +67,13 @@ InspectorWidget::InspectorWidget(WebView::OutOfProcessWebView& content_view) }; m_inspector_client->on_requested_dom_node_attribute_context_menu = [this](auto position, auto const&, auto const& attribute) { + static constexpr size_t MAX_ATTRIBUTE_VALUE_LENGTH = 32; + m_edit_node_action->set_text(DeprecatedString::formatted("&Edit attribute \"{}\"", attribute.name)); m_remove_attribute_action->set_text(DeprecatedString::formatted("&Remove attribute \"{}\"", attribute.name)); + m_copy_attribute_value_action->set_text(DeprecatedString::formatted("Copy attribute &value \"{:.{}}{}\"", + attribute.value, MAX_ATTRIBUTE_VALUE_LENGTH, + attribute.value.bytes_as_string_view().length() > MAX_ATTRIBUTE_VALUE_LENGTH ? "..."sv : ""sv)); m_dom_node_attribute_context_menu->popup(to_widget_position(position)); }; diff --git a/Userland/Applications/Browser/InspectorWidget.h b/Userland/Applications/Browser/InspectorWidget.h index 90623d2e99..99e6f9d1da 100644 --- a/Userland/Applications/Browser/InspectorWidget.h +++ b/Userland/Applications/Browser/InspectorWidget.h @@ -43,6 +43,7 @@ private: RefPtr m_delete_node_action; RefPtr m_add_attribute_action; RefPtr m_remove_attribute_action; + RefPtr m_copy_attribute_value_action; }; } diff --git a/Userland/Libraries/LibWebView/InspectorClient.cpp b/Userland/Libraries/LibWebView/InspectorClient.cpp index fee639a97c..0da75d8672 100644 --- a/Userland/Libraries/LibWebView/InspectorClient.cpp +++ b/Userland/Libraries/LibWebView/InspectorClient.cpp @@ -268,6 +268,17 @@ void InspectorClient::context_menu_remove_dom_node_attribute() 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::load_inspector() { StringBuilder builder; diff --git a/Userland/Libraries/LibWebView/InspectorClient.h b/Userland/Libraries/LibWebView/InspectorClient.h index 5e1cce5cf1..81f78f276d 100644 --- a/Userland/Libraries/LibWebView/InspectorClient.h +++ b/Userland/Libraries/LibWebView/InspectorClient.h @@ -30,6 +30,7 @@ public: 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(); Function on_requested_dom_node_text_context_menu; Function on_requested_dom_node_tag_context_menu;