Bug 1494333 - index crons just like artifacts r=Callek
[gecko.git] / dom / html / HTMLMenuItemElement.cpp
blob96a7868b1cbd69c2c4f94b5957cac24da3edcb0b
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)
18 namespace mozilla {
19 namespace dom {
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,
30 CMD_TYPE_CHECKBOX,
31 CMD_TYPE_RADIO
34 static const nsAttrValue::EnumTable kMenuItemTypeTable[] = {
35 { "menuitem", CMD_TYPE_MENUITEM },
36 { "checkbox", CMD_TYPE_CHECKBOX },
37 { "radio", CMD_TYPE_RADIO },
38 { nullptr, 0 }
41 static const nsAttrValue::EnumTable* kMenuItemDefaultType =
42 &kMenuItemTypeTable[0];
44 // A base class inherited by all radio visitors.
45 class Visitor
47 public:
48 Visitor() { }
49 virtual ~Visitor() { }
51 /**
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
54 * stopped.
56 virtual bool Visit(HTMLMenuItemElement* aMenuItem) = 0;
59 // Find the selected radio, see GetSelectedRadio().
60 class GetCheckedVisitor : public Visitor
62 public:
63 explicit GetCheckedVisitor(HTMLMenuItemElement** aResult)
64 : mResult(aResult)
65 { }
66 virtual bool Visit(HTMLMenuItemElement* aMenuItem) override
68 if (aMenuItem->IsChecked()) {
69 *mResult = aMenuItem;
70 return false;
72 return true;
74 protected:
75 HTMLMenuItemElement** mResult;
78 // Deselect all radios except the one passed to the constructor.
79 class ClearCheckedVisitor : public Visitor
81 public:
82 explicit ClearCheckedVisitor(HTMLMenuItemElement* aExcludeMenuItem)
83 : mExcludeMenuItem(aExcludeMenuItem)
84 { }
85 virtual bool Visit(HTMLMenuItemElement* aMenuItem) override
87 if (aMenuItem != mExcludeMenuItem && aMenuItem->IsChecked()) {
88 aMenuItem->ClearChecked();
90 return true;
92 protected:
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
100 public:
101 GetCheckedDirtyVisitor(bool* aCheckedDirty,
102 HTMLMenuItemElement* aExcludeMenuItem)
103 : mCheckedDirty(aCheckedDirty),
104 mExcludeMenuItem(aExcludeMenuItem)
106 virtual bool Visit(HTMLMenuItemElement* aMenuItem) override
108 if (aMenuItem == mExcludeMenuItem) {
109 return true;
111 *mCheckedDirty = aMenuItem->IsCheckedDirty();
112 return false;
114 protected:
115 bool* mCheckedDirty;
116 HTMLMenuItemElement* mExcludeMenuItem;
119 // Set checked dirty to true on all radios in the group.
120 class SetCheckedDirtyVisitor : public Visitor
122 public:
123 SetCheckedDirtyVisitor()
125 virtual bool Visit(HTMLMenuItemElement* aMenuItem) override
127 aMenuItem->SetCheckedDirty();
128 return true;
132 // A helper visitor that is used to combine two operations (visitors) to avoid
133 // iterating over radios twice.
134 class CombinedVisitor : public Visitor
136 public:
137 CombinedVisitor(Visitor* aVisitor1, Visitor* aVisitor2)
138 : mVisitor1(aVisitor1), mVisitor2(aVisitor2),
139 mContinue1(true), mContinue2(true)
141 virtual bool Visit(HTMLMenuItemElement* aMenuItem) override
143 if (mContinue1) {
144 mContinue1 = mVisitor1->Visit(aMenuItem);
146 if (mContinue2) {
147 mContinue2 = mVisitor2->Visit(aMenuItem);
149 return mContinue1 || mContinue2;
151 protected:
152 Visitor* mVisitor1;
153 Visitor* mVisitor2;
154 bool mContinue1;
155 bool 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),
166 mChecked(false)
168 mParserCreating = aFromParser;
171 HTMLMenuItemElement::~HTMLMenuItemElement()
176 //NS_IMPL_ELEMENT_CLONE(HTMLMenuItemElement)
178 nsresult
179 HTMLMenuItemElement::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const
181 *aResult = nullptr;
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)) {
186 switch (mType) {
187 case CMD_TYPE_CHECKBOX:
188 case CMD_TYPE_RADIO:
189 if (mCheckedDirty) {
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;
195 break;
198 it.forget(aResult);
201 return rv;
204 void
205 HTMLMenuItemElement::GetType(DOMString& aValue)
207 GetEnumAttr(nsGkAtoms::type, kMenuItemDefaultType->tag, aValue);
210 void
211 HTMLMenuItemElement::SetChecked(bool aChecked)
213 bool checkedChanged = mChecked != aChecked;
215 mChecked = aChecked;
217 if (mType == CMD_TYPE_RADIO) {
218 if (checkedChanged) {
219 if (mCheckedDirty) {
220 ClearCheckedVisitor visitor(this);
221 WalkRadioGroup(&visitor);
222 } else {
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);
232 } else {
233 mCheckedDirty = true;
237 void
238 HTMLMenuItemElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
240 if (aVisitor.mEvent->mMessage == eMouseClick) {
242 bool originalCheckedValue = false;
243 switch (mType) {
244 case CMD_TYPE_CHECKBOX:
245 originalCheckedValue = mChecked;
246 SetChecked(!originalCheckedValue);
247 aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
248 break;
249 case CMD_TYPE_RADIO:
250 // casting back to Element* here to resolve nsISupports ambiguity.
251 Element* supports = GetSelectedRadio();
252 aVisitor.mItemData = supports;
254 originalCheckedValue = mChecked;
255 if (!originalCheckedValue) {
256 SetChecked(true);
257 aVisitor.mItemFlags |= NS_CHECKED_IS_TOGGLED;
259 break;
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);
273 nsresult
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);
286 if (selectedRadio) {
287 selectedRadio->SetChecked(true);
288 if (mType != CMD_TYPE_RADIO) {
289 SetChecked(false);
291 } else if (oldType == CMD_TYPE_CHECKBOX) {
292 SetChecked(originalCheckedValue);
296 return NS_OK;
299 nsresult
300 HTMLMenuItemElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
301 nsIContent* aBindingParent)
303 nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
304 aBindingParent);
306 if (NS_SUCCEEDED(rv) && aDocument && mType == CMD_TYPE_RADIO) {
307 AddedToRadioGroup();
310 return rv;
313 bool
314 HTMLMenuItemElement::ParseAttribute(int32_t aNamespaceID,
315 nsAtom* aAttribute,
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);
328 return true;
332 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
333 aMaybeScriptedPrincipal, aResult);
336 void
337 HTMLMenuItemElement::DoneCreatingElement()
339 mParserCreating = false;
341 if (mShouldInitChecked) {
342 InitChecked();
343 mShouldInitChecked = false;
347 void
348 HTMLMenuItemElement::GetText(nsAString& aText)
350 nsAutoString text;
351 nsContentUtils::GetNodeTextContent(this, false, text);
353 text.CompressWhitespace(true, true);
354 aText = text;
357 nsresult
358 HTMLMenuItemElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
359 const nsAttrValue* aValue,
360 const nsAttrValue* aOldValue,
361 nsIPrincipal* aSubjectPrincipal,
362 bool aNotify)
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) {
368 if (aValue) {
369 mType = aValue->GetEnumValue();
370 } else {
371 mType = kMenuItemDefaultType->value;
375 if ((aName == nsGkAtoms::radiogroup || aName == nsGkAtoms::type) &&
376 mType == CMD_TYPE_RADIO &&
377 !mParserCreating) {
378 if (IsInUncomposedDoc() && GetParent()) {
379 AddedToRadioGroup();
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 &&
386 !mCheckedDirty) {
387 if (mParserCreating) {
388 mShouldInitChecked = true;
389 } else {
390 InitChecked();
395 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
396 aOldValue, aSubjectPrincipal, aNotify);
399 void
400 HTMLMenuItemElement::WalkRadioGroup(Visitor* aVisitor)
402 nsIContent* parent = GetParent();
403 if (!parent) {
404 aVisitor->Visit(this);
405 return;
408 BorrowedAttrInfo info1(GetAttrInfo(kNameSpaceID_None,
409 nsGkAtoms::radiogroup));
410 bool info1Empty = !info1.mValue || info1.mValue->IsEmptyString();
412 for (nsIContent* cur = parent->GetFirstChild();
413 cur;
414 cur = cur->GetNextSibling()) {
415 HTMLMenuItemElement* menuitem = HTMLMenuItemElement::FromNode(cur);
417 if (!menuitem || menuitem->GetType() != CMD_TYPE_RADIO) {
418 continue;
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))) {
427 continue;
430 if (!aVisitor->Visit(menuitem)) {
431 break;
436 HTMLMenuItemElement*
437 HTMLMenuItemElement::GetSelectedRadio()
439 HTMLMenuItemElement* result = nullptr;
441 GetCheckedVisitor visitor(&result);
442 WalkRadioGroup(&visitor);
444 return result;
447 void
448 HTMLMenuItemElement::AddedToRadioGroup()
450 bool checkedDirty = mCheckedDirty;
451 if (mChecked) {
452 ClearCheckedVisitor visitor1(this);
453 GetCheckedDirtyVisitor visitor2(&checkedDirty, this);
454 CombinedVisitor visitor(&visitor1, &visitor2);
455 WalkRadioGroup(&visitor);
456 } else {
457 GetCheckedDirtyVisitor visitor(&checkedDirty, this);
458 WalkRadioGroup(&visitor);
460 mCheckedDirty = checkedDirty;
463 void
464 HTMLMenuItemElement::InitChecked()
466 bool defaultChecked = DefaultChecked();
467 mChecked = defaultChecked;
468 if (mType == CMD_TYPE_RADIO) {
469 ClearCheckedVisitor visitor(this);
470 WalkRadioGroup(&visitor);
474 JSObject*
475 HTMLMenuItemElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
477 return HTMLMenuItemElement_Binding::Wrap(aCx, this, aGivenProto);
480 } // namespace dom
481 } // namespace mozilla
483 #undef NS_ORIGINAL_CHECKED_VALUE