This patch was written with the help of the following script. Also, manually
add mozilla qualifier to the enum values in nsStyleCoord.h, gfxRect.h, and
Types.h to make it build.
function rename() {
find .\
-type f\
! -path "./obj*"\
! -path "./.git"\
! -path "./.hg"\
\( -name "*.cpp" -or\
-name "*.h" \)\
-exec sed -i -e "s/$1/$2/g" "{}" \;
}
rename "NS_SIDE_TOP" "eSideTop"
rename "NS_SIDE_RIGHT" "eSideRight"
rename "NS_SIDE_BOTTOM" "eSideBottom"
rename "NS_SIDE_LEFT" "eSideLeft"
MozReview-Commit-ID: 9T0ORsqM6nP
472 lines
15 KiB
C++
472 lines
15 KiB
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 "DOMIntersectionObserver.h"
|
|
#include "nsCSSParser.h"
|
|
#include "nsCSSPropertyID.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsLayoutUtils.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserverEntry)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserverEntry)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserverEntry)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMIntersectionObserverEntry, mOwner,
|
|
mRootBounds, mBoundingClientRect,
|
|
mIntersectionRect, mTarget)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMIntersectionObserver)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_ENTRY(DOMIntersectionObserver)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMIntersectionObserver)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMIntersectionObserver)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMIntersectionObserver)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMIntersectionObserver)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMIntersectionObserver)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRoot)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMIntersectionObserver)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRoot)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
already_AddRefed<DOMIntersectionObserver>
|
|
DOMIntersectionObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal,
|
|
mozilla::dom::IntersectionCallback& aCb,
|
|
mozilla::ErrorResult& aRv)
|
|
{
|
|
return Constructor(aGlobal, aCb, IntersectionObserverInit(), aRv);
|
|
}
|
|
|
|
already_AddRefed<DOMIntersectionObserver>
|
|
DOMIntersectionObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal,
|
|
mozilla::dom::IntersectionCallback& aCb,
|
|
const mozilla::dom::IntersectionObserverInit& aOptions,
|
|
mozilla::ErrorResult& aRv)
|
|
{
|
|
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
|
|
if (!window) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return nullptr;
|
|
}
|
|
RefPtr<DOMIntersectionObserver> observer =
|
|
new DOMIntersectionObserver(window.forget(), aCb);
|
|
|
|
observer->mRoot = aOptions.mRoot;
|
|
|
|
if (!observer->SetRootMargin(aOptions.mRootMargin)) {
|
|
aRv.ThrowDOMException(NS_ERROR_DOM_SYNTAX_ERR,
|
|
NS_LITERAL_CSTRING("rootMargin must be specified in pixels or percent."));
|
|
return nullptr;
|
|
}
|
|
|
|
if (aOptions.mThreshold.IsDoubleSequence()) {
|
|
const mozilla::dom::Sequence<double>& thresholds = aOptions.mThreshold.GetAsDoubleSequence();
|
|
observer->mThresholds.SetCapacity(thresholds.Length());
|
|
for (const auto& thresh : thresholds) {
|
|
if (thresh < 0.0 || thresh > 1.0) {
|
|
aRv.ThrowTypeError<dom::MSG_THRESHOLD_RANGE_ERROR>();
|
|
return nullptr;
|
|
}
|
|
observer->mThresholds.AppendElement(thresh);
|
|
}
|
|
observer->mThresholds.Sort();
|
|
} else {
|
|
double thresh = aOptions.mThreshold.GetAsDouble();
|
|
if (thresh < 0.0 || thresh > 1.0) {
|
|
aRv.ThrowTypeError<dom::MSG_THRESHOLD_RANGE_ERROR>();
|
|
return nullptr;
|
|
}
|
|
observer->mThresholds.AppendElement(thresh);
|
|
}
|
|
|
|
return observer.forget();
|
|
}
|
|
|
|
bool
|
|
DOMIntersectionObserver::SetRootMargin(const nsAString& aString)
|
|
{
|
|
// By not passing a CSS Loader object we make sure we don't parse in quirks
|
|
// mode so that pixel/percent and unit-less values will be differentiated.
|
|
nsCSSParser parser(nullptr);
|
|
nsCSSValue value;
|
|
if (!parser.ParseMarginString(aString, nullptr, 0, value, true)) {
|
|
return false;
|
|
}
|
|
|
|
mRootMargin = value.GetRectValue();
|
|
|
|
for (auto side : nsCSSRect::sides) {
|
|
nsCSSValue& value = mRootMargin.*side;
|
|
if (!(value.IsPixelLengthUnit() || value.IsPercentLengthUnit())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::GetRootMargin(mozilla::dom::DOMString& aRetVal)
|
|
{
|
|
mRootMargin.AppendToString(eCSSProperty_DOM, aRetVal, nsCSSValue::eNormalized);
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::GetThresholds(nsTArray<double>& aRetVal)
|
|
{
|
|
aRetVal = mThresholds;
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::Observe(Element& aTarget)
|
|
{
|
|
if (mObservationTargets.Contains(&aTarget)) {
|
|
return;
|
|
}
|
|
aTarget.RegisterIntersectionObserver(this);
|
|
mObservationTargets.PutEntry(&aTarget);
|
|
Connect();
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::Unobserve(Element& aTarget)
|
|
{
|
|
if (UnlinkTarget(aTarget)) {
|
|
aTarget.UnregisterIntersectionObserver(this);
|
|
}
|
|
}
|
|
|
|
bool
|
|
DOMIntersectionObserver::UnlinkTarget(Element& aTarget)
|
|
{
|
|
if (!mObservationTargets.Contains(&aTarget)) {
|
|
return false;
|
|
}
|
|
if (mObservationTargets.Count() == 1) {
|
|
Disconnect();
|
|
return false;
|
|
}
|
|
mObservationTargets.RemoveEntry(&aTarget);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::Connect()
|
|
{
|
|
if (mConnected) {
|
|
return;
|
|
}
|
|
nsIDocument* document = mOwner->GetExtantDoc();
|
|
document->AddIntersectionObserver(this);
|
|
mConnected = true;
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::Disconnect()
|
|
{
|
|
if (!mConnected) {
|
|
return;
|
|
}
|
|
for (auto iter = mObservationTargets.Iter(); !iter.Done(); iter.Next()) {
|
|
Element* target = iter.Get()->GetKey();
|
|
target->UnregisterIntersectionObserver(this);
|
|
}
|
|
mObservationTargets.Clear();
|
|
if (mOwner) {
|
|
nsIDocument* document = mOwner->GetExtantDoc();
|
|
document->RemoveIntersectionObserver(this);
|
|
}
|
|
mConnected = false;
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::TakeRecords(nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal)
|
|
{
|
|
aRetVal.SwapElements(mQueuedEntries);
|
|
mQueuedEntries.Clear();
|
|
}
|
|
|
|
static bool
|
|
CheckSimilarOrigin(nsINode* aNode1, nsINode* aNode2)
|
|
{
|
|
nsIPrincipal* principal1 = aNode1->NodePrincipal();
|
|
nsIPrincipal* principal2 = aNode2->NodePrincipal();
|
|
nsAutoCString baseDomain1;
|
|
nsAutoCString baseDomain2;
|
|
|
|
nsresult rv = principal1->GetBaseDomain(baseDomain1);
|
|
if (NS_FAILED(rv)) {
|
|
return principal1 == principal2;
|
|
}
|
|
|
|
rv = principal2->GetBaseDomain(baseDomain2);
|
|
if (NS_FAILED(rv)) {
|
|
return principal1 == principal2;
|
|
}
|
|
|
|
return baseDomain1 == baseDomain2;
|
|
}
|
|
|
|
static Maybe<nsRect>
|
|
EdgeInclusiveIntersection(const nsRect& aRect, const nsRect& aOtherRect)
|
|
{
|
|
nscoord left = std::max(aRect.x, aOtherRect.x);
|
|
nscoord top = std::max(aRect.y, aOtherRect.y);
|
|
nscoord right = std::min(aRect.XMost(), aOtherRect.XMost());
|
|
nscoord bottom = std::min(aRect.YMost(), aOtherRect.YMost());
|
|
if (left > right || top > bottom) {
|
|
return Nothing();
|
|
}
|
|
return Some(nsRect(left, top, right - left, bottom - top));
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::Update(nsIDocument* aDocument, DOMHighResTimeStamp time)
|
|
{
|
|
Element* root = nullptr;
|
|
nsIFrame* rootFrame = nullptr;
|
|
nsRect rootRect;
|
|
|
|
if (mRoot) {
|
|
root = mRoot;
|
|
rootFrame = root->GetPrimaryFrame();
|
|
if (rootFrame) {
|
|
if (rootFrame->GetType() == nsGkAtoms::scrollFrame) {
|
|
nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame);
|
|
rootRect = nsLayoutUtils::TransformFrameRectToAncestor(
|
|
rootFrame,
|
|
rootFrame->GetContentRectRelativeToSelf(),
|
|
scrollFrame->GetScrolledFrame());
|
|
} else {
|
|
rootRect = nsLayoutUtils::GetAllInFlowRectsUnion(rootFrame,
|
|
nsLayoutUtils::GetContainingBlockForClientRect(rootFrame),
|
|
nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
|
|
}
|
|
}
|
|
} else {
|
|
nsCOMPtr<nsIPresShell> presShell = aDocument->GetShell();
|
|
if (presShell) {
|
|
rootFrame = presShell->GetRootScrollFrame();
|
|
if (rootFrame) {
|
|
nsPresContext* presContext = rootFrame->PresContext();
|
|
while (!presContext->IsRootContentDocument()) {
|
|
presContext = rootFrame->PresContext()->GetParentPresContext();
|
|
rootFrame = presContext->PresShell()->GetRootScrollFrame();
|
|
}
|
|
root = rootFrame->GetContent()->AsElement();
|
|
nsIScrollableFrame* scrollFrame = do_QueryFrame(rootFrame);
|
|
rootRect = scrollFrame->GetScrollPortRect();
|
|
}
|
|
}
|
|
}
|
|
|
|
nsMargin rootMargin;
|
|
NS_FOR_CSS_SIDES(side) {
|
|
nscoord basis = side == eSideTop || side == eSideBottom ?
|
|
rootRect.height : rootRect.width;
|
|
nsCSSValue value = mRootMargin.*nsCSSRect::sides[side];
|
|
nsStyleCoord coord;
|
|
if (value.IsPixelLengthUnit()) {
|
|
coord.SetCoordValue(value.GetPixelLength());
|
|
} else if (value.IsPercentLengthUnit()) {
|
|
coord.SetPercentValue(value.GetPercentValue());
|
|
} else {
|
|
MOZ_ASSERT_UNREACHABLE("invalid length unit");
|
|
}
|
|
rootMargin.Side(side) = nsLayoutUtils::ComputeCBDependentValue(basis, coord);
|
|
}
|
|
|
|
for (auto iter = mObservationTargets.Iter(); !iter.Done(); iter.Next()) {
|
|
Element* target = iter.Get()->GetKey();
|
|
nsIFrame* targetFrame = target->GetPrimaryFrame();
|
|
nsRect targetRect;
|
|
Maybe<nsRect> intersectionRect;
|
|
|
|
if (rootFrame && targetFrame) {
|
|
// If mRoot is set we are testing intersection with a container element
|
|
// instead of the implicit root.
|
|
if (mRoot) {
|
|
// Skip further processing of this target if it is not in the same
|
|
// Document as the intersection root, e.g. if root is an element of
|
|
// the main document and target an element from an embedded iframe.
|
|
if (target->GetComposedDoc() != root->GetComposedDoc()) {
|
|
continue;
|
|
}
|
|
// Skip further processing of this target if is not a descendant of the
|
|
// intersection root in the containing block chain. E.g. this would be
|
|
// the case if the target is in a position:absolute element whose
|
|
// containing block is an ancestor of root.
|
|
if (!nsLayoutUtils::IsAncestorFrameCrossDoc(rootFrame, targetFrame)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
targetRect = nsLayoutUtils::GetAllInFlowRectsUnion(
|
|
targetFrame,
|
|
nsLayoutUtils::GetContainingBlockForClientRect(targetFrame),
|
|
nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS
|
|
);
|
|
intersectionRect = Some(targetFrame->GetVisualOverflowRect());
|
|
|
|
nsIFrame* containerFrame = nsLayoutUtils::GetCrossDocParentFrame(targetFrame);
|
|
while (containerFrame && containerFrame != rootFrame) {
|
|
if (containerFrame->GetType() == nsGkAtoms::scrollFrame) {
|
|
nsIScrollableFrame* scrollFrame = do_QueryFrame(containerFrame);
|
|
nsRect subFrameRect = scrollFrame->GetScrollPortRect();
|
|
nsRect intersectionRectRelativeToContainer =
|
|
nsLayoutUtils::TransformFrameRectToAncestor(targetFrame,
|
|
intersectionRect.value(),
|
|
containerFrame);
|
|
intersectionRect = EdgeInclusiveIntersection(intersectionRectRelativeToContainer,
|
|
subFrameRect);
|
|
if (!intersectionRect) {
|
|
break;
|
|
}
|
|
targetFrame = containerFrame;
|
|
}
|
|
|
|
// TODO: Apply clip-path.
|
|
|
|
containerFrame = nsLayoutUtils::GetCrossDocParentFrame(containerFrame);
|
|
}
|
|
}
|
|
|
|
nsRect rootIntersectionRect = rootRect;
|
|
bool isInSimilarOriginBrowsingContext = rootFrame && targetFrame &&
|
|
CheckSimilarOrigin(root, target);
|
|
|
|
if (isInSimilarOriginBrowsingContext) {
|
|
rootIntersectionRect.Inflate(rootMargin);
|
|
}
|
|
|
|
if (intersectionRect.isSome()) {
|
|
nsRect intersectionRectRelativeToRoot =
|
|
nsLayoutUtils::TransformFrameRectToAncestor(
|
|
targetFrame,
|
|
intersectionRect.value(),
|
|
nsLayoutUtils::GetContainingBlockForClientRect(rootFrame)
|
|
);
|
|
intersectionRect = EdgeInclusiveIntersection(
|
|
intersectionRectRelativeToRoot,
|
|
rootIntersectionRect
|
|
);
|
|
if (intersectionRect.isSome()) {
|
|
intersectionRect = Some(nsLayoutUtils::TransformFrameRectToAncestor(
|
|
nsLayoutUtils::GetContainingBlockForClientRect(rootFrame),
|
|
intersectionRect.value(),
|
|
targetFrame->PresContext()->PresShell()->GetRootScrollFrame()
|
|
));
|
|
}
|
|
}
|
|
|
|
double targetArea = targetRect.width * targetRect.height;
|
|
double intersectionArea = !intersectionRect ?
|
|
0 : intersectionRect->width * intersectionRect->height;
|
|
double intersectionRatio = targetArea > 0.0 ? intersectionArea / targetArea : 0.0;
|
|
|
|
size_t threshold = -1;
|
|
if (intersectionRatio > 0.0) {
|
|
if (intersectionRatio >= 1.0) {
|
|
intersectionRatio = 1.0;
|
|
threshold = mThresholds.Length();
|
|
} else {
|
|
for (size_t k = 0; k < mThresholds.Length(); ++k) {
|
|
if (mThresholds[k] <= intersectionRatio) {
|
|
threshold = k + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (intersectionRect.isSome()) {
|
|
threshold = 0;
|
|
}
|
|
|
|
if (target->UpdateIntersectionObservation(this, threshold)) {
|
|
QueueIntersectionObserverEntry(
|
|
target, time,
|
|
isInSimilarOriginBrowsingContext ? Some(rootIntersectionRect) : Nothing(),
|
|
targetRect, intersectionRect, intersectionRatio
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::QueueIntersectionObserverEntry(Element* aTarget,
|
|
DOMHighResTimeStamp time,
|
|
const Maybe<nsRect>& aRootRect,
|
|
const nsRect& aTargetRect,
|
|
const Maybe<nsRect>& aIntersectionRect,
|
|
double aIntersectionRatio)
|
|
{
|
|
RefPtr<DOMRect> rootBounds;
|
|
if (aRootRect.isSome()) {
|
|
rootBounds = new DOMRect(this);
|
|
rootBounds->SetLayoutRect(aRootRect.value());
|
|
}
|
|
RefPtr<DOMRect> boundingClientRect = new DOMRect(this);
|
|
boundingClientRect->SetLayoutRect(aTargetRect);
|
|
RefPtr<DOMRect> intersectionRect = new DOMRect(this);
|
|
if (aIntersectionRect.isSome()) {
|
|
intersectionRect->SetLayoutRect(aIntersectionRect.value());
|
|
}
|
|
RefPtr<DOMIntersectionObserverEntry> entry = new DOMIntersectionObserverEntry(
|
|
this,
|
|
time,
|
|
rootBounds.forget(),
|
|
boundingClientRect.forget(),
|
|
intersectionRect.forget(),
|
|
aTarget, aIntersectionRatio);
|
|
mQueuedEntries.AppendElement(entry.forget());
|
|
}
|
|
|
|
void
|
|
DOMIntersectionObserver::Notify()
|
|
{
|
|
if (!mQueuedEntries.Length()) {
|
|
return;
|
|
}
|
|
mozilla::dom::Sequence<mozilla::OwningNonNull<DOMIntersectionObserverEntry>> entries;
|
|
if (entries.SetCapacity(mQueuedEntries.Length(), mozilla::fallible)) {
|
|
for (uint32_t i = 0; i < mQueuedEntries.Length(); ++i) {
|
|
RefPtr<DOMIntersectionObserverEntry> next = mQueuedEntries[i];
|
|
*entries.AppendElement(mozilla::fallible) = next;
|
|
}
|
|
}
|
|
mQueuedEntries.Clear();
|
|
mCallback->Call(this, entries, *this);
|
|
}
|
|
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|