Changeset 288174 in webkit
- Timestamp:
- Jan 18, 2022 6:31:32 PM (6 months ago)
- Location:
- trunk
- Files:
-
- 14 edited
-
LayoutTests/ChangeLog (modified) (1 diff)
-
LayoutTests/imported/w3c/ChangeLog (modified) (1 diff)
-
LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/inserted-or-removed-expected.txt (modified) (1 diff)
-
LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/select-validity-expected.txt (modified) (1 diff)
-
LayoutTests/platform/ios-wk2/imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/select-validity-expected.txt (modified) (1 diff)
-
Source/WebCore/ChangeLog (modified) (1 diff)
-
Source/WebCore/dom/CharacterData.cpp (modified) (1 diff)
-
Source/WebCore/dom/ContainerNode.cpp (modified) (5 diffs)
-
Source/WebCore/dom/ContainerNode.h (modified) (2 diffs)
-
Source/WebCore/html/HTMLOptGroupElement.cpp (modified) (1 diff)
-
Source/WebCore/html/HTMLOptionElement.cpp (modified) (1 diff)
-
Source/WebCore/html/HTMLOptionElement.h (modified) (2 diffs)
-
Source/WebCore/html/HTMLSelectElement.cpp (modified) (1 diff)
-
Source/WebCore/html/HTMLSelectElement.h (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r288159 r288174 1 2022-01-18 Chris Dumez <cdumez@apple.com> 2 3 When inserting a selected <option> in a <select> element, its selected state should remain 4 https://bugs.webkit.org/show_bug.cgi?id=235237 5 6 Reviewed by Darin Adler. 7 8 Rebaseline WPT test that is now passing. 9 10 * platform/ios-wk2/imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/select-validity-expected.txt: 11 1 12 2022-01-18 Alan Bujtas <zalan@apple.com> 2 13 -
trunk/LayoutTests/imported/w3c/ChangeLog
r288162 r288174 1 2022-01-18 Chris Dumez <cdumez@apple.com> 2 3 When inserting a selected <option> in a <select> element, its selected state should remain 4 https://bugs.webkit.org/show_bug.cgi?id=235237 5 6 Reviewed by Darin Adler. 7 8 Rebaseline WPT tests that are now passing. 9 10 * web-platform-tests/html/semantics/forms/the-select-element/inserted-or-removed-expected.txt: 11 * web-platform-tests/html/semantics/forms/the-select-element/select-validity-expected.txt: 12 1 13 2022-01-18 Chris Dumez <cdumez@apple.com> 2 14 -
trunk/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/inserted-or-removed-expected.txt
r269598 r288174 2 2 3 3 PASS The last selected OPTION should win; Inserted by parser 4 FAIL The last selected OPTION should win; Inserted by DOM API assert_equals: expected "Second" but got "First" 4 PASS The last selected OPTION should win; Inserted by DOM API 5 5 PASS The last selected OPTION should win; Inserted by jQuery append() 6 6 PASS The last selected OPTION should win; Inserted by innerHTML -
trunk/LayoutTests/imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/select-validity-expected.txt
r283525 r288174 5 5 PASS Validation on selects with multiple set 6 6 PASS Validation on selects with non-empty disabled option 7 FAIL Remove and add back the placeholder label option assert_false: If the placeholder label option is selected, required select element shouldn't be valid. expected false got true 7 PASS Remove and add back the placeholder label option 8 8 -
trunk/LayoutTests/platform/ios-wk2/imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/select-validity-expected.txt
r283538 r288174 5 5 PASS Validation on selects with multiple set 6 6 PASS Validation on selects with non-empty disabled option 7 FAIL Remove and add back the placeholder label option assert_false: If the placeholder label option is selected, required select element shouldn't be valid. expected false got true 7 PASS Remove and add back the placeholder label option 8 8 -
trunk/Source/WebCore/ChangeLog
r288167 r288174 1 2022-01-18 Chris Dumez <cdumez@apple.com> 2 3 When inserting a selected <option> in a <select> element, its selected state should remain 4 https://bugs.webkit.org/show_bug.cgi?id=235237 5 6 Reviewed by Darin Adler. 7 8 When inserting a selected <option> in a <select> element, its selected state should remain and other selected 9 options should be de-selected. 10 11 This is as per the specification [1] that says: 12 """ 13 If the multiple attribute is absent, whenever an option element in the select element's list of options has 14 its selectedness set to true, and whenever an option element with its selectedness set to true is added to 15 the select element's list of options, the user agent must set the selectedness of all the other option 16 elements in its list of options to false. 17 """ 18 19 Firefox and Chrome correctly implement this. 20 21 WebKit was trying to implement this logic from inside HTMLOptionElement::insertedIntoAncestor(). However, 22 there were several issues with that: 23 1. It was checking m_isSelected after calling updateValidity(). updateValidity() would call recalcListItems(), 24 which could update m_isSelected. This part could have been addressed by saving m_isSelected before 25 calling updateValidity() but would not have addressed the following issues. 26 2. In the case where an <optgroup> containing several <option> elements is inserted into a <select>, 27 insertedIntoAncestor() gets called from each options being inserted. When calling insertedIntoAncestor() 28 from the first <option> and if this <option> is selected, it would deselect all following <option> elements 29 even though insertedIntoAncestor() has not yet been called for them. As a result, we would end up selecting 30 the first inserted <option> that had the selected state, instead of the last one. 31 3. When more than one <option> is inserted at once, the current implementation would be really inefficient as 32 every <option> would dirty and recalc the item list from insertedIntoAncestor(). 33 34 To address these issues, I got rid of HTMLOptionElement::insertedIntoAncestor(). Instead we now deal with 35 <option> insertion from inside HTMLSelectElement::childrenChanged() and HTMLOptGroupElement::childrenChanged(). 36 Using the parent element's childrenChanged() is useful because it only gets called once when several <option> 37 elements are inserted at once. I added logic to those childrenChanged() functions to keep track of the last 38 selected <option> element being inserted. Then, after we recalc the item list (which may change <option>s' 39 selected state, I make sure to this <option> is selected. This is similar to the logic that was previously 40 in HTMLOptionElement::insertedIntoAncestor(). 41 42 43 [1] https://html.spec.whatwg.org/multipage/form-elements.html#the-select-element 44 45 No new tests, rebaselined existing tests. 46 47 * dom/CharacterData.cpp: 48 (WebCore::makeChildChange): 49 * dom/ContainerNode.cpp: 50 (WebCore::ContainerNode::removeAllChildrenWithScriptAssertion): 51 (WebCore::makeChildChangeForRemoval): 52 (WebCore::makeChildChangeForInsertion): 53 (WebCore::ContainerNode::childrenChanged): 54 (WebCore::affectsElements): Deleted. 55 * dom/ContainerNode.h: 56 (WebCore::ContainerNode::ChildChange::isInsertion const): 57 (WebCore::ContainerNode::ChildChange::affectsElements const): 58 - Add 'siblingChanged' member to ChildChange in addition to the previous / next siblings. 59 This is useful for HTMLOptGroupElement and HTMLSelectElement where we want to know 60 if a newly inserted child is an HTMLOptionElement (and if it is selected). 61 - Move affectsElements() to the header so that it can be reused by HTMLOptGroupElement 62 and HTMLOptionElement. 63 64 * html/HTMLOptGroupElement.cpp: 65 (WebCore::HTMLOptGroupElement::childrenChanged): 66 * html/HTMLOptGroupElement.h: 67 * html/HTMLOptionElement.cpp: 68 (WebCore::HTMLOptionElement::insertedIntoAncestor): Deleted. 69 * html/HTMLOptionElement.h: 70 * html/HTMLSelectElement.cpp: 71 (WebCore::HTMLSelectElement::optionToSelectFromChildChangeScope): Added. 72 (WebCore::HTMLSelectElement::childrenChanged): 73 1 74 2022-01-18 Michael Catanzaro <mcatanzaro@gnome.org> 2 75 -
trunk/Source/WebCore/dom/CharacterData.cpp
r288069 r288174 79 79 return { 80 80 ContainerNode::ChildChange::Type::TextChanged, 81 nullptr, 81 82 ElementTraversal::previousSibling(characterData), 82 83 ElementTraversal::nextSibling(characterData), -
trunk/Source/WebCore/dom/ContainerNode.cpp
r288069 r288174 113 113 disconnectSubframesIfNeeded(*this, DescendantsOnly); 114 114 115 ContainerNode::ChildChange childChange { ChildChange::Type::AllChildrenRemoved, nullptr, nullptr, source };115 ContainerNode::ChildChange childChange { ChildChange::Type::AllChildrenRemoved, nullptr, nullptr, nullptr, source }; 116 116 117 117 WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; … … 153 153 return { 154 154 changeType, 155 dynamicDowncast<Element>(childToRemove), 155 156 ElementTraversal::previousSibling(childToRemove), 156 157 ElementTraversal::nextSibling(childToRemove), … … 226 227 { 227 228 if (replacedAllChildren == ReplacedAllChildren::Yes) 228 return { ContainerNode::ChildChange::Type::AllChildrenReplaced, nullptr, nullptr, source };229 return { ContainerNode::ChildChange::Type::AllChildrenReplaced, nullptr, nullptr, nullptr, source }; 229 230 230 231 auto changeType = [&] { … … 238 239 return { 239 240 changeType, 241 dynamicDowncast<Element>(child), 240 242 beforeChild ? ElementTraversal::previousSibling(*beforeChild) : ElementTraversal::lastChild(containerNode), 241 243 !beforeChild || is<Element>(*beforeChild) ? downcast<Element>(beforeChild) : ElementTraversal::nextSibling(*beforeChild), … … 827 829 } 828 830 829 static bool affectsElements(const ContainerNode::ChildChange& change)830 {831 switch (change.type) {832 case ContainerNode::ChildChange::Type::ElementInserted:833 case ContainerNode::ChildChange::Type::ElementRemoved:834 case ContainerNode::ChildChange::Type::AllChildrenRemoved:835 case ContainerNode::ChildChange::Type::AllChildrenReplaced:836 return true;837 case ContainerNode::ChildChange::Type::TextInserted:838 case ContainerNode::ChildChange::Type::TextRemoved:839 case ContainerNode::ChildChange::Type::TextChanged:840 case ContainerNode::ChildChange::Type::NonContentsChildInserted:841 case ContainerNode::ChildChange::Type::NonContentsChildRemoved:842 return false;843 }844 ASSERT_NOT_REACHED();845 return false;846 }847 848 831 void ContainerNode::childrenChanged(const ChildChange& change) 849 832 { 850 833 document().incDOMTreeVersion(); 851 834 852 if ( affectsElements(change))835 if (change.affectsElements()) 853 836 document().invalidateAccessKeyCache(); 854 837 -
trunk/Source/WebCore/dom/ContainerNode.h
r288069 r288174 81 81 82 82 ChildChange::Type type; 83 Element* siblingChanged; 83 84 Element* previousSiblingElement; 84 85 Element* nextSiblingElement; … … 103 104 return false; 104 105 } 106 107 bool affectsElements() const 108 { 109 switch (type) { 110 case ChildChange::Type::ElementInserted: 111 case ChildChange::Type::ElementRemoved: 112 case ChildChange::Type::AllChildrenRemoved: 113 case ChildChange::Type::AllChildrenReplaced: 114 return true; 115 case ChildChange::Type::TextInserted: 116 case ChildChange::Type::TextRemoved: 117 case ChildChange::Type::TextChanged: 118 case ChildChange::Type::NonContentsChildInserted: 119 case ChildChange::Type::NonContentsChildRemoved: 120 return false; 121 } 122 ASSERT_NOT_REACHED(); 123 return false; 124 } 105 125 }; 106 126 virtual void childrenChanged(const ChildChange&); -
trunk/Source/WebCore/html/HTMLOptGroupElement.cpp
r288069 r288174 78 78 void HTMLOptGroupElement::childrenChanged(const ChildChange& change) 79 79 { 80 bool isRelevant = change.affectsElements(); 81 RefPtr select = isRelevant ? ownerSelectElement() : nullptr; 82 if (!isRelevant || !select) { 83 HTMLElement::childrenChanged(change); 84 return; 85 } 86 87 auto selectOptionIfNecessaryScope = select->optionToSelectFromChildChangeScope(change, this); 88 80 89 recalcSelectOptions(); 81 90 HTMLElement::childrenChanged(change); -
trunk/Source/WebCore/html/HTMLOptionElement.cpp
r288033 r288174 316 316 } 317 317 318 Node::InsertedIntoAncestorResult HTMLOptionElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)319 {320 if (RefPtr select = ownerSelectElement()) {321 select->setRecalcListItems();322 select->updateValidity();323 // Do not call selected() since calling updateListItemSelectedStates()324 // at this time won't do the right thing. (Why, exactly?)325 // FIXME: Might be better to call this unconditionally, always passing m_isSelected,326 // rather than only calling it if we are selected.327 if (m_isSelected)328 select->optionSelectionStateChanged(*this, true);329 select->scrollToSelection();330 }331 332 return HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree);333 }334 335 318 String HTMLOptionElement::collectOptionInnerText() const 336 319 { -
trunk/Source/WebCore/html/HTMLOptionElement.h
r287363 r288174 62 62 63 63 void setSelectedState(bool); 64 bool selectedWithoutUpdate() const { return m_isSelected; } 64 65 65 66 private: … … 72 73 void parseAttribute(const QualifiedName&, const AtomString&) final; 73 74 74 InsertedIntoAncestorResult insertedIntoAncestor(InsertionType, ContainerNode&) final;75 75 bool accessKeyAction(bool) final; 76 76 -
trunk/Source/WebCore/html/HTMLSelectElement.cpp
r288033 r288174 372 372 } 373 373 374 CompletionHandlerCallingScope HTMLSelectElement::optionToSelectFromChildChangeScope(const ContainerNode::ChildChange& change, HTMLOptGroupElement* parentOptGroup) 375 { 376 if (multiple()) 377 return { }; 378 379 auto getLastSelectedOption = [](HTMLOptGroupElement& optGroup) -> HTMLOptionElement* { 380 for (auto* option = Traversal<HTMLOptionElement>::lastChild(optGroup); option; option = Traversal<HTMLOptionElement>::previousSibling(*option)) { 381 if (option->selectedWithoutUpdate()) 382 return option; 383 } 384 return nullptr; 385 }; 386 387 RefPtr<HTMLOptionElement> optionToSelect; 388 if (change.type == ChildChange::Type::ElementInserted) { 389 if (is<HTMLOptionElement>(change.siblingChanged)) { 390 auto& option = downcast<HTMLOptionElement>(*change.siblingChanged); 391 if (option.selectedWithoutUpdate()) 392 optionToSelect = &option; 393 } else if (!parentOptGroup && is<HTMLOptGroupElement>(change.siblingChanged)) 394 optionToSelect = getLastSelectedOption(*downcast<HTMLOptGroupElement>(change.siblingChanged)); 395 } else if (parentOptGroup && change.type == ContainerNode::ChildChange::Type::AllChildrenReplaced) 396 optionToSelect = getLastSelectedOption(*parentOptGroup); 397 398 return CompletionHandlerCallingScope { [optionToSelect = WTFMove(optionToSelect), isInsertion = change.isInsertion(), select = Ref { *this }] { 399 if (optionToSelect) 400 select->optionSelectionStateChanged(*optionToSelect, true); 401 else if (isInsertion) 402 select->scrollToSelection(); 403 } }; 404 } 405 374 406 void HTMLSelectElement::childrenChanged(const ChildChange& change) 375 407 { 408 if (!change.affectsElements()) { 409 HTMLFormControlElementWithState::childrenChanged(change); 410 return; 411 } 412 413 auto selectOptionIfNecessaryScope = optionToSelectFromChildChangeScope(change); 414 376 415 setRecalcListItems(); 377 416 updateValidity(); -
trunk/Source/WebCore/html/HTMLSelectElement.h
r287529 r288174 28 28 #include "HTMLFormControlElementWithState.h" 29 29 #include "TypeAhead.h" 30 #include <wtf/CompletionHandler.h> 30 31 31 32 namespace WebCore { … … 105 106 void optionSelectionStateChanged(HTMLOptionElement&, bool optionIsSelected); 106 107 bool allowsNonContiguousSelection() const { return m_allowsNonContiguousSelection; }; 108 109 CompletionHandlerCallingScope optionToSelectFromChildChangeScope(const ChildChange&, HTMLOptGroupElement* parentOptGroup = nullptr); 107 110 108 111 protected:
Note: See TracChangeset
for help on using the changeset viewer.