WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
175016
getClientRects doesn't work with list box option elements
https://bugs.webkit.org/show_bug.cgi?id=175016
Summary
getClientRects doesn't work with list box option elements
Carlos Garcia Campos
Reported
2017-08-01 04:07:23 PDT
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.
Attachments
Patch
(15.87 KB, patch)
2017-08-01 04:12 PDT
,
Carlos Garcia Campos
buildbot
: commit-queue-
Details
Formatted Diff
Diff
Archive of layout-test-results from ews123 for ios-simulator-wk2
(969.19 KB, application/zip)
2017-08-01 05:38 PDT
,
Build Bot
no flags
Details
Updated patch
(23.35 KB, patch)
2017-08-02 01:57 PDT
,
Carlos Garcia Campos
buildbot
: commit-queue-
Details
Formatted Diff
Diff
Archive of layout-test-results from ews113 for mac-elcapitan
(2.06 MB, application/zip)
2017-08-02 11:15 PDT
,
Build Bot
no flags
Details
Updated patch
(22.80 KB, patch)
2017-08-03 23:41 PDT
,
Carlos Garcia Campos
darin
: review+
Details
Formatted Diff
Diff
Show Obsolete
(4)
View All
Add attachment
proposed patch, testcase, etc.
Carlos Garcia Campos
Comment 1
2017-08-01 04:12:23 PDT
Created
attachment 316844
[details]
Patch
Build Bot
Comment 2
2017-08-01 05:38:38 PDT
Comment on
attachment 316844
[details]
Patch
Attachment 316844
[details]
did not pass ios-sim-ews (ios-simulator-wk2): Output:
http://webkit-queues.webkit.org/results/4233497
New failing tests: fast/dom/HTMLSelectElement/listbox-items-client-rects.html
Build Bot
Comment 3
2017-08-01 05:38:39 PDT
Created
attachment 316848
[details]
Archive of layout-test-results from ews123 for ios-simulator-wk2 The attached test failures were seen while running run-webkit-tests on the ios-sim-ews. Bot: ews123 Port: ios-simulator-wk2 Platform: Mac OS X 10.12.5
Darin Adler
Comment 4
2017-08-01 09:03:09 PDT
Comment on
attachment 316844
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
> Source/WebCore/dom/Element.cpp:1154 > + if (is<HTMLOptionElement>(*this) || is<HTMLOptGroupElement>(*this)) {
This kind of thing is really unfortunate for long term maintainability of our code. But we do have a lot of code like this. We could consider a more traditional solution where we use virtual functions instead of sprinkling type-specific checks into base classes.
> Source/WebCore/dom/Element.cpp:1158 > + Vector<FloatQuad> quads = { rect }; > + return DOMRectList::create(quads);
Reads nicely on a single line: return DOMRectList::create(Vector<FloatQuad> { rect });
> Source/WebCore/dom/Element.cpp:1211 > + // Get the bounding box of the select items individually in case of list box. > + auto* selectElement = is<HTMLOptionElement>(*this) ? downcast<HTMLOptionElement>(*this).ownerSelectElement() : downcast<HTMLOptGroupElement>(*this).ownerSelectElement(); > + if (selectElement && selectElement->renderer() && is<RenderListBox>(selectElement->renderer())) { > + renderer = selectElement->renderer(); > + auto& listBoxRenderer = downcast<RenderListBox>(*renderer); > + LayoutRect boundingBox; > + size_t optionIndex = 0; > + for (auto* item : selectElement->listItems()) { > + if (item == this) { > + LayoutPoint additionOffset; > + boundingBox = listBoxRenderer.itemBoundingBoxRect(additionOffset, optionIndex); > + if (!is<HTMLOptGroupElement>(*this)) > + break; > + } else if (is<HTMLOptGroupElement>(*this) && !boundingBox.isEmpty()) { > + if (item->parentNode() != this) > + break; > + LayoutPoint additionOffset; > + boundingBox.setHeight(boundingBox.height() + listBoxRenderer.itemBoundingBoxRect(additionOffset, optionIndex).height()); > + } > + ++optionIndex; > + } > + quads.append(renderer->localToAbsoluteQuad(FloatQuad(boundingBox)));
This is big enough that I think we should consider putting it in a separate function. It’s bigger than the entire rest of the function combined and overwhelms the main logic here.
Carlos Garcia Campos
Comment 5
2017-08-01 09:23:26 PDT
(In reply to Darin Adler from
comment #4
)
> Comment on
attachment 316844
[details]
> Patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
> > > Source/WebCore/dom/Element.cpp:1154 > > + if (is<HTMLOptionElement>(*this) || is<HTMLOptGroupElement>(*this)) { > > This kind of thing is really unfortunate for long term maintainability of > our code. But we do have a lot of code like this. We could consider a more > traditional solution where we use virtual functions instead of sprinkling > type-specific checks into base classes.
hmm, maybe we can add a virtual absoluteQuads() to Node? or Element? The default implementation would simply use RenderBoxModelObject::absoluteQuads() and elements not having a renderer would implement it too.
> > Source/WebCore/dom/Element.cpp:1158 > > + Vector<FloatQuad> quads = { rect }; > > + return DOMRectList::create(quads); > > Reads nicely on a single line: > > return DOMRectList::create(Vector<FloatQuad> { rect });
Indeed.
> > Source/WebCore/dom/Element.cpp:1211 > > + // Get the bounding box of the select items individually in case of list box. > > + auto* selectElement = is<HTMLOptionElement>(*this) ? downcast<HTMLOptionElement>(*this).ownerSelectElement() : downcast<HTMLOptGroupElement>(*this).ownerSelectElement(); > > + if (selectElement && selectElement->renderer() && is<RenderListBox>(selectElement->renderer())) { > > + renderer = selectElement->renderer(); > > + auto& listBoxRenderer = downcast<RenderListBox>(*renderer); > > + LayoutRect boundingBox; > > + size_t optionIndex = 0; > > + for (auto* item : selectElement->listItems()) { > > + if (item == this) { > > + LayoutPoint additionOffset; > > + boundingBox = listBoxRenderer.itemBoundingBoxRect(additionOffset, optionIndex); > > + if (!is<HTMLOptGroupElement>(*this)) > > + break; > > + } else if (is<HTMLOptGroupElement>(*this) && !boundingBox.isEmpty()) { > > + if (item->parentNode() != this) > > + break; > > + LayoutPoint additionOffset; > > + boundingBox.setHeight(boundingBox.height() + listBoxRenderer.itemBoundingBoxRect(additionOffset, optionIndex).height()); > > + } > > + ++optionIndex; > > + } > > + quads.append(renderer->localToAbsoluteQuad(FloatQuad(boundingBox))); > > This is big enough that I think we should consider putting it in a separate > function. It’s bigger than the entire rest of the function combined and > overwhelms the main logic here.
Ok.
Carlos Garcia Campos
Comment 6
2017-08-01 09:27:18 PDT
Btw, any idea why the test is failing in ios-simulator-wk2?
Darin Adler
Comment 7
2017-08-01 09:29:15 PDT
Form elements are a lot different on iOS. It’s possible that these cases render a lot differently on iOS and this affects the test.
alan
Comment 8
2017-08-01 09:30:10 PDT
Comment on
attachment 316844
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
> Source/WebCore/dom/Element.cpp:1161 > +
Having bounding rect code here before calling updateLayoutIgnorePendingStylesheets() is confusing. I know boundingClientRect() will clean the style/renderer tree, but it is highly error prone.
alan
Comment 9
2017-08-01 09:35:42 PDT
Comment on
attachment 316844
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
>>> Source/WebCore/dom/Element.cpp:1154 >>> + if (is<HTMLOptionElement>(*this) || is<HTMLOptGroupElement>(*this)) { >> >> This kind of thing is really unfortunate for long term maintainability of our code. But we do have a lot of code like this. We could consider a more traditional solution where we use virtual functions instead of sprinkling type-specific checks into base classes. > > hmm, maybe we can add a virtual absoluteQuads() to Node? or Element? The default implementation would simply use RenderBoxModelObject::absoluteQuads() and elements not having a renderer would implement it too.
What do you mean by elements not having renderer would implement it too? What would they return? -elements with no renderers don't compute client rects.
Carlos Garcia Campos
Comment 10
2017-08-01 09:59:41 PDT
(In reply to Darin Adler from
comment #7
)
> Form elements are a lot different on iOS. It’s possible that these cases > render a lot differently on iOS and this affects the test.
Should I just mark it as failure then? I have no idea how to make this work on iOS.
Carlos Garcia Campos
Comment 11
2017-08-01 10:00:32 PDT
(In reply to zalan from
comment #8
)
> Comment on
attachment 316844
[details]
> Patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
> > > Source/WebCore/dom/Element.cpp:1161 > > + > > Having bounding rect code here before calling > updateLayoutIgnorePendingStylesheets() is confusing. I know > boundingClientRect() will clean the style/renderer tree, but it is highly > error prone.
I agree, if it's not a problem in terms of performance, I guess it's ok to call it twice.
Carlos Garcia Campos
Comment 12
2017-08-01 10:02:55 PDT
(In reply to zalan from
comment #9
)
> Comment on
attachment 316844
[details]
> Patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
> > >>> Source/WebCore/dom/Element.cpp:1154 > >>> + if (is<HTMLOptionElement>(*this) || is<HTMLOptGroupElement>(*this)) { > >> > >> This kind of thing is really unfortunate for long term maintainability of our code. But we do have a lot of code like this. We could consider a more traditional solution where we use virtual functions instead of sprinkling type-specific checks into base classes. > > > > hmm, maybe we can add a virtual absoluteQuads() to Node? or Element? The default implementation would simply use RenderBoxModelObject::absoluteQuads() and elements not having a renderer would implement it too. > > What do you mean by elements not having renderer would implement it too? > What would they return? -elements with no renderers don't compute client > rects.
What this patch is doing :-) HTMLOption and HTMLOptGroup don't have a renderer, but it's possible to use its select element to get their bounding boxes. So, those elements would override absoluteQuads(). I don't know if there are more cases like this.
alan
Comment 13
2017-08-01 10:06:12 PDT
(In reply to Carlos Garcia Campos from
comment #10
)
> (In reply to Darin Adler from
comment #7
) > > Form elements are a lot different on iOS. It’s possible that these cases > > render a lot differently on iOS and this affects the test. > > Should I just mark it as failure then? I have no idea how to make this work > on iOS.
No, you could either come up with a different type of test where you add a dedicated iOS expected file, or ignore this test on iOS and do another one specifically for iOS (I'd rather do the first one though).
alan
Comment 14
2017-08-01 10:09:25 PDT
(In reply to Carlos Garcia Campos from
comment #12
)
> (In reply to zalan from
comment #9
) > > Comment on
attachment 316844
[details]
> > Patch > > > > View in context: > >
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
> > > > >>> Source/WebCore/dom/Element.cpp:1154 > > >>> + if (is<HTMLOptionElement>(*this) || is<HTMLOptGroupElement>(*this)) { > > >> > > >> This kind of thing is really unfortunate for long term maintainability of our code. But we do have a lot of code like this. We could consider a more traditional solution where we use virtual functions instead of sprinkling type-specific checks into base classes. > > > > > > hmm, maybe we can add a virtual absoluteQuads() to Node? or Element? The default implementation would simply use RenderBoxModelObject::absoluteQuads() and elements not having a renderer would implement it too. > > > > What do you mean by elements not having renderer would implement it too? > > What would they return? -elements with no renderers don't compute client > > rects. > > What this patch is doing :-) HTMLOption and HTMLOptGroup don't have a > renderer, but it's possible to use its select element to get their bounding > boxes. So, those elements would override absoluteQuads(). I don't know if > there are more cases like this.
These are special cases, the typical case when an element does not have a renderer is when the element is display: none.
Carlos Garcia Campos
Comment 15
2017-08-01 10:20:46 PDT
(In reply to zalan from
comment #13
)
> (In reply to Carlos Garcia Campos from
comment #10
) > > (In reply to Darin Adler from
comment #7
) > > > Form elements are a lot different on iOS. It’s possible that these cases > > > render a lot differently on iOS and this affects the test. > > > > Should I just mark it as failure then? I have no idea how to make this work > > on iOS. > No, you could either come up with a different type of test where you add a > dedicated iOS expected file, or ignore this test on iOS and do another one > specifically for iOS (I'd rather do the first one though).
Ok, I'll add a expected file for iOS then.
Carlos Garcia Campos
Comment 16
2017-08-01 10:21:28 PDT
(In reply to zalan from
comment #14
)
> (In reply to Carlos Garcia Campos from
comment #12
) > > (In reply to zalan from
comment #9
) > > > Comment on
attachment 316844
[details]
> > > Patch > > > > > > View in context: > > >
https://bugs.webkit.org/attachment.cgi?id=316844&action=review
> > > > > > >>> Source/WebCore/dom/Element.cpp:1154 > > > >>> + if (is<HTMLOptionElement>(*this) || is<HTMLOptGroupElement>(*this)) { > > > >> > > > >> This kind of thing is really unfortunate for long term maintainability of our code. But we do have a lot of code like this. We could consider a more traditional solution where we use virtual functions instead of sprinkling type-specific checks into base classes. > > > > > > > > hmm, maybe we can add a virtual absoluteQuads() to Node? or Element? The default implementation would simply use RenderBoxModelObject::absoluteQuads() and elements not having a renderer would implement it too. > > > > > > What do you mean by elements not having renderer would implement it too? > > > What would they return? -elements with no renderers don't compute client > > > rects. > > > > What this patch is doing :-) HTMLOption and HTMLOptGroup don't have a > > renderer, but it's possible to use its select element to get their bounding > > boxes. So, those elements would override absoluteQuads(). I don't know if > > there are more cases like this. > These are special cases, the typical case when an element does not have a > renderer is when the element is display: none.
Ah, ok, I actually meant these special cases in particular not the general case of not having a renderer.
Simon Fraser (smfr)
Comment 17
2017-08-01 22:20:16 PDT
<option> rendering is just weird in general:
bug 169039
Carlos Garcia Campos
Comment 18
2017-08-02 01:57:46 PDT
Created
attachment 316950
[details]
Updated patch Added a helper function in the end.
Build Bot
Comment 19
2017-08-02 11:15:26 PDT
Comment on
attachment 316950
[details]
Updated patch
Attachment 316950
[details]
did not pass mac-debug-ews (mac): Output:
http://webkit-queues.webkit.org/results/4240671
New failing tests: fast/dom/StyleSheet/detached-sheet-owner-node-link.html
Build Bot
Comment 20
2017-08-02 11:15:28 PDT
Created
attachment 316977
[details]
Archive of layout-test-results from ews113 for mac-elcapitan The attached test failures were seen while running run-webkit-tests on the mac-debug-ews. Bot: ews113 Port: mac-elcapitan Platform: Mac OS X 10.11.6
Darin Adler
Comment 21
2017-08-03 21:53:01 PDT
Comment on
attachment 316950
[details]
Updated patch View in context:
https://bugs.webkit.org/attachment.cgi?id=316950&action=review
This seems like an improvement over the earlier version. Not a huge fan of the use of variant just to deal with the two different ways of getting the select element. I would consider something much simpler using overloading or something. I would have made these two helper functions: bool isOptionOrGroup(Element&); HTMLSelectElement* selectForOptionOrGroup(Element&);
> Source/WebCore/dom/Element.cpp:1152 > +static std::optional<LayoutRect> listBoxElementBoundingBox(Variant<RefPtr<HTMLOptionElement>, RefPtr<HTMLOptGroupElement>> optionOrOptGroupElement, RenderObject*& renderer)
Seems strange to use RefPtr in the variant. Why not just a plain old pointer? Also ugly to have the out argument. If we need to return both a renderer and a rectangle, then they should both be return values: static std::optional<std::pair<RenderObject*, LayoutRect>> listBoxElementBoundingBox(Element& element) { if (!isOptionOrGroup(element)) return std::nullopt;
> Source/WebCore/dom/Element.cpp:1156 > + auto* selectElement = WTF::switchOn(optionOrOptGroupElement, > + [](const auto& element) -> HTMLSelectElement* { return element->ownerSelectElement(); } > + );
auto* selectElement = selectForOptionOrGroup(element);
> Source/WebCore/dom/Element.cpp:1162 > + renderer = selectElement->renderer(); > + auto& listBoxRenderer = downcast<RenderListBox>(*renderer);
merge into a single line, and just name it renderer, I think
> Source/WebCore/dom/Element.cpp:1168 > + size_t optionIndex = 0;
The type of the argument to itemBoundingBoxRect is "int", not "size_t".
> Source/WebCore/dom/Element.cpp:1173 > + if (is<HTMLOptionElement>(element))
Strange to do this test again inside the loop like this after going to all the trouble of using a variant.
> Source/WebCore/dom/Element.cpp:1174 > + return boundingBox;
break;
> Source/WebCore/dom/Element.cpp:1175 > + } else if (is<HTMLOptGroupElement>(element) && boundingBox) {
Strange to do this test again inside the loop like this after going to all the trouble of using a variant.
> Source/WebCore/dom/Element.cpp:1177 > + return boundingBox;
break;
> Source/WebCore/dom/Element.cpp:1184 > + return boundingBox;
if (!boundingBox) return std::nullopt; return { &renderer, boundingBox.value() }; // <<< with whatever explicit type names are needed to make it compile
> Source/WebCore/dom/Element.cpp:1230 > + } else if (is<HTMLOptionElement>(*this)) { > + if (auto boundingBox = listBoxElementBoundingBox(downcast<HTMLOptionElement>(this), renderer)) > + quads.append(renderer->localToAbsoluteQuad(FloatQuad(boundingBox.value()))); > + } else if (is<HTMLOptGroupElement>(*this)) { > + if (auto boundingBox = listBoxElementBoundingBox(downcast<HTMLOptGroupElement>(this), renderer)) > + quads.append(renderer->localToAbsoluteQuad(FloatQuad(boundingBox.value())));
And I would write something more like this: } else if (isOptionOrGroup(*this)) { if (auto pair = listBoxElementBoundingBox(*this)) { renderer = pair.value().first; quads.append(renderer->localToAbsoluteQuad(FloatQuad { pair.value().second) }); } ...
Darin Adler
Comment 22
2017-08-03 21:57:24 PDT
Comment on
attachment 316950
[details]
Updated patch View in context:
https://bugs.webkit.org/attachment.cgi?id=316950&action=review
>> Source/WebCore/dom/Element.cpp:1152 >> +static std::optional<LayoutRect> listBoxElementBoundingBox(Variant<RefPtr<HTMLOptionElement>, RefPtr<HTMLOptGroupElement>> optionOrOptGroupElement, RenderObject*& renderer) > > Seems strange to use RefPtr in the variant. Why not just a plain old pointer? Also ugly to have the out argument. If we need to return both a renderer and a rectangle, then they should both be return values: > > static std::optional<std::pair<RenderObject*, LayoutRect>> listBoxElementBoundingBox(Element& element) > { > if (!isOptionOrGroup(element)) > return std::nullopt;
Or no helper functions and just start with this preamble: { 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; auto& downcastedElement = downcast<HTMLElement>(element);
>> Source/WebCore/dom/Element.cpp:1230 >> + quads.append(renderer->localToAbsoluteQuad(FloatQuad(boundingBox.value()))); > > And I would write something more like this: > > } else if (isOptionOrGroup(*this)) { > if (auto pair = listBoxElementBoundingBox(*this)) { > renderer = pair.value().first; > quads.append(renderer->localToAbsoluteQuad(FloatQuad { pair.value().second) }); > } > ...
Sorry, I meant: } else if {auto pair = listBoxElementBoundingBox(*this)) { renderer = pair.value().first; quads.append(renderer->localToAbsoluteQuad(FloatQuad { pair.value().second) }); } No need to call isOptionOrGroup here.
Carlos Garcia Campos
Comment 23
2017-08-03 23:41:22 PDT
Created
attachment 317218
[details]
Updated patch Addressed all review comments
Darin Adler
Comment 24
2017-08-04 09:57:21 PDT
Comment on
attachment 317218
[details]
Updated patch View in context:
https://bugs.webkit.org/attachment.cgi?id=317218&action=review
> Source/WebCore/dom/Element.cpp:1166 > + if (!selectElement->renderer() || !is<RenderListBox>(selectElement->renderer())) > + return std::nullopt;
I think we still need to null-check selectElement here, since ownerSelectElement can return nullptr.
Carlos Garcia Campos
Comment 25
2017-08-05 00:33:36 PDT
(In reply to Darin Adler from
comment #24
)
> Comment on
attachment 317218
[details]
> Updated patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=317218&action=review
> > > Source/WebCore/dom/Element.cpp:1166 > > + if (!selectElement->renderer() || !is<RenderListBox>(selectElement->renderer())) > > + return std::nullopt; > > I think we still need to null-check selectElement here, since > ownerSelectElement can return nullptr.
Indeed, good catch.
Carlos Garcia Campos
Comment 26
2017-08-05 01:08:27 PDT
Committed
r220313
: <
http://trac.webkit.org/changeset/220313
>
Radar WebKit Bug Importer
Comment 27
2017-08-05 01:09:49 PDT
<
rdar://problem/33739164
>
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug