Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/xul/tree/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 134 kB image not shown  

Quelle  nsTreeBodyFrame.cpp   Sprache: C

 
/* -*- 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 "SimpleXULLeafFrame.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/Likely.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/Try.h"
#include "mozilla/intl/Segmenter.h"

#include "gfxUtils.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsFontMetrics.h"
#include "nsITreeView.h"
#include "nsPresContext.h"
#include "nsNameSpaceManager.h"

#include "nsTreeBodyFrame.h"
#include "nsTreeSelection.h"
#include "nsTreeImageListener.h"

#include "nsGkAtoms.h"
#include "nsCSSAnonBoxes.h"

#include "gfxContext.h"
#include "nsIContent.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/intl/Segmenter.h"
#include "nsCSSRendering.h"
#include "nsString.h"
#include "nsContainerFrame.h"
#include "nsView.h"
#include "nsViewManager.h"
#include "nsWidgetsCID.h"
#include "nsIFrameInlines.h"
#include "nsTreeContentView.h"
#include "nsTreeUtils.h"
#include "nsStyleConsts.h"
#include "imgIRequest.h"
#include "imgIContainer.h"
#include "mozilla/dom/NodeInfo.h"
#include "nsContentUtils.h"
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "mozilla/dom/CustomEvent.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TreeColumnBinding.h"
#include <algorithm>
#include "ScrollbarActivity.h"

#ifdef ACCESSIBILITY
#  include "nsAccessibilityService.h"
#  include "nsIWritablePropertyBag2.h"
#endif
#include "nsBidiUtils.h"

using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
using namespace mozilla::image;
using namespace mozilla::layout;

enum CroppingStyle { CropNone, CropLeft, CropRight, CropCenter, CropAuto };

// FIXME: Maybe unify with MiddleCroppingBlockFrame?
static void CropStringForWidth(nsAString& aText, gfxContext& aRenderingContext,
                               nsFontMetrics& aFontMetrics, nscoord aWidth,
                               CroppingStyle aCropType) {
  DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();

  // See if the width is even smaller than the ellipsis
  // If so, clear the text completely.
  const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
  aFontMetrics.SetTextRunRTL(false);
  nscoord ellipsisWidth =
      nsLayoutUtils::AppUnitWidthOfString(kEllipsis, aFontMetrics, drawTarget);

  if (ellipsisWidth > aWidth) {
    aText.Truncate(0);
    return;
  }
  if (ellipsisWidth == aWidth) {
    aText.Assign(kEllipsis);
    return;
  }

  // We will be drawing an ellipsis, thank you very much.
  // Subtract out the required width of the ellipsis.
  // This is the total remaining width we have to play with.
  aWidth -= ellipsisWidth;

  using mozilla::intl::GraphemeClusterBreakIteratorUtf16;
  using mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16;

  // Now we crop. This is quite basic: it will not be really accurate in the
  // presence of complex scripts with contextual shaping, etc., as it measures
  // each grapheme cluster in isolation, not in its proper context.
  switch (aCropType) {
    case CropAuto:
    case CropNone:
    case CropRight: {
      const Span text(aText);
      GraphemeClusterBreakIteratorUtf16 iter(text);
      uint32_t pos = 0;
      nscoord totalWidth = 0;

      while (Maybe<uint32_t> nextPos = iter.Next()) {
        const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
            text.FromTo(pos, *nextPos), aFontMetrics, drawTarget);
        if (totalWidth + charWidth > aWidth) {
          break;
        }
        pos = *nextPos;
        totalWidth += charWidth;
      }

      if (pos < aText.Length()) {
        aText.Replace(pos, aText.Length() - pos, kEllipsis);
      }
    } break;

    case CropLeft: {
      const Span text(aText);
      GraphemeClusterBreakReverseIteratorUtf16 iter(text);
      uint32_t pos = text.Length();
      nscoord totalWidth = 0;

      // nextPos is decreasing since we use a reverse iterator.
      while (Maybe<uint32_t> nextPos = iter.Next()) {
        const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
            text.FromTo(*nextPos, pos), aFontMetrics, drawTarget);
        if (totalWidth + charWidth > aWidth) {
          break;
        }

        pos = *nextPos;
        totalWidth += charWidth;
      }

      if (pos > 0) {
        aText.Replace(0, pos, kEllipsis);
      }
    } break;

    case CropCenter: {
      const Span text(aText);
      nscoord totalWidth = 0;
      GraphemeClusterBreakIteratorUtf16 leftIter(text);
      GraphemeClusterBreakReverseIteratorUtf16 rightIter(text);
      uint32_t leftPos = 0;
      uint32_t rightPos = text.Length();

      while (leftPos < rightPos) {
        Maybe<uint32_t> nextPos = leftIter.Next();
        nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
            text.FromTo(leftPos, *nextPos), aFontMetrics, drawTarget);
        if (totalWidth + charWidth > aWidth) {
          break;
        }

        leftPos = *nextPos;
        totalWidth += charWidth;

        if (leftPos >= rightPos) {
          break;
        }

        nextPos = rightIter.Next();
        charWidth = nsLayoutUtils::AppUnitWidthOfString(
            text.FromTo(*nextPos, rightPos), aFontMetrics, drawTarget);
        if (totalWidth + charWidth > aWidth) {
          break;
        }

        rightPos = *nextPos;
        totalWidth += charWidth;
      }

      if (leftPos < rightPos) {
        aText.Replace(leftPos, rightPos - leftPos, kEllipsis);
      }
    } break;
  }
}

nsTreeImageCacheEntry::nsTreeImageCacheEntry() = default;
nsTreeImageCacheEntry::nsTreeImageCacheEntry(imgIRequest* aRequest,
                                             nsTreeImageListener* aListener)
    : request(aRequest), listener(aListener) {}
nsTreeImageCacheEntry::~nsTreeImageCacheEntry() = default;

static void DoCancelImageCacheEntry(const nsTreeImageCacheEntry& aEntry,
                                    nsPresContext* aPc) {
  // If our imgIRequest object was registered with the refresh driver
  // then we need to deregister it.
  aEntry.listener->ClearFrame();
  nsLayoutUtils::DeregisterImageRequest(aPc, aEntry.request, nullptr);
  aEntry.request->UnlockImage();
  aEntry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
}

// Function that cancels all the image requests in our cache.
void nsTreeBodyFrame::CancelImageRequests() {
  auto* pc = PresContext();
  for (const nsTreeImageCacheEntry& entry : mImageCache.Values()) {
    DoCancelImageCacheEntry(entry, pc);
  }
}

//
// NS_NewTreeFrame
//
// Creates a new tree frame
//
nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
  return new (aPresShell) nsTreeBodyFrame(aStyle, aPresShell->GetPresContext());
}

NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame)

NS_QUERYFRAME_HEAD(nsTreeBodyFrame)
  NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
  NS_QUERYFRAME_ENTRY(nsTreeBodyFrame)
NS_QUERYFRAME_TAIL_INHERITING(SimpleXULLeafFrame)

// Constructor
nsTreeBodyFrame::nsTreeBodyFrame(ComputedStyle* aStyle,
                                 nsPresContext* aPresContext)
    : SimpleXULLeafFrame(aStyle, aPresContext, kClassID),
      mTopRowIndex(0),
      mPageLength(0),
      mHorzPosition(0),
      mOriginalHorzWidth(-1),
      mHorzWidth(0),
      mRowHeight(0),
      mIndentation(0),
      mUpdateBatchNest(0),
      mRowCount(0),
      mMouseOverRow(-1),
      mFocused(false),
      mHasFixedRowCount(false),
      mVerticalOverflow(false),
      mReflowCallbackPosted(false),
      mCheckingOverflow(false) {
  mColumns = new nsTreeColumns(this);
}

// Destructor
nsTreeBodyFrame::~nsTreeBodyFrame() { CancelImageRequests(); }

static void GetBorderPadding(ComputedStyle* aStyle, nsMargin& aMargin) {
  aMargin.SizeTo(0, 0, 0, 0);
  aStyle->StylePadding()->GetPadding(aMargin);
  aMargin += aStyle->StyleBorder()->GetComputedBorder();
}

static void AdjustForBorderPadding(ComputedStyle* aStyle, nsRect& aRect) {
  nsMargin borderPadding(0, 0, 0, 0);
  GetBorderPadding(aStyle, borderPadding);
  aRect.Deflate(borderPadding);
}

void nsTreeBodyFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
                           nsIFrame* aPrevInFlow) {
  SimpleXULLeafFrame::Init(aContent, aParent, aPrevInFlow);

  mIndentation = GetIndentation();
  mRowHeight = GetRowHeight();

  // Call GetBaseElement so that mTree is assigned.
  RefPtr<XULTreeElement> tree(GetBaseElement());
  if (MOZ_LIKELY(tree)) {
    nsAutoString rows;
    if (tree->GetAttr(nsGkAtoms::rows, rows)) {
      nsresult err;
      mPageLength = rows.ToInteger(&err);
      mHasFixedRowCount = true;
    }
  }

  if (PresContext()->UseOverlayScrollbars()) {
    mScrollbarActivity =
        new ScrollbarActivity(static_cast<nsIScrollbarMediator*>(this));
  }
}

void nsTreeBodyFrame::Destroy(DestroyContext& aContext) {
  if (mScrollbarActivity) {
    mScrollbarActivity->Destroy();
    mScrollbarActivity = nullptr;
  }

  mScrollEvent.Revoke();
  // Make sure we cancel any posted callbacks.
  if (mReflowCallbackPosted) {
    PresShell()->CancelReflowCallback(this);
    mReflowCallbackPosted = false;
  }

  if (mColumns) {
    mColumns->SetTree(nullptr);
  }

  RefPtr tree = mTree;

  if (nsCOMPtr<nsITreeView> view = std::move(mView)) {
    nsCOMPtr<nsITreeSelection> sel;
    view->GetSelection(getter_AddRefs(sel));
    if (sel) {
      sel->SetTree(nullptr);
    }
    view->SetTree(nullptr);
  }

  // Make this call now because view->SetTree can run js which can undo this
  // call.
  if (tree) {
    tree->BodyDestroyed(mTopRowIndex);
  }
  if (mTree && mTree != tree) {
    mTree->BodyDestroyed(mTopRowIndex);
  }

  SimpleXULLeafFrame::Destroy(aContext);
}

void nsTreeBodyFrame::EnsureView() {
  if (mView) {
    return;
  }

  if (PresShell()->IsReflowLocked()) {
    if (!mReflowCallbackPosted) {
      mReflowCallbackPosted = true;
      PresShell()->PostReflowCallback(this);
    }
    return;
  }

  AutoWeakFrame weakFrame(this);

  RefPtr<XULTreeElement> tree = GetBaseElement();
  if (!tree) {
    return;
  }
  nsCOMPtr<nsITreeView> treeView = tree->GetView();
  if (!treeView || !weakFrame.IsAlive()) {
    return;
  }
  int32_t rowIndex = tree->GetCachedTopVisibleRow();

  // Set our view.
  SetView(treeView);
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());

  // Scroll to the given row.
  // XXX is this optimal if we haven't laid out yet?
  ScrollToRow(rowIndex);
  NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
}

void nsTreeBodyFrame::ManageReflowCallback() {
  const nscoord horzWidth = mRect.width;
  if (!mReflowCallbackPosted) {
    if (!mLastReflowRect || !mLastReflowRect->IsEqualEdges(mRect) ||
        mHorzWidth != horzWidth) {
      PresShell()->PostReflowCallback(this);
      mReflowCallbackPosted = true;
      mOriginalHorzWidth = mHorzWidth;
    }
  } else if (mHorzWidth != horzWidth && mOriginalHorzWidth == horzWidth) {
    // FIXME(emilio): This doesn't seem sound to me, if the rect changes in the
    // block axis.
    PresShell()->CancelReflowCallback(this);
    mReflowCallbackPosted = false;
    mOriginalHorzWidth = -1;
  }
  mLastReflowRect = Some(mRect);
  mHorzWidth = horzWidth;
}

IntrinsicSize nsTreeBodyFrame::GetIntrinsicSize() {
  IntrinsicSize intrinsicSize;
  if (mHasFixedRowCount) {
    intrinsicSize.BSize(GetWritingMode()).emplace(mRowHeight * mPageLength);
  }
  return intrinsicSize;
}

void nsTreeBodyFrame::DidReflow(nsPresContext* aPresContext,
                                const ReflowInput* aReflowInput) {
  ManageReflowCallback();
  SimpleXULLeafFrame::DidReflow(aPresContext, aReflowInput);
}

bool nsTreeBodyFrame::ReflowFinished() {
  if (!mView) {
    AutoWeakFrame weakFrame(this);
    EnsureView();
    NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
  }
  if (mView) {
    CalcInnerBox();
    ScrollParts parts = GetScrollParts();
    mHorzWidth = mRect.width;
    if (!mHasFixedRowCount) {
      mPageLength =
          (mRowHeight > 0) ? (mInnerBox.height / mRowHeight) : mRowCount;
    }

    int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength);
    if (mTopRowIndex > lastPageTopRow) {
      ScrollToRowInternal(parts, lastPageTopRow);
    }

    XULTreeElement* treeContent = GetBaseElement();
    if (treeContent && treeContent->AttrValueIs(
                           kNameSpaceID_None, nsGkAtoms::keepcurrentinview,
                           nsGkAtoms::_true, eCaseMatters)) {
      // make sure that the current selected item is still
      // visible after the tree changes size.
      if (nsCOMPtr<nsITreeSelection> sel = GetSelection()) {
        int32_t currentIndex;
        sel->GetCurrentIndex(¤tIndex);
        if (currentIndex != -1) {
          EnsureRowIsVisibleInternal(parts, currentIndex);
        }
      }
    }

    if (!FullScrollbarsUpdate(false)) {
      return false;
    }
  }

  mReflowCallbackPosted = false;
  return false;
}

void nsTreeBodyFrame::ReflowCallbackCanceled() {
  mReflowCallbackPosted = false;
}

nsresult nsTreeBodyFrame::GetView(nsITreeView** aView) {
  *aView = nullptr;
  AutoWeakFrame weakFrame(this);
  EnsureView();
  NS_ENSURE_STATE(weakFrame.IsAlive());
  NS_IF_ADDREF(*aView = mView);
  return NS_OK;
}

nsresult nsTreeBodyFrame::SetView(nsITreeView* aView) {
  if (aView == mView) {
    return NS_OK;
  }

  // First clear out the old view.
  nsCOMPtr<nsITreeView> oldView = std::move(mView);
  if (oldView) {
    AutoWeakFrame weakFrame(this);

    nsCOMPtr<nsITreeSelection> sel;
    oldView->GetSelection(getter_AddRefs(sel));
    if (sel) {
      sel->SetTree(nullptr);
    }
    oldView->SetTree(nullptr);

    NS_ENSURE_STATE(weakFrame.IsAlive());

    // Only reset the top row index and delete the columns if we had an old
    // non-null view.
    mTopRowIndex = 0;
  }

  // Tree, meet the view.
  mView = aView;

  // Changing the view causes us to refetch our data.  This will
  // necessarily entail a full invalidation of the tree.
  Invalidate();

  RefPtr<XULTreeElement> treeContent = GetBaseElement();
  if (treeContent) {
#ifdef ACCESSIBILITY
    if (nsAccessibilityService* accService = GetAccService()) {
      accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent,
                                  mView);
    }
#endif  // #ifdef ACCESSIBILITY
    FireDOMEvent(u"TreeViewChanged"_ns, treeContent);
  }

  if (aView) {
    // Give the view a new empty selection object to play with, but only if it
    // doesn't have one already.
    nsCOMPtr<nsITreeSelection> sel;
    aView->GetSelection(getter_AddRefs(sel));
    if (sel) {
      sel->SetTree(treeContent);
    } else {
      NS_NewTreeSelection(treeContent, getter_AddRefs(sel));
      aView->SetSelection(sel);
    }

    // View, meet the tree.
    AutoWeakFrame weakFrame(this);
    aView->SetTree(treeContent);
    NS_ENSURE_STATE(weakFrame.IsAlive());
    aView->GetRowCount(&mRowCount);

    if (!PresShell()->IsReflowLocked()) {
      // The scrollbar will need to be updated.
      FullScrollbarsUpdate(false);
    } else if (!mReflowCallbackPosted) {
      mReflowCallbackPosted = true;
      PresShell()->PostReflowCallback(this);
    }
  }

  return NS_OK;
}

already_AddRefed<nsITreeSelection> nsTreeBodyFrame::GetSelection() const {
  nsCOMPtr<nsITreeSelection> sel;
  if (nsCOMPtr<nsITreeView> view = GetExistingView()) {
    view->GetSelection(getter_AddRefs(sel));
  }
  return sel.forget();
}

nsresult nsTreeBodyFrame::SetFocused(bool aFocused) {
  if (mFocused != aFocused) {
    mFocused = aFocused;
    if (nsCOMPtr<nsITreeSelection> sel = GetSelection()) {
      sel->InvalidateSelection();
    }
  }
  return NS_OK;
}

nsresult nsTreeBodyFrame::GetTreeBody(Element** aElement) {
  // NS_ASSERTION(mContent, "no content, see bug #104878");
  if (!mContent) {
    return NS_ERROR_NULL_POINTER;
  }

  RefPtr<Element> element = mContent->AsElement();
  element.forget(aElement);
  return NS_OK;
}

int32_t nsTreeBodyFrame::RowHeight() const {
  return nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
}

int32_t nsTreeBodyFrame::RowWidth() {
  return nsPresContext::AppUnitsToIntCSSPixels(mRect.width);
}

Maybe<CSSIntRegion> nsTreeBodyFrame::GetSelectionRegion() {
  if (!mView) {
    return Nothing();
  }

  AutoWeakFrame wf(this);
  nsCOMPtr<nsITreeSelection> selection = GetSelection();
  if (!selection || !wf.IsAlive()) {
    return Nothing();
  }

  nsIntRect rect = mRect.ToOutsidePixels(AppUnitsPerCSSPixel());

  nsIFrame* rootFrame = PresShell()->GetRootFrame();
  nsPoint origin = GetOffsetTo(rootFrame);

  CSSIntRegion region;

  // iterate through the visible rows and add the selected ones to the
  // drag region
  int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
  int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
  int32_t top = y;
  int32_t end = LastVisibleRow();
  int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
  for (int32_t i = mTopRowIndex; i <= end; i++) {
    bool isSelected;
    selection->IsSelected(i, &isSelected);
    if (isSelected) {
      region.OrWith(CSSIntRect(x, y, rect.width, rowHeight));
    }
    y += rowHeight;
  }

  // clip to the tree boundary in case one row extends past it
  region.AndWith(CSSIntRect(x, top, rect.width, rect.height));

  return Some(region);
}

nsresult nsTreeBodyFrame::Invalidate() {
  if (mUpdateBatchNest) {
    return NS_OK;
  }

  InvalidateFrame();

  return NS_OK;
}

nsresult nsTreeBodyFrame::InvalidateColumn(nsTreeColumn* aCol) {
  if (mUpdateBatchNest) {
    return NS_OK;
  }

  if (!aCol) {
    return NS_ERROR_INVALID_ARG;
  }

#ifdef ACCESSIBILITY
  if (GetAccService()) {
    FireInvalidateEvent(-1, -1, aCol, aCol);
  }
#endif  // #ifdef ACCESSIBILITY

  nsRect columnRect;
  nsresult rv = aCol->GetRect(this, mInnerBox.y, mInnerBox.height, &columnRect);
  NS_ENSURE_SUCCESS(rv, rv);

  // When false then column is out of view
  if (OffsetForHorzScroll(columnRect, true)) {
    InvalidateFrameWithRect(columnRect);
  }

  return NS_OK;
}

nsresult nsTreeBodyFrame::InvalidateRow(int32_t aIndex) {
  if (mUpdateBatchNest) {
    return NS_OK;
  }

#ifdef ACCESSIBILITY
  if (GetAccService()) {
    FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr);
  }
#endif  // #ifdef ACCESSIBILITY

  aIndex -= mTopRowIndex;
  if (aIndex < 0 || aIndex > mPageLength) {
    return NS_OK;
  }

  nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight * aIndex,
                 mInnerBox.width, mRowHeight);
  InvalidateFrameWithRect(rowRect);

  return NS_OK;
}

nsresult nsTreeBodyFrame::InvalidateCell(int32_t aIndex, nsTreeColumn* aCol) {
  if (mUpdateBatchNest) {
    return NS_OK;
  }

#ifdef ACCESSIBILITY
  if (GetAccService()) {
    FireInvalidateEvent(aIndex, aIndex, aCol, aCol);
  }
#endif  // #ifdef ACCESSIBILITY

  aIndex -= mTopRowIndex;
  if (aIndex < 0 || aIndex > mPageLength) {
    return NS_OK;
  }

  if (!aCol) {
    return NS_ERROR_INVALID_ARG;
  }

  nsRect cellRect;
  nsresult rv = aCol->GetRect(this, mInnerBox.y + mRowHeight * aIndex,
                              mRowHeight, &cellRect);
  NS_ENSURE_SUCCESS(rv, rv);

  if (OffsetForHorzScroll(cellRect, true)) {
    InvalidateFrameWithRect(cellRect);
  }

  return NS_OK;
}

nsresult nsTreeBodyFrame::InvalidateRange(int32_t aStart, int32_t aEnd) {
  if (mUpdateBatchNest) {
    return NS_OK;
  }

  if (aStart == aEnd) {
    return InvalidateRow(aStart);
  }

  int32_t last = LastVisibleRow();
  if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last) {
    return NS_OK;
  }

  if (aStart < mTopRowIndex) {
    aStart = mTopRowIndex;
  }

  if (aEnd > last) {
    aEnd = last;
  }

#ifdef ACCESSIBILITY
  if (GetAccService()) {
    int32_t end =
        mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
    FireInvalidateEvent(aStart, end, nullptr, nullptr);
  }
#endif  // #ifdef ACCESSIBILITY

  nsRect rangeRect(mInnerBox.x,
                   mInnerBox.y + mRowHeight * (aStart - mTopRowIndex),
                   mInnerBox.width, mRowHeight * (aEnd - aStart + 1));
  InvalidateFrameWithRect(rangeRect);

  return NS_OK;
}

static void FindScrollParts(nsIFrame* aCurrFrame,
                            nsTreeBodyFrame::ScrollParts* aResult) {
  if (nsScrollbarFrame* sf = do_QueryFrame(aCurrFrame)) {
    if (!sf->IsHorizontal() && !aResult->mVScrollbar) {
      aResult->mVScrollbar = sf;
    }
    // don't bother searching inside a scrollbar
    return;
  }

  nsIFrame* child = aCurrFrame->PrincipalChildList().FirstChild();
  while (child && !child->GetContent()->IsRootOfNativeAnonymousSubtree() &&
         !aResult->mVScrollbar) {
    FindScrollParts(child, aResult);
    child = child->GetNextSibling();
  }
}

nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts() {
  ScrollParts result;
  XULTreeElement* tree = GetBaseElement();
  if (nsIFrame* treeFrame = tree ? tree->GetPrimaryFrame() : nullptr) {
    // The way we do this, searching through the entire frame subtree, is pretty
    // dumb! We should know where these frames are.
    FindScrollParts(treeFrame, &result);
    if (result.mVScrollbar) {
      result.mVScrollbar->SetScrollbarMediatorContent(GetContent());
      result.mVScrollbarContent = result.mVScrollbar->GetContent()->AsElement();
    }
  }
  return result;
}

void nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts) {
  nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);

  AutoWeakFrame weakFrame(this);

  if (aParts.mVScrollbar) {
    nsAutoString curPos;
    curPos.AppendInt(mTopRowIndex * rowHeightAsPixels);
    aParts.mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::curpos,
                                       curPos, true);
    // 'this' might be deleted here
  }

  if (weakFrame.IsAlive() && mScrollbarActivity) {
    mScrollbarActivity->ActivityOccurred();
  }
}

void nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts) {
  bool verticalOverflowChanged = false;
  if (!mVerticalOverflow && mRowCount > mPageLength) {
    mVerticalOverflow = true;
    verticalOverflowChanged = true;
  } else if (mVerticalOverflow && mRowCount <= mPageLength) {
    mVerticalOverflow = false;
    verticalOverflowChanged = true;
  }

  if (!verticalOverflowChanged) {
    return;
  }

  AutoWeakFrame weakFrame(this);

  RefPtr<nsPresContext> presContext = PresContext();
  RefPtr<mozilla::PresShell> presShell = presContext->GetPresShell();
  nsCOMPtr<nsIContent> content = mContent;

  InternalScrollPortEvent event(
      true, mVerticalOverflow ? eScrollPortOverflow : eScrollPortUnderflow,
      nullptr);
  event.mOrient = InternalScrollPortEvent::eVertical;
  EventDispatcher::Dispatch(content, presContext, &event);

  // The synchronous event dispatch above can trigger reflow notifications.
  // Flush those explicitly now, so that we can guard against potential infinite
  // recursion. See bug 905909.
  if (!weakFrame.IsAlive()) {
    return;
  }
  NS_ASSERTION(!mCheckingOverflow,
               "mCheckingOverflow should not already be set");
  // Don't use AutoRestore since we want to not touch mCheckingOverflow if we
  // fail the weakFrame.IsAlive() check below
  mCheckingOverflow = true;
  presShell->FlushPendingNotifications(FlushType::Layout);
  if (!weakFrame.IsAlive()) {
    return;
  }
  mCheckingOverflow = false;
}

void nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts) {
  if (mUpdateBatchNest || !mView) {
    return;
  }
  AutoWeakFrame weakFrame(this);

  if (aParts.mVScrollbar) {
    // Do Vertical Scrollbar
    nsAutoString maxposStr;

    nscoord rowHeightAsPixels =
        nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);

    int32_t size = rowHeightAsPixels *
                   (mRowCount > mPageLength ? mRowCount - mPageLength : 0);
    maxposStr.AppendInt(size);
    aParts.mVScrollbarContent->SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos,
                                       maxposStr, true);
    NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());

    // Also set our page increment and decrement.
    nscoord pageincrement = mPageLength * rowHeightAsPixels;
    nsAutoString pageStr;
    pageStr.AppendInt(pageincrement);
    aParts.mVScrollbarContent->SetAttr(kNameSpaceID_None,
                                       nsGkAtoms::pageincrement, pageStr, true);
    NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
  }

  if (weakFrame.IsAlive() && mScrollbarActivity) {
    mScrollbarActivity->ActivityOccurred();
  }
}

// Takes client x/y in pixels, converts them to appunits, and converts into
// values relative to this nsTreeBodyFrame frame.
nsPoint nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX,
                                                           int32_t aY) {
  nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX),
                nsPresContext::CSSPixelsToAppUnits(aY));

  nsPresContext* presContext = PresContext();
  point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame());

  // Adjust by the inner box coords, so that we're in the inner box's
  // coordinate space.
  point -= mInnerBox.TopLeft();
  return point;
}  // AdjustClientCoordsToBoxCoordSpace

int32_t nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY) {
  if (!mView) {
    return 0;
  }

  nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);

  // Check if the coordinates are above our visible space.
  if (point.y < 0) {
    return -1;
  }

  return GetRowAtInternal(point.x, point.y);
}

nsresult nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow,
                                    nsTreeColumn** aCol,
                                    nsACString& aChildElt) {
  if (!mView) {
    return NS_OK;
  }

  nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);

  // Check if the coordinates are above our visible space.
  if (point.y < 0) {
    *aRow = -1;
    return NS_OK;
  }

  nsTreeColumn* col;
  nsCSSAnonBoxPseudoStaticAtom* child;
  GetCellAt(point.x, point.y, aRow, &col, &child);

  if (col) {
    NS_ADDREF(*aCol = col);
    if (child == nsCSSAnonBoxes::mozTreeCell()) {
      aChildElt.AssignLiteral("cell");
    } else if (child == nsCSSAnonBoxes::mozTreeTwisty()) {
      aChildElt.AssignLiteral("twisty");
    } else if (child == nsCSSAnonBoxes::mozTreeImage()) {
      aChildElt.AssignLiteral("image");
    } else if (child == nsCSSAnonBoxes::mozTreeCellText()) {
      aChildElt.AssignLiteral("text");
    }
  }

  return NS_OK;
}

//
// GetCoordsForCellItem
//
// Find the x/y location and width/height (all in PIXELS) of the given object
// in the given column.
//
// XXX IMPORTANT XXX:
// Hyatt says in the bug for this, that the following needs to be done:
// (1) You need to deal with overflow when computing cell rects.  See other
// column iteration examples... if you don't deal with this, you'll mistakenly
// extend the cell into the scrollbar's rect.
//
// (2) You are adjusting the cell rect by the *row" border padding.  That's
// wrong.  You need to first adjust a row rect by its border/padding, and then
// the cell rect fits inside the adjusted row rect.  It also can have
// border/padding as well as margins.  The vertical direction isn't that
// important, but you need to get the horizontal direction right.
//
// (3) GetImageSize() does not include margins (but it does include
// border/padding). You need to make sure to add in the image's margins as well.
//
nsresult nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsTreeColumn* aCol,
                                               const nsACString& aElement,
                                               int32_t* aX, int32_t* aY,
                                               int32_t* aWidth,
                                               int32_t* aHeight) {
  *aX = 0;
  *aY = 0;
  *aWidth = 0;
  *aHeight = 0;

  bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
  nscoord currX = mInnerBox.x - mHorzPosition;

  // The Rect for the requested item.
  nsRect theRect;

  nsPresContext* presContext = PresContext();

  nsCOMPtr<nsITreeView> view = GetExistingView();

  for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
       currCol = currCol->GetNext()) {
    // The Rect for the current cell.
    nscoord colWidth;
#ifdef DEBUG
    nsresult rv =
#endif
        currCol->GetWidthInTwips(this, &colWidth);
    NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column");

    nsRect cellRect(currX, mInnerBox.y + mRowHeight * (aRow - mTopRowIndex),
                    colWidth, mRowHeight);

    // Check the ID of the current column to see if it matches. If it doesn't
    // increment the current X value and continue to the next column.
    if (currCol != aCol) {
      currX += cellRect.width;
      continue;
    }
    // Now obtain the properties for our cell.
    PrefillPropertyArray(aRow, currCol);

    nsAutoString properties;
    view->GetCellProperties(aRow, currCol, properties);
    nsTreeUtils::TokenizeProperties(properties, mScratchArray);

    ComputedStyle* rowContext =
        GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());

    // We don't want to consider any of the decorations that may be present
    // on the current row, so we have to deflate the rect by the border and
    // padding and offset its left and top coordinates appropriately.
    AdjustForBorderPadding(rowContext, cellRect);

    ComputedStyle* cellContext =
        GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());

    constexpr auto cell = "cell"_ns;
    if (currCol->IsCycler() || cell.Equals(aElement)) {
      // If the current Column is a Cycler, then the Rect is just the cell - the
      // margins. Similarly, if we're just being asked for the cell rect,
      // provide it.

      theRect = cellRect;
      nsMargin cellMargin;
      cellContext->StyleMargin()->GetMargin(cellMargin);
      theRect.Deflate(cellMargin);
      break;
    }

    // Since we're not looking for the cell, and since the cell isn't a cycler,
    // we're looking for some subcomponent, and now we need to subtract the
    // borders and padding of the cell from cellRect so this does not
    // interfere with our computations.
    AdjustForBorderPadding(cellContext, cellRect);

    UniquePtr<gfxContext> rc =
        presContext->PresShell()->CreateReferenceRenderingContext();

    // Now we'll start making our way across the cell, starting at the edge of
    // the cell and proceeding until we hit the right edge. |cellX| is the
    // working X value that we will increment as we crawl from left to right.
    nscoord cellX = cellRect.x;
    nscoord remainWidth = cellRect.width;

    if (currCol->IsPrimary()) {
      // If the current Column is a Primary, then we need to take into account
      // the indentation and possibly a twisty.

      // The amount of indentation is the indentation width (|mIndentation|) by
      // the level.
      int32_t level;
      view->GetLevel(aRow, &level);
      if (!isRTL) {
        cellX += mIndentation * level;
      }
      remainWidth -= mIndentation * level;

      // Find the twisty rect by computing its size.
      nsRect imageRect;
      nsRect twistyRect(cellRect);
      ComputedStyle* twistyContext =
          GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
      GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext,
                    twistyContext);

      if ("twisty"_ns.Equals(aElement)) {
        // If we're looking for the twisty Rect, just return the size
        theRect = twistyRect;
        break;
      }

      // Now we need to add in the margins of the twisty element, so that we
      // can find the offset of the next element in the cell.
      nsMargin twistyMargin;
      twistyContext->StyleMargin()->GetMargin(twistyMargin);
      twistyRect.Inflate(twistyMargin);

      // Adjust our working X value with the twisty width (image size, margins,
      // borders, padding.
      if (!isRTL) {
        cellX += twistyRect.width;
      }
    }

    // Cell Image
    ComputedStyle* imageContext =
        GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());

    nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext);
    if ("image"_ns.Equals(aElement)) {
      theRect = imageSize;
      theRect.x = cellX;
      theRect.y = cellRect.y;
      break;
    }

    // Add in the margins of the cell image.
    nsMargin imageMargin;
    imageContext->StyleMargin()->GetMargin(imageMargin);
    imageSize.Inflate(imageMargin);

    // Increment cellX by the image width
    if (!isRTL) {
      cellX += imageSize.width;
    }

    // Cell Text
    nsAutoString cellText;
    view->GetCellText(aRow, currCol, cellText);
    // We're going to measure this text so we need to ensure bidi is enabled if
    // necessary
    CheckTextForBidi(cellText);

    // Create a scratch rect to represent the text rectangle, with the current
    // X and Y coords, and a guess at the width and height. The width is the
    // remaining width we have left to traverse in the cell, which will be the
    // widest possible value for the text rect, and the row height.
    nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height);

    // Measure the width of the text. If the width of the text is greater than
    // the remaining width available, then we just assume that the text has
    // been cropped and use the remaining rect as the text Rect. Otherwise,
    // we add in borders and padding to the text dimension and give that back.
    ComputedStyle* textContext =
        GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());

    RefPtr<nsFontMetrics> fm =
        nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, presContext);
    nscoord height = fm->MaxHeight();

    nsMargin textMargin;
    textContext->StyleMargin()->GetMargin(textMargin);
    textRect.Deflate(textMargin);

    // Center the text. XXX Obey vertical-align style prop?
    if (height < textRect.height) {
      textRect.y += (textRect.height - height) / 2;
      textRect.height = height;
    }

    nsMargin bp(0, 0, 0, 0);
    GetBorderPadding(textContext, bp);
    textRect.height += bp.top + bp.bottom;

    AdjustForCellText(cellText, aRow, currCol, *rc, *fm, textRect);

    theRect = textRect;
  }

  if (isRTL) {
    theRect.x = mInnerBox.width - theRect.x - theRect.width;
  }

  *aX = nsPresContext::AppUnitsToIntCSSPixels(theRect.x);
  *aY = nsPresContext::AppUnitsToIntCSSPixels(theRect.y);
  *aWidth = nsPresContext::AppUnitsToIntCSSPixels(theRect.width);
  *aHeight = nsPresContext::AppUnitsToIntCSSPixels(theRect.height);

  return NS_OK;
}

int32_t nsTreeBodyFrame::GetRowAtInternal(nscoord aX, nscoord aY) {
  if (mRowHeight <= 0) {
    return -1;
  }

  // Now just mod by our total inner box height and add to our top row index.
  int32_t row = (aY / mRowHeight) + mTopRowIndex;

  // Check if the coordinates are below our visible space (or within our visible
  // space but below any row).
  if (row > mTopRowIndex + mPageLength || row >= mRowCount) {
    return -1;
  }

  return row;
}

void nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText) {
  // We could check to see whether the prescontext already has bidi enabled,
  // but usually it won't, so it's probably faster to avoid the call to
  // GetPresContext() when it's not needed.
  if (HasRTLChars(aText)) {
    PresContext()->SetBidiEnabled();
  }
}

void nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText, int32_t aRowIndex,
                                        nsTreeColumn* aColumn,
                                        gfxContext& aRenderingContext,
                                        nsFontMetrics& aFontMetrics,
                                        nsRect& aTextRect) {
  MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");

  DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();

  nscoord maxWidth = aTextRect.width;
  bool widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(
      aText, aFontMetrics, drawTarget, maxWidth);

  nsCOMPtr<nsITreeView> view = GetExistingView();
  if (aColumn->Overflow()) {
    DebugOnly<nsresult> rv;
    nsTreeColumn* nextColumn = aColumn->GetNext();
    while (nextColumn && widthIsGreater) {
      while (nextColumn) {
        nscoord width;
        rv = nextColumn->GetWidthInTwips(this, &width);
        NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");

        if (width != 0) {
          break;
        }

        nextColumn = nextColumn->GetNext();
      }

      if (nextColumn) {
        nsAutoString nextText;
        view->GetCellText(aRowIndex, nextColumn, nextText);
        // We don't measure or draw this text so no need to check it for
        // bidi-ness

        if (nextText.Length() == 0) {
          nscoord width;
          rv = nextColumn->GetWidthInTwips(this, &width);
          NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");

          maxWidth += width;
          widthIsGreater = nsLayoutUtils::StringWidthIsGreaterThan(
              aText, aFontMetrics, drawTarget, maxWidth);

          nextColumn = nextColumn->GetNext();
        } else {
          nextColumn = nullptr;
        }
      }
    }
  }

  CroppingStyle cropType = CroppingStyle::CropRight;
  if (aColumn->GetCropStyle() == 1) {
    cropType = CroppingStyle::CropCenter;
  } else if (aColumn->GetCropStyle() == 2) {
    cropType = CroppingStyle::CropLeft;
  }
  CropStringForWidth(aText, aRenderingContext, aFontMetrics, maxWidth,
                     cropType);

  nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(
      aText, this, aFontMetrics, aRenderingContext);

  switch (aColumn->GetTextAlignment()) {
    case mozilla::StyleTextAlign::Right:
      aTextRect.x += aTextRect.width - width;
      break;
    case mozilla::StyleTextAlign::Center:
      aTextRect.x += (aTextRect.width - width) / 2;
      break;
    default:
      break;
  }

  aTextRect.width = width;
}

nsCSSAnonBoxPseudoStaticAtom* nsTreeBodyFrame::GetItemWithinCellAt(
    nscoord aX, const nsRect& aCellRect, int32_t aRowIndex,
    nsTreeColumn* aColumn) {
  MOZ_ASSERT(aColumn && aColumn->GetFrame(), "invalid column passed");

  // Obtain the properties for our cell.
  PrefillPropertyArray(aRowIndex, aColumn);
  nsAutoString properties;
  nsCOMPtr<nsITreeView> view = GetExistingView();
  view->GetCellProperties(aRowIndex, aColumn, properties);
  nsTreeUtils::TokenizeProperties(properties, mScratchArray);

  // Resolve style for the cell.
  ComputedStyle* cellContext =
      GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());

  // Obtain the margins for the cell and then deflate our rect by that
  // amount.  The cell is assumed to be contained within the deflated rect.
  nsRect cellRect(aCellRect);
  nsMargin cellMargin;
  cellContext->StyleMargin()->GetMargin(cellMargin);
  cellRect.Deflate(cellMargin);

  // Adjust the rect for its border and padding.
  AdjustForBorderPadding(cellContext, cellRect);

  if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) {
    // The user clicked within the cell's margins/borders/padding.  This
    // constitutes a click on the cell.
    return nsCSSAnonBoxes::mozTreeCell();
  }

  nscoord currX = cellRect.x;
  nscoord remainingWidth = cellRect.width;

  // Handle right alignment hit testing.
  bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;

  nsPresContext* presContext = PresContext();
  UniquePtr<gfxContext> rc =
      presContext->PresShell()->CreateReferenceRenderingContext();

  if (aColumn->IsPrimary()) {
    // If we're the primary column, we have indentation and a twisty.
    int32_t level;
    view->GetLevel(aRowIndex, &level);

    if (!isRTL) {
      currX += mIndentation * level;
    }
    remainingWidth -= mIndentation * level;

    if ((isRTL && aX > currX + remainingWidth) || (!isRTL && aX < currX)) {
      // The user clicked within the indentation.
      return nsCSSAnonBoxes::mozTreeCell();
    }

    // Always leave space for the twisty.
    nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
    bool hasTwisty = false;
    bool isContainer = false;
    view->IsContainer(aRowIndex, &isContainer);
    if (isContainer) {
      bool isContainerEmpty = false;
      view->IsContainerEmpty(aRowIndex, &isContainerEmpty);
      if (!isContainerEmpty) {
        hasTwisty = true;
      }
    }

    // Resolve style for the twisty.
    ComputedStyle* twistyContext =
        GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());

    nsRect imageSize;
    GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, presContext,
                  twistyContext);

    // We will treat a click as hitting the twisty if it happens on the margins,
    // borders, padding, or content of the twisty object.  By allowing a "slop"
    // into the margin, we make it a little bit easier for a user to hit the
    // twisty.  (We don't want to be too picky here.)
    nsMargin twistyMargin;
    twistyContext->StyleMargin()->GetMargin(twistyMargin);
    twistyRect.Inflate(twistyMargin);
    if (isRTL) {
      twistyRect.x = currX + remainingWidth - twistyRect.width;
    }

    // Now we test to see if aX is actually within the twistyRect.  If it is,
    // and if the item should have a twisty, then we return "twisty".  If it is
    // within the rect but we shouldn't have a twisty, then we return "cell".
    if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) {
      if (hasTwisty) {
        return nsCSSAnonBoxes::mozTreeTwisty();
      }
      return nsCSSAnonBoxes::mozTreeCell();
    }

    if (!isRTL) {
      currX += twistyRect.width;
    }
    remainingWidth -= twistyRect.width;
  }

  // Now test to see if the user hit the icon for the cell.
  nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);

  // Resolve style for the image.
  ComputedStyle* imageContext =
      GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());

  nsRect iconSize = GetImageSize(aRowIndex, aColumn, false, imageContext);
  nsMargin imageMargin;
  imageContext->StyleMargin()->GetMargin(imageMargin);
  iconSize.Inflate(imageMargin);
  iconRect.width = iconSize.width;
  if (isRTL) {
    iconRect.x = currX + remainingWidth - iconRect.width;
  }

  if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) {
    // The user clicked on the image.
    return nsCSSAnonBoxes::mozTreeImage();
  }

  if (!isRTL) {
    currX += iconRect.width;
  }
  remainingWidth -= iconRect.width;

  nsAutoString cellText;
  view->GetCellText(aRowIndex, aColumn, cellText);
  // We're going to measure this text so we need to ensure bidi is enabled if
  // necessary
  CheckTextForBidi(cellText);

  nsRect textRect(currX, cellRect.y, remainingWidth, cellRect.height);

  ComputedStyle* textContext =
      GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());

  nsMargin textMargin;
  textContext->StyleMargin()->GetMargin(textMargin);
  textRect.Deflate(textMargin);

  AdjustForBorderPadding(textContext, textRect);

  RefPtr<nsFontMetrics> fm =
      nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, presContext);
  AdjustForCellText(cellText, aRowIndex, aColumn, *rc, *fm, textRect);

  if (aX >= textRect.x && aX < textRect.x + textRect.width) {
    return nsCSSAnonBoxes::mozTreeCellText();
  }
  return nsCSSAnonBoxes::mozTreeCell();
}

void nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow,
                                nsTreeColumn** aCol,
                                nsCSSAnonBoxPseudoStaticAtom** aChildElt) {
  *aCol = nullptr;
  *aChildElt = nullptr;

  *aRow = GetRowAtInternal(aX, aY);
  if (*aRow < 0) {
    return;
  }

  // Determine the column hit.
  for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
       currCol = currCol->GetNext()) {
    nsRect cellRect;
    nsresult rv = currCol->GetRect(
        this, mInnerBox.y + mRowHeight * (*aRow - mTopRowIndex), mRowHeight,
        &cellRect);
    if (NS_FAILED(rv)) {
      MOZ_ASSERT_UNREACHABLE("column has no frame");
      continue;
    }

    if (!OffsetForHorzScroll(cellRect, false)) {
      continue;
    }

    if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) {
      // We know the column hit now.
      *aCol = currCol;

      if (currCol->IsCycler()) {
        // Cyclers contain only images.  Fill this in immediately and return.
        *aChildElt = nsCSSAnonBoxes::mozTreeImage();
      } else {
        *aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol);
      }
      break;
    }
  }
}

nsresult nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
                                       gfxContext* aRenderingContext,
                                       nscoord& aDesiredSize,
                                       nscoord& aCurrentSize) {
  MOZ_ASSERT(aCol, "aCol must not be null");
  MOZ_ASSERT(aRenderingContext, "aRenderingContext must not be null");

  // The rect for the current cell.
  nscoord colWidth;
  nsresult rv = aCol->GetWidthInTwips(this, &colWidth);
  NS_ENSURE_SUCCESS(rv, rv);

  nsRect cellRect(0, 0, colWidth, mRowHeight);

  int32_t overflow =
      cellRect.x + cellRect.width - (mInnerBox.x + mInnerBox.width);
  if (overflow > 0) {
    cellRect.width -= overflow;
  }

  // Adjust borders and padding for the cell.
  ComputedStyle* cellContext =
      GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
  nsMargin bp(0, 0, 0, 0);
  GetBorderPadding(cellContext, bp);

  aCurrentSize = cellRect.width;
  aDesiredSize = bp.left + bp.right;
  nsCOMPtr<nsITreeView> view = GetExistingView();

  if (aCol->IsPrimary()) {
    // If the current Column is a Primary, then we need to take into account
    // the indentation and possibly a twisty.

    // The amount of indentation is the indentation width (|mIndentation|) by
    // the level.
    int32_t level;
    view->GetLevel(aRow, &level);
    aDesiredSize += mIndentation * level;

    // Find the twisty rect by computing its size.
    ComputedStyle* twistyContext =
        GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());

    nsRect imageSize;
    nsRect twistyRect(cellRect);
    GetTwistyRect(aRow, aCol, imageSize, twistyRect, PresContext(),
                  twistyContext);

    // Add in the margins of the twisty element.
    nsMargin twistyMargin;
    twistyContext->StyleMargin()->GetMargin(twistyMargin);
    twistyRect.Inflate(twistyMargin);

    aDesiredSize += twistyRect.width;
  }

  ComputedStyle* imageContext =
      GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeImage());

  // Account for the width of the cell image.
  nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext);
  // Add in the margins of the cell image.
  nsMargin imageMargin;
  imageContext->StyleMargin()->GetMargin(imageMargin);
  imageSize.Inflate(imageMargin);

  aDesiredSize += imageSize.width;

  // Get the cell text.
  nsAutoString cellText;
  view->GetCellText(aRow, aCol, cellText);
  // We're going to measure this text so we need to ensure bidi is enabled if
  // necessary
  CheckTextForBidi(cellText);

  ComputedStyle* textContext =
      GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());

  // Get the borders and padding for the text.
  GetBorderPadding(textContext, bp);

  RefPtr<nsFontMetrics> fm =
      nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, PresContext());
  // Get the width of the text itself
  nscoord width = nsLayoutUtils::AppUnitWidthOfStringBidi(cellText, this, *fm,
                                                          *aRenderingContext);
  nscoord totalTextWidth = width + bp.left + bp.right;
  aDesiredSize += totalTextWidth;
  return NS_OK;
}

nsresult nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsTreeColumn* aCol,
                                        bool* _retval) {
  nscoord currentSize, desiredSize;
  nsresult rv;

  if (!aCol) {
    return NS_ERROR_INVALID_ARG;
  }

  UniquePtr<gfxContext> rc = PresShell()->CreateReferenceRenderingContext();

  rv = GetCellWidth(aRow, aCol, rc.get(), desiredSize, currentSize);
  NS_ENSURE_SUCCESS(rv, rv);

  *_retval = desiredSize > currentSize;

  return NS_OK;
}

nsresult nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID,
                                      nsTimerCallbackFunc aFunc, int32_t aType,
                                      nsITimer** aTimer, const char* aName) {
  // Get the delay from the look and feel service.
  int32_t delay = LookAndFeel::GetInt(aID, 0);

  nsCOMPtr<nsITimer> timer;

  // Create a new timer only if the delay is greater than zero.
  // Zero value means that this feature is completely disabled.
  if (delay > 0) {
    MOZ_TRY_VAR(timer,
                NS_NewTimerWithFuncCallback(aFunc, this, delay, aType, aName,
                                            GetMainThreadSerialEventTarget()));
  }

  timer.forget(aTimer);
  return NS_OK;
}

nsresult nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount) {
  if (aCount == 0 || !mView) {
    return NS_OK;  // Nothing to do.
  }

#ifdef ACCESSIBILITY
  if (GetAccService()) {
    FireRowCountChangedEvent(aIndex, aCount);
  }
#endif  // #ifdef ACCESSIBILITY

  AutoWeakFrame weakFrame(this);

  // Adjust our selection.
  if (nsCOMPtr<nsITreeSelection> sel = GetSelection()) {
    sel->AdjustSelection(aIndex, aCount);
  }

  NS_ENSURE_STATE(weakFrame.IsAlive());

  if (mUpdateBatchNest) {
    return NS_OK;
  }

  mRowCount += aCount;
#ifdef DEBUG
  int32_t rowCount = mRowCount;
  mView->GetRowCount(&rowCount);
  NS_ASSERTION(
      rowCount == mRowCount,
      "row count did not change by the amount suggested, check caller");
#endif

  int32_t count = Abs(aCount);
  int32_t last = LastVisibleRow();
  if (aIndex >= mTopRowIndex && aIndex <= last) {
    InvalidateRange(aIndex, last);
  }

  ScrollParts parts = GetScrollParts();

  if (mTopRowIndex == 0) {
    // Just update the scrollbar and return.
    FullScrollbarsUpdate(false);
    return NS_OK;
  }

  bool needsInvalidation = false;
  // Adjust our top row index.
  if (aCount > 0) {
    if (mTopRowIndex > aIndex) {
      // Rows came in above us.  Augment the top row index.
      mTopRowIndex += aCount;
    }
  } else if (aCount < 0) {
    if (mTopRowIndex > aIndex + count - 1) {
      // No need to invalidate. The remove happened
      // completely above us (offscreen).
      mTopRowIndex -= count;
    } else if (mTopRowIndex >= aIndex) {
      // This is a full-blown invalidate.
      if (mTopRowIndex + mPageLength > mRowCount - 1) {
        mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
      }
      needsInvalidation = true;
    }
  }

  FullScrollbarsUpdate(needsInvalidation);
  return NS_OK;
}

nsresult nsTreeBodyFrame::BeginUpdateBatch() {
  ++mUpdateBatchNest;

  return NS_OK;
}

nsresult nsTreeBodyFrame::EndUpdateBatch() {
  NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");

  if (--mUpdateBatchNest != 0) {
    return NS_OK;
  }

  nsCOMPtr<nsITreeView> view = GetExistingView();
  if (!view) {
    return NS_OK;
  }

  Invalidate();
  int32_t countBeforeUpdate = mRowCount;
  view->GetRowCount(&mRowCount);
  if (countBeforeUpdate != mRowCount) {
    if (mTopRowIndex + mPageLength > mRowCount - 1) {
      mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
    }
    FullScrollbarsUpdate(false);
  }

  return NS_OK;
}

void nsTreeBodyFrame::PrefillPropertyArray(int32_t aRowIndex,
                                           nsTreeColumn* aCol) {
  MOZ_ASSERT(!aCol || aCol->GetFrame(), "invalid column passed");
  mScratchArray.Clear();

  // focus
  if (mFocused) {
    mScratchArray.AppendElement(nsGkAtoms::focus);
  } else {
    mScratchArray.AppendElement(nsGkAtoms::blur);
  }

  // sort
  bool sorted = false;
  mView->IsSorted(&sorted);
  if (sorted) {
    mScratchArray.AppendElement(nsGkAtoms::sorted);
  }

  // drag session
  if (mSlots && mSlots->mIsDragging) {
    mScratchArray.AppendElement(nsGkAtoms::dragSession);
  }

  if (aRowIndex != -1) {
    if (aRowIndex == mMouseOverRow) {
      mScratchArray.AppendElement(nsGkAtoms::hover);
    }

    nsCOMPtr<nsITreeSelection> selection = GetSelection();
    if (selection) {
      // selected
      bool isSelected;
      selection->IsSelected(aRowIndex, &isSelected);
      if (isSelected) {
        mScratchArray.AppendElement(nsGkAtoms::selected);
      }

      // current
      int32_t currentIndex;
      selection->GetCurrentIndex(¤tIndex);
      if (aRowIndex == currentIndex) {
        mScratchArray.AppendElement(nsGkAtoms::current);
      }
    }

    // container or leaf
    bool isContainer = false;
    mView->IsContainer(aRowIndex, &isContainer);
    if (isContainer) {
      mScratchArray.AppendElement(nsGkAtoms::container);

      // open or closed
      bool isOpen = false;
      mView->IsContainerOpen(aRowIndex, &isOpen);
      if (isOpen) {
        mScratchArray.AppendElement(nsGkAtoms::open);
      } else {
        mScratchArray.AppendElement(nsGkAtoms::closed);
      }
    } else {
      mScratchArray.AppendElement(nsGkAtoms::leaf);
    }

    // drop orientation
    if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) {
      if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) {
        mScratchArray.AppendElement(nsGkAtoms::dropBefore);
      } else if (mSlots->mDropOrient == nsITreeView::DROP_ON) {
        mScratchArray.AppendElement(nsGkAtoms::dropOn);
      } else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER) {
        mScratchArray.AppendElement(nsGkAtoms::dropAfter);
      }
    }

    // odd or even
    if (aRowIndex % 2) {
      mScratchArray.AppendElement(nsGkAtoms::odd);
    } else {
      mScratchArray.AppendElement(nsGkAtoms::even);
    }

    XULTreeElement* tree = GetBaseElement();
    if (tree && tree->HasAttr(nsGkAtoms::editing)) {
      mScratchArray.AppendElement(nsGkAtoms::editing);
    }

    // multiple columns
    if (mColumns->GetColumnAt(1)) {
      mScratchArray.AppendElement(nsGkAtoms::multicol);
    }
  }

  if (aCol) {
    mScratchArray.AppendElement(aCol->GetAtom());

    if (aCol->IsPrimary()) {
      mScratchArray.AppendElement(nsGkAtoms::primary);
    }

    if (aCol->GetType() == TreeColumn_Binding::TYPE_CHECKBOX) {
      mScratchArray.AppendElement(nsGkAtoms::checkbox);

      if (aRowIndex != -1) {
        nsAutoString value;
        mView->GetCellValue(aRowIndex, aCol, value);
        if (value.EqualsLiteral("true")) {
          mScratchArray.AppendElement(nsGkAtoms::checked);
        }
      }
    }

    if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ordinal,
                                    u"1"_ns, eIgnoreCase)) {
      mScratchArray.AppendElement(nsGkAtoms::firstColumn);
    }

    // Read special properties from attributes on the column content node
    if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertbefore,
                                    nsGkAtoms::_true, eCaseMatters)) {
      mScratchArray.AppendElement(nsGkAtoms::insertbefore);
    }
    if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertafter,
                                    nsGkAtoms::_true, eCaseMatters)) {
      mScratchArray.AppendElement(nsGkAtoms::insertafter);
    }
  }
}

void nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex, nsTreeColumn* aColumn,
                                    nsRect& aImageRect, nsRect& aTwistyRect,
                                    nsPresContext* aPresContext,
                                    ComputedStyle* aTwistyContext) {
  // The twisty rect extends all the way to the end of the cell.  This is
  // incorrect.  We need to determine the twisty rect's true width.  This is
  // done by examining the ComputedStyle for a width first.  If it has one, we
  // use that.  If it doesn't, we use the image's natural width. If the image
  // hasn't loaded and if no width is specified, then we just bail. If there is
  // a -moz-appearance involved, adjust the rect by the minimum widget size
  // provided by the theme implementation.
  aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext);
  if (aImageRect.height > aTwistyRect.height) {
    aImageRect.height = aTwistyRect.height;
  }
  if (aImageRect.width > aTwistyRect.width) {
    aImageRect.width = aTwistyRect.width;
  } else {
    aTwistyRect.width = aImageRect.width;
  }
}

already_AddRefed<imgIContainer> nsTreeBodyFrame::GetImage(
    int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
    ComputedStyle* aComputedStyle) {
  Document* doc = PresContext()->Document();
  nsAutoString imageSrc;
  mView->GetImageSrc(aRowIndex, aCol, imageSrc);
  RefPtr<imgRequestProxy> styleRequest;
  nsCOMPtr<nsIURI> uri;
  if (aUseContext || imageSrc.IsEmpty()) {
    // Obtain the URL from the ComputedStyle.
    styleRequest =
        aComputedStyle->StyleList()->mListStyleImage.GetImageRequest();
    if (!styleRequest) {
      return nullptr;
    }
    styleRequest->GetURI(getter_AddRefs(uri));
  } else {
    nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), imageSrc,
                                              doc, mContent->GetBaseURI());
  }
  if (!uri) {
    return nullptr;
  }
  // Look the image up in our cache.
  nsTreeImageCacheEntry entry;
  if (mImageCache.Get(uri, &entry)) {
    nsCOMPtr<imgIContainer> result;
    entry.request->GetImage(getter_AddRefs(result));
    entry.listener->AddCell(aRowIndex, aCol);
    return result.forget();
  }

  // Create a new nsTreeImageListener object and pass it our row and column
  // information.
  auto listener = MakeRefPtr<nsTreeImageListener>(this);
  listener->AddCell(aRowIndex, aCol);

  RefPtr<imgRequestProxy> imageRequest;
  if (styleRequest) {
    styleRequest->SyncClone(listener, doc, getter_AddRefs(imageRequest));
  } else {
    auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
    // XXXbz what's the origin principal for this stuff that comes from our
    // view?  I guess we should assume that it's the node's principal...
    nsContentUtils::LoadImage(uri, mContent, doc, mContent->NodePrincipal(), 0,
                              referrerInfo, listener, nsIRequest::LOAD_NORMAL,
                              u""_ns, getter_AddRefs(imageRequest));
  }
  listener->UnsuppressInvalidation();
  if (!imageRequest) {
    return nullptr;
  }

  // NOTE(heycam): If it's an SVG image, and we need to want the image to
  // able to respond to media query changes, it needs to be added to the
  // document's ImageTracker.  For now, assume we don't need this.
  // We don't want discarding/decode-on-draw for xul images
  imageRequest->StartDecoding(imgIContainer::FLAG_ASYNC_NOTIFY);
  imageRequest->LockImage();

  // In a case it was already cached.
  mImageCache.InsertOrUpdate(uri,
                             nsTreeImageCacheEntry(imageRequest, listener));
  nsCOMPtr<imgIContainer> result;
  imageRequest->GetImage(getter_AddRefs(result));
  return result.forget();
}

nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol,
                                     bool aUseContext,
                                     ComputedStyle* aComputedStyle) {
  // XXX We should respond to visibility rules for collapsed vs. hidden.

  // This method returns the width of the twisty INCLUDING borders and padding.
  // It first checks the ComputedStyle for a width.  If none is found, it tries
  // to use the default image width for the twisty.  If no image is found, it
  // defaults to border+padding.
  nsRect r(0, 0, 0, 0);
  nsMargin bp(0, 0, 0, 0);
  GetBorderPadding(aComputedStyle, bp);
  r.Inflate(bp);

  // Now r contains our border+padding info.  We now need to get our width and
  // height.
  bool needWidth = false;
  bool needHeight = false;

  // We have to load image even though we already have a size.
  // Don't change this, otherwise things start to go awry.
  nsCOMPtr<imgIContainer> image =
      GetImage(aRowIndex, aCol, aUseContext, aComputedStyle);

  const nsStylePosition* myPosition = aComputedStyle->StylePosition();
  if (myPosition->GetWidth().ConvertsToLength()) {
    int32_t val = myPosition->GetWidth().ToLength();
    r.width += val;
  } else {
    needWidth = true;
  }

  if (myPosition->GetHeight().ConvertsToLength()) {
    int32_t val = myPosition->GetHeight().ToLength();
    r.height += val;
  } else {
    needHeight = true;
  }

  if (image) {
    if (needWidth || needHeight) {
      // Get the natural image size.

      if (needWidth) {
        // Get the size from the image.
        nscoord width;
        image->GetWidth(&width);
        r.width += nsPresContext::CSSPixelsToAppUnits(width);
      }

      if (needHeight) {
        nscoord height;
        image->GetHeight(&height);
        r.height += nsPresContext::CSSPixelsToAppUnits(height);
      }
    }
  }

  return r;
}

// GetImageDestSize returns the destination size of the image.
// The width and height do not include borders and padding.
// The width and height have not been adjusted to fit in the row height
// or cell width.
// The width and height reflect the destination size specified in CSS,
// or the image region specified in CSS, or the natural size of the
// image.
// If only the destination width has been specified in CSS, the height is
// calculated to maintain the aspect ratio of the image.
// If only the destination height has been specified in CSS, the width is
// calculated to maintain the aspect ratio of the image.
nsSize nsTreeBodyFrame::GetImageDestSize(ComputedStyle* aComputedStyle,
                                         imgIContainer* image) {
  nsSize size(0, 0);

  // We need to get the width and height.
  bool needWidth = false;
  bool needHeight = false;

  // Get the style position to see if the CSS has specified the
  // destination width/height.
  const nsStylePosition* myPosition = aComputedStyle->StylePosition();

  if (myPosition->GetWidth().ConvertsToLength()) {
    // CSS has specified the destination width.
    size.width = myPosition->GetWidth().ToLength();
  } else {
    // We'll need to get the width of the image/region.
    needWidth = true;
  }

  if (myPosition->GetHeight().ConvertsToLength()) {
    // CSS has specified the destination height.
    size.height = myPosition->GetHeight().ToLength();
  } else {
    // We'll need to get the height of the image/region.
    needHeight = true;
  }

  if (needWidth || needHeight) {
    // We need to get the size of the image/region.
    nsSize imageSize(0, 0);
    if (image) {
      nscoord width;
      image->GetWidth(&width);
      imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
      nscoord height;
      image->GetHeight(&height);
      imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
    }

    if (needWidth) {
      if (!needHeight && imageSize.height != 0) {
        // The CSS specified the destination height, but not the destination
        // width. We need to calculate the width so that we maintain the
        // image's aspect ratio.
        size.width = imageSize.width * size.height / imageSize.height;
      } else {
        size.width = imageSize.width;
      }
    }

    if (needHeight) {
      if (!needWidth && imageSize.width != 0) {
        // The CSS specified the destination width, but not the destination
        // height. We need to calculate the height so that we maintain the
        // image's aspect ratio.
        size.height = imageSize.height * size.width / imageSize.width;
      } else {
        size.height = imageSize.height;
      }
    }
  }

  return size;
}

// GetImageSourceRect returns the source rectangle of the image to be
// displayed.
// The width and height reflect the image region specified in CSS, or
// the natural size of the image.
// The width and height do not include borders and padding.
// The width and height do not reflect the destination size specified
// in CSS.
nsRect nsTreeBodyFrame::GetImageSourceRect(ComputedStyle* aComputedStyle,
                                           imgIContainer* image) {
  if (!image) {
    return nsRect();
  }

  nsRect r;
  // Use the actual image size.
  nscoord coord;
  if (NS_SUCCEEDED(image->GetWidth(&coord))) {
    r.width = nsPresContext::CSSPixelsToAppUnits(coord);
  }
  if (NS_SUCCEEDED(image->GetHeight(&coord))) {
    r.height = nsPresContext::CSSPixelsToAppUnits(coord);
  }
  return r;
}

int32_t nsTreeBodyFrame::GetRowHeight() {
  // Look up the correct height.  It is equal to the specified height
  // + the specified margins.
  mScratchArray.Clear();
  ComputedStyle* rowContext =
      GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
  if (rowContext) {
    const nsStylePosition* myPosition = rowContext->StylePosition();

    nscoord minHeight = 0;
    if (myPosition->GetMinHeight().ConvertsToLength()) {
      minHeight = myPosition->GetMinHeight().ToLength();
    }

    nscoord height = 0;
    if (myPosition->GetHeight().ConvertsToLength()) {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=95 G=93

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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 und die Messung sind noch experimentell.