summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/DOM
diff options
context:
space:
mode:
authorLuke Wilde <lukew@serenityos.org>2022-11-05 15:10:13 +0000
committerAndreas Kling <kling@serenityos.org>2022-11-07 14:10:41 +0100
commita268026e4e6aad8339539d618349dd66f8f456e0 (patch)
tree959b7748c5d5f7683c425c91b710b99f966f7c3f /Userland/Libraries/LibWeb/DOM
parent6b0152e150c89f2c797b1a431280412e194bfe18 (diff)
downloadserenity-a268026e4e6aad8339539d618349dd66f8f456e0.zip
LibWeb: Start implementing the Element scroll attributes
These are required for hit testing the document in Google Docs. If they aren't defined, the Google Docs hit test code will add undefined to certain values, causing them to turn into NaN. This causes NaNs to propagate through their hit test code, which eventually makes it infinitely loop.
Diffstat (limited to 'Userland/Libraries/LibWeb/DOM')
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.h1
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.cpp249
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.h10
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.idl4
4 files changed, 264 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h
index 3a0ce51dfa..3cf09999a2 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.h
+++ b/Userland/Libraries/LibWeb/DOM/Document.h
@@ -298,6 +298,7 @@ public:
WebIDL::ExceptionOr<void> close();
HTML::Window* default_view() { return m_window.ptr(); }
+ HTML::Window const* default_view() const { return m_window.ptr(); }
String const& content_type() const { return m_content_type; }
void set_content_type(String const& content_type) { m_content_type = content_type; }
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp
index adb878149e..1531adec5c 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Element.cpp
@@ -764,6 +764,255 @@ void Element::set_tab_index(i32 tab_index)
MUST(set_attribute(HTML::AttributeNames::tabindex, String::number(tab_index)));
}
+// https://drafts.csswg.org/cssom-view/#potentially-scrollable
+bool Element::is_potentially_scrollable() const
+{
+ // NOTE: Ensure that layout is up-to-date before looking at metrics.
+ const_cast<Document&>(document()).update_layout();
+
+ // An element body (which will be the body element) is potentially scrollable if all of the following conditions are true:
+ VERIFY(is<HTML::HTMLBodyElement>(this) || is<HTML::HTMLFrameSetElement>(this));
+
+ // Since this should always be the body element, the body element must have a <html> element parent. See Document::body().
+ VERIFY(parent());
+
+ // - body has an associated box.
+ // - body’s parent element’s computed value of the overflow-x or overflow-y properties is neither visible nor clip.
+ // - body’s computed value of the overflow-x or overflow-y properties is neither visible nor clip.
+ return layout_node()
+ && (parent()->layout_node()
+ && parent()->layout_node()->computed_values().overflow_x() != CSS::Overflow::Visible && parent()->layout_node()->computed_values().overflow_x() != CSS::Overflow::Clip
+ && parent()->layout_node()->computed_values().overflow_y() != CSS::Overflow::Visible && parent()->layout_node()->computed_values().overflow_y() != CSS::Overflow::Clip)
+ && (layout_node()->computed_values().overflow_x() != CSS::Overflow::Visible && layout_node()->computed_values().overflow_x() != CSS::Overflow::Clip
+ && layout_node()->computed_values().overflow_y() != CSS::Overflow::Visible && layout_node()->computed_values().overflow_y() != CSS::Overflow::Clip);
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
+double Element::scroll_top() const
+{
+ // 1. Let document be the element’s node document.
+ auto& document = this->document();
+
+ // 2. If document is not the active document, return zero and terminate these steps.
+ if (!document.is_active())
+ return 0.0;
+
+ // 3. Let window be the value of document’s defaultView attribute.
+ auto* window = document.default_view();
+
+ // 4. If window is null, return zero and terminate these steps.
+ if (!window)
+ return 0.0;
+
+ // 5. If the element is the root element and document is in quirks mode, return zero and terminate these steps.
+ if (document.document_element() == this && document.in_quirks_mode())
+ return 0.0;
+
+ // NOTE: Ensure that layout is up-to-date before looking at metrics.
+ const_cast<Document&>(document).update_layout();
+
+ // 6. If the element is the root element return the value of scrollY on window.
+ if (document.document_element() == this)
+ return window->scroll_y();
+
+ // 7. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, return the value of scrollY on window.
+ if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable())
+ return window->scroll_y();
+
+ // 8. If the element does not have any associated box, return zero and terminate these steps.
+ if (!layout_node() || !is<Layout::BlockContainer>(layout_node()))
+ return 0.0;
+
+ auto const* block_container = static_cast<Layout::BlockContainer const*>(layout_node());
+
+ // 9. Return the y-coordinate of the scrolling area at the alignment point with the top of the padding edge of the element.
+ // FIXME: Is this correct?
+ return block_container->scroll_offset().y();
+}
+
+double Element::scroll_left() const
+{
+ // 1. Let document be the element’s node document.
+ auto& document = this->document();
+
+ // 2. If document is not the active document, return zero and terminate these steps.
+ if (!document.is_active())
+ return 0.0;
+
+ // 3. Let window be the value of document’s defaultView attribute.
+ auto* window = document.default_view();
+
+ // 4. If window is null, return zero and terminate these steps.
+ if (!window)
+ return 0.0;
+
+ // 5. If the element is the root element and document is in quirks mode, return zero and terminate these steps.
+ if (document.document_element() == this && document.in_quirks_mode())
+ return 0.0;
+
+ // NOTE: Ensure that layout is up-to-date before looking at metrics.
+ const_cast<Document&>(document).update_layout();
+
+ // 6. If the element is the root element return the value of scrollX on window.
+ if (document.document_element() == this)
+ return window->scroll_x();
+
+ // 7. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, return the value of scrollX on window.
+ if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable())
+ return window->scroll_x();
+
+ // 8. If the element does not have any associated box, return zero and terminate these steps.
+ if (!layout_node() || !is<Layout::BlockContainer>(layout_node()))
+ return 0.0;
+
+ auto const* block_container = static_cast<Layout::BlockContainer const*>(layout_node());
+
+ // 9. Return the x-coordinate of the scrolling area at the alignment point with the left of the padding edge of the element.
+ // FIXME: Is this correct?
+ return block_container->scroll_offset().x();
+}
+
+// https://drafts.csswg.org/cssom-view/#dom-element-scrollleft
+void Element::set_scroll_left(double x)
+{
+ // 1. Let x be the given value.
+
+ // 2. Normalize non-finite values for x.
+ if (!isfinite(x))
+ x = 0.0;
+
+ // 3. Let document be the element’s node document.
+ auto& document = this->document();
+
+ // 4. If document is not the active document, terminate these steps.
+ if (!document.is_active())
+ return;
+
+ // 5. Let window be the value of document’s defaultView attribute.
+ auto* window = document.default_view();
+
+ // 6. If window is null, terminate these steps.
+ if (!window)
+ return;
+
+ // 7. If the element is the root element and document is in quirks mode, terminate these steps.
+ if (document.document_element() == this && document.in_quirks_mode())
+ return;
+
+ // NOTE: Ensure that layout is up-to-date before looking at metrics or scrolling the page.
+ const_cast<Document&>(document).update_layout();
+
+ // 8. If the element is the root element invoke scroll() on window with x as first argument and scrollY on window as second argument, and terminate these steps.
+ if (document.document_element() == this) {
+ // FIXME: Implement this in terms of invoking scroll() on window.
+ if (auto* page = document.page())
+ page->client().page_did_request_scroll_to({ static_cast<float>(x), window->scroll_y() });
+
+ return;
+ }
+
+ // 9. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, invoke scroll() on window with x as first argument and scrollY on window as second argument, and terminate these steps.
+ if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable()) {
+ // FIXME: Implement this in terms of invoking scroll() on window.
+ if (auto* page = document.page())
+ page->client().page_did_request_scroll_to({ static_cast<float>(x), window->scroll_y() });
+
+ return;
+ }
+
+ // 10. If the element does not have any associated box, the element has no associated scrolling box, or the element has no overflow, terminate these steps.
+ if (!layout_node() || !is<Layout::BlockContainer>(layout_node()))
+ return;
+
+ auto* block_container = static_cast<Layout::BlockContainer*>(layout_node());
+ if (!block_container->is_scrollable())
+ return;
+
+ // FIXME: or the element has no overflow.
+
+ // 11. Scroll the element to x,scrollTop, with the scroll behavior being "auto".
+ // FIXME: Implement this in terms of calling "scroll the element".
+ auto scroll_offset = block_container->scroll_offset();
+ scroll_offset.set_x(static_cast<float>(x));
+ block_container->set_scroll_offset(scroll_offset);
+}
+
+void Element::set_scroll_top(double y)
+{
+ // 1. Let y be the given value.
+
+ // 2. Normalize non-finite values for y.
+ if (!isfinite(y))
+ y = 0.0;
+
+ // 3. Let document be the element’s node document.
+ auto& document = this->document();
+
+ // 4. If document is not the active document, terminate these steps.
+ if (!document.is_active())
+ return;
+
+ // 5. Let window be the value of document’s defaultView attribute.
+ auto* window = document.default_view();
+
+ // 6. If window is null, terminate these steps.
+ if (!window)
+ return;
+
+ // 7. If the element is the root element and document is in quirks mode, terminate these steps.
+ if (document.document_element() == this && document.in_quirks_mode())
+ return;
+
+ // NOTE: Ensure that layout is up-to-date before looking at metrics or scrolling the page.
+ const_cast<Document&>(document).update_layout();
+
+ // 8. If the element is the root element invoke scroll() on window with scrollX on window as first argument and y as second argument, and terminate these steps.
+ if (document.document_element() == this) {
+ // FIXME: Implement this in terms of invoking scroll() on window.
+ if (auto* page = document.page())
+ page->client().page_did_request_scroll_to({ window->scroll_x(), static_cast<float>(y) });
+
+ return;
+ }
+
+ // 9. If the element is the body element, document is in quirks mode, and the element is not potentially scrollable, invoke scroll() on window with scrollX as first argument and y as second argument, and terminate these steps.
+ if (document.body() == this && document.in_quirks_mode() && !is_potentially_scrollable()) {
+ // FIXME: Implement this in terms of invoking scroll() on window.
+ if (auto* page = document.page())
+ page->client().page_did_request_scroll_to({ window->scroll_x(), static_cast<float>(y) });
+
+ return;
+ }
+
+ // 10. If the element does not have any associated box, the element has no associated scrolling box, or the element has no overflow, terminate these steps.
+ if (!layout_node() || !is<Layout::BlockContainer>(layout_node()))
+ return;
+
+ auto* block_container = static_cast<Layout::BlockContainer*>(layout_node());
+ if (!block_container->is_scrollable())
+ return;
+
+ // FIXME: or the element has no overflow.
+
+ // 11. Scroll the element to scrollLeft,y, with the scroll behavior being "auto".
+ // FIXME: Implement this in terms of calling "scroll the element".
+ auto scroll_offset = block_container->scroll_offset();
+ scroll_offset.set_y(static_cast<float>(y));
+ block_container->set_scroll_offset(scroll_offset);
+}
+
+int Element::scroll_width() const
+{
+ dbgln("FIXME: Implement Element::scroll_width() (called on element: {})", debug_description());
+ return 0;
+}
+
+int Element::scroll_height() const
+{
+ dbgln("FIXME: Implement Element::scroll_height() (called on element: {})", debug_description());
+ return 0;
+}
+
// https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled
bool Element::is_actually_disabled() const
{
diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h
index c58f89b8ef..e54e89de5a 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.h
+++ b/Userland/Libraries/LibWeb/DOM/Element.h
@@ -159,6 +159,16 @@ public:
i32 tab_index() const;
void set_tab_index(i32 tab_index);
+ bool is_potentially_scrollable() const;
+
+ double scroll_top() const;
+ double scroll_left() const;
+ void set_scroll_top(double y);
+ void set_scroll_left(double x);
+
+ int scroll_width() const;
+ int scroll_height() const;
+
bool is_actually_disabled() const;
WebIDL::ExceptionOr<JS::GCPtr<Element>> insert_adjacent_element(String const& where, JS::NonnullGCPtr<Element> element);
diff --git a/Userland/Libraries/LibWeb/DOM/Element.idl b/Userland/Libraries/LibWeb/DOM/Element.idl
index addfe89d8a..46a98c7145 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.idl
+++ b/Userland/Libraries/LibWeb/DOM/Element.idl
@@ -63,6 +63,10 @@ interface Element : Node {
DOMRect getBoundingClientRect();
DOMRectList getClientRects();
+ attribute unrestricted double scrollTop;
+ attribute unrestricted double scrollLeft;
+ readonly attribute long scrollWidth;
+ readonly attribute long scrollHeight;
readonly attribute long clientTop;
readonly attribute long clientLeft;
readonly attribute long clientWidth;