/* -*- 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/. */
/* * Base class for all element classes and DocumentFragment.
*/
NS_INTERFACE_MAP_BEGIN(nsIContent)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY // Don't bother to QI to cycle collection, because our CC impl is // not doing anything anyway.
NS_INTERFACE_MAP_ENTRY(nsIContent)
NS_INTERFACE_MAP_ENTRY(nsINode)
NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference, new nsNodeSupportsWeakRefTearoff(this)) // DOM bindings depend on the identity pointer being the // same as nsINode (which nsIContent inherits).
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
/** * Additional check for open flag SET: * If slotable’s parent’s shadow root's mode is not "open", * then return null.
*/ if (GetParent()->GetShadowRoot()->IsClosed()) { return nullptr;
}
return slot;
}
nsIContent::IMEState nsIContent::GetDesiredIMEState() { if (!IsEditable() || !IsInComposedDoc()) { // Check for the special case where we're dealing with elements which don't // have the editable flag set, but are readwrite (such as text controls). if (!IsElement() ||
!AsElement()->State().HasState(ElementState::READWRITE)) { return IMEState(IMEEnabled::Disabled);
}
} // NOTE: The content for independent editors (e.g., input[type=text], // textarea) must override this method, so, we don't need to worry about // that here.
nsIContent* editableAncestor = GetEditingHost();
// This is in another editable content, use the result of it. if (editableAncestor && editableAncestor != this) { return editableAncestor->GetDesiredIMEState();
}
Document* doc = GetComposedDoc(); if (!doc) { return IMEState(IMEEnabled::Disabled);
}
nsPresContext* pc = doc->GetPresContext(); if (!pc) { return IMEState(IMEEnabled::Disabled);
}
HTMLEditor* htmlEditor = nsContentUtils::GetHTMLEditor(pc); if (!htmlEditor) { return IMEState(IMEEnabled::Disabled);
}
IMEState state;
htmlEditor->GetPreferredIMEState(&state); return state;
}
dom::Element* nsIContent::GetEditingHost() { // If this isn't editable, return nullptr. if (!IsEditable()) { return nullptr;
}
Document* doc = GetComposedDoc(); if (!doc) { return nullptr;
}
// If this is in designMode, we should return <body> if (IsInDesignMode() && !IsInShadowTree()) { // FIXME: There may be no <body>. In such case and aLimitInBodyElement is // "No", we should use root element instead. return doc->GetBodyElement();
}
nsresult nsIContent::LookupNamespaceURIInternal( const nsAString& aNamespacePrefix, nsAString& aNamespaceURI) const { if (aNamespacePrefix.EqualsLiteral("xml")) { // Special-case for xml prefix
aNamespaceURI.AssignLiteral("http://www.w3.org/XML/1998/namespace"); return NS_OK;
}
if (aNamespacePrefix.EqualsLiteral("xmlns")) { // Special-case for xmlns prefix
aNamespaceURI.AssignLiteral("http://www.w3.org/2000/xmlns/"); return NS_OK;
}
RefPtr<nsAtom> name; if (!aNamespacePrefix.IsEmpty()) {
name = NS_Atomize(aNamespacePrefix);
NS_ENSURE_TRUE(name, NS_ERROR_OUT_OF_MEMORY);
} else {
name = nsGkAtoms::xmlns;
} // Trace up the content parent chain looking for the namespace // declaration that declares aNamespacePrefix. for (Element* element = GetAsElementOrParentElement(); element;
element = element->GetParentElement()) { if (element->GetAttr(kNameSpaceID_XMLNS, name, aNamespaceURI)) { return NS_OK;
}
} return NS_ERROR_FAILURE;
}
nsAtom* nsIContent::GetLang() const { for (const Element* element = GetAsElementOrParentElement(); element;
element = element->GetParentElement()) { if (!element->GetAttrCount()) { continue;
}
// xml:lang has precedence over lang on HTML elements (see // XHTML1 section C.7). const nsAttrValue* attr =
element->GetParsedAttr(nsGkAtoms::lang, kNameSpaceID_XML); if (!attr && element->SupportsLangAttr()) {
attr = element->GetParsedAttr(nsGkAtoms::lang);
} if (attr) {
MOZ_ASSERT(attr->Type() == nsAttrValue::eAtom);
MOZ_ASSERT(attr->GetAtomValue()); return attr->GetAtomValue();
}
}
return nullptr;
}
nsIURI* nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const { if (SVGUseElement* use = GetContainingSVGUseShadowHost()) { if (URLExtraData* data = use->GetContentURLData()) { return data->BaseURI();
}
}
nsIURI* nsIContent::GetBaseURIForStyleAttr() const { if (SVGUseElement* use = GetContainingSVGUseShadowHost()) { if (URLExtraData* data = use->GetContentURLData()) { return data->BaseURI();
}
} // This also ignores the case that SVG inside XBL binding. // But it is probably fine. return OwnerDoc()->GetDocBaseURI();
}
already_AddRefed<URLExtraData> nsIContent::GetURLDataForStyleAttr(
nsIPrincipal* aSubjectPrincipal) const { if (SVGUseElement* use = GetContainingSVGUseShadowHost()) { if (URLExtraData* data = use->GetContentURLData()) { return do_AddRef(data);
}
} auto* doc = OwnerDoc(); if (aSubjectPrincipal && aSubjectPrincipal != NodePrincipal()) {
nsCOMPtr<nsIReferrerInfo> referrerInfo =
doc->ReferrerInfoForInternalCSSAndSVGResources(); // TODO: Cache this? return MakeAndAddRef<URLExtraData>(doc->GetDocBaseURI(), referrerInfo,
aSubjectPrincipal);
} return do_AddRef(doc->DefaultStyleAttrURLData());
}
bool nsIContent::InclusiveDescendantMayNeedSpellchecking(HTMLEditor* aEditor) { // Return true if the node may have elements as children, since those or their // descendants may have spellcheck attributes. return HasFlag(NODE_MAY_HAVE_ELEMENT_CHILDREN) ||
mozInlineSpellChecker::ShouldSpellCheckNode(aEditor, this);
}
// If the wrapper is known-live, the list can't be part of a garbage cycle.
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsAttrChildContentList) return tmp->HasKnownLiveWrapper();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
static_assert(sizeof(nsINode::nsSlots) <= MaxDOMSlotSizeAllowed, "DOM slots cannot be grown without consideration");
static_assert(sizeof(FragmentOrElement::nsDOMSlots) <= MaxDOMSlotSizeAllowed, "DOM slots cannot be grown without consideration");
size_t nsIContent::nsExtendedContentSlots::SizeOfExcludingThis(
MallocSizeOf aMallocSizeOf) const { // For now, nothing to measure here. We don't actually own any of our // members. return 0;
}
size_t FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(
MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
nsExtendedContentSlots* extendedSlots = GetExtendedContentSlots(); if (extendedSlots) { if (OwnsExtendedSlots()) {
n += aMallocSizeOf(extendedSlots);
}
n += extendedSlots->SizeOfExcludingThis(aMallocSizeOf);
}
if (mAttributeMap) {
n += mAttributeMap->SizeOfIncludingThis(aMallocSizeOf);
}
if (mChildrenList) {
n += mChildrenList->SizeOfIncludingThis(aMallocSizeOf);
}
// Measurement of the following members may be added later if DMD finds it is // worthwhile: // - Superclass members (nsINode::nsSlots) // - mStyle // - mClassList
// The following member are not measured: // - mControllers: because it is non-owning return n;
}
for (auto& tableEntry : mAttrElementsMap) { auto& [explicitlySetElements, cachedAttrElements] =
*tableEntry.GetModifiableData(); if (cachedAttrElements) {
ImplCycleCollectionTraverse(aCb, *cachedAttrElements, "cached attribute elements entry", 0);
}
}
if (mCustomElementData) {
mCustomElementData->Traverse(aCb);
} if (mAnimations) {
mAnimations->Traverse(aCb);
} if (mRadioGroupContainer) {
RadioGroupContainer::Traverse(mRadioGroupContainer.get(), aCb);
}
}
size_t FragmentOrElement::nsExtendedDOMSlots::SizeOfExcludingThis(
MallocSizeOf aMallocSizeOf) const {
size_t n =
nsIContent::nsExtendedContentSlots::SizeOfExcludingThis(aMallocSizeOf);
// We own mSMILOverrideStyle but there seems to be no memory reporting on CSS // declarations? At least report the memory the declaration takes up // directly. if (mSMILOverrideStyle) {
n += aMallocSizeOf(mSMILOverrideStyle);
}
// We don't really own mSMILOverrideStyleDeclaration. mSMILOverrideStyle owns // it.
// We don't seem to have memory reporting for nsXULControllers. At least // report the memory it's using directly. if (mControllers) {
n += aMallocSizeOf(mControllers);
}
if (mLabelsList) {
n += mLabelsList->SizeOfIncludingThis(aMallocSizeOf);
}
// mShadowRoot should be handled during normal DOM tree memory reporting, just // like kids, siblings, etc.
if (mCustomElementData) {
n += mCustomElementData->SizeOfIncludingThis(aMallocSizeOf);
}
if (mRadioGroupContainer) {
n += mRadioGroupContainer->SizeOfIncludingThis(aMallocSizeOf);
}
FragmentOrElement::~FragmentOrElement() {
MOZ_ASSERT(!IsInUncomposedDoc(), "Please remove this from the document properly"); if (GetParent()) {
NS_RELEASE(mParent);
}
}
void nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) { // FIXME! Document how this event retargeting works, Bug 329124.
aVisitor.mCanHandle = true;
aVisitor.mMayHaveListenerManager = HasListenerManager();
if (IsInShadowTree()) {
aVisitor.mItemInShadowTree = true;
}
// Don't propagate mouseover and mouseout events when mouse is moving // inside chrome access only content. constbool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
aVisitor.mRootOfClosedTree = isAnonForEvents; if ((aVisitor.mEvent->mMessage == eMouseOver ||
aVisitor.mEvent->mMessage == eMouseOut ||
aVisitor.mEvent->mMessage == ePointerOver ||
aVisitor.mEvent->mMessage == ePointerOut) && // Check if we should stop event propagation when event has just been // dispatched or when we're about to propagate from // chrome access only subtree or if we are about to propagate out of // a shadow root to a shadow root host.
((this == aVisitor.mEvent->mOriginalTarget && !ChromeOnlyAccess()) ||
isAnonForEvents)) {
nsIContent* relatedTarget = nsIContent::FromEventTargetOrNull(
aVisitor.mEvent->AsMouseEvent()->mRelatedTarget); if (relatedTarget && relatedTarget->OwnerDoc() == OwnerDoc()) { // If current target is anonymous for events or we know that related // target is descendant of an element which is anonymous for events, // we may want to stop event propagation. // If this is the original target, aVisitor.mRelatedTargetIsInAnon // must be updated. if (isAnonForEvents || aVisitor.mRelatedTargetIsInAnon ||
(aVisitor.mEvent->mOriginalTarget == this &&
(aVisitor.mRelatedTargetIsInAnon =
relatedTarget->ChromeOnlyAccessForEvents()))) {
nsINode* anonOwner = FindChromeAccessOnlySubtreeOwnerForEvents(this); if (anonOwner) {
nsINode* anonOwnerRelated =
FindChromeAccessOnlySubtreeOwnerForEvents(relatedTarget); if (anonOwnerRelated) { // Note, anonOwnerRelated may still be inside some other // native anonymous subtree. The case where anonOwner is still // inside native anonymous subtree will be handled when event // propagates up in the DOM tree. while (anonOwner != anonOwnerRelated &&
anonOwnerRelated->ChromeOnlyAccessForEvents()) {
anonOwnerRelated =
FindChromeAccessOnlySubtreeOwnerForEvents(anonOwnerRelated);
} if (anonOwner == anonOwnerRelated) { #ifdef DEBUG_smaug
nsIContent* originalTarget = nsIContent::FromEventTargetOrNull(
aVisitor.mEvent->mOriginalTarget);
nsAutoString ot, ct, rt; if (originalTarget) {
originalTarget->NodeInfo()->NameAtom()->ToString(ot);
}
NodeInfo()->NameAtom()->ToString(ct);
relatedTarget->NodeInfo()->NameAtom()->ToString(rt);
printf( "Stopping %s propagation:" "\n\toriginalTarget=%s \n\tcurrentTarget=%s %s" "\n\trelatedTarget=%s %s \n%s",
(aVisitor.mEvent->mMessage == eMouseOver) ? "mouseover"
: "mouseout",
NS_ConvertUTF16toUTF8(ot).get(),
NS_ConvertUTF16toUTF8(ct).get(),
isAnonForEvents
? "(is native anonymous)"
: (ChromeOnlyAccess() ? "(is in native anonymous subtree)"
: ""),
NS_ConvertUTF16toUTF8(rt).get(),
relatedTarget->ChromeOnlyAccess()
? "(is in native anonymous subtree)"
: "",
(originalTarget &&
relatedTarget->FindFirstNonChromeOnlyAccessContent() ==
originalTarget->FindFirstNonChromeOnlyAccessContent())
? ""
: "Wrong event propagation!?!\n"); #endif
aVisitor.SetParentTarget(nullptr, false); // Event should not propagate to non-anon content.
aVisitor.mCanHandle = isAnonForEvents; return;
}
}
}
}
}
}
// Event parent is the assigned slot, if node is assigned, or node's parent // otherwise.
HTMLSlotElement* slot = GetAssignedSlot();
nsIContent* parent = slot ? slot : GetParent();
// Event may need to be retargeted if this is the root of a native anonymous // content subtree. if (isAnonForEvents) { #ifdef DEBUG // If a DOM event is explicitly dispatched using node.dispatchEvent(), then // all the events are allowed even in the native anonymous content..
nsIContent* t =
nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
NS_ASSERTION(!t || !t->ChromeOnlyAccessForEvents() ||
aVisitor.mEvent->mClass != eMutationEventClass ||
aVisitor.mDOMEvent, "Mutation event dispatched in native anonymous content!?!"); #endif if (aVisitor.mEvent->mClass == eTransitionEventClass ||
aVisitor.mEvent->mClass == eAnimationEventClass) { // Event should not propagate to non-anon content.
aVisitor.SetParentTarget(nullptr, false); return;
}
aVisitor.mEventTargetAtParent = parent;
} elseif (parent && aVisitor.mOriginalTargetIsInAnon) {
nsIContent* content =
nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mTarget); if (content &&
content->GetClosestNativeAnonymousSubtreeRootParentOrHost() == parent) {
aVisitor.mEventTargetAtParent = parent;
}
}
if (!ChromeOnlyAccessForEvents() &&
!aVisitor.mRelatedTargetRetargetedInCurrentScope) { // We don't support Shadow DOM in native anonymous content yet.
aVisitor.mRelatedTargetRetargetedInCurrentScope = true; if (aVisitor.mEvent->mOriginalRelatedTarget) { // https://dom.spec.whatwg.org/#concept-event-dispatch // Step 3. // "Let relatedTarget be the result of retargeting event's relatedTarget // against target if event's relatedTarget is non-null, and null // otherwise." // // This is a bit complicated because the event might be from native // anonymous content, but we need to deal with non-native anonymous // content there. bool initialTarget = this == aVisitor.mEvent->mOriginalTarget;
nsINode* originalTargetAsNode = nullptr; // Use of mOriginalTargetIsInAnon is an optimization here. if (!initialTarget && aVisitor.mOriginalTargetIsInAnon) {
originalTargetAsNode = FindChromeAccessOnlySubtreeOwnerForEvents(
aVisitor.mEvent->mOriginalTarget);
initialTarget = originalTargetAsNode == this;
} if (initialTarget) {
nsINode* relatedTargetAsNode =
FindChromeAccessOnlySubtreeOwnerForEvents(
aVisitor.mEvent->mOriginalRelatedTarget); if (!originalTargetAsNode) {
originalTargetAsNode =
nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
}
if (relatedTargetAsNode && originalTargetAsNode) {
nsINode* retargetedRelatedTarget = nsContentUtils::Retarget(
relatedTargetAsNode, originalTargetAsNode); if (originalTargetAsNode == retargetedRelatedTarget &&
retargetedRelatedTarget != relatedTargetAsNode) { // Step 4. // "If target is relatedTarget and target is not event's // relatedTarget, then return true."
aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting(); // Old code relies on mTarget to point to the first element which // was not added to the event target chain because of mCanHandle // being false, but in Shadow DOM case mTarget really should // point to a node in Shadow DOM.
aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope; return;
}
// Part of step 5. Retargeting target has happened already higher // up in this method. // "Append to an event path with event, target, targetOverride, // relatedTarget, and false."
aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
}
} elseif (nsINode* relatedTargetAsNode =
FindChromeAccessOnlySubtreeOwnerForEvents(
aVisitor.mEvent->mOriginalRelatedTarget)) { // Step 11.3. // "Let relatedTarget be the result of retargeting event's // relatedTarget against parent if event's relatedTarget is non-null, // and null otherwise.".
nsINode* retargetedRelatedTarget =
nsContentUtils::Retarget(relatedTargetAsNode, this);
nsINode* targetInKnownToBeHandledScope =
FindChromeAccessOnlySubtreeOwnerForEvents(
aVisitor.mTargetInKnownToBeHandledScope); // If aVisitor.mTargetInKnownToBeHandledScope wasn't nsINode, // targetInKnownToBeHandledScope will be null. This may happen when // dispatching event to Window object in a content page and // propagating the event to a chrome Element. if (targetInKnownToBeHandledScope &&
IsShadowIncludingInclusiveDescendantOf(
targetInKnownToBeHandledScope->SubtreeRoot())) { // Part of step 11.4. // "If target's root is a shadow-including inclusive ancestor of // parent, then" // "...Append to an event path with event, parent, null, // relatedTarget, " and slot-in-closed-tree."
aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget;
} elseif (this == retargetedRelatedTarget) { // Step 11.5 // "Otherwise, if parent and relatedTarget are identical, then set // parent to null."
aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting(); // Old code relies on mTarget to point to the first element which // was not added to the event target chain because of mCanHandle // being false, but in Shadow DOM case mTarget really should // point to a node in Shadow DOM.
aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope; return;
} elseif (targetInKnownToBeHandledScope) { // Note, if targetInKnownToBeHandledScope is null, // mTargetInKnownToBeHandledScope could be Window object in content // page and we're in chrome document in the same process.
if (autofocus) { if (IsFocusable(el)) { // Found an autofocus candidate. return el;
}
} elseif (!potentialFocus) { if (Focusable focusable = IsFocusable(el)) { if (IsHTMLElement(nsGkAtoms::dialog)) { if (focusable.mTabIndex >= 0) { // If focusTarget is a dialog element and descendant is sequentially // focusable, then set focusableArea to descendant.
potentialFocus = el;
}
} else { // This element could be the one if we can't find an // autofocus candidate which has the precedence.
potentialFocus = el;
}
}
}
if (!autofocus && potentialFocus) { // Nothing else to do, we are not looking for more focusable elements // here. continue;
}
if (auto* shadow = el->GetShadowRoot()) { if (shadow->DelegatesFocus()) { if (Element* delegatedFocus = shadow->GetFocusDelegate(aFlags)) { if (autofocus) { // This element has autofocus and we found an focus delegates // in its descendants, so use the focus delegates return delegatedFocus;
} if (!potentialFocus) {
potentialFocus = delegatedFocus;
}
}
}
}
}
return potentialFocus;
}
Focusable nsIContent::IsFocusableWithoutStyle(IsFocusableFlags) { // Default, not tabbable return {};
}
void FragmentOrElement::DestroyContent() { // Drop any servo data. We do this before the RemovedFromDocument call below // so that it doesn't need to try to keep the style state sane when shuffling // around the flattened tree. // // TODO(emilio): I suspect this can be asserted against instead, with a bit of // effort to avoid calling Document::Destroy with a shell... if (IsElement()) {
AsElement()->ClearServoData();
}
for (nsIContent* child = GetFirstChild(); child;
child = child->GetNextSibling()) {
child->DestroyContent();
MOZ_ASSERT(child->GetParent() == this, "Mutating the tree during XBL destructors is evil");
}
MOZ_ASSERT(oldChildCount == GetChildCount(), "Mutating the tree during XBL destructors is evil");
if (ShadowRoot* shadowRoot = GetShadowRoot()) {
shadowRoot->DestroyContent();
}
}
class ContentUnbinder : public Runnable { public:
ContentUnbinder() : Runnable("ContentUnbinder") { mLast = this; }
~ContentUnbinder() { Run(); }
void UnbindSubtree(nsIContent* aNode) { if (aNode->NodeType() != nsINode::ELEMENT_NODE &&
aNode->NodeType() != nsINode::DOCUMENT_FRAGMENT_NODE) { return;
}
FragmentOrElement* container = static_cast<FragmentOrElement*>(aNode); if (container->HasChildren()) { // Invalidate cached array of child nodes
container->InvalidateChildNodes();
while (container->HasChildren()) { // Hold a strong ref to the node when we remove it, because we may be // the last reference to it. We need to call DisconnectChild() // before calling UnbindFromTree, since this last can notify various // observers and they should really see consistent // tree state. // If this code changes, change the corresponding code in // FragmentOrElement's and Document's unlink impls.
nsCOMPtr<nsIContent> child = container->GetLastChild();
container->DisconnectChild(child);
UnbindSubtree(child);
child->UnbindFromTree();
}
}
}
NS_IMETHOD Run() override {
nsAutoScriptBlocker scriptBlocker;
uint32_t len = mSubtreeRoots.Length(); if (len) { for (uint32_t i = 0; i < len; ++i) {
UnbindSubtree(mSubtreeRoots[i]);
}
mSubtreeRoots.Clear();
}
nsCycleCollector_dispatchDeferredDeletion(); if (this == sContentUnbinder) {
sContentUnbinder = nullptr; if (mNext) {
RefPtr<ContentUnbinder> next;
next.swap(mNext);
sContentUnbinder = next;
next->mLast = mLast;
mLast = nullptr;
NS_DispatchToCurrentThreadQueue(next.forget(),
EventQueuePriority::Idle);
}
} return NS_OK;
}
// Note, _INHERITED macro isn't used here since nsINode implementations are // rather special.
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FragmentOrElement)
// We purposefully don't UNLINK_BEGIN_INHERITED here.
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
nsIContent::Unlink(tmp);
// Unlink child content (and unbind our subtree). if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) { // Don't allow script to run while we're unbinding everything.
nsAutoScriptBlocker scriptBlocker; while (tmp->HasChildren()) { // Hold a strong ref to the node when we remove it, because we may be // the last reference to it. // If this code changes, change the corresponding code in Document's // unlink impl and ContentUnbinder::UnbindSubtree.
nsCOMPtr<nsIContent> child = tmp->GetLastChild();
tmp->DisconnectChild(child);
child->UnbindFromTree();
}
} elseif (!tmp->GetParent() && tmp->HasChildren()) {
ContentUnbinder::Append(tmp);
} /* else { The subtree root will end up to a ContentUnbinder, and that will unbind the child nodes.
} */
// Marked to be in-CC-generation or if the document is an svg image that's // being kept alive by the image cache. (Note that an svg image's internal // SVG document will receive an OnPageHide() call when it gets purged from // the image cache; hence, we use IsVisible() as a hint that the document is // actively being kept alive by the cache.) return nsCCUncollectableMarker::InGeneration(aDoc->GetMarkedCCGeneration()) ||
(nsCCUncollectableMarker::sGeneration && aDoc->IsBeingUsedAsImage() &&
aDoc->IsVisible());
}
// static bool FragmentOrElement::CanSkipInCC(nsINode* aNode) { // Don't try to optimize anything during shutdown. if (nsCCUncollectableMarker::sGeneration == 0) { returnfalse;
}
// Subtree has been traversed already. if (root->CCMarkedRoot()) { return root->InCCBlackTree() && !NeedsScriptTraverse(aNode);
}
if (!gCCBlackMarkedNodes) {
gCCBlackMarkedNodes = new nsTHashSet<nsINode*>(1020);
}
// nodesToUnpurple contains nodes which will be removed // from the purple buffer if the DOM tree is known-live.
AutoTArray<nsIContent*, 1020> nodesToUnpurple; // grayNodes need script traverse, so they aren't removed from // the purple buffer, but are marked to be in known-live subtree so that // traverse is faster.
AutoTArray<nsINode*, 1020> grayNodes;
// Traverse the subtree and check if we could know without CC // that it is known-live. // Note, this traverse is non-virtual and inline, so it should be a lot faster // than CC's generic traverse. for (nsIContent* node = root->GetFirstChild(); node;
node = node->GetNextNode(root)) {
foundLiveWrapper = foundLiveWrapper || node->HasKnownLiveWrapper(); if (foundLiveWrapper && currentDoc) { // If we can mark the whole document known-live, no need to optimize // so much, since when the next purple node in the document will be // handled, it is fast to check that currentDoc is in CCGeneration. break;
} if (NeedsScriptTraverse(node)) { // Gray nodes need real CC traverse.
grayNodes.AppendElement(node);
} elseif (node->IsPurple()) {
nodesToUnpurple.AppendElement(node);
}
}
if (currentDoc) { // Special case documents. If we know the document is known-live, // we can mark the document to be in CCGeneration.
currentDoc->MarkUncollectableForCCGeneration(
nsCCUncollectableMarker::sGeneration);
} else { for (uint32_t i = 0; i < grayNodes.Length(); ++i) {
nsINode* node = grayNodes[i];
node->SetInCCBlackTree(true);
gCCBlackMarkedNodes->Insert(node);
}
}
// Subtree is known-live, we can remove non-gray purple nodes from // purple buffer. for (uint32_t i = 0; i < nodesToUnpurple.Length(); ++i) {
nsIContent* purple = nodesToUnpurple[i]; // Can't remove currently handled purple node. if (purple != aNode) {
purple->RemovePurple();
}
} return !NeedsScriptTraverse(aNode);
}
void ClearCycleCollectorCleanupData() { if (gPurpleRoots) {
uint32_t len = gPurpleRoots->Length(); for (uint32_t i = 0; i < len; ++i) {
nsINode* n = gPurpleRoots->ElementAt(i);
n->SetIsPurpleRoot(false);
} delete gPurpleRoots;
gPurpleRoots = nullptr;
} if (gNodesToUnbind) {
uint32_t len = gNodesToUnbind->Length(); for (uint32_t i = 0; i < len; ++i) {
nsIContent* c = gNodesToUnbind->ElementAt(i);
c->SetIsPurpleRoot(false);
ContentUnbinder::Append(c);
} delete gNodesToUnbind;
gNodesToUnbind = nullptr;
}
}
staticbool ShouldClearPurple(nsIContent* aContent) {
MOZ_ASSERT(aContent); if (aContent->IsPurple()) { returntrue;
}
JSObject* o = GetJSObjectChild(aContent); if (o && JS::ObjectIsMarkedGray(o)) { returntrue;
}
if (aContent->HasListenerManager()) { returntrue;
}
return aContent->HasProperties();
}
// If aNode is not optimizable, but is an element // with a frame in a document which has currently active presshell, // we can act as if it was optimizable. When the primary frame dies, aNode // will end up to the purple buffer because of the refcount change. bool NodeHasActiveFrame(Document* aCurrentDoc, nsINode* aNode) { return aCurrentDoc->GetPresShell() && aNode->IsElement() &&
aNode->AsElement()->GetPrimaryFrame();
}
// CanSkip checks if aNode is known-live, and if it is, returns true. If aNode // is in a known-live DOM tree, CanSkip may also remove other objects from // purple buffer and unmark event listeners and user data. If the root of the // DOM tree is a document, less optimizations are done since checking the // liveness of the current document is usually fast and we don't want slow down // such common cases. bool FragmentOrElement::CanSkip(nsINode* aNode, bool aRemovingAllowed) { // Don't try to optimize anything during shutdown. if (nsCCUncollectableMarker::sGeneration == 0) { returnfalse;
}
// Subtree has been traversed already, and aNode has // been handled in a way that doesn't require revisiting it. if (root->IsPurpleRoot()) { returnfalse;
}
// nodesToClear contains nodes which are either purple or // gray.
AutoTArray<nsIContent*, 1020> nodesToClear;
bool foundLiveWrapper = root->HasKnownLiveWrapper(); bool domOnlyCycle = false; if (root != currentDoc) {
currentDoc = nullptr; if (!foundLiveWrapper) {
domOnlyCycle = static_cast<nsIContent*>(root)->OwnedOnlyByTheDOMTree();
} if (ShouldClearPurple(static_cast<nsIContent*>(root))) {
nodesToClear.AppendElement(static_cast<nsIContent*>(root));
}
}
// Traverse the subtree and check if we could know without CC // that it is known-live. // Note, this traverse is non-virtual and inline, so it should be a lot faster // than CC's generic traverse. for (nsIContent* node = root->GetFirstChild(); node;
node = node->GetNextNode(root)) {
foundLiveWrapper = foundLiveWrapper || node->HasKnownLiveWrapper(); if (foundLiveWrapper) {
domOnlyCycle = false; if (currentDoc) { // If we can mark the whole document live, no need to optimize // so much, since when the next purple node in the document will be // handled, it is fast to check that the currentDoc is in CCGeneration. break;
} // No need to put stuff to the nodesToClear array, if we can clear it // already here. if (node->IsPurple() && (node != aNode || aRemovingAllowed)) {
node->RemovePurple();
}
MarkNodeChildren(node);
} else {
domOnlyCycle = domOnlyCycle && node->OwnedOnlyByTheDOMTree(); if (ShouldClearPurple(node)) { // Collect interesting nodes which we can clear if we find that // they are kept alive in a known-live tree or are in a DOM-only cycle.
nodesToClear.AppendElement(node);
}
}
}
if (!currentDoc || !foundLiveWrapper) {
root->SetIsPurpleRoot(true); if (domOnlyCycle) { if (!gNodesToUnbind) {
gNodesToUnbind = new AutoTArray<nsIContent*, 1020>();
}
gNodesToUnbind->AppendElement(static_cast<nsIContent*>(root)); for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
nsIContent* n = nodesToClear[i]; if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
n->RemovePurple();
}
} returntrue;
} else { if (!gPurpleRoots) {
gPurpleRoots = new AutoTArray<nsINode*, 1020>();
}
gPurpleRoots->AppendElement(root);
}
}
if (!foundLiveWrapper) { returnfalse;
}
if (currentDoc) { // Special case documents. If we know the document is known-live, // we can mark the document to be in CCGeneration.
currentDoc->MarkUncollectableForCCGeneration(
nsCCUncollectableMarker::sGeneration);
MarkNodeChildren(currentDoc);
}
// Subtree is known-live, so we can remove purple nodes from // purple buffer and mark stuff that to be certainly alive. for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
nsIContent* n = nodesToClear[i];
MarkNodeChildren(n); // Can't remove currently handled purple node, // unless aRemovingAllowed is true. if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
n->RemovePurple();
}
} returntrue;
}
bool FragmentOrElement::CanSkipThis(nsINode* aNode) { if (nsCCUncollectableMarker::sGeneration == 0) { returnfalse;
} if (aNode->HasKnownLiveWrapper()) { returntrue;
}
Document* c = aNode->GetComposedDoc(); return ((c && IsCertainlyAliveNode(aNode, c)) || aNode->InCCBlackTree()) &&
!NeedsScriptTraverse(aNode);
}
// We purposefully don't TRAVERSE_BEGIN_INHERITED here. All the bits // we should traverse should be added here or in nsINode::Traverse.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement) if (MOZ_UNLIKELY(cb.WantDebugInfo())) { char name[512];
uint32_t nsid = tmp->GetNameSpaceID();
nsAtomCString localName(tmp->NodeInfo()->NameAtom());
nsAutoCString uri; if (tmp->OwnerDoc()->GetDocumentURI()) {
uri = tmp->OwnerDoc()->GetDocumentURI()->GetSpecOrDefault();
}
uint32_t FragmentOrElement::TextLength() const { // We can remove this assertion if it turns out to be useful to be able // to depend on this returning 0
MOZ_ASSERT_UNREACHABLE("called FragmentOrElement::TextLength");
static mozilla::BitBloomFilter<12, nsAtom> sFilter; staticbool sInitialized = false; if (!sInitialized) {
sInitialized = true; for (uint32_t i = 0; i < std::size(voidElements); ++i) {
sFilter.add(voidElements[i]);
}
}
if (sFilter.mightContain(aTag)) { for (uint32_t i = 0; i < std::size(voidElements); ++i) { if (aTag == voidElements[i]) { returntrue;
}
}
} returnfalse;
}
nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder(); if (!docEncoder) {
docEncoder = do_createDocumentEncoder(
PromiseFlatCString(NS_ConvertUTF16toUTF8(contentType)).get());
} if (!docEncoder) { // This could be some type for which we create a synthetic document. Try // again as XML
contentType.AssignLiteral("application/xml");
docEncoder = do_createDocumentEncoder("application/xml"); // Don't try to cache the encoder since it would point to a different // contentType once it has been reinitialized.
tryToCacheEncoder = false;
}
NS_ENSURE_TRUE_VOID(docEncoder);
uint32_t flags = nsIDocumentEncoder::OutputEncodeBasicEntities | // Output DOM-standard newlines
nsIDocumentEncoder::OutputLFLineBreak | // Don't do linebreaking that's not present in // the source
nsIDocumentEncoder::OutputRaw | // Only check for mozdirty when necessary (bug 599983)
nsIDocumentEncoder::OutputIgnoreMozDirty;
if (aIncludeSelf) {
docEncoder->SetNode(this);
} else {
docEncoder->SetContainerNode(this);
}
rv = docEncoder->EncodeToString(aMarkup);
MOZ_ASSERT(NS_SUCCEEDED(rv)); if (tryToCacheEncoder) {
doc->SetCachedEncoder(docEncoder.forget());
}
}
staticbool ContainsMarkup(const nsAString& aStr) { // Note: we can't use FindCharInSet because null is one of the characters we // want to search for. const char16_t* start = aStr.BeginReading(); const char16_t* end = aStr.EndReading();
while (start != end) {
char16_t c = *start; if (c == char16_t('<') || c == char16_t('&') || c == char16_t('\r') ||
c == char16_t('\0')) { returntrue;
}
++start;
}
returnfalse;
}
void FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML,
ErrorResult& aError) { // Keep "this" alive should be guaranteed by the caller, and also the content // of a template element (if this is one) should never been released by from // this during this call. Therefore, using raw pointer here is safe.
FragmentOrElement* target = this; // Handle template case. if (target->IsTemplateElement()) {
DocumentFragment* frag =
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.37 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.