diff options
Diffstat (limited to 'layout/inspector/InspectorUtils.cpp')
-rw-r--r-- | layout/inspector/InspectorUtils.cpp | 807 |
1 files changed, 807 insertions, 0 deletions
diff --git a/layout/inspector/InspectorUtils.cpp b/layout/inspector/InspectorUtils.cpp new file mode 100644 index 0000000000..f6aed279c1 --- /dev/null +++ b/layout/inspector/InspectorUtils.cpp @@ -0,0 +1,807 @@ +/* -*- 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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/EventStates.h" + +#include "inLayoutUtils.h" + +#include "gfxTextRun.h" +#include "nsArray.h" +#include "nsString.h" +#include "nsIContentInlines.h" +#include "nsIScrollableFrame.h" +#include "mozilla/dom/Document.h" +#include "ChildIterator.h" +#include "nsComputedDOMStyle.h" +#include "mozilla/EventStateManager.h" +#include "nsAtom.h" +#include "nsPresContext.h" +#include "nsRange.h" +#include "mozilla/PresShell.h" +#include "mozilla/PresShellInlines.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/dom/CharacterData.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/CSSStyleRule.h" +#include "mozilla/dom/InspectorUtilsBinding.h" +#include "mozilla/dom/LinkStyle.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsCSSProps.h" +#include "nsCSSValue.h" +#include "nsColor.h" +#include "mozilla/ServoStyleSet.h" +#include "nsLayoutUtils.h" +#include "nsStyleUtil.h" +#include "nsQueryObject.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/ServoStyleRuleMap.h" +#include "mozilla/ServoCSSParser.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/dom/InspectorUtils.h" +#include "mozilla/dom/InspectorFontFace.h" +#include "mozilla/gfx/Matrix.h" + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::dom; + +namespace mozilla { +namespace dom { + +/* static */ +void InspectorUtils::GetAllStyleSheets(GlobalObject& aGlobalObject, + Document& aDocument, bool aDocumentOnly, + nsTArray<RefPtr<StyleSheet>>& aResult) { + // Get the agent, then user and finally xbl sheets in the style set. + PresShell* presShell = aDocument.GetPresShell(); + + if (presShell) { + ServoStyleSet* styleSet = presShell->StyleSet(); + + if (!aDocumentOnly) { + const StyleOrigin kOrigins[] = {StyleOrigin::UserAgent, + StyleOrigin::User}; + for (const auto origin : kOrigins) { + for (size_t i = 0, count = styleSet->SheetCount(origin); i < count; + i++) { + aResult.AppendElement(styleSet->SheetAt(origin, i)); + } + } + } + + AutoTArray<StyleSheet*, 32> nonDocumentSheets; + styleSet->AppendAllNonDocumentAuthorSheets(nonDocumentSheets); + + // The non-document stylesheet array can't have duplicates right now, but it + // could once we include adopted stylesheets. + nsTHashtable<nsPtrHashKey<StyleSheet>> sheetSet; + for (StyleSheet* sheet : nonDocumentSheets) { + if (sheetSet.EnsureInserted(sheet)) { + aResult.AppendElement(sheet); + } + } + } + + // Get the document sheets. + for (size_t i = 0; i < aDocument.SheetCount(); i++) { + aResult.AppendElement(aDocument.SheetAt(i)); + } + + // FIXME(emilio, bug 1617948): This doesn't deal with adopted stylesheets, and + // it should. It should also handle duplicates correctly when it does, see + // above. +} + +bool InspectorUtils::IsIgnorableWhitespace(CharacterData& aDataNode) { + if (!aDataNode.TextIsOnlyWhitespace()) { + return false; + } + + // Okay. We have only white space. Let's check the white-space + // property now and make sure that this isn't preformatted text... + if (nsIFrame* frame = aDataNode.GetPrimaryFrame()) { + return !frame->StyleText()->WhiteSpaceIsSignificant(); + } + + // empty inter-tag text node without frame, e.g., in between <table>\n<tr> + return true; +} + +/* static */ +nsINode* InspectorUtils::GetParentForNode(nsINode& aNode, + bool aShowingAnonymousContent) { + // First do the special cases -- document nodes and anonymous content + nsINode* parent = nullptr; + + if (aNode.IsDocument()) { + parent = inLayoutUtils::GetContainerFor(*aNode.AsDocument()); + } else if (aShowingAnonymousContent) { + if (aNode.IsContent()) { + parent = aNode.AsContent()->GetFlattenedTreeParent(); + } + } + + if (!parent) { + // Ok, just get the normal DOM parent node + return aNode.GetParentNode(); + } + + return parent; +} + +/* static */ +already_AddRefed<nsINodeList> InspectorUtils::GetChildrenForNode( + nsINode& aNode, bool aShowingAnonymousContent) { + nsCOMPtr<nsINodeList> kids; + + if (aShowingAnonymousContent) { + if (aNode.IsContent()) { + kids = aNode.AsContent()->GetChildren(nsIContent::eAllChildren); + } + } + + if (!kids) { + kids = aNode.ChildNodes(); + } + + return kids.forget(); +} + +/* static */ +void InspectorUtils::GetCSSStyleRules( + GlobalObject& aGlobalObject, Element& aElement, const nsAString& aPseudo, + bool aIncludeVisitedStyle, nsTArray<RefPtr<BindingStyleRule>>& aResult) { + RefPtr<nsAtom> pseudoElt; + if (!aPseudo.IsEmpty()) { + pseudoElt = NS_Atomize(aPseudo); + } + + RefPtr<ComputedStyle> computedStyle = + GetCleanComputedStyleForElement(&aElement, pseudoElt); + if (!computedStyle) { + // This can fail for elements that are not in the document or + // if the document they're in doesn't have a presshell. Bail out. + return; + } + + if (aIncludeVisitedStyle) { + if (ComputedStyle* styleIfVisited = computedStyle->GetStyleIfVisited()) { + computedStyle = styleIfVisited; + } + } + + Document* doc = aElement.OwnerDoc(); + PresShell* presShell = doc->GetPresShell(); + if (!presShell) { + return; + } + + nsTArray<const RawServoStyleRule*> rawRuleList; + Servo_ComputedValues_GetStyleRuleList(computedStyle, &rawRuleList); + + AutoTArray<ServoStyleRuleMap*, 1> maps; + { + ServoStyleSet* styleSet = presShell->StyleSet(); + ServoStyleRuleMap* map = styleSet->StyleRuleMap(); + maps.AppendElement(map); + } + + // Now shadow DOM stuff... + if (auto* shadow = aElement.GetShadowRoot()) { + maps.AppendElement(&shadow->ServoStyleRuleMap()); + } + + for (auto* shadow = aElement.GetContainingShadow(); shadow; + shadow = shadow->Host()->GetContainingShadow()) { + maps.AppendElement(&shadow->ServoStyleRuleMap()); + } + + // Rules from the assigned slot. + for (auto* slot = aElement.GetAssignedSlot(); slot; + slot = slot->GetAssignedSlot()) { + if (auto* shadow = slot->GetContainingShadow()) { + maps.AppendElement(&shadow->ServoStyleRuleMap()); + } + } + + // Find matching rules in the table. + for (const RawServoStyleRule* rawRule : Reversed(rawRuleList)) { + CSSStyleRule* rule = nullptr; + for (ServoStyleRuleMap* map : maps) { + rule = map->Lookup(rawRule); + if (rule) { + break; + } + } + if (rule) { + aResult.AppendElement(rule); + } else { +#ifdef DEBUG + nsAutoCString str; + fprintf(stderr, "%s\n", str.get()); + Servo_StyleRule_Debug(rawRule, &str); + MOZ_CRASH_UNSAFE_PRINTF( + "We should be able to map a raw rule to a rule: %s\n", str.get()); +#endif + } + } +} + +/* static */ +uint32_t InspectorUtils::GetRuleLine(GlobalObject& aGlobal, css::Rule& aRule) { + return aRule.GetLineNumber(); +} + +/* static */ +uint32_t InspectorUtils::GetRuleColumn(GlobalObject& aGlobal, + css::Rule& aRule) { + return aRule.GetColumnNumber(); +} + +/* static */ +uint32_t InspectorUtils::GetRelativeRuleLine(GlobalObject& aGlobal, + css::Rule& aRule) { + uint32_t lineNumber = aRule.GetLineNumber(); + + // If aRule was parsed along with its stylesheet, then it will + // have an absolute lineNumber that we need to remap to its + // containing node. But if aRule was added via CSSOM after parsing, + // then it has a sort-of relative line number already: + // Gecko gives all rules a 0 lineNumber. + // Servo gives the first line of a rule a 0 lineNumber, and then + // counts up from there. + + // The Servo behavior is arguably more correct, but harder to + // interpret for purposes of deciding whether a lineNumber is + // relative or absolute. + + // Since most of the time, inserted rules are single line and + // therefore have 0 lineNumbers in both Gecko and Servo, we use + // that to detect that a lineNumber is already relative. + + // There is one ugly edge case that we avoid: if an inserted rule + // is multi-line, then Servo will give it 0+ lineNumbers. If we + // do relative number mapping on those line numbers, we could get + // negative underflow. So we check for underflow and instead report + // a 0 lineNumber. + StyleSheet* sheet = aRule.GetStyleSheet(); + if (sheet && lineNumber != 0) { + if (auto* link = LinkStyle::FromNodeOrNull(sheet->GetOwnerNode())) { + // Check for underflow, which is one indication that we're + // trying to remap an already relative lineNumber. + uint32_t linkLineIndex0 = link->GetLineNumber() - 1; + if (linkLineIndex0 > lineNumber) { + lineNumber = 0; + } else { + lineNumber -= linkLineIndex0; + } + } + } + + return lineNumber; +} + +/* static */ +bool InspectorUtils::HasRulesModifiedByCSSOM(GlobalObject& aGlobal, + StyleSheet& aSheet) { + return aSheet.HasModifiedRulesForDevtools(); +} + +/* static */ +uint32_t InspectorUtils::GetSelectorCount(GlobalObject& aGlobal, + BindingStyleRule& aRule) { + return aRule.GetSelectorCount(); +} + +/* static */ +void InspectorUtils::GetSelectorText(GlobalObject& aGlobal, + BindingStyleRule& aRule, + uint32_t aSelectorIndex, nsACString& aText, + ErrorResult& aRv) { + aRv = aRule.GetSelectorText(aSelectorIndex, aText); +} + +/* static */ +uint64_t InspectorUtils::GetSpecificity(GlobalObject& aGlobal, + BindingStyleRule& aRule, + uint32_t aSelectorIndex, + ErrorResult& aRv) { + uint64_t s; + aRv = aRule.GetSpecificity(aSelectorIndex, &s); + return s; +} + +/* static */ +bool InspectorUtils::SelectorMatchesElement( + GlobalObject& aGlobalObject, Element& aElement, BindingStyleRule& aRule, + uint32_t aSelectorIndex, const nsAString& aPseudo, + bool aRelevantLinkVisited, ErrorResult& aRv) { + bool result = false; + aRv = aRule.SelectorMatchesElement(&aElement, aSelectorIndex, aPseudo, + aRelevantLinkVisited, &result); + return result; +} + +/* static */ +bool InspectorUtils::IsInheritedProperty(GlobalObject& aGlobalObject, + const nsACString& aPropertyName) { + return Servo_Property_IsInherited(&aPropertyName); +} + +/* static */ +void InspectorUtils::GetCSSPropertyNames(GlobalObject& aGlobalObject, + const PropertyNamesOptions& aOptions, + nsTArray<nsString>& aResult) { + CSSEnabledState enabledState = aOptions.mIncludeExperimentals + ? CSSEnabledState::IgnoreEnabledState + : CSSEnabledState::ForAllContent; + + auto appendProperty = [enabledState, &aResult](uint32_t prop) { + nsCSSPropertyID cssProp = nsCSSPropertyID(prop); + if (nsCSSProps::IsEnabled(cssProp, enabledState)) { + aResult.AppendElement( + NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(cssProp))); + } + }; + + uint32_t prop = 0; + for (; prop < eCSSProperty_COUNT_no_shorthands; ++prop) { + if (!nsCSSProps::PropHasFlags(nsCSSPropertyID(prop), + CSSPropFlags::Inaccessible)) { + appendProperty(prop); + } + } + + if (aOptions.mIncludeShorthands) { + for (; prop < eCSSProperty_COUNT; ++prop) { + appendProperty(prop); + } + } + + if (aOptions.mIncludeAliases) { + for (prop = eCSSProperty_COUNT; prop < eCSSProperty_COUNT_with_aliases; + ++prop) { + appendProperty(prop); + } + } +} + +/* static */ +void InspectorUtils::GetCSSPropertyPrefs(GlobalObject& aGlobalObject, + nsTArray<PropertyPref>& aResult) { + for (const auto* src = nsCSSProps::kPropertyPrefTable; + src->mPropID != eCSSProperty_UNKNOWN; src++) { + PropertyPref& dest = *aResult.AppendElement(); + dest.mName.Assign( + NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(src->mPropID))); + dest.mPref.AssignASCII(src->mPref); + } +} + +/* static */ +void InspectorUtils::GetSubpropertiesForCSSProperty(GlobalObject& aGlobal, + const nsACString& aProperty, + nsTArray<nsString>& aResult, + ErrorResult& aRv) { + nsCSSPropertyID propertyID = nsCSSProps::LookupProperty(aProperty); + + if (propertyID == eCSSProperty_UNKNOWN) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (propertyID == eCSSPropertyExtra_variable) { + aResult.AppendElement(NS_ConvertUTF8toUTF16(aProperty)); + return; + } + + if (!nsCSSProps::IsShorthand(propertyID)) { + nsString* name = aResult.AppendElement(); + CopyASCIItoUTF16(nsCSSProps::GetStringValue(propertyID), *name); + return; + } + + for (const nsCSSPropertyID* props = + nsCSSProps::SubpropertyEntryFor(propertyID); + *props != eCSSProperty_UNKNOWN; ++props) { + nsString* name = aResult.AppendElement(); + CopyASCIItoUTF16(nsCSSProps::GetStringValue(*props), *name); + } +} + +/* static */ +bool InspectorUtils::CssPropertyIsShorthand(GlobalObject& aGlobalObject, + const nsACString& aProperty, + ErrorResult& aRv) { + bool found; + bool isShorthand = Servo_Property_IsShorthand(&aProperty, &found); + if (!found) { + aRv.Throw(NS_ERROR_FAILURE); + } + return isShorthand; +} + +// This should match the constants in specified_value_info.rs +// +// Once we can use bitflags in consts, we can also cbindgen that and use them +// here instead. +static uint8_t ToServoCssType(InspectorPropertyType aType) { + switch (aType) { + case InspectorPropertyType::Color: + return 1; + case InspectorPropertyType::Gradient: + return 1 << 1; + case InspectorPropertyType::Timing_function: + return 1 << 2; + default: + MOZ_ASSERT_UNREACHABLE("Unknown property type?"); + return 0; + } +} + +bool InspectorUtils::Supports(GlobalObject&, const nsACString& aDeclaration, + const SupportsOptions& aOptions) { + return Servo_CSSSupports(&aDeclaration, aOptions.mUserAgent, aOptions.mChrome, + aOptions.mQuirks); +} + +bool InspectorUtils::CssPropertySupportsType(GlobalObject& aGlobalObject, + const nsACString& aProperty, + InspectorPropertyType aType, + ErrorResult& aRv) { + bool found; + bool result = + Servo_Property_SupportsType(&aProperty, ToServoCssType(aType), &found); + if (!found) { + aRv.Throw(NS_ERROR_FAILURE); + return false; + } + return result; +} + +/* static */ +void InspectorUtils::GetCSSValuesForProperty(GlobalObject& aGlobalObject, + const nsACString& aProperty, + nsTArray<nsString>& aResult, + ErrorResult& aRv) { + bool found; + Servo_Property_GetCSSValuesForProperty(&aProperty, &found, &aResult); + if (!found) { + aRv.Throw(NS_ERROR_FAILURE); + } +} + +/* static */ +void InspectorUtils::RgbToColorName(GlobalObject& aGlobalObject, uint8_t aR, + uint8_t aG, uint8_t aB, + nsAString& aColorName, ErrorResult& aRv) { + const char* color = NS_RGBToColorName(NS_RGB(aR, aG, aB)); + if (!color) { + aColorName.Truncate(); + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + aColorName.AssignASCII(color); +} + +/* static */ +void InspectorUtils::ColorToRGBA(GlobalObject&, const nsACString& aColorString, + const Document* aDoc, + Nullable<InspectorRGBATuple>& aResult) { + nscolor color = NS_RGB(0, 0, 0); + + ServoStyleSet* styleSet = nullptr; + if (aDoc) { + if (PresShell* ps = aDoc->GetPresShell()) { + styleSet = ps->StyleSet(); + } + } + + if (!ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0), aColorString, + &color)) { + aResult.SetNull(); + return; + } + + InspectorRGBATuple& tuple = aResult.SetValue(); + tuple.mR = NS_GET_R(color); + tuple.mG = NS_GET_G(color); + tuple.mB = NS_GET_B(color); + tuple.mA = nsStyleUtil::ColorComponentToFloat(NS_GET_A(color)); +} + +/* static */ +bool InspectorUtils::IsValidCSSColor(GlobalObject& aGlobalObject, + const nsACString& aColorString) { + return ServoCSSParser::IsValidCSSColor(aColorString); +} + +/* static */ +bool InspectorUtils::SetContentState(GlobalObject& aGlobalObject, + Element& aElement, uint64_t aState, + ErrorResult& aRv) { + RefPtr<EventStateManager> esm = + inLayoutUtils::GetEventStateManagerFor(aElement); + EventStates state(aState); + if (!esm || !EventStateManager::ManagesState(state)) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return false; + } + return esm->SetContentState(&aElement, state); +} + +/* static */ +bool InspectorUtils::RemoveContentState(GlobalObject& aGlobalObject, + Element& aElement, uint64_t aState, + bool aClearActiveDocument, + ErrorResult& aRv) { + RefPtr<EventStateManager> esm = + inLayoutUtils::GetEventStateManagerFor(aElement); + EventStates state(aState); + if (!esm || !EventStateManager::ManagesState(state)) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return false; + } + + bool result = esm->SetContentState(nullptr, state); + + if (aClearActiveDocument && state == NS_EVENT_STATE_ACTIVE) { + EventStateManager* activeESM = static_cast<EventStateManager*>( + EventStateManager::GetActiveEventStateManager()); + if (activeESM == esm) { + EventStateManager::ClearGlobalActiveContent(nullptr); + } + } + + return result; +} + +/* static */ +uint64_t InspectorUtils::GetContentState(GlobalObject& aGlobalObject, + Element& aElement) { + // NOTE: if this method is removed, + // please remove GetInternalValue from EventStates + return aElement.State().GetInternalValue(); +} + +/* static */ +already_AddRefed<ComputedStyle> InspectorUtils::GetCleanComputedStyleForElement( + dom::Element* aElement, nsAtom* aPseudo) { + MOZ_ASSERT(aElement); + + Document* doc = aElement->GetComposedDoc(); + if (!doc) { + return nullptr; + } + + PresShell* presShell = doc->GetPresShell(); + if (!presShell) { + return nullptr; + } + + nsPresContext* presContext = presShell->GetPresContext(); + if (!presContext) { + return nullptr; + } + + presContext->EnsureSafeToHandOutCSSRules(); + + return nsComputedDOMStyle::GetComputedStyle(aElement, aPseudo); +} + +/* static */ +void InspectorUtils::GetUsedFontFaces(GlobalObject& aGlobalObject, + nsRange& aRange, uint32_t aMaxRanges, + bool aSkipCollapsedWhitespace, + nsLayoutUtils::UsedFontFaceList& aResult, + ErrorResult& aRv) { + nsresult rv = + aRange.GetUsedFontFaces(aResult, aMaxRanges, aSkipCollapsedWhitespace); + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + } +} + +static EventStates GetStatesForPseudoClass(const nsAString& aStatePseudo) { + if (aStatePseudo.IsEmpty() || aStatePseudo[0] != u':') { + return EventStates(); + } + NS_ConvertUTF16toUTF8 statePseudo(Substring(aStatePseudo, 1)); + return EventStates(Servo_PseudoClass_GetStates(&statePseudo)); +} + +/* static */ +void InspectorUtils::GetCSSPseudoElementNames(GlobalObject& aGlobalObject, + nsTArray<nsString>& aResult) { + const auto kPseudoCount = + static_cast<size_t>(PseudoStyleType::CSSPseudoElementsEnd); + for (size_t i = 0; i < kPseudoCount; ++i) { + PseudoStyleType type = static_cast<PseudoStyleType>(i); + if (nsCSSPseudoElements::IsEnabled(type, CSSEnabledState::ForAllContent)) { + nsAtom* atom = nsCSSPseudoElements::GetPseudoAtom(type); + aResult.AppendElement(nsDependentAtomString(atom)); + } + } +} + +/* static */ +void InspectorUtils::AddPseudoClassLock(GlobalObject& aGlobalObject, + Element& aElement, + const nsAString& aPseudoClass, + bool aEnabled) { + EventStates state = GetStatesForPseudoClass(aPseudoClass); + if (state.IsEmpty()) { + return; + } + + aElement.LockStyleStates(state, aEnabled); +} + +/* static */ +void InspectorUtils::RemovePseudoClassLock(GlobalObject& aGlobal, + Element& aElement, + const nsAString& aPseudoClass) { + EventStates state = GetStatesForPseudoClass(aPseudoClass); + if (state.IsEmpty()) { + return; + } + + aElement.UnlockStyleStates(state); +} + +/* static */ +bool InspectorUtils::HasPseudoClassLock(GlobalObject& aGlobalObject, + Element& aElement, + const nsAString& aPseudoClass) { + EventStates state = GetStatesForPseudoClass(aPseudoClass); + if (state.IsEmpty()) { + return false; + } + + EventStates locks = aElement.LockedStyleStates().mLocks; + return locks.HasAllStates(state); +} + +/* static */ +void InspectorUtils::ClearPseudoClassLocks(GlobalObject& aGlobalObject, + Element& aElement) { + aElement.ClearStyleStateLocks(); +} + +/* static */ +void InspectorUtils::ParseStyleSheet(GlobalObject& aGlobalObject, + StyleSheet& aSheet, + const nsACString& aInput, + ErrorResult& aRv) { + aSheet.ReparseSheet(aInput, aRv); +} + +bool InspectorUtils::IsCustomElementName(GlobalObject&, const nsAString& aName, + const nsAString& aNamespaceURI) { + if (aName.IsEmpty()) { + return false; + } + + int32_t namespaceID; + nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI, + namespaceID); + + RefPtr<nsAtom> nameElt = NS_Atomize(aName); + return nsContentUtils::IsCustomElementName(nameElt, namespaceID); +} + +bool InspectorUtils::IsElementThemed(GlobalObject&, Element& aElement) { + // IsThemed will check if the native theme supports the widget using + // ThemeSupportsWidget which in turn will check that the widget is not + // already styled by content through nsNativeTheme::IsWidgetStyled. We + // assume that if the native theme styles the widget and the author did not + // override the appropriate styles, the theme will provide focus styling. + nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames); + return frame && frame->IsThemed(); +} + +Element* InspectorUtils::ContainingBlockOf(GlobalObject&, Element& aElement) { + nsIFrame* frame = aElement.GetPrimaryFrame(FlushType::Frames); + if (!frame) { + return nullptr; + } + nsIFrame* cb = frame->GetContainingBlock(); + if (!cb) { + return nullptr; + } + return Element::FromNodeOrNull(cb->GetContent()); +} + +static bool FrameHasSpecifiedSize(const nsIFrame* aFrame) { + auto wm = aFrame->GetWritingMode(); + + const nsStylePosition* stylePos = aFrame->StylePosition(); + + return stylePos->ISize(wm).IsLengthPercentage() || + stylePos->BSize(wm).IsLengthPercentage(); +} + +static bool IsFrameOutsideOfAncestor(const nsIFrame* aFrame, + const nsIFrame* aAncestorFrame, + const nsRect& aAncestorRect) { + nsRect frameRectInAncestorSpace = nsLayoutUtils::TransformFrameRectToAncestor( + aFrame, aFrame->ScrollableOverflowRect(), RelativeTo{aAncestorFrame}, + nullptr, nullptr, false, nullptr); + + // We use nsRect::SaturatingUnionEdges because it correctly handles the case + // of a zero-width or zero-height frame, which we still want to consider as + // contributing to the union. + nsRect unionizedRect = + frameRectInAncestorSpace.SaturatingUnionEdges(aAncestorRect); + + // If frameRectInAncestorSpace is inside aAncestorRect then union of + // frameRectInAncestorSpace and aAncestorRect should be equal to aAncestorRect + // hence if it is equal, then false should be returned. + + return !(unionizedRect == aAncestorRect); +} + +static void AddOverflowingChildrenOfElement(const nsIFrame* aFrame, + const nsIFrame* aAncestorFrame, + const nsRect& aRect, + nsSimpleContentList& aList) { + MOZ_ASSERT(aFrame, "we assume the passed-in frame is non-null"); + for (const auto& childList : aFrame->ChildLists()) { + for (const nsIFrame* child : childList.mList) { + // We want to identify if the child or any of its children have a + // frame that is outside of aAncestorFrame. Ideally, child would have + // a frame rect that encompasses all of its children, but this is not + // guaranteed by the frame tree. So instead we first check other + // conditions that indicate child is an interesting frame: + // + // 1) child has a specified size + // 2) none of child's children are implicated + // + // If either of these conditions are true, we *then* check if child's + // frame is outside of aAncestorFrame, and if so, we add child's content + // to aList. + + if (FrameHasSpecifiedSize(child) && + IsFrameOutsideOfAncestor(child, aAncestorFrame, aRect)) { + aList.MaybeAppendElement(child->GetContent()); + continue; + } + + uint32_t currListLength = aList.Length(); + AddOverflowingChildrenOfElement(child, aAncestorFrame, aRect, aList); + + // If child is a leaf node, length of aList should remain same after + // calling AddOverflowingChildrenOfElement on it. + if (currListLength == aList.Length() && + IsFrameOutsideOfAncestor(child, aAncestorFrame, aRect)) { + aList.MaybeAppendElement(child->GetContent()); + } + } + } +} + +already_AddRefed<nsINodeList> InspectorUtils::GetOverflowingChildrenOfElement( + GlobalObject& aGlobal, Element& aElement) { + RefPtr<nsSimpleContentList> list = new nsSimpleContentList(&aElement); + const nsIScrollableFrame* scrollFrame = aElement.GetScrollFrame(); + // Element must have a nsIScrollableFrame + if (!scrollFrame) { + return list.forget(); + } + + auto scrollPortRect = scrollFrame->GetScrollPortRect(); + const nsIFrame* outerFrame = do_QueryFrame(scrollFrame); + const nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame(); + AddOverflowingChildrenOfElement(scrolledFrame, outerFrame, scrollPortRect, + *list); + return list.forget(); +} + +} // namespace dom +} // namespace mozilla |