summaryrefslogtreecommitdiffstats
path: root/dom/base/nsIContent.h
blob: e6fc8fc4309d5377ade77861e5c3b4b05e439ad7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsIContent_h___
#define nsIContent_h___

#include "mozilla/FlushType.h"
#include "nsINode.h"
#include "nsStringFwd.h"

// Forward declarations
class nsIURI;
class nsTextFragment;
class nsIFrame;

namespace mozilla {
enum class IsFocusableFlags : uint8_t;
class EventChainPreVisitor;
class HTMLEditor;
struct URLExtraData;
namespace dom {
struct BindContext;
struct UnbindContext;
class ShadowRoot;
class HTMLSlotElement;
}  // namespace dom
namespace widget {
enum class IMEEnabled;
struct IMEState;
}  // namespace widget
}  // namespace mozilla

struct Focusable {
  bool mFocusable = false;
  // The computed tab index:
  //         < 0 if not tabbable
  //         == 0 if in normal tab order
  //         > 0 can be tabbed to in the order specified by this value
  int32_t mTabIndex = -1;
  explicit operator bool() const { return mFocusable; }
};

// IID for the nsIContent interface
// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
#define NS_ICONTENT_IID                              \
  {                                                  \
    0x8e1bab9d, 0x8815, 0x4d2c, {                    \
      0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22 \
    }                                                \
  }

/**
 * A node of content in a document's content model. This interface
 * is supported by all content objects.
 */
class nsIContent : public nsINode {
 public:
  using IMEEnabled = mozilla::widget::IMEEnabled;
  using IMEState = mozilla::widget::IMEState;
  using BindContext = mozilla::dom::BindContext;
  using UnbindContext = mozilla::dom::UnbindContext;

  void ConstructUbiNode(void* storage) override;

#ifdef MOZILLA_INTERNAL_API
  // If you're using the external API, the only thing you can know about
  // nsIContent is that it exists with an IID

  explicit nsIContent(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
      : nsINode(std::move(aNodeInfo)) {
    MOZ_ASSERT(mNodeInfo);
    MOZ_ASSERT(static_cast<nsINode*>(this) == reinterpret_cast<nsINode*>(this));
    SetNodeIsContent();
  }
#endif  // MOZILLA_INTERNAL_API

  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENT_IID)

  NS_DECL_ISUPPORTS_INHERITED
  NS_IMETHOD_(void) DeleteCycleCollectable(void) final;

  NS_DECL_CYCLE_COLLECTION_CLASS(nsIContent)

  NS_DECL_DOMARENA_DESTROY

  NS_IMPL_FROMNODE_HELPER(nsIContent, IsContent())

  /**
   * Bind this content node to a tree.  If this method throws, the caller must
   * call UnbindFromTree() on the node.  In the typical case of a node being
   * appended to a parent, this will be called after the node has been added to
   * the parent's child list and before nsIDocumentObserver notifications for
   * the addition are dispatched.
   * BindContext propagates various information down the subtree; see its
   * documentation to know how to set it up.
   * @param aParent The new parent node for the content node. May be a document.
   * @note This method must not be called by consumers of nsIContent on a node
   *       that is already bound to a tree.  Call UnbindFromTree first.
   * @note This method will handle rebinding descendants appropriately (eg
   *       changing their binding parent as needed).
   * @note This method does not add the content node to aParent's child list
   * @throws NS_ERROR_OUT_OF_MEMORY if that happens
   *
   * TODO(emilio): Should we move to nsIContent::BindToTree most of the
   * FragmentOrElement / CharacterData duplicated code?
   */
  virtual nsresult BindToTree(BindContext&, nsINode& aParent) = 0;

  /**
   * Unbind this content node from a tree.  This will set its current document
   * and binding parent to null.  In the typical case of a node being removed
   * from a parent, this will be called after it has been removed from the
   * parent's child list and after the nsIDocumentObserver notifications for
   * the removal have been dispatched.
   * @note This method is safe to call on nodes that are not bound to a tree.
   */
  virtual void UnbindFromTree(UnbindContext&) = 0;
  void UnbindFromTree();

  enum {
    /**
     * All XBL flattened tree children of the node, as well as :before and
     * :after anonymous content and native anonymous children.
     *
     * @note the result children order is
     *   1. :before generated node
     *   2. Shadow DOM flattened tree children of this node
     *   3. native anonymous nodes
     *   4. :after generated node
     */
    eAllChildren = 0,

    /**
     * Skip native anonymous content created for placeholder of HTML input.
     */
    eSkipPlaceholderContent = 1 << 0,

    /**
     * Skip native anonymous content created by ancestor frames of the root
     * element's primary frame, such as scrollbar elements created by the root
     * scroll frame.
     */
    eSkipDocumentLevelNativeAnonymousContent = 1 << 1,
  };

  /**
   * Makes this content anonymous
   * @see nsIAnonymousContentCreator
   */
  void SetIsNativeAnonymousRoot() {
    SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE |
             NODE_IS_NATIVE_ANONYMOUS_ROOT);
  }

  /**
   * Returns |this| if it is not chrome-only/native anonymous, otherwise
   * first non chrome-only/native anonymous ancestor.
   */
  nsIContent* FindFirstNonChromeOnlyAccessContent() const;

  /**
   * Return true iff this node is in an HTML document (in the HTML5 sense of
   * the term, i.e. not in an XHTML/XML document).
   */
  inline bool IsInHTMLDocument() const;

  /**
   * Returns true if in a chrome document
   */
  inline bool IsInChromeDocument() const;

  /**
   * Get the namespace that this element's tag is defined in
   * @return the namespace
   */
  inline int32_t GetNameSpaceID() const { return mNodeInfo->NamespaceID(); }

  inline bool IsHTMLElement() const {
    return IsInNamespace(kNameSpaceID_XHTML);
  }

  inline bool IsHTMLElement(const nsAtom* aTag) const {
    return mNodeInfo->Equals(aTag, kNameSpaceID_XHTML);
  }

  template <typename First, typename... Args>
  inline bool IsAnyOfHTMLElements(First aFirst, Args... aArgs) const {
    return IsHTMLElement() && IsNodeInternal(aFirst, aArgs...);
  }

  inline bool IsSVGElement() const { return IsInNamespace(kNameSpaceID_SVG); }

  inline bool IsSVGElement(const nsAtom* aTag) const {
    return mNodeInfo->Equals(aTag, kNameSpaceID_SVG);
  }

  template <typename First, typename... Args>
  inline bool IsAnyOfSVGElements(First aFirst, Args... aArgs) const {
    return IsSVGElement() && IsNodeInternal(aFirst, aArgs...);
  }

  inline bool IsXULElement() const { return IsInNamespace(kNameSpaceID_XUL); }

  inline bool IsXULElement(const nsAtom* aTag) const {
    return mNodeInfo->Equals(aTag, kNameSpaceID_XUL);
  }

  template <typename First, typename... Args>
  inline bool IsAnyOfXULElements(First aFirst, Args... aArgs) const {
    return IsXULElement() && IsNodeInternal(aFirst, aArgs...);
  }

  inline bool IsMathMLElement() const {
    return IsInNamespace(kNameSpaceID_MathML);
  }

  inline bool IsMathMLElement(const nsAtom* aTag) const {
    return mNodeInfo->Equals(aTag, kNameSpaceID_MathML);
  }

  template <typename First, typename... Args>
  inline bool IsAnyOfMathMLElements(First aFirst, Args... aArgs) const {
    return IsMathMLElement() && IsNodeInternal(aFirst, aArgs...);
  }

  /**
   * Get direct access (but read only) to the text in the text content.
   * NOTE: For elements this is *not* the concatenation of all text children,
   * it is simply null;
   */
  virtual const nsTextFragment* GetText() = 0;

  /**
   * Get the length of the text content.
   * NOTE: This should not be called on elements.
   */
  virtual uint32_t TextLength() const = 0;

  /**
   * Determines if an event attribute name (such as onclick) is valid for
   * a given element type.
   * @note calls nsContentUtils::IsEventAttributeName with right flag
   * @note *Internal is overridden by subclasses as needed
   * @param aName the event name to look up
   */
  bool IsEventAttributeName(nsAtom* aName);

  virtual bool IsEventAttributeNameInternal(nsAtom* aName) { return false; }

  /**
   * Query method to see if the frame is nothing but whitespace
   * NOTE: Always returns false for elements
   */
  virtual bool TextIsOnlyWhitespace() = 0;

  /**
   * Thread-safe version of TextIsOnlyWhitespace.
   */
  virtual bool ThreadSafeTextIsOnlyWhitespace() const = 0;

  /**
   * Check if this content is focusable and in the current tab order.
   * Note: most callers should use nsIFrame::IsFocusable() instead as it
   *       checks visibility and other layout factors as well.
   * Tabbable is indicated by a nonnegative tabindex & is a subset of focusable.
   * For example, only the selected radio button in a group is in the
   * tab order, unless the radio group has no selection in which case
   * all of the visible, non-disabled radio buttons in the group are
   * in the tab order. On the other hand, all of the visible, non-disabled
   * radio buttons are always focusable via clicking or script.
   * Also, depending on either the accessibility.tabfocus pref or
   * a system setting (nowadays: Full keyboard access, mac only)
   * some widgets may be focusable but removed from the tab order.
   * @return whether the content is focusable via mouse, kbd or script.
   */
  virtual Focusable IsFocusableWithoutStyle(
      mozilla::IsFocusableFlags = mozilla::IsFocusableFlags(0));

  // https://html.spec.whatwg.org/multipage/interaction.html#focus-delegate
  mozilla::dom::Element* GetFocusDelegate(mozilla::IsFocusableFlags) const;

  // https://html.spec.whatwg.org/multipage/interaction.html#autofocus-delegate
  mozilla::dom::Element* GetAutofocusDelegate(mozilla::IsFocusableFlags) const;

  /*
   * Get desired IME state for the content.
   *
   * @return The desired IME status for the content.
   *         This is a combination of an IME enabled value and
   *         an IME open value of widget::IMEState.
   *         If you return IMEEnabled::Disabled, you should not set the OPEN
   *         nor CLOSE value.
   *         IMEEnabled::Password should be returned only from password editor,
   *         this value has a special meaning. It is used as alternative of
   *         IMEEnabled::Disabled. IMEENabled::Plugin should be returned only
   *         when plug-in has focus.  When a plug-in is focused content, we
   *         should send native events directly. Because we don't process some
   *         native events, but they may be needed by the plug-in.
   */
  virtual IMEState GetDesiredIMEState();

  /**
   * Gets the ShadowRoot binding for this element.
   *
   * @return The ShadowRoot currently bound to this element.
   */
  inline mozilla::dom::ShadowRoot* GetShadowRoot() const;

  /**
   * Gets the root of the node tree for this content if it is in a shadow tree.
   *
   * @return The ShadowRoot that is the root of the node tree.
   */
  mozilla::dom::ShadowRoot* GetContainingShadow() const {
    const nsExtendedContentSlots* slots = GetExistingExtendedContentSlots();
    return slots ? slots->mContainingShadow.get() : nullptr;
  }

  /**
   * Gets the assigned slot associated with this content.
   *
   * @return The assigned slot element or null.
   */
  mozilla::dom::HTMLSlotElement* GetAssignedSlot() const {
    const nsExtendedContentSlots* slots = GetExistingExtendedContentSlots();
    return slots ? slots->mAssignedSlot.get() : nullptr;
  }

  /**
   * Sets the assigned slot associated with this content.
   *
   * @param aSlot The assigned slot.
   */
  void SetAssignedSlot(mozilla::dom::HTMLSlotElement* aSlot);

  /**
   * Gets the assigned slot associated with this content based on parent's
   * shadow root mode. Returns null if parent's shadow root is "closed".
   * https://dom.spec.whatwg.org/#dom-slotable-assignedslot
   *
   * @return The assigned slot element or null.
   */
  mozilla::dom::HTMLSlotElement* GetAssignedSlotByMode() const;

  mozilla::dom::HTMLSlotElement* GetManualSlotAssignment() const {
    const nsExtendedContentSlots* slots = GetExistingExtendedContentSlots();
    return slots ? slots->mManualSlotAssignment : nullptr;
  }

  void SetManualSlotAssignment(mozilla::dom::HTMLSlotElement* aSlot) {
    MOZ_ASSERT(aSlot || GetExistingExtendedContentSlots());
    ExtendedContentSlots()->mManualSlotAssignment = aSlot;
  }

  /**
   * Same as GetFlattenedTreeParentNode, but returns null if the parent is
   * non-nsIContent.
   */
  inline nsIContent* GetFlattenedTreeParent() const;

 protected:
  // Handles getting inserted or removed directly under a <slot> element.
  // This is meant to only be called from the two functions below.
  inline void HandleInsertionToOrRemovalFromSlot();

  // Handles Shadow-DOM-related state tracking. Meant to be called near the
  // end of BindToTree(), only if the tree we're in actually changed, that is,
  // after the subtree has been bound to the new parent.
  inline void HandleShadowDOMRelatedInsertionSteps(bool aHadParent);

  // Handles Shadow-DOM related state tracking. Meant to be called near the
  // beginning of UnbindFromTree(), before the node has lost the reference to
  // its parent.
  inline void HandleShadowDOMRelatedRemovalSteps(bool aNullParent);

 public:
  /**
   * This method is called when the parser finishes creating the element.  This
   * particularly means that it has done everything you would expect it to have
   * done after it encounters the > at the end of the tag (for HTML or XML).
   * This includes setting the attributes, setting the document / form, and
   * placing the element into the tree at its proper place.
   *
   * For container elements, this is called *before* any of the children are
   * created or added into the tree.
   *
   * NOTE: this is only called for elements listed in
   * RequiresDoneCreatingElement. This is an efficiency measure.
   *
   * If you also need to determine whether the parser is the one creating your
   * element (through createElement() or cloneNode() generally) then add a
   * uint32_t aFromParser to the NS_NewXXX() constructor for your element and
   * have the parser pass the appropriate flags. See HTMLInputElement.cpp and
   * nsHTMLContentSink::MakeContentObject().
   *
   * DO NOT USE THIS METHOD to get around the fact that it's hard to deal with
   * attributes dynamically.  If you make attributes affect your element from
   * this method, it will only happen on initialization and JavaScript will not
   * be able to create elements (which requires them to first create the
   * element and then call setAttribute() directly, at which point
   * DoneCreatingElement() has already been called and is out of the picture).
   */
  virtual void DoneCreatingElement() {}

  /**
   * This method is called when the parser finishes creating the element's
   * children, if any are present.
   *
   * NOTE: this is only called for elements listed in
   * RequiresDoneAddingChildren. This is an efficiency measure.
   *
   * If you also need to determine whether the parser is the one creating your
   * element (through createElement() or cloneNode() generally) then add a
   * boolean aFromParser to the NS_NewXXX() constructor for your element and
   * have the parser pass true.  See HTMLInputElement.cpp and
   * nsHTMLContentSink::MakeContentObject().
   *
   * @param aHaveNotified Whether there has been a
   *        ContentInserted/ContentAppended notification for this content node
   *        yet.
   */
  virtual void DoneAddingChildren(bool aHaveNotified) {}

  /**
   * Returns true if an element needs its DoneCreatingElement method to be
   * called after it has been created.
   * @see nsIContent::DoneCreatingElement
   *
   * @param aNamespaceID the node's namespace ID
   * @param aName the node's tag name
   */
  static inline bool RequiresDoneCreatingElement(int32_t aNamespace,
                                                 nsAtom* aName) {
    if (aNamespace == kNameSpaceID_XHTML) {
      if (aName == nsGkAtoms::input || aName == nsGkAtoms::button ||
          aName == nsGkAtoms::audio || aName == nsGkAtoms::video) {
        MOZ_ASSERT(!RequiresDoneAddingChildren(aNamespace, aName),
                   "Both DoneCreatingElement and DoneAddingChildren on a "
                   "same element isn't supported.");
        return true;
      }
      if (aName->IsDynamic()) {
        // This could be a form-associated custom element, so check if its
        // name includes a -.
        nsDependentString name(aName->GetUTF16String());
        return name.Contains('-');
      }
    }
    return false;
  }

  /**
   * Returns true if an element needs its DoneAddingChildren method to be
   * called after all of its children have been added.
   * @see nsIContent::DoneAddingChildren
   *
   * @param aNamespace the node's namespace ID
   * @param aName the node's tag name
   */
  static inline bool RequiresDoneAddingChildren(int32_t aNamespace,
                                                nsAtom* aName) {
    return (aNamespace == kNameSpaceID_XHTML &&
            (aName == nsGkAtoms::select || aName == nsGkAtoms::textarea ||
             aName == nsGkAtoms::head || aName == nsGkAtoms::title ||
             aName == nsGkAtoms::object || aName == nsGkAtoms::output)) ||
           (aNamespace == kNameSpaceID_SVG && aName == nsGkAtoms::title) ||
           (aNamespace == kNameSpaceID_XUL && aName == nsGkAtoms::linkset);
  }

  /**
   * Get the ID of this content node (the atom corresponding to the
   * value of the id attribute).  This may be null if there is no ID.
   */
  nsAtom* GetID() const {
    if (HasID()) {
      return DoGetID();
    }
    return nullptr;
  }

  /**
   * Should be called when the node can become editable or when it can stop
   * being editable (for example when its contentEditable attribute changes,
   * when it is moved into an editable parent, ...).  If aNotify is true and
   * the node is an element, this will notify the state change.
   */
  virtual void UpdateEditableState(bool aNotify);

  /**
   * Destroy this node and its children. Ideally this shouldn't be needed
   * but for now we need to do it to break cycles.
   */
  virtual void DestroyContent() {}

  /**
   * Saves the form state of this node and its children.
   */
  virtual void SaveSubtreeState() = 0;

  /**
   * Getter and setter for our primary frame pointer.  This is the frame that
   * is most closely associated with the content. A frame is more closely
   * associated with the content than another frame if the one frame contains
   * directly or indirectly the other frame (e.g., when a frame is scrolled
   * there is a scroll frame that contains the frame being scrolled). This
   * frame is always the first continuation.
   *
   * In the case of absolutely positioned elements and floated elements, this
   * frame is the out of flow frame, not the placeholder.
   */
  nsIFrame* GetPrimaryFrame() const {
    return (IsInUncomposedDoc() || IsInShadowTree()) ? mPrimaryFrame : nullptr;
  }

  /**
   * Get the primary frame for this content with flushing
   *
   * @param aType the kind of flush to do, typically FlushType::Frames or
   *              FlushType::Layout
   * @return the primary frame
   */
  nsIFrame* GetPrimaryFrame(mozilla::FlushType aType);

  // Defined in nsIContentInlines.h because it needs nsIFrame.
  inline void SetPrimaryFrame(nsIFrame* aFrame);

  nsresult LookupNamespaceURIInternal(const nsAString& aNamespacePrefix,
                                      nsAString& aNamespaceURI) const;

  /**
   * If this content has independent selection, e.g., if this is input field
   * or textarea, this return TRUE.  Otherwise, false.
   */
  bool HasIndependentSelection() const;

  /**
   * If the content is a part of HTML editor, this returns editing
   * host content.  When the content is in designMode, this returns its body
   * element.  Also, when the content isn't editable, this returns null.
   */
  mozilla::dom::Element* GetEditingHost();

  bool SupportsLangAttr() const {
    return IsHTMLElement() || IsSVGElement() || IsXULElement();
  }

  /**
   * Determining language. Look at the nearest ancestor element that has a lang
   * attribute in the XML namespace or is an HTML/SVG element and has a lang in
   * no namespace attribute.
   *
   * Returns null if no language was specified. Can return the empty atom.
   */
  nsAtom* GetLang() const;

  bool GetLang(nsAString& aResult) const {
    if (auto* lang = GetLang()) {
      aResult.Assign(nsDependentAtomString(lang));
      return true;
    }

    return false;
  }

  // Overloaded from nsINode
  nsIURI* GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;

  // Returns base URI for style attribute.
  nsIURI* GetBaseURIForStyleAttr() const;

  // Returns the URL data for style attribute.
  // If aSubjectPrincipal is passed, it should be the scripted principal
  // responsible for generating the URL data.
  already_AddRefed<mozilla::URLExtraData> GetURLDataForStyleAttr(
      nsIPrincipal* aSubjectPrincipal = nullptr) const;

  void GetEventTargetParent(mozilla::EventChainPreVisitor& aVisitor) override;

  bool IsPurple() const { return mRefCnt.IsPurple(); }

  void RemovePurple() { mRefCnt.RemovePurple(); }

  // Note, currently this doesn't handle the case when frame tree has multiple
  // references to the nsIContent object.
  bool OwnedOnlyByTheDOMAndFrameTrees() {
    return OwnedOnlyByTheDOMTree(GetPrimaryFrame() ? 1 : 0);
  }

  bool OwnedOnlyByTheDOMTree(uint32_t aExpectedRefs = 0) {
    uint32_t rc = mRefCnt.get();
    if (GetParent()) {
      --rc;
    }
    rc -= GetChildCount();
    return rc == aExpectedRefs;
  }

  /**
   * Use this method with designMode and contentEditable to check if the
   * node may need spellchecking.
   */
  bool InclusiveDescendantMayNeedSpellchecking(mozilla::HTMLEditor* aEditor);

 protected:
  /**
   * Lazily allocated extended slots to avoid
   * that may only be instantiated when a content object is accessed
   * through the DOM. Rather than burn actual slots in the content
   * objects for each of these instance variables, we put them off
   * in a side structure that's only allocated when the content is
   * accessed through the DOM.
   */
  class nsExtendedContentSlots {
   public:
    nsExtendedContentSlots();
    virtual ~nsExtendedContentSlots();

    virtual void TraverseExtendedSlots(nsCycleCollectionTraversalCallback&);
    virtual void UnlinkExtendedSlots(nsIContent&);

    virtual size_t SizeOfExcludingThis(
        mozilla::MallocSizeOf aMallocSizeOf) const;

    /**
     * @see nsIContent::GetContainingShadow
     */
    RefPtr<mozilla::dom::ShadowRoot> mContainingShadow;

    /**
     * @see nsIContent::GetAssignedSlot
     */
    RefPtr<mozilla::dom::HTMLSlotElement> mAssignedSlot;

    mozilla::dom::HTMLSlotElement* mManualSlotAssignment = nullptr;
  };

  class nsContentSlots : public nsINode::nsSlots {
   public:
    nsContentSlots() : mExtendedSlots(0) {}

    ~nsContentSlots() {
      if (!(mExtendedSlots & sNonOwningExtendedSlotsFlag)) {
        delete GetExtendedContentSlots();
      }
    }

    void Traverse(nsCycleCollectionTraversalCallback& aCb) override {
      nsINode::nsSlots::Traverse(aCb);
      if (mExtendedSlots) {
        GetExtendedContentSlots()->TraverseExtendedSlots(aCb);
      }
    }

    void Unlink(nsINode& aNode) override {
      nsINode::nsSlots::Unlink(aNode);
      if (mExtendedSlots) {
        GetExtendedContentSlots()->UnlinkExtendedSlots(*aNode.AsContent());
      }
    }

    void SetExtendedContentSlots(nsExtendedContentSlots* aSlots, bool aOwning) {
      mExtendedSlots = reinterpret_cast<uintptr_t>(aSlots);
      if (!aOwning) {
        mExtendedSlots |= sNonOwningExtendedSlotsFlag;
      }
    }

    // OwnsExtendedSlots returns true if we have no extended slots or if we
    // have extended slots and own them.
    bool OwnsExtendedSlots() const {
      return !(mExtendedSlots & sNonOwningExtendedSlotsFlag);
    }

    nsExtendedContentSlots* GetExtendedContentSlots() const {
      return reinterpret_cast<nsExtendedContentSlots*>(
          mExtendedSlots & ~sNonOwningExtendedSlotsFlag);
    }

   private:
    static const uintptr_t sNonOwningExtendedSlotsFlag = 1u;

    uintptr_t mExtendedSlots;
  };

  // Override from nsINode
  nsContentSlots* CreateSlots() override { return new nsContentSlots(); }

  nsContentSlots* ContentSlots() {
    return static_cast<nsContentSlots*>(Slots());
  }

  const nsContentSlots* GetExistingContentSlots() const {
    return static_cast<nsContentSlots*>(GetExistingSlots());
  }

  nsContentSlots* GetExistingContentSlots() {
    return static_cast<nsContentSlots*>(GetExistingSlots());
  }

  virtual nsExtendedContentSlots* CreateExtendedSlots() {
    return new nsExtendedContentSlots();
  }

  const nsExtendedContentSlots* GetExistingExtendedContentSlots() const {
    const nsContentSlots* slots = GetExistingContentSlots();
    return slots ? slots->GetExtendedContentSlots() : nullptr;
  }

  nsExtendedContentSlots* GetExistingExtendedContentSlots() {
    nsContentSlots* slots = GetExistingContentSlots();
    return slots ? slots->GetExtendedContentSlots() : nullptr;
  }

  nsExtendedContentSlots* ExtendedContentSlots() {
    nsContentSlots* slots = ContentSlots();
    if (!slots->GetExtendedContentSlots()) {
      slots->SetExtendedContentSlots(CreateExtendedSlots(), true);
    }
    return slots->GetExtendedContentSlots();
  }

  /**
   * Hook for implementing GetID.  This is guaranteed to only be
   * called if HasID() is true.
   */
  nsAtom* DoGetID() const;

  ~nsIContent() = default;

 public:
#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
#  define MOZ_DOM_LIST
#endif

#ifdef MOZ_DOM_LIST
  /**
   * An alias for List() with default arguments. Since some debuggers can't
   * figure the default arguments easily, having an out-of-line, non-static
   * function helps quite a lot.
   */
  void Dump();

  /**
   * List the content (and anything it contains) out to the given
   * file stream. Use aIndent as the base indent during formatting.
   */
  virtual void List(FILE* out = stdout, int32_t aIndent = 0) const = 0;

  /**
   * Dump the content (and anything it contains) out to the given
   * file stream. Use aIndent as the base indent during formatting.
   */
  virtual void DumpContent(FILE* out = stdout, int32_t aIndent = 0,
                           bool aDumpAll = true) const = 0;
#endif
};

NON_VIRTUAL_ADDREF_RELEASE(nsIContent)

NS_DEFINE_STATIC_IID_ACCESSOR(nsIContent, NS_ICONTENT_IID)

#endif /* nsIContent_h___ */