1 /*
2 * Copyright (c) 2002-2026 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.html;
16
17 import org.w3c.dom.Element;
18 import org.w3c.dom.Node;
19
20 /**
21 * A marker interface for those classes that can be disabled.
22 *
23 * @author David D. Kilzer
24 * @author Ronald Brill
25 */
26 public interface DisabledElement extends Element {
27
28 /** The "disabled" attribute name. */
29 String ATTRIBUTE_DISABLED = "disabled";
30
31 /**
32 * Returns {@code true} if the disabled attribute is set for this element.
33 * <p>
34 * Per spec, a disabled {@link HtmlFieldSet} ancestor disables its
35 * descendants EXCEPT for descendants of that fieldset's first
36 * {@code <legend>} child, if any. This is checked cheaply while walking
37 * up the ancestor chain: at each step, the node we just came from
38 * (tracked as {@code previous}) is by construction a direct child of the
39 * node we're currently examining, so when that node turns out to be a
40 * disabled fieldset we already know its relevant direct child without
41 * any second traversal -- see {@link #isFirstLegendChild(HtmlFieldSet, Node)}.
42 * </p>
43 *
44 * @return {@code true} if the disabled attribute is set for this element
45 */
46 default boolean isDisabled() {
47 if (hasAttribute(ATTRIBUTE_DISABLED)) {
48 return true;
49 }
50
51 Node previous = this;
52 Node node = getParentNode();
53 while (node != null) {
54 if (node instanceof HtmlFieldSet fieldSet) {
55 if (fieldSet.hasAttribute(ATTRIBUTE_DISABLED)
56 && !isFirstLegendChild(fieldSet, previous)) {
57 return true;
58 }
59 }
60 else if (node instanceof DisabledElement element
61 && element.hasAttribute(ATTRIBUTE_DISABLED)) {
62 return true;
63 }
64 previous = node;
65 node = node.getParentNode();
66 }
67
68 return false;
69 }
70
71 /**
72 * Checks whether {@code candidate} -- the direct child of {@code fieldSet}
73 * that lies on the path up from this element -- is {@code fieldSet}'s
74 * FIRST {@code <legend>} child. The exemption from fieldset disabling
75 * applies only to the first legend -- a control inside a second,
76 * non-conforming {@code <legend>} is still disabled. Bounded by how many
77 * children precede the first legend (in practice O(1), since a legend is
78 * almost always the first child), not by how deep this element is nested
79 * inside it.
80 *
81 * @param fieldSet the disabled fieldset ancestor to check the exemption against
82 * @param candidate the direct child of {@code fieldSet} on the path up from this element
83 * @return {@code true} if {@code candidate} is {@code fieldSet}'s first {@code <legend>} child
84 */
85 private static boolean isFirstLegendChild(final HtmlFieldSet fieldSet, final Node candidate) {
86 if (!(candidate instanceof HtmlLegend)) {
87 return false;
88 }
89
90 for (final DomNode child : fieldSet.getChildren()) {
91 if (child instanceof HtmlLegend) {
92 return child == candidate;
93 }
94 }
95 return false;
96 }
97
98 /**
99 * Returns the value of the attribute {@code disabled}. Refer to the
100 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
101 * documentation for details on the use of this attribute.
102 *
103 * @return the value of the attribute {@code disabled} or an empty string if that attribute isn't defined
104 */
105 String getDisabledAttribute();
106 }