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"
16 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(MenuItem
)
21 // First bits are needed for the menuitem type.
22 #define NS_CHECKED_IS_TOGGLED (1 << 2)
23 #define NS_ORIGINAL_CHECKED_VALUE (1 << 3)
24 #define NS_MENUITEM_TYPE(bits) ((bits) & ~( \
25 NS_CHECKED_IS_TOGGLED | NS_ORIGINAL_CHECKED_VALUE))
27 enum CmdType
: uint8_t
29 CMD_TYPE_MENUITEM
= 1,
34 static const nsAttrValue::EnumTable kMenuItemTypeTable
[] = {
35 { "menuitem", CMD_TYPE_MENUITEM
},
36 { "checkbox", CMD_TYPE_CHECKBOX
},
37 { "radio", CMD_TYPE_RADIO
},
41 static const nsAttrValue::EnumTable
* kMenuItemDefaultType
=
42 &kMenuItemTypeTable
[0];
44 // A base class inherited by all radio visitors.
49 virtual ~Visitor() { }
52 * Visit a node in the tree. This is meant to be called on all radios in a
53 * group, sequentially. If the method returns false then the iteration is
56 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) = 0;
59 // Find the selected radio, see GetSelectedRadio().
60 class GetCheckedVisitor
: public Visitor
63 explicit GetCheckedVisitor(HTMLMenuItemElement
** aResult
)
66 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
68 if (aMenuItem
->IsChecked()) {
75 HTMLMenuItemElement
** mResult
;
78 // Deselect all radios except the one passed to the constructor.
79 class ClearCheckedVisitor
: public Visitor
82 explicit ClearCheckedVisitor(HTMLMenuItemElement
* aExcludeMenuItem
)
83 : mExcludeMenuItem(aExcludeMenuItem
)
85 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
87 if (aMenuItem
!= mExcludeMenuItem
&& aMenuItem
->IsChecked()) {
88 aMenuItem
->ClearChecked();
93 HTMLMenuItemElement
* mExcludeMenuItem
;
96 // Get current value of the checked dirty flag. The same value is stored on all
97 // radios in the group, so we need to check only the first one.
98 class GetCheckedDirtyVisitor
: public Visitor
101 GetCheckedDirtyVisitor(bool* aCheckedDirty
,
102 HTMLMenuItemElement
* aExcludeMenuItem
)
103 : mCheckedDirty(aCheckedDirty
),
104 mExcludeMenuItem(aExcludeMenuItem
)
106 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
108 if (aMenuItem
== mExcludeMenuItem
) {
111 *mCheckedDirty
= aMenuItem
->IsCheckedDirty();
116 HTMLMenuItemElement
* mExcludeMenuItem
;
119 // Set checked dirty to true on all radios in the group.
120 class SetCheckedDirtyVisitor
: public Visitor
123 SetCheckedDirtyVisitor()
125 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
127 aMenuItem
->SetCheckedDirty();
132 // A helper visitor that is used to combine two operations (visitors) to avoid
133 // iterating over radios twice.
134 class CombinedVisitor
: public Visitor
137 CombinedVisitor(Visitor
* aVisitor1
, Visitor
* aVisitor2
)
138 : mVisitor1(aVisitor1
), mVisitor2(aVisitor2
),
139 mContinue1(true), mContinue2(true)
141 virtual bool Visit(HTMLMenuItemElement
* aMenuItem
) override
144 mContinue1
= mVisitor1
->Visit(aMenuItem
);
147 mContinue2
= mVisitor2
->Visit(aMenuItem
);
149 return mContinue1
|| mContinue2
;
159 HTMLMenuItemElement::HTMLMenuItemElement(
160 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
, FromParser aFromParser
)
161 : nsGenericHTMLElement(std::move(aNodeInfo
)),
162 mType(kMenuItemDefaultType
->value
),
163 mParserCreating(false),
164 mShouldInitChecked(false),
165 mCheckedDirty(false),
168 mParserCreating
= aFromParser
;
171 HTMLMenuItemElement::~HTMLMenuItemElement()
176 //NS_IMPL_ELEMENT_CLONE(HTMLMenuItemElement)
179 HTMLMenuItemElement::Clone(dom::NodeInfo
* aNodeInfo
, nsINode
** aResult
) const
182 RefPtr
<HTMLMenuItemElement
> it
=
183 new HTMLMenuItemElement(do_AddRef(aNodeInfo
), NOT_FROM_PARSER
);
184 nsresult rv
= const_cast<HTMLMenuItemElement
*>(this)->CopyInnerTo(it
);
185 if (NS_SUCCEEDED(rv
)) {
187 case CMD_TYPE_CHECKBOX
:
190 // We no longer have our original checked state. Set our
191 // checked state on the clone.
192 it
->mCheckedDirty
= true;
193 it
->mChecked
= mChecked
;
205 HTMLMenuItemElement::GetType(DOMString
& aValue
)
207 GetEnumAttr(nsGkAtoms::type
, kMenuItemDefaultType
->tag
, aValue
);
211 HTMLMenuItemElement::SetChecked(bool aChecked
)
213 bool checkedChanged
= mChecked
!= aChecked
;
217 if (mType
== CMD_TYPE_RADIO
) {
218 if (checkedChanged
) {
220 ClearCheckedVisitor
visitor(this);
221 WalkRadioGroup(&visitor
);
223 ClearCheckedVisitor
visitor1(this);
224 SetCheckedDirtyVisitor visitor2
;
225 CombinedVisitor
visitor(&visitor1
, &visitor2
);
226 WalkRadioGroup(&visitor
);
228 } else if (!mCheckedDirty
) {
229 SetCheckedDirtyVisitor visitor
;
230 WalkRadioGroup(&visitor
);
233 mCheckedDirty
= true;
238 HTMLMenuItemElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
)
240 if (aVisitor
.mEvent
->mMessage
== eMouseClick
) {
242 bool originalCheckedValue
= false;
244 case CMD_TYPE_CHECKBOX
:
245 originalCheckedValue
= mChecked
;
246 SetChecked(!originalCheckedValue
);
247 aVisitor
.mItemFlags
|= NS_CHECKED_IS_TOGGLED
;
250 // casting back to Element* here to resolve nsISupports ambiguity.
251 Element
* supports
= GetSelectedRadio();
252 aVisitor
.mItemData
= supports
;
254 originalCheckedValue
= mChecked
;
255 if (!originalCheckedValue
) {
257 aVisitor
.mItemFlags
|= NS_CHECKED_IS_TOGGLED
;
262 if (originalCheckedValue
) {
263 aVisitor
.mItemFlags
|= NS_ORIGINAL_CHECKED_VALUE
;
266 // We must cache type because mType may change during JS event.
267 aVisitor
.mItemFlags
|= mType
;
270 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
274 HTMLMenuItemElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
)
276 // Check to see if the event was cancelled.
277 if (aVisitor
.mEvent
->mMessage
== eMouseClick
&&
278 aVisitor
.mItemFlags
& NS_CHECKED_IS_TOGGLED
&&
279 aVisitor
.mEventStatus
== nsEventStatus_eConsumeNoDefault
) {
280 bool originalCheckedValue
=
281 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_CHECKED_VALUE
);
282 uint8_t oldType
= NS_MENUITEM_TYPE(aVisitor
.mItemFlags
);
284 nsCOMPtr
<nsIContent
> content(do_QueryInterface(aVisitor
.mItemData
));
285 RefPtr
<HTMLMenuItemElement
> selectedRadio
= HTMLMenuItemElement::FromNodeOrNull(content
);
287 selectedRadio
->SetChecked(true);
288 if (mType
!= CMD_TYPE_RADIO
) {
291 } else if (oldType
== CMD_TYPE_CHECKBOX
) {
292 SetChecked(originalCheckedValue
);
300 HTMLMenuItemElement::BindToTree(nsIDocument
* aDocument
, nsIContent
* aParent
,
301 nsIContent
* aBindingParent
)
303 nsresult rv
= nsGenericHTMLElement::BindToTree(aDocument
, aParent
,
306 if (NS_SUCCEEDED(rv
) && aDocument
&& mType
== CMD_TYPE_RADIO
) {
314 HTMLMenuItemElement::ParseAttribute(int32_t aNamespaceID
,
316 const nsAString
& aValue
,
317 nsIPrincipal
* aMaybeScriptedPrincipal
,
318 nsAttrValue
& aResult
)
320 if (aNamespaceID
== kNameSpaceID_None
) {
321 if (aAttribute
== nsGkAtoms::type
) {
322 return aResult
.ParseEnumValue(aValue
, kMenuItemTypeTable
, false,
323 kMenuItemDefaultType
);
326 if (aAttribute
== nsGkAtoms::radiogroup
) {
327 aResult
.ParseAtom(aValue
);
332 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
333 aMaybeScriptedPrincipal
, aResult
);
337 HTMLMenuItemElement::DoneCreatingElement()
339 mParserCreating
= false;
341 if (mShouldInitChecked
) {
343 mShouldInitChecked
= false;
348 HTMLMenuItemElement::GetText(nsAString
& aText
)
351 nsContentUtils::GetNodeTextContent(this, false, text
);
353 text
.CompressWhitespace(true, true);
358 HTMLMenuItemElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
359 const nsAttrValue
* aValue
,
360 const nsAttrValue
* aOldValue
,
361 nsIPrincipal
* aSubjectPrincipal
,
364 if (aNameSpaceID
== kNameSpaceID_None
) {
365 // Handle type changes first, since some of the later conditions in this
366 // method look at mType and want to see the new value.
367 if (aName
== nsGkAtoms::type
) {
369 mType
= aValue
->GetEnumValue();
371 mType
= kMenuItemDefaultType
->value
;
375 if ((aName
== nsGkAtoms::radiogroup
|| aName
== nsGkAtoms::type
) &&
376 mType
== CMD_TYPE_RADIO
&&
378 if (IsInUncomposedDoc() && GetParent()) {
383 // Checked must be set no matter what type of menuitem it is, since
384 // GetChecked() must reflect the new value
385 if (aName
== nsGkAtoms::checked
&&
387 if (mParserCreating
) {
388 mShouldInitChecked
= true;
395 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID
, aName
, aValue
,
396 aOldValue
, aSubjectPrincipal
, aNotify
);
400 HTMLMenuItemElement::WalkRadioGroup(Visitor
* aVisitor
)
402 nsIContent
* parent
= GetParent();
404 aVisitor
->Visit(this);
408 BorrowedAttrInfo
info1(GetAttrInfo(kNameSpaceID_None
,
409 nsGkAtoms::radiogroup
));
410 bool info1Empty
= !info1
.mValue
|| info1
.mValue
->IsEmptyString();
412 for (nsIContent
* cur
= parent
->GetFirstChild();
414 cur
= cur
->GetNextSibling()) {
415 HTMLMenuItemElement
* menuitem
= HTMLMenuItemElement::FromNode(cur
);
417 if (!menuitem
|| menuitem
->GetType() != CMD_TYPE_RADIO
) {
421 BorrowedAttrInfo
info2(menuitem
->GetAttrInfo(kNameSpaceID_None
,
422 nsGkAtoms::radiogroup
));
423 bool info2Empty
= !info2
.mValue
|| info2
.mValue
->IsEmptyString();
425 if (info1Empty
!= info2Empty
||
426 (info1
.mValue
&& info2
.mValue
&& !info1
.mValue
->Equals(*info2
.mValue
))) {
430 if (!aVisitor
->Visit(menuitem
)) {
437 HTMLMenuItemElement::GetSelectedRadio()
439 HTMLMenuItemElement
* result
= nullptr;
441 GetCheckedVisitor
visitor(&result
);
442 WalkRadioGroup(&visitor
);
448 HTMLMenuItemElement::AddedToRadioGroup()
450 bool checkedDirty
= mCheckedDirty
;
452 ClearCheckedVisitor
visitor1(this);
453 GetCheckedDirtyVisitor
visitor2(&checkedDirty
, this);
454 CombinedVisitor
visitor(&visitor1
, &visitor2
);
455 WalkRadioGroup(&visitor
);
457 GetCheckedDirtyVisitor
visitor(&checkedDirty
, this);
458 WalkRadioGroup(&visitor
);
460 mCheckedDirty
= checkedDirty
;
464 HTMLMenuItemElement::InitChecked()
466 bool defaultChecked
= DefaultChecked();
467 mChecked
= defaultChecked
;
468 if (mType
== CMD_TYPE_RADIO
) {
469 ClearCheckedVisitor
visitor(this);
470 WalkRadioGroup(&visitor
);
475 HTMLMenuItemElement::WrapNode(JSContext
* aCx
, JS::Handle
<JSObject
*> aGivenProto
)
477 return HTMLMenuItemElement_Binding::Wrap(aCx
, this, aGivenProto
);
481 } // namespace mozilla
483 #undef NS_ORIGINAL_CHECKED_VALUE