mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 12:48:10 +00:00
LibWeb: Bring handling of anchor elements closer to spec
This commit moves the regular handling of links to the anchor elements' activation behavior, and implements a few auxiliary algorithms as defined by the HTML specification. Note that certain things such as javascript links, fragments and opening a new tab are still handled directly in EventHandler, but they have been moved to handle_mouseup so that it behaves closer to how it would if it was entirely up-to-spec.
This commit is contained in:
parent
034c57f1f9
commit
aaa954f900
8 changed files with 357 additions and 40 deletions
|
@ -428,4 +428,118 @@ RefPtr<DOM::Node> BrowsingContext::currently_focused_area()
|
|||
return candidate;
|
||||
}
|
||||
|
||||
BrowsingContext* BrowsingContext::choose_a_browsing_context(StringView name, bool)
|
||||
{
|
||||
// The rules for choosing a browsing context, given a browsing context name
|
||||
// name, a browsing context current, and a boolean noopener are as follows:
|
||||
|
||||
// 1. Let chosen be null.
|
||||
BrowsingContext* chosen = nullptr;
|
||||
|
||||
// FIXME: 2. Let windowType be "existing or none".
|
||||
|
||||
// FIXME: 3. Let sandboxingFlagSet be current's active document's active
|
||||
// sandboxing flag set.
|
||||
|
||||
// 4. If name is the empty string or an ASCII case-insensitive match for "_self", then set chosen to current.
|
||||
if (name.is_empty() || name.equals_ignoring_case("_self"sv))
|
||||
chosen = this;
|
||||
|
||||
// 5. Otherwise, if name is an ASCII case-insensitive match for "_parent",
|
||||
// set chosen to current's parent browsing context, if any, and current
|
||||
// otherwise.
|
||||
if (name.equals_ignoring_case("_parent"sv)) {
|
||||
if (auto* parent = this->parent())
|
||||
chosen = parent;
|
||||
else
|
||||
chosen = this;
|
||||
}
|
||||
|
||||
// 6. Otherwise, if name is an ASCII case-insensitive match for "_top", set
|
||||
// chosen to current's top-level browsing context, if any, and current
|
||||
// otherwise.
|
||||
if (name.equals_ignoring_case("_top"sv)) {
|
||||
chosen = &top_level_browsing_context();
|
||||
}
|
||||
|
||||
// FIXME: 7. Otherwise, if name is not an ASCII case-insensitive match for
|
||||
// "_blank", there exists a browsing context whose name is the same as name,
|
||||
// current is familiar with that browsing context, and the user agent
|
||||
// determines that the two browsing contexts are related enough that it is
|
||||
// ok if they reach each other, set chosen to that browsing context. If
|
||||
// there are multiple matching browsing contexts, the user agent should set
|
||||
// chosen to one in some arbitrary consistent manner, such as the most
|
||||
// recently opened, most recently focused, or more closely related.
|
||||
if (!name.equals_ignoring_case("_blank"sv)) {
|
||||
chosen = this;
|
||||
} else {
|
||||
// 8. Otherwise, a new browsing context is being requested, and what
|
||||
// happens depends on the user agent's configuration and abilities — it
|
||||
// is determined by the rules given for the first applicable option from
|
||||
// the following list:
|
||||
dbgln("FIXME: Create a new browsing context!");
|
||||
|
||||
// --> If current's active window does not have transient activation and
|
||||
// the user agent has been configured to not show popups (i.e., the
|
||||
// user agent has a "popup blocker" enabled)
|
||||
//
|
||||
// The user agent may inform the user that a popup has been blocked.
|
||||
|
||||
// --> If sandboxingFlagSet has the sandboxed auxiliary navigation
|
||||
// browsing context flag set
|
||||
//
|
||||
// The user agent may report to a developer console that a popup has
|
||||
// been blocked.
|
||||
|
||||
// --> If the user agent has been configured such that in this instance
|
||||
// it will create a new browsing context
|
||||
//
|
||||
// 1. Set windowType to "new and unrestricted".
|
||||
|
||||
// 2. If current's top-level browsing context's active document's
|
||||
// cross-origin opener policy's value is "same-origin" or
|
||||
// "same-origin-plus-COEP", then:
|
||||
|
||||
// 2.1. Let currentDocument be current's active document.
|
||||
|
||||
// 2.2. If currentDocument's origin is not same origin with
|
||||
// currentDocument's relevant settings object's top-level
|
||||
// origin, then set noopener to true, name to "_blank", and
|
||||
// windowType to "new with no opener".
|
||||
|
||||
// 3. If noopener is true, then set chosen to the result of creating
|
||||
// a new top-level browsing context.
|
||||
|
||||
// 4. Otherwise:
|
||||
|
||||
// 4.1. Set chosen to the result of creating a new auxiliary
|
||||
// browsing context with current.
|
||||
|
||||
// 4.2. If sandboxingFlagSet's sandboxed navigation browsing
|
||||
// context flag is set, then current must be set as chosen's one
|
||||
// permitted sandboxed navigator.
|
||||
|
||||
// 5. If sandboxingFlagSet's sandbox propagates to auxiliary
|
||||
// browsing contexts flag is set, then all the flags that are set in
|
||||
// sandboxingFlagSet must be set in chosen's popup sandboxing flag
|
||||
// set.
|
||||
|
||||
// 6. If name is not an ASCII case-insensitive match for "_blank",
|
||||
// then set chosen's name to name.
|
||||
|
||||
// --> If the user agent has been configured such that in this instance
|
||||
// it will reuse current
|
||||
//
|
||||
// Set chosen to current.
|
||||
|
||||
// --> If the user agent has been configured such that in this instance
|
||||
// it will not find a browsing context
|
||||
//
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// 9. Return chosen and windowType.
|
||||
return chosen;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -76,6 +76,8 @@ public:
|
|||
|
||||
BrowsingContext const& top_level_browsing_context() const { return const_cast<BrowsingContext*>(this)->top_level_browsing_context(); }
|
||||
|
||||
BrowsingContext* choose_a_browsing_context(StringView name, bool noopener);
|
||||
|
||||
HTML::BrowsingContextContainer* container() { return m_container; }
|
||||
HTML::BrowsingContextContainer const* container() const { return m_container; }
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@ namespace Web::HTML {
|
|||
HTMLAnchorElement::HTMLAnchorElement(DOM::Document& document, DOM::QualifiedName qualified_name)
|
||||
: HTMLElement(document, move(qualified_name))
|
||||
{
|
||||
activation_behavior = [this](auto const& event) {
|
||||
run_activation_behavior(event);
|
||||
};
|
||||
}
|
||||
|
||||
HTMLAnchorElement::~HTMLAnchorElement() = default;
|
||||
|
@ -33,4 +36,42 @@ void HTMLAnchorElement::set_hyperlink_element_utils_href(String href)
|
|||
set_attribute(HTML::AttributeNames::href, move(href));
|
||||
}
|
||||
|
||||
void HTMLAnchorElement::run_activation_behavior(Web::DOM::Event const&)
|
||||
{
|
||||
// The activation behavior of an a element element given an event event is:
|
||||
|
||||
// 1. If element has no href attribute, then return.
|
||||
if (href().is_empty())
|
||||
return;
|
||||
|
||||
// 2. Let hyperlinkSuffix be null.
|
||||
Optional<String> hyperlink_suffix {};
|
||||
|
||||
// FIXME: 3. If event's target is an img with an ismap attribute
|
||||
// specified, then:
|
||||
// 3.1. Let x and y be 0.
|
||||
//
|
||||
// 3.2. If event's isTrusted attribute is initialized to true, then
|
||||
// set x to the distance in CSS pixels from the left edge of the image
|
||||
// to the location of the click, and set y to the distance in CSS
|
||||
// pixels from the top edge of the image to the location of the click.
|
||||
//
|
||||
// 3.3. If x is negative, set x to 0.
|
||||
//
|
||||
// 3.4. If y is negative, set y to 0.
|
||||
//
|
||||
// 3.5. Set hyperlinkSuffix to the concatenation of U+003F (?), the
|
||||
// value of x expressed as a base-ten integer using ASCII digits,
|
||||
// U+002C (,), and the value of y expressed as a base-ten integer
|
||||
// using ASCII digits.
|
||||
|
||||
// FIXME: 4. If element has a download attribute, or if the user has
|
||||
// expressed a preference to download the hyperlink, then download the
|
||||
// hyperlink created by element given hyperlinkSuffix.
|
||||
|
||||
// 5. Otherwise, follow the hyperlink created by element given
|
||||
// hyperlinkSuffix.
|
||||
follow_the_hyperlink(hyperlink_suffix);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,19 +21,29 @@ public:
|
|||
virtual ~HTMLAnchorElement() override;
|
||||
|
||||
String target() const { return attribute(HTML::AttributeNames::target); }
|
||||
String download() const { return attribute(HTML::AttributeNames::download); }
|
||||
|
||||
virtual bool is_focusable() const override { return has_attribute(HTML::AttributeNames::href); }
|
||||
|
||||
virtual bool is_html_anchor_element() const override { return true; }
|
||||
|
||||
private:
|
||||
void run_activation_behavior(Web::DOM::Event const&);
|
||||
|
||||
// ^DOM::Element
|
||||
virtual void parse_attribute(FlyString const& name, String const& value) override;
|
||||
|
||||
// ^HTML::HTMLHyperlinkElementUtils
|
||||
virtual DOM::Document const& hyperlink_element_utils_document() const override { return document(); }
|
||||
virtual DOM::Document& hyperlink_element_utils_document() override { return document(); }
|
||||
virtual String hyperlink_element_utils_href() const override;
|
||||
virtual void set_hyperlink_element_utils_href(String) override;
|
||||
virtual bool hyperlink_element_utils_is_html_anchor_element() const final { return true; }
|
||||
virtual bool hyperlink_element_utils_is_connected() const final { return is_connected(); }
|
||||
virtual String hyperlink_element_utils_target() const final { return target(); }
|
||||
virtual void hyperlink_element_utils_queue_an_element_task(HTML::Task::Source source, Function<void()> steps) override
|
||||
{
|
||||
queue_an_element_task(source, move(steps));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -26,9 +26,16 @@ private:
|
|||
virtual void parse_attribute(FlyString const& name, String const& value) override;
|
||||
|
||||
// ^HTML::HTMLHyperlinkElementUtils
|
||||
virtual DOM::Document const& hyperlink_element_utils_document() const override { return document(); }
|
||||
virtual DOM::Document& hyperlink_element_utils_document() override { return document(); }
|
||||
virtual String hyperlink_element_utils_href() const override;
|
||||
virtual void set_hyperlink_element_utils_href(String) override;
|
||||
virtual bool hyperlink_element_utils_is_html_anchor_element() const override { return false; }
|
||||
virtual bool hyperlink_element_utils_is_connected() const override { return is_connected(); }
|
||||
virtual String hyperlink_element_utils_target() const override { return ""; }
|
||||
virtual void hyperlink_element_utils_queue_an_element_task(HTML::Task::Source source, Function<void()> steps) override
|
||||
{
|
||||
queue_an_element_task(source, move(steps));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <AK/URLParser.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/HTMLHyperlinkElementUtils.h>
|
||||
#include <LibWeb/Loader/FrameLoader.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -449,4 +450,125 @@ void HTMLHyperlinkElementUtils::update_href()
|
|||
// To update href, set the element's href content attribute's value to the element's url, serialized.
|
||||
}
|
||||
|
||||
bool HTMLHyperlinkElementUtils::cannot_navigate() const
|
||||
{
|
||||
// An element element cannot navigate if one of the following is true:
|
||||
|
||||
// 1. element's node document is not fully active
|
||||
auto const& document = const_cast<HTMLHyperlinkElementUtils*>(this)->hyperlink_element_utils_document();
|
||||
if (!document.is_fully_active())
|
||||
return true;
|
||||
|
||||
// 2. element is not an a element and is not connected.
|
||||
if (!hyperlink_element_utils_is_html_anchor_element() && !hyperlink_element_utils_is_connected())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/links.html#following-hyperlinks-2
|
||||
void HTMLHyperlinkElementUtils::follow_the_hyperlink(Optional<String> hyperlink_suffix)
|
||||
{
|
||||
// To follow the hyperlink created by an element subject, given an optional hyperlinkSuffix (default null):
|
||||
|
||||
// 1. If subject cannot navigate, then return.
|
||||
if (cannot_navigate())
|
||||
return;
|
||||
|
||||
// FIXME: 2. Let replace be false.
|
||||
|
||||
// 3. Let source be subject's node document's browsing context.
|
||||
auto* source = hyperlink_element_utils_document().browsing_context();
|
||||
if (!source)
|
||||
return;
|
||||
|
||||
// 4. Let targetAttributeValue be the empty string.
|
||||
// 5. If subject is an a or area element, then set targetAttributeValue to
|
||||
// the result of getting an element's target given subject.
|
||||
String target_attribute_value = get_an_elements_target();
|
||||
|
||||
// 6. Let noopener be the result of getting an element's noopener with subject and targetAttributeValue.
|
||||
bool noopener = get_an_elements_noopener(target_attribute_value);
|
||||
|
||||
// 7. Let target be the first return value of applying the rules for
|
||||
// choosing a browsing context given targetAttributeValue, source, and
|
||||
// noopener.
|
||||
auto* target = source->choose_a_browsing_context(target_attribute_value, noopener);
|
||||
|
||||
// 8. If target is null, then return.
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// 9. Parse a URL given subject's href attribute, relative to subject's node
|
||||
// document.
|
||||
auto url = source->active_document()->parse_url(href());
|
||||
|
||||
// 10. If that is successful, let URL be the resulting URL string.
|
||||
auto url_string = url.to_string();
|
||||
|
||||
// 11. Otherwise, if parsing the URL failed, the user agent may report the
|
||||
// error to the user in a user-agent-specific manner, may queue an element
|
||||
// task on the DOM manipulation task source given subject to navigate the
|
||||
// target browsing context to an error page to report the error, or may
|
||||
// ignore the error and do nothing. In any case, the user agent must then
|
||||
// return.
|
||||
|
||||
// 12. If hyperlinkSuffix is non-null, then append it to URL.
|
||||
if (hyperlink_suffix.has_value()) {
|
||||
StringBuilder url_builder;
|
||||
url_builder.append(url_string);
|
||||
url_builder.append(*hyperlink_suffix);
|
||||
|
||||
url_string = url_builder.to_string();
|
||||
}
|
||||
|
||||
// FIXME: 13. Let request be a new request whose URL is URL and whose
|
||||
// referrer policy is the current state of subject's referrerpolicy content
|
||||
// attribute.
|
||||
|
||||
// FIXME: 14. If subject's link types includes the noreferrer keyword, then
|
||||
// set request's referrer to "no-referrer".
|
||||
|
||||
// 15. Queue an element task on the DOM manipulation task source given
|
||||
// subject to navigate target to request with the source browsing context
|
||||
// set to source.
|
||||
// FIXME: "navigate" means implementing the navigation algorithm here:
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
|
||||
hyperlink_element_utils_queue_an_element_task(Task::Source::DOMManipulation, [url_string, target] {
|
||||
target->loader().load(url_string, FrameLoader::Type::Navigation);
|
||||
});
|
||||
}
|
||||
|
||||
String HTMLHyperlinkElementUtils::get_an_elements_target() const
|
||||
{
|
||||
// To get an element's target, given an a, area, or form element element, run these steps:
|
||||
|
||||
// 1. If element has a target attribute, then return that attribute's value.
|
||||
if (auto target = hyperlink_element_utils_target(); !target.is_empty())
|
||||
return target;
|
||||
|
||||
// FIXME: 2. If element's node document contains a base element with a
|
||||
// target attribute, then return the value of the target attribute of the
|
||||
// first such base element.
|
||||
|
||||
// 3. Return the empty string.
|
||||
return "";
|
||||
}
|
||||
|
||||
bool HTMLHyperlinkElementUtils::get_an_elements_noopener(StringView target) const
|
||||
{
|
||||
// To get an element's noopener, given an a, area, or form element element and a string target:
|
||||
|
||||
// FIXME: 1. If element's link types include the noopener or noreferrer
|
||||
// keyword, then return true.
|
||||
|
||||
// FIXME: 2. If element's link types do not include the opener keyword and
|
||||
// target is an ASCII case-insensitive match for "_blank", then return true.
|
||||
if (target.equals_ignoring_case("_blank"sv))
|
||||
return true;
|
||||
|
||||
// 3. Return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <AK/URL.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/HTML/EventLoop/Task.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -48,15 +49,23 @@ public:
|
|||
void set_hash(String);
|
||||
|
||||
protected:
|
||||
virtual DOM::Document const& hyperlink_element_utils_document() const = 0;
|
||||
virtual DOM::Document& hyperlink_element_utils_document() = 0;
|
||||
virtual String hyperlink_element_utils_href() const = 0;
|
||||
virtual void set_hyperlink_element_utils_href(String) = 0;
|
||||
virtual bool hyperlink_element_utils_is_html_anchor_element() const = 0;
|
||||
virtual bool hyperlink_element_utils_is_connected() const = 0;
|
||||
virtual String hyperlink_element_utils_target() const = 0;
|
||||
virtual void hyperlink_element_utils_queue_an_element_task(HTML::Task::Source source, Function<void()> steps) = 0;
|
||||
|
||||
void set_the_url();
|
||||
void follow_the_hyperlink(Optional<String> hyperlink_suffix);
|
||||
|
||||
private:
|
||||
void reinitialize_url() const;
|
||||
void update_href();
|
||||
bool cannot_navigate() const;
|
||||
String get_an_elements_target() const;
|
||||
bool get_an_elements_noopener(StringView target) const;
|
||||
|
||||
Optional<AK::URL> m_url;
|
||||
};
|
||||
|
|
|
@ -194,7 +194,46 @@ bool EventHandler::handle_mouseup(const Gfx::IntPoint& position, unsigned button
|
|||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::mouseup, offset.x(), offset.y(), position.x(), position.y()));
|
||||
handled_event = true;
|
||||
|
||||
if (node.ptr() == m_mousedown_target) {
|
||||
bool should_dispatch_event = true;
|
||||
|
||||
// FIXME: This is ad-hoc and incorrect. The reason this exists is
|
||||
// because we are missing browsing context navigation:
|
||||
//
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
|
||||
//
|
||||
// Additionally, we currently cannot spawn a new top-level
|
||||
// browsing context for new tab operations, because the new
|
||||
// top-level browsing context would be in another process. To
|
||||
// fix this, there needs to be some way to be able to
|
||||
// communicate with browsing contexts in remote WebContent
|
||||
// processes, and then step 8 of this algorithm needs to be
|
||||
// implemented in BrowsingContext::choose_a_browsing_context:
|
||||
//
|
||||
// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
|
||||
if (RefPtr<HTML::HTMLAnchorElement> link = node->enclosing_link_element()) {
|
||||
NonnullRefPtr document = *m_browsing_context.active_document();
|
||||
auto href = link->href();
|
||||
auto url = document->parse_url(href);
|
||||
dbgln("Web::EventHandler: Clicking on a link to {}", url);
|
||||
if (button == GUI::MouseButton::Primary) {
|
||||
if (href.starts_with("javascript:")) {
|
||||
document->run_javascript(href.substring_view(11, href.length() - 11));
|
||||
} else if (!url.fragment().is_null() && url.equals(document->url(), AK::URL::ExcludeFragment::Yes)) {
|
||||
m_browsing_context.scroll_to_anchor(url.fragment());
|
||||
} else if (modifiers != 0) {
|
||||
if (m_browsing_context.is_top_level()) {
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_click_link(url, link->target(), modifiers);
|
||||
}
|
||||
}
|
||||
} else if (button == GUI::MouseButton::Middle) {
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_middle_click_link(url, link->target(), modifiers);
|
||||
should_dispatch_event = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.ptr() == m_mousedown_target && should_dispatch_event) {
|
||||
node->dispatch_event(UIEvents::MouseEvent::create(UIEvents::EventNames::click, offset.x(), offset.y(), position.x(), position.y()));
|
||||
}
|
||||
}
|
||||
|
@ -272,37 +311,10 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
|
|||
return true;
|
||||
}
|
||||
|
||||
if (RefPtr<HTML::HTMLAnchorElement> link = node->enclosing_link_element()) {
|
||||
auto href = link->href();
|
||||
auto url = document->parse_url(href);
|
||||
dbgln("Web::EventHandler: Clicking on a link to {}", url);
|
||||
if (button == GUI::MouseButton::Primary) {
|
||||
if (href.starts_with("javascript:")) {
|
||||
document->run_javascript(href.substring_view(11, href.length() - 11));
|
||||
} else if (!url.fragment().is_null() && url.equals(document->url(), AK::URL::ExcludeFragment::Yes)) {
|
||||
m_browsing_context.scroll_to_anchor(url.fragment());
|
||||
} else {
|
||||
document->set_active_element(link);
|
||||
if (m_browsing_context.is_top_level()) {
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_click_link(url, link->target(), modifiers);
|
||||
} else {
|
||||
// FIXME: Handle different targets!
|
||||
m_browsing_context.loader().load(url, FrameLoader::Type::Navigation);
|
||||
}
|
||||
}
|
||||
} else if (button == GUI::MouseButton::Secondary) {
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_request_link_context_menu(m_browsing_context.to_top_level_position(position), url, link->target(), modifiers);
|
||||
} else if (button == GUI::MouseButton::Middle) {
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_middle_click_link(url, link->target(), modifiers);
|
||||
}
|
||||
} else {
|
||||
if (button == GUI::MouseButton::Primary) {
|
||||
auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::TextCursor);
|
||||
if (result.has_value() && result->dom_node()) {
|
||||
|
||||
if (button == GUI::MouseButton::Primary) {
|
||||
if (auto result = paint_root()->hit_test(position.to_type<float>(), Painting::HitTestType::TextCursor); result.has_value()) {
|
||||
auto paintable = result->paintable;
|
||||
if (paintable->dom_node()) {
|
||||
// See if we want to focus something.
|
||||
bool did_focus_something = false;
|
||||
for (auto candidate = node; candidate; candidate = candidate->parent()) {
|
||||
|
@ -316,15 +328,15 @@ bool EventHandler::handle_mousedown(const Gfx::IntPoint& position, unsigned butt
|
|||
// If we didn't focus anything, place the document text cursor at the mouse position.
|
||||
// FIXME: This is all rather strange. Find a better solution.
|
||||
if (!did_focus_something) {
|
||||
m_browsing_context.set_cursor_position(DOM::Position(*result->dom_node(), result->index_in_node));
|
||||
layout_root()->set_selection({ { result->paintable->layout_node(), result->index_in_node }, {} });
|
||||
m_browsing_context.set_cursor_position(DOM::Position(*paintable->dom_node(), result->index_in_node));
|
||||
layout_root()->set_selection({ { paintable->layout_node(), result->index_in_node }, {} });
|
||||
m_in_mouse_selection = true;
|
||||
}
|
||||
}
|
||||
} else if (button == GUI::MouseButton::Secondary) {
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_request_context_menu(m_browsing_context.to_top_level_position(position));
|
||||
}
|
||||
} else if (button == GUI::MouseButton::Secondary) {
|
||||
if (auto* page = m_browsing_context.page())
|
||||
page->client().page_did_request_context_menu(m_browsing_context.to_top_level_position(position));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue