+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
--- /dev/null
+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
+
--- /dev/null
+<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>
--- /dev/null
+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
+
+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
#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"
#include "PointerLockController.h"
#include "RenderFlowThread.h"
#include "RenderLayer.h"
+#include "RenderListBox.h"
#include "RenderNamedFlowFragment.h"
#include "RenderRegion.h"
#include "RenderTheme.h"
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);
}
{
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 { };
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;
}