Bug 1243846 - Implement Intersection Observer API. r=mrbkap, r=mstange

--HG--
extra : rebase_source : fed18f84d17be6f8d1a6196d3a0b6c66f005fa8b
extra : histedit_source : 2d2579bba67ba6bb1857ecbf92a1ab4a21e0d81e
This commit is contained in:
Tobias Schneider 2016-10-12 20:15:16 -07:00
Родитель cea0812b0c
Коммит e7229ba450
24 изменённых файлов: 2100 добавлений и 11 удалений

Просмотреть файл

@ -0,0 +1,448 @@
/* -*- 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)
double
DOMIntersectionObserverEntry::IntersectionRatio()
{
double targetArea = mBoundingClientRect->Width() * mBoundingClientRect->Height();
double intersectionArea = mIntersectionRect->Width() * mIntersectionRect->Height();
double intersectionRatio = targetArea > 0.0 ? intersectionArea / targetArea : 0.0;
return intersectionRatio;
}
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 (uint32_t i = 0; i < ArrayLength(nsCSSRect::sides); ++i) {
nsCSSValue value = mRootMargin.*nsCSSRect::sides[i];
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 (!mObservationTargets.Contains(&aTarget)) {
return;
}
if (mObservationTargets.Count() == 1) {
Disconnect();
return;
}
aTarget.UnregisterIntersectionObserver(this);
mObservationTargets.RemoveEntry(&aTarget);
}
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();
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;
nsIFrame* rootFrame;
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();
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 == NS_SIDE_TOP || side == NS_SIDE_BOTTOM ?
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 &&
(!mRoot || nsLayoutUtils::IsAncestorFrameCrossDoc(rootFrame, targetFrame))) {
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 = 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) {
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
);
}
}
}
void
DOMIntersectionObserver::QueueIntersectionObserverEntry(Element* aTarget,
DOMHighResTimeStamp time,
const Maybe<nsRect>& aRootRect,
const nsRect& aTargetRect,
const Maybe<nsRect>& aIntersectionRect)
{
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);
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

Просмотреть файл

@ -0,0 +1,172 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DOMIntersectionObserver_h
#define DOMIntersectionObserver_h
#include "mozilla/dom/IntersectionObserverBinding.h"
#include "nsCSSValue.h"
#include "nsTArray.h"
using mozilla::dom::DOMRect;
using mozilla::dom::Element;
namespace mozilla {
namespace dom {
class DOMIntersectionObserver;
class DOMIntersectionObserverEntry final : public nsISupports,
public nsWrapperCache
{
~DOMIntersectionObserverEntry() {}
public:
DOMIntersectionObserverEntry(nsISupports* aOwner,
DOMHighResTimeStamp aTime,
RefPtr<DOMRect> aRootBounds,
RefPtr<DOMRect> aBoundingClientRect,
RefPtr<DOMRect> aIntersectionRect,
Element* aTarget)
: mOwner(aOwner),
mTime(aTime),
mRootBounds(aRootBounds),
mBoundingClientRect(aBoundingClientRect),
mIntersectionRect(aIntersectionRect),
mTarget(aTarget)
{
}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMIntersectionObserverEntry)
nsISupports* GetParentObject() const
{
return mOwner;
}
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
{
return mozilla::dom::IntersectionObserverEntryBinding::Wrap(aCx, this, aGivenProto);
}
DOMHighResTimeStamp Time()
{
return mTime;
}
DOMRect* GetRootBounds()
{
return mRootBounds;
}
DOMRect* BoundingClientRect()
{
return mBoundingClientRect;
}
DOMRect* IntersectionRect()
{
return mIntersectionRect;
}
double IntersectionRatio();
Element* Target()
{
return mTarget;
}
protected:
nsCOMPtr<nsISupports> mOwner;
DOMHighResTimeStamp mTime;
RefPtr<DOMRect> mRootBounds;
RefPtr<DOMRect> mBoundingClientRect;
RefPtr<DOMRect> mIntersectionRect;
RefPtr<Element> mTarget;
};
#define NS_DOM_INTERSECTION_OBSERVER_IID \
{ 0x8570a575, 0xe303, 0x4d18, \
{ 0xb6, 0xb1, 0x4d, 0x2b, 0x49, 0xd8, 0xef, 0x94 } }
class DOMIntersectionObserver final : public nsISupports,
public nsWrapperCache
{
virtual ~DOMIntersectionObserver() { }
public:
DOMIntersectionObserver(already_AddRefed<nsPIDOMWindowInner>&& aOwner,
mozilla::dom::IntersectionCallback& aCb)
: mOwner(aOwner), mCallback(&aCb), mConnected(false)
{
}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMIntersectionObserver)
NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_INTERSECTION_OBSERVER_IID)
static already_AddRefed<DOMIntersectionObserver>
Constructor(const mozilla::dom::GlobalObject& aGlobal,
mozilla::dom::IntersectionCallback& aCb,
mozilla::ErrorResult& aRv);
static already_AddRefed<DOMIntersectionObserver>
Constructor(const mozilla::dom::GlobalObject& aGlobal,
mozilla::dom::IntersectionCallback& aCb,
const mozilla::dom::IntersectionObserverInit& aOptions,
mozilla::ErrorResult& aRv);
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
{
return mozilla::dom::IntersectionObserverBinding::Wrap(aCx, this, aGivenProto);
}
nsISupports* GetParentObject() const
{
return mOwner;
}
Element* GetRoot() const {
return mRoot;
}
void GetRootMargin(mozilla::dom::DOMString& aRetVal);
void GetThresholds(nsTArray<double>& aRetVal);
void Observe(Element& aTarget);
void Unobserve(Element& aTarget);
void Disconnect();
void TakeRecords(nsTArray<RefPtr<DOMIntersectionObserverEntry>>& aRetVal);
mozilla::dom::IntersectionCallback* IntersectionCallback() { return mCallback; }
bool SetRootMargin(const nsAString& aString);
void Update(nsIDocument* aDocument, DOMHighResTimeStamp time);
void Notify();
protected:
void Connect();
void QueueIntersectionObserverEntry(Element* aTarget,
DOMHighResTimeStamp time,
const Maybe<nsRect>& aRootRect,
const nsRect& aTargetRect,
const Maybe<nsRect>& aIntersectionRect);
nsCOMPtr<nsPIDOMWindowInner> mOwner;
RefPtr<mozilla::dom::IntersectionCallback> mCallback;
RefPtr<Element> mRoot;
nsCSSRect mRootMargin;
nsTArray<double> mThresholds;
nsTHashtable<nsPtrHashKey<Element>> mObservationTargets;
nsTArray<RefPtr<DOMIntersectionObserverEntry>> mQueuedEntries;
bool mConnected;
};
NS_DEFINE_STATIC_IID_ACCESSOR(DOMIntersectionObserver, NS_DOM_INTERSECTION_OBSERVER_IID)
} // namespace dom
} // namespace mozilla
#endif

Просмотреть файл

@ -149,6 +149,7 @@
#include "mozilla/Preferences.h"
#include "nsComputedDOMStyle.h"
#include "nsDOMStringMap.h"
#include "DOMIntersectionObserver.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -3886,3 +3887,44 @@ Element::ClearDataset()
slots->mDataset = nullptr;
}
nsTArray<Element::nsDOMSlots::IntersectionObserverRegistration>*
Element::RegisteredIntersectionObservers()
{
nsDOMSlots* slots = DOMSlots();
return &slots->mRegisteredIntersectionObservers;
}
void
Element::RegisterIntersectionObserver(DOMIntersectionObserver* aObserver)
{
RegisteredIntersectionObservers()->AppendElement(
nsDOMSlots::IntersectionObserverRegistration { aObserver, -1 });
}
void
Element::UnregisterIntersectionObserver(DOMIntersectionObserver* aObserver)
{
nsTArray<nsDOMSlots::IntersectionObserverRegistration>* observers =
RegisteredIntersectionObservers();
for (uint32_t i = 0; i < observers->Length(); ++i) {
nsDOMSlots::IntersectionObserverRegistration reg = observers->ElementAt(i);
if (reg.observer == aObserver) {
observers->RemoveElementAt(i);
break;
}
}
}
bool
Element::UpdateIntersectionObservation(DOMIntersectionObserver* aObserver, int32_t aThreshold)
{
nsTArray<nsDOMSlots::IntersectionObserverRegistration>* observers =
RegisteredIntersectionObservers();
for (auto& reg : *observers) {
if (reg.observer == aObserver && reg.previousThreshold != aThreshold) {
reg.previousThreshold = aThreshold;
return true;
}
}
return false;
}

Просмотреть файл

@ -39,6 +39,7 @@
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/Nullable.h"
#include "Units.h"
#include "DOMIntersectionObserver.h"
class nsIFrame;
class nsIDOMMozNamedAttrMap;
@ -61,6 +62,7 @@ namespace dom {
struct AnimationFilter;
struct ScrollIntoViewOptions;
struct ScrollToOptions;
class DOMIntersectionObserver;
class ElementOrCSSPseudoElement;
class UnrestrictedDoubleOrKeyframeAnimationOptions;
} // namespace dom
@ -1149,6 +1151,10 @@ public:
// to it.
void ClearDataset();
void RegisterIntersectionObserver(DOMIntersectionObserver* aObserver);
void UnregisterIntersectionObserver(DOMIntersectionObserver* aObserver);
bool UpdateIntersectionObservation(DOMIntersectionObserver* aObserver, int32_t threshold);
protected:
/*
* Named-bools for use with SetAttrAndNotify to make call sites easier to
@ -1362,6 +1368,8 @@ protected:
nsDOMTokenList* GetTokenList(nsIAtom* aAtom,
const DOMTokenListSupportedTokenArray aSupportedTokens = nullptr);
nsTArray<nsDOMSlots::IntersectionObserverRegistration>* RegisteredIntersectionObservers();
private:
/**
* Get this element's client area rect in app units.

Просмотреть файл

@ -37,6 +37,7 @@ namespace css {
class Declaration;
} // namespace css
namespace dom {
class DOMIntersectionObserver;
class Element;
} // namespace dom
} // namespace mozilla
@ -344,6 +345,16 @@ public:
* Web components custom element data.
*/
RefPtr<CustomElementData> mCustomElementData;
/**
* Registered Intersection Observers on the element.
*/
struct IntersectionObserverRegistration {
DOMIntersectionObserver* observer;
int32_t previousThreshold;
};
nsTArray<IntersectionObserverRegistration> mRegisteredIntersectionObservers;
};
protected:

Просмотреть файл

@ -165,6 +165,7 @@ EXPORTS.mozilla.dom += [
'DOMError.h',
'DOMException.h',
'DOMImplementation.h',
'DOMIntersectionObserver.h',
'DOMMatrix.h',
'DOMParser.h',
'DOMPoint.h',
@ -358,6 +359,8 @@ if CONFIG['MOZ_WEBRTC']:
# these files couldn't be in UNIFIED_SOURCES for now for reasons given below:
SOURCES += [
# Several conflicts with other bindings.
'DOMIntersectionObserver.cpp',
# Because of OS X headers.
'nsContentUtils.cpp',
# this file doesn't like windows.h

Просмотреть файл

@ -1451,6 +1451,8 @@ nsDocument::~nsDocument()
// Clear mObservers to keep it in sync with the mutationobserver list
mObservers.Clear();
mIntersectionObservers.Clear();
if (mStyleSheetSetList) {
mStyleSheetSetList->Disconnect();
}
@ -1720,6 +1722,8 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOnDemandBuiltInUASheets)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntersectionObservers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubImportLinks)
for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
@ -1806,6 +1810,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntersectionObservers)
tmp->ClearAllBoxObjects();
if (tmp->mListenerManager) {
@ -12306,6 +12312,51 @@ nsDocument::ReportUseCounters()
}
}
void
nsDocument::AddIntersectionObserver(DOMIntersectionObserver* aObserver)
{
NS_ASSERTION(mIntersectionObservers.IndexOf(aObserver) == nsTArray<int>::NoIndex,
"Intersection observer already in the list");
mIntersectionObservers.AppendElement(aObserver);
}
void
nsDocument::RemoveIntersectionObserver(DOMIntersectionObserver* aObserver)
{
mIntersectionObservers.RemoveElement(aObserver);
}
void
nsDocument::UpdateIntersectionObservations()
{
DOMHighResTimeStamp time = 0;
if (nsPIDOMWindowInner* window = GetInnerWindow()) {
Performance* perf = window->GetPerformance();
if (perf) {
time = perf->Now();
}
}
for (const auto& observer : mIntersectionObservers) {
observer->Update(this, time);
}
}
void
nsDocument::ScheduleIntersectionObserverNotification()
{
nsCOMPtr<nsIRunnable> notification = NewRunnableMethod(this,
&nsDocument::NotifyIntersectionObservers);
NS_DispatchToCurrentThread(notification);
}
void
nsDocument::NotifyIntersectionObservers()
{
for (const auto& observer : mIntersectionObservers) {
observer->Notify();
}
}
XPathEvaluator*
nsIDocument::XPathEvaluator()
{

Просмотреть файл

@ -69,6 +69,7 @@
#include "ImportManager.h"
#include "mozilla/LinkedList.h"
#include "CustomElementRegistry.h"
#include "mozilla/dom/Performance.h"
#define XML_DECLARATION_BITS_DECLARATION_EXISTS (1 << 0)
#define XML_DECLARATION_BITS_ENCODING_EXISTS (1 << 1)
@ -97,6 +98,8 @@ class BoxObject;
class ImageTracker;
struct LifecycleCallbacks;
class CallbackFunction;
class DOMIntersectionObserver;
class Performance;
struct FullscreenRequest : public LinkedListElement<FullscreenRequest>
{
@ -773,6 +776,15 @@ public:
void ReportUseCounters();
virtual void AddIntersectionObserver(
mozilla::dom::DOMIntersectionObserver* aObserver) override;
virtual void RemoveIntersectionObserver(
mozilla::dom::DOMIntersectionObserver* aObserver) override;
virtual void UpdateIntersectionObservations() override;
virtual void ScheduleIntersectionObserverNotification() override;
virtual void NotifyIntersectionObservers() override;
private:
void AddOnDemandBuiltInUASheet(mozilla::StyleSheet* aSheet);
nsRadioGroupStruct* GetRadioGroupInternal(const nsAString& aName) const;
@ -1325,6 +1337,9 @@ protected:
// Array of observers
nsTObserverArray<nsIDocumentObserver*> mObservers;
// Array of intersection observers
nsTArray<RefPtr<mozilla::dom::DOMIntersectionObserver>> mIntersectionObservers;
// Tracker for animations that are waiting to start.
// nullptr until GetOrCreatePendingAnimationTracker is called.
RefPtr<mozilla::PendingAnimationTracker> mPendingAnimationTracker;

Просмотреть файл

@ -127,6 +127,7 @@ class DocumentFragment;
class DocumentTimeline;
class DocumentType;
class DOMImplementation;
class DOMIntersectionObserver;
class DOMStringList;
class Element;
struct ElementCreationOptions;
@ -2844,6 +2845,15 @@ public:
return mHasScrollLinkedEffect;
}
virtual void AddIntersectionObserver(
mozilla::dom::DOMIntersectionObserver* aObserver) = 0;
virtual void RemoveIntersectionObserver(
mozilla::dom::DOMIntersectionObserver* aObserver) = 0;
virtual void UpdateIntersectionObservations() = 0;
virtual void ScheduleIntersectionObserverNotification() = 0;
virtual void NotifyIntersectionObservers() = 0;
protected:
bool GetUseCounter(mozilla::UseCounter aUseCounter)
{

Просмотреть файл

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#target5 {
position: absolute;
top: 0px;
left: 0px;
width: 20px;
height: 20px;
background: #f00;
}
</style>
<body>
<div id="target5"></div>
<script>
var io = new IntersectionObserver(function (records) {
parent.postMessage(records[0].rootBounds == null, 'http://mochi.test:8888');
}, {});
io.observe(document.getElementById("target5"));
</script>
</body>
</html>

Просмотреть файл

@ -236,6 +236,7 @@ support-files =
!/dom/xhr/tests/file_XHRSendData.sjs
script_bug1238440.js
file_blobURL_expiring.html
intersectionobserver_subframe.html
[test_anchor_area_referrer.html]
[test_anchor_area_referrer_changing.html]
@ -697,6 +698,7 @@ skip-if = e10s || os != 'linux' || buildapp != 'browser' # Already tests multipr
[test_img_referrer.html]
[test_innersize_scrollport.html]
[test_integer_attr_with_leading_zero.html]
[test_intersectionobservers.html]
[test_ipc_messagemanager_blob.html]
[test_link_prefetch.html]
skip-if = !e10s # Track Bug 1281415

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -581,6 +581,15 @@ DOMInterfaces = {
'notflattened': True
},
'IntersectionObserver': {
'nativeType': 'mozilla::dom::DOMIntersectionObserver',
},
'IntersectionObserverEntry': {
'nativeType': 'mozilla::dom::DOMIntersectionObserverEntry',
'headerFile': 'DOMIntersectionObserver.h',
},
'KeyEvent': {
'concrete': False
},

Просмотреть файл

@ -103,3 +103,4 @@ MSG_DEF(MSG_TOKENLIST_NO_SUPPORTED_TOKENS, 2, JSEXN_TYPEERR, "{0} attribute of <
MSG_DEF(MSG_CACHE_STREAM_CLOSED, 0, JSEXN_TYPEERR, "Response body is a cache file stream that has already been closed.")
MSG_DEF(MSG_TIME_VALUE_OUT_OF_RANGE, 1, JSEXN_TYPEERR, "{0} is outside the supported range for time values.")
MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 1, JSEXN_TYPEERR, "Request mode '{0}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.")
MSG_DEF(MSG_THRESHOLD_RANGE_ERROR, 0, JSEXN_RANGEERR, "Threshold values must all be in the range [0, 1].")

Просмотреть файл

@ -29,4 +29,11 @@ interface DOMRectReadOnly {
readonly attribute unrestricted double right;
readonly attribute unrestricted double bottom;
readonly attribute unrestricted double left;
};
};
dictionary DOMRectInit {
unrestricted double x = 0;
unrestricted double y = 0;
unrestricted double width = 0;
unrestricted double height = 0;
};

Просмотреть файл

@ -0,0 +1,59 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/.
*
* The origin of this IDL file is
* https://wicg.github.io/IntersectionObserver/
*/
[ProbablyShortLivingObject]
interface IntersectionObserverEntry {
[Constant]
readonly attribute DOMHighResTimeStamp time;
[Constant]
readonly attribute DOMRectReadOnly? rootBounds;
[Constant]
readonly attribute DOMRectReadOnly boundingClientRect;
[Constant]
readonly attribute DOMRectReadOnly intersectionRect;
[Constant]
readonly attribute double intersectionRatio;
[Constant]
readonly attribute Element target;
};
[Constructor(IntersectionCallback intersectionCallback,
optional IntersectionObserverInit options)]
interface IntersectionObserver {
[Constant]
readonly attribute Element? root;
[Constant]
readonly attribute DOMString rootMargin;
[Constant,Cached]
readonly attribute sequence<double> thresholds;
void observe(Element target);
void unobserve(Element target);
void disconnect();
sequence<IntersectionObserverEntry> takeRecords();
[ChromeOnly]
readonly attribute IntersectionCallback intersectionCallback;
};
callback IntersectionCallback =
void (sequence<IntersectionObserverEntry> entries, IntersectionObserver observer);
dictionary IntersectionObserverEntryInit {
required DOMHighResTimeStamp time;
required DOMRectInit rootBounds;
required DOMRectInit boundingClientRect;
required DOMRectInit intersectionRect;
required Element target;
};
dictionary IntersectionObserverInit {
Element? root = null;
DOMString rootMargin = "0px";
(double or sequence<double>) threshold = 0;
};

Просмотреть файл

@ -281,6 +281,7 @@ WEBIDL_FILES = [
'InputPort.webidl',
'InputPortManager.webidl',
'InspectorUtils.webidl',
'IntersectionObserver.webidl',
'IterableIterator.webidl',
'KeyAlgorithm.webidl',
'KeyboardEvent.webidl',

Просмотреть файл

@ -80,6 +80,7 @@ static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
#define DEFAULT_THROTTLED_FRAME_RATE 1
#define DEFAULT_RECOMPUTE_VISIBILITY_INTERVAL_MS 1000
#define DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS 100
// after 10 minutes, stop firing off inactive timers
#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
@ -976,6 +977,17 @@ nsRefreshDriver::GetMinRecomputeVisibilityInterval()
return TimeDuration::FromMilliseconds(interval);
}
/* static */ mozilla::TimeDuration
nsRefreshDriver::GetMinNotifyIntersectionObserversInterval()
{
int32_t interval =
Preferences::GetInt("layout.visibility.min-notify-intersection-observers-interval-ms", -1);
if (interval <= 0) {
interval = DEFAULT_NOTIFY_INTERSECTION_OBSERVERS_INTERVAL_MS;
}
return TimeDuration::FromMilliseconds(interval);
}
double
nsRefreshDriver::GetRefreshTimerInterval() const
{
@ -1018,6 +1030,8 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds(
GetThrottledTimerInterval())),
mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
mMinNotifyIntersectionObserversInterval(
GetMinNotifyIntersectionObserversInterval()),
mThrottled(false),
mNeedToRecomputeVisibility(false),
mTestControllingRefreshes(false),
@ -1038,6 +1052,7 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
mMostRecentTick = mMostRecentRefresh;
mNextThrottledFrameRequestTick = mMostRecentTick;
mNextRecomputeVisibilityTick = mMostRecentTick;
mNextNotifyIntersectionObserversTick = mMostRecentTick;
++sRefreshDriverCount;
}
@ -1828,6 +1843,22 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
presShell->ScheduleApproximateFrameVisibilityUpdateNow();
}
bool notifyIntersectionObservers = false;
if (aNowTime >= mNextNotifyIntersectionObserversTick) {
mNextNotifyIntersectionObserversTick =
aNowTime + mMinNotifyIntersectionObserversInterval;
notifyIntersectionObservers = true;
}
nsCOMArray<nsIDocument> documents;
CollectDocuments(mPresContext->Document(), &documents);
for (int32_t i = 0; i < documents.Count(); ++i) {
nsIDocument* doc = documents[i];
doc->UpdateIntersectionObservations();
if (notifyIntersectionObservers) {
doc->ScheduleIntersectionObserverNotification();
}
}
/*
* Perform notification to imgIRequests subscribed to listen
* for refresh events.

Просмотреть файл

@ -368,6 +368,7 @@ private:
static double GetThrottledTimerInterval();
static mozilla::TimeDuration GetMinRecomputeVisibilityInterval();
static mozilla::TimeDuration GetMinNotifyIntersectionObserversInterval();
bool HaveFrameRequestCallbacks() const {
return mFrameRequestCallbackDocs.Length() != 0;
@ -403,6 +404,8 @@ private:
// flush since the last time we did it.
const mozilla::TimeDuration mMinRecomputeVisibilityInterval;
const mozilla::TimeDuration mMinNotifyIntersectionObserversInterval;
bool mThrottled;
bool mNeedToRecomputeVisibility;
bool mTestControllingRefreshes;
@ -433,6 +436,7 @@ private:
mozilla::TimeStamp mTickStart;
mozilla::TimeStamp mNextThrottledFrameRequestTick;
mozilla::TimeStamp mNextRecomputeVisibilityTick;
mozilla::TimeStamp mNextNotifyIntersectionObserversTick;
// separate arrays for each flush type we support
ObserverArray mObservers[3];

Просмотреть файл

@ -232,6 +232,12 @@ public:
nsCSSValue& aValue,
bool aSuppressErrors /* false */);
bool ParseMarginString(const nsSubstring& aBuffer,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue,
bool aSuppressErrors /* false */);
nsresult ParseSelectorString(const nsSubstring& aSelectorString,
nsIURI* aURL, // for error reporting
uint32_t aLineNumber, // for error reporting
@ -1158,7 +1164,8 @@ protected:
void AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue);
bool ParseBoxProperties(const nsCSSPropertyID aPropIDs[]);
bool ParseGroupedBoxProperty(int32_t aVariantMask,
nsCSSValue& aValue);
nsCSSValue& aValue,
uint32_t aRestrictions);
bool ParseBoxCornerRadius(const nsCSSPropertyID aPropID);
bool ParseBoxCornerRadiiInternals(nsCSSValue array[]);
bool ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[]);
@ -2272,6 +2279,32 @@ CSSParserImpl::ParseColorString(const nsSubstring& aBuffer,
return colorParsed;
}
bool
CSSParserImpl::ParseMarginString(const nsSubstring& aBuffer,
nsIURI* aURI, // for error reporting
uint32_t aLineNumber, // for error reporting
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
nsCSSScanner scanner(aBuffer, aLineNumber);
css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
InitScanner(scanner, reporter, aURI, aURI, nullptr);
nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);
// Parse a margin, and check that there's nothing else after it.
bool marginParsed = ParseGroupedBoxProperty(VARIANT_LP, aValue, 0) && !GetToken(true);
if (aSuppressErrors) {
CLEAR_ERROR();
} else {
OUTPUT_ERROR();
}
ReleaseScanner();
return marginParsed;
}
bool
CSSParserImpl::ParseFontFamilyListString(const nsSubstring& aBuffer,
nsIURI* aURI, // for error reporting
@ -11184,10 +11217,11 @@ CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[])
}
// Similar to ParseBoxProperties, except there is only one property
// with the result as its value, not four. Requires values be nonnegative.
// with the result as its value, not four.
bool
CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask,
/** outparam */ nsCSSValue& aValue)
/** outparam */ nsCSSValue& aValue,
uint32_t aRestrictions)
{
nsCSSRect& result = aValue.SetRectValue();
@ -11196,7 +11230,7 @@ CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask,
CSSParseResult parseResult =
ParseVariantWithRestrictions(result.*(nsCSSRect::sides[index]),
aVariantMask, nullptr,
CSS_PROPERTY_VALUE_NONNEGATIVE);
aRestrictions);
if (parseResult == CSSParseResult::NotFound) {
break;
}
@ -13210,7 +13244,8 @@ CSSParserImpl::ParseBorderImageSlice(bool aAcceptsInherit,
// Parse the box dimensions.
nsCSSValue imageSliceBoxValue;
if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue)) {
if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue,
CSS_PROPERTY_VALUE_NONNEGATIVE)) {
if (!hasFill && aConsumedTokens) {
*aConsumedTokens = false;
}
@ -13254,7 +13289,7 @@ CSSParserImpl::ParseBorderImageWidth(bool aAcceptsInherit)
}
// Parse the box dimensions.
if (!ParseGroupedBoxProperty(VARIANT_ALPN, value)) {
if (!ParseGroupedBoxProperty(VARIANT_ALPN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
return false;
}
@ -13277,7 +13312,7 @@ CSSParserImpl::ParseBorderImageOutset(bool aAcceptsInherit)
}
// Parse the box dimensions.
if (!ParseGroupedBoxProperty(VARIANT_LN, value)) {
if (!ParseGroupedBoxProperty(VARIANT_LN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
return false;
}
@ -17994,6 +18029,17 @@ nsCSSParser::ParseColorString(const nsSubstring& aBuffer,
ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}
bool
nsCSSParser::ParseMarginString(const nsSubstring& aBuffer,
nsIURI* aURI,
uint32_t aLineNumber,
nsCSSValue& aValue,
bool aSuppressErrors /* false */)
{
return static_cast<CSSParserImpl*>(mImpl)->
ParseMarginString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}
nsresult
nsCSSParser::ParseSelectorString(const nsSubstring& aSelectorString,
nsIURI* aURI,

Просмотреть файл

@ -205,6 +205,18 @@ public:
nsCSSValue& aValue,
bool aSuppressErrors = false);
/**
* Parse aBuffer into a nsCSSValue |aValue|. Will return false
* if aBuffer is not a valid CSS margin specification.
* One can use nsRuleNode::GetRectValue to compute an nsCSSRect from
* the returned nsCSSValue.
*/
bool ParseMarginString(const nsSubstring& aBuffer,
nsIURI* aURL,
uint32_t aLineNumber,
nsCSSValue& aValue,
bool aSuppressErrors = false);
/**
* Parse aBuffer into a selector list. On success, caller must
* delete *aSelectorList when done with it.

Просмотреть файл

@ -63,7 +63,10 @@ enum nsCSSPropertyID {
eCSSPropertyExtra_x_auto_value,
// Extra value to represent custom properties (--*).
eCSSPropertyExtra_variable
eCSSPropertyExtra_variable,
// Extra value for use in the DOM API's
eCSSProperty_DOM
};
namespace mozilla {

Просмотреть файл

@ -1218,7 +1218,8 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
// eCSSProperty_UNKNOWN gets used for some recursive calls below.
MOZ_ASSERT((0 <= aProperty &&
aProperty <= eCSSProperty_COUNT_no_shorthands) ||
aProperty == eCSSProperty_UNKNOWN,
aProperty == eCSSProperty_UNKNOWN ||
aProperty == eCSSProperty_DOM,
"property ID out of range");
nsCSSUnit unit = GetUnit();
@ -2429,7 +2430,8 @@ nsCSSRect::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult,
if (eCSSProperty_border_image_slice == aProperty ||
eCSSProperty_border_image_width == aProperty ||
eCSSProperty_border_image_outset == aProperty) {
eCSSProperty_border_image_outset == aProperty ||
eCSSProperty_DOM == aProperty) {
NS_NAMED_LITERAL_STRING(space, " ");
mTop.AppendToString(aProperty, aResult, aSerialization);

Просмотреть файл

@ -665,6 +665,10 @@ public:
{ return eCSSUnit_Point <= aUnit && aUnit <= eCSSUnit_Pixel; }
bool IsPixelLengthUnit() const
{ return IsPixelLengthUnit(mUnit); }
static bool IsPercentLengthUnit(nsCSSUnit aUnit)
{ return aUnit == eCSSUnit_Percent; }
bool IsPercentLengthUnit()
{ return IsPercentLengthUnit(mUnit); }
static bool IsFloatUnit(nsCSSUnit aUnit)
{ return eCSSUnit_Number <= aUnit; }
bool IsAngularUnit() const