getClientRects doesn't work with list box option elements
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 5 Aug 2017 08:08:16 +0000 (08:08 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 5 Aug 2017 08:08:16 +0000 (08:08 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175016

Reviewed by Darin Adler.

Source/WebCore:

Since HTMLOptionElement and HTMLOptGroupElement don't have a renderer, we are always returning an empty list
from getClientRects. This is working fine in both chromium and firefox, option elements return its own bounding
box and group elements return the bounding box of the group label and all its children items.

Test: fast/dom/HTMLSelectElement/listbox-items-client-rects.html

* dom/Element.cpp:
(WebCore::listBoxElementBoundingBox): Helper function to return the bounding box of a HTMLOptionElement or
HTMLOptGroupElement element.
(WebCore::Element::getClientRects): Use listBoxElementBoundingBox() in case of HTMLOptionElement or
HTMLOptGroupElement.
(WebCore::Element::boundingClientRect): Ditto.

LayoutTests:

Add new test to check list box option elements client rects.

* fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt: Added.
* fast/dom/HTMLSelectElement/listbox-items-client-rects.html: Added.
* platform/ios-simulator-wk2/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220313 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/HTMLSelectElement/listbox-items-client-rects.html [new file with mode: 0644]
LayoutTests/platform/ios-simulator-wk2/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/Element.cpp

index 612a56b17e7332d49292d1edaf1f1bf0361a4b5f..0f77c31d0038a0a6db28164477392bb3e101e6b5 100644 (file)
@@ -1,3 +1,16 @@
+2017-08-05  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        getClientRects doesn't work with list box option elements
+        https://bugs.webkit.org/show_bug.cgi?id=175016
+
+        Reviewed by Darin Adler.
+
+        Add new test to check list box option elements client rects.
+
+        * fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt: Added.
+        * fast/dom/HTMLSelectElement/listbox-items-client-rects.html: Added.
+        * platform/ios-simulator-wk2/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt: Added.
+
 2017-08-04  Youenn Fablet  <youenn@apple.com>
 
         [Cache API] Add Cache and CacheStorage IDL definitions
diff --git a/LayoutTests/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt b/LayoutTests/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt
new file mode 100644 (file)
index 0000000..886cfd4
--- /dev/null
@@ -0,0 +1,60 @@
+PASS listBoundingBoxes.length is 1
+PASS listBoundingBoxes[0].left is listBoundingBox.left
+PASS listBoundingBoxes[0].right is listBoundingBox.right
+PASS listBoundingBoxes[0].top is listBoundingBox.top
+PASS listBoundingBoxes[0].bottom is listBoundingBox.bottom
+PASS listBoundingBoxes[0].width is listBoundingBox.width
+PASS listBoundingBoxes[0].height is listBoundingBox.height
+PASS groupBoundingBoxes.length is 1
+PASS groupBoundingBoxes[0].left is groupBoundingBox.left
+PASS groupBoundingBoxes[0].right is groupBoundingBox.right
+PASS groupBoundingBoxes[0].top is groupBoundingBox.top
+PASS groupBoundingBoxes[0].bottom is groupBoundingBox.bottom
+PASS groupBoundingBoxes[0].width is groupBoundingBox.width
+PASS groupBoundingBoxes[0].height is groupBoundingBox.height
+PASS option1BoundingBoxes.length is 1
+PASS option1BoundingBoxes[0].left is option1BoundingBox.left
+PASS option1BoundingBoxes[0].right is option1BoundingBox.right
+PASS option1BoundingBoxes[0].top is option1BoundingBox.top
+PASS option1BoundingBoxes[0].bottom is option1BoundingBox.bottom
+PASS option1BoundingBoxes[0].width is option1BoundingBox.width
+PASS option1BoundingBoxes[0].height is option1BoundingBox.height
+PASS option2BoundingBoxes.length is 1
+PASS option2BoundingBoxes[0].left is option2BoundingBox.left
+PASS option2BoundingBoxes[0].right is option2BoundingBox.right
+PASS option2BoundingBoxes[0].top is option2BoundingBox.top
+PASS option2BoundingBoxes[0].bottom is option2BoundingBox.bottom
+PASS option2BoundingBoxes[0].width is option2BoundingBox.width
+PASS option2BoundingBoxes[0].height is option2BoundingBox.height
+PASS option3BoundingBoxes.length is 1
+PASS option3BoundingBoxes[0].left is option3BoundingBox.left
+PASS option3BoundingBoxes[0].right is option3BoundingBox.right
+PASS option3BoundingBoxes[0].top is option3BoundingBox.top
+PASS option3BoundingBoxes[0].bottom is option3BoundingBox.bottom
+PASS option3BoundingBoxes[0].width is option3BoundingBox.width
+PASS option3BoundingBoxes[0].height is option3BoundingBox.height
+PASS groupBoundingBox.left is listBoundingBox.left + border
+PASS groupBoundingBox.top is listBoundingBox.top + border
+PASS groupBoundingBox.right is listBoundingBox.right - border
+PASS groupBoundingBox.bottom is groupBoundingBox.top + 3 * optionHeight
+PASS groupBoundingBox.width is listBoundingBox.width - 2 * border
+PASS groupBoundingBox.height is 3 * optionHeight
+PASS option1BoundingBox.left is listBoundingBox.left + border
+PASS option1BoundingBox.top is listBoundingBox.top + border + optionHeight
+PASS option1BoundingBox.right is listBoundingBox.right - border
+PASS option1BoundingBox.bottom is option1BoundingBox.top + option1BoundingBox.height
+PASS option1BoundingBox.width is listBoundingBox.width - 2 * border
+PASS option2BoundingBox.left is listBoundingBox.left + border
+PASS option2BoundingBox.top is listBoundingBox.top + border + 2 * optionHeight
+PASS option2BoundingBox.right is listBoundingBox.right - border
+PASS option2BoundingBox.bottom is option2BoundingBox.top + option2BoundingBox.height
+PASS option2BoundingBox.width is listBoundingBox.width - 2 * border
+PASS option3BoundingBox.left is listBoundingBox.left + border
+PASS option3BoundingBox.top is listBoundingBox.top + border + 3 * optionHeight
+PASS option3BoundingBox.right is listBoundingBox.right - border
+PASS option3BoundingBox.bottom is option3BoundingBox.top + option3BoundingBox.height
+PASS option3BoundingBox.width is listBoundingBox.width - 2 * border
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/dom/HTMLSelectElement/listbox-items-client-rects.html b/LayoutTests/fast/dom/HTMLSelectElement/listbox-items-client-rects.html
new file mode 100644 (file)
index 0000000..a23fd72
--- /dev/null
@@ -0,0 +1,119 @@
+<script src="../../../resources/js-test-pre.js"></script>
+<style>
+    select {
+        width: 100px;
+        height: 150px;
+        padding: 0px;
+        outline: 1px;
+    }
+    select::-webkit-scrollbar { display: none; }
+</style>
+<div id="testArea">
+    <select id="list" multiple>
+      <optgroup id="group" label="Group">
+        <option id="option1">Option 1</option>
+        <option id="option2">Option 2</option>
+      </optgroup>
+      <option id="option3">Option 3</option>
+    </select>
+    <br><br>
+</div>
+<div id="console"></div>
+<script>
+    if (window.testRunner)
+        testRunner.dumpAsText();
+
+    var list = document.getElementById('list');
+    var listBoundingBox = list.getBoundingClientRect();
+    var listBoundingBoxes = list.getClientRects();
+
+    shouldBe("listBoundingBoxes.length", "1");
+    shouldBe("listBoundingBoxes[0].left", "listBoundingBox.left");
+    shouldBe("listBoundingBoxes[0].right", "listBoundingBox.right");
+    shouldBe("listBoundingBoxes[0].top", "listBoundingBox.top");
+    shouldBe("listBoundingBoxes[0].bottom", "listBoundingBox.bottom");
+    shouldBe("listBoundingBoxes[0].width", "listBoundingBox.width");
+    shouldBe("listBoundingBoxes[0].height", "listBoundingBox.height");
+
+    var group = document.getElementById('group')
+    var groupBoundingBox = group.getBoundingClientRect();
+    var groupBoundingBoxes = group.getClientRects();
+
+    shouldBe("groupBoundingBoxes.length", "1");
+    shouldBe("groupBoundingBoxes[0].left", "groupBoundingBox.left");
+    shouldBe("groupBoundingBoxes[0].right", "groupBoundingBox.right");
+    shouldBe("groupBoundingBoxes[0].top", "groupBoundingBox.top");
+    shouldBe("groupBoundingBoxes[0].bottom", "groupBoundingBox.bottom");
+    shouldBe("groupBoundingBoxes[0].width", "groupBoundingBox.width");
+    shouldBe("groupBoundingBoxes[0].height", "groupBoundingBox.height");
+
+    var option1 = document.getElementById('option1');
+    var option1BoundingBox = option1.getBoundingClientRect();
+    var option1BoundingBoxes = option1.getClientRects();
+
+    shouldBe("option1BoundingBoxes.length", "1");
+    shouldBe("option1BoundingBoxes[0].left", "option1BoundingBox.left");
+    shouldBe("option1BoundingBoxes[0].right", "option1BoundingBox.right");
+    shouldBe("option1BoundingBoxes[0].top", "option1BoundingBox.top");
+    shouldBe("option1BoundingBoxes[0].bottom", "option1BoundingBox.bottom");
+    shouldBe("option1BoundingBoxes[0].width", "option1BoundingBox.width");
+    shouldBe("option1BoundingBoxes[0].height", "option1BoundingBox.height");
+
+    var option2 = document.getElementById('option2');
+    var option2BoundingBox = option2.getBoundingClientRect();
+    var option2BoundingBoxes = option2.getClientRects();
+
+    shouldBe("option2BoundingBoxes.length", "1");
+    shouldBe("option2BoundingBoxes[0].left", "option2BoundingBox.left");
+    shouldBe("option2BoundingBoxes[0].right", "option2BoundingBox.right");
+    shouldBe("option2BoundingBoxes[0].top", "option2BoundingBox.top");
+    shouldBe("option2BoundingBoxes[0].bottom", "option2BoundingBox.bottom");
+    shouldBe("option2BoundingBoxes[0].width", "option2BoundingBox.width");
+    shouldBe("option2BoundingBoxes[0].height", "option2BoundingBox.height");
+
+    var option3 = document.getElementById('option3');
+    var option3BoundingBox = option3.getBoundingClientRect();
+    var option3BoundingBoxes = option3.getClientRects();
+
+    shouldBe("option3BoundingBoxes.length", "1");
+    shouldBe("option3BoundingBoxes[0].left", "option3BoundingBox.left");
+    shouldBe("option3BoundingBoxes[0].right", "option3BoundingBox.right");
+    shouldBe("option3BoundingBoxes[0].top", "option3BoundingBox.top");
+    shouldBe("option3BoundingBoxes[0].bottom", "option3BoundingBox.bottom");
+    shouldBe("option3BoundingBoxes[0].width", "option3BoundingBox.width");
+    shouldBe("option3BoundingBoxes[0].height", "option3BoundingBox.height");
+
+    var border = 1;
+    var optionHeight = option1BoundingBox.height;
+
+    shouldBe("groupBoundingBox.left", "listBoundingBox.left + border");
+    shouldBe("groupBoundingBox.top", "listBoundingBox.top + border");
+    shouldBe("groupBoundingBox.right", "listBoundingBox.right - border");
+    shouldBe("groupBoundingBox.bottom", "groupBoundingBox.top + 3 * optionHeight");
+    shouldBe("groupBoundingBox.width", "listBoundingBox.width - 2 * border");
+    shouldBe("groupBoundingBox.height", "3 * optionHeight");
+
+    shouldBe("option1BoundingBox.left", "listBoundingBox.left + border");
+    shouldBe("option1BoundingBox.top", "listBoundingBox.top + border + optionHeight");
+    shouldBe("option1BoundingBox.right", "listBoundingBox.right - border");
+    shouldBe("option1BoundingBox.bottom", "option1BoundingBox.top + option1BoundingBox.height");
+    shouldBe("option1BoundingBox.width", "listBoundingBox.width - 2 * border");
+
+    shouldBe("option2BoundingBox.left", "listBoundingBox.left + border");
+    shouldBe("option2BoundingBox.top", "listBoundingBox.top + border + 2 * optionHeight");
+    shouldBe("option2BoundingBox.right", "listBoundingBox.right - border");
+    shouldBe("option2BoundingBox.bottom", "option2BoundingBox.top + option2BoundingBox.height");
+    shouldBe("option2BoundingBox.width", "listBoundingBox.width - 2 * border");
+
+    shouldBe("option3BoundingBox.left", "listBoundingBox.left + border");
+    shouldBe("option3BoundingBox.top", "listBoundingBox.top + border + 3 * optionHeight");
+    shouldBe("option3BoundingBox.right", "listBoundingBox.right - border");
+    shouldBe("option3BoundingBox.bottom", "option3BoundingBox.top + option3BoundingBox.height");
+    shouldBe("option3BoundingBox.width", "listBoundingBox.width - 2 * border");
+
+    if (window.testRunner) {
+        var area = document.getElementById('testArea');
+        area.parentNode.removeChild(area);
+    }
+</script>
+<script src="../../../resources/js-test-post.js"></script>
diff --git a/LayoutTests/platform/ios-simulator-wk2/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt b/LayoutTests/platform/ios-simulator-wk2/fast/dom/HTMLSelectElement/listbox-items-client-rects-expected.txt
new file mode 100644 (file)
index 0000000..9fecc71
--- /dev/null
@@ -0,0 +1,60 @@
+PASS listBoundingBoxes.length is 1
+PASS listBoundingBoxes[0].left is listBoundingBox.left
+PASS listBoundingBoxes[0].right is listBoundingBox.right
+PASS listBoundingBoxes[0].top is listBoundingBox.top
+PASS listBoundingBoxes[0].bottom is listBoundingBox.bottom
+PASS listBoundingBoxes[0].width is listBoundingBox.width
+PASS listBoundingBoxes[0].height is listBoundingBox.height
+FAIL groupBoundingBoxes.length should be 1. Was 0.
+FAIL groupBoundingBoxes[0].left should be 0. Threw exception TypeError: undefined is not an object (evaluating 'groupBoundingBoxes[0].left')
+FAIL groupBoundingBoxes[0].right should be 0. Threw exception TypeError: undefined is not an object (evaluating 'groupBoundingBoxes[0].right')
+FAIL groupBoundingBoxes[0].top should be 0. Threw exception TypeError: undefined is not an object (evaluating 'groupBoundingBoxes[0].top')
+FAIL groupBoundingBoxes[0].bottom should be 0. Threw exception TypeError: undefined is not an object (evaluating 'groupBoundingBoxes[0].bottom')
+FAIL groupBoundingBoxes[0].width should be 0. Threw exception TypeError: undefined is not an object (evaluating 'groupBoundingBoxes[0].width')
+FAIL groupBoundingBoxes[0].height should be 0. Threw exception TypeError: undefined is not an object (evaluating 'groupBoundingBoxes[0].height')
+FAIL option1BoundingBoxes.length should be 1. Was 0.
+FAIL option1BoundingBoxes[0].left should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option1BoundingBoxes[0].left')
+FAIL option1BoundingBoxes[0].right should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option1BoundingBoxes[0].right')
+FAIL option1BoundingBoxes[0].top should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option1BoundingBoxes[0].top')
+FAIL option1BoundingBoxes[0].bottom should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option1BoundingBoxes[0].bottom')
+FAIL option1BoundingBoxes[0].width should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option1BoundingBoxes[0].width')
+FAIL option1BoundingBoxes[0].height should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option1BoundingBoxes[0].height')
+FAIL option2BoundingBoxes.length should be 1. Was 0.
+FAIL option2BoundingBoxes[0].left should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option2BoundingBoxes[0].left')
+FAIL option2BoundingBoxes[0].right should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option2BoundingBoxes[0].right')
+FAIL option2BoundingBoxes[0].top should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option2BoundingBoxes[0].top')
+FAIL option2BoundingBoxes[0].bottom should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option2BoundingBoxes[0].bottom')
+FAIL option2BoundingBoxes[0].width should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option2BoundingBoxes[0].width')
+FAIL option2BoundingBoxes[0].height should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option2BoundingBoxes[0].height')
+FAIL option3BoundingBoxes.length should be 1. Was 0.
+FAIL option3BoundingBoxes[0].left should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option3BoundingBoxes[0].left')
+FAIL option3BoundingBoxes[0].right should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option3BoundingBoxes[0].right')
+FAIL option3BoundingBoxes[0].top should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option3BoundingBoxes[0].top')
+FAIL option3BoundingBoxes[0].bottom should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option3BoundingBoxes[0].bottom')
+FAIL option3BoundingBoxes[0].width should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option3BoundingBoxes[0].width')
+FAIL option3BoundingBoxes[0].height should be 0. Threw exception TypeError: undefined is not an object (evaluating 'option3BoundingBoxes[0].height')
+FAIL groupBoundingBox.left should be 9. Was 0.
+FAIL groupBoundingBox.top should be 9. Was 0.
+FAIL groupBoundingBox.right should be 107. Was 0.
+PASS groupBoundingBox.bottom is groupBoundingBox.top + 3 * optionHeight
+FAIL groupBoundingBox.width should be 98. Was 0.
+PASS groupBoundingBox.height is 3 * optionHeight
+FAIL option1BoundingBox.left should be 9. Was 0.
+FAIL option1BoundingBox.top should be 9. Was 0.
+FAIL option1BoundingBox.right should be 107. Was 0.
+PASS option1BoundingBox.bottom is option1BoundingBox.top + option1BoundingBox.height
+FAIL option1BoundingBox.width should be 98. Was 0.
+FAIL option2BoundingBox.left should be 9. Was 0.
+FAIL option2BoundingBox.top should be 9. Was 0.
+FAIL option2BoundingBox.right should be 107. Was 0.
+PASS option2BoundingBox.bottom is option2BoundingBox.top + option2BoundingBox.height
+FAIL option2BoundingBox.width should be 98. Was 0.
+FAIL option3BoundingBox.left should be 9. Was 0.
+FAIL option3BoundingBox.top should be 9. Was 0.
+FAIL option3BoundingBox.right should be 107. Was 0.
+PASS option3BoundingBox.bottom is option3BoundingBox.top + option3BoundingBox.height
+FAIL option3BoundingBox.width should be 98. Was 0.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
index e577b9e7079c70c0121f744aa0bc6896eec6b073..8cf33c7410fbae1f860573d54521f5f94bbbc5ec 100644 (file)
@@ -1,3 +1,23 @@
+2017-08-05  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        getClientRects doesn't work with list box option elements
+        https://bugs.webkit.org/show_bug.cgi?id=175016
+
+        Reviewed by Darin Adler.
+
+        Since HTMLOptionElement and HTMLOptGroupElement don't have a renderer, we are always returning an empty list
+        from getClientRects. This is working fine in both chromium and firefox, option elements return its own bounding
+        box and group elements return the bounding box of the group label and all its children items.
+
+        Test: fast/dom/HTMLSelectElement/listbox-items-client-rects.html
+
+        * dom/Element.cpp:
+        (WebCore::listBoxElementBoundingBox): Helper function to return the bounding box of a HTMLOptionElement or
+        HTMLOptGroupElement element.
+        (WebCore::Element::getClientRects): Use listBoxElementBoundingBox() in case of HTMLOptionElement or
+        HTMLOptGroupElement.
+        (WebCore::Element::boundingClientRect): Ditto.
+
 2017-08-04  Youenn Fablet  <youenn@apple.com>
 
         [Cache API] Add Cache and CacheStorage IDL definitions
index afc587ba17045280b5044b01622091c9bdded2d0..1d0ccf7dc0d53d0c00e98a0da9aa2f24448d1f8a 100644 (file)
 #include "HTMLLabelElement.h"
 #include "HTMLNameCollection.h"
 #include "HTMLObjectElement.h"
+#include "HTMLOptGroupElement.h"
+#include "HTMLOptionElement.h"
 #include "HTMLParserIdioms.h"
+#include "HTMLSelectElement.h"
 #include "HTMLTemplateElement.h"
 #include "IdChangeInvalidation.h"
 #include "IdTargetObserverRegistry.h"
@@ -78,6 +81,7 @@
 #include "PointerLockController.h"
 #include "RenderFlowThread.h"
 #include "RenderLayer.h"
+#include "RenderListBox.h"
 #include "RenderNamedFlowFragment.h"
 #include "RenderRegion.h"
 #include "RenderTheme.h"
@@ -1145,20 +1149,66 @@ LayoutRect Element::absoluteEventHandlerBounds(bool& includesFixedPositionElemen
     return absoluteEventBoundsOfElementAndDescendants(includesFixedPositionElements);
 }
 
+static std::optional<std::pair<RenderObject*, LayoutRect>> listBoxElementBoundingBox(Element& element)
+{
+    HTMLSelectElement* selectElement;
+    bool isGroup;
+    if (is<HTMLOptionElement>(element)) {
+        selectElement = downcast<HTMLOptionElement>(element).ownerSelectElement();
+        isGroup = false;
+    } else if (is<HTMLOptGroupElement>(element)) {
+        selectElement = downcast<HTMLOptGroupElement>(element).ownerSelectElement();
+        isGroup = true;
+    } else
+        return std::nullopt;
+
+    if (!selectElement || !selectElement->renderer() || !is<RenderListBox>(selectElement->renderer()))
+        return std::nullopt;
+
+    auto& renderer = downcast<RenderListBox>(*selectElement->renderer());
+    std::optional<LayoutRect> boundingBox;
+    int optionIndex = 0;
+    for (auto* item : selectElement->listItems()) {
+        if (item == &element) {
+            LayoutPoint additionOffset;
+            boundingBox = renderer.itemBoundingBoxRect(additionOffset, optionIndex);
+            if (!isGroup)
+                break;
+        } else if (isGroup && boundingBox) {
+            if (item->parentNode() != &element)
+                break;
+            LayoutPoint additionOffset;
+            boundingBox->setHeight(boundingBox->height() + renderer.itemBoundingBoxRect(additionOffset, optionIndex).height());
+        }
+        ++optionIndex;
+    }
+
+    if (!boundingBox)
+        return std::nullopt;
+
+    return std::pair<RenderObject*, LayoutRect> { &renderer, boundingBox.value() };
+}
+
 Ref<DOMRectList> Element::getClientRects()
 {
     document().updateLayoutIgnorePendingStylesheets();
 
-    RenderBoxModelObject* renderBoxModelObject = this->renderBoxModelObject();
-    if (!renderBoxModelObject)
-        return DOMRectList::create();
+    RenderObject* renderer = this->renderer();
+    Vector<FloatQuad> quads;
+
+    if (auto pair = listBoxElementBoundingBox(*this)) {
+        renderer = pair.value().first;
+        quads.append(renderer->localToAbsoluteQuad(FloatQuad { pair.value().second }));
+    } else if (auto* renderBoxModelObject = this->renderBoxModelObject())
+        renderBoxModelObject->absoluteQuads(quads);
 
     // FIXME: Handle SVG elements.
     // FIXME: Handle table/inline-table with a caption.
 
-    Vector<FloatQuad> quads;
-    renderBoxModelObject->absoluteQuads(quads);
-    document().convertAbsoluteToClientQuads(quads, renderBoxModelObject->style());
+    if (quads.isEmpty())
+        return DOMRectList::create();
+
+    document().convertAbsoluteToClientQuads(quads, renderer->style());
     return DOMRectList::create(quads);
 }
 
@@ -1166,18 +1216,19 @@ FloatRect Element::boundingClientRect()
 {
     document().updateLayoutIgnorePendingStylesheets();
 
+    RenderObject* renderer = this->renderer();
     Vector<FloatQuad> quads;
-    if (isSVGElement() && renderer() && !renderer()->isSVGRoot()) {
+    if (isSVGElement() && renderer && !renderer->isSVGRoot()) {
         // Get the bounding rectangle from the SVG model.
         SVGElement& svgElement = downcast<SVGElement>(*this);
         FloatRect localRect;
         if (svgElement.getBoundingBox(localRect))
-            quads.append(renderer()->localToAbsoluteQuad(localRect));
-    } else {
-        // Get the bounding rectangle from the box model.
-        if (renderBoxModelObject())
-            renderBoxModelObject()->absoluteQuads(quads);
-    }
+            quads.append(renderer->localToAbsoluteQuad(localRect));
+    } else if (auto pair = listBoxElementBoundingBox(*this)) {
+        renderer = pair.value().first;
+        quads.append(renderer->localToAbsoluteQuad(FloatQuad { pair.value().second }));
+    } else if (auto* renderBoxModelObject = this->renderBoxModelObject())
+        renderBoxModelObject->absoluteQuads(quads);
 
     if (quads.isEmpty())
         return { };
@@ -1186,7 +1237,7 @@ FloatRect Element::boundingClientRect()
     for (size_t i = 1; i < quads.size(); ++i)
         result.unite(quads[i].boundingBox());
 
-    document().convertAbsoluteToClientRect(result, renderer()->style());
+    document().convertAbsoluteToClientRect(result, renderer->style());
     return result;
 }