1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/HTMLMenuItemElement.h"
9 #include "mozilla/BasicEvents.h"
10 #include "mozilla/EventDispatcher.h"
11 #include "mozilla/dom/HTMLMenuItemElementBinding.h"
12 #include "nsAttrValueInlines.h"
13 #include "nsContentUtils.h"
15 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(MenuItem
)
20 // First bits are needed for the menuitem type.
21 #define NS_CHECKED_IS_TOGGLED (1 << 2)
22 #define NS_ORIGINAL_CHECKED_VALUE (1 << 3)
23 #define NS_MENUITEM_TYPE(bits) \
24 ((bits) & ~(NS_CHECKED_IS_TOGGLED | NS_ORIGINAL_CHECKED_VALUE))
26 enum CmdType
: uint8_t {
27 CMD_TYPE_MENUITEM
= 1,
32 static const nsAttrValue::EnumTable kMenuItemTypeTable
[] = {
33 {"menuitem", CMD_TYPE_MENUITEM
},
34 {"checkbox", CMD_TYPE_CHECKBOX
},
35 {"radio", CMD_TYPE_RADIO
},
38 static const nsAttrValue::EnumTable
* kMenuItemDefaultType
=
39 &kMenuItemTypeTable
[0];
41 // A base class inherited by all radio visitors.
48 * Visit a node in the tree. This is meant to be called on all radios in a
49 * group, sequentially. If the method returns false then the iteration is
52 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) = 0;
55 // Find the selected radio, see GetSelectedRadio().
56 class GetCheckedVisitor
: public Visitor
{
58 explicit GetCheckedVisitor(HTMLMenuItemElement
** aResult
)
60 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
{
61 if (aMenuItem
->IsChecked()) {
69 HTMLMenuItemElement
** mResult
;
72 // Deselect all radios except the one passed to the constructor.
73 class ClearCheckedVisitor
: public Visitor
{
75 explicit ClearCheckedVisitor(HTMLMenuItemElement
* aExcludeMenuItem
)
76 : mExcludeMenuItem(aExcludeMenuItem
) {}
77 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
{
78 if (aMenuItem
!= mExcludeMenuItem
&& aMenuItem
->IsChecked()) {
79 aMenuItem
->ClearChecked();
85 HTMLMenuItemElement
* mExcludeMenuItem
;
88 // Get current value of the checked dirty flag. The same value is stored on all
89 // radios in the group, so we need to check only the first one.
90 class GetCheckedDirtyVisitor
: public Visitor
{
92 GetCheckedDirtyVisitor(bool* aCheckedDirty
,
93 HTMLMenuItemElement
* aExcludeMenuItem
)
94 : mCheckedDirty(aCheckedDirty
), mExcludeMenuItem(aExcludeMenuItem
) {}
95 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
{
96 if (aMenuItem
== mExcludeMenuItem
) {
99 *mCheckedDirty
= aMenuItem
->IsCheckedDirty();
105 HTMLMenuItemElement
* mExcludeMenuItem
;
108 // Set checked dirty to true on all radios in the group.
109 class SetCheckedDirtyVisitor
: public Visitor
{
111 SetCheckedDirtyVisitor() {}
112 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
{
113 aMenuItem
->SetCheckedDirty();
118 // A helper visitor that is used to combine two operations (visitors) to avoid
119 // iterating over radios twice.
120 class CombinedVisitor
: public Visitor
{
122 CombinedVisitor(Visitor
* aVisitor1
, Visitor
* aVisitor2
)
123 : mVisitor1(aVisitor1
),
124 mVisitor2(aVisitor2
),
127 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
{
129 mContinue1
= mVisitor1
->Visit(aMenuItem
);
132 mContinue2
= mVisitor2
->Visit(aMenuItem
);
134 return mContinue1
|| mContinue2
;
144 HTMLMenuItemElement::HTMLMenuItemElement(
145 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
146 FromParser aFromParser
)
147 : nsGenericHTMLElement(std::move(aNodeInfo
)),
148 mType(kMenuItemDefaultType
->value
),
149 mParserCreating(false),
150 mShouldInitChecked(false),
151 mCheckedDirty(false),
153 mParserCreating
= aFromParser
;
156 HTMLMenuItemElement::~HTMLMenuItemElement() {}
158 // NS_IMPL_ELEMENT_CLONE(HTMLMenuItemElement)
160 nsresult
HTMLMenuItemElement::Clone(dom::NodeInfo
* aNodeInfo
,
161 nsINode
** aResult
) const {
163 RefPtr
<HTMLMenuItemElement
> it
=
164 new HTMLMenuItemElement(do_AddRef(aNodeInfo
), NOT_FROM_PARSER
);
165 nsresult rv
= const_cast<HTMLMenuItemElement
*>(this)->CopyInnerTo(it
);
166 if (NS_SUCCEEDED(rv
)) {
168 case CMD_TYPE_CHECKBOX
:
171 // We no longer have our original checked state. Set our
172 // checked state on the clone.
173 it
->mCheckedDirty
= true;
174 it
->mChecked
= mChecked
;
185 void HTMLMenuItemElement::GetType(DOMString
& aValue
) {
186 GetEnumAttr(nsGkAtoms::type
, kMenuItemDefaultType
->tag
, aValue
);
189 void HTMLMenuItemElement::SetChecked(bool aChecked
) {
190 bool checkedChanged
= mChecked
!= aChecked
;
194 if (mType
== CMD_TYPE_RADIO
) {
195 if (checkedChanged
) {
197 ClearCheckedVisitor
visitor(this);
198 WalkRadioGroup(&visitor
);
200 ClearCheckedVisitor
visitor1(this);
201 SetCheckedDirtyVisitor visitor2
;
202 CombinedVisitor
visitor(&visitor1
, &visitor2
);
203 WalkRadioGroup(&visitor
);
205 } else if (!mCheckedDirty
) {
206 SetCheckedDirtyVisitor visitor
;
207 WalkRadioGroup(&visitor
);
210 mCheckedDirty
= true;
214 void HTMLMenuItemElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
215 if (aVisitor
.mEvent
->mMessage
== eMouseClick
) {
216 bool originalCheckedValue
= false;
218 case CMD_TYPE_CHECKBOX
:
219 originalCheckedValue
= mChecked
;
220 SetChecked(!originalCheckedValue
);
221 aVisitor
.mItemFlags
|= NS_CHECKED_IS_TOGGLED
;
224 // casting back to Element* here to resolve nsISupports ambiguity.
225 Element
* supports
= GetSelectedRadio();
226 aVisitor
.mItemData
= supports
;
228 originalCheckedValue
= mChecked
;
229 if (!originalCheckedValue
) {
231 aVisitor
.mItemFlags
|= NS_CHECKED_IS_TOGGLED
;
236 if (originalCheckedValue
) {
237 aVisitor
.mItemFlags
|= NS_ORIGINAL_CHECKED_VALUE
;
240 // We must cache type because mType may change during JS event.
241 aVisitor
.mItemFlags
|= mType
;
244 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
247 nsresult
HTMLMenuItemElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
248 // Check to see if the event was cancelled.
249 if (aVisitor
.mEvent
->mMessage
== eMouseClick
&&
250 aVisitor
.mItemFlags
& NS_CHECKED_IS_TOGGLED
&&
251 aVisitor
.mEventStatus
== nsEventStatus_eConsumeNoDefault
) {
252 bool originalCheckedValue
=
253 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_CHECKED_VALUE
);
254 uint8_t oldType
= NS_MENUITEM_TYPE(aVisitor
.mItemFlags
);
256 nsCOMPtr
<nsIContent
> content(do_QueryInterface(aVisitor
.mItemData
));
257 RefPtr
<HTMLMenuItemElement
> selectedRadio
=
258 HTMLMenuItemElement::FromNodeOrNull(content
);
260 selectedRadio
->SetChecked(true);
261 if (mType
!= CMD_TYPE_RADIO
) {
264 } else if (oldType
== CMD_TYPE_CHECKBOX
) {
265 SetChecked(originalCheckedValue
);
272 nsresult
HTMLMenuItemElement::BindToTree(BindContext
& aContext
,
274 nsresult rv
= nsGenericHTMLElement::BindToTree(aContext
, aParent
);
275 NS_ENSURE_SUCCESS(rv
, rv
);
277 if (IsInUncomposedDoc() && mType
== CMD_TYPE_RADIO
) {
284 bool HTMLMenuItemElement::ParseAttribute(int32_t aNamespaceID
,
286 const nsAString
& aValue
,
287 nsIPrincipal
* aMaybeScriptedPrincipal
,
288 nsAttrValue
& aResult
) {
289 if (aNamespaceID
== kNameSpaceID_None
) {
290 if (aAttribute
== nsGkAtoms::type
) {
291 return aResult
.ParseEnumValue(aValue
, kMenuItemTypeTable
, false,
292 kMenuItemDefaultType
);
295 if (aAttribute
== nsGkAtoms::radiogroup
) {
296 aResult
.ParseAtom(aValue
);
301 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
302 aMaybeScriptedPrincipal
, aResult
);
305 void HTMLMenuItemElement::DoneCreatingElement() {
306 mParserCreating
= false;
308 if (mShouldInitChecked
) {
310 mShouldInitChecked
= false;
314 void HTMLMenuItemElement::GetText(nsAString
& aText
) {
316 nsContentUtils::GetNodeTextContent(this, false, text
);
318 text
.CompressWhitespace(true, true);
322 nsresult
HTMLMenuItemElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
323 const nsAttrValue
* aValue
,
324 const nsAttrValue
* aOldValue
,
325 nsIPrincipal
* aSubjectPrincipal
,
327 if (aNameSpaceID
== kNameSpaceID_None
) {
328 // Handle type changes first, since some of the later conditions in this
329 // method look at mType and want to see the new value.
330 if (aName
== nsGkAtoms::type
) {
332 mType
= aValue
->GetEnumValue();
334 mType
= kMenuItemDefaultType
->value
;
338 if ((aName
== nsGkAtoms::radiogroup
|| aName
== nsGkAtoms::type
) &&
339 mType
== CMD_TYPE_RADIO
&& !mParserCreating
) {
340 if (IsInUncomposedDoc() && GetParent()) {
345 // Checked must be set no matter what type of menuitem it is, since
346 // GetChecked() must reflect the new value
347 if (aName
== nsGkAtoms::checked
&& !mCheckedDirty
) {
348 if (mParserCreating
) {
349 mShouldInitChecked
= true;
356 return nsGenericHTMLElement::AfterSetAttr(
357 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
360 void HTMLMenuItemElement::WalkRadioGroup(Visitor
* aVisitor
) {
361 nsIContent
* parent
= GetParent();
363 aVisitor
->Visit(this);
367 BorrowedAttrInfo
info1(GetAttrInfo(kNameSpaceID_None
, nsGkAtoms::radiogroup
));
368 bool info1Empty
= !info1
.mValue
|| info1
.mValue
->IsEmptyString();
370 for (nsIContent
* cur
= parent
->GetFirstChild(); cur
;
371 cur
= cur
->GetNextSibling()) {
372 HTMLMenuItemElement
* menuitem
= HTMLMenuItemElement::FromNode(cur
);
374 if (!menuitem
|| menuitem
->GetType() != CMD_TYPE_RADIO
) {
378 BorrowedAttrInfo
info2(
379 menuitem
->GetAttrInfo(kNameSpaceID_None
, nsGkAtoms::radiogroup
));
380 bool info2Empty
= !info2
.mValue
|| info2
.mValue
->IsEmptyString();
382 if (info1Empty
!= info2Empty
|| (info1
.mValue
&& info2
.mValue
&&
383 !info1
.mValue
->Equals(*info2
.mValue
))) {
387 if (!aVisitor
->Visit(menuitem
)) {
393 HTMLMenuItemElement
* HTMLMenuItemElement::GetSelectedRadio() {
394 HTMLMenuItemElement
* result
= nullptr;
396 GetCheckedVisitor
visitor(&result
);
397 WalkRadioGroup(&visitor
);
402 void HTMLMenuItemElement::AddedToRadioGroup() {
403 bool checkedDirty
= mCheckedDirty
;
405 ClearCheckedVisitor
visitor1(this);
406 GetCheckedDirtyVisitor
visitor2(&checkedDirty
, this);
407 CombinedVisitor
visitor(&visitor1
, &visitor2
);
408 WalkRadioGroup(&visitor
);
410 GetCheckedDirtyVisitor
visitor(&checkedDirty
, this);
411 WalkRadioGroup(&visitor
);
413 mCheckedDirty
= checkedDirty
;
416 void HTMLMenuItemElement::InitChecked() {
417 bool defaultChecked
= DefaultChecked();
418 mChecked
= defaultChecked
;
419 if (mType
== CMD_TYPE_RADIO
) {
420 ClearCheckedVisitor
visitor(this);
421 WalkRadioGroup(&visitor
);
425 JSObject
* HTMLMenuItemElement::WrapNode(JSContext
* aCx
,
426 JS::Handle
<JSObject
*> aGivenProto
) {
427 return HTMLMenuItemElement_Binding::Wrap(aCx
, this, aGivenProto
);
431 } // namespace mozilla
433 #undef NS_ORIGINAL_CHECKED_VALUE