Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/html/HTMLOptionElement.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
#include "mozilla/dom/HTMLOptionElement.h"
8
#include "mozilla/dom/HTMLOptionElementBinding.h"
9
#include "mozilla/dom/HTMLSelectElement.h"
10
#include "nsGkAtoms.h"
11
#include "nsStyleConsts.h"
12
#include "nsIFormControl.h"
13
#include "nsIForm.h"
14
#include "nsISelectControlFrame.h"
15
16
// Notify/query select frame for selected state
17
#include "nsIFormControlFrame.h"
18
#include "nsIDocument.h"
19
#include "nsNodeInfoManager.h"
20
#include "nsCOMPtr.h"
21
#include "mozilla/EventStates.h"
22
#include "nsContentCreatorFunctions.h"
23
#include "mozAutoDocUpdate.h"
24
#include "nsTextNode.h"
25
26
/**
27
 * Implementation of <option>
28
 */
29
30
NS_IMPL_NS_NEW_HTML_ELEMENT(Option)
31
32
namespace mozilla {
33
namespace dom {
34
35
HTMLOptionElement::HTMLOptionElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
36
  : nsGenericHTMLElement(std::move(aNodeInfo)),
37
    mSelectedChanged(false),
38
    mIsSelected(false),
39
    mIsInSetDefaultSelected(false)
40
0
{
41
0
  // We start off enabled
42
0
  AddStatesSilently(NS_EVENT_STATE_ENABLED);
43
0
}
44
45
HTMLOptionElement::~HTMLOptionElement()
46
0
{
47
0
}
48
49
NS_IMPL_ELEMENT_CLONE(HTMLOptionElement)
50
51
mozilla::dom::HTMLFormElement*
52
HTMLOptionElement::GetForm()
53
0
{
54
0
  HTMLSelectElement* selectControl = GetSelect();
55
0
  return selectControl ? selectControl->GetForm() : nullptr;
56
0
}
57
58
void
59
HTMLOptionElement::SetSelectedInternal(bool aValue, bool aNotify)
60
0
{
61
0
  mSelectedChanged = true;
62
0
  mIsSelected = aValue;
63
0
64
0
  // When mIsInSetDefaultSelected is true, the state change will be handled by
65
0
  // SetAttr/UnsetAttr.
66
0
  if (!mIsInSetDefaultSelected) {
67
0
    UpdateState(aNotify);
68
0
  }
69
0
}
70
71
void
72
HTMLOptionElement::OptGroupDisabledChanged(bool aNotify)
73
0
{
74
0
  UpdateDisabledState(aNotify);
75
0
}
76
77
void
78
HTMLOptionElement::UpdateDisabledState(bool aNotify)
79
0
{
80
0
  bool isDisabled = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
81
0
82
0
  if (!isDisabled) {
83
0
    nsIContent* parent = GetParent();
84
0
    if (auto optGroupElement = HTMLOptGroupElement::FromNodeOrNull(parent)) {
85
0
      isDisabled = optGroupElement->IsDisabled();
86
0
    }
87
0
  }
88
0
89
0
  EventStates disabledStates;
90
0
  if (isDisabled) {
91
0
    disabledStates |= NS_EVENT_STATE_DISABLED;
92
0
  } else {
93
0
    disabledStates |= NS_EVENT_STATE_ENABLED;
94
0
  }
95
0
96
0
  EventStates oldDisabledStates = State() & DISABLED_STATES;
97
0
  EventStates changedStates = disabledStates ^ oldDisabledStates;
98
0
99
0
  if (!changedStates.IsEmpty()) {
100
0
    ToggleStates(changedStates, aNotify);
101
0
  }
102
0
}
103
104
void
105
HTMLOptionElement::SetSelected(bool aValue)
106
0
{
107
0
  // Note: The select content obj maintains all the PresState
108
0
  // so defer to it to get the answer
109
0
  HTMLSelectElement* selectInt = GetSelect();
110
0
  if (selectInt) {
111
0
    int32_t index = Index();
112
0
    uint32_t mask = HTMLSelectElement::SET_DISABLED | HTMLSelectElement::NOTIFY;
113
0
    if (aValue) {
114
0
      mask |= HTMLSelectElement::IS_SELECTED;
115
0
    }
116
0
117
0
    // This should end up calling SetSelectedInternal
118
0
    selectInt->SetOptionsSelectedByIndex(index, index, mask);
119
0
  } else {
120
0
    SetSelectedInternal(aValue, true);
121
0
  }
122
0
}
123
124
int32_t
125
HTMLOptionElement::Index()
126
0
{
127
0
  static int32_t defaultIndex = 0;
128
0
129
0
  // Only select elements can contain a list of options.
130
0
  HTMLSelectElement* selectElement = GetSelect();
131
0
  if (!selectElement) {
132
0
    return defaultIndex;
133
0
  }
134
0
135
0
  HTMLOptionsCollection* options = selectElement->GetOptions();
136
0
  if (!options) {
137
0
    return defaultIndex;
138
0
  }
139
0
140
0
  int32_t index = defaultIndex;
141
0
  MOZ_ALWAYS_SUCCEEDS(options->GetOptionIndex(this, 0, true, &index));
142
0
  return index;
143
0
}
144
145
nsChangeHint
146
HTMLOptionElement::GetAttributeChangeHint(const nsAtom* aAttribute,
147
                                          int32_t aModType) const
148
0
{
149
0
  nsChangeHint retval =
150
0
      nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
151
0
152
0
  if (aAttribute == nsGkAtoms::label ||
153
0
      aAttribute == nsGkAtoms::text) {
154
0
    retval |= NS_STYLE_HINT_REFLOW;
155
0
  }
156
0
  return retval;
157
0
}
158
159
nsresult
160
HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
161
                                 const nsAttrValueOrString* aValue,
162
                                 bool aNotify)
163
0
{
164
0
  nsresult rv = nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName,
165
0
                                                    aValue, aNotify);
166
0
  NS_ENSURE_SUCCESS(rv, rv);
167
0
168
0
  if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected ||
169
0
      mSelectedChanged) {
170
0
    return NS_OK;
171
0
  }
172
0
173
0
  // We just changed out selected state (since we look at the "selected"
174
0
  // attribute when mSelectedChanged is false).  Let's tell our select about
175
0
  // it.
176
0
  HTMLSelectElement* selectInt = GetSelect();
177
0
  if (!selectInt) {
178
0
    // If option is a child of select, SetOptionsSelectedByIndex will set
179
0
    // mIsSelected if needed.
180
0
    mIsSelected = aValue;
181
0
    return NS_OK;
182
0
  }
183
0
184
0
  NS_ASSERTION(!mSelectedChanged, "Shouldn't be here");
185
0
186
0
  bool inSetDefaultSelected = mIsInSetDefaultSelected;
187
0
  mIsInSetDefaultSelected = true;
188
0
189
0
  int32_t index = Index();
190
0
  uint32_t mask = HTMLSelectElement::SET_DISABLED;
191
0
  if (aValue) {
192
0
    mask |= HTMLSelectElement::IS_SELECTED;
193
0
  }
194
0
195
0
  if (aNotify) {
196
0
    mask |= HTMLSelectElement::NOTIFY;
197
0
  }
198
0
199
0
  // This can end up calling SetSelectedInternal if our selected state needs to
200
0
  // change, which we will allow to take effect so that parts of
201
0
  // SetOptionsSelectedByIndex that might depend on it working don't get
202
0
  // confused.
203
0
  selectInt->SetOptionsSelectedByIndex(index, index, mask);
204
0
205
0
  // Now reset our members; when we finish the attr set we'll end up with the
206
0
  // rigt selected state.
207
0
  mIsInSetDefaultSelected = inSetDefaultSelected;
208
0
  // mIsSelected might have been changed by SetOptionsSelectedByIndex.  Possibly
209
0
  // more than once; make sure our mSelectedChanged state is set back correctly.
210
0
  mSelectedChanged = false;
211
0
212
0
  return NS_OK;
213
0
}
214
215
nsresult
216
HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
217
                                const nsAttrValue* aValue,
218
                                const nsAttrValue* aOldValue,
219
                                nsIPrincipal* aSubjectPrincipal,
220
                                bool aNotify)
221
0
{
222
0
  if (aNameSpaceID == kNameSpaceID_None) {
223
0
    if (aName == nsGkAtoms::disabled) {
224
0
      UpdateDisabledState(aNotify);
225
0
    }
226
0
227
0
    if (aName == nsGkAtoms::value && Selected()) {
228
0
      // Since this option is selected, changing value
229
0
      // may have changed missing validity state of the
230
0
      // Select element
231
0
      HTMLSelectElement* select = GetSelect();
232
0
      if (select) {
233
0
        select->UpdateValueMissingValidityState();
234
0
      }
235
0
    }
236
0
  }
237
0
238
0
  return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
239
0
                                            aValue, aOldValue, aSubjectPrincipal, aNotify);
240
0
}
241
242
void
243
HTMLOptionElement::GetText(nsAString& aText)
244
0
{
245
0
  nsAutoString text;
246
0
247
0
  nsIContent* child = nsINode::GetFirstChild();
248
0
  while (child) {
249
0
    if (Text* textChild = child->GetAsText()) {
250
0
      textChild->AppendTextTo(text);
251
0
    }
252
0
    if (child->IsHTMLElement(nsGkAtoms::script) ||
253
0
        child->IsSVGElement(nsGkAtoms::script)) {
254
0
      child = child->GetNextNonChildNode(this);
255
0
    } else {
256
0
      child = child->GetNextNode(this);
257
0
    }
258
0
  }
259
0
260
0
  // XXX No CompressWhitespace for nsAString.  Sad.
261
0
  text.CompressWhitespace(true, true);
262
0
  aText = text;
263
0
}
264
265
void
266
HTMLOptionElement::SetText(const nsAString& aText, ErrorResult& aRv)
267
0
{
268
0
  aRv = nsContentUtils::SetNodeTextContent(this, aText, true);
269
0
}
270
271
nsresult
272
HTMLOptionElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
273
                              nsIContent* aBindingParent)
274
0
{
275
0
  nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
276
0
                                                 aBindingParent);
277
0
  NS_ENSURE_SUCCESS(rv, rv);
278
0
279
0
  // Our new parent might change :disabled/:enabled state.
280
0
  UpdateDisabledState(false);
281
0
282
0
  return NS_OK;
283
0
}
284
285
void
286
HTMLOptionElement::UnbindFromTree(bool aDeep, bool aNullParent)
287
0
{
288
0
  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
289
0
290
0
  // Our previous parent could have been involved in :disabled/:enabled state.
291
0
  UpdateDisabledState(false);
292
0
}
293
294
EventStates
295
HTMLOptionElement::IntrinsicState() const
296
0
{
297
0
  EventStates state = nsGenericHTMLElement::IntrinsicState();
298
0
  if (Selected()) {
299
0
    state |= NS_EVENT_STATE_CHECKED;
300
0
  }
301
0
  if (DefaultSelected()) {
302
0
    state |= NS_EVENT_STATE_DEFAULT;
303
0
  }
304
0
305
0
  return state;
306
0
}
307
308
// Get the select content element that contains this option
309
HTMLSelectElement*
310
HTMLOptionElement::GetSelect()
311
0
{
312
0
  nsIContent* parent = GetParent();
313
0
  if (!parent) {
314
0
    return nullptr;
315
0
  }
316
0
317
0
  HTMLSelectElement* select = HTMLSelectElement::FromNode(parent);
318
0
  if (select) {
319
0
    return select;
320
0
  }
321
0
322
0
  if (!parent->IsHTMLElement(nsGkAtoms::optgroup)) {
323
0
    return nullptr;
324
0
  }
325
0
326
0
  return HTMLSelectElement::FromNodeOrNull(parent->GetParent());
327
0
}
328
329
already_AddRefed<HTMLOptionElement>
330
HTMLOptionElement::Option(const GlobalObject& aGlobal,
331
                          const nsAString& aText,
332
                          const Optional<nsAString>& aValue,
333
                          bool aDefaultSelected,
334
                          bool aSelected,
335
                          ErrorResult& aError)
336
0
{
337
0
  nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
338
0
  nsIDocument* doc;
339
0
  if (!win || !(doc = win->GetExtantDoc())) {
340
0
    aError.Throw(NS_ERROR_FAILURE);
341
0
    return nullptr;
342
0
  }
343
0
344
0
  RefPtr<mozilla::dom::NodeInfo> nodeInfo =
345
0
    doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::option, nullptr,
346
0
                                        kNameSpaceID_XHTML,
347
0
                                        ELEMENT_NODE);
348
0
349
0
  RefPtr<HTMLOptionElement> option = new HTMLOptionElement(nodeInfo.forget());
350
0
351
0
  if (!aText.IsEmpty()) {
352
0
    // Create a new text node and append it to the option
353
0
    RefPtr<nsTextNode> textContent =
354
0
      new nsTextNode(option->NodeInfo()->NodeInfoManager());
355
0
356
0
    textContent->SetText(aText, false);
357
0
358
0
    aError = option->AppendChildTo(textContent, false);
359
0
    if (aError.Failed()) {
360
0
      return nullptr;
361
0
    }
362
0
  }
363
0
364
0
  if (aValue.WasPassed()) {
365
0
    // Set the value attribute for this element. We're calling SetAttr
366
0
    // directly because we want to pass aNotify == false.
367
0
    aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
368
0
                             aValue.Value(), false);
369
0
    if (aError.Failed()) {
370
0
      return nullptr;
371
0
    }
372
0
  }
373
0
374
0
  if (aDefaultSelected) {
375
0
    // We're calling SetAttr directly because we want to pass
376
0
    // aNotify == false.
377
0
    aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::selected,
378
0
                             EmptyString(), false);
379
0
    if (aError.Failed()) {
380
0
      return nullptr;
381
0
    }
382
0
  }
383
0
384
0
  option->SetSelected(aSelected);
385
0
  option->SetSelectedChanged(false);
386
0
387
0
  return option.forget();
388
0
}
389
390
nsresult
391
HTMLOptionElement::CopyInnerTo(Element* aDest)
392
0
{
393
0
  nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
394
0
  NS_ENSURE_SUCCESS(rv, rv);
395
0
396
0
  if (aDest->OwnerDoc()->IsStaticDocument()) {
397
0
    static_cast<HTMLOptionElement*>(aDest)->SetSelected(Selected());
398
0
  }
399
0
  return NS_OK;
400
0
}
401
402
JSObject*
403
HTMLOptionElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
404
0
{
405
0
  return HTMLOptionElement_Binding::Wrap(aCx, this, aGivenProto);
406
0
}
407
408
} // namespace dom
409
} // namespace mozilla