/src/mozilla-central/dom/base/DocumentOrShadowRoot.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 "DocumentOrShadowRoot.h" |
8 | | #include "mozilla/EventStateManager.h" |
9 | | #include "mozilla/dom/HTMLInputElement.h" |
10 | | #include "mozilla/dom/ShadowRoot.h" |
11 | | #include "mozilla/dom/StyleSheetList.h" |
12 | | #include "nsDocument.h" |
13 | | #include "nsFocusManager.h" |
14 | | #include "nsIRadioVisitor.h" |
15 | | #include "nsIFormControl.h" |
16 | | #include "nsLayoutUtils.h" |
17 | | #include "nsSVGUtils.h" |
18 | | #include "nsWindowSizes.h" |
19 | | |
20 | | namespace mozilla { |
21 | | namespace dom { |
22 | | |
23 | | DocumentOrShadowRoot::DocumentOrShadowRoot(mozilla::dom::ShadowRoot& aShadowRoot) |
24 | | : mAsNode(aShadowRoot) |
25 | | , mKind(Kind::ShadowRoot) |
26 | 0 | {} |
27 | | |
28 | | DocumentOrShadowRoot::DocumentOrShadowRoot(nsIDocument& aDoc) |
29 | | : mAsNode(aDoc) |
30 | | , mKind(Kind::Document) |
31 | 0 | {} |
32 | | |
33 | | void |
34 | | DocumentOrShadowRoot::AddSizeOfOwnedSheetArrayExcludingThis( |
35 | | nsWindowSizes& aSizes, |
36 | | const nsTArray<RefPtr<StyleSheet>>& aSheets) const |
37 | 0 | { |
38 | 0 | size_t n = 0; |
39 | 0 | n += aSheets.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); |
40 | 0 | for (StyleSheet* sheet : aSheets) { |
41 | 0 | if (!sheet->GetAssociatedDocumentOrShadowRoot()) { |
42 | 0 | // Avoid over-reporting shared sheets. |
43 | 0 | continue; |
44 | 0 | } |
45 | 0 | n += sheet->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); |
46 | 0 | } |
47 | 0 |
|
48 | 0 | if (mKind == Kind::ShadowRoot) { |
49 | 0 | aSizes.mLayoutShadowDomStyleSheetsSize += n; |
50 | 0 | } else { |
51 | 0 | aSizes.mLayoutStyleSheetsSize += n; |
52 | 0 | } |
53 | 0 | } |
54 | | |
55 | | void |
56 | | DocumentOrShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const |
57 | 0 | { |
58 | 0 | AddSizeOfOwnedSheetArrayExcludingThis(aSizes, mStyleSheets); |
59 | 0 | aSizes.mDOMOtherSize += |
60 | 0 | mIdentifierMap.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf); |
61 | 0 | } |
62 | | |
63 | | DocumentOrShadowRoot::~DocumentOrShadowRoot() |
64 | 0 | { |
65 | 0 | for (StyleSheet* sheet : mStyleSheets) { |
66 | 0 | sheet->ClearAssociatedDocumentOrShadowRoot(); |
67 | 0 | } |
68 | 0 | } |
69 | | |
70 | | StyleSheetList& |
71 | | DocumentOrShadowRoot::EnsureDOMStyleSheets() |
72 | 0 | { |
73 | 0 | if (!mDOMStyleSheets) { |
74 | 0 | mDOMStyleSheets = new StyleSheetList(*this); |
75 | 0 | } |
76 | 0 | return *mDOMStyleSheets; |
77 | 0 | } |
78 | | |
79 | | void |
80 | | DocumentOrShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) |
81 | 0 | { |
82 | 0 | aSheet.SetAssociatedDocumentOrShadowRoot( |
83 | 0 | this, StyleSheet::OwnedByDocumentOrShadowRoot); |
84 | 0 | mStyleSheets.InsertElementAt(aIndex, &aSheet); |
85 | 0 | } |
86 | | |
87 | | already_AddRefed<StyleSheet> |
88 | | DocumentOrShadowRoot::RemoveSheet(StyleSheet& aSheet) |
89 | 0 | { |
90 | 0 | auto index = mStyleSheets.IndexOf(&aSheet); |
91 | 0 | if (index == mStyleSheets.NoIndex) { |
92 | 0 | return nullptr; |
93 | 0 | } |
94 | 0 | RefPtr<StyleSheet> sheet = std::move(mStyleSheets[index]); |
95 | 0 | mStyleSheets.RemoveElementAt(index); |
96 | 0 | sheet->ClearAssociatedDocumentOrShadowRoot(); |
97 | 0 | return sheet.forget(); |
98 | 0 | } |
99 | | |
100 | | Element* |
101 | | DocumentOrShadowRoot::GetElementById(const nsAString& aElementId) |
102 | 0 | { |
103 | 0 | if (MOZ_UNLIKELY(aElementId.IsEmpty())) { |
104 | 0 | nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc()); |
105 | 0 | return nullptr; |
106 | 0 | } |
107 | 0 | |
108 | 0 | if (nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aElementId)) { |
109 | 0 | if (Element* el = entry->GetIdElement()) { |
110 | 0 | return el; |
111 | 0 | } |
112 | 0 | } |
113 | 0 | |
114 | 0 | return nullptr; |
115 | 0 | } |
116 | | |
117 | | already_AddRefed<nsContentList> |
118 | | DocumentOrShadowRoot::GetElementsByTagNameNS(const nsAString& aNamespaceURI, |
119 | | const nsAString& aLocalName) |
120 | 0 | { |
121 | 0 | ErrorResult rv; |
122 | 0 | RefPtr<nsContentList> list = |
123 | 0 | GetElementsByTagNameNS(aNamespaceURI, aLocalName, rv); |
124 | 0 | if (rv.Failed()) { |
125 | 0 | return nullptr; |
126 | 0 | } |
127 | 0 | return list.forget(); |
128 | 0 | } |
129 | | |
130 | | already_AddRefed<nsContentList> |
131 | | DocumentOrShadowRoot::GetElementsByTagNameNS(const nsAString& aNamespaceURI, |
132 | | const nsAString& aLocalName, |
133 | | mozilla::ErrorResult& aResult) |
134 | 0 | { |
135 | 0 | int32_t nameSpaceId = kNameSpaceID_Wildcard; |
136 | 0 |
|
137 | 0 | if (!aNamespaceURI.EqualsLiteral("*")) { |
138 | 0 | aResult = |
139 | 0 | nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI, |
140 | 0 | nameSpaceId); |
141 | 0 | if (aResult.Failed()) { |
142 | 0 | return nullptr; |
143 | 0 | } |
144 | 0 | } |
145 | 0 | |
146 | 0 | NS_ASSERTION(nameSpaceId != kNameSpaceID_Unknown, "Unexpected namespace ID!"); |
147 | 0 | return NS_GetContentList(&AsNode(), nameSpaceId, aLocalName); |
148 | 0 | } |
149 | | |
150 | | already_AddRefed<nsContentList> |
151 | | DocumentOrShadowRoot::GetElementsByClassName(const nsAString& aClasses) |
152 | 0 | { |
153 | 0 | return nsContentUtils::GetElementsByClassName(&AsNode(), aClasses); |
154 | 0 | } |
155 | | |
156 | | nsIContent* |
157 | | DocumentOrShadowRoot::Retarget(nsIContent* aContent) const |
158 | 0 | { |
159 | 0 | for (nsIContent* cur = aContent; |
160 | 0 | cur; |
161 | 0 | cur = cur->GetContainingShadowHost()) { |
162 | 0 | if (cur->SubtreeRoot() == &AsNode()) { |
163 | 0 | return cur; |
164 | 0 | } |
165 | 0 | } |
166 | 0 | return nullptr; |
167 | 0 | } |
168 | | |
169 | | Element* |
170 | | DocumentOrShadowRoot::GetRetargetedFocusedElement() |
171 | 0 | { |
172 | 0 | if (nsCOMPtr<nsPIDOMWindowOuter> window = AsNode().OwnerDoc()->GetWindow()) { |
173 | 0 | nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; |
174 | 0 | nsIContent* focusedContent = |
175 | 0 | nsFocusManager::GetFocusedDescendant(window, |
176 | 0 | nsFocusManager::eOnlyCurrentWindow, |
177 | 0 | getter_AddRefs(focusedWindow)); |
178 | 0 | // be safe and make sure the element is from this document |
179 | 0 | if (focusedContent && focusedContent->OwnerDoc() == AsNode().OwnerDoc()) { |
180 | 0 | if (focusedContent->ChromeOnlyAccess()) { |
181 | 0 | focusedContent = focusedContent->FindFirstNonChromeOnlyAccessContent(); |
182 | 0 | } |
183 | 0 |
|
184 | 0 | if (focusedContent) { |
185 | 0 | if (!nsDocument::IsShadowDOMEnabled(focusedContent)) { |
186 | 0 | return focusedContent->AsElement(); |
187 | 0 | } |
188 | 0 | |
189 | 0 | if (nsIContent* retarget = Retarget(focusedContent)) { |
190 | 0 | return retarget->AsElement(); |
191 | 0 | } |
192 | 0 | } |
193 | 0 | } |
194 | 0 | } |
195 | 0 | |
196 | 0 | return nullptr; |
197 | 0 | } |
198 | | |
199 | | Element* |
200 | | DocumentOrShadowRoot::GetPointerLockElement() |
201 | 0 | { |
202 | 0 | nsCOMPtr<Element> pointerLockedElement = |
203 | 0 | do_QueryReferent(EventStateManager::sPointerLockedElement); |
204 | 0 | if (!pointerLockedElement) { |
205 | 0 | return nullptr; |
206 | 0 | } |
207 | 0 | |
208 | 0 | nsIContent* retargetedPointerLockedElement = Retarget(pointerLockedElement); |
209 | 0 | return |
210 | 0 | retargetedPointerLockedElement && retargetedPointerLockedElement->IsElement() ? |
211 | 0 | retargetedPointerLockedElement->AsElement() : nullptr; |
212 | 0 | } |
213 | | |
214 | | Element* |
215 | | DocumentOrShadowRoot::GetFullscreenElement() |
216 | 0 | { |
217 | 0 | if (!AsNode().IsInComposedDoc()) { |
218 | 0 | return nullptr; |
219 | 0 | } |
220 | 0 | |
221 | 0 | Element* element = AsNode().OwnerDoc()->FullscreenStackTop(); |
222 | 0 | NS_ASSERTION(!element || |
223 | 0 | element->State().HasState(NS_EVENT_STATE_FULLSCREEN), |
224 | 0 | "Fullscreen element should have fullscreen styles applied"); |
225 | 0 |
|
226 | 0 | nsIContent* retargeted = Retarget(element); |
227 | 0 | if (retargeted && retargeted->IsElement()) { |
228 | 0 | return retargeted->AsElement(); |
229 | 0 | } |
230 | 0 | |
231 | 0 | return nullptr; |
232 | 0 | } |
233 | | |
234 | | Element* |
235 | | DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) |
236 | 0 | { |
237 | 0 | return ElementFromPointHelper(aX, aY, false, true); |
238 | 0 | } |
239 | | |
240 | | void |
241 | | DocumentOrShadowRoot::ElementsFromPoint(float aX, float aY, |
242 | | nsTArray<RefPtr<Element>>& aElements) |
243 | 0 | { |
244 | 0 | ElementsFromPointHelper(aX, aY, nsIDocument::FLUSH_LAYOUT, aElements); |
245 | 0 | } |
246 | | |
247 | | Element* |
248 | | DocumentOrShadowRoot::ElementFromPointHelper(float aX, float aY, |
249 | | bool aIgnoreRootScrollFrame, |
250 | | bool aFlushLayout) |
251 | 0 | { |
252 | 0 | AutoTArray<RefPtr<Element>, 1> elementArray; |
253 | 0 | ElementsFromPointHelper(aX, aY, |
254 | 0 | ((aIgnoreRootScrollFrame ? nsIDocument::IGNORE_ROOT_SCROLL_FRAME : 0) | |
255 | 0 | (aFlushLayout ? nsIDocument::FLUSH_LAYOUT : 0) | |
256 | 0 | nsIDocument::IS_ELEMENT_FROM_POINT), |
257 | 0 | elementArray); |
258 | 0 | if (elementArray.IsEmpty()) { |
259 | 0 | return nullptr; |
260 | 0 | } |
261 | 0 | return elementArray[0]; |
262 | 0 | } |
263 | | |
264 | | void |
265 | | DocumentOrShadowRoot::ElementsFromPointHelper(float aX, float aY, |
266 | | uint32_t aFlags, |
267 | | nsTArray<RefPtr<mozilla::dom::Element>>& aElements) |
268 | 0 | { |
269 | 0 | // As per the the spec, we return null if either coord is negative |
270 | 0 | if (!(aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) && (aX < 0 || aY < 0)) { |
271 | 0 | return; |
272 | 0 | } |
273 | 0 | |
274 | 0 | nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); |
275 | 0 | nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); |
276 | 0 | nsPoint pt(x, y); |
277 | 0 |
|
278 | 0 | nsCOMPtr<nsIDocument> doc = AsNode().OwnerDoc(); |
279 | 0 |
|
280 | 0 | // Make sure the layout information we get is up-to-date, and |
281 | 0 | // ensure we get a root frame (for everything but XUL) |
282 | 0 | if (aFlags & nsIDocument::FLUSH_LAYOUT) { |
283 | 0 | doc->FlushPendingNotifications(FlushType::Layout); |
284 | 0 | } |
285 | 0 |
|
286 | 0 | nsIPresShell* ps = doc->GetShell(); |
287 | 0 | if (!ps) { |
288 | 0 | return; |
289 | 0 | } |
290 | 0 | nsIFrame* rootFrame = ps->GetRootFrame(); |
291 | 0 |
|
292 | 0 | // XUL docs, unlike HTML, have no frame tree until everything's done loading |
293 | 0 | if (!rootFrame) { |
294 | 0 | return; // return null to premature XUL callers as a reminder to wait |
295 | 0 | } |
296 | 0 | |
297 | 0 | nsTArray<nsIFrame*> outFrames; |
298 | 0 | // Emulate what GetFrameAtPoint does, since we want all the frames under our |
299 | 0 | // point. |
300 | 0 | nsLayoutUtils::GetFramesForArea(rootFrame, nsRect(pt, nsSize(1, 1)), outFrames, |
301 | 0 | nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC | |
302 | 0 | ((aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0)); |
303 | 0 |
|
304 | 0 | // Dunno when this would ever happen, as we should at least have a root frame under us? |
305 | 0 | if (outFrames.IsEmpty()) { |
306 | 0 | return; |
307 | 0 | } |
308 | 0 | |
309 | 0 | // Used to filter out repeated elements in sequence. |
310 | 0 | nsIContent* lastAdded = nullptr; |
311 | 0 |
|
312 | 0 | for (uint32_t i = 0; i < outFrames.Length(); i++) { |
313 | 0 | nsIContent* node = doc->GetContentInThisDocument(outFrames[i]); |
314 | 0 |
|
315 | 0 | if (!node || !node->IsElement()) { |
316 | 0 | // If this helper is called via ElementsFromPoint, we need to make sure |
317 | 0 | // our frame is an element. Otherwise return whatever the top frame is |
318 | 0 | // even if it isn't the top-painted element. |
319 | 0 | // SVG 'text' element's SVGTextFrame doesn't respond to hit-testing, so |
320 | 0 | // if 'node' is a child of such an element then we need to manually defer |
321 | 0 | // to the parent here. |
322 | 0 | if (!(aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) && |
323 | 0 | !nsSVGUtils::IsInSVGTextSubtree(outFrames[i])) { |
324 | 0 | continue; |
325 | 0 | } |
326 | 0 | node = node->GetParent(); |
327 | 0 | if (ShadowRoot* shadow = ShadowRoot::FromNodeOrNull(node)) { |
328 | 0 | node = shadow->Host(); |
329 | 0 | } |
330 | 0 | } |
331 | 0 |
|
332 | 0 | //XXXsmaug There is plenty of unspec'ed behavior here |
333 | 0 | // https://github.com/w3c/webcomponents/issues/735 |
334 | 0 | // https://github.com/w3c/webcomponents/issues/736 |
335 | 0 | node = Retarget(node); |
336 | 0 |
|
337 | 0 | if (node && node != lastAdded) { |
338 | 0 | aElements.AppendElement(node->AsElement()); |
339 | 0 | lastAdded = node; |
340 | 0 | // If this helper is called via ElementFromPoint, just return the first |
341 | 0 | // element we find. |
342 | 0 | if (aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) { |
343 | 0 | return; |
344 | 0 | } |
345 | 0 | } |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | | Element* |
350 | | DocumentOrShadowRoot::AddIDTargetObserver(nsAtom* aID, |
351 | | IDTargetObserver aObserver, |
352 | | void* aData, bool aForImage) |
353 | 0 | { |
354 | 0 | nsDependentAtomString id(aID); |
355 | 0 |
|
356 | 0 | if (!CheckGetElementByIdArg(id)) { |
357 | 0 | return nullptr; |
358 | 0 | } |
359 | 0 | |
360 | 0 | nsIdentifierMapEntry* entry = mIdentifierMap.PutEntry(aID); |
361 | 0 | NS_ENSURE_TRUE(entry, nullptr); |
362 | 0 |
|
363 | 0 | entry->AddContentChangeCallback(aObserver, aData, aForImage); |
364 | 0 | return aForImage ? entry->GetImageIdElement() : entry->GetIdElement(); |
365 | 0 | } |
366 | | |
367 | | void |
368 | | DocumentOrShadowRoot::RemoveIDTargetObserver(nsAtom* aID, |
369 | | IDTargetObserver aObserver, |
370 | | void* aData, bool aForImage) |
371 | 0 | { |
372 | 0 | nsDependentAtomString id(aID); |
373 | 0 |
|
374 | 0 | if (!CheckGetElementByIdArg(id)) { |
375 | 0 | return; |
376 | 0 | } |
377 | 0 | |
378 | 0 | nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aID); |
379 | 0 | if (!entry) { |
380 | 0 | return; |
381 | 0 | } |
382 | 0 | |
383 | 0 | entry->RemoveContentChangeCallback(aObserver, aData, aForImage); |
384 | 0 | } |
385 | | |
386 | | |
387 | | Element* |
388 | | DocumentOrShadowRoot::LookupImageElement(const nsAString& aId) |
389 | 0 | { |
390 | 0 | if (aId.IsEmpty()) { |
391 | 0 | return nullptr; |
392 | 0 | } |
393 | 0 | |
394 | 0 | nsIdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId); |
395 | 0 | return entry ? entry->GetImageIdElement() : nullptr; |
396 | 0 | } |
397 | | |
398 | | void |
399 | | DocumentOrShadowRoot::ReportEmptyGetElementByIdArg() |
400 | 0 | { |
401 | 0 | nsContentUtils::ReportEmptyGetElementByIdArg(AsNode().OwnerDoc()); |
402 | 0 | } |
403 | | |
404 | | /** |
405 | | * A struct that holds all the information about a radio group. |
406 | | */ |
407 | | struct nsRadioGroupStruct |
408 | | { |
409 | | nsRadioGroupStruct() |
410 | | : mRequiredRadioCount(0) |
411 | | , mGroupSuffersFromValueMissing(false) |
412 | 0 | {} |
413 | | |
414 | | /** |
415 | | * A strong pointer to the currently selected radio button. |
416 | | */ |
417 | | RefPtr<HTMLInputElement> mSelectedRadioButton; |
418 | | nsCOMArray<nsIFormControl> mRadioButtons; |
419 | | uint32_t mRequiredRadioCount; |
420 | | bool mGroupSuffersFromValueMissing; |
421 | | }; |
422 | | |
423 | | nsresult |
424 | | DocumentOrShadowRoot::WalkRadioGroup(const nsAString& aName, |
425 | | nsIRadioVisitor* aVisitor, |
426 | | bool aFlushContent) |
427 | 0 | { |
428 | 0 | nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); |
429 | 0 |
|
430 | 0 | for (int i = 0; i < radioGroup->mRadioButtons.Count(); i++) { |
431 | 0 | if (!aVisitor->Visit(radioGroup->mRadioButtons[i])) { |
432 | 0 | return NS_OK; |
433 | 0 | } |
434 | 0 | } |
435 | 0 |
|
436 | 0 | return NS_OK; |
437 | 0 | } |
438 | | |
439 | | void |
440 | | DocumentOrShadowRoot::SetCurrentRadioButton(const nsAString& aName, |
441 | | HTMLInputElement* aRadio) |
442 | 0 | { |
443 | 0 | nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); |
444 | 0 | radioGroup->mSelectedRadioButton = aRadio; |
445 | 0 | } |
446 | | |
447 | | HTMLInputElement* |
448 | | DocumentOrShadowRoot::GetCurrentRadioButton(const nsAString& aName) |
449 | 0 | { |
450 | 0 | return GetOrCreateRadioGroup(aName)->mSelectedRadioButton; |
451 | 0 | } |
452 | | |
453 | | nsresult |
454 | | DocumentOrShadowRoot::GetNextRadioButton(const nsAString& aName, |
455 | | const bool aPrevious, |
456 | | HTMLInputElement* aFocusedRadio, |
457 | | HTMLInputElement** aRadioOut) |
458 | 0 | { |
459 | 0 | // XXX Can we combine the HTML radio button method impls of |
460 | 0 | // nsDocument and nsHTMLFormControl? |
461 | 0 | // XXX Why is HTML radio button stuff in nsDocument, as |
462 | 0 | // opposed to nsHTMLDocument? |
463 | 0 | *aRadioOut = nullptr; |
464 | 0 |
|
465 | 0 | nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); |
466 | 0 |
|
467 | 0 | // Return the radio button relative to the focused radio button. |
468 | 0 | // If no radio is focused, get the radio relative to the selected one. |
469 | 0 | RefPtr<HTMLInputElement> currentRadio; |
470 | 0 | if (aFocusedRadio) { |
471 | 0 | currentRadio = aFocusedRadio; |
472 | 0 | } else { |
473 | 0 | currentRadio = radioGroup->mSelectedRadioButton; |
474 | 0 | if (!currentRadio) { |
475 | 0 | return NS_ERROR_FAILURE; |
476 | 0 | } |
477 | 0 | } |
478 | 0 | int32_t index = radioGroup->mRadioButtons.IndexOf(currentRadio); |
479 | 0 | if (index < 0) { |
480 | 0 | return NS_ERROR_FAILURE; |
481 | 0 | } |
482 | 0 | |
483 | 0 | int32_t numRadios = radioGroup->mRadioButtons.Count(); |
484 | 0 | RefPtr<HTMLInputElement> radio; |
485 | 0 | do { |
486 | 0 | if (aPrevious) { |
487 | 0 | if (--index < 0) { |
488 | 0 | index = numRadios -1; |
489 | 0 | } |
490 | 0 | } else if (++index >= numRadios) { |
491 | 0 | index = 0; |
492 | 0 | } |
493 | 0 | NS_ASSERTION(static_cast<nsGenericHTMLFormElement*>(radioGroup->mRadioButtons[index])->IsHTMLElement(nsGkAtoms::input), |
494 | 0 | "mRadioButtons holding a non-radio button"); |
495 | 0 | radio = static_cast<HTMLInputElement*>(radioGroup->mRadioButtons[index]); |
496 | 0 | } while (radio->Disabled() && radio != currentRadio); |
497 | 0 |
|
498 | 0 | radio.forget(aRadioOut); |
499 | 0 | return NS_OK; |
500 | 0 | } |
501 | | |
502 | | void |
503 | | DocumentOrShadowRoot::AddToRadioGroup(const nsAString& aName, |
504 | | HTMLInputElement* aRadio) |
505 | 0 | { |
506 | 0 | nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); |
507 | 0 | radioGroup->mRadioButtons.AppendObject(aRadio); |
508 | 0 |
|
509 | 0 | if (aRadio->IsRequired()) { |
510 | 0 | radioGroup->mRequiredRadioCount++; |
511 | 0 | } |
512 | 0 | } |
513 | | |
514 | | void |
515 | | DocumentOrShadowRoot::RemoveFromRadioGroup(const nsAString& aName, |
516 | | HTMLInputElement* aRadio) |
517 | 0 | { |
518 | 0 | nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); |
519 | 0 | radioGroup->mRadioButtons.RemoveObject(aRadio); |
520 | 0 |
|
521 | 0 | if (aRadio->IsRequired()) { |
522 | 0 | NS_ASSERTION(radioGroup->mRequiredRadioCount != 0, |
523 | 0 | "mRequiredRadioCount about to wrap below 0!"); |
524 | 0 | radioGroup->mRequiredRadioCount--; |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | | uint32_t |
529 | | DocumentOrShadowRoot::GetRequiredRadioCount(const nsAString& aName) const |
530 | 0 | { |
531 | 0 | nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); |
532 | 0 | return radioGroup ? radioGroup->mRequiredRadioCount : 0; |
533 | 0 | } |
534 | | |
535 | | void |
536 | | DocumentOrShadowRoot::RadioRequiredWillChange(const nsAString& aName, |
537 | | bool aRequiredAdded) |
538 | 0 | { |
539 | 0 | nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); |
540 | 0 |
|
541 | 0 | if (aRequiredAdded) { |
542 | 0 | radioGroup->mRequiredRadioCount++; |
543 | 0 | } else { |
544 | 0 | NS_ASSERTION(radioGroup->mRequiredRadioCount != 0, |
545 | 0 | "mRequiredRadioCount about to wrap below 0!"); |
546 | 0 | radioGroup->mRequiredRadioCount--; |
547 | 0 | } |
548 | 0 | } |
549 | | |
550 | | bool |
551 | | DocumentOrShadowRoot::GetValueMissingState(const nsAString& aName) const |
552 | 0 | { |
553 | 0 | nsRadioGroupStruct* radioGroup = GetRadioGroup(aName); |
554 | 0 | return radioGroup && radioGroup->mGroupSuffersFromValueMissing; |
555 | 0 | } |
556 | | |
557 | | void |
558 | | DocumentOrShadowRoot::SetValueMissingState(const nsAString& aName, bool aValue) |
559 | 0 | { |
560 | 0 | nsRadioGroupStruct* radioGroup = GetOrCreateRadioGroup(aName); |
561 | 0 | radioGroup->mGroupSuffersFromValueMissing = aValue; |
562 | 0 | } |
563 | | |
564 | | nsRadioGroupStruct* |
565 | | DocumentOrShadowRoot::GetRadioGroup(const nsAString& aName) const |
566 | 0 | { |
567 | 0 | nsRadioGroupStruct* radioGroup = nullptr; |
568 | 0 | mRadioGroups.Get(aName, &radioGroup); |
569 | 0 | return radioGroup; |
570 | 0 | } |
571 | | |
572 | | nsRadioGroupStruct* |
573 | | DocumentOrShadowRoot::GetOrCreateRadioGroup(const nsAString& aName) |
574 | 0 | { |
575 | 0 | return mRadioGroups.LookupForAdd(aName).OrInsert( |
576 | 0 | [] () { return new nsRadioGroupStruct(); }); |
577 | 0 | } |
578 | | |
579 | | void |
580 | | DocumentOrShadowRoot::Traverse(DocumentOrShadowRoot* tmp, |
581 | | nsCycleCollectionTraversalCallback &cb) |
582 | 0 | { |
583 | 0 | for (auto iter = tmp->mRadioGroups.Iter(); !iter.Done(); iter.Next()) { |
584 | 0 | nsRadioGroupStruct* radioGroup = iter.UserData(); |
585 | 0 | NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( |
586 | 0 | cb, "mRadioGroups entry->mSelectedRadioButton"); |
587 | 0 | cb.NoteXPCOMChild(ToSupports(radioGroup->mSelectedRadioButton)); |
588 | 0 |
|
589 | 0 | uint32_t i, count = radioGroup->mRadioButtons.Count(); |
590 | 0 | for (i = 0; i < count; ++i) { |
591 | 0 | NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( |
592 | 0 | cb, "mRadioGroups entry->mRadioButtons[i]"); |
593 | 0 | cb.NoteXPCOMChild(radioGroup->mRadioButtons[i]); |
594 | 0 | } |
595 | 0 | } |
596 | 0 | } |
597 | | |
598 | | void |
599 | | DocumentOrShadowRoot::Unlink(DocumentOrShadowRoot* tmp) |
600 | 0 | { |
601 | 0 | tmp->mRadioGroups.Clear(); |
602 | 0 | } |
603 | | |
604 | | } |
605 | | } |