mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-22 17:29:01 +00:00
LibWeb+LibWebView+WebContent: Add basic find in page functionality
This allows the browser to send a query to the WebContent process, which will search the page for the given string and highlight any occurrences of that string.
This commit is contained in:
parent
b08d43a5d3
commit
7aea87c9df
Notes:
sideshowbarker
2024-07-16 18:06:41 +09:00
Author: https://github.com/tcl3
Commit: 7aea87c9df
Pull-request: https://github.com/SerenityOS/serenity/pull/24480
Reviewed-by: https://github.com/awesomekling
9 changed files with 197 additions and 0 deletions
|
@ -5068,4 +5068,33 @@ void Document::set_needs_to_refresh_scroll_state(bool b)
|
|||
paintable->set_needs_to_refresh_scroll_state(b);
|
||||
}
|
||||
|
||||
Vector<JS::Handle<DOM::Range>> Document::find_matching_text(String const& query)
|
||||
{
|
||||
if (!document_element() || !document_element()->layout_node())
|
||||
return {};
|
||||
|
||||
Vector<JS::Handle<DOM::Range>> matches;
|
||||
document_element()->layout_node()->for_each_in_inclusive_subtree_of_type<Layout::TextNode>([&](auto const& text_node) {
|
||||
auto const& text = text_node.text_for_rendering();
|
||||
size_t offset = 0;
|
||||
while (true) {
|
||||
auto match_index = text.find_byte_offset(query, offset);
|
||||
if (!match_index.has_value())
|
||||
break;
|
||||
|
||||
auto range = create_range();
|
||||
auto& dom_node = const_cast<DOM::Text&>(text_node.dom_node());
|
||||
(void)range->set_start(dom_node, match_index.value());
|
||||
(void)range->set_end(dom_node, match_index.value() + query.code_points().length());
|
||||
|
||||
matches.append(range);
|
||||
offset = match_index.value() + 1;
|
||||
}
|
||||
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -667,6 +667,8 @@ public:
|
|||
// Does document represent an embedded svg img
|
||||
[[nodiscard]] bool is_decoded_svg() const;
|
||||
|
||||
Vector<JS::Handle<DOM::Range>> find_matching_text(String const&);
|
||||
|
||||
protected:
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Ledbetter <timledbetter@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -11,6 +12,7 @@
|
|||
#include <LibIPC/Encoder.h>
|
||||
#include <LibWeb/CSS/StyleComputer.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
|
@ -20,8 +22,10 @@
|
|||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/HTML/SelectedFile.h>
|
||||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
#include <LibWeb/Selection/Selection.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
|
@ -44,6 +48,7 @@ void Page::visit_edges(JS::Cell::Visitor& visitor)
|
|||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_top_level_traversable);
|
||||
visitor.visit(m_client);
|
||||
visitor.visit(m_find_in_page_matches);
|
||||
}
|
||||
|
||||
HTML::Navigable& Page::focused_navigable()
|
||||
|
@ -519,6 +524,104 @@ void Page::set_user_style(String source)
|
|||
}
|
||||
}
|
||||
|
||||
void Page::clear_selection()
|
||||
{
|
||||
auto documents = HTML::main_thread_event_loop().documents_in_this_event_loop();
|
||||
for (auto const& document : documents) {
|
||||
if (&document->page() != this)
|
||||
continue;
|
||||
|
||||
auto selection = document->get_selection();
|
||||
if (!selection)
|
||||
continue;
|
||||
|
||||
selection->remove_all_ranges();
|
||||
}
|
||||
}
|
||||
|
||||
void Page::find_in_page(String const& query)
|
||||
{
|
||||
m_find_in_page_match_index = 0;
|
||||
|
||||
if (query.is_empty()) {
|
||||
m_find_in_page_matches = {};
|
||||
update_find_in_page_selection();
|
||||
return;
|
||||
}
|
||||
|
||||
auto documents = HTML::main_thread_event_loop().documents_in_this_event_loop();
|
||||
Vector<JS::Handle<DOM::Range>> all_matches;
|
||||
for (auto const& document : documents) {
|
||||
if (&document->page() != this)
|
||||
continue;
|
||||
|
||||
auto matches = document->find_matching_text(query);
|
||||
all_matches.extend(move(matches));
|
||||
}
|
||||
|
||||
m_find_in_page_matches.clear_with_capacity();
|
||||
for (auto& match : all_matches)
|
||||
m_find_in_page_matches.append(*match);
|
||||
|
||||
update_find_in_page_selection();
|
||||
}
|
||||
|
||||
void Page::find_in_page_next_match()
|
||||
{
|
||||
if (m_find_in_page_matches.is_empty())
|
||||
return;
|
||||
|
||||
if (m_find_in_page_match_index == m_find_in_page_matches.size() - 1) {
|
||||
m_find_in_page_match_index = 0;
|
||||
} else {
|
||||
m_find_in_page_match_index++;
|
||||
}
|
||||
|
||||
update_find_in_page_selection();
|
||||
}
|
||||
|
||||
void Page::find_in_page_previous_match()
|
||||
{
|
||||
if (m_find_in_page_matches.is_empty())
|
||||
return;
|
||||
|
||||
if (m_find_in_page_match_index == 0) {
|
||||
m_find_in_page_match_index = m_find_in_page_matches.size() - 1;
|
||||
} else {
|
||||
m_find_in_page_match_index--;
|
||||
}
|
||||
|
||||
update_find_in_page_selection();
|
||||
}
|
||||
|
||||
void Page::update_find_in_page_selection()
|
||||
{
|
||||
clear_selection();
|
||||
|
||||
if (m_find_in_page_matches.is_empty())
|
||||
return;
|
||||
|
||||
auto current_range = m_find_in_page_matches[m_find_in_page_match_index];
|
||||
auto common_ancestor_container = current_range->common_ancestor_container();
|
||||
auto& document = common_ancestor_container->document();
|
||||
if (!document.window())
|
||||
return;
|
||||
|
||||
auto selection = document.get_selection();
|
||||
if (!selection)
|
||||
return;
|
||||
|
||||
selection->add_range(*current_range);
|
||||
|
||||
if (auto* element = common_ancestor_container->parent_element()) {
|
||||
DOM::ScrollIntoViewOptions scroll_options;
|
||||
scroll_options.block = Bindings::ScrollLogicalPosition::Nearest;
|
||||
scroll_options.inline_ = Bindings::ScrollLogicalPosition::Nearest;
|
||||
scroll_options.behavior = Bindings::ScrollBehavior::Instant;
|
||||
(void)element->scroll_into_view(scroll_options);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
|
|
|
@ -178,12 +178,20 @@ public:
|
|||
|
||||
bool pdf_viewer_supported() const { return m_pdf_viewer_supported; }
|
||||
|
||||
void clear_selection();
|
||||
|
||||
void find_in_page(String const& query);
|
||||
void find_in_page_next_match();
|
||||
void find_in_page_previous_match();
|
||||
|
||||
private:
|
||||
explicit Page(JS::NonnullGCPtr<PageClient>);
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
JS::GCPtr<HTML::HTMLMediaElement> media_context_menu_element();
|
||||
|
||||
void update_find_in_page_selection();
|
||||
|
||||
JS::NonnullGCPtr<PageClient> m_client;
|
||||
|
||||
WeakPtr<HTML::Navigable> m_focused_navigable;
|
||||
|
@ -225,6 +233,8 @@ private:
|
|||
// Spec Note: This value also impacts the navigation processing model.
|
||||
// FIXME: Actually support pdf viewing
|
||||
bool m_pdf_viewer_supported { false };
|
||||
size_t m_find_in_page_match_index { 0 };
|
||||
Vector<JS::NonnullGCPtr<DOM::Range>> m_find_in_page_matches;
|
||||
};
|
||||
|
||||
struct PaintOptions {
|
||||
|
|
|
@ -180,6 +180,21 @@ void ViewImplementation::paste(String const& text)
|
|||
client().async_paste(page_id(), text);
|
||||
}
|
||||
|
||||
void ViewImplementation::find_in_page(String const& query)
|
||||
{
|
||||
client().async_find_in_page(page_id(), query);
|
||||
}
|
||||
|
||||
void ViewImplementation::find_in_page_next_match()
|
||||
{
|
||||
client().async_find_in_page_next_match(page_id());
|
||||
}
|
||||
|
||||
void ViewImplementation::find_in_page_previous_match()
|
||||
{
|
||||
client().async_find_in_page_previous_match(page_id());
|
||||
}
|
||||
|
||||
void ViewImplementation::get_source()
|
||||
{
|
||||
client().async_get_source(page_id());
|
||||
|
|
|
@ -66,6 +66,9 @@ public:
|
|||
ByteString selected_text();
|
||||
Optional<String> selected_text_with_whitespace_collapsed();
|
||||
void select_all();
|
||||
void find_in_page(String const& query);
|
||||
void find_in_page_next_match();
|
||||
void find_in_page_previous_match();
|
||||
void paste(String const&);
|
||||
|
||||
void get_source();
|
||||
|
|
|
@ -827,6 +827,33 @@ void ConnectionFromClient::select_all(u64 page_id)
|
|||
page->page().focused_navigable().select_all();
|
||||
}
|
||||
|
||||
void ConnectionFromClient::find_in_page(u64 page_id, String const& query)
|
||||
{
|
||||
auto page = this->page(page_id);
|
||||
if (!page.has_value())
|
||||
return;
|
||||
|
||||
page->page().find_in_page(query);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::find_in_page_next_match(u64 page_id)
|
||||
{
|
||||
auto page = this->page(page_id);
|
||||
if (!page.has_value())
|
||||
return;
|
||||
|
||||
page->page().find_in_page_next_match();
|
||||
}
|
||||
|
||||
void ConnectionFromClient::find_in_page_previous_match(u64 page_id)
|
||||
{
|
||||
auto page = this->page(page_id);
|
||||
if (!page.has_value())
|
||||
return;
|
||||
|
||||
page->page().find_in_page_previous_match();
|
||||
}
|
||||
|
||||
void ConnectionFromClient::paste(u64 page_id, String const& text)
|
||||
{
|
||||
if (auto page = this->page(page_id); page.has_value())
|
||||
|
|
|
@ -130,6 +130,10 @@ private:
|
|||
virtual Messages::WebContentServer::GetSelectedTextResponse get_selected_text(u64 page_id) override;
|
||||
virtual void select_all(u64 page_id) override;
|
||||
|
||||
virtual void find_in_page(u64 page_id, String const& query) override;
|
||||
virtual void find_in_page_next_match(u64 page_id) override;
|
||||
virtual void find_in_page_previous_match(u64 page_id) override;
|
||||
|
||||
virtual void paste(u64 page_id, String const& text) override;
|
||||
|
||||
void report_finished_handling_input_event(u64 page_id, bool event_was_handled);
|
||||
|
|
|
@ -69,6 +69,10 @@ endpoint WebContentServer
|
|||
select_all(u64 page_id) =|
|
||||
paste(u64 page_id, String text) =|
|
||||
|
||||
find_in_page(u64 page_id, String query) =|
|
||||
find_in_page_next_match(u64 page_id) =|
|
||||
find_in_page_previous_match(u64 page_id) =|
|
||||
|
||||
set_content_filters(u64 page_id, Vector<String> filters) =|
|
||||
set_autoplay_allowed_on_all_websites(u64 page_id) =|
|
||||
set_autoplay_allowlist(u64 page_id, Vector<String> allowlist) =|
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue