Line data Source code
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/HTMLFormControlsCollection.h"
8 :
9 : #include "mozilla/FlushType.h"
10 : #include "mozilla/dom/BindingUtils.h"
11 : #include "mozilla/dom/Element.h"
12 : #include "mozilla/dom/HTMLFormControlsCollectionBinding.h"
13 : #include "mozilla/dom/HTMLFormElement.h"
14 : #include "nsGenericHTMLElement.h" // nsGenericHTMLFormElement
15 : #include "nsIDocument.h"
16 : #include "nsIDOMNode.h"
17 : #include "nsIDOMNodeList.h"
18 : #include "nsIFormControl.h"
19 : #include "RadioNodeList.h"
20 : #include "jsfriendapi.h"
21 :
22 : namespace mozilla {
23 : namespace dom {
24 :
25 : /* static */ bool
26 0 : HTMLFormControlsCollection::ShouldBeInElements(nsIFormControl* aFormControl)
27 : {
28 : // For backwards compatibility (with 4.x and IE) we must not add
29 : // <input type=image> elements to the list of form controls in a
30 : // form.
31 :
32 0 : switch (aFormControl->ControlType()) {
33 : case NS_FORM_BUTTON_BUTTON :
34 : case NS_FORM_BUTTON_RESET :
35 : case NS_FORM_BUTTON_SUBMIT :
36 : case NS_FORM_INPUT_BUTTON :
37 : case NS_FORM_INPUT_CHECKBOX :
38 : case NS_FORM_INPUT_COLOR :
39 : case NS_FORM_INPUT_EMAIL :
40 : case NS_FORM_INPUT_FILE :
41 : case NS_FORM_INPUT_HIDDEN :
42 : case NS_FORM_INPUT_RESET :
43 : case NS_FORM_INPUT_PASSWORD :
44 : case NS_FORM_INPUT_RADIO :
45 : case NS_FORM_INPUT_SEARCH :
46 : case NS_FORM_INPUT_SUBMIT :
47 : case NS_FORM_INPUT_TEXT :
48 : case NS_FORM_INPUT_TEL :
49 : case NS_FORM_INPUT_URL :
50 : case NS_FORM_INPUT_NUMBER :
51 : case NS_FORM_INPUT_RANGE :
52 : case NS_FORM_INPUT_DATE :
53 : case NS_FORM_INPUT_TIME :
54 : case NS_FORM_INPUT_MONTH :
55 : case NS_FORM_INPUT_WEEK :
56 : case NS_FORM_INPUT_DATETIME_LOCAL :
57 : case NS_FORM_SELECT :
58 : case NS_FORM_TEXTAREA :
59 : case NS_FORM_FIELDSET :
60 : case NS_FORM_OBJECT :
61 : case NS_FORM_OUTPUT :
62 0 : return true;
63 : }
64 :
65 : // These form control types are not supposed to end up in the
66 : // form.elements array
67 : //
68 : // NS_FORM_INPUT_IMAGE
69 : //
70 : // XXXbz maybe we should just check for that type here instead of the big
71 : // switch?
72 :
73 0 : return false;
74 : }
75 :
76 0 : HTMLFormControlsCollection::HTMLFormControlsCollection(HTMLFormElement* aForm)
77 : : mForm(aForm)
78 : // Initialize the elements list to have an initial capacity
79 : // of 8 to reduce allocations on small forms.
80 : , mElements(8)
81 0 : , mNameLookupTable(HTMLFormElement::FORM_CONTROL_LIST_HASHTABLE_LENGTH)
82 : {
83 0 : }
84 :
85 0 : HTMLFormControlsCollection::~HTMLFormControlsCollection()
86 : {
87 0 : mForm = nullptr;
88 0 : Clear();
89 0 : }
90 :
91 : void
92 0 : HTMLFormControlsCollection::DropFormReference()
93 : {
94 0 : mForm = nullptr;
95 0 : Clear();
96 0 : }
97 :
98 : void
99 0 : HTMLFormControlsCollection::Clear()
100 : {
101 : // Null out childrens' pointer to me. No refcounting here
102 0 : for (int32_t i = mElements.Length() - 1; i >= 0; i--) {
103 0 : mElements[i]->ClearForm(false, false);
104 : }
105 0 : mElements.Clear();
106 :
107 0 : for (int32_t i = mNotInElements.Length() - 1; i >= 0; i--) {
108 0 : mNotInElements[i]->ClearForm(false, false);
109 : }
110 0 : mNotInElements.Clear();
111 :
112 0 : mNameLookupTable.Clear();
113 0 : }
114 :
115 : void
116 0 : HTMLFormControlsCollection::FlushPendingNotifications()
117 : {
118 0 : if (mForm) {
119 0 : nsIDocument* doc = mForm->GetUncomposedDoc();
120 0 : if (doc) {
121 0 : doc->FlushPendingNotifications(FlushType::Content);
122 : }
123 : }
124 0 : }
125 :
126 : NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormControlsCollection)
127 :
128 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLFormControlsCollection)
129 : // Note: We intentionally don't set tmp->mForm to nullptr here, since doing
130 : // so may result in crashes because of inconsistent null-checking after the
131 : // object gets unlinked.
132 0 : tmp->Clear();
133 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
134 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
135 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(HTMLFormControlsCollection)
136 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNameLookupTable)
137 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
138 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HTMLFormControlsCollection)
139 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
140 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
141 :
142 : // XPConnect interface list for HTMLFormControlsCollection
143 0 : NS_INTERFACE_TABLE_HEAD(HTMLFormControlsCollection)
144 0 : NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
145 0 : NS_INTERFACE_TABLE(HTMLFormControlsCollection,
146 : nsIHTMLCollection,
147 : nsIDOMHTMLCollection)
148 0 : NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(HTMLFormControlsCollection)
149 0 : NS_INTERFACE_MAP_END
150 :
151 :
152 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLFormControlsCollection)
153 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLFormControlsCollection)
154 :
155 :
156 : // nsIDOMHTMLCollection interface
157 :
158 : NS_IMETHODIMP
159 0 : HTMLFormControlsCollection::GetLength(uint32_t* aLength)
160 : {
161 0 : FlushPendingNotifications();
162 0 : *aLength = mElements.Length();
163 0 : return NS_OK;
164 : }
165 :
166 : NS_IMETHODIMP
167 0 : HTMLFormControlsCollection::Item(uint32_t aIndex, nsIDOMNode** aReturn)
168 : {
169 0 : nsISupports* item = GetElementAt(aIndex);
170 0 : if (!item) {
171 0 : *aReturn = nullptr;
172 :
173 0 : return NS_OK;
174 : }
175 :
176 0 : return CallQueryInterface(item, aReturn);
177 : }
178 :
179 : NS_IMETHODIMP
180 0 : HTMLFormControlsCollection::NamedItem(const nsAString& aName,
181 : nsIDOMNode** aReturn)
182 : {
183 0 : FlushPendingNotifications();
184 :
185 0 : *aReturn = nullptr;
186 :
187 0 : nsCOMPtr<nsISupports> supports;
188 :
189 0 : if (!mNameLookupTable.Get(aName, getter_AddRefs(supports))) {
190 : // key not found
191 0 : return NS_OK;
192 : }
193 :
194 0 : if (!supports) {
195 0 : return NS_OK;
196 : }
197 :
198 : // We found something, check if it's a node
199 0 : CallQueryInterface(supports, aReturn);
200 0 : if (*aReturn) {
201 0 : return NS_OK;
202 : }
203 :
204 : // If not, we check if it's a node list.
205 0 : nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports);
206 0 : NS_ASSERTION(nodeList, "Huh, what's going one here?");
207 0 : if (!nodeList) {
208 0 : return NS_OK;
209 : }
210 :
211 : // And since we're only asking for one node here, we return the first
212 : // one from the list.
213 0 : return nodeList->Item(0, aReturn);
214 : }
215 :
216 : nsISupports*
217 0 : HTMLFormControlsCollection::NamedItemInternal(const nsAString& aName,
218 : bool aFlushContent)
219 : {
220 0 : if (aFlushContent) {
221 0 : FlushPendingNotifications();
222 : }
223 :
224 0 : return mNameLookupTable.GetWeak(aName);
225 : }
226 :
227 : nsresult
228 0 : HTMLFormControlsCollection::AddElementToTable(nsGenericHTMLFormElement* aChild,
229 : const nsAString& aName)
230 : {
231 0 : if (!ShouldBeInElements(aChild)) {
232 0 : return NS_OK;
233 : }
234 :
235 0 : return mForm->AddElementToTableInternal(mNameLookupTable, aChild, aName);
236 : }
237 :
238 : nsresult
239 0 : HTMLFormControlsCollection::IndexOfControl(nsIFormControl* aControl,
240 : int32_t* aIndex)
241 : {
242 : // Note -- not a DOM method; callers should handle flushing themselves
243 :
244 0 : NS_ENSURE_ARG_POINTER(aIndex);
245 :
246 0 : *aIndex = mElements.IndexOf(aControl);
247 :
248 0 : return NS_OK;
249 : }
250 :
251 : nsresult
252 0 : HTMLFormControlsCollection::RemoveElementFromTable(
253 : nsGenericHTMLFormElement* aChild, const nsAString& aName)
254 : {
255 0 : if (!ShouldBeInElements(aChild)) {
256 0 : return NS_OK;
257 : }
258 :
259 0 : return mForm->RemoveElementFromTableInternal(mNameLookupTable, aChild, aName);
260 : }
261 :
262 : nsresult
263 0 : HTMLFormControlsCollection::GetSortedControls(
264 : nsTArray<nsGenericHTMLFormElement*>& aControls) const
265 : {
266 : #ifdef DEBUG
267 0 : HTMLFormElement::AssertDocumentOrder(mElements, mForm);
268 0 : HTMLFormElement::AssertDocumentOrder(mNotInElements, mForm);
269 : #endif
270 :
271 0 : aControls.Clear();
272 :
273 : // Merge the elements list and the not in elements list. Both lists are
274 : // already sorted.
275 0 : uint32_t elementsLen = mElements.Length();
276 0 : uint32_t notInElementsLen = mNotInElements.Length();
277 0 : aControls.SetCapacity(elementsLen + notInElementsLen);
278 :
279 0 : uint32_t elementsIdx = 0;
280 0 : uint32_t notInElementsIdx = 0;
281 :
282 0 : while (elementsIdx < elementsLen || notInElementsIdx < notInElementsLen) {
283 : // Check whether we're done with mElements
284 0 : if (elementsIdx == elementsLen) {
285 0 : NS_ASSERTION(notInElementsIdx < notInElementsLen,
286 : "Should have remaining not-in-elements");
287 : // Append the remaining mNotInElements elements
288 0 : if (!aControls.AppendElements(mNotInElements.Elements() +
289 0 : notInElementsIdx,
290 0 : notInElementsLen -
291 : notInElementsIdx)) {
292 0 : return NS_ERROR_OUT_OF_MEMORY;
293 : }
294 0 : break;
295 : }
296 : // Check whether we're done with mNotInElements
297 0 : if (notInElementsIdx == notInElementsLen) {
298 0 : NS_ASSERTION(elementsIdx < elementsLen,
299 : "Should have remaining in-elements");
300 : // Append the remaining mElements elements
301 0 : if (!aControls.AppendElements(mElements.Elements() +
302 0 : elementsIdx,
303 0 : elementsLen -
304 : elementsIdx)) {
305 0 : return NS_ERROR_OUT_OF_MEMORY;
306 : }
307 0 : break;
308 : }
309 : // Both lists have elements left.
310 0 : NS_ASSERTION(mElements[elementsIdx] &&
311 : mNotInElements[notInElementsIdx],
312 : "Should have remaining elements");
313 : // Determine which of the two elements should be ordered
314 : // first and add it to the end of the list.
315 : nsGenericHTMLFormElement* elementToAdd;
316 0 : if (HTMLFormElement::CompareFormControlPosition(
317 0 : mElements[elementsIdx], mNotInElements[notInElementsIdx], mForm) < 0) {
318 0 : elementToAdd = mElements[elementsIdx];
319 0 : ++elementsIdx;
320 : } else {
321 0 : elementToAdd = mNotInElements[notInElementsIdx];
322 0 : ++notInElementsIdx;
323 : }
324 : // Add the first element to the list.
325 0 : if (!aControls.AppendElement(elementToAdd)) {
326 0 : return NS_ERROR_OUT_OF_MEMORY;
327 : }
328 : }
329 :
330 0 : NS_ASSERTION(aControls.Length() == elementsLen + notInElementsLen,
331 : "Not all form controls were added to the sorted list");
332 : #ifdef DEBUG
333 0 : HTMLFormElement::AssertDocumentOrder(aControls, mForm);
334 : #endif
335 :
336 0 : return NS_OK;
337 : }
338 :
339 : Element*
340 0 : HTMLFormControlsCollection::GetElementAt(uint32_t aIndex)
341 : {
342 0 : FlushPendingNotifications();
343 :
344 0 : return mElements.SafeElementAt(aIndex, nullptr);
345 : }
346 :
347 : /* virtual */ nsINode*
348 0 : HTMLFormControlsCollection::GetParentObject()
349 : {
350 0 : return mForm;
351 : }
352 :
353 : /* virtual */ Element*
354 0 : HTMLFormControlsCollection::GetFirstNamedElement(const nsAString& aName, bool& aFound)
355 : {
356 0 : Nullable<OwningRadioNodeListOrElement> maybeResult;
357 0 : NamedGetter(aName, aFound, maybeResult);
358 0 : if (!aFound) {
359 0 : return nullptr;
360 : }
361 0 : MOZ_ASSERT(!maybeResult.IsNull());
362 0 : const OwningRadioNodeListOrElement& result = maybeResult.Value();
363 0 : if (result.IsElement()) {
364 0 : return result.GetAsElement().get();
365 : }
366 0 : if (result.IsRadioNodeList()) {
367 0 : RadioNodeList& nodelist = result.GetAsRadioNodeList();
368 0 : return nodelist.Item(0)->AsElement();
369 : }
370 0 : MOZ_ASSERT_UNREACHABLE("Should only have Elements and NodeLists here.");
371 : return nullptr;
372 : }
373 :
374 : void
375 0 : HTMLFormControlsCollection::NamedGetter(const nsAString& aName,
376 : bool& aFound,
377 : Nullable<OwningRadioNodeListOrElement>& aResult)
378 : {
379 0 : nsISupports* item = NamedItemInternal(aName, true);
380 0 : if (!item) {
381 0 : aFound = false;
382 0 : return;
383 : }
384 0 : aFound = true;
385 0 : if (nsCOMPtr<Element> element = do_QueryInterface(item)) {
386 0 : aResult.SetValue().SetAsElement() = element;
387 0 : return;
388 : }
389 0 : if (nsCOMPtr<RadioNodeList> nodelist = do_QueryInterface(item)) {
390 0 : aResult.SetValue().SetAsRadioNodeList() = nodelist;
391 0 : return;
392 : }
393 0 : MOZ_ASSERT_UNREACHABLE("Should only have Elements and NodeLists here.");
394 : }
395 :
396 : void
397 0 : HTMLFormControlsCollection::GetSupportedNames(nsTArray<nsString>& aNames)
398 : {
399 0 : FlushPendingNotifications();
400 : // Just enumerate mNameLookupTable. This won't guarantee order, but
401 : // that's OK, because the HTML5 spec doesn't define an order for
402 : // this enumeration.
403 0 : for (auto iter = mNameLookupTable.Iter(); !iter.Done(); iter.Next()) {
404 0 : aNames.AppendElement(iter.Key());
405 : }
406 0 : }
407 :
408 : /* virtual */ JSObject*
409 0 : HTMLFormControlsCollection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
410 : {
411 0 : return HTMLFormControlsCollectionBinding::Wrap(aCx, this, aGivenProto);
412 : }
413 :
414 : } // namespace dom
415 : } // namespace mozilla
|