import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,392 @@
/* -*- 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 "AccessibleCaret.h"
#include "AccessibleCaretLogger.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Preferences.h"
#include "mozilla/ToString.h"
#include "nsCanvasFrame.h"
#include "nsCaret.h"
#include "nsDOMTokenList.h"
#include "nsIFrame.h"
namespace mozilla {
using namespace dom;
#undef AC_LOG
#define AC_LOG(message, ...) \
AC_LOG_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
#undef AC_LOGV
#define AC_LOGV(message, ...) \
AC_LOGV_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
NS_IMPL_ISUPPORTS(AccessibleCaret::DummyTouchListener, nsIDOMEventListener)
float AccessibleCaret::sWidth = 0.0f;
float AccessibleCaret::sHeight = 0.0f;
float AccessibleCaret::sMarginLeft = 0.0f;
float AccessibleCaret::sBarWidth = 0.0f;
NS_NAMED_LITERAL_STRING(AccessibleCaret::sTextOverlayElementId, "text-overlay");
NS_NAMED_LITERAL_STRING(AccessibleCaret::sCaretImageElementId, "image");
NS_NAMED_LITERAL_STRING(AccessibleCaret::sSelectionBarElementId, "bar");
#define AC_PROCESS_ENUM_TO_STREAM(e) case(e): aStream << #e; break;
std::ostream&
operator<<(std::ostream& aStream, const AccessibleCaret::Appearance& aAppearance)
{
using Appearance = AccessibleCaret::Appearance;
switch (aAppearance) {
AC_PROCESS_ENUM_TO_STREAM(Appearance::None);
AC_PROCESS_ENUM_TO_STREAM(Appearance::Normal);
AC_PROCESS_ENUM_TO_STREAM(Appearance::NormalNotShown);
AC_PROCESS_ENUM_TO_STREAM(Appearance::Left);
AC_PROCESS_ENUM_TO_STREAM(Appearance::Right);
}
return aStream;
}
std::ostream&
operator<<(std::ostream& aStream,
const AccessibleCaret::PositionChangedResult& aResult)
{
using PositionChangedResult = AccessibleCaret::PositionChangedResult;
switch (aResult) {
AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::NotChanged);
AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Changed);
AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible);
}
return aStream;
}
#undef AC_PROCESS_ENUM_TO_STREAM
// -----------------------------------------------------------------------------
// Implementation of AccessibleCaret methods
AccessibleCaret::AccessibleCaret(nsIPresShell* aPresShell)
: mPresShell(aPresShell)
{
// Check all resources required.
if (mPresShell) {
MOZ_ASSERT(RootFrame());
MOZ_ASSERT(mPresShell->GetDocument());
MOZ_ASSERT(mPresShell->GetCanvasFrame());
MOZ_ASSERT(mPresShell->GetCanvasFrame()->GetCustomContentContainer());
InjectCaretElement(mPresShell->GetDocument());
}
static bool prefsAdded = false;
if (!prefsAdded) {
Preferences::AddFloatVarCache(&sWidth, "layout.accessiblecaret.width");
Preferences::AddFloatVarCache(&sHeight, "layout.accessiblecaret.height");
Preferences::AddFloatVarCache(&sMarginLeft, "layout.accessiblecaret.margin-left");
Preferences::AddFloatVarCache(&sBarWidth, "layout.accessiblecaret.bar.width");
prefsAdded = true;
}
}
AccessibleCaret::~AccessibleCaret()
{
if (mPresShell) {
RemoveCaretElement(mPresShell->GetDocument());
}
}
void
AccessibleCaret::SetAppearance(Appearance aAppearance)
{
if (mAppearance == aAppearance) {
return;
}
ErrorResult rv;
CaretElement()->ClassList()->Remove(AppearanceString(mAppearance), rv);
MOZ_ASSERT(!rv.Failed(), "Remove old appearance failed!");
CaretElement()->ClassList()->Add(AppearanceString(aAppearance), rv);
MOZ_ASSERT(!rv.Failed(), "Add new appearance failed!");
AC_LOG("%s: %s -> %s", __FUNCTION__, ToString(mAppearance).c_str(),
ToString(aAppearance).c_str());
mAppearance = aAppearance;
// Need to reset rect since the cached rect will be compared in SetPosition.
if (mAppearance == Appearance::None) {
mImaginaryCaretRect = nsRect();
mZoomLevel = 0.0f;
}
}
void
AccessibleCaret::SetSelectionBarEnabled(bool aEnabled)
{
if (mSelectionBarEnabled == aEnabled) {
return;
}
AC_LOG("Set selection bar %s", aEnabled ? "Enabled" : "Disabled");
ErrorResult rv;
CaretElement()->ClassList()->Toggle(NS_LITERAL_STRING("no-bar"),
Optional<bool>(!aEnabled), rv);
MOZ_ASSERT(!rv.Failed());
mSelectionBarEnabled = aEnabled;
}
/* static */ nsAutoString
AccessibleCaret::AppearanceString(Appearance aAppearance)
{
nsAutoString string;
switch (aAppearance) {
case Appearance::None:
case Appearance::NormalNotShown:
string = NS_LITERAL_STRING("none");
break;
case Appearance::Normal:
string = NS_LITERAL_STRING("normal");
break;
case Appearance::Right:
string = NS_LITERAL_STRING("right");
break;
case Appearance::Left:
string = NS_LITERAL_STRING("left");
break;
}
return string;
}
bool
AccessibleCaret::Intersects(const AccessibleCaret& aCaret) const
{
MOZ_ASSERT(mPresShell == aCaret.mPresShell);
if (!IsVisuallyVisible() || !aCaret.IsVisuallyVisible()) {
return false;
}
nsRect rect = nsLayoutUtils::GetRectRelativeToFrame(CaretElement(), RootFrame());
nsRect rhsRect = nsLayoutUtils::GetRectRelativeToFrame(aCaret.CaretElement(), RootFrame());
return rect.Intersects(rhsRect);
}
bool
AccessibleCaret::Contains(const nsPoint& aPoint, TouchArea aTouchArea) const
{
if (!IsVisuallyVisible()) {
return false;
}
nsRect textOverlayRect =
nsLayoutUtils::GetRectRelativeToFrame(TextOverlayElement(), RootFrame());
nsRect caretImageRect =
nsLayoutUtils::GetRectRelativeToFrame(CaretImageElement(), RootFrame());
if (aTouchArea == TouchArea::CaretImage) {
return caretImageRect.Contains(aPoint);
}
MOZ_ASSERT(aTouchArea == TouchArea::Full, "Unexpected TouchArea type!");
return textOverlayRect.Contains(aPoint) || caretImageRect.Contains(aPoint);
}
void
AccessibleCaret::EnsureApzAware()
{
// If the caret element was cloned, the listener might have been lost. So
// if that's the case we register a dummy listener if there isn't one on
// the element already.
if (!CaretElement()->IsApzAware()) {
CaretElement()->AddEventListener(NS_LITERAL_STRING("touchstart"),
mDummyTouchListener, false);
}
}
void
AccessibleCaret::InjectCaretElement(nsIDocument* aDocument)
{
ErrorResult rv;
nsCOMPtr<Element> element = CreateCaretElement(aDocument);
mCaretElementHolder = aDocument->InsertAnonymousContent(*element, rv);
MOZ_ASSERT(!rv.Failed(), "Insert anonymous content should not fail!");
MOZ_ASSERT(mCaretElementHolder.get(), "We must have anonymous content!");
// InsertAnonymousContent will clone the element to make an AnonymousContent.
// Since event listeners are not being cloned when cloning a node, we need to
// add the listener here.
EnsureApzAware();
}
already_AddRefed<Element>
AccessibleCaret::CreateCaretElement(nsIDocument* aDocument) const
{
// Content structure of AccessibleCaret
// <div class="moz-accessiblecaret"> <- CaretElement()
// <div id="text-overlay" <- TextOverlayElement()
// <div id="image"> <- CaretImageElement()
// <div id="bar"> <- SelectionBarElement()
ErrorResult rv;
nsCOMPtr<Element> parent = aDocument->CreateHTMLElement(nsGkAtoms::div);
parent->ClassList()->Add(NS_LITERAL_STRING("moz-accessiblecaret"), rv);
parent->ClassList()->Add(NS_LITERAL_STRING("none"), rv);
parent->ClassList()->Add(NS_LITERAL_STRING("no-bar"), rv);
auto CreateAndAppendChildElement = [aDocument, &parent](
const nsLiteralString& aElementId)
{
nsCOMPtr<Element> child = aDocument->CreateHTMLElement(nsGkAtoms::div);
child->SetAttr(kNameSpaceID_None, nsGkAtoms::id, aElementId, true);
parent->AppendChildTo(child, false);
};
CreateAndAppendChildElement(sTextOverlayElementId);
CreateAndAppendChildElement(sCaretImageElementId);
CreateAndAppendChildElement(sSelectionBarElementId);
return parent.forget();
}
void
AccessibleCaret::RemoveCaretElement(nsIDocument* aDocument)
{
CaretElement()->RemoveEventListener(NS_LITERAL_STRING("touchstart"),
mDummyTouchListener, false);
ErrorResult rv;
aDocument->RemoveAnonymousContent(*mCaretElementHolder, rv);
// It's OK rv is failed since nsCanvasFrame might not exists now.
rv.SuppressException();
}
AccessibleCaret::PositionChangedResult
AccessibleCaret::SetPosition(nsIFrame* aFrame, int32_t aOffset)
{
if (!CustomContentContainerFrame()) {
return PositionChangedResult::NotChanged;
}
nsRect imaginaryCaretRectInFrame =
nsCaret::GetGeometryForFrame(aFrame, aOffset, nullptr);
imaginaryCaretRectInFrame =
nsLayoutUtils::ClampRectToScrollFrames(aFrame, imaginaryCaretRectInFrame);
if (imaginaryCaretRectInFrame.IsEmpty()) {
// Don't bother to set the caret position since it's invisible.
mImaginaryCaretRect = nsRect();
mZoomLevel = 0.0f;
return PositionChangedResult::Invisible;
}
nsRect imaginaryCaretRect = imaginaryCaretRectInFrame;
nsLayoutUtils::TransformRect(aFrame, RootFrame(), imaginaryCaretRect);
float zoomLevel = GetZoomLevel();
if (imaginaryCaretRect.IsEqualEdges(mImaginaryCaretRect) &&
FuzzyEqualsMultiplicative(zoomLevel, mZoomLevel)) {
return PositionChangedResult::NotChanged;
}
mImaginaryCaretRect = imaginaryCaretRect;
mZoomLevel = zoomLevel;
// SetCaretElementStyle() requires the input rect relative to container frame.
nsRect imaginaryCaretRectInContainerFrame = imaginaryCaretRectInFrame;
nsLayoutUtils::TransformRect(aFrame, CustomContentContainerFrame(),
imaginaryCaretRectInContainerFrame);
SetCaretElementStyle(imaginaryCaretRectInContainerFrame, mZoomLevel);
return PositionChangedResult::Changed;
}
nsIFrame*
AccessibleCaret::CustomContentContainerFrame() const
{
nsCanvasFrame* canvasFrame = mPresShell->GetCanvasFrame();
Element* container = canvasFrame->GetCustomContentContainer();
nsIFrame* containerFrame = container->GetPrimaryFrame();
return containerFrame;
}
void
AccessibleCaret::SetCaretElementStyle(const nsRect& aRect, float aZoomLevel)
{
nsPoint position = CaretElementPosition(aRect);
nsAutoString styleStr;
styleStr.AppendPrintf("left: %dpx; top: %dpx; "
"width: %.2fpx; height: %.2fpx; margin-left: %.2fpx",
nsPresContext::AppUnitsToIntCSSPixels(position.x),
nsPresContext::AppUnitsToIntCSSPixels(position.y),
sWidth / aZoomLevel,
sHeight / aZoomLevel,
sMarginLeft / aZoomLevel);
CaretElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr, true);
AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
// Set style string for children.
SetTextOverlayElementStyle(aRect, aZoomLevel);
SetCaretImageElementStyle(aRect, aZoomLevel);
SetSelectionBarElementStyle(aRect, aZoomLevel);
}
void
AccessibleCaret::SetTextOverlayElementStyle(const nsRect& aRect,
float aZoomLevel)
{
nsAutoString styleStr;
styleStr.AppendPrintf("height: %dpx;",
nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
TextOverlayElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
true);
AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
}
void
AccessibleCaret::SetCaretImageElementStyle(const nsRect& aRect,
float aZoomLevel)
{
nsAutoString styleStr;
styleStr.AppendPrintf("margin-top: %dpx;",
nsPresContext::AppUnitsToIntCSSPixels(aRect.height));
CaretImageElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
true);
AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
}
void
AccessibleCaret::SetSelectionBarElementStyle(const nsRect& aRect,
float aZoomLevel)
{
nsAutoString styleStr;
styleStr.AppendPrintf("height: %dpx; width: %.2fpx;",
nsPresContext::AppUnitsToIntCSSPixels(aRect.height),
sBarWidth / aZoomLevel);
SelectionBarElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
true);
AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
}
float
AccessibleCaret::GetZoomLevel()
{
// Full zoom on desktop.
float fullZoom = mPresShell->GetPresContext()->GetFullZoom();
// Pinch-zoom on B2G or fennec.
float resolution = mPresShell->GetCumulativeResolution();
return fullZoom * resolution;
}
} // namespace mozilla

View file

@ -0,0 +1,257 @@
/* -*- 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 AccessibleCaret_h__
#define AccessibleCaret_h__
#include "mozilla/Attributes.h"
#include "mozilla/dom/AnonymousContent.h"
#include "mozilla/dom/Element.h"
#include "nsCOMPtr.h"
#include "nsIDOMEventListener.h"
#include "nsISupportsBase.h"
#include "nsISupportsImpl.h"
#include "nsLiteralString.h"
#include "nsRect.h"
#include "mozilla/RefPtr.h"
#include "nsString.h"
class nsIDocument;
class nsIFrame;
class nsIPresShell;
struct nsPoint;
namespace mozilla {
// -----------------------------------------------------------------------------
// Upon the creation of AccessibleCaret, it will insert DOM Element as an
// anonymous content containing the caret image. The caret appearance and
// position can be controlled by SetAppearance() and SetPosition().
//
// All the rect or point are relative to root frame except being specified
// explicitly.
//
// None of the methods in AccessibleCaret will flush layout or style. To ensure
// that SetPosition() works correctly, the caller must make sure the layout is
// up to date.
//
// Please see the wiki page for more information.
// https://wiki.mozilla.org/AccessibleCaret
//
class AccessibleCaret
{
public:
explicit AccessibleCaret(nsIPresShell* aPresShell);
virtual ~AccessibleCaret();
// This enumeration representing the visibility and visual style of an
// AccessibleCaret.
//
// Use SetAppearance() to change the appearance, and use GetAppearance() to
// get the current appearance.
enum class Appearance : uint8_t {
// Do not display the caret at all.
None,
// Display the caret in default style.
Normal,
// The caret should be displayed logically but it is kept invisible to the
// user. This enum is the only difference between "logically visible" and
// "visually visible". It can be used for reasons such as:
// 1. Out of scroll port.
// 2. For UX requirement such as hide a caret in an empty text area.
NormalNotShown,
// Display the caret which is tilted to the left.
Left,
// Display the caret which is tilted to the right.
Right
};
friend std::ostream& operator<<(std::ostream& aStream,
const Appearance& aAppearance);
Appearance GetAppearance() const
{
return mAppearance;
}
virtual void SetAppearance(Appearance aAppearance);
// Return true if current appearance is either Normal, NormalNotShown, Left,
// or Right.
bool IsLogicallyVisible() const
{
return mAppearance != Appearance::None;
}
// Return true if current appearance is either Normal, Left, or Right.
bool IsVisuallyVisible() const
{
return (mAppearance != Appearance::None) &&
(mAppearance != Appearance::NormalNotShown);
}
// Set true to enable the "Text Selection Bar" described in "Text Selection
// Visual Spec" in bug 921965.
virtual void SetSelectionBarEnabled(bool aEnabled);
// This enumeration representing the result returned by SetPosition().
enum class PositionChangedResult : uint8_t {
// Position is not changed.
NotChanged,
// Position or zoom level is changed.
Changed,
// Position is out of scroll port.
Invisible
};
friend std::ostream& operator<<(std::ostream& aStream,
const PositionChangedResult& aResult);
virtual PositionChangedResult SetPosition(nsIFrame* aFrame, int32_t aOffset);
// Does two AccessibleCarets overlap?
bool Intersects(const AccessibleCaret& aCaret) const;
// Is the point within the caret's rect? The point should be relative to root
// frame.
enum class TouchArea {
Full, // Contains both text overlay and caret image.
CaretImage
};
bool Contains(const nsPoint& aPoint, TouchArea aTouchArea) const;
// The geometry center of the imaginary caret (nsCaret) to which this
// AccessibleCaret is attached. It is needed when dragging the caret.
nsPoint LogicalPosition() const
{
return mImaginaryCaretRect.Center();
}
// Element for 'Intersects' test. Container of image and bar elements.
dom::Element* CaretElement() const
{
return mCaretElementHolder->GetContentNode();
}
// Ensures that the caret element is made "APZ aware" so that the APZ code
// doesn't scroll the page when the user is trying to drag the caret.
void EnsureApzAware();
protected:
// Argument aRect should be relative to CustomContentContainerFrame().
void SetCaretElementStyle(const nsRect& aRect, float aZoomLevel);
void SetTextOverlayElementStyle(const nsRect& aRect, float aZoomLevel);
void SetCaretImageElementStyle(const nsRect& aRect, float aZoomLevel);
void SetSelectionBarElementStyle(const nsRect& aRect, float aZoomLevel);
// Get current zoom level.
float GetZoomLevel();
// Element which contains the text overly for the 'Contains' test.
dom::Element* TextOverlayElement() const
{
return mCaretElementHolder->GetElementById(sTextOverlayElementId);
}
// Element which contains the caret image for 'Contains' test.
dom::Element* CaretImageElement() const
{
return mCaretElementHolder->GetElementById(sCaretImageElementId);
}
// Element which represents the text selection bar.
dom::Element* SelectionBarElement() const
{
return mCaretElementHolder->GetElementById(sSelectionBarElementId);
}
nsIFrame* RootFrame() const
{
return mPresShell->GetRootFrame();
}
nsIFrame* CustomContentContainerFrame() const;
// Transform Appearance to CSS id used in ua.css.
static nsAutoString AppearanceString(Appearance aAppearance);
already_AddRefed<dom::Element> CreateCaretElement(nsIDocument* aDocument) const;
// Inject caret element into custom content container.
void InjectCaretElement(nsIDocument* aDocument);
// Remove caret element from custom content container.
void RemoveCaretElement(nsIDocument* aDocument);
// The top-center of the imaginary caret to which this AccessibleCaret is
// attached.
static nsPoint CaretElementPosition(const nsRect& aRect)
{
return aRect.TopLeft() + nsPoint(aRect.width / 2, 0);
}
class DummyTouchListener final : public nsIDOMEventListener
{
public:
NS_DECL_ISUPPORTS
NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override
{
return NS_OK;
}
private:
virtual ~DummyTouchListener() {};
};
// Member variables
Appearance mAppearance = Appearance::None;
bool mSelectionBarEnabled = false;
// AccessibleCaretManager owns us by a UniquePtr. When it's terminated by
// AccessibleCaretEventHub::Terminate() which is called in
// PresShell::Destroy(), it frees us automatically. No need to worry if we
// outlive mPresShell.
nsIPresShell* MOZ_NON_OWNING_REF const mPresShell = nullptr;
RefPtr<dom::AnonymousContent> mCaretElementHolder;
// mImaginaryCaretRect is relative to root frame.
nsRect mImaginaryCaretRect;
// Cache current zoom level to determine whether position is changed.
float mZoomLevel = 0.0f;
// A no-op touch-start listener which prevents APZ from panning when dragging
// the caret.
RefPtr<DummyTouchListener> mDummyTouchListener{new DummyTouchListener()};
// Static class variables
static float sWidth;
static float sHeight;
static float sMarginLeft;
static float sBarWidth;
static const nsLiteralString sTextOverlayElementId;
static const nsLiteralString sCaretImageElementId;
static const nsLiteralString sSelectionBarElementId;
}; // class AccessibleCaret
std::ostream& operator<<(std::ostream& aStream,
const AccessibleCaret::Appearance& aAppearance);
std::ostream& operator<<(std::ostream& aStream,
const AccessibleCaret::PositionChangedResult& aResult);
} // namespace mozilla
#endif // AccessibleCaret_h__

View file

@ -0,0 +1,829 @@
/* -*- 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 "AccessibleCaretEventHub.h"
#include "AccessibleCaretLogger.h"
#include "AccessibleCaretManager.h"
#include "Layers.h"
#include "gfxPrefs.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/Preferences.h"
#include "nsCanvasFrame.h"
#include "nsDocShell.h"
#include "nsFocusManager.h"
#include "nsFrameSelection.h"
#include "nsITimer.h"
#include "nsPresContext.h"
namespace mozilla {
#undef AC_LOG
#define AC_LOG(message, ...) \
AC_LOG_BASE("AccessibleCaretEventHub (%p): " message, this, ##__VA_ARGS__);
#undef AC_LOGV
#define AC_LOGV(message, ...) \
AC_LOGV_BASE("AccessibleCaretEventHub (%p): " message, this, ##__VA_ARGS__);
NS_IMPL_ISUPPORTS(AccessibleCaretEventHub, nsIReflowObserver, nsIScrollObserver,
nsISelectionListener, nsISupportsWeakReference);
// -----------------------------------------------------------------------------
// NoActionState
//
class AccessibleCaretEventHub::NoActionState
: public AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const override { return "NoActionState"; }
virtual nsEventStatus OnPress(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint, int32_t aTouchId,
EventClassID aEventClass) override
{
nsEventStatus rv = nsEventStatus_eIgnore;
if (NS_SUCCEEDED(aContext->mManager->PressCaret(aPoint, aEventClass))) {
aContext->SetState(aContext->PressCaretState());
rv = nsEventStatus_eConsumeNoDefault;
} else {
aContext->SetState(aContext->PressNoCaretState());
}
aContext->mPressPoint = aPoint;
aContext->mActiveTouchId = aTouchId;
return rv;
}
virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnScrollStart();
aContext->SetState(aContext->ScrollState());
}
virtual void OnScrollPositionChanged(
AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnScrollPositionChanged();
}
virtual void OnSelectionChanged(AccessibleCaretEventHub* aContext,
nsIDOMDocument* aDoc, nsISelection* aSel,
int16_t aReason) override
{
aContext->mManager->OnSelectionChanged(aDoc, aSel, aReason);
}
virtual void OnBlur(AccessibleCaretEventHub* aContext,
bool aIsLeavingDocument) override
{
aContext->mManager->OnBlur();
}
virtual void OnReflow(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnReflow();
}
virtual void Enter(AccessibleCaretEventHub* aContext) override
{
aContext->mPressPoint = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
aContext->mActiveTouchId = kInvalidTouchId;
}
};
// -----------------------------------------------------------------------------
// PressCaretState: Always consume the event since we've pressed on the caret.
//
class AccessibleCaretEventHub::PressCaretState
: public AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const override { return "PressCaretState"; }
virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint) override
{
if (aContext->MoveDistanceIsLarge(aPoint)) {
if (NS_SUCCEEDED(aContext->mManager->DragCaret(aPoint))) {
aContext->SetState(aContext->DragCaretState());
}
}
return nsEventStatus_eConsumeNoDefault;
}
virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->ReleaseCaret();
aContext->mManager->TapCaret(aContext->mPressPoint);
aContext->SetState(aContext->NoActionState());
return nsEventStatus_eConsumeNoDefault;
}
virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint) override
{
return nsEventStatus_eConsumeNoDefault;
}
};
// -----------------------------------------------------------------------------
// DragCaretState: Always consume the event since we've pressed on the caret.
//
class AccessibleCaretEventHub::DragCaretState
: public AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const override { return "DragCaretState"; }
virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint) override
{
aContext->mManager->DragCaret(aPoint);
return nsEventStatus_eConsumeNoDefault;
}
virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->ReleaseCaret();
aContext->SetState(aContext->NoActionState());
return nsEventStatus_eConsumeNoDefault;
}
};
// -----------------------------------------------------------------------------
// PressNoCaretState
//
class AccessibleCaretEventHub::PressNoCaretState
: public AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const override { return "PressNoCaretState"; }
virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint) override
{
if (aContext->MoveDistanceIsLarge(aPoint)) {
aContext->SetState(aContext->NoActionState());
}
return nsEventStatus_eIgnore;
}
virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
{
aContext->SetState(aContext->NoActionState());
return nsEventStatus_eIgnore;
}
virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint) override
{
aContext->SetState(aContext->LongTapState());
return aContext->GetState()->OnLongTap(aContext, aPoint);
}
virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnScrollStart();
aContext->SetState(aContext->ScrollState());
}
virtual void OnBlur(AccessibleCaretEventHub* aContext,
bool aIsLeavingDocument) override
{
aContext->mManager->OnBlur();
if (aIsLeavingDocument) {
aContext->SetState(aContext->NoActionState());
}
}
virtual void OnSelectionChanged(AccessibleCaretEventHub* aContext,
nsIDOMDocument* aDoc, nsISelection* aSel,
int16_t aReason) override
{
aContext->mManager->OnSelectionChanged(aDoc, aSel, aReason);
}
virtual void OnReflow(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnReflow();
}
virtual void Enter(AccessibleCaretEventHub* aContext) override
{
aContext->LaunchLongTapInjector();
}
virtual void Leave(AccessibleCaretEventHub* aContext) override
{
aContext->CancelLongTapInjector();
}
};
// -----------------------------------------------------------------------------
// ScrollState
//
class AccessibleCaretEventHub::ScrollState
: public AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const override { return "ScrollState"; }
virtual void OnScrollEnd(AccessibleCaretEventHub* aContext) override
{
aContext->SetState(aContext->PostScrollState());
}
virtual void OnBlur(AccessibleCaretEventHub* aContext,
bool aIsLeavingDocument) override
{
aContext->mManager->OnBlur();
if (aIsLeavingDocument) {
aContext->SetState(aContext->NoActionState());
}
}
};
// -----------------------------------------------------------------------------
// PostScrollState: In this state, we are waiting for another APZ start or press
// event.
//
class AccessibleCaretEventHub::PostScrollState
: public AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const override { return "PostScrollState"; }
virtual nsEventStatus OnPress(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint, int32_t aTouchId,
EventClassID aEventClass) override
{
aContext->mManager->OnScrollEnd();
aContext->SetState(aContext->NoActionState());
return aContext->GetState()->OnPress(aContext, aPoint, aTouchId,
aEventClass);
}
virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
{
aContext->SetState(aContext->ScrollState());
}
virtual void OnScrollEnd(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnScrollEnd();
aContext->SetState(aContext->NoActionState());
}
virtual void OnBlur(AccessibleCaretEventHub* aContext,
bool aIsLeavingDocument) override
{
aContext->mManager->OnBlur();
if (aIsLeavingDocument) {
aContext->SetState(aContext->NoActionState());
}
}
virtual void Enter(AccessibleCaretEventHub* aContext) override
{
// Launch the injector to leave PostScrollState.
aContext->LaunchScrollEndInjector();
}
virtual void Leave(AccessibleCaretEventHub* aContext) override
{
aContext->CancelScrollEndInjector();
}
};
// -----------------------------------------------------------------------------
// LongTapState
//
class AccessibleCaretEventHub::LongTapState
: public AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const override { return "LongTapState"; }
virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint) override
{
// In general text selection is lower-priority than the context menu. If
// we consume this long-press event, then it prevents the context menu from
// showing up on desktop Firefox (because that happens on long-tap-up, if
// the long-tap was not cancelled). So we return eIgnore instead.
aContext->mManager->SelectWordOrShortcut(aPoint);
return nsEventStatus_eIgnore;
}
virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override
{
aContext->SetState(aContext->NoActionState());
// Do not consume the release since the press is not consumed in
// PressNoCaretState either.
return nsEventStatus_eIgnore;
}
virtual void OnScrollStart(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnScrollStart();
aContext->SetState(aContext->ScrollState());
}
virtual void OnReflow(AccessibleCaretEventHub* aContext) override
{
aContext->mManager->OnReflow();
}
};
// -----------------------------------------------------------------------------
// Implementation of AccessibleCaretEventHub methods
//
AccessibleCaretEventHub::State*
AccessibleCaretEventHub::GetState() const
{
return mState;
}
void
AccessibleCaretEventHub::SetState(State* aState)
{
MOZ_ASSERT(aState);
AC_LOG("%s -> %s", mState->Name(), aState->Name());
mState->Leave(this);
mState = aState;
mState->Enter(this);
}
MOZ_IMPL_STATE_CLASS_GETTER(NoActionState)
MOZ_IMPL_STATE_CLASS_GETTER(PressCaretState)
MOZ_IMPL_STATE_CLASS_GETTER(DragCaretState)
MOZ_IMPL_STATE_CLASS_GETTER(PressNoCaretState)
MOZ_IMPL_STATE_CLASS_GETTER(ScrollState)
MOZ_IMPL_STATE_CLASS_GETTER(PostScrollState)
MOZ_IMPL_STATE_CLASS_GETTER(LongTapState)
bool AccessibleCaretEventHub::sUseLongTapInjector = false;
AccessibleCaretEventHub::AccessibleCaretEventHub(nsIPresShell* aPresShell)
: mPresShell(aPresShell)
{
static bool prefsAdded = false;
if (!prefsAdded) {
Preferences::AddBoolVarCache(
&sUseLongTapInjector, "layout.accessiblecaret.use_long_tap_injector");
prefsAdded = true;
}
}
AccessibleCaretEventHub::~AccessibleCaretEventHub()
{
}
void
AccessibleCaretEventHub::Init()
{
if (mInitialized && mManager) {
mManager->OnFrameReconstruction();
}
if (mInitialized || !mPresShell || !mPresShell->GetCanvasFrame() ||
!mPresShell->GetCanvasFrame()->GetCustomContentContainer()) {
return;
}
// Without nsAutoScriptBlocker, the script might be run after constructing
// mFirstCaret in AccessibleCaretManager's constructor, which might destructs
// the whole frame tree. Therefore we'll fail to construct mSecondCaret
// because we cannot get root frame or canvas frame from mPresShell to inject
// anonymous content. To avoid that, we protect Init() by nsAutoScriptBlocker.
// To reproduce, run "./mach crashtest layout/base/crashtests/897852.html"
// without the following scriptBlocker.
nsAutoScriptBlocker scriptBlocker;
nsPresContext* presContext = mPresShell->GetPresContext();
MOZ_ASSERT(presContext, "PresContext should be given in PresShell::Init()");
nsIDocShell* docShell = presContext->GetDocShell();
if (!docShell) {
return;
}
docShell->AddWeakReflowObserver(this);
docShell->AddWeakScrollObserver(this);
mDocShell = static_cast<nsDocShell*>(docShell);
if (sUseLongTapInjector) {
mLongTapInjectorTimer = do_CreateInstance("@mozilla.org/timer;1");
}
mScrollEndInjectorTimer = do_CreateInstance("@mozilla.org/timer;1");
mManager = MakeUnique<AccessibleCaretManager>(mPresShell);
mInitialized = true;
}
void
AccessibleCaretEventHub::Terminate()
{
if (!mInitialized) {
return;
}
RefPtr<nsDocShell> docShell(mDocShell.get());
if (docShell) {
docShell->RemoveWeakReflowObserver(this);
docShell->RemoveWeakScrollObserver(this);
}
if (mLongTapInjectorTimer) {
mLongTapInjectorTimer->Cancel();
}
if (mScrollEndInjectorTimer) {
mScrollEndInjectorTimer->Cancel();
}
mManager->Terminate();
mPresShell = nullptr;
mInitialized = false;
}
nsEventStatus
AccessibleCaretEventHub::HandleEvent(WidgetEvent* aEvent)
{
nsEventStatus status = nsEventStatus_eIgnore;
if (!mInitialized) {
return status;
}
MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
switch (aEvent->mClass) {
case eMouseEventClass:
status = HandleMouseEvent(aEvent->AsMouseEvent());
break;
case eTouchEventClass:
status = HandleTouchEvent(aEvent->AsTouchEvent());
break;
case eKeyboardEventClass:
status = HandleKeyboardEvent(aEvent->AsKeyboardEvent());
break;
default:
break;
}
return status;
}
nsEventStatus
AccessibleCaretEventHub::HandleMouseEvent(WidgetMouseEvent* aEvent)
{
nsEventStatus rv = nsEventStatus_eIgnore;
if (aEvent->button != WidgetMouseEvent::eLeftButton) {
return rv;
}
int32_t id =
(mActiveTouchId == kInvalidTouchId ? kDefaultTouchId : mActiveTouchId);
nsPoint point = GetMouseEventPosition(aEvent);
if (aEvent->mMessage == eMouseDown ||
aEvent->mMessage == eMouseUp ||
aEvent->mMessage == eMouseClick ||
aEvent->mMessage == eMouseDoubleClick ||
aEvent->mMessage == eMouseLongTap) {
// Don't reset the source on mouse movement since that can
// happen anytime, even randomly during a touch sequence.
mManager->SetLastInputSource(aEvent->inputSource);
}
switch (aEvent->mMessage) {
case eMouseDown:
AC_LOGV("Before eMouseDown, state: %s", mState->Name());
rv = mState->OnPress(this, point, id, eMouseEventClass);
AC_LOGV("After eMouseDown, state: %s, consume: %d", mState->Name(), rv);
break;
case eMouseMove:
AC_LOGV("Before eMouseMove, state: %s", mState->Name());
rv = mState->OnMove(this, point);
AC_LOGV("After eMouseMove, state: %s, consume: %d", mState->Name(), rv);
break;
case eMouseUp:
AC_LOGV("Before eMouseUp, state: %s", mState->Name());
rv = mState->OnRelease(this);
AC_LOGV("After eMouseUp, state: %s, consume: %d", mState->Name(), rv);
break;
case eMouseLongTap:
AC_LOGV("Before eMouseLongTap, state: %s", mState->Name());
rv = mState->OnLongTap(this, point);
AC_LOGV("After eMouseLongTap, state: %s, consume: %d", mState->Name(),
rv);
break;
default:
break;
}
return rv;
}
nsEventStatus
AccessibleCaretEventHub::HandleTouchEvent(WidgetTouchEvent* aEvent)
{
if (aEvent->mTouches.IsEmpty()) {
AC_LOG("%s: Receive a touch event without any touch data!", __FUNCTION__);
return nsEventStatus_eIgnore;
}
nsEventStatus rv = nsEventStatus_eIgnore;
int32_t id =
(mActiveTouchId == kInvalidTouchId ? aEvent->mTouches[0]->Identifier()
: mActiveTouchId);
nsPoint point = GetTouchEventPosition(aEvent, id);
mManager->SetLastInputSource(nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
switch (aEvent->mMessage) {
case eTouchStart:
AC_LOGV("Before eTouchStart, state: %s", mState->Name());
rv = mState->OnPress(this, point, id, eTouchEventClass);
AC_LOGV("After eTouchStart, state: %s, consume: %d", mState->Name(), rv);
break;
case eTouchMove:
AC_LOGV("Before eTouchMove, state: %s", mState->Name());
rv = mState->OnMove(this, point);
AC_LOGV("After eTouchMove, state: %s, consume: %d", mState->Name(), rv);
break;
case eTouchEnd:
AC_LOGV("Before eTouchEnd, state: %s", mState->Name());
rv = mState->OnRelease(this);
AC_LOGV("After eTouchEnd, state: %s, consume: %d", mState->Name(), rv);
break;
case eTouchCancel:
AC_LOGV("Got eTouchCancel, state: %s", mState->Name());
// Do nothing since we don't really care eTouchCancel anyway.
break;
default:
break;
}
return rv;
}
nsEventStatus
AccessibleCaretEventHub::HandleKeyboardEvent(WidgetKeyboardEvent* aEvent)
{
mManager->SetLastInputSource(nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD);
switch (aEvent->mMessage) {
case eKeyUp:
AC_LOGV("eKeyUp, state: %s", mState->Name());
mManager->OnKeyboardEvent();
break;
case eKeyDown:
AC_LOGV("eKeyDown, state: %s", mState->Name());
mManager->OnKeyboardEvent();
break;
case eKeyPress:
AC_LOGV("eKeyPress, state: %s", mState->Name());
mManager->OnKeyboardEvent();
break;
default:
break;
}
return nsEventStatus_eIgnore;
}
bool
AccessibleCaretEventHub::MoveDistanceIsLarge(const nsPoint& aPoint) const
{
nsPoint delta = aPoint - mPressPoint;
return NS_hypot(delta.x, delta.y) >
nsPresContext::AppUnitsPerCSSPixel() * kMoveStartToleranceInPixel;
}
void
AccessibleCaretEventHub::LaunchLongTapInjector()
{
if (!mLongTapInjectorTimer) {
return;
}
int32_t longTapDelay = gfxPrefs::UiClickHoldContextMenusDelay();
mLongTapInjectorTimer->InitWithFuncCallback(FireLongTap, this, longTapDelay,
nsITimer::TYPE_ONE_SHOT);
}
void
AccessibleCaretEventHub::CancelLongTapInjector()
{
if (!mLongTapInjectorTimer) {
return;
}
mLongTapInjectorTimer->Cancel();
}
/* static */ void
AccessibleCaretEventHub::FireLongTap(nsITimer* aTimer,
void* aAccessibleCaretEventHub)
{
auto* self = static_cast<AccessibleCaretEventHub*>(aAccessibleCaretEventHub);
self->mState->OnLongTap(self, self->mPressPoint);
}
NS_IMETHODIMP
AccessibleCaretEventHub::Reflow(DOMHighResTimeStamp aStart,
DOMHighResTimeStamp aEnd)
{
if (!mInitialized) {
return NS_OK;
}
MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
if (mIsInReflowCallback) {
return NS_OK;
}
AutoRestore<bool> autoRestoreIsInReflowCallback(mIsInReflowCallback);
mIsInReflowCallback = true;
AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
mState->OnReflow(this);
return NS_OK;
}
NS_IMETHODIMP
AccessibleCaretEventHub::ReflowInterruptible(DOMHighResTimeStamp aStart,
DOMHighResTimeStamp aEnd)
{
// Defer the error checking to Reflow().
return Reflow(aStart, aEnd);
}
void
AccessibleCaretEventHub::AsyncPanZoomStarted()
{
if (!mInitialized) {
return;
}
MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
mState->OnScrollStart(this);
}
void
AccessibleCaretEventHub::AsyncPanZoomStopped()
{
if (!mInitialized) {
return;
}
MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
mState->OnScrollEnd(this);
}
void
AccessibleCaretEventHub::ScrollPositionChanged()
{
if (!mInitialized) {
return;
}
MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
mState->OnScrollPositionChanged(this);
}
void
AccessibleCaretEventHub::LaunchScrollEndInjector()
{
if (!mScrollEndInjectorTimer) {
return;
}
mScrollEndInjectorTimer->InitWithFuncCallback(
FireScrollEnd, this, kScrollEndTimerDelay, nsITimer::TYPE_ONE_SHOT);
}
void
AccessibleCaretEventHub::CancelScrollEndInjector()
{
if (!mScrollEndInjectorTimer) {
return;
}
mScrollEndInjectorTimer->Cancel();
}
/* static */ void
AccessibleCaretEventHub::FireScrollEnd(nsITimer* aTimer,
void* aAccessibleCaretEventHub)
{
auto* self = static_cast<AccessibleCaretEventHub*>(aAccessibleCaretEventHub);
self->mState->OnScrollEnd(self);
}
nsresult
AccessibleCaretEventHub::NotifySelectionChanged(nsIDOMDocument* aDoc,
nsISelection* aSel,
int16_t aReason)
{
if (!mInitialized) {
return NS_OK;
}
MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
AC_LOG("%s, state: %s, reason: %d", __FUNCTION__, mState->Name(), aReason);
mState->OnSelectionChanged(this, aDoc, aSel, aReason);
return NS_OK;
}
void
AccessibleCaretEventHub::NotifyBlur(bool aIsLeavingDocument)
{
if (!mInitialized) {
return;
}
MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
mState->OnBlur(this, aIsLeavingDocument);
}
nsPoint
AccessibleCaretEventHub::GetTouchEventPosition(WidgetTouchEvent* aEvent,
int32_t aIdentifier) const
{
for (dom::Touch* touch : aEvent->mTouches) {
if (touch->Identifier() == aIdentifier) {
LayoutDeviceIntPoint touchIntPoint = touch->mRefPoint;
// Get event coordinate relative to root frame.
nsIFrame* rootFrame = mPresShell->GetRootFrame();
return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, touchIntPoint,
rootFrame);
}
}
return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
}
nsPoint
AccessibleCaretEventHub::GetMouseEventPosition(WidgetMouseEvent* aEvent) const
{
LayoutDeviceIntPoint mouseIntPoint = aEvent->AsGUIEvent()->mRefPoint;
// Get event coordinate relative to root frame.
nsIFrame* rootFrame = mPresShell->GetRootFrame();
return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, mouseIntPoint,
rootFrame);
}
} // namespace mozilla

View file

@ -0,0 +1,226 @@
/* -*- 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 AccessibleCaretEventHub_h
#define AccessibleCaretEventHub_h
#include "mozilla/EventForwards.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"
#include "nsCOMPtr.h"
#include "nsIFrame.h"
#include "nsIReflowObserver.h"
#include "nsIScrollObserver.h"
#include "nsISelectionListener.h"
#include "nsPoint.h"
#include "mozilla/RefPtr.h"
#include "nsWeakReference.h"
class nsDocShell;
class nsIPresShell;
class nsITimer;
namespace mozilla {
class AccessibleCaretManager;
class WidgetKeyboardEvent;
class WidgetMouseEvent;
class WidgetTouchEvent;
// -----------------------------------------------------------------------------
// Each PresShell holds a shared pointer to an AccessibleCaretEventHub; each
// AccessibleCaretEventHub holds a unique pointer to an AccessibleCaretManager.
// Thus, there's one AccessibleCaretManager per PresShell.
//
// AccessibleCaretEventHub implements a state pattern. It receives events from
// PresShell and callbacks by observers and listeners, and then relays them to
// the current concrete state which calls necessary event-handling methods in
// AccessibleCaretManager.
//
// We separate AccessibleCaretEventHub from AccessibleCaretManager to make the
// state transitions in AccessibleCaretEventHub testable. We put (nearly) all
// the operations involving PresShell, Selection, and AccessibleCaret
// manipulation in AccessibleCaretManager so that we can mock methods in
// AccessibleCaretManager in gtest. We test the correctness of the state
// transitions by giving events, callbacks, and the return values by mocked
// methods of AccessibleCaretEventHub. See TestAccessibleCaretEventHub.cpp.
//
// Besides dealing with real events, AccessibleCaretEventHub could also
// synthesize fake long-tap events and inject those events to itself on the
// platform lacks eMouseLongTap. Turn on this preference
// "layout.accessiblecaret.use_long_tap_injector" for the fake long-tap events.
//
// State transition diagram:
// http://hg.mozilla.org/mozilla-central/raw-file/default/layout/base/doc/AccessibleCaretEventHubStates.png
// Source code of the diagram:
// http://hg.mozilla.org/mozilla-central/file/default/layout/base/doc/AccessibleCaretEventHubStates.dot
//
// Please see the wiki page for more information.
// https://wiki.mozilla.org/AccessibleCaret
//
class AccessibleCaretEventHub : public nsIReflowObserver,
public nsIScrollObserver,
public nsISelectionListener,
public nsSupportsWeakReference
{
public:
explicit AccessibleCaretEventHub(nsIPresShell* aPresShell);
void Init();
void Terminate();
nsEventStatus HandleEvent(WidgetEvent* aEvent);
// Call this function to notify the blur event happened.
void NotifyBlur(bool aIsLeavingDocument);
NS_DECL_ISUPPORTS
NS_DECL_NSIREFLOWOBSERVER
NS_DECL_NSISELECTIONLISTENER
// Override nsIScrollObserver methods.
virtual void ScrollPositionChanged() override;
virtual void AsyncPanZoomStarted() override;
virtual void AsyncPanZoomStopped() override;
// Base state
class State;
State* GetState() const;
protected:
virtual ~AccessibleCaretEventHub();
#define MOZ_DECL_STATE_CLASS_GETTER(aClassName) \
class aClassName; \
static State* aClassName();
#define MOZ_IMPL_STATE_CLASS_GETTER(aClassName) \
AccessibleCaretEventHub::State* AccessibleCaretEventHub::aClassName() \
{ \
static class aClassName singleton; \
return &singleton; \
}
// Concrete state getters
MOZ_DECL_STATE_CLASS_GETTER(NoActionState)
MOZ_DECL_STATE_CLASS_GETTER(PressCaretState)
MOZ_DECL_STATE_CLASS_GETTER(DragCaretState)
MOZ_DECL_STATE_CLASS_GETTER(PressNoCaretState)
MOZ_DECL_STATE_CLASS_GETTER(ScrollState)
MOZ_DECL_STATE_CLASS_GETTER(PostScrollState)
MOZ_DECL_STATE_CLASS_GETTER(LongTapState)
void SetState(State* aState);
nsEventStatus HandleMouseEvent(WidgetMouseEvent* aEvent);
nsEventStatus HandleTouchEvent(WidgetTouchEvent* aEvent);
nsEventStatus HandleKeyboardEvent(WidgetKeyboardEvent* aEvent);
virtual nsPoint GetTouchEventPosition(WidgetTouchEvent* aEvent,
int32_t aIdentifier) const;
virtual nsPoint GetMouseEventPosition(WidgetMouseEvent* aEvent) const;
bool MoveDistanceIsLarge(const nsPoint& aPoint) const;
void LaunchLongTapInjector();
void CancelLongTapInjector();
static void FireLongTap(nsITimer* aTimer, void* aAccessibleCaretEventHub);
void LaunchScrollEndInjector();
void CancelScrollEndInjector();
static void FireScrollEnd(nsITimer* aTimer, void* aAccessibleCaretEventHub);
// Member variables
State* mState = NoActionState();
// Will be set to nullptr in Terminate().
nsIPresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
UniquePtr<AccessibleCaretManager> mManager;
WeakPtr<nsDocShell> mDocShell;
// Use this timer for injecting a long tap event when APZ is disabled. If APZ
// is enabled, it will send long tap event to us.
nsCOMPtr<nsITimer> mLongTapInjectorTimer;
// Use this timer for injecting a simulated scroll end.
nsCOMPtr<nsITimer> mScrollEndInjectorTimer;
// Last mouse button down event or touch start event point.
nsPoint mPressPoint{ NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE };
// For filter multitouch event
int32_t mActiveTouchId = kInvalidTouchId;
// Flag to indicate the class has been initialized.
bool mInitialized = false;
// Flag to avoid calling Reflow() callback recursively.
bool mIsInReflowCallback = false;
// Simulate long tap if the platform does not support eMouseLongTap events.
static bool sUseLongTapInjector;
static const int32_t kScrollEndTimerDelay = 300;
static const int32_t kMoveStartToleranceInPixel = 5;
static const int32_t kInvalidTouchId = -1;
static const int32_t kDefaultTouchId = 0; // For mouse event
};
// -----------------------------------------------------------------------------
// The base class for concrete states. A concrete state should inherit from this
// class, and override the methods to handle the events or callbacks. A concrete
// state is also responsible for transforming itself to the next concrete state.
//
class AccessibleCaretEventHub::State
{
public:
virtual const char* Name() const { return ""; }
virtual nsEventStatus OnPress(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint, int32_t aTouchId,
EventClassID aEventClass)
{
return nsEventStatus_eIgnore;
}
virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint)
{
return nsEventStatus_eIgnore;
}
virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext)
{
return nsEventStatus_eIgnore;
}
virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
const nsPoint& aPoint)
{
return nsEventStatus_eIgnore;
}
virtual void OnScrollStart(AccessibleCaretEventHub* aContext) {}
virtual void OnScrollEnd(AccessibleCaretEventHub* aContext) {}
virtual void OnScrollPositionChanged(AccessibleCaretEventHub* aContext) {}
virtual void OnBlur(AccessibleCaretEventHub* aContext,
bool aIsLeavingDocument) {}
virtual void OnSelectionChanged(AccessibleCaretEventHub* aContext,
nsIDOMDocument* aDoc, nsISelection* aSel,
int16_t aReason) {}
virtual void OnReflow(AccessibleCaretEventHub* aContext) {}
virtual void Enter(AccessibleCaretEventHub* aContext) {}
virtual void Leave(AccessibleCaretEventHub* aContext) {}
explicit State() = default;
virtual ~State() = default;
State(const State&) = delete;
State& operator=(const State&) = delete;
};
} // namespace mozilla
#endif // AccessibleCaretEventHub_h

View file

@ -0,0 +1,27 @@
/* -*- 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 AccessibleCaretLog_h
#define AccessibleCaretLog_h
#include "mozilla/Logging.h"
namespace mozilla {
static LazyLogModule sAccessibleCaretLog("AccessibleCaret");
#ifndef AC_LOG_BASE
#define AC_LOG_BASE(...) MOZ_LOG(sAccessibleCaretLog, mozilla::LogLevel::Debug, (__VA_ARGS__));
#endif
#ifndef AC_LOGV_BASE
#define AC_LOGV_BASE(...) \
MOZ_LOG(sAccessibleCaretLog, LogLevel::Verbose, (__VA_ARGS__));
#endif
} // namespace mozilla
#endif // AccessibleCaretLog_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,347 @@
/* -*- 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 AccessibleCaretManager_h
#define AccessibleCaretManager_h
#include "AccessibleCaret.h"
#include "nsCOMPtr.h"
#include "nsCoord.h"
#include "nsIDOMMouseEvent.h"
#include "nsIFrame.h"
#include "nsISelectionListener.h"
#include "mozilla/RefPtr.h"
#include "nsWeakReference.h"
#include "mozilla/dom/CaretStateChangedEvent.h"
#include "mozilla/EventForwards.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"
class nsFrameSelection;
class nsIContent;
class nsIPresShell;
struct nsPoint;
namespace mozilla {
namespace dom {
class Element;
class Selection;
} // namespace dom
// -----------------------------------------------------------------------------
// AccessibleCaretManager does not deal with events or callbacks directly. It
// relies on AccessibleCaretEventHub to call its public methods to do the work.
// All codes needed to interact with PresShell, Selection, and AccessibleCaret
// should be written in AccessibleCaretManager.
//
// None the public methods in AccessibleCaretManager will flush layout or style
// prior to performing its task. The caller must ensure the layout is up to
// date.
//
// Please see the wiki page for more information.
// https://wiki.mozilla.org/AccessibleCaret
//
class AccessibleCaretManager
{
public:
explicit AccessibleCaretManager(nsIPresShell* aPresShell);
virtual ~AccessibleCaretManager();
// Called by AccessibleCaretEventHub to inform us that PresShell is destroyed.
void Terminate();
// The aPoint in the following public methods should be relative to root
// frame.
// Press caret on the given point. Return NS_OK if the point is actually on
// one of the carets.
virtual nsresult PressCaret(const nsPoint& aPoint, EventClassID aEventClass);
// Drag caret to the given point. It's required to call PressCaret()
// beforehand.
virtual nsresult DragCaret(const nsPoint& aPoint);
// Release caret from he previous press action. It's required to call
// PressCaret() beforehand.
virtual nsresult ReleaseCaret();
// A quick single tap on caret on given point without dragging.
virtual nsresult TapCaret(const nsPoint& aPoint);
// Select a word or bring up paste shortcut (if Gaia is listening) under the
// given point.
virtual nsresult SelectWordOrShortcut(const nsPoint& aPoint);
// Handle scroll-start event.
virtual void OnScrollStart();
// Handle scroll-end event.
virtual void OnScrollEnd();
// Handle ScrollPositionChanged from nsIScrollObserver. This might be called
// at anytime, not necessary between OnScrollStart and OnScrollEnd.
virtual void OnScrollPositionChanged();
// Handle reflow event from nsIReflowObserver.
virtual void OnReflow();
// Handle blur event from nsFocusManager.
virtual void OnBlur();
// Handle NotifySelectionChanged event from nsISelectionListener.
virtual nsresult OnSelectionChanged(nsIDOMDocument* aDoc,
nsISelection* aSel,
int16_t aReason);
// Handle key event.
virtual void OnKeyboardEvent();
// The canvas frame holding the accessible caret anonymous content elements
// was reconstructed, resulting in the content elements getting cloned.
virtual void OnFrameReconstruction();
// Update the manager with the last input source that was observed. This
// is used in part to determine if the carets should be shown or hidden.
void SetLastInputSource(uint16_t aInputSource);
protected:
// This enum representing the number of AccessibleCarets on the screen.
enum class CaretMode : uint8_t {
// No caret on the screen.
None,
// One caret, i.e. the selection is collapsed.
Cursor,
// Two carets, i.e. the selection is not collapsed.
Selection
};
friend std::ostream& operator<<(std::ostream& aStream,
const CaretMode& aCaretMode);
enum class UpdateCaretsHint : uint8_t {
// Update everything including appearance and position.
Default,
// Update everything while respecting the old appearance. For example, if
// the caret in cursor mode is hidden due to timeout, do not change its
// appearance to Normal.
RespectOldAppearance
};
friend std::ostream& operator<<(std::ostream& aStream,
const UpdateCaretsHint& aResult);
// Update carets based on current selection status. This function will flush
// layout, so caller must ensure the PresShell is still valid after calling
// this method.
void UpdateCarets(UpdateCaretsHint aHint = UpdateCaretsHint::Default);
// Force hiding all carets regardless of the current selection status.
void HideCarets();
void UpdateCaretsForCursorMode(UpdateCaretsHint aHint);
void UpdateCaretsForSelectionMode(UpdateCaretsHint aHint);
// Provide haptic / touch feedback, primarily for select on longpress.
void ProvideHapticFeedback();
// Get the nearest enclosing focusable frame of aFrame.
// @return focusable frame if there is any; nullptr otherwise.
nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const;
// Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus
// then re-focus the window.
void ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const;
nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
void SetSelectionDragState(bool aState) const;
// Called to extend a selection if possible that it's a phone number.
void SelectMoreIfPhoneNumber() const;
// Extend the current phone number selection in the requested direction.
void ExtendPhoneNumberSelection(const nsAString& aDirection) const;
void SetSelectionDirection(nsDirection aDir) const;
// If aDirection is eDirNext, get the frame for the range start in the first
// range from the current selection, and return the offset into that frame as
// well as the range start node and the node offset. Otherwise, get the frame
// and offset for the range end in the last range instead.
nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd(
nsDirection aDirection, int32_t* aOutOffset, nsINode** aOutNode = nullptr,
int32_t* aOutNodeOffset = nullptr) const;
nsresult DragCaretInternal(const nsPoint& aPoint);
nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
void ClearMaintainedSelection() const;
// Caller is responsible to use IsTerminated() to check whether PresShell is
// still valid.
void FlushLayout() const;
dom::Element* GetEditingHostForFrame(nsIFrame* aFrame) const;
dom::Selection* GetSelection() const;
already_AddRefed<nsFrameSelection> GetFrameSelection() const;
// Get the union of all the child frame scrollable overflow rects for aFrame,
// which is used as a helper function to restrict the area where the caret can
// be dragged. Returns the rect relative to aFrame.
nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame) const;
// Restrict the active caret's dragging position based on
// sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first
// caret, the `limit` will be the previous character of the second caret.
// Otherwise, the `limit` will be the next character of the first caret.
//
// @param aOffsets is the new position of the active caret, and it will be set
// to the `limit` when 1) sCaretsAllowDraggingAcrossOtherCaret is false and
// it's being dragged past the limit. 2) sCaretsAllowDraggingAcrossOtherCaret
// is true and the active caret's position is the same as the inactive's
// position.
// @return true if the aOffsets is suitable for changing the selection.
bool RestrictCaretDraggingOffsets(nsIFrame::ContentOffsets& aOffsets);
// Timeout in milliseconds to hide the AccessibleCaret under cursor mode while
// no one touches it.
uint32_t CaretTimeoutMs() const;
void LaunchCaretTimeoutTimer();
void CancelCaretTimeoutTimer();
// ---------------------------------------------------------------------------
// The following functions are made virtual for stubbing or mocking in gtest.
//
// @return true if Terminate() had been called.
virtual bool IsTerminated() const { return !mPresShell; }
// Get caret mode based on current selection.
virtual CaretMode GetCaretMode() const;
// @return true if aStartFrame comes before aEndFrame.
virtual bool CompareTreePosition(nsIFrame* aStartFrame,
nsIFrame* aEndFrame) const;
// Check if the two carets is overlapping to become tilt.
virtual void UpdateCaretsForOverlappingTilt();
// Make the two carets always tilt.
virtual void UpdateCaretsForAlwaysTilt(nsIFrame* aStartFrame,
nsIFrame* aEndFrame);
// Check whether AccessibleCaret is displayable in cursor mode or not.
// @param aOutFrame returns frame of the cursor if it's displayable.
// @param aOutOffset returns frame offset as well.
virtual bool IsCaretDisplayableInCursorMode(nsIFrame** aOutFrame = nullptr,
int32_t* aOutOffset = nullptr) const;
virtual bool HasNonEmptyTextContent(nsINode* aNode) const;
// This function will flush layout, so caller must ensure the PresShell is
// still valid after calling this method.
virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason) const;
// ---------------------------------------------------------------------------
// Member variables
//
nscoord mOffsetYToCaretLogicalPosition = NS_UNCONSTRAINEDSIZE;
// AccessibleCaretEventHub owns us by a UniquePtr. When it's destroyed, we'll
// also be destroyed. No need to worry if we outlive mPresShell.
//
// mPresShell will be set to nullptr in Terminate(). Therefore mPresShell is
// nullptr either we are in gtest or PresShell::IsDestroying() is true.
nsIPresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
// First caret is attached to nsCaret in cursor mode, and is attached to
// selection highlight as the left caret in selection mode.
UniquePtr<AccessibleCaret> mFirstCaret;
// Second caret is used solely in selection mode, and is attached to selection
// highlight as the right caret.
UniquePtr<AccessibleCaret> mSecondCaret;
// The caret being pressed or dragged.
AccessibleCaret* mActiveCaret = nullptr;
// The timer for hiding the caret in cursor mode after timeout behind the
// preference "layout.accessiblecaret.timeout_ms".
nsCOMPtr<nsITimer> mCaretTimeoutTimer;
// The caret mode since last update carets.
CaretMode mLastUpdateCaretMode = CaretMode::None;
// Store the appearance of the carets when calling OnScrollStart() so that it
// can be restored in OnScrollEnd().
AccessibleCaret::Appearance mFirstCaretAppearanceOnScrollStart =
AccessibleCaret::Appearance::None;
AccessibleCaret::Appearance mSecondCaretAppearanceOnScrollStart =
AccessibleCaret::Appearance::None;
// The last input source that the event hub saw. We use this to decide whether
// or not show the carets when the selection is updated, as we want to hide
// the carets for mouse-triggered selection changes but show them for other
// input types such as touch.
uint16_t mLastInputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
static const int32_t kAutoScrollTimerDelay = 30;
// Clicking on the boundary of input or textarea will move the caret to the
// front or end of the content. To avoid this, we need to deflate the content
// boundary by 61 app units, which is 1 pixel + 1 app unit as defined in
// AppUnit.h.
static const int32_t kBoundaryAppUnits = 61;
// Preference to show selection bars at the two ends in selection mode. The
// selection bar is always disabled in cursor mode.
static bool sSelectionBarEnabled;
// Preference to allow smarter selection of phone numbers,
// when user long presses text to start.
static bool sExtendSelectionForPhoneNumber;
// Preference to show caret in cursor mode when long tapping on an empty
// content. This also changes the default update behavior in cursor mode,
// which is based on the emptiness of the content, into something more
// heuristic. See UpdateCaretsForCursorMode() for the details.
static bool sCaretShownWhenLongTappingOnEmptyContent;
// Preference to make carets always tilt in selection mode. By default, the
// carets become tilt only when they are overlapping.
static bool sCaretsAlwaysTilt;
// Preference to allow carets always show when scrolling (either panning or
// zooming) the page. When set to false, carets will hide during scrolling,
// and show again after the user lifts the finger off the screen.
static bool sCaretsAlwaysShowWhenScrolling;
// By default, javascript content selection changes closes AccessibleCarets and
// UI interactions. Optionally, we can try to maintain the active UI, keeping
// carets and ActionBar available.
static bool sCaretsScriptUpdates;
// Preference to allow one caret to be dragged across the other caret without
// any limitation. When set to false, one caret cannot be dragged across the
// other one.
static bool sCaretsAllowDraggingAcrossOtherCaret;
// AccessibleCaret pref for haptic feedback behaviour on longPress.
static bool sHapticFeedback;
// Preference to keep carets hidden when the selection is being manipulated
// by mouse input (as opposed to touch/pen/etc.).
static bool sHideCaretsForMouseInput;
};
std::ostream& operator<<(std::ostream& aStream,
const AccessibleCaretManager::CaretMode& aCaretMode);
std::ostream& operator<<(std::ostream& aStream,
const AccessibleCaretManager::UpdateCaretsHint& aResult);
} // namespace mozilla
#endif // AccessibleCaretManager_h

View file

@ -0,0 +1,555 @@
/* 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 "ActiveLayerTracker.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/dom/KeyframeEffectReadOnly.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/EffectSet.h"
#include "mozilla/PodOperations.h"
#include "gfx2DGlue.h"
#include "nsExpirationTracker.h"
#include "nsContainerFrame.h"
#include "nsIContent.h"
#include "nsRefreshDriver.h"
#include "nsPIDOMWindow.h"
#include "nsIDocument.h"
#include "nsAnimationManager.h"
#include "nsStyleTransformMatrix.h"
#include "nsTransitionManager.h"
#include "nsDisplayList.h"
#include "nsDOMCSSDeclaration.h"
namespace mozilla {
using namespace gfx;
/**
* This tracks the state of a frame that may need active layers due to
* ongoing content changes or style changes that indicate animation.
*
* When no changes of *any* kind are detected after 75-100ms we remove this
* object. Because we only track all kinds of activity with a single
* nsExpirationTracker, it's possible a frame might remain active somewhat
* spuriously if different kinds of changes kept happening, but that almost
* certainly doesn't matter.
*/
class LayerActivity {
public:
enum ActivityIndex {
ACTIVITY_OPACITY,
ACTIVITY_TRANSFORM,
ACTIVITY_LEFT,
ACTIVITY_TOP,
ACTIVITY_RIGHT,
ACTIVITY_BOTTOM,
ACTIVITY_MARGIN_LEFT,
ACTIVITY_MARGIN_TOP,
ACTIVITY_MARGIN_RIGHT,
ACTIVITY_MARGIN_BOTTOM,
ACTIVITY_BACKGROUND_POSITION,
ACTIVITY_SCALE,
// keep as last item
ACTIVITY_COUNT
};
explicit LayerActivity(nsIFrame* aFrame)
: mFrame(aFrame)
, mContent(nullptr)
, mContentActive(false)
{
PodArrayZero(mRestyleCounts);
}
~LayerActivity();
nsExpirationState* GetExpirationState() { return &mState; }
uint8_t& RestyleCountForProperty(nsCSSPropertyID aProperty)
{
return mRestyleCounts[GetActivityIndexForProperty(aProperty)];
}
static ActivityIndex GetActivityIndexForProperty(nsCSSPropertyID aProperty)
{
switch (aProperty) {
case eCSSProperty_opacity: return ACTIVITY_OPACITY;
case eCSSProperty_transform: return ACTIVITY_TRANSFORM;
case eCSSProperty_left: return ACTIVITY_LEFT;
case eCSSProperty_top: return ACTIVITY_TOP;
case eCSSProperty_right: return ACTIVITY_RIGHT;
case eCSSProperty_bottom: return ACTIVITY_BOTTOM;
case eCSSProperty_margin_left: return ACTIVITY_MARGIN_LEFT;
case eCSSProperty_margin_top: return ACTIVITY_MARGIN_TOP;
case eCSSProperty_margin_right: return ACTIVITY_MARGIN_RIGHT;
case eCSSProperty_margin_bottom: return ACTIVITY_MARGIN_BOTTOM;
case eCSSProperty_background_position: return ACTIVITY_BACKGROUND_POSITION;
case eCSSProperty_background_position_x: return ACTIVITY_BACKGROUND_POSITION;
case eCSSProperty_background_position_y: return ACTIVITY_BACKGROUND_POSITION;
default: MOZ_ASSERT(false); return ACTIVITY_OPACITY;
}
}
// While tracked, exactly one of mFrame or mContent is non-null, depending
// on whether this property is stored on a frame or on a content node.
// When this property is expired by the layer activity tracker, both mFrame
// and mContent are nulled-out and the property is deleted.
nsIFrame* mFrame;
nsIContent* mContent;
nsExpirationState mState;
// Previous scale due to the CSS transform property.
Maybe<gfxSize> mPreviousTransformScale;
// The scroll frame during for which we most recently received a call to
// NotifyAnimatedFromScrollHandler.
nsWeakFrame mAnimatingScrollHandlerFrame;
// The set of activities that were triggered during
// mAnimatingScrollHandlerFrame's scroll event handler.
EnumSet<ActivityIndex> mScrollHandlerInducedActivity;
// Number of restyle operations detected
uint8_t mRestyleCounts[ACTIVITY_COUNT];
bool mContentActive;
};
class LayerActivityTracker final : public nsExpirationTracker<LayerActivity,4> {
public:
// 75-100ms is a good timeout period. We use 4 generations of 25ms each.
enum { GENERATION_MS = 100 };
LayerActivityTracker()
: nsExpirationTracker<LayerActivity,4>(GENERATION_MS,
"LayerActivityTracker")
, mDestroying(false)
{}
~LayerActivityTracker() {
mDestroying = true;
AgeAllGenerations();
}
virtual void NotifyExpired(LayerActivity* aObject);
public:
nsWeakFrame mCurrentScrollHandlerFrame;
private:
bool mDestroying;
};
static LayerActivityTracker* gLayerActivityTracker = nullptr;
LayerActivity::~LayerActivity()
{
if (mFrame || mContent) {
NS_ASSERTION(gLayerActivityTracker, "Should still have a tracker");
gLayerActivityTracker->RemoveObject(this);
}
}
// Frames with this property have NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY set
NS_DECLARE_FRAME_PROPERTY_DELETABLE(LayerActivityProperty, LayerActivity)
void
LayerActivityTracker::NotifyExpired(LayerActivity* aObject)
{
if (!mDestroying && aObject->mAnimatingScrollHandlerFrame.IsAlive()) {
// Reset the restyle counts, but let the layer activity survive.
PodArrayZero(aObject->mRestyleCounts);
MarkUsed(aObject);
return;
}
RemoveObject(aObject);
nsIFrame* f = aObject->mFrame;
nsIContent* c = aObject->mContent;
aObject->mFrame = nullptr;
aObject->mContent = nullptr;
MOZ_ASSERT((f == nullptr) != (c == nullptr),
"A LayerActivity object should always have a reference to either its frame or its content");
if (f) {
// The pres context might have been detached during the delay -
// that's fine, just skip the paint.
if (f->PresContext()->GetContainerWeak()) {
f->SchedulePaint();
}
f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
f->Properties().Delete(LayerActivityProperty());
} else {
c->DeleteProperty(nsGkAtoms::LayerActivity);
}
}
static LayerActivity*
GetLayerActivity(nsIFrame* aFrame)
{
if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
return nullptr;
}
FrameProperties properties = aFrame->Properties();
return properties.Get(LayerActivityProperty());
}
static LayerActivity*
GetLayerActivityForUpdate(nsIFrame* aFrame)
{
FrameProperties properties = aFrame->Properties();
LayerActivity* layerActivity = properties.Get(LayerActivityProperty());
if (layerActivity) {
gLayerActivityTracker->MarkUsed(layerActivity);
} else {
if (!gLayerActivityTracker) {
gLayerActivityTracker = new LayerActivityTracker();
}
layerActivity = new LayerActivity(aFrame);
gLayerActivityTracker->AddObject(layerActivity);
aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
properties.Set(LayerActivityProperty(), layerActivity);
}
return layerActivity;
}
static void
IncrementMutationCount(uint8_t* aCount)
{
*aCount = uint8_t(std::min(0xFF, *aCount + 1));
}
/* static */ void
ActiveLayerTracker::TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent)
{
if (!aFrame->HasAnyStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)) {
return;
}
FrameProperties properties = aFrame->Properties();
LayerActivity* layerActivity = properties.Remove(LayerActivityProperty());
aFrame->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
if (!layerActivity) {
return;
}
layerActivity->mFrame = nullptr;
layerActivity->mContent = aContent;
aContent->SetProperty(nsGkAtoms::LayerActivity, layerActivity,
nsINode::DeleteProperty<LayerActivity>, true);
}
/* static */ void
ActiveLayerTracker::TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame)
{
LayerActivity* layerActivity = static_cast<LayerActivity*>(
aContent->UnsetProperty(nsGkAtoms::LayerActivity));
if (!layerActivity) {
return;
}
layerActivity->mContent = nullptr;
layerActivity->mFrame = aFrame;
aFrame->AddStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
aFrame->Properties().Set(LayerActivityProperty(), layerActivity);
}
static void
IncrementScaleRestyleCountIfNeeded(nsIFrame* aFrame, LayerActivity* aActivity)
{
const nsStyleDisplay* display = aFrame->StyleDisplay();
if (!display->mSpecifiedTransform) {
// The transform was removed.
aActivity->mPreviousTransformScale = Nothing();
IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
return;
}
// Compute the new scale due to the CSS transform property.
nsPresContext* presContext = aFrame->PresContext();
RuleNodeCacheConditions dummy;
bool dummyBool;
nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
Matrix4x4 transform =
nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform->mHead,
aFrame->StyleContext(),
presContext,
dummy, refBox,
presContext->AppUnitsPerCSSPixel(),
&dummyBool);
Matrix transform2D;
if (!transform.Is2D(&transform2D)) {
// We don't attempt to handle 3D transforms; just assume the scale changed.
aActivity->mPreviousTransformScale = Nothing();
IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
return;
}
gfxSize scale = ThebesMatrix(transform2D).ScaleFactors(true);
if (aActivity->mPreviousTransformScale == Some(scale)) {
return; // Nothing changed.
}
aActivity->mPreviousTransformScale = Some(scale);
IncrementMutationCount(&aActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE]);
}
/* static */ void
ActiveLayerTracker::NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty)
{
LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
IncrementMutationCount(&mutationCount);
if (aProperty == eCSSProperty_transform) {
IncrementScaleRestyleCountIfNeeded(aFrame, layerActivity);
}
}
/* static */ void
ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame)
{
LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT]);
IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP]);
IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT]);
IncrementMutationCount(&layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM]);
}
/* static */ void
ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame,
nsCSSPropertyID aProperty,
const nsAString& aNewValue,
nsDOMCSSDeclaration* aDOMCSSDecl)
{
LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
if (mutationCount != 0xFF) {
nsAutoString oldValue;
aDOMCSSDecl->GetPropertyValue(aProperty, oldValue);
if (aNewValue != oldValue) {
// We know this is animated, so just hack the mutation count.
mutationCount = 0xFF;
}
}
}
/* static */ void
ActiveLayerTracker::NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSPropertyID aProperty,
nsIFrame* aScrollFrame)
{
if (aFrame->PresContext() != aScrollFrame->PresContext()) {
// Don't allow cross-document dependencies.
return;
}
LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
if (layerActivity->mAnimatingScrollHandlerFrame.GetFrame() != aScrollFrame) {
// Discard any activity of a different scroll frame. We only track the
// most recent scroll handler induced activity.
layerActivity->mScrollHandlerInducedActivity.clear();
layerActivity->mAnimatingScrollHandlerFrame = aScrollFrame;
}
layerActivity->mScrollHandlerInducedActivity += activityIndex;
}
static bool
IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext)
{
if (aPresContext->RefreshDriver()->IsInRefresh()) {
return true;
}
// Treat timeouts/setintervals as scripted animation callbacks for our
// purposes.
nsPIDOMWindowInner* win = aPresContext->Document()->GetInnerWindow();
return win && win->IsRunningTimeout();
}
/* static */ void
ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame,
nsCSSPropertyID aProperty,
const nsAString& aNewValue,
nsDOMCSSDeclaration* aDOMCSSDecl)
{
if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
NotifyAnimated(aFrame, aProperty, aNewValue, aDOMCSSDecl);
}
if (gLayerActivityTracker &&
gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) {
NotifyAnimatedFromScrollHandler(aFrame, aProperty,
gLayerActivityTracker->mCurrentScrollHandlerFrame.GetFrame());
}
}
/* static */ bool
ActiveLayerTracker::IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty)
{
return IsStyleAnimated(nullptr, aFrame, aProperty);
}
/* static */ bool
ActiveLayerTracker::IsBackgroundPositionAnimated(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame)
{
return IsStyleAnimated(aBuilder, aFrame, eCSSProperty_background_position_x) ||
IsStyleAnimated(aBuilder, aFrame, eCSSProperty_background_position_y);
}
static bool
CheckScrollInducedActivity(LayerActivity* aLayerActivity,
LayerActivity::ActivityIndex aActivityIndex,
nsDisplayListBuilder* aBuilder)
{
if (!aLayerActivity->mScrollHandlerInducedActivity.contains(aActivityIndex) ||
!aLayerActivity->mAnimatingScrollHandlerFrame.IsAlive()) {
return false;
}
nsIScrollableFrame* scrollFrame =
do_QueryFrame(aLayerActivity->mAnimatingScrollHandlerFrame.GetFrame());
if (scrollFrame && (!aBuilder || scrollFrame->IsScrollingActive(aBuilder))) {
return true;
}
// The scroll frame has been destroyed or has become inactive. Clear it from
// the layer activity so that it can expire.
aLayerActivity->mAnimatingScrollHandlerFrame = nullptr;
aLayerActivity->mScrollHandlerInducedActivity.clear();
return false;
}
/* static */ bool
ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsCSSPropertyID aProperty)
{
// TODO: Add some abuse restrictions
if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_TRANSFORM) &&
aProperty == eCSSProperty_transform &&
(!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
return true;
}
if ((aFrame->StyleDisplay()->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) &&
aProperty == eCSSProperty_opacity &&
(!aBuilder || aBuilder->IsInWillChangeBudget(aFrame, aFrame->GetSize()))) {
return true;
}
LayerActivity* layerActivity = GetLayerActivity(aFrame);
if (layerActivity) {
LayerActivity::ActivityIndex activityIndex = LayerActivity::GetActivityIndexForProperty(aProperty);
if (layerActivity->mRestyleCounts[activityIndex] >= 2) {
return true;
}
if (CheckScrollInducedActivity(layerActivity, activityIndex, aBuilder)) {
return true;
}
}
if (aProperty == eCSSProperty_transform && aFrame->Combines3DTransformWithAncestors()) {
return IsStyleAnimated(aBuilder, aFrame->GetParent(), aProperty);
}
return nsLayoutUtils::HasEffectiveAnimation(aFrame, aProperty);
}
/* static */ bool
ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame)
{
LayerActivity* layerActivity = GetLayerActivity(aFrame);
if (layerActivity) {
if (layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_LEFT] >= 2 ||
layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_TOP] >= 2 ||
layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_RIGHT] >= 2 ||
layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_BOTTOM] >= 2 ||
layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_LEFT] >= 2 ||
layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_TOP] >= 2 ||
layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_RIGHT] >= 2 ||
layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_MARGIN_BOTTOM] >= 2) {
return true;
}
}
// We should also check for running CSS animations of these properties once
// bug 1009693 is fixed. Until that happens, layerization isn't useful for
// animations of these properties because we'll invalidate the layer contents
// on every change anyway.
// See bug 1151346 for a patch that adds a check for CSS animations.
return false;
}
// A helper function for IsScaleSubjectToAnimation which returns true if the
// given EffectSet contains a current effect that animates scale.
static bool
ContainsAnimatedScale(EffectSet& aEffects, nsIFrame* aFrame)
{
for (dom::KeyframeEffectReadOnly* effect : aEffects) {
if (!effect->IsCurrent()) {
continue;
}
for (const AnimationProperty& prop : effect->Properties()) {
if (prop.mProperty != eCSSProperty_transform) {
continue;
}
for (AnimationPropertySegment segment : prop.mSegments) {
gfxSize from = segment.mFromValue.GetScaleValue(aFrame);
if (from != gfxSize(1.0f, 1.0f)) {
return true;
}
gfxSize to = segment.mToValue.GetScaleValue(aFrame);
if (to != gfxSize(1.0f, 1.0f)) {
return true;
}
}
}
}
return false;
}
/* static */ bool
ActiveLayerTracker::IsScaleSubjectToAnimation(nsIFrame* aFrame)
{
// Check whether JavaScript is animating this frame's scale.
LayerActivity* layerActivity = GetLayerActivity(aFrame);
if (layerActivity && layerActivity->mRestyleCounts[LayerActivity::ACTIVITY_SCALE] >= 2) {
return true;
}
// Check if any animations, transitions, etc. associated with this frame may
// animate its scale.
EffectSet* effects = EffectSet::GetEffectSet(aFrame);
if (effects && ContainsAnimatedScale(*effects, aFrame)) {
return true;
}
return false;
}
/* static */ void
ActiveLayerTracker::NotifyContentChange(nsIFrame* aFrame)
{
LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
layerActivity->mContentActive = true;
}
/* static */ bool
ActiveLayerTracker::IsContentActive(nsIFrame* aFrame)
{
LayerActivity* layerActivity = GetLayerActivity(aFrame);
return layerActivity && layerActivity->mContentActive;
}
/* static */ void
ActiveLayerTracker::SetCurrentScrollHandlerFrame(nsIFrame* aFrame)
{
if (!gLayerActivityTracker) {
gLayerActivityTracker = new LayerActivityTracker();
}
gLayerActivityTracker->mCurrentScrollHandlerFrame = aFrame;
}
/* static */ void
ActiveLayerTracker::Shutdown()
{
delete gLayerActivityTracker;
gLayerActivityTracker = nullptr;
}
} // namespace mozilla

View file

@ -0,0 +1,141 @@
/* 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 ACTIVELAYERTRACKER_H_
#define ACTIVELAYERTRACKER_H_
#include "nsCSSPropertyID.h"
class nsIFrame;
class nsIContent;
class nsDisplayListBuilder;
class nsDOMCSSDeclaration;
namespace mozilla {
/**
* This class receives various notifications about style changes and content
* changes that affect layerization decisions, and implements the heuristics
* that drive those decisions. It manages per-frame state to support those
* heuristics.
*/
class ActiveLayerTracker {
public:
static void Shutdown();
/*
* We track style changes to selected styles:
* eCSSProperty_transform
* eCSSProperty_opacity
* eCSSProperty_left, eCSSProperty_top, eCSSProperty_right, eCSSProperty_bottom
* and use that information to guess whether style changes are animated.
*/
/**
* Notify aFrame's style property as having changed due to a restyle,
* and therefore possibly wanting an active layer to render that style.
* Any such marking will time out after a short period.
* @param aProperty the property that has changed
*/
static void NotifyRestyle(nsIFrame* aFrame, nsCSSPropertyID aProperty);
/**
* Notify aFrame's left/top/right/bottom properties as having (maybe)
* changed due to a restyle, and therefore possibly wanting an active layer
* to render that style. Any such marking will time out after a short period.
*/
static void NotifyOffsetRestyle(nsIFrame* aFrame);
/**
* Mark aFrame as being known to have an animation of aProperty.
* Any such marking will time out after a short period.
* aNewValue and aDOMCSSDecl are used to determine whether the property's
* value has changed.
*/
static void NotifyAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty,
const nsAString& aNewValue,
nsDOMCSSDeclaration* aDOMCSSDecl);
/**
* Notify aFrame as being known to have an animation of aProperty through an
* inline style modification during aScrollFrame's scroll event handler.
*/
static void NotifyAnimatedFromScrollHandler(nsIFrame* aFrame, nsCSSPropertyID aProperty,
nsIFrame* aScrollFrame);
/**
* Notify that a property in the inline style rule of aFrame's element
* has been modified.
* This notification is incomplete --- not all modifications to inline
* style will trigger this.
* aNewValue and aDOMCSSDecl are used to determine whether the property's
* value has changed.
*/
static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, nsCSSPropertyID aProperty,
const nsAString& aNewValue,
nsDOMCSSDeclaration* aDOMCSSDecl);
/**
* Return true if aFrame's aProperty style should be considered as being animated
* for pre-rendering.
*/
static bool IsStyleMaybeAnimated(nsIFrame* aFrame, nsCSSPropertyID aProperty);
/**
* Return true if aFrame's aProperty style should be considered as being animated
* for constructing active layers.
*/
static bool IsStyleAnimated(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsCSSPropertyID aProperty);
/**
* Return true if any of aFrame's offset property styles should be considered
* as being animated for constructing active layers.
*/
static bool IsOffsetOrMarginStyleAnimated(nsIFrame* aFrame);
/**
* Return true if aFrame's background-position-x or background-position-y
* property is animated.
*/
static bool IsBackgroundPositionAnimated(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame);
/**
* Return true if aFrame either has an animated scale now, or is likely to
* have one in the future because it has a CSS animation or transition
* (which may not be playing right now) that affects its scale.
*/
static bool IsScaleSubjectToAnimation(nsIFrame* aFrame);
/**
* Transfer the LayerActivity property to the frame's content node when the
* frame is about to be destroyed so that layer activity can be tracked
* throughout reframes of an element. Only call this when aFrame is the
* primary frame of aContent.
*/
static void TransferActivityToContent(nsIFrame* aFrame, nsIContent* aContent);
/**
* Transfer the LayerActivity property back to the content node's primary
* frame after the frame has been created.
*/
static void TransferActivityToFrame(nsIContent* aContent, nsIFrame* aFrame);
/*
* We track modifications to the content of certain frames (i.e. canvas frames)
* and use that to make layering decisions.
*/
/**
* Mark aFrame's content as being active. This marking will time out after
* a short period.
*/
static void NotifyContentChange(nsIFrame* aFrame);
/**
* Return true if this frame's content is still marked as active.
*/
static bool IsContentActive(nsIFrame* aFrame);
/**
* Called before and after a scroll event handler is executed, with the
* scrollframe or nullptr, respectively. This acts as a hint to treat
* inline style changes during the handler differently.
*/
static void SetCurrentScrollHandlerFrame(nsIFrame* aFrame);
};
} // namespace mozilla
#endif /* ACTIVELAYERTRACKER_H_ */

View file

@ -0,0 +1,36 @@
/* -*- Mode: C++; 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/. */
/* enum type for objects that can be allocated by an nsPresArena */
#ifndef mozilla_ArenaObjectID_h
#define mozilla_ArenaObjectID_h
#include "nsQueryFrame.h"
namespace mozilla {
enum ArenaObjectID {
eArenaObjectID_DummyBeforeFirstObjectID = nsQueryFrame::NON_FRAME_MARKER - 1,
#define PRES_ARENA_OBJECT(name_) \
eArenaObjectID_##name_,
#include "nsPresArenaObjectList.h"
#undef PRES_ARENA_OBJECT
/**
* The PresArena implementation uses this bit to distinguish objects
* allocated by size from objects allocated by type ID (that is, frames
* using AllocateByFrameID and other objects using AllocateByObjectID).
* It should not collide with any Object ID (above) or frame ID (in
* nsQueryFrame.h). It is not 0x80000000 to avoid the question of
* whether enumeration constants are signed.
*/
eArenaObjectID_NON_OBJECT_MARKER = 0x40000000
};
};
#endif

167
layout/base/ArenaRefPtr.h Normal file
View file

@ -0,0 +1,167 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=2 sw=2 et tw=78:
* 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/.
*/
/* smart pointer for strong references to nsPresArena-allocated objects
that might be held onto until the arena's destruction */
#include "mozilla/Assertions.h"
#include "mozilla/RefPtr.h"
#ifndef mozilla_ArenaRefPtr_h
#define mozilla_ArenaRefPtr_h
class nsPresArena;
namespace mozilla {
/**
* A class for holding strong references to nsPresArena-allocated
* objects.
*
* Since the arena's lifetime is not related to the refcounts
* of the objects allocated within it, it is possible to have a strong
* reference to an arena-allocated object that lives until the
* destruction of the arena. An ArenaRefPtr acts like a weak reference
* in that it will clear its referent if the arena is about to go away.
*
* T must be a class that has these two methods:
*
* static mozilla::ArenaObjectID ArenaObjectID();
* U* Arena();
*
* where U is a class that has these two methods:
*
* void RegisterArenaRefPtr(ArenaRefPtr<T>*);
* void DeregisterArenaRefPtr(ArenaRefPtr<T>*);
*
* Currently, both nsPresArena and nsIPresShell can be used as U.
*
* The ArenaObjectID method must return the mozilla::ArenaObjectID that
* uniquely identifies T, and the Arena method must return the nsPresArena
* (or a proxy for it) in which the object was allocated.
*/
template<typename T>
class ArenaRefPtr
{
friend class ::nsPresArena;
public:
ArenaRefPtr()
{
AssertValidType();
}
template<typename I>
MOZ_IMPLICIT ArenaRefPtr(already_AddRefed<I>& aRhs)
{
AssertValidType();
assign(aRhs);
}
template<typename I>
MOZ_IMPLICIT ArenaRefPtr(already_AddRefed<I>&& aRhs)
{
AssertValidType();
assign(aRhs);
}
MOZ_IMPLICIT ArenaRefPtr(T* aRhs)
{
AssertValidType();
assign(aRhs);
}
template<typename I>
ArenaRefPtr<T>& operator=(already_AddRefed<I>& aRhs)
{
assign(aRhs);
return *this;
}
template<typename I>
ArenaRefPtr<T>& operator=(already_AddRefed<I>&& aRhs)
{
assign(aRhs);
return *this;
}
ArenaRefPtr<T>& operator=(T* aRhs)
{
assign(aRhs);
return *this;
}
~ArenaRefPtr() { assign(nullptr); }
#ifdef MOZ_HAVE_REF_QUALIFIERS
operator T*() const & { return get(); }
operator T*() const && = delete;
explicit operator bool() const { return !!mPtr; }
bool operator!() const { return !mPtr; }
#else
operator T*() const { return get(); }
#endif
T* operator->() const { return mPtr.operator->(); }
T& operator*() const { return *get(); }
T* get() const { return mPtr; }
private:
void AssertValidType();
/**
* Clears the pointer to the arena-allocated object but skips the usual
* step of deregistering the ArenaRefPtr from the nsPresArena. This
* method is called by nsPresArena when clearing all registered ArenaRefPtrs
* so that it can deregister them all at once, avoiding hash table churn.
*/
void ClearWithoutDeregistering()
{
mPtr = nullptr;
}
template<typename I>
void assign(already_AddRefed<I>& aSmartPtr)
{
RefPtr<T> newPtr(aSmartPtr);
assignFrom(newPtr);
}
template<typename I>
void assign(already_AddRefed<I>&& aSmartPtr)
{
RefPtr<T> newPtr(aSmartPtr);
assignFrom(newPtr);
}
void assign(T* aPtr) { assignFrom(aPtr); }
template<typename I>
void assignFrom(I& aPtr)
{
if (aPtr == mPtr) {
return;
}
bool sameArena = mPtr && aPtr && mPtr->Arena() == aPtr->Arena();
if (mPtr && !sameArena) {
MOZ_ASSERT(mPtr->Arena());
mPtr->Arena()->DeregisterArenaRefPtr(this);
}
mPtr = Move(aPtr);
if (mPtr && !sameArena) {
MOZ_ASSERT(mPtr->Arena());
mPtr->Arena()->RegisterArenaRefPtr(this);
}
}
RefPtr<T> mPtr;
};
} // namespace mozilla
#endif // mozilla_ArenaRefPtr_h

View file

@ -0,0 +1,47 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=2 sw=2 et tw=78:
* 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/.
*/
/* inline methods that belong in ArenaRefPtr.h, except that they require
the inclusion of headers for all types that ArenaRefPtr can handle */
#ifndef mozilla_ArenaRefPtrInlines_h
#define mozilla_ArenaRefPtrInlines_h
#include "mozilla/ArenaObjectID.h"
#include "mozilla/Assertions.h"
#include "nsStyleStruct.h"
namespace mozilla {
template<typename T>
void
ArenaRefPtr<T>::AssertValidType()
{
#ifdef DEBUG
bool ok =
#define PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT(name_) \
T::ArenaObjectID() == eArenaObjectID_##name_ ||
#include "nsPresArenaObjectList.h"
#undef PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT
false;
MOZ_ASSERT(ok, "ArenaRefPtr<T> template parameter T must be declared in "
"nsPresArenaObjectList with "
"PRES_ARENA_OBJECT_WITH_ARENAREFPTR_SUPPORT");
#endif
}
} // namespace mozilla
template<typename T>
void
nsPresArena::RegisterArenaRefPtr(mozilla::ArenaRefPtr<T>* aPtr)
{
MOZ_ASSERT(!mArenaRefPtrs.Contains(aPtr));
mArenaRefPtrs.Put(aPtr, T::ArenaObjectID());
}
#endif

75
layout/base/BorderCache.h Normal file
View file

@ -0,0 +1,75 @@
/* -*- Mode: C++; 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/. */
#ifndef mozilla_BorderCache_h_
#define mozilla_BorderCache_h_
#include "mozilla/gfx/2D.h"
#include "mozilla/HashFunctions.h"
#include "nsDataHashtable.h"
#include "PLDHashTable.h"
namespace mozilla {
// Cache for best overlap and best dashLength.
struct FourFloats
{
typedef mozilla::gfx::Float Float;
Float n[4];
FourFloats()
{
n[0] = 0.0f;
n[1] = 0.0f;
n[2] = 0.0f;
n[3] = 0.0f;
}
FourFloats(Float a, Float b, Float c, Float d)
{
n[0] = a;
n[1] = b;
n[2] = c;
n[3] = d;
}
bool
operator==(const FourFloats& aOther) const
{
return n[0] == aOther.n[0] &&
n[1] == aOther.n[1] &&
n[2] == aOther.n[2] &&
n[3] == aOther.n[3];
}
};
class FourFloatsHashKey : public PLDHashEntryHdr
{
public:
typedef const FourFloats& KeyType;
typedef const FourFloats* KeyTypePointer;
explicit FourFloatsHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
FourFloatsHashKey(const FourFloatsHashKey& aToCopy) : mValue(aToCopy.mValue) {}
~FourFloatsHashKey() {}
KeyType GetKey() const { return mValue; }
bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
static PLDHashNumber HashKey(KeyTypePointer aKey)
{
return HashBytes(aKey->n, sizeof(mozilla::gfx::Float) * 4);
}
enum { ALLOW_MEMMOVE = true };
private:
const FourFloats mValue;
};
} // namespace mozilla
#endif /* mozilla_BorderCache_h_ */

View file

@ -0,0 +1,28 @@
/* -*- Mode: C++; 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/. */
#ifndef mozilla_BorderConsts_h_
#define mozilla_BorderConsts_h_
// thickness of dashed line relative to dotted line
#define DOT_LENGTH 1 // square
#define DASH_LENGTH 3 // 3 times longer than dot
// some shorthand for side bits
#define SIDE_BIT_TOP (1 << NS_SIDE_TOP)
#define SIDE_BIT_RIGHT (1 << NS_SIDE_RIGHT)
#define SIDE_BIT_BOTTOM (1 << NS_SIDE_BOTTOM)
#define SIDE_BIT_LEFT (1 << NS_SIDE_LEFT)
#define SIDE_BITS_ALL (SIDE_BIT_TOP|SIDE_BIT_RIGHT|SIDE_BIT_BOTTOM|SIDE_BIT_LEFT)
#define C_TL NS_CORNER_TOP_LEFT
#define C_TR NS_CORNER_TOP_RIGHT
#define C_BR NS_CORNER_BOTTOM_RIGHT
#define C_BL NS_CORNER_BOTTOM_LEFT
#define BORDER_SEGMENT_COUNT_MAX 100
#define BORDER_DOTTED_CORNER_MAX_RADIUS 100000
#endif /* mozilla_BorderConsts_h_ */

View file

@ -0,0 +1,22 @@
/* 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 CaretAssociationHint_h___
#define CaretAssociationHint_h___
namespace mozilla {
/**
* Hint whether a caret is associated with the content before a
* given character offset (ASSOCIATE_BEFORE), or with the content after a given
* character offset (ASSOCIATE_AFTER).
*/
enum CaretAssociationHint {
CARET_ASSOCIATE_BEFORE,
CARET_ASSOCIATE_AFTER
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,427 @@
/* -*- Mode: C++; 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/. */
#include "DashedCornerFinder.h"
#include "mozilla/Move.h"
#include "BorderCache.h"
#include "BorderConsts.h"
namespace mozilla {
using namespace gfx;
struct BestDashLength
{
typedef mozilla::gfx::Float Float;
Float dashLength;
size_t count;
BestDashLength()
: dashLength(0.0f), count(0)
{}
BestDashLength(Float aDashLength, size_t aCount)
: dashLength(aDashLength), count(aCount)
{}
};
static const size_t DashedCornerCacheSize = 256;
nsDataHashtable<FourFloatsHashKey, BestDashLength> DashedCornerCache;
DashedCornerFinder::DashedCornerFinder(const Bezier& aOuterBezier,
const Bezier& aInnerBezier,
Float aBorderWidthH, Float aBorderWidthV,
const Size& aCornerDim)
: mOuterBezier(aOuterBezier),
mInnerBezier(aInnerBezier),
mLastOuterP(aOuterBezier.mPoints[0]), mLastInnerP(aInnerBezier.mPoints[0]),
mLastOuterT(0.0f), mLastInnerT(0.0f),
mBestDashLength(DOT_LENGTH * DASH_LENGTH),
mHasZeroBorderWidth(false), mHasMore(true),
mMaxCount(aCornerDim.width + aCornerDim.height),
mType(OTHER),
mI(0), mCount(0)
{
NS_ASSERTION(aBorderWidthH > 0.0f || aBorderWidthV > 0.0f,
"At least one side should have non-zero width.");
DetermineType(aBorderWidthH, aBorderWidthV);
Reset();
}
void
DashedCornerFinder::DetermineType(Float aBorderWidthH, Float aBorderWidthV)
{
if (aBorderWidthH < aBorderWidthV) {
// Always draw from wider side to thinner side.
Swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
Swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
Swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
Swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
mLastOuterP = mOuterBezier.mPoints[0];
mLastInnerP = mInnerBezier.mPoints[0];
}
// See the comment at mType declaration for each condition.
Float borderRadiusA = fabs(mOuterBezier.mPoints[0].x -
mOuterBezier.mPoints[3].x);
Float borderRadiusB = fabs(mOuterBezier.mPoints[0].y -
mOuterBezier.mPoints[3].y);
if (aBorderWidthH == aBorderWidthV &&
borderRadiusA == borderRadiusB &&
borderRadiusA > aBorderWidthH * 2.0f) {
Float curveHeight = borderRadiusA - aBorderWidthH / 2.0;
mType = PERFECT;
Float borderLength = M_PI * curveHeight / 2.0f;
Float dashWidth = aBorderWidthH * DOT_LENGTH * DASH_LENGTH;
size_t count = ceil(borderLength / dashWidth);
if (count % 2) {
count++;
}
mCount = count / 2 + 1;
mBestDashLength = borderLength / (aBorderWidthH * count);
}
Float minBorderWidth = std::min(aBorderWidthH, aBorderWidthV);
if (minBorderWidth == 0.0f) {
mHasZeroBorderWidth = true;
}
if (mType == OTHER && !mHasZeroBorderWidth) {
Float minBorderRadius = std::min(borderRadiusA, borderRadiusB);
Float maxBorderRadius = std::max(borderRadiusA, borderRadiusB);
Float maxBorderWidth = std::max(aBorderWidthH, aBorderWidthV);
FindBestDashLength(minBorderWidth, maxBorderWidth,
minBorderRadius, maxBorderRadius);
}
}
bool
DashedCornerFinder::HasMore(void) const
{
if (mHasZeroBorderWidth) {
return mI < mMaxCount && mHasMore;
}
return mI < mCount;
}
DashedCornerFinder::Result
DashedCornerFinder::Next(void)
{
Float lastOuterT, lastInnerT, outerT, innerT;
if (mI == 0) {
lastOuterT = 0.0f;
lastInnerT = 0.0f;
} else {
if (mType == PERFECT) {
lastOuterT = lastInnerT = (mI * 2.0f - 0.5f) / ((mCount - 1) * 2.0f);
} else {
Float last2OuterT = mLastOuterT;
Float last2InnerT = mLastInnerT;
(void)FindNext(mBestDashLength);
//
// mLastOuterT lastOuterT
// | |
// v v
// +---+---+---+---+ <- last2OuterT
// | |###|###| |
// | |###|###| |
// | |###|###| |
// +---+---+---+---+ <- last2InnerT
// ^ ^
// | |
// mLastInnerT lastInnerT
lastOuterT = (mLastOuterT + last2OuterT) / 2.0f;
lastInnerT = (mLastInnerT + last2InnerT) / 2.0f;
}
}
if ((!mHasZeroBorderWidth && mI == mCount - 1) ||
(mHasZeroBorderWidth && !mHasMore)) {
outerT = 1.0f;
innerT = 1.0f;
} else {
if (mType == PERFECT) {
outerT = innerT = (mI * 2.0f + 0.5f) / ((mCount - 1) * 2.0f);
} else {
Float last2OuterT = mLastOuterT;
Float last2InnerT = mLastInnerT;
(void)FindNext(mBestDashLength);
//
// outerT last2OuterT
// | |
// v v
// mLastOuterT -> +---+---+---+---+
// | |###|###| |
// | |###|###| |
// | |###|###| |
// mLastInnerT -> +---+---+---+---+
// ^ ^
// | |
// innerT last2InnerT
outerT = (mLastOuterT + last2OuterT) / 2.0f;
innerT = (mLastInnerT + last2InnerT) / 2.0f;
}
}
mI++;
Bezier outerSectionBezier;
Bezier innerSectionBezier;
GetSubBezier(&outerSectionBezier, mOuterBezier, lastOuterT, outerT);
GetSubBezier(&innerSectionBezier, mInnerBezier, lastInnerT, innerT);
return DashedCornerFinder::Result(outerSectionBezier, innerSectionBezier);
}
void
DashedCornerFinder::Reset(void)
{
mLastOuterP = mOuterBezier.mPoints[0];
mLastInnerP = mInnerBezier.mPoints[0];
mLastOuterT = 0.0f;
mLastInnerT = 0.0f;
mHasMore = true;
}
Float
DashedCornerFinder::FindNext(Float dashLength)
{
Float upper = 1.0f;
Float lower = mLastOuterT;
Point OuterP, InnerP;
// Start from upper bound to check if this is the last segment.
Float outerT = upper;
Float innerT;
Float W = 0.0f;
Float L = 0.0f;
const Float LENGTH_MARGIN = 0.1f;
for (size_t i = 0; i < MAX_LOOP; i++) {
OuterP = GetBezierPoint(mOuterBezier, outerT);
InnerP = FindBezierNearestPoint(mInnerBezier, OuterP, outerT, &innerT);
// Calculate approximate dash length.
//
// W = (W1 + W2) / 2
// L = (OuterL + InnerL) / 2
// dashLength = L / W
//
// ____----+----____
// OuterP ___--- | ---___ mLastOuterP
// +--- | ---+
// | | |
// | | |
// | W | W1 |
// | | |
// W2 | | |
// | | ______------+
// | ____+---- mLastInnerP
// | ___---
// | __---
// +--
// InnerP
// OuterL
// ____---------____
// OuterP ___--- ---___ mLastOuterP
// +--- ---+
// | L |
// | ___----------______ |
// | __--- -----+
// | __-- |
// +-- |
// | InnerL ______------+
// | ____----- mLastInnerP
// | ___---
// | __---
// +--
// InnerP
Float W1 = (mLastOuterP - mLastInnerP).Length();
Float W2 = (OuterP - InnerP).Length();
Float OuterL = GetBezierLength(mOuterBezier, mLastOuterT, outerT);
Float InnerL = GetBezierLength(mInnerBezier, mLastInnerT, innerT);
W = (W1 + W2) / 2.0f;
L = (OuterL + InnerL) / 2.0f;
if (L > W * dashLength + LENGTH_MARGIN) {
if (i > 0) {
upper = outerT;
}
} else if (L < W * dashLength - LENGTH_MARGIN) {
if (i == 0) {
// This is the last segment with shorter dashLength.
mHasMore = false;
break;
}
lower = outerT;
} else {
break;
}
outerT = (upper + lower) / 2.0f;
}
mLastOuterP = OuterP;
mLastInnerP = InnerP;
mLastOuterT = outerT;
mLastInnerT = innerT;
if (W == 0.0f) {
return 1.0f;
}
return L / W;
}
void
DashedCornerFinder::FindBestDashLength(Float aMinBorderWidth,
Float aMaxBorderWidth,
Float aMinBorderRadius,
Float aMaxBorderRadius)
{
// If dashLength is not calculateable, find it with binary search,
// such that there exists i that OuterP_i == OuterP_n and
// InnerP_i == InnerP_n with given dashLength.
FourFloats key(aMinBorderWidth, aMaxBorderWidth,
aMinBorderRadius, aMaxBorderRadius);
BestDashLength best;
if (DashedCornerCache.Get(key, &best)) {
mCount = best.count;
mBestDashLength = best.dashLength;
return;
}
Float lower = 1.0f;
Float upper = DOT_LENGTH * DASH_LENGTH;
Float dashLength = upper;
size_t targetCount = 0;
const Float LENGTH_MARGIN = 0.1f;
for (size_t j = 0; j < MAX_LOOP; j++) {
size_t count;
Float actualDashLength;
if (!GetCountAndLastDashLength(dashLength, &count, &actualDashLength)) {
if (j == 0) {
mCount = mMaxCount;
break;
}
}
if (j == 0) {
if (count == 1) {
// If only 1 segment fits, fill entire region
//
// count = 1
// mCount = 1
// | 1 |
// +---+---+
// |###|###|
// |###|###|
// |###|###|
// +---+---+
// 1
mCount = 1;
break;
}
// targetCount should be 2n.
//
// targetCount = 2
// mCount = 2
// | 1 | 2 |
// +---+---+---+---+
// |###| | |###|
// |###| | |###|
// |###| | |###|
// +---+---+---+---+
// 1 2
//
// targetCount = 6
// mCount = 4
// | 1 | 2 | 3 | 4 | 5 | 6 |
// +---+---+---+---+---+---+---+---+---+---+---+---+
// |###| | |###|###| | |###|###| | |###|
// |###| | |###|###| | |###|###| | |###|
// |###| | |###|###| | |###|###| | |###|
// +---+---+---+---+---+---+---+---+---+---+---+---+
// 1 2 3 4
if (count % 2) {
targetCount = count + 1;
} else {
targetCount = count;
}
mCount = targetCount / 2 + 1;
}
if (count == targetCount) {
mBestDashLength = dashLength;
// actualDashLength won't be greater than dashLength.
if (actualDashLength > dashLength - LENGTH_MARGIN) {
break;
}
// We started from upper bound, no need to update range when j == 0.
if (j > 0) {
upper = dashLength;
}
} else {
// |j == 0 && count != targetCount| means that |targetCount = count + 1|,
// and we started from upper bound, no need to update range when j == 0.
if (j > 0) {
if (count > targetCount) {
lower = dashLength;
} else {
upper = dashLength;
}
}
}
dashLength = (upper + lower) / 2.0f;
}
if (DashedCornerCache.Count() > DashedCornerCacheSize) {
DashedCornerCache.Clear();
}
DashedCornerCache.Put(key, BestDashLength(mBestDashLength, mCount));
}
bool
DashedCornerFinder::GetCountAndLastDashLength(Float aDashLength,
size_t* aCount,
Float* aActualDashLength)
{
// Return the number of segments and the last segment's dashLength for
// the given dashLength.
Reset();
for (size_t i = 0; i < mMaxCount; i++) {
Float actualDashLength = FindNext(aDashLength);
if (mLastOuterT >= 1.0f) {
*aCount = i + 1;
*aActualDashLength = actualDashLength;
return true;
}
}
return false;
}
} // namespace mozilla

View file

@ -0,0 +1,277 @@
/* -*- Mode: C++; 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/. */
#ifndef mozilla_DashedCornerFinder_h_
#define mozilla_DashedCornerFinder_h_
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/BezierUtils.h"
namespace mozilla {
// Calculate {OuterT_i, InnerT_i} for each 1 < i < n, that
// (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2
// where
// OuterP_i: OuterCurve(OuterT_i)
// InnerP_i: InnerCurve(OuterT_i)
// OuterL_i: Elliptic arc length between OuterP_i - OuterP_{i-1}
// InnerL_i: Elliptic arc length between InnerP_i - InnerP_{i-1}
// W_i = |OuterP_i - InnerP_i|
// 1.0 < dashLength < 3.0
//
// OuterP_1 OuterP_0
// _+__-----------+ OuterCurve
// OuterP_2 __---- | OuterL_1 |
// __+--- | |
// __--- | OuterL_2 | |
// OuterP_3 _-- | | W_1 | W_0
// _+ | | |
// / \ W_2 | | |
// / \ | | InnerL_1 |
// | \ | InnerL_2|____-------+ InnerCurve
// | \ |____----+ InnerP_0
// | . \ __---+ InnerP_1
// | \ / InnerP_2
// | . /+ InnerP_3
// | |
// | . |
// | |
// | |
// | |
// OuterP_{n-1} +--------+ InnerP_{n-1}
// | |
// | |
// | |
// | |
// | |
// OuterP_n +--------+ InnerP_n
//
// Returns region with [OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2),
// OuterCurve((OuterT_{2j} + OuterT_{2j-1}) / 2),
// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2),
// InnerCurve((OuterT_{2j} + OuterT_{2j+1}) / 2)],
// to start and end with half segment.
//
// _+__----+------+ OuterCurve
// _+---- | |######|
// __+---#| | |######|
// _+---##|####| | |######|
// _-- |#####|#####| | |#####|
// _+ |#####|#####| | |#####|
// / \ |#####|####| | |#####|
// / \ |####|#####| | |#####|
// | \ |####|####| |____-+-----+ InnerCurve
// | \ |####|____+---+
// | . \ __+---+
// | \ /
// | . /+
// | |
// | . |
// | |
// | |
// | |
// +--------+
// | |
// | |
// +--------+
// |########|
// |########|
// +--------+
class DashedCornerFinder
{
typedef mozilla::gfx::Bezier Bezier;
typedef mozilla::gfx::Float Float;
typedef mozilla::gfx::Point Point;
typedef mozilla::gfx::Size Size;
public:
struct Result
{
// Control points for the outer curve and the inner curve.
//
// outerSectionBezier
// |
// v _+ 3
// ___---#|
// 0 +---#######|
// |###########|
// |###########|
// |##########|
// |##########|
// |#########|
// |#####____+ 3
// 0 +----
// ^
// |
// innerSectionBezier
Bezier outerSectionBezier;
Bezier innerSectionBezier;
Result(const Bezier& aOuterSectionBezier,
const Bezier& aInnerSectionBezier)
: outerSectionBezier(aOuterSectionBezier),
innerSectionBezier(aInnerSectionBezier)
{}
};
// aCornerDim.width
// |<----------------->|
// | |
// --+-------------___---+--
// ^ | __-- | ^
// | | _- | |
// | | / | | aBorderWidthH
// | | / | |
// | | | | v
// | | | __--+--
// aCornerDim.height | || _-
// | || /
// | | /
// | | |
// | | |
// | | |
// | | |
// v | |
// --+---------+
// | |
// |<------->|
// aBorderWidthV
DashedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier,
Float aBorderWidthH, Float aBorderWidthV,
const Size& aCornerDim);
bool HasMore(void) const;
Result Next(void);
private:
static const size_t MAX_LOOP = 32;
// Bezier control points for the outer curve and the inner curve.
//
// ___---+ outer curve
// __-- |
// _- |
// / |
// / |
// | |
// | __--+ inner curve
// | _-
// | /
// | /
// | |
// | |
// | |
// | |
// | |
// +---------+
Bezier mOuterBezier;
Bezier mInnerBezier;
Point mLastOuterP;
Point mLastInnerP;
Float mLastOuterT;
Float mLastInnerT;
// Length for each segment, ratio of the border width at that point.
Float mBestDashLength;
// If one of border-widths is 0, do not calculate mBestDashLength, and draw
// segments until it reaches the other side or exceeds mMaxCount.
bool mHasZeroBorderWidth;
bool mHasMore;
// The maximum number of segments.
size_t mMaxCount;
enum {
// radius.width
// |<----------------->|
// | |
// --+-------------___---+--
// ^ | __-- | ^
// | | _- | |
// | | / + | top-width
// | | / | |
// | | | | v
// | | | __--+--
// radius.height | || _-
// | || /
// | | /
// | | |
// | | |
// | | |
// | | |
// v | |
// --+----+----+
// | |
// |<------->|
// left-width
// * top-width == left-width
// * radius.width == radius.height
// * top-width < radius.width * 2
//
// Split the perfect circle's arc into 2n segments, each segment's length is
// top-width * dashLength. Then split the inner curve and the outer curve
// with same angles.
//
// radius.width
// |<---------------------->|
// | | v
// --+------------------------+--
// ^ | | | top-width / 2
// | | perfect | |
// | | circle's ___---+--
// | | arc __-+ | ^
// | | | _- | |
// radius.height | | | + | +--
// | | | / \ | |
// | | | | \ | |
// | | | | \ | |
// | | +->| \ | |
// | | +---__ \ | |
// | | | --__ \ | |
// | | | ---__ \ | |
// v | | --_\||
// --+----+----+--------------+
// | | |
// |<-->| |
// left-width / 2
PERFECT,
// Other cases.
//
// Split the outer curve and the inner curve into 2n segments, each segment
// satisfies following:
// (OuterL_i + InnerL_i) / 2 == dashLength * (W_i + W_{i-1}) / 2
OTHER
} mType;
size_t mI;
size_t mCount;
// Determine mType from parameters.
void DetermineType(Float aBorderWidthH, Float aBorderWidthV);
// Reset calculation.
void Reset(void);
// Find next segment.
Float FindNext(Float dashLength);
// Find mBestDashLength for parameters.
void FindBestDashLength(Float aMinBorderWidth, Float aMaxBorderWidth,
Float aMinBorderRadius, Float aMaxBorderRadius);
// Fill corner with dashes with given dash length, and return the number of
// segments and last segment's dash length.
bool GetCountAndLastDashLength(Float aDashLength,
size_t* aCount, Float* aActualDashLength);
};
} // namespace mozilla
#endif /* mozilla_DashedCornerFinder_h_ */

View file

@ -0,0 +1,476 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#include "DisplayItemClip.h"
#include "gfxContext.h"
#include "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/PathHelpers.h"
#include "nsPresContext.h"
#include "nsCSSRendering.h"
#include "nsLayoutUtils.h"
#include "nsRegion.h"
using namespace mozilla::gfx;
namespace mozilla {
void
DisplayItemClip::SetTo(const nsRect& aRect)
{
SetTo(aRect, nullptr);
}
void
DisplayItemClip::SetTo(const nsRect& aRect, const nscoord* aRadii)
{
mHaveClipRect = true;
mClipRect = aRect;
if (aRadii) {
mRoundedClipRects.SetLength(1);
mRoundedClipRects[0].mRect = aRect;
memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord)*8);
} else {
mRoundedClipRects.Clear();
}
}
void
DisplayItemClip::SetTo(const nsRect& aRect,
const nsRect& aRoundedRect,
const nscoord* aRadii)
{
mHaveClipRect = true;
mClipRect = aRect;
mRoundedClipRects.SetLength(1);
mRoundedClipRects[0].mRect = aRoundedRect;
memcpy(mRoundedClipRects[0].mRadii, aRadii, sizeof(nscoord)*8);
}
bool
DisplayItemClip::MayIntersect(const nsRect& aRect) const
{
if (!mHaveClipRect) {
return !aRect.IsEmpty();
}
nsRect r = aRect.Intersect(mClipRect);
if (r.IsEmpty()) {
return false;
}
for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
const RoundedRect& rr = mRoundedClipRects[i];
if (!nsLayoutUtils::RoundedRectIntersectsRect(rr.mRect, rr.mRadii, r)) {
return false;
}
}
return true;
}
void
DisplayItemClip::IntersectWith(const DisplayItemClip& aOther)
{
if (!aOther.mHaveClipRect) {
return;
}
if (!mHaveClipRect) {
*this = aOther;
return;
}
if (!mClipRect.IntersectRect(mClipRect, aOther.mClipRect)) {
mRoundedClipRects.Clear();
return;
}
mRoundedClipRects.AppendElements(aOther.mRoundedClipRects);
}
void
DisplayItemClip::ApplyTo(gfxContext* aContext,
nsPresContext* aPresContext,
uint32_t aBegin, uint32_t aEnd)
{
int32_t A2D = aPresContext->AppUnitsPerDevPixel();
ApplyRectTo(aContext, A2D);
ApplyRoundedRectClipsTo(aContext, A2D, aBegin, aEnd);
}
void
DisplayItemClip::ApplyRectTo(gfxContext* aContext, int32_t A2D) const
{
aContext->NewPath();
gfxRect clip = nsLayoutUtils::RectToGfxRect(mClipRect, A2D);
aContext->Rectangle(clip, true);
aContext->Clip();
}
void
DisplayItemClip::ApplyRoundedRectClipsTo(gfxContext* aContext,
int32_t A2D,
uint32_t aBegin, uint32_t aEnd) const
{
DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length());
for (uint32_t i = aBegin; i < aEnd; ++i) {
RefPtr<Path> roundedRect =
MakeRoundedRectPath(aDrawTarget, A2D, mRoundedClipRects[i]);
aContext->Clip(roundedRect);
}
}
void
DisplayItemClip::FillIntersectionOfRoundedRectClips(gfxContext* aContext,
const Color& aColor,
int32_t aAppUnitsPerDevPixel,
uint32_t aBegin,
uint32_t aEnd) const
{
DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
aEnd = std::min<uint32_t>(aEnd, mRoundedClipRects.Length());
if (aBegin >= aEnd) {
return;
}
// Push clips for any rects that come BEFORE the rect at |aEnd - 1|, if any:
ApplyRoundedRectClipsTo(aContext, aAppUnitsPerDevPixel, aBegin, aEnd - 1);
// Now fill the rect at |aEnd - 1|:
RefPtr<Path> roundedRect = MakeRoundedRectPath(aDrawTarget,
aAppUnitsPerDevPixel,
mRoundedClipRects[aEnd - 1]);
ColorPattern color(ToDeviceColor(aColor));
aDrawTarget.Fill(roundedRect, color);
// Finally, pop any clips that we may have pushed:
for (uint32_t i = aBegin; i < aEnd - 1; ++i) {
aContext->PopClip();
}
}
already_AddRefed<Path>
DisplayItemClip::MakeRoundedRectPath(DrawTarget& aDrawTarget,
int32_t A2D,
const RoundedRect &aRoundRect) const
{
RectCornerRadii pixelRadii;
nsCSSRendering::ComputePixelRadii(aRoundRect.mRadii, A2D, &pixelRadii);
Rect rect = NSRectToSnappedRect(aRoundRect.mRect, A2D, aDrawTarget);
return MakePathForRoundedRect(aDrawTarget, rect, pixelRadii);
}
nsRect
DisplayItemClip::ApproximateIntersectInward(const nsRect& aRect) const
{
nsRect r = aRect;
if (mHaveClipRect) {
r.IntersectRect(r, mClipRect);
}
for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
i < iEnd; ++i) {
const RoundedRect &rr = mRoundedClipRects[i];
nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, r);
r = rgn.GetLargestRectangle();
}
return r;
}
// Test if (aXPoint, aYPoint) is in the ellipse with center (aXCenter, aYCenter)
// and radii aXRadius, aYRadius.
bool IsInsideEllipse(nscoord aXRadius, nscoord aXCenter, nscoord aXPoint,
nscoord aYRadius, nscoord aYCenter, nscoord aYPoint)
{
float scaledX = float(aXPoint - aXCenter) / float(aXRadius);
float scaledY = float(aYPoint - aYCenter) / float(aYRadius);
return scaledX * scaledX + scaledY * scaledY < 1.0f;
}
bool
DisplayItemClip::IsRectClippedByRoundedCorner(const nsRect& aRect) const
{
if (mRoundedClipRects.IsEmpty())
return false;
nsRect rect;
rect.IntersectRect(aRect, NonRoundedIntersection());
for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
i < iEnd; ++i) {
const RoundedRect &rr = mRoundedClipRects[i];
// top left
if (rect.x < rr.mRect.x + rr.mRadii[NS_CORNER_TOP_LEFT_X] &&
rect.y < rr.mRect.y + rr.mRadii[NS_CORNER_TOP_LEFT_Y]) {
if (!IsInsideEllipse(rr.mRadii[NS_CORNER_TOP_LEFT_X],
rr.mRect.x + rr.mRadii[NS_CORNER_TOP_LEFT_X],
rect.x,
rr.mRadii[NS_CORNER_TOP_LEFT_Y],
rr.mRect.y + rr.mRadii[NS_CORNER_TOP_LEFT_Y],
rect.y)) {
return true;
}
}
// top right
if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[NS_CORNER_TOP_RIGHT_X] &&
rect.y < rr.mRect.y + rr.mRadii[NS_CORNER_TOP_RIGHT_Y]) {
if (!IsInsideEllipse(rr.mRadii[NS_CORNER_TOP_RIGHT_X],
rr.mRect.XMost() - rr.mRadii[NS_CORNER_TOP_RIGHT_X],
rect.XMost(),
rr.mRadii[NS_CORNER_TOP_RIGHT_Y],
rr.mRect.y + rr.mRadii[NS_CORNER_TOP_RIGHT_Y],
rect.y)) {
return true;
}
}
// bottom left
if (rect.x < rr.mRect.x + rr.mRadii[NS_CORNER_BOTTOM_LEFT_X] &&
rect.YMost() > rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_LEFT_Y]) {
if (!IsInsideEllipse(rr.mRadii[NS_CORNER_BOTTOM_LEFT_X],
rr.mRect.x + rr.mRadii[NS_CORNER_BOTTOM_LEFT_X],
rect.x,
rr.mRadii[NS_CORNER_BOTTOM_LEFT_Y],
rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_LEFT_Y],
rect.YMost())) {
return true;
}
}
// bottom right
if (rect.XMost() > rr.mRect.XMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_X] &&
rect.YMost() > rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_Y]) {
if (!IsInsideEllipse(rr.mRadii[NS_CORNER_BOTTOM_RIGHT_X],
rr.mRect.XMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_X],
rect.XMost(),
rr.mRadii[NS_CORNER_BOTTOM_RIGHT_Y],
rr.mRect.YMost() - rr.mRadii[NS_CORNER_BOTTOM_RIGHT_Y],
rect.YMost())) {
return true;
}
}
}
return false;
}
nsRect
DisplayItemClip::NonRoundedIntersection() const
{
NS_ASSERTION(mHaveClipRect, "Must have a clip rect!");
nsRect result = mClipRect;
for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
i < iEnd; ++i) {
result.IntersectRect(result, mRoundedClipRects[i].mRect);
}
return result;
}
bool
DisplayItemClip::IsRectAffectedByClip(const nsRect& aRect) const
{
if (mHaveClipRect && !mClipRect.Contains(aRect)) {
return true;
}
for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
i < iEnd; ++i) {
const RoundedRect &rr = mRoundedClipRects[i];
nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(rr.mRect, rr.mRadii, aRect);
if (!rgn.Contains(aRect)) {
return true;
}
}
return false;
}
bool
DisplayItemClip::IsRectAffectedByClip(const nsIntRect& aRect,
float aXScale,
float aYScale,
int32_t A2D) const
{
if (mHaveClipRect) {
nsIntRect pixelClipRect = mClipRect.ScaleToNearestPixels(aXScale, aYScale, A2D);
if (!pixelClipRect.Contains(aRect)) {
return true;
}
}
// Rounded rect clipping only snaps to user-space pixels, not device space.
nsIntRect unscaled = aRect;
unscaled.Scale(1/aXScale, 1/aYScale);
for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
i < iEnd; ++i) {
const RoundedRect &rr = mRoundedClipRects[i];
nsIntRect pixelRect = rr.mRect.ToNearestPixels(A2D);
RectCornerRadii pixelRadii;
nsCSSRendering::ComputePixelRadii(rr.mRadii, A2D, &pixelRadii);
nsIntRegion rgn = nsLayoutUtils::RoundedRectIntersectIntRect(pixelRect, pixelRadii, unscaled);
if (!rgn.Contains(unscaled)) {
return true;
}
}
return false;
}
nsRect
DisplayItemClip::ApplyNonRoundedIntersection(const nsRect& aRect) const
{
if (!mHaveClipRect) {
return aRect;
}
nsRect result = aRect.Intersect(mClipRect);
for (uint32_t i = 0, iEnd = mRoundedClipRects.Length();
i < iEnd; ++i) {
result = result.Intersect(mRoundedClipRects[i].mRect);
}
return result;
}
void
DisplayItemClip::RemoveRoundedCorners()
{
if (mRoundedClipRects.IsEmpty())
return;
mClipRect = NonRoundedIntersection();
mRoundedClipRects.Clear();
}
// Computes the difference between aR1 and aR2, limited to aBounds.
static void
AccumulateRectDifference(const nsRect& aR1, const nsRect& aR2, const nsRect& aBounds, nsRegion* aOut)
{
if (aR1.IsEqualInterior(aR2))
return;
nsRegion r;
r.Xor(aR1, aR2);
r.And(r, aBounds);
aOut->Or(*aOut, r);
}
void
DisplayItemClip::AddOffsetAndComputeDifference(uint32_t aStart,
const nsPoint& aOffset,
const nsRect& aBounds,
const DisplayItemClip& aOther,
uint32_t aOtherStart,
const nsRect& aOtherBounds,
nsRegion* aDifference)
{
if (mHaveClipRect != aOther.mHaveClipRect ||
aStart != aOtherStart ||
mRoundedClipRects.Length() != aOther.mRoundedClipRects.Length()) {
aDifference->Or(*aDifference, aBounds);
aDifference->Or(*aDifference, aOtherBounds);
return;
}
if (mHaveClipRect) {
AccumulateRectDifference(mClipRect + aOffset, aOther.mClipRect,
aBounds.Union(aOtherBounds),
aDifference);
}
for (uint32_t i = aStart; i < mRoundedClipRects.Length(); ++i) {
if (mRoundedClipRects[i] + aOffset != aOther.mRoundedClipRects[i]) {
// The corners make it tricky so we'll just add both rects here.
aDifference->Or(*aDifference, mRoundedClipRects[i].mRect.Intersect(aBounds));
aDifference->Or(*aDifference, aOther.mRoundedClipRects[i].mRect.Intersect(aOtherBounds));
}
}
}
uint32_t
DisplayItemClip::GetCommonRoundedRectCount(const DisplayItemClip& aOther,
uint32_t aMax) const
{
uint32_t end = std::min(std::min(mRoundedClipRects.Length(), size_t(aMax)),
aOther.mRoundedClipRects.Length());
uint32_t clipCount = 0;
for (; clipCount < end; ++clipCount) {
if (mRoundedClipRects[clipCount] !=
aOther.mRoundedClipRects[clipCount]) {
return clipCount;
}
}
return clipCount;
}
void
DisplayItemClip::AppendRoundedRects(nsTArray<RoundedRect>* aArray, uint32_t aCount) const
{
size_t count = std::min(mRoundedClipRects.Length(), size_t(aCount));
aArray->AppendElements(mRoundedClipRects.Elements(), count);
}
bool
DisplayItemClip::ComputeRegionInClips(DisplayItemClip* aOldClip,
const nsPoint& aShift,
nsRegion* aCombined) const
{
if (!mHaveClipRect || (aOldClip && !aOldClip->mHaveClipRect)) {
return false;
}
if (aOldClip) {
*aCombined = aOldClip->NonRoundedIntersection();
aCombined->MoveBy(aShift);
aCombined->Or(*aCombined, NonRoundedIntersection());
} else {
*aCombined = NonRoundedIntersection();
}
return true;
}
void
DisplayItemClip::MoveBy(nsPoint aPoint)
{
if (!mHaveClipRect)
return;
mClipRect += aPoint;
for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
mRoundedClipRects[i].mRect += aPoint;
}
}
static DisplayItemClip* gNoClip;
const DisplayItemClip&
DisplayItemClip::NoClip()
{
if (!gNoClip) {
gNoClip = new DisplayItemClip();
}
return *gNoClip;
}
void
DisplayItemClip::Shutdown()
{
delete gNoClip;
gNoClip = nullptr;
}
nsCString
DisplayItemClip::ToString() const
{
nsAutoCString str;
if (mHaveClipRect) {
str.AppendPrintf("%d,%d,%d,%d", mClipRect.x, mClipRect.y,
mClipRect.width, mClipRect.height);
for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
const RoundedRect& r = mRoundedClipRects[i];
str.AppendPrintf(" [%d,%d,%d,%d corners %d,%d,%d,%d,%d,%d,%d,%d]",
r.mRect.x, r.mRect.y, r.mRect.width, r.mRect.height,
r.mRadii[0], r.mRadii[1], r.mRadii[2], r.mRadii[3],
r.mRadii[4], r.mRadii[5], r.mRadii[6], r.mRadii[7]);
}
}
return str;
}
} // namespace mozilla

View file

@ -0,0 +1,191 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#ifndef DISPLAYITEMCLIP_H_
#define DISPLAYITEMCLIP_H_
#include "mozilla/RefPtr.h"
#include "nsRect.h"
#include "nsTArray.h"
#include "nsStyleConsts.h"
class gfxContext;
class nsPresContext;
class nsRegion;
namespace mozilla {
namespace gfx {
class DrawTarget;
class Path;
} // namespace gfx
} // namespace mozilla
namespace mozilla {
/**
* An DisplayItemClip represents the intersection of an optional rectangle
* with a list of rounded rectangles (which is often empty), all in appunits.
* It can represent everything CSS clipping can do to an element (except for
* SVG clip-path), including no clipping at all.
*/
class DisplayItemClip {
typedef mozilla::gfx::Color Color;
typedef mozilla::gfx::DrawTarget DrawTarget;
typedef mozilla::gfx::Path Path;
public:
struct RoundedRect {
nsRect mRect;
// Indices into mRadii are the NS_CORNER_* constants in nsStyleConsts.h
nscoord mRadii[8];
RoundedRect operator+(const nsPoint& aOffset) const {
RoundedRect r = *this;
r.mRect += aOffset;
return r;
}
bool operator==(const RoundedRect& aOther) const {
if (!mRect.IsEqualInterior(aOther.mRect)) {
return false;
}
NS_FOR_CSS_HALF_CORNERS(corner) {
if (mRadii[corner] != aOther.mRadii[corner]) {
return false;
}
}
return true;
}
bool operator!=(const RoundedRect& aOther) const {
return !(*this == aOther);
}
};
// Constructs a DisplayItemClip that does no clipping at all.
DisplayItemClip() : mHaveClipRect(false) {}
void SetTo(const nsRect& aRect);
void SetTo(const nsRect& aRect, const nscoord* aRadii);
void SetTo(const nsRect& aRect, const nsRect& aRoundedRect, const nscoord* aRadii);
void IntersectWith(const DisplayItemClip& aOther);
// Apply this |DisplayItemClip| to the given gfxContext. Any saving of state
// or clearing of other clips must be done by the caller.
// See aBegin/aEnd note on ApplyRoundedRectsTo.
void ApplyTo(gfxContext* aContext, nsPresContext* aPresContext,
uint32_t aBegin = 0, uint32_t aEnd = UINT32_MAX);
void ApplyRectTo(gfxContext* aContext, int32_t A2D) const;
// Applies the rounded rects in this Clip to aContext
// Will only apply rounded rects from aBegin (inclusive) to aEnd
// (exclusive) or the number of rounded rects, whichever is smaller.
void ApplyRoundedRectClipsTo(gfxContext* aContext, int32_t A2DPRInt32,
uint32_t aBegin, uint32_t aEnd) const;
// Draw (fill) the rounded rects in this clip to aContext
void FillIntersectionOfRoundedRectClips(gfxContext* aContext,
const Color& aColor,
int32_t aAppUnitsPerDevPixel,
uint32_t aBegin,
uint32_t aEnd) const;
// 'Draw' (create as a path, does not stroke or fill) aRoundRect to aContext
already_AddRefed<Path> MakeRoundedRectPath(DrawTarget& aDrawTarget,
int32_t A2D,
const RoundedRect &aRoundRect) const;
// Returns true if the intersection of aRect and this clip region is
// non-empty. This is precise for DisplayItemClips with at most one
// rounded rectangle. When multiple rounded rectangles are present, we just
// check that the rectangle intersects all of them (but possibly in different
// places). So it may return true when the correct answer is false.
bool MayIntersect(const nsRect& aRect) const;
// Return a rectangle contained in the intersection of aRect with this
// clip region. Tries to return the largest possible rectangle, but may
// not succeed.
nsRect ApproximateIntersectInward(const nsRect& aRect) const;
/*
* Computes a region which contains the clipped area of this DisplayItemClip,
* or if aOldClip is non-null, the union of the clipped area of this
* DisplayItemClip with the clipped area of aOldClip translated by aShift.
* The result is stored in aCombined. If the result would be infinite
* (because one or both of the clips does no clipping), returns false.
*/
bool ComputeRegionInClips(DisplayItemClip* aOldClip,
const nsPoint& aShift,
nsRegion* aCombined) const;
// Returns false if aRect is definitely not clipped by a rounded corner in
// this clip. Returns true if aRect is clipped by a rounded corner in this
// clip or it can not be quickly determined that it is not clipped by a
// rounded corner in this clip.
bool IsRectClippedByRoundedCorner(const nsRect& aRect) const;
// Returns false if aRect is definitely not clipped by anything in this clip.
// Fast but not necessarily accurate.
bool IsRectAffectedByClip(const nsRect& aRect) const;
bool IsRectAffectedByClip(const nsIntRect& aRect, float aXScale, float aYScale, int32_t A2D) const;
// Intersection of all rects in this clip ignoring any rounded corners.
nsRect NonRoundedIntersection() const;
// Intersect the given rects with all rects in this clip, ignoring any
// rounded corners.
nsRect ApplyNonRoundedIntersection(const nsRect& aRect) const;
// Gets rid of any rounded corners in this clip.
void RemoveRoundedCorners();
// Adds the difference between Intersect(*this + aPoint, aBounds) and
// Intersect(aOther, aOtherBounds) to aDifference (or a bounding-box thereof).
void AddOffsetAndComputeDifference(uint32_t aStart, const nsPoint& aPoint, const nsRect& aBounds,
const DisplayItemClip& aOther, uint32_t aOtherStart, const nsRect& aOtherBounds,
nsRegion* aDifference);
bool operator==(const DisplayItemClip& aOther) const {
return mHaveClipRect == aOther.mHaveClipRect &&
(!mHaveClipRect || mClipRect.IsEqualInterior(aOther.mClipRect)) &&
mRoundedClipRects == aOther.mRoundedClipRects;
}
bool operator!=(const DisplayItemClip& aOther) const {
return !(*this == aOther);
}
bool HasClip() const { return mHaveClipRect; }
const nsRect& GetClipRect() const
{
NS_ASSERTION(HasClip(), "No clip rect!");
return mClipRect;
}
void MoveBy(nsPoint aPoint);
nsCString ToString() const;
/**
* Find the largest N such that the first N rounded rects in 'this' are
* equal to the first N rounded rects in aOther, and N <= aMax.
*/
uint32_t GetCommonRoundedRectCount(const DisplayItemClip& aOther,
uint32_t aMax) const;
uint32_t GetRoundedRectCount() const { return mRoundedClipRects.Length(); }
void AppendRoundedRects(nsTArray<RoundedRect>* aArray, uint32_t aCount) const;
static const DisplayItemClip& NoClip();
static void Shutdown();
private:
nsRect mClipRect;
nsTArray<RoundedRect> mRoundedClipRects;
// If mHaveClipRect is false then this object represents no clipping at all
// and mRoundedClipRects must be empty.
bool mHaveClipRect;
};
} // namespace mozilla
#endif /* DISPLAYITEMCLIP_H_ */

View file

@ -0,0 +1,57 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#include "DisplayItemScrollClip.h"
#include "DisplayItemClip.h"
namespace mozilla {
/* static */ bool
DisplayItemScrollClip::IsAncestor(const DisplayItemScrollClip* aAncestor,
const DisplayItemScrollClip* aDescendant)
{
if (!aAncestor) {
// null means root.
return true;
}
for (const DisplayItemScrollClip* sc = aDescendant; sc; sc = sc->mParent) {
if (sc == aAncestor) {
return true;
}
}
return false;
}
bool
DisplayItemScrollClip::HasRoundedCorners() const
{
for (const DisplayItemScrollClip* scrollClip = this;
scrollClip; scrollClip = scrollClip->mParent) {
if (scrollClip->mClip->GetRoundedRectCount() > 0) {
return true;
}
}
return false;
}
/* static */ nsCString
DisplayItemScrollClip::ToString(const DisplayItemScrollClip* aScrollClip)
{
nsAutoCString str;
for (const DisplayItemScrollClip* scrollClip = aScrollClip;
scrollClip; scrollClip = scrollClip->mParent) {
str.AppendPrintf("<%s>%s", scrollClip->mClip ? scrollClip->mClip->ToString().get() : "null",
scrollClip->mIsAsyncScrollable ? " [async-scrollable]" : "");
if (scrollClip->mParent) {
str.Append(", ");
}
}
return str;
}
} // namespace mozilla

View file

@ -0,0 +1,126 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#ifndef DISPLAYITEMSCROLLCLIP_H_
#define DISPLAYITEMSCROLLCLIP_H_
#include "mozilla/Assertions.h"
#include "nsString.h"
class nsIScrollableFrame;
namespace mozilla {
class DisplayItemClip;
/**
* A DisplayItemScrollClip represents a DisplayItemClip from a scrollable
* frame. This clip is stored on a display item separately from the item's
* regular DisplayItemClip for async scrolling purposes: The item's regular
* clip can be fused to the item's contents when drawing layer contents, but
* scroll clips need to be kept separate so that they can be applied at
* composition time, after any async scroll offsets have been applied.
* Scroll clips are created during display list construction when
* async-scrollable scroll frames are entered. At that point, the current
* regular clip is cleared on the display list clip state. They are also
* created for scroll frames that are inactivate and not async scrollable;
* in that case, mIsAsyncScrollable on the scroll clip will be false.
* When async-scrollable scroll frames are nested, the inner display items
* can walk the whole chain of scroll clips via the DisplayItemScrollClip's
* mParent pointer.
* Storing scroll clips on display items allows easy access of all scroll
* frames that affect a certain display item, and it allows some display list
* operations to compute more accurate clips, for example when computing the
* bounds of a container item for a frame that contains an async-scrollable
* scroll frame.
*/
class DisplayItemScrollClip {
public:
DisplayItemScrollClip(const DisplayItemScrollClip* aParent,
nsIScrollableFrame* aScrollableFrame,
const DisplayItemClip* aClip,
bool aIsAsyncScrollable)
: mParent(aParent)
, mScrollableFrame(aScrollableFrame)
, mClip(aClip)
, mIsAsyncScrollable(aIsAsyncScrollable)
, mDepth(aParent ? aParent->mDepth + 1 : 1)
{
MOZ_ASSERT(mScrollableFrame);
}
/**
* "Pick descendant" is analogous to the intersection operation on regular
* clips: In some cases, there are multiple candidate clips that can apply to
* an item, one of them being the ancestor of the other. This method picks
* the descendant.
* Both aClip1 and aClip2 are allowed to be null.
*/
static const DisplayItemScrollClip*
PickDescendant(const DisplayItemScrollClip* aClip1,
const DisplayItemScrollClip* aClip2)
{
MOZ_ASSERT(IsAncestor(aClip1, aClip2) || IsAncestor(aClip2, aClip1),
"one of the scroll clips must be an ancestor of the other");
return Depth(aClip1) > Depth(aClip2) ? aClip1 : aClip2;
}
static const DisplayItemScrollClip*
PickAncestor(const DisplayItemScrollClip* aClip1,
const DisplayItemScrollClip* aClip2)
{
MOZ_ASSERT(IsAncestor(aClip1, aClip2) || IsAncestor(aClip2, aClip1),
"one of the scroll clips must be an ancestor of the other");
return Depth(aClip1) < Depth(aClip2) ? aClip1 : aClip2;
}
/**
* Returns whether aAncestor is an ancestor scroll clip of aDescendant.
* A scroll clip that's null is considered the root scroll clip.
*/
static bool IsAncestor(const DisplayItemScrollClip* aAncestor,
const DisplayItemScrollClip* aDescendant);
/**
* Return a string which contains the list of stringified clips for this
* scroll clip and its ancestors. aScrollClip can be null.
*/
static nsCString ToString(const DisplayItemScrollClip* aScrollClip);
bool HasRoundedCorners() const;
/**
* The previous (outer) scroll clip, or null.
*/
const DisplayItemScrollClip* mParent;
/**
* The scrollable frame that this scroll clip is for. Always non-null.
*/
nsIScrollableFrame* mScrollableFrame;
/**
* The clip represented by this scroll clip, relative to mScrollableFrame's
* reference frame. Can be null.
*/
const DisplayItemClip* mClip;
/**
* Whether this scroll clip is for an async-scrollable scroll frame.
* Can change during display list construction.
*/
bool mIsAsyncScrollable;
private:
static uint32_t Depth(const DisplayItemScrollClip* aSC)
{ return aSC ? aSC->mDepth : 0; }
const uint32_t mDepth;
};
} // namespace mozilla
#endif /* DISPLAYITEMSCROLLCLIP_H_ */

View file

@ -0,0 +1,227 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#include "DisplayListClipState.h"
#include "DisplayItemScrollClip.h"
#include "nsDisplayList.h"
namespace mozilla {
const DisplayItemClip*
DisplayListClipState::GetCurrentCombinedClip(nsDisplayListBuilder* aBuilder)
{
if (mCurrentCombinedClip) {
return mCurrentCombinedClip;
}
if (!mClipContentDescendants && !mClipContainingBlockDescendants) {
return nullptr;
}
if (mClipContentDescendants) {
if (mClipContainingBlockDescendants) {
DisplayItemClip intersection = *mClipContentDescendants;
intersection.IntersectWith(*mClipContainingBlockDescendants);
mCurrentCombinedClip = aBuilder->AllocateDisplayItemClip(intersection);
} else {
mCurrentCombinedClip =
aBuilder->AllocateDisplayItemClip(*mClipContentDescendants);
}
} else {
mCurrentCombinedClip =
aBuilder->AllocateDisplayItemClip(*mClipContainingBlockDescendants);
}
return mCurrentCombinedClip;
}
void
DisplayListClipState::SetScrollClipForContainingBlockDescendants(
nsDisplayListBuilder* aBuilder,
const DisplayItemScrollClip* aScrollClip)
{
if (aBuilder->IsPaintingToWindow() &&
mClipContentDescendants &&
aScrollClip != mScrollClipContainingBlockDescendants) {
// Disable paint skipping for all scroll frames on the way to aScrollClip.
for (const DisplayItemScrollClip* sc = mClipContentDescendantsScrollClip;
sc && !DisplayItemScrollClip::IsAncestor(sc, aScrollClip);
sc = sc->mParent) {
if (sc->mScrollableFrame) {
sc->mScrollableFrame->SetScrollsClipOnUnscrolledOutOfFlow();
}
}
mClipContentDescendantsScrollClip = nullptr;
}
mScrollClipContainingBlockDescendants = aScrollClip;
mStackingContextAncestorSC = DisplayItemScrollClip::PickAncestor(mStackingContextAncestorSC, aScrollClip);
}
void
DisplayListClipState::ClipContainingBlockDescendants(const nsRect& aRect,
const nscoord* aRadii,
DisplayItemClip& aClipOnStack)
{
if (aRadii) {
aClipOnStack.SetTo(aRect, aRadii);
} else {
aClipOnStack.SetTo(aRect);
}
if (mClipContainingBlockDescendants) {
aClipOnStack.IntersectWith(*mClipContainingBlockDescendants);
}
mClipContainingBlockDescendants = &aClipOnStack;
mClipContentDescendantsScrollClip = GetCurrentInnermostScrollClip();
mCurrentCombinedClip = nullptr;
}
void
DisplayListClipState::ClipContentDescendants(const nsRect& aRect,
const nscoord* aRadii,
DisplayItemClip& aClipOnStack)
{
if (aRadii) {
aClipOnStack.SetTo(aRect, aRadii);
} else {
aClipOnStack.SetTo(aRect);
}
if (mClipContentDescendants) {
aClipOnStack.IntersectWith(*mClipContentDescendants);
}
mClipContentDescendants = &aClipOnStack;
mCurrentCombinedClip = nullptr;
}
void
DisplayListClipState::ClipContentDescendants(const nsRect& aRect,
const nsRect& aRoundedRect,
const nscoord* aRadii,
DisplayItemClip& aClipOnStack)
{
if (aRadii) {
aClipOnStack.SetTo(aRect, aRoundedRect, aRadii);
} else {
nsRect intersect = aRect.Intersect(aRoundedRect);
aClipOnStack.SetTo(intersect);
}
if (mClipContentDescendants) {
aClipOnStack.IntersectWith(*mClipContentDescendants);
}
mClipContentDescendants = &aClipOnStack;
mCurrentCombinedClip = nullptr;
}
void
DisplayListClipState::ClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
DisplayItemClip& aClipOnStack,
uint32_t aFlags)
{
nscoord radii[8];
bool hasBorderRadius = aFrame->GetContentBoxBorderRadii(radii);
if (!hasBorderRadius && (aFlags & ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT)) {
return;
}
nsRect clipRect = aFrame->GetContentRectRelativeToSelf() +
aBuilder->ToReferenceFrame(aFrame);
// If we have a border-radius, we have to clip our content to that
// radius.
ClipContainingBlockDescendants(clipRect, hasBorderRadius ? radii : nullptr,
aClipOnStack);
}
const DisplayItemScrollClip*
DisplayListClipState::GetCurrentInnermostScrollClip()
{
return DisplayItemScrollClip::PickDescendant(
mScrollClipContentDescendants, mScrollClipContainingBlockDescendants);
}
void
DisplayListClipState::TurnClipIntoScrollClipForContentDescendants(
nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip();
mScrollClipContentDescendants =
aBuilder->AllocateDisplayItemScrollClip(parent,
aScrollableFrame,
GetCurrentCombinedClip(aBuilder), true);
Clear();
}
void
DisplayListClipState::TurnClipIntoScrollClipForContainingBlockDescendants(
nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip();
mScrollClipContainingBlockDescendants =
aBuilder->AllocateDisplayItemScrollClip(parent,
aScrollableFrame,
GetCurrentCombinedClip(aBuilder), true);
Clear();
}
const DisplayItemClip*
WithoutRoundedCorners(nsDisplayListBuilder* aBuilder, const DisplayItemClip* aClip)
{
if (!aClip) {
return nullptr;
}
DisplayItemClip rectClip(*aClip);
rectClip.RemoveRoundedCorners();
return aBuilder->AllocateDisplayItemClip(rectClip);
}
DisplayItemScrollClip*
DisplayListClipState::CreateInactiveScrollClip(
nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
// We ignore the rounded corners on the current clip because we don't want
// them to be double-applied (as scroll clip and as regular clip).
// Double-applying rectangle clips doesn't make a visual difference so it's
// fine.
const DisplayItemClip* rectClip =
WithoutRoundedCorners(aBuilder, GetCurrentCombinedClip(aBuilder));
const DisplayItemScrollClip* parent = GetCurrentInnermostScrollClip();
DisplayItemScrollClip* scrollClip =
aBuilder->AllocateDisplayItemScrollClip(parent,
aScrollableFrame,
rectClip, false);
return scrollClip;
}
DisplayItemScrollClip*
DisplayListClipState::InsertInactiveScrollClipForContentDescendants(
nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
DisplayItemScrollClip* scrollClip =
CreateInactiveScrollClip(aBuilder, aScrollableFrame);
mScrollClipContentDescendants = scrollClip;
return scrollClip;
}
DisplayItemScrollClip*
DisplayListClipState::InsertInactiveScrollClipForContainingBlockDescendants(
nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
DisplayItemScrollClip* scrollClip =
CreateInactiveScrollClip(aBuilder, aScrollableFrame);
mScrollClipContainingBlockDescendants = scrollClip;
return scrollClip;
}
DisplayListClipState::AutoSaveRestore::AutoSaveRestore(nsDisplayListBuilder* aBuilder)
: mState(aBuilder->ClipState())
, mSavedState(aBuilder->ClipState())
#ifdef DEBUG
, mClipUsed(false)
, mRestored(false)
#endif
, mClearedForStackingContextContents(false)
{
mState.mStackingContextAncestorSC = mState.GetCurrentInnermostScrollClip();
}
} // namespace mozilla

View file

@ -0,0 +1,468 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#ifndef DISPLAYLISTCLIPSTATE_H_
#define DISPLAYLISTCLIPSTATE_H_
#include "DisplayItemClip.h"
#include "DisplayItemScrollClip.h"
#include "mozilla/DebugOnly.h"
class nsIFrame;
class nsIScrollableFrame;
class nsDisplayListBuilder;
namespace mozilla {
/**
* All clip coordinates are in appunits relative to the reference frame
* for the display item we're building.
*/
class DisplayListClipState {
public:
DisplayListClipState()
: mClipContentDescendants(nullptr)
, mClipContainingBlockDescendants(nullptr)
, mCurrentCombinedClip(nullptr)
, mScrollClipContentDescendants(nullptr)
, mScrollClipContainingBlockDescendants(nullptr)
, mClipContentDescendantsScrollClip(nullptr)
, mStackingContextAncestorSC(nullptr)
{}
/**
* Returns intersection of mClipContainingBlockDescendants and
* mClipContentDescendants, allocated on aBuilder's arena.
*/
const DisplayItemClip* GetCurrentCombinedClip(nsDisplayListBuilder* aBuilder);
const DisplayItemClip* GetClipForContainingBlockDescendants() const
{
return mClipContainingBlockDescendants;
}
const DisplayItemClip* GetClipForContentDescendants() const
{
return mClipContentDescendants;
}
const DisplayItemScrollClip* GetCurrentInnermostScrollClip();
const DisplayItemScrollClip* CurrentAncestorScrollClipForStackingContextContents()
{
return mStackingContextAncestorSC;
}
class AutoSaveRestore;
friend class AutoSaveRestore;
class AutoClipContainingBlockDescendantsToContentBox;
friend class AutoClipContainingBlockDescendantsToContentBox;
class AutoClipMultiple;
friend class AutoClipMultiple;
enum {
ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT = 0x01
};
private:
void SetClipForContainingBlockDescendants(const DisplayItemClip* aClip)
{
mClipContainingBlockDescendants = aClip;
mCurrentCombinedClip = nullptr;
}
void SetScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
const DisplayItemScrollClip* aScrollClip);
void Clear()
{
mClipContentDescendants = nullptr;
mClipContainingBlockDescendants = nullptr;
mCurrentCombinedClip = nullptr;
// We do not clear scroll clips.
}
void EnterStackingContextContents(bool aClear)
{
if (aClear) {
mClipContentDescendants = nullptr;
mClipContainingBlockDescendants = nullptr;
mCurrentCombinedClip = nullptr;
mScrollClipContentDescendants = nullptr;
mScrollClipContainingBlockDescendants = nullptr;
mStackingContextAncestorSC = nullptr;
} else {
mStackingContextAncestorSC = GetCurrentInnermostScrollClip();
}
}
/**
* Clear the current clip, and instead add it as a scroll clip to the current
* scroll clip chain.
*/
void TurnClipIntoScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder,
nsIScrollableFrame* aScrollableFrame);
void TurnClipIntoScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
nsIScrollableFrame* aScrollableFrame);
/**
* Insert a scroll clip without clearing the current clip.
* The returned DisplayItemScrollClip will have mIsAsyncScrollable == false,
* and it can be activated once the scroll frame knows that it needs to be
* async scrollable.
*/
DisplayItemScrollClip* InsertInactiveScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder,
nsIScrollableFrame* aScrollableFrame);
DisplayItemScrollClip* InsertInactiveScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
nsIScrollableFrame* aScrollableFrame);
DisplayItemScrollClip* CreateInactiveScrollClip(nsDisplayListBuilder* aBuilder,
nsIScrollableFrame* aScrollableFrame);
/**
* Intersects the given clip rect (with optional aRadii) with the current
* mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
* the result, stored in aClipOnStack.
*/
void ClipContainingBlockDescendants(const nsRect& aRect,
const nscoord* aRadii,
DisplayItemClip& aClipOnStack);
void ClipContentDescendants(const nsRect& aRect,
const nscoord* aRadii,
DisplayItemClip& aClipOnStack);
void ClipContentDescendants(const nsRect& aRect,
const nsRect& aRoundedRect,
const nscoord* aRadii,
DisplayItemClip& aClipOnStack);
/**
* Clips containing-block descendants to the frame's content-box,
* taking border-radius into account.
* If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then
* we assume display items will not draw outside the content rect, so
* clipping is only required if there is a border-radius. This is an
* optimization to reduce the amount of clipping required.
*/
void ClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
DisplayItemClip& aClipOnStack,
uint32_t aFlags);
/**
* All content descendants (i.e. following placeholder frames to their
* out-of-flows if necessary) should be clipped by mClipContentDescendants.
* Null if no clipping applies.
*/
const DisplayItemClip* mClipContentDescendants;
/**
* All containing-block descendants (i.e. frame descendants), including
* display items for the current frame, should be clipped by
* mClipContainingBlockDescendants.
* Null if no clipping applies.
*/
const DisplayItemClip* mClipContainingBlockDescendants;
/**
* The intersection of mClipContentDescendants and
* mClipContainingBlockDescendants.
* Allocated in the nsDisplayListBuilder arena. Null if none has been
* allocated or both mClipContentDescendants and mClipContainingBlockDescendants
* are null.
*/
const DisplayItemClip* mCurrentCombinedClip;
/**
* The same for scroll clips.
*/
const DisplayItemScrollClip* mScrollClipContentDescendants;
const DisplayItemScrollClip* mScrollClipContainingBlockDescendants;
/**
* The scroll clip that was in effect when mClipContentDescendants was set.
*/
const DisplayItemScrollClip* mClipContentDescendantsScrollClip;
/**
* A scroll clip that is an ancestor of all the scroll clips that were
* "current" on this clip state since EnterStackingContextContents was
* called.
*/
const DisplayItemScrollClip* mStackingContextAncestorSC;
};
/**
* A class to automatically save and restore the current clip state. Also
* offers methods for modifying the clip state. Only one modification is allowed
* to be in scope at a time using one of these objects; multiple modifications
* require nested objects. The interface is written this way to prevent
* dangling pointers to DisplayItemClips.
*/
class DisplayListClipState::AutoSaveRestore {
public:
explicit AutoSaveRestore(nsDisplayListBuilder* aBuilder);
void Restore()
{
if (!mClearedForStackingContextContents) {
// Forward along the ancestor scroll clip to the original clip state.
mSavedState.mStackingContextAncestorSC =
DisplayItemScrollClip::PickAncestor(mSavedState.mStackingContextAncestorSC,
mState.mStackingContextAncestorSC);
}
mState = mSavedState;
#ifdef DEBUG
mRestored = true;
#endif
}
~AutoSaveRestore()
{
Restore();
}
void Clear()
{
NS_ASSERTION(!mRestored, "Already restored!");
mState.Clear();
#ifdef DEBUG
mClipUsed = false;
#endif
}
void EnterStackingContextContents(bool aClear)
{
NS_ASSERTION(!mRestored, "Already restored!");
mState.EnterStackingContextContents(aClear);
mClearedForStackingContextContents = aClear;
}
void ExitStackingContextContents(const DisplayItemScrollClip** aOutContainerSC)
{
if (mClearedForStackingContextContents) {
// If we cleared the scroll clip, then the scroll clip that was current
// just before we cleared it is the one that needs to be set on the
// container item.
*aOutContainerSC = mSavedState.GetCurrentInnermostScrollClip();
} else {
// If we didn't clear the scroll clip, then the container item needs to
// get a scroll clip that's an ancestor of all its direct child items'
// scroll clips.
// The simplest way to satisfy this requirement would be to just take the
// root scroll clip (i.e. nullptr). However, this can cause the bounds of
// the container items to be enlarged unnecessarily, so instead we try to
// take the "deepest" scroll clip that satisfies the requirement.
// Usually this is the scroll clip that was current before we entered
// the stacking context contents (call that the "initial scroll clip").
// There are two cases in which the container scroll clip *won't* be the
// initial scroll clip (instead the container scroll clip will be a
// proper ancestor of the initial scroll clip):
// (1) If SetScrollClipForContainingBlockDescendants was called with an
// ancestor scroll clip of the initial scroll clip while we were
// building our direct child items. This happens if we entered a
// position:absolute or position:fixed element whose containing
// block is an ancestor of the frame that generated the initial
// scroll clip. Then the "ancestor scroll clip for stacking context
// contents" will be set to that scroll clip.
// (2) If one of our direct child items is a container item for which
// (1) or (2) happened.
*aOutContainerSC = mState.CurrentAncestorScrollClipForStackingContextContents();
}
Restore();
}
bool SavedStateHasRoundedCorners()
{
const DisplayItemScrollClip* scrollClip = mSavedState.GetCurrentInnermostScrollClip();
if (scrollClip && scrollClip->HasRoundedCorners()) {
return true;
}
const DisplayItemClip* clip = mSavedState.GetClipForContainingBlockDescendants();
if (clip && clip->GetRoundedRectCount() > 0) {
return true;
}
clip = mSavedState.GetClipForContentDescendants();
if (clip && clip->GetRoundedRectCount() > 0) {
return true;
}
return false;
}
void TurnClipIntoScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
NS_ASSERTION(!mRestored, "Already restored!");
mState.TurnClipIntoScrollClipForContentDescendants(aBuilder, aScrollableFrame);
#ifdef DEBUG
mClipUsed = true;
#endif
}
void TurnClipIntoScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
NS_ASSERTION(!mRestored, "Already restored!");
mState.TurnClipIntoScrollClipForContainingBlockDescendants(aBuilder, aScrollableFrame);
#ifdef DEBUG
mClipUsed = true;
#endif
}
DisplayItemScrollClip* InsertInactiveScrollClipForContentDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
NS_ASSERTION(!mRestored, "Already restored!");
DisplayItemScrollClip* scrollClip = mState.InsertInactiveScrollClipForContentDescendants(aBuilder, aScrollableFrame);
#ifdef DEBUG
mClipUsed = true;
#endif
return scrollClip;
}
DisplayItemScrollClip* InsertInactiveScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder, nsIScrollableFrame* aScrollableFrame)
{
NS_ASSERTION(!mRestored, "Already restored!");
DisplayItemScrollClip* scrollClip = mState.InsertInactiveScrollClipForContainingBlockDescendants(aBuilder, aScrollableFrame);
#ifdef DEBUG
mClipUsed = true;
#endif
return scrollClip;
}
/**
* Intersects the given clip rect (with optional aRadii) with the current
* mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
* the result, stored in aClipOnStack.
*/
void ClipContainingBlockDescendants(const nsRect& aRect,
const nscoord* aRadii = nullptr)
{
NS_ASSERTION(!mRestored, "Already restored!");
NS_ASSERTION(!mClipUsed, "mClip already used");
#ifdef DEBUG
mClipUsed = true;
#endif
mState.ClipContainingBlockDescendants(aRect, aRadii, mClip);
}
void ClipContentDescendants(const nsRect& aRect,
const nscoord* aRadii = nullptr)
{
NS_ASSERTION(!mRestored, "Already restored!");
NS_ASSERTION(!mClipUsed, "mClip already used");
#ifdef DEBUG
mClipUsed = true;
#endif
mState.ClipContentDescendants(aRect, aRadii, mClip);
}
void ClipContentDescendants(const nsRect& aRect,
const nsRect& aRoundedRect,
const nscoord* aRadii = nullptr)
{
NS_ASSERTION(!mRestored, "Already restored!");
NS_ASSERTION(!mClipUsed, "mClip already used");
#ifdef DEBUG
mClipUsed = true;
#endif
mState.ClipContentDescendants(aRect, aRoundedRect, aRadii, mClip);
}
/**
* Clips containing-block descendants to the frame's content-box,
* taking border-radius into account.
* If aFlags contains ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT then
* we assume display items will not draw outside the content rect, so
* clipping is only required if there is a border-radius. This is an
* optimization to reduce the amount of clipping required.
*/
void ClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
uint32_t aFlags = 0)
{
NS_ASSERTION(!mRestored, "Already restored!");
NS_ASSERTION(!mClipUsed, "mClip already used");
#ifdef DEBUG
mClipUsed = true;
#endif
mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, mClip, aFlags);
}
protected:
DisplayListClipState& mState;
DisplayListClipState mSavedState;
DisplayItemClip mClip;
#ifdef DEBUG
bool mClipUsed;
bool mRestored;
#endif
bool mClearedForStackingContextContents;
};
class DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox : public AutoSaveRestore {
public:
AutoClipContainingBlockDescendantsToContentBox(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
uint32_t aFlags = 0)
: AutoSaveRestore(aBuilder)
{
#ifdef DEBUG
mClipUsed = true;
#endif
mState.ClipContainingBlockDescendantsToContentBox(aBuilder, aFrame, mClip, aFlags);
}
};
/**
* Do not use this outside of nsFrame::BuildDisplayListForChild, use
* multiple AutoSaveRestores instead. We provide this class just to ensure
* BuildDisplayListForChild is as efficient as possible.
*/
class DisplayListClipState::AutoClipMultiple : public AutoSaveRestore {
public:
explicit AutoClipMultiple(nsDisplayListBuilder* aBuilder)
: AutoSaveRestore(aBuilder)
#ifdef DEBUG
, mExtraClipUsed(false)
#endif
{}
/**
* *aClip must survive longer than this object. Be careful!!!
*/
void SetClipForContainingBlockDescendants(const DisplayItemClip* aClip)
{
mState.SetClipForContainingBlockDescendants(aClip);
}
void SetScrollClipForContainingBlockDescendants(nsDisplayListBuilder* aBuilder,
const DisplayItemScrollClip* aScrollClip)
{
mState.SetScrollClipForContainingBlockDescendants(aBuilder, aScrollClip);
}
/**
* Intersects the given clip rect (with optional aRadii) with the current
* mClipContainingBlockDescendants and sets mClipContainingBlockDescendants to
* the result, stored in aClipOnStack.
*/
void ClipContainingBlockDescendantsExtra(const nsRect& aRect,
const nscoord* aRadii)
{
NS_ASSERTION(!mRestored, "Already restored!");
NS_ASSERTION(!mExtraClipUsed, "mExtraClip already used");
#ifdef DEBUG
mExtraClipUsed = true;
#endif
mState.ClipContainingBlockDescendants(aRect, aRadii, mExtraClip);
}
protected:
DisplayItemClip mExtraClip;
#ifdef DEBUG
bool mExtraClipUsed;
#endif
};
} // namespace mozilla
#endif /* DISPLAYLISTCLIPSTATE_H_ */

View file

@ -0,0 +1,568 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
// vim:cindent:ts=2:et:sw=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/. */
#include "DottedCornerFinder.h"
#include "mozilla/Move.h"
#include "BorderCache.h"
#include "BorderConsts.h"
namespace mozilla {
using namespace gfx;
static inline Float
Square(Float x)
{
return x * x;
}
static Point
PointRotateCCW90(const Point& aP)
{
return Point(aP.y, -aP.x);
}
struct BestOverlap
{
Float overlap;
size_t count;
BestOverlap()
: overlap(0.0f), count(0)
{}
BestOverlap(Float aOverlap, size_t aCount)
: overlap(aOverlap), count(aCount)
{}
};
static const size_t DottedCornerCacheSize = 256;
nsDataHashtable<FourFloatsHashKey, BestOverlap> DottedCornerCache;
DottedCornerFinder::DottedCornerFinder(const Bezier& aOuterBezier,
const Bezier& aInnerBezier,
mozilla::css::Corner aCorner,
Float aBorderRadiusX,
Float aBorderRadiusY,
const Point& aC0, Float aR0,
const Point& aCn, Float aRn,
const Size& aCornerDim)
: mOuterBezier(aOuterBezier),
mInnerBezier(aInnerBezier),
mCorner(aCorner),
mNormalSign((aCorner == C_TL || aCorner == C_BR) ? -1.0f : 1.0f),
mC0(aC0), mCn(aCn),
mR0(aR0), mRn(aRn), mMaxR(std::max(aR0, aRn)),
mCenterCurveOrigin(mC0.x, mCn.y),
mInnerCurveOrigin(mInnerBezier.mPoints[0].x, mInnerBezier.mPoints[3].y),
mBestOverlap(0.0f),
mHasZeroBorderWidth(false), mHasMore(true),
mMaxCount(aCornerDim.width + aCornerDim.height),
mType(OTHER),
mI(0), mCount(0)
{
NS_ASSERTION(mR0 > 0.0f || mRn > 0.0f,
"At least one side should have non-zero radius.");
mInnerWidth = fabs(mInnerBezier.mPoints[0].x - mInnerBezier.mPoints[3].x);
mInnerHeight = fabs(mInnerBezier.mPoints[0].y - mInnerBezier.mPoints[3].y);
DetermineType(aBorderRadiusX, aBorderRadiusY);
Reset();
}
static bool
IsSingleCurve(Float aMinR, Float aMaxR,
Float aMinBorderRadius, Float aMaxBorderRadius)
{
return aMinR > 0.0f &&
aMinBorderRadius > aMaxR * 4.0f &&
aMinBorderRadius / aMaxBorderRadius > 0.5f;
}
void
DottedCornerFinder::DetermineType(Float aBorderRadiusX, Float aBorderRadiusY)
{
// Calculate parameters for the center curve before swap.
Float centerCurveWidth = fabs(mC0.x - mCn.x);
Float centerCurveHeight = fabs(mC0.y - mCn.y);
Point cornerPoint(mCn.x, mC0.y);
bool swapped = false;
if (mR0 < mRn) {
// Always draw from wider side to thinner side.
Swap(mC0, mCn);
Swap(mR0, mRn);
Swap(mInnerBezier.mPoints[0], mInnerBezier.mPoints[3]);
Swap(mInnerBezier.mPoints[1], mInnerBezier.mPoints[2]);
Swap(mOuterBezier.mPoints[0], mOuterBezier.mPoints[3]);
Swap(mOuterBezier.mPoints[1], mOuterBezier.mPoints[2]);
mNormalSign = -mNormalSign;
swapped = true;
}
// See the comment at mType declaration for each condition.
Float minR = std::min(mR0, mRn);
Float minBorderRadius = std::min(aBorderRadiusX, aBorderRadiusY);
Float maxBorderRadius = std::max(aBorderRadiusX, aBorderRadiusY);
if (IsSingleCurve(minR, mMaxR, minBorderRadius, maxBorderRadius)) {
if (mR0 == mRn) {
Float borderLength;
if (minBorderRadius == maxBorderRadius) {
mType = PERFECT;
borderLength = M_PI * centerCurveHeight / 2.0f;
mCenterCurveR = centerCurveWidth;
} else {
mType = SINGLE_CURVE_AND_RADIUS;
borderLength = GetQuarterEllipticArcLength(centerCurveWidth,
centerCurveHeight);
}
Float diameter = mR0 * 2.0f;
size_t count = round(borderLength / diameter);
if (count % 2) {
count++;
}
mCount = count / 2 - 1;
if (mCount > 0) {
mBestOverlap = 1.0f - borderLength / (diameter * count);
}
} else {
mType = SINGLE_CURVE;
}
}
if (mType == SINGLE_CURVE_AND_RADIUS || mType == SINGLE_CURVE) {
Size cornerSize(centerCurveWidth, centerCurveHeight);
GetBezierPointsForCorner(&mCenterBezier, mCorner,
cornerPoint, cornerSize);
if (swapped) {
Swap(mCenterBezier.mPoints[0], mCenterBezier.mPoints[3]);
Swap(mCenterBezier.mPoints[1], mCenterBezier.mPoints[2]);
}
}
if (minR == 0.0f) {
mHasZeroBorderWidth = true;
}
if ((mType == SINGLE_CURVE || mType == OTHER) && !mHasZeroBorderWidth) {
FindBestOverlap(minR, minBorderRadius, maxBorderRadius);
}
}
bool
DottedCornerFinder::HasMore(void) const
{
if (mHasZeroBorderWidth) {
return mI < mMaxCount && mHasMore;
}
return mI < mCount;
}
DottedCornerFinder::Result
DottedCornerFinder::Next(void)
{
mI++;
if (mType == PERFECT) {
Float phi = mI * 4.0f * mR0 * (1 - mBestOverlap) / mCenterCurveR;
if (mCorner == C_TL) {
phi = -M_PI / 2.0f - phi;
} else if (mCorner == C_TR) {
phi = -M_PI / 2.0f + phi;
} else if (mCorner == C_BR) {
phi = M_PI / 2.0f - phi;
} else {
phi = M_PI / 2.0f + phi;
}
Point C(mCenterCurveOrigin.x + mCenterCurveR * cos(phi),
mCenterCurveOrigin.y + mCenterCurveR * sin(phi));
return DottedCornerFinder::Result(C, mR0);
}
// Find unfilled and filled circles.
(void)FindNext(mBestOverlap);
if (mHasMore) {
(void)FindNext(mBestOverlap);
}
return Result(mLastC, mLastR);
}
void
DottedCornerFinder::Reset(void)
{
mLastC = mC0;
mLastR = mR0;
mLastT = 0.0f;
mHasMore = true;
}
void
DottedCornerFinder::FindPointAndRadius(Point& C, Float& r,
const Point& innerTangent,
const Point& normal, Float t)
{
// Find radius for the given tangent point on the inner curve such that the
// circle is also tangent to the outer curve.
NS_ASSERTION(mType == OTHER, "Wrong mType");
Float lower = 0.0f;
Float upper = mMaxR;
const Float DIST_MARGIN = 0.1f;
for (size_t i = 0; i < MAX_LOOP; i++) {
r = (upper + lower) / 2.0f;
C = innerTangent + normal * r;
Point Near = FindBezierNearestPoint(mOuterBezier, C, t);
Float distSquare = (C - Near).LengthSquare();
if (distSquare > Square(r + DIST_MARGIN)) {
lower = r;
} else if (distSquare < Square(r - DIST_MARGIN)) {
upper = r;
} else {
break;
}
}
}
Float
DottedCornerFinder::FindNext(Float overlap)
{
Float lower = mLastT;
Float upper = 1.0f;
Float t;
Point C = mLastC;
Float r = 0.0f;
Float factor = (1.0f - overlap);
Float circlesDist = 0.0f;
Float expectedDist = 0.0f;
const Float DIST_MARGIN = 0.1f;
if (mType == SINGLE_CURVE_AND_RADIUS) {
r = mR0;
expectedDist = (r + mLastR) * factor;
// Find C_i on the center curve.
for (size_t i = 0; i < MAX_LOOP; i++) {
t = (upper + lower) / 2.0f;
C = GetBezierPoint(mCenterBezier, t);
// Check overlap along arc.
circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
if (circlesDist < expectedDist - DIST_MARGIN) {
lower = t;
} else if (circlesDist > expectedDist + DIST_MARGIN) {
upper = t;
} else {
break;
}
}
} else if (mType == SINGLE_CURVE) {
// Find C_i on the center curve, and calculate r_i.
for (size_t i = 0; i < MAX_LOOP; i++) {
t = (upper + lower) / 2.0f;
C = GetBezierPoint(mCenterBezier, t);
Point Diff = GetBezierDifferential(mCenterBezier, t);
Float DiffLength = Diff.Length();
if (DiffLength == 0.0f) {
// Basically this shouldn't happen.
// If differential is 0, we cannot calculate tangent circle,
// skip this point.
t = (t + upper) / 2.0f;
continue;
}
Point normal = PointRotateCCW90(Diff / DiffLength) * (-mNormalSign);
r = CalculateDistanceToEllipticArc(C, normal, mInnerCurveOrigin,
mInnerWidth, mInnerHeight);
// Check overlap along arc.
circlesDist = GetBezierLength(mCenterBezier, mLastT, t);
expectedDist = (r + mLastR) * factor;
if (circlesDist < expectedDist - DIST_MARGIN) {
lower = t;
} else if (circlesDist > expectedDist + DIST_MARGIN) {
upper = t;
} else {
break;
}
}
} else {
Float distSquareMax = Square(mMaxR * 3.0f);
Float circlesDistSquare = 0.0f;
// Find C_i and r_i.
for (size_t i = 0; i < MAX_LOOP; i++) {
t = (upper + lower) / 2.0f;
Point innerTangent = GetBezierPoint(mInnerBezier, t);
if ((innerTangent - mLastC).LengthSquare() > distSquareMax) {
// It's clear that this tangent point is too far, skip it.
upper = t;
continue;
}
Point Diff = GetBezierDifferential(mInnerBezier, t);
Float DiffLength = Diff.Length();
if (DiffLength == 0.0f) {
// Basically this shouldn't happen.
// If differential is 0, we cannot calculate tangent circle,
// skip this point.
t = (t + upper) / 2.0f;
continue;
}
Point normal = PointRotateCCW90(Diff / DiffLength) * mNormalSign;
FindPointAndRadius(C, r, innerTangent, normal, t);
// Check overlap with direct distance.
circlesDistSquare = (C - mLastC).LengthSquare();
expectedDist = (r + mLastR) * factor;
if (circlesDistSquare < Square(expectedDist - DIST_MARGIN)) {
lower = t;
} else if (circlesDistSquare > Square(expectedDist + DIST_MARGIN)) {
upper = t;
} else {
break;
}
}
circlesDist = sqrt(circlesDistSquare);
}
if (mHasZeroBorderWidth) {
// When calculating circle around r=0, it may result in wrong radius that
// is bigger than previous circle. Detect it and stop calculating.
const Float R_MARGIN = 0.1f;
if (mLastR < R_MARGIN && r > mLastR) {
mHasMore = false;
mLastR = 0.0f;
return 0.0f;
}
}
mLastT = t;
mLastC = C;
mLastR = r;
if (mHasZeroBorderWidth) {
const Float T_MARGIN = 0.001f;
if (mLastT >= 1.0f - T_MARGIN ||
(mLastC - mCn).LengthSquare() < Square(mLastR)) {
mHasMore = false;
}
}
if (expectedDist == 0.0f) {
return 0.0f;
}
return 1.0f - circlesDist * factor / expectedDist;
}
void
DottedCornerFinder::FindBestOverlap(Float aMinR, Float aMinBorderRadius,
Float aMaxBorderRadius)
{
// If overlap is not calculateable, find it with binary search,
// such that there exists i that C_i == C_n with the given overlap.
FourFloats key(aMinR, mMaxR,
aMinBorderRadius, aMaxBorderRadius);
BestOverlap best;
if (DottedCornerCache.Get(key, &best)) {
mCount = best.count;
mBestOverlap = best.overlap;
return;
}
Float lower = 0.0f;
Float upper = 0.5f;
// Start from lower bound to find the minimum number of circles.
Float overlap = 0.0f;
mBestOverlap = overlap;
size_t targetCount = 0;
const Float OVERLAP_MARGIN = 0.1f;
for (size_t j = 0; j < MAX_LOOP; j++) {
Reset();
size_t count;
Float actualOverlap;
if (!GetCountAndLastOverlap(overlap, &count, &actualOverlap)) {
if (j == 0) {
mCount = mMaxCount;
break;
}
}
if (j == 0) {
if (count < 3 || (count == 3 && actualOverlap > 0.5f)) {
// |count == 3 && actualOverlap > 0.5f| means there could be
// a circle but it is too near from both ends.
//
// if actualOverlap == 0.0
// 1 2 3
// +-------+-------+-------+-------+
// | ##### | ***** | ##### | ##### |
// |#######|*******|#######|#######|
// |###+###|***+***|###+###|###+###|
// |# C_0 #|* C_1 *|# C_2 #|# C_n #|
// | ##### | ***** | ##### | ##### |
// +-------+-------+-------+-------+
// |
// V
// +-------+---+-------+---+-------+
// | ##### | | ##### | | ##### |
// |#######| |#######| |#######|
// |###+###| |###+###| |###+###| Find the best overlap to place
// |# C_0 #| |# C_1 #| |# C_n #| C_1 at the middle of them
// | ##### | | ##### | | ##### |
// +-------+---+-------+---|-------+
//
// if actualOverlap == 0.5
// 1 2 3
// +-------+-------+-------+---+
// | ##### | ***** | ##### |## |
// |#######|*******|##### C_n #|
// |###+###|***+***|###+###+###|
// |# C_0 #|* C_1 *|# C_2 #|###|
// | ##### | ***** | ##### |## |
// +-------+-------+-------+---+
// |
// V
// +-------+-+-------+-+-------+
// | ##### | | ##### | | ##### |
// |#######| |#######| |#######|
// |###+###| |###+###| |###+###| Even if we place C_1 at the middle
// |# C_0 #| |# C_1 #| |# C_n #| of them, it's too near from them
// | ##### | | ##### | | ##### |
// +-------+-+-------+-|-------+
// |
// V
// +-------+-----------+-------+
// | ##### | | ##### |
// |#######| |#######|
// |###+###| |###+###| Do not draw any circle
// |# C_0 #| |# C_n #|
// | ##### | | ##### |
// +-------+-----------+-------+
mCount = 0;
break;
}
// targetCount should be 2n, as we're searching C_1 to C_n.
//
// targetCount = 4
// mCount = 1
// 1 2 3 4
// +-------+-------+-------+-------+-------+
// | ##### | ***** | ##### | ***** | ##### |
// |#######|*******|#######|*******|#######|
// |###+###|***+***|###+###|***+***|###+###|
// |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_n #|
// | ##### | ***** | ##### | ***** | ##### |
// +-------+-------+-------+-------+-------+
// 1
//
// targetCount = 6
// mCount = 2
// 1 2 3 4 5 6
// +-------+-------+-------+-------+-------+-------+-------+
// | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
// |#######|*******|#######|*******|#######|*******|#######|
// |###+###|***+***|###+###|***+***|###+###|***+***|###+###|
// |# C_0 #|* C_1 *|# C_2 #|* C_3 *|# C_4 #|* C_5 *|# C_n #|
// | ##### | ***** | ##### | ***** | ##### | ***** | ##### |
// +-------+-------+-------+-------+-------+-------+-------+
// 1 2
if (count % 2) {
targetCount = count + 1;
} else {
targetCount = count;
}
mCount = targetCount / 2 - 1;
}
if (count == targetCount) {
mBestOverlap = overlap;
if (fabs(actualOverlap - overlap) < OVERLAP_MARGIN) {
break;
}
// We started from upper bound, no need to update range when j == 0.
if (j > 0) {
if (actualOverlap > overlap) {
lower = overlap;
} else {
upper = overlap;
}
}
} else {
// |j == 0 && count != targetCount| means that |targetCount = count + 1|,
// and we started from upper bound, no need to update range when j == 0.
if (j > 0) {
if (count > targetCount) {
upper = overlap;
} else {
lower = overlap;
}
}
}
overlap = (upper + lower) / 2.0f;
}
if (DottedCornerCache.Count() > DottedCornerCacheSize) {
DottedCornerCache.Clear();
}
DottedCornerCache.Put(key, BestOverlap(mBestOverlap, mCount));
}
bool
DottedCornerFinder::GetCountAndLastOverlap(Float aOverlap,
size_t* aCount,
Float* aActualOverlap)
{
// Return the number of circles and the last circles' overlap for the
// given overlap.
Reset();
const Float T_MARGIN = 0.001f;
const Float DIST_MARGIN = 0.1f;
const Float DIST_MARGIN_SQUARE = Square(DIST_MARGIN);
for (size_t i = 0; i < mMaxCount; i++) {
Float actualOverlap = FindNext(aOverlap);
if (mLastT >= 1.0f - T_MARGIN ||
(mLastC - mCn).LengthSquare() < DIST_MARGIN_SQUARE) {
*aCount = i + 1;
*aActualOverlap = actualOverlap;
return true;
}
}
return false;
}
} // namespace mozilla

View file

@ -0,0 +1,438 @@
/* -*- Mode: C++; 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/. */
#ifndef mozilla_DottedCornerFinder_h_
#define mozilla_DottedCornerFinder_h_
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/BezierUtils.h"
#include "gfxRect.h"
namespace mozilla {
// Calculate C_i and r_i for each filled/unfilled circles in dotted corner.
// Returns circle with C_{2j} and r_{2j} where 0 < 2j < n.
//
// ____-----------+
// __---- ***** ###|
// __---- ********* ####|
// __--- ##### ***********#####|
// _-- ######### *****+*****#####+ C_0
// _- ########### *** C_1****#####|
// / #####+##### ********* ####|
// / . ### C_2 ### ***** ###|
// | ######### ____-------+
// | . #####____-----
// | __----
// | . /
// | /
// | ***** |
// | ******* |
// |*********|
// |****+****|
// | C_{n-1} |
// | ******* |
// | ***** |
// | ##### |
// | ####### |
// |#########|
// +----+----+
// C_n
class DottedCornerFinder
{
typedef mozilla::gfx::Bezier Bezier;
typedef mozilla::gfx::Float Float;
typedef mozilla::gfx::Point Point;
typedef mozilla::gfx::Size Size;
public:
struct Result
{
// Center point of dot and its radius.
Point C;
Float r;
Result(const Point& aC, Float aR)
: C(aC), r(aR)
{
MOZ_ASSERT(aR >= 0);
}
};
// aBorderRadiusX
// aCornerDim.width
// |<----------------->|
// | | v
// --+-------------___---+--
// ^ | __-- | |
// | | _- | | aR0
// | | / aC0 +--
// | | / | ^
// | | | |
// aBorderRadiusY | | | __--+
// aCornerDim.height | || _-
// | || /
// | | /
// | | |
// | | |
// | | |
// | | |
// v | aCn |
// --+----+----+
// | |
// |<-->|
// aRn
//
// aCornerDim and (aBorderRadiusX, aBorderRadiusY) can be different when
// aBorderRadiusX is smaller than aRn*2 or
// aBorderRadiusY is smaller than aR0*2.
//
// aCornerDim.width
// |<----------------->|
// | |
// | aBorderRadiusX |
// |<--------->| |
// | | |
// -------------------+-------__--+-------+--
// ^ ^ | _- | ^
// | | | / | |
// | | | / | |
// | aBorderRadiusY | | | | | aR0
// | | || | |
// | | | | |
// aCornerDim.height | v | | v
// | --+ aC0 +--
// | | |
// | | |
// | | |
// | | |
// | | |
// v | aCn |
// -------------------+---------+---------+
// | |
// |<------->|
// aRn
DottedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier,
mozilla::css::Corner aCorner,
Float aBorderRadiusX, Float aBorderRadiusY,
const Point& aC0, Float aR0, const Point& aCn, Float aRn,
const Size& aCornerDim);
bool HasMore(void) const;
Result Next(void);
private:
static const size_t MAX_LOOP = 32;
// Bezier control points for the outer curve, the inner curve, and a curve
// that center points of circles are on (center curve).
//
// ___---+ outer curve
// __-- |
// _- |
// / __---+ center curve
// / __-- |
// | / |
// | / __--+ inner curve
// | | _-
// | | /
// | | /
// | | |
// | | |
// | | |
// | | |
// | | |
// +----+----+
Bezier mOuterBezier;
Bezier mInnerBezier;
Bezier mCenterBezier;
mozilla::css::Corner mCorner;
// Sign of the normal vector used in radius calculation, flipped depends on
// corner and start and end radii.
Float mNormalSign;
// Center points and raii for start and end circles, mR0 >= mRn.
// mMaxR = max(mR0, mRn)
//
// v
// ___---+------
// __-- #|# | mRn
// _- ##|## |
// / ##+## ---
// / mCn ^
// | #|#
// | __--+
// | _-
// | /
// | /
// | |
// | |
// | ##### |
// | ####### |
// |#########|
// +----+----+
// |## mC0 ##|
// | ####### |
// | ##### |
// | |
// |<-->|
//
// mR0
//
Point mC0;
Point mCn;
Float mR0;
Float mRn;
Float mMaxR;
// Parameters for the center curve with perfect circle and the inner curve.
// The center curve doesn't necessarily share the origin with others.
//
// ___---+
// __-- |
// _- |
// / __-+ |
// / __-- |
// | / |
// | / __--+--
// | | _- | ^
// | | / | |
// | | / | |
// | | | | |
// | | | | | mInnerHeight
// | | | | |
// | + | | |
// | | | v
// +---------+---------+
// | | mInnerCurveOrigin
// |<------->|
// mInnerWidth
//
// ___---+
// __--
// _-
// / __-+
// / __-- |
// | / |
// | / __--+
// | | _- |
// | | / |
// | | / |
// | | | |
// | | | |
// | | | |
// | +--- | ------+
// | | | | mCenterCurveOrigin
// + | + |
// | |
// | |
// | |
// | |
// |<---------->|
// mCenterCurveR
//
Point mCenterCurveOrigin;
Float mCenterCurveR;
Point mInnerCurveOrigin;
Float mInnerWidth;
Float mInnerHeight;
Point mLastC;
Float mLastR;
Float mLastT;
// Overlap between two circles.
// It uses arc length on PERFECT, SINGLE_CURVE_AND_RADIUS, and SINGLE_CURVE,
// and direct distance on OTHER.
Float mBestOverlap;
// If one of border-widths is 0, do not calculate overlap, and draw circles
// until it reaches the other side or exceeds mMaxCount.
bool mHasZeroBorderWidth;
bool mHasMore;
// The maximum number of filled/unfilled circles.
size_t mMaxCount;
enum {
// radius.width
// |<----------------->|
// | |
// --+-------------___---+----
// ^ | __-- #|# ^
// | | _- ##|## |
// | | / ##+## | top-width
// | | / ##|## |
// | | | #|# v
// | | | __--+----
// radius.height | || _-
// | || /
// | | /
// | | |
// | | |
// | | ##### |
// | | ####### |
// v |#########|
// --+----+----+
// |#########|
// | ####### |
// | ##### |
// | |
// |<------->|
// left-width
// * top-width == left-width
// * radius.width == radius.height
// * top-width < radius.width * 2
//
// All circles has same radii and are on single perfect circle's arc.
// Overlap is known.
//
// Split the perfect circle's arc into 2n segments, each segment's length is
// top-width * (1 - overlap). Place each circle's center point C_i on each
// end of the segment, each circle's radius r_i is top-width / 2
//
// #####
// #######
// perfect #########
// circle's ___---+####
// arc ##### __-- ## C_0 ##
// | #####_- ###|###
// | ####+#### ##|##
// | ##/C_i ## |
// | |###### |
// | | ##### |
// +->| |
// | |
// ##|## |
// ###|### |
// ####|#### |
// ####+-------------------+
// ## C_n ##
// #######
// #####
PERFECT,
// * top-width == left-width
// * 0.5 < radius.width / radius.height < 2.0
// * top-width < min(radius.width, radius.height) * 2
//
// All circles has same radii and are on single elliptic arc.
// Overlap is known.
//
// Split the elliptic arc into 2n segments, each segment's length is
// top-width * (1 - overlap). Place each circle's center point C_i on each
// end of the segment, each circle's radius r_i is top-width / 2
//
// #####
// #######
// ##### #########
// ####### ____----+####
// elliptic ######__--- ## C_0 ##
// arc ##__+-### ###|###
// | / # C_i # ##|##
// +--> / ##### |
// | |
// ###|# |
// ###|### |
// ####|#### |
// ####+------------------------+
// ## C_n ##
// #######
// #####
SINGLE_CURVE_AND_RADIUS,
// * top-width != left-width
// * 0 < min(top-width, left-width)
// * 0.5 < radius.width / radius.height < 2.0
// * max(top-width, left-width) < min(radius.width, radius.height) * 2
//
// All circles are on single elliptic arc.
// Overlap is unknown.
//
// Place each circle's center point C_i on elliptic arc, each circle's
// radius r_i is the distance between the center point and the inner curve.
// The arc segment's length between C_i and C_{i-1} is
// (r_i + r_{i-1}) * (1 - overlap).
//
// outer curve
// /
// /
// / / center curve
// / ####### /
// /## /#
// +# / #
// /# / #
// / # C_i / #
// / # + # /
// / # / \ # / inner curve
// # / \ #/
// # / r_i \+
// #/ ##/
// / ####### /
// /
SINGLE_CURVE,
// Other cases.
// Circles are not on single elliptic arc.
// Overlap are unknown.
//
// Place tangent point innerTangent on the inner curve and find circle's
// center point C_i and radius r_i where the circle is also tangent to the
// outer curve.
// Distance between C_i and C_{i-1} is (r_i + r_{i-1}) * (1 - overlap).
//
// outer curve
// /
// /
// /
// / #######
// /## ##
// +# #
// /# \ #
// / # \ #
// / # + # /
// / # C_i \ # / inner curve
// # \ #/
// # r_i \+
// ## ##/ innerTangent
// ####### /
// /
OTHER
} mType;
size_t mI;
size_t mCount;
// Determine mType from parameters.
void DetermineType(Float aBorderRadiusX, Float aBorderRadiusY);
// Reset calculation.
void Reset(void);
// Find radius for the given tangent point on the inner curve such that the
// circle is also tangent to the outer curve.
void FindPointAndRadius(Point& C, Float& r, const Point& innerTangent,
const Point& normal, Float t);
// Find next dot.
Float FindNext(Float overlap);
// Find mBestOverlap for parameters.
void FindBestOverlap(Float aMinR,
Float aMinBorderRadius, Float aMaxBorderRadius);
// Fill corner with dots with given overlap, and return the number of dots
// and last two dots's overlap.
bool GetCountAndLastOverlap(Float aOverlap,
size_t* aCount, Float* aActualOverlap);
};
} // namespace mozilla
#endif /* mozilla_DottedCornerFinder_h_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,737 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#ifndef FRAMELAYERBUILDER_H_
#define FRAMELAYERBUILDER_H_
#include "nsAutoPtr.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "nsTArray.h"
#include "nsRegion.h"
#include "nsIFrame.h"
#include "DisplayItemClip.h"
#include "mozilla/gfx/MatrixFwd.h"
#include "mozilla/layers/LayersTypes.h"
#include "LayerState.h"
#include "Layers.h"
#include "LayerUserData.h"
class nsDisplayListBuilder;
class nsDisplayList;
class nsDisplayItem;
class gfxContext;
class nsDisplayItemGeometry;
class nsDisplayMask;
namespace mozilla {
class DisplayItemScrollClip;
namespace layers {
class ContainerLayer;
class LayerManager;
class BasicLayerManager;
class PaintedLayer;
class ImageLayer;
} // namespace layers
class FrameLayerBuilder;
class LayerManagerData;
class PaintedLayerData;
class ContainerState;
class RefCountedRegion {
private:
~RefCountedRegion() {}
public:
NS_INLINE_DECL_REFCOUNTING(RefCountedRegion)
RefCountedRegion() : mIsInfinite(false) {}
nsRegion mRegion;
bool mIsInfinite;
};
struct ContainerLayerParameters {
ContainerLayerParameters()
: mXScale(1)
, mYScale(1)
, mLayerContentsVisibleRect(nullptr)
, mBackgroundColor(NS_RGBA(0,0,0,0))
, mScrollClip(nullptr)
, mScrollClipForPerspectiveChild(nullptr)
, mInTransformedSubtree(false)
, mInActiveTransformedSubtree(false)
, mDisableSubpixelAntialiasingInDescendants(false)
, mInLowPrecisionDisplayPort(false)
, mForEventsAndPluginsOnly(false)
, mLayerCreationHint(layers::LayerManager::NONE)
{}
ContainerLayerParameters(float aXScale, float aYScale)
: mXScale(aXScale)
, mYScale(aYScale)
, mLayerContentsVisibleRect(nullptr)
, mBackgroundColor(NS_RGBA(0,0,0,0))
, mScrollClip(nullptr)
, mScrollClipForPerspectiveChild(nullptr)
, mInTransformedSubtree(false)
, mInActiveTransformedSubtree(false)
, mDisableSubpixelAntialiasingInDescendants(false)
, mInLowPrecisionDisplayPort(false)
, mForEventsAndPluginsOnly(false)
, mLayerCreationHint(layers::LayerManager::NONE)
{}
ContainerLayerParameters(float aXScale, float aYScale,
const nsIntPoint& aOffset,
const ContainerLayerParameters& aParent)
: mXScale(aXScale)
, mYScale(aYScale)
, mLayerContentsVisibleRect(nullptr)
, mOffset(aOffset)
, mBackgroundColor(aParent.mBackgroundColor)
, mScrollClip(aParent.mScrollClip)
, mScrollClipForPerspectiveChild(aParent.mScrollClipForPerspectiveChild)
, mInTransformedSubtree(aParent.mInTransformedSubtree)
, mInActiveTransformedSubtree(aParent.mInActiveTransformedSubtree)
, mDisableSubpixelAntialiasingInDescendants(aParent.mDisableSubpixelAntialiasingInDescendants)
, mInLowPrecisionDisplayPort(aParent.mInLowPrecisionDisplayPort)
, mForEventsAndPluginsOnly(aParent.mForEventsAndPluginsOnly)
, mLayerCreationHint(aParent.mLayerCreationHint)
{}
float mXScale, mYScale;
LayoutDeviceToLayerScale2D Scale() const {
return LayoutDeviceToLayerScale2D(mXScale, mYScale);
}
/**
* If non-null, the rectangle in which BuildContainerLayerFor stores the
* visible rect of the layer, in the coordinate system of the created layer.
*/
nsIntRect* mLayerContentsVisibleRect;
/**
* An offset to apply to all child layers created.
*/
nsIntPoint mOffset;
LayerIntPoint Offset() const {
return LayerIntPoint::FromUnknownPoint(mOffset);
}
nscolor mBackgroundColor;
const DisplayItemScrollClip* mScrollClip;
// usually nullptr, except when building children of an nsDisplayPerspective
const DisplayItemScrollClip* mScrollClipForPerspectiveChild;
bool mInTransformedSubtree;
bool mInActiveTransformedSubtree;
bool mDisableSubpixelAntialiasingInDescendants;
bool mInLowPrecisionDisplayPort;
bool mForEventsAndPluginsOnly;
layers::LayerManager::PaintedLayerCreationHint mLayerCreationHint;
/**
* When this is false, PaintedLayer coordinates are drawn to with an integer
* translation and the scale in mXScale/mYScale.
*/
bool AllowResidualTranslation()
{
// If we're in a transformed subtree, but no ancestor transform is actively
// changing, we'll use the residual translation when drawing into the
// PaintedLayer to ensure that snapping exactly matches the ideal transform.
return mInTransformedSubtree && !mInActiveTransformedSubtree;
}
};
/**
* The FrameLayerBuilder is responsible for converting display lists
* into layer trees. Every LayerManager needs a unique FrameLayerBuilder
* to build layers.
*
* The most important API in this class is BuildContainerLayerFor. This
* method takes a display list as input and constructs a ContainerLayer
* with child layers that render the contents of the display list. It
* records the relationship between frames and layers.
*
* That data enables us to retain layer trees. When constructing a
* ContainerLayer, we first check to see if there's an existing
* ContainerLayer for the same frame that can be recycled. If we recycle
* it, we also try to reuse its existing PaintedLayer children to render
* the display items without layers of their own. The idea is that by
* recycling layers deterministically, we can ensure that when nothing
* changes in a display list, we will reuse the existing layers without
* changes.
*
* We expose a GetLeafLayerFor method that can be called by display items
* that make their own layers (e.g. canvas and video); this method
* locates the last layer used to render the display item, if any, and
* return it as a candidate for recycling.
*
* FrameLayerBuilder sets up PaintedLayers so that 0,0 in the Painted layer
* corresponds to the (pixel-snapped) top-left of the aAnimatedGeometryRoot.
* It sets up ContainerLayers so that 0,0 in the container layer
* corresponds to the snapped top-left of the display item reference frame.
*
* When we construct a container layer, we know the transform that will be
* applied to the layer. If the transform scales the content, we can get
* better results when intermediate buffers are used by pushing some scale
* from the container's transform down to the children. For PaintedLayer
* children, the scaling can be achieved by changing the size of the layer
* and drawing into it with increased or decreased resolution. By convention,
* integer types (nsIntPoint/nsIntSize/nsIntRect/nsIntRegion) are all in layer
* coordinates, post-scaling, whereas appunit types are all pre-scaling.
*/
class FrameLayerBuilder : public layers::LayerUserData {
public:
typedef layers::ContainerLayer ContainerLayer;
typedef layers::Layer Layer;
typedef layers::PaintedLayer PaintedLayer;
typedef layers::ImageLayer ImageLayer;
typedef layers::LayerManager LayerManager;
typedef layers::BasicLayerManager BasicLayerManager;
typedef layers::EventRegions EventRegions;
FrameLayerBuilder();
~FrameLayerBuilder();
static void Shutdown();
void Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
PaintedLayerData* aLayerData = nullptr);
/**
* Call this to notify that we have just started a transaction on the
* retained layer manager aManager.
*/
void DidBeginRetainedLayerTransaction(LayerManager* aManager);
/**
* Call this just before we end a transaction.
*/
void WillEndTransaction();
/**
* Call this after we end a transaction.
*/
void DidEndTransaction();
enum {
/**
* Set this when pulling an opaque background color from behind the
* container layer into the container doesn't change the visual results,
* given the effects you're going to apply to the container layer.
* For example, this is compatible with opacity or clipping/masking, but
* not with non-OVER blend modes or filters.
*/
CONTAINER_ALLOW_PULL_BACKGROUND_COLOR = 0x01
};
/**
* Build a container layer for a display item that contains a child
* list, either reusing an existing one or creating a new one. It
* sets the container layer children to layers which together render
* the contents of the display list. It reuses existing layers from
* the retained layer manager if possible.
* aContainerItem may be null, in which case we construct a root layer.
* This gets called by display list code. It calls BuildLayer on the
* items in the display list, making items with their own layers
* children of the new container, and assigning all other items to
* PaintedLayer children created and managed by the FrameLayerBuilder.
* Returns a layer with clip rect cleared; it is the
* caller's responsibility to add any clip rect. The visible region
* is set based on what's in the layer.
* The container layer is transformed by aTransform (if non-null), and
* the result is transformed by the scale factors in aContainerParameters.
* aChildren is modified due to display item merging and flattening.
* The visible region of the returned layer is set only if aContainerItem
* is null.
*/
already_AddRefed<ContainerLayer>
BuildContainerLayerFor(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
nsIFrame* aContainerFrame,
nsDisplayItem* aContainerItem,
nsDisplayList* aChildren,
const ContainerLayerParameters& aContainerParameters,
const gfx::Matrix4x4* aTransform,
uint32_t aFlags = 0);
/**
* Get a retained layer for a display item that needs to create its own
* layer for rendering (i.e. under nsDisplayItem::BuildLayer). Returns
* null if no retained layer is available, which usually means that this
* display item didn't have a layer before so the caller will
* need to create one.
* Returns a layer with clip rect cleared; it is the
* caller's responsibility to add any clip rect and set the visible
* region.
*/
Layer* GetLeafLayerFor(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem);
/**
* Call this to force all retained layers to be discarded and recreated at
* the next paint.
*/
static void InvalidateAllLayers(LayerManager* aManager);
static void InvalidateAllLayersForFrame(nsIFrame *aFrame);
/**
* Call this to determine if a frame has a dedicated (non-Painted) layer
* for the given display item key. If there isn't one, we return null,
* otherwise we return the layer.
*/
static Layer* GetDedicatedLayer(nsIFrame* aFrame, uint32_t aDisplayItemKey);
/**
* This callback must be provided to EndTransaction. The callback data
* must be the nsDisplayListBuilder containing this FrameLayerBuilder.
* This function can be called multiple times in a row to draw
* different regions. This will occur when, for example, progressive paint is
* enabled. In these cases aDirtyRegion can be used to specify a larger region
* than aRegionToDraw that will be drawn during the transaction, possibly
* allowing the callback to make optimizations.
*/
static void DrawPaintedLayer(PaintedLayer* aLayer,
gfxContext* aContext,
const nsIntRegion& aRegionToDraw,
const nsIntRegion& aDirtyRegion,
mozilla::layers::DrawRegionClip aClip,
const nsIntRegion& aRegionToInvalidate,
void* aCallbackData);
/**
* Dumps this FrameLayerBuilder's retained layer manager's retained
* layer tree. Defaults to dumping to stdout in non-HTML format.
*/
static void DumpRetainedLayerTree(LayerManager* aManager, std::stringstream& aStream, bool aDumpHtml = false);
/**
* Returns the most recently allocated geometry item for the given display
* item.
*
* XXX(seth): The current implementation must iterate through all display
* items allocated for this display item's frame. This may lead to O(n^2)
* behavior in some situations.
*/
static nsDisplayItemGeometry* GetMostRecentGeometry(nsDisplayItem* aItem);
/******* PRIVATE METHODS to FrameLayerBuilder.cpp ********/
/* These are only in the public section because they need
* to be called by file-scope helper functions in FrameLayerBuilder.cpp.
*/
/**
* Record aItem as a display item that is rendered by aLayer.
*
* @param aLayer Layer that the display item will be rendered into
* @param aItem Display item to be drawn.
* @param aLayerState What LayerState the item is using.
* @param aManager If the layer is in the LAYER_INACTIVE state,
* then this is the temporary layer manager to draw with.
*/
void AddLayerDisplayItem(Layer* aLayer,
nsDisplayItem* aItem,
LayerState aLayerState,
BasicLayerManager* aManager);
/**
* Record aItem as a display item that is rendered by the PaintedLayer
* aLayer, with aClipRect, where aContainerLayerFrame is the frame
* for the container layer this ThebesItem belongs to.
* aItem must have an underlying frame.
* @param aTopLeft offset from active scrolled root to reference frame
*/
void AddPaintedDisplayItem(PaintedLayerData* aLayer,
nsDisplayItem* aItem,
const DisplayItemClip& aClip,
ContainerState& aContainerState,
LayerState aLayerState,
const nsPoint& aTopLeft);
/**
* Calls GetOldLayerForFrame on the underlying frame of the display item,
* and each subsequent merged frame if no layer is found for the underlying
* frame.
*/
Layer* GetOldLayerFor(nsDisplayItem* aItem,
nsDisplayItemGeometry** aOldGeometry = nullptr,
DisplayItemClip** aOldClip = nullptr);
void ClearCachedGeometry(nsDisplayItem* aItem);
static Layer* GetDebugOldLayerFor(nsIFrame* aFrame, uint32_t aDisplayItemKey);
/**
* Return the layer that all display items of aFrame were assigned to in the
* last paint, or nullptr if there was no single layer assigned to all of the
* frame's display items (i.e. zero, or more than one).
* This function is for testing purposes and not performance sensitive.
*/
static PaintedLayer* GetDebugSingleOldPaintedLayerForFrame(nsIFrame* aFrame);
/**
* Destroy any stored LayerManagerDataProperty and the associated data for
* aFrame.
*/
static void DestroyDisplayItemDataFor(nsIFrame* aFrame);
LayerManager* GetRetainingLayerManager() { return mRetainingManager; }
/**
* Returns true if the given display item was rendered during the previous
* paint. Returns false otherwise.
*/
static bool HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey);
class DisplayItemData;
typedef void (*DisplayItemDataCallback)(nsIFrame *aFrame, DisplayItemData* aItem);
static void IterateRetainedDataFor(nsIFrame* aFrame, DisplayItemDataCallback aCallback);
/**
* Save transform that was in aLayer when we last painted, and the position
* of the active scrolled root frame. It must be an integer
* translation.
*/
void SavePreviousDataForLayer(PaintedLayer* aLayer, uint32_t aClipCount);
/**
* Get the translation transform that was in aLayer when we last painted. It's either
* the transform saved by SaveLastPaintTransform, or else the transform
* that's currently in the layer (which must be an integer translation).
*/
nsIntPoint GetLastPaintOffset(PaintedLayer* aLayer);
/**
* Return the resolution at which we expect to render aFrame's contents,
* assuming they are being painted to retained layers. This takes into account
* the resolution the contents of the ContainerLayer containing aFrame are
* being rendered at, as well as any currently-inactive transforms between
* aFrame and that container layer.
*/
static gfxSize GetPaintedLayerScaleForFrame(nsIFrame* aFrame);
/**
* Stores a Layer as the dedicated layer in the DisplayItemData for a given frame/key pair.
*
* Used when we optimize a PaintedLayer into an ImageLayer and want to retroactively update the
* DisplayItemData so we can retrieve the layer from within layout.
*/
void StoreOptimizedLayerForFrame(nsDisplayItem* aItem, Layer* aLayer);
NS_DECLARE_FRAME_PROPERTY_WITH_FRAME_IN_DTOR(LayerManagerDataProperty,
nsTArray<DisplayItemData*>,
RemoveFrameFromLayerManager)
/**
* Retained data storage:
*
* Each layer manager (widget, and inactive) stores a LayerManagerData object
* that keeps a hash-set of DisplayItemData items that were drawn into it.
* Each frame also keeps a list of DisplayItemData pointers that were
* created for that frame. DisplayItemData objects manage these lists automatically.
*
* During layer construction we update the data in the LayerManagerData object, marking
* items that are modified. At the end we sweep the LayerManagerData hash-set and remove
* all items that haven't been modified.
*/
/**
* Retained data for a display item.
*/
class DisplayItemData final {
public:
friend class FrameLayerBuilder;
uint32_t GetDisplayItemKey() { return mDisplayItemKey; }
Layer* GetLayer() { return mLayer; }
nsDisplayItemGeometry* GetGeometry() const { return mGeometry.get(); }
void Invalidate() { mIsInvalid = true; }
void ClearAnimationCompositorState();
private:
DisplayItemData(LayerManagerData* aParent,
uint32_t aKey,
Layer* aLayer,
nsIFrame* aFrame = nullptr);
/**
* Removes any references to this object from frames
* in mFrameList.
*/
~DisplayItemData();
NS_INLINE_DECL_REFCOUNTING(DisplayItemData)
/**
* Associates this DisplayItemData with a frame, and adds it
* to the LayerManagerDataProperty list on the frame.
*/
void AddFrame(nsIFrame* aFrame);
void RemoveFrame(nsIFrame* aFrame);
const nsTArray<nsIFrame*>& GetFrameListChanges();
/**
* Updates the contents of this item to a new set of data, instead of allocating a new
* object.
* Set the passed in parameters, and clears the opt layer and inactive manager.
* Parent, and display item key are assumed to be the same.
*
* EndUpdate must be called before the end of the transaction to complete the update.
*/
void BeginUpdate(Layer* aLayer, LayerState aState,
uint32_t aContainerLayerGeneration, nsDisplayItem* aItem = nullptr);
/**
* Completes the update of this, and removes any references to data that won't live
* longer than the transaction.
*
* Updates the geometry, frame list and clip.
* For items within a PaintedLayer, a geometry object must be specified to retain
* until the next transaction.
*
*/
void EndUpdate(nsAutoPtr<nsDisplayItemGeometry> aGeometry);
void EndUpdate();
LayerManagerData* mParent;
RefPtr<Layer> mLayer;
RefPtr<Layer> mOptLayer;
RefPtr<BasicLayerManager> mInactiveManager;
AutoTArray<nsIFrame*, 1> mFrameList;
nsAutoPtr<nsDisplayItemGeometry> mGeometry;
DisplayItemClip mClip;
uint32_t mDisplayItemKey;
uint32_t mContainerLayerGeneration;
LayerState mLayerState;
/**
* Temporary stoarage of the display item being referenced, only valid between
* BeginUpdate and EndUpdate.
*/
nsDisplayItem* mItem;
AutoTArray<nsIFrame*, 1> mFrameListChanges;
/**
* Used to track if data currently stored in mFramesWithLayers (from an existing
* paint) has been updated in the current paint.
*/
bool mUsed;
bool mIsInvalid;
};
protected:
friend class LayerManagerData;
static void RemoveFrameFromLayerManager(const nsIFrame* aFrame,
nsTArray<DisplayItemData*>* aArray);
/**
* Given a frame and a display item key that uniquely identifies a
* display item for the frame, find the layer that was last used to
* render that display item. Returns null if there is no such layer.
* This could be a dedicated layer for the display item, or a PaintedLayer
* that renders many display items.
*/
DisplayItemData* GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey);
/**
* Stores DisplayItemData associated with aFrame, stores the data in
* mNewDisplayItemData.
*/
DisplayItemData* StoreDataForFrame(nsDisplayItem* aItem, Layer* aLayer, LayerState aState);
void StoreDataForFrame(nsIFrame* aFrame,
uint32_t aDisplayItemKey,
Layer* aLayer,
LayerState aState);
// Flash the area within the context clip if paint flashing is enabled.
static void FlashPaint(gfxContext *aContext);
/*
* Get the DisplayItemData array associated with this frame, or null if one
* doesn't exist.
*
* Note that the pointer returned here is only valid so long as you don't
* poke the LayerManagerData's mFramesWithLayers hashtable.
*/
DisplayItemData* GetDisplayItemData(nsIFrame *aFrame, uint32_t aKey);
/*
* Get the DisplayItemData associated with this frame / display item pair,
* using the LayerManager instead of FrameLayerBuilder.
*/
static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame,
uint32_t aDisplayItemKey,
LayerManager* aManager);
static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame,
uint32_t aDisplayItemKey);
static DisplayItemData* GetDisplayItemDataForManager(nsDisplayItem* aItem, LayerManager* aManager);
static DisplayItemData* GetDisplayItemDataForManager(nsIFrame* aFrame,
uint32_t aDisplayItemKey,
LayerManagerData* aData);
/**
* We store one of these for each display item associated with a
* PaintedLayer, in a hashtable that maps each PaintedLayer to an array
* of ClippedDisplayItems. (PaintedLayerItemsEntry is the hash entry
* for that hashtable.)
* These are only stored during the paint process, so that the
* DrawPaintedLayer callback can figure out which items to draw for the
* PaintedLayer.
*/
struct ClippedDisplayItem {
ClippedDisplayItem(nsDisplayItem* aItem, uint32_t aGeneration);
~ClippedDisplayItem();
nsDisplayItem* mItem;
/**
* If the display item is being rendered as an inactive
* layer, then this stores the layer manager being
* used for the inactive transaction.
*/
RefPtr<LayerManager> mInactiveLayerManager;
uint32_t mContainerLayerGeneration;
};
static void RecomputeVisibilityForItems(nsTArray<ClippedDisplayItem>& aItems,
nsDisplayListBuilder* aBuilder,
const nsIntRegion& aRegionToDraw,
const nsIntPoint& aOffset,
int32_t aAppUnitsPerDevPixel,
float aXScale,
float aYScale);
void PaintItems(nsTArray<ClippedDisplayItem>& aItems,
const nsIntRect& aRect,
gfxContext* aContext,
nsRenderingContext* aRC,
nsDisplayListBuilder* aBuilder,
nsPresContext* aPresContext,
const nsIntPoint& aOffset,
float aXScale, float aYScale,
int32_t aCommonClipCount);
/**
* We accumulate ClippedDisplayItem elements in a hashtable during
* the paint process. This is the hashentry for that hashtable.
*/
public:
class PaintedLayerItemsEntry : public nsPtrHashKey<PaintedLayer> {
public:
explicit PaintedLayerItemsEntry(const PaintedLayer *key);
PaintedLayerItemsEntry(const PaintedLayerItemsEntry&);
~PaintedLayerItemsEntry();
nsTArray<ClippedDisplayItem> mItems;
nsIFrame* mContainerLayerFrame;
// The translation set on this PaintedLayer before we started updating the
// layer tree.
nsIntPoint mLastPaintOffset;
uint32_t mLastCommonClipCount;
uint32_t mContainerLayerGeneration;
bool mHasExplicitLastPaintOffset;
/**
* The first mCommonClipCount rounded rectangle clips are identical for
* all items in the layer. Computed in PaintedLayerData.
*/
uint32_t mCommonClipCount;
enum { ALLOW_MEMMOVE = true };
};
/**
* Get the PaintedLayerItemsEntry object associated with aLayer in this
* FrameLayerBuilder
*/
PaintedLayerItemsEntry* GetPaintedLayerItemsEntry(PaintedLayer* aLayer)
{
return mPaintedLayerItems.GetEntry(aLayer);
}
PaintedLayerData* GetContainingPaintedLayerData()
{
return mContainingPaintedLayer;
}
bool IsBuildingRetainedLayers()
{
return !mContainingPaintedLayer && mRetainingManager;
}
/**
* Attempt to build the most compressed layer tree possible, even if it means
* throwing away existing retained buffers.
*/
void SetLayerTreeCompressionMode() { mInLayerTreeCompressionMode = true; }
bool CheckInLayerTreeCompressionMode();
void ComputeGeometryChangeForItem(DisplayItemData* aData);
protected:
/**
* Returns true if the DOM has been modified since we started painting,
* in which case we should bail out and not paint anymore. This should
* never happen, but plugins can trigger it in some cases.
*/
bool CheckDOMModified();
/**
* The layer manager belonging to the widget that is being retained
* across paints.
*/
LayerManager* mRetainingManager;
/**
* The root prescontext for the display list builder reference frame
*/
RefPtr<nsRootPresContext> mRootPresContext;
/**
* The display list builder being used.
*/
nsDisplayListBuilder* mDisplayListBuilder;
/**
* A map from PaintedLayers to the list of display items (plus
* clipping data) to be rendered in the layer.
*/
nsTHashtable<PaintedLayerItemsEntry> mPaintedLayerItems;
/**
* When building layers for an inactive layer, this is where the
* inactive layer will be placed.
*/
PaintedLayerData* mContainingPaintedLayer;
/**
* Saved generation counter so we can detect DOM changes.
*/
uint32_t mInitialDOMGeneration;
/**
* Set to true if we have detected and reported DOM modification during
* the current paint.
*/
bool mDetectedDOMModification;
/**
* Indicates that the entire layer tree should be rerendered
* during this paint.
*/
bool mInvalidateAllLayers;
bool mInLayerTreeCompressionMode;
uint32_t mContainerLayerGeneration;
uint32_t mMaxContainerLayerGeneration;
};
} // namespace mozilla
#endif /* FRAMELAYERBUILDER_H_ */

View file

@ -0,0 +1,239 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#include "FramePropertyTable.h"
#include "mozilla/MemoryReporting.h"
namespace mozilla {
void
FramePropertyTable::SetInternal(
const nsIFrame* aFrame, UntypedDescriptor aProperty, void* aValue)
{
NS_ASSERTION(aFrame, "Null frame?");
NS_ASSERTION(aProperty, "Null property?");
if (mLastFrame != aFrame || !mLastEntry) {
mLastFrame = aFrame;
mLastEntry = mEntries.PutEntry(aFrame);
}
Entry* entry = mLastEntry;
if (!entry->mProp.IsArray()) {
if (!entry->mProp.mProperty) {
// Empty entry, so we can just store our property in the empty slot
entry->mProp.mProperty = aProperty;
entry->mProp.mValue = aValue;
return;
}
if (entry->mProp.mProperty == aProperty) {
// Just overwrite the current value
entry->mProp.DestroyValueFor(aFrame);
entry->mProp.mValue = aValue;
return;
}
// We need to expand the single current entry to an array
PropertyValue current = entry->mProp;
entry->mProp.mProperty = nullptr;
static_assert(sizeof(nsTArray<PropertyValue>) <= sizeof(void *),
"Property array must fit entirely within entry->mProp.mValue");
new (&entry->mProp.mValue) nsTArray<PropertyValue>(4);
entry->mProp.ToArray()->AppendElement(current);
}
nsTArray<PropertyValue>* array = entry->mProp.ToArray();
nsTArray<PropertyValue>::index_type index =
array->IndexOf(aProperty, 0, PropertyComparator());
if (index != nsTArray<PropertyValue>::NoIndex) {
PropertyValue* pv = &array->ElementAt(index);
pv->DestroyValueFor(aFrame);
pv->mValue = aValue;
return;
}
array->AppendElement(PropertyValue(aProperty, aValue));
}
void*
FramePropertyTable::GetInternal(
const nsIFrame* aFrame, UntypedDescriptor aProperty, bool* aFoundResult)
{
NS_ASSERTION(aFrame, "Null frame?");
NS_ASSERTION(aProperty, "Null property?");
if (aFoundResult) {
*aFoundResult = false;
}
if (mLastFrame != aFrame) {
mLastFrame = aFrame;
mLastEntry = mEntries.GetEntry(mLastFrame);
}
Entry* entry = mLastEntry;
if (!entry)
return nullptr;
if (entry->mProp.mProperty == aProperty) {
if (aFoundResult) {
*aFoundResult = true;
}
return entry->mProp.mValue;
}
if (!entry->mProp.IsArray()) {
// There's just one property and it's not the one we want, bail
return nullptr;
}
nsTArray<PropertyValue>* array = entry->mProp.ToArray();
nsTArray<PropertyValue>::index_type index =
array->IndexOf(aProperty, 0, PropertyComparator());
if (index == nsTArray<PropertyValue>::NoIndex)
return nullptr;
if (aFoundResult) {
*aFoundResult = true;
}
return array->ElementAt(index).mValue;
}
void*
FramePropertyTable::RemoveInternal(
const nsIFrame* aFrame, UntypedDescriptor aProperty, bool* aFoundResult)
{
NS_ASSERTION(aFrame, "Null frame?");
NS_ASSERTION(aProperty, "Null property?");
if (aFoundResult) {
*aFoundResult = false;
}
if (mLastFrame != aFrame) {
mLastFrame = aFrame;
mLastEntry = mEntries.GetEntry(aFrame);
}
Entry* entry = mLastEntry;
if (!entry)
return nullptr;
if (entry->mProp.mProperty == aProperty) {
// There's only one entry and it's the one we want
void* value = entry->mProp.mValue;
// Here it's ok to use RemoveEntry() -- which may resize mEntries --
// because we null mLastEntry at the same time.
mEntries.RemoveEntry(entry);
mLastEntry = nullptr;
if (aFoundResult) {
*aFoundResult = true;
}
return value;
}
if (!entry->mProp.IsArray()) {
// There's just one property and it's not the one we want, bail
return nullptr;
}
nsTArray<PropertyValue>* array = entry->mProp.ToArray();
nsTArray<PropertyValue>::index_type index =
array->IndexOf(aProperty, 0, PropertyComparator());
if (index == nsTArray<PropertyValue>::NoIndex) {
// No such property, bail
return nullptr;
}
if (aFoundResult) {
*aFoundResult = true;
}
void* result = array->ElementAt(index).mValue;
uint32_t last = array->Length() - 1;
array->ElementAt(index) = array->ElementAt(last);
array->RemoveElementAt(last);
if (last == 1) {
PropertyValue pv = array->ElementAt(0);
array->~nsTArray<PropertyValue>();
entry->mProp = pv;
}
return result;
}
void
FramePropertyTable::DeleteInternal(
const nsIFrame* aFrame, UntypedDescriptor aProperty)
{
NS_ASSERTION(aFrame, "Null frame?");
NS_ASSERTION(aProperty, "Null property?");
bool found;
void* v = RemoveInternal(aFrame, aProperty, &found);
if (found) {
PropertyValue pv(aProperty, v);
pv.DestroyValueFor(aFrame);
}
}
/* static */ void
FramePropertyTable::DeleteAllForEntry(Entry* aEntry)
{
if (!aEntry->mProp.IsArray()) {
aEntry->mProp.DestroyValueFor(aEntry->GetKey());
return;
}
nsTArray<PropertyValue>* array = aEntry->mProp.ToArray();
for (uint32_t i = 0; i < array->Length(); ++i) {
array->ElementAt(i).DestroyValueFor(aEntry->GetKey());
}
array->~nsTArray<PropertyValue>();
}
void
FramePropertyTable::DeleteAllFor(const nsIFrame* aFrame)
{
NS_ASSERTION(aFrame, "Null frame?");
Entry* entry = mEntries.GetEntry(aFrame);
if (!entry)
return;
if (mLastFrame == aFrame) {
// Flush cache. We assume DeleteAllForEntry will be called before
// a frame is destroyed.
mLastFrame = nullptr;
mLastEntry = nullptr;
}
DeleteAllForEntry(entry);
// mLastEntry points into mEntries, so we use RawRemoveEntry() which will not
// resize mEntries.
mEntries.RawRemoveEntry(entry);
}
void
FramePropertyTable::DeleteAll()
{
mLastFrame = nullptr;
mLastEntry = nullptr;
for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) {
DeleteAllForEntry(iter.Get());
}
mEntries.Clear();
}
size_t
FramePropertyTable::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
return mEntries.SizeOfExcludingThis(aMallocSizeOf);
}
} // namespace mozilla

View file

@ -0,0 +1,442 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#ifndef FRAMEPROPERTYTABLE_H_
#define FRAMEPROPERTYTABLE_H_
#include "mozilla/MemoryReporting.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/Unused.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
class nsIFrame;
namespace mozilla {
struct FramePropertyDescriptorUntyped
{
/**
* mDestructor will be called if it's non-null.
*/
typedef void UntypedDestructor(void* aPropertyValue);
UntypedDestructor* mDestructor;
/**
* mDestructorWithFrame will be called if it's non-null and mDestructor
* is null. WARNING: The frame passed to mDestructorWithFrame may
* be a dangling frame pointer, if this is being called during
* presshell teardown. Do not use it except to compare against
* other frame pointers. No frame will have been allocated with
* the same address yet.
*/
typedef void UntypedDestructorWithFrame(const nsIFrame* aFrame,
void* aPropertyValue);
UntypedDestructorWithFrame* mDestructorWithFrame;
/**
* mDestructor and mDestructorWithFrame may both be null, in which case
* no value destruction is a no-op.
*/
protected:
/**
* At most one destructor should be passed in. In general, you should
* just use the static function FramePropertyDescriptor::New* below
* instead of using this constructor directly.
*/
constexpr FramePropertyDescriptorUntyped(
UntypedDestructor* aDtor, UntypedDestructorWithFrame* aDtorWithFrame)
: mDestructor(aDtor)
, mDestructorWithFrame(aDtorWithFrame)
{}
};
/**
* A pointer to a FramePropertyDescriptor serves as a unique property ID.
* The FramePropertyDescriptor stores metadata about the property.
* Currently the only metadata is a destructor function. The destructor
* function is called on property values when they are overwritten or
* deleted.
*
* To use this class, declare a global (i.e., file, class or function-scope
* static member) FramePropertyDescriptor and pass its address as
* aProperty in the FramePropertyTable methods.
*/
template<typename T>
struct FramePropertyDescriptor : public FramePropertyDescriptorUntyped
{
typedef void Destructor(T* aPropertyValue);
typedef void DestructorWithFrame(const nsIFrame* aaFrame,
T* aPropertyValue);
template<Destructor Dtor>
static constexpr const FramePropertyDescriptor<T> NewWithDestructor()
{
return { Destruct<Dtor>, nullptr };
}
template<DestructorWithFrame Dtor>
static constexpr
const FramePropertyDescriptor<T> NewWithDestructorWithFrame()
{
return { nullptr, DestructWithFrame<Dtor> };
}
static constexpr const FramePropertyDescriptor<T> NewWithoutDestructor()
{
return { nullptr, nullptr };
}
private:
constexpr FramePropertyDescriptor(
UntypedDestructor* aDtor, UntypedDestructorWithFrame* aDtorWithFrame)
: FramePropertyDescriptorUntyped(aDtor, aDtorWithFrame)
{}
template<Destructor Dtor>
static void Destruct(void* aPropertyValue)
{
Dtor(static_cast<T*>(aPropertyValue));
}
template<DestructorWithFrame Dtor>
static void DestructWithFrame(const nsIFrame* aFrame, void* aPropertyValue)
{
Dtor(aFrame, static_cast<T*>(aPropertyValue));
}
};
// SmallValueHolder<T> is a placeholder intended to be used as template
// argument of FramePropertyDescriptor for types which can fit into the
// size of a pointer directly. This class should never be defined, so
// that we won't use it for unexpected purpose by mistake.
template<typename T>
class SmallValueHolder;
namespace detail {
template<typename T>
struct FramePropertyTypeHelper
{
typedef T* Type;
};
template<typename T>
struct FramePropertyTypeHelper<SmallValueHolder<T>>
{
typedef T Type;
};
}
/**
* The FramePropertyTable is optimized for storing 0 or 1 properties on
* a given frame. Storing very large numbers of properties on a single
* frame will not be efficient.
*
* Property values are passed as void* but do not actually have to be
* valid pointers. You can use NS_INT32_TO_PTR/NS_PTR_TO_INT32 to
* store int32_t values. Null/zero values can be stored and retrieved.
* Of course, the destructor function (if any) must handle such values
* correctly.
*/
class FramePropertyTable {
public:
template<typename T>
using Descriptor = const FramePropertyDescriptor<T>*;
using UntypedDescriptor = const FramePropertyDescriptorUntyped*;
template<typename T>
using PropertyType = typename detail::FramePropertyTypeHelper<T>::Type;
FramePropertyTable() : mLastFrame(nullptr), mLastEntry(nullptr)
{
}
~FramePropertyTable()
{
DeleteAll();
}
/**
* Set a property value on a frame. This requires one hashtable
* lookup (using the frame as the key) and a linear search through
* the properties of that frame. Any existing value for the property
* is destroyed.
*/
template<typename T>
void Set(const nsIFrame* aFrame, Descriptor<T> aProperty,
PropertyType<T> aValue)
{
void* ptr = ReinterpretHelper<T>::ToPointer(aValue);
SetInternal(aFrame, aProperty, ptr);
}
/**
* @return true if @aProperty is set for @aFrame. This requires one hashtable
* lookup (using the frame as the key) and a linear search through the
* properties of that frame.
*
* In most cases, this shouldn't be used outside of assertions, because if
* you're doing a lookup anyway it would be far more efficient to call Get()
* or Remove() and check the aFoundResult outparam to find out whether the
* property is set. Legitimate non-assertion uses include:
*
* - Checking if a frame property is set in cases where that's all we want
* to know (i.e., we don't intend to read the actual value or remove the
* property).
*
* - Calling Has() before Set() in cases where we don't want to overwrite
* an existing value for the frame property.
*/
template<typename T>
bool Has(const nsIFrame* aFrame, Descriptor<T> aProperty)
{
bool foundResult = false;
mozilla::Unused << GetInternal(aFrame, aProperty, &foundResult);
return foundResult;
}
/**
* Get a property value for a frame. This requires one hashtable
* lookup (using the frame as the key) and a linear search through
* the properties of that frame. If the frame has no such property,
* returns zero-filled result, which means null for pointers and
* zero for integers and floating point types.
* @param aFoundResult if non-null, receives a value 'true' iff
* the frame has a value for the property. This lets callers
* disambiguate a null result, which can mean 'no such property' or
* 'property value is null'.
*/
template<typename T>
PropertyType<T> Get(const nsIFrame* aFrame, Descriptor<T> aProperty,
bool* aFoundResult = nullptr)
{
void* ptr = GetInternal(aFrame, aProperty, aFoundResult);
return ReinterpretHelper<T>::FromPointer(ptr);
}
/**
* Remove a property value for a frame. This requires one hashtable
* lookup (using the frame as the key) and a linear search through
* the properties of that frame. The old property value is returned
* (and not destroyed). If the frame has no such property,
* returns zero-filled result, which means null for pointers and
* zero for integers and floating point types.
* @param aFoundResult if non-null, receives a value 'true' iff
* the frame had a value for the property. This lets callers
* disambiguate a null result, which can mean 'no such property' or
* 'property value is null'.
*/
template<typename T>
PropertyType<T> Remove(const nsIFrame* aFrame, Descriptor<T> aProperty,
bool* aFoundResult = nullptr)
{
void* ptr = RemoveInternal(aFrame, aProperty, aFoundResult);
return ReinterpretHelper<T>::FromPointer(ptr);
}
/**
* Remove and destroy a property value for a frame. This requires one
* hashtable lookup (using the frame as the key) and a linear search
* through the properties of that frame. If the frame has no such
* property, nothing happens.
*/
template<typename T>
void Delete(const nsIFrame* aFrame, Descriptor<T> aProperty)
{
DeleteInternal(aFrame, aProperty);
}
/**
* Remove and destroy all property values for a frame. This requires one
* hashtable lookup (using the frame as the key).
*/
void DeleteAllFor(const nsIFrame* aFrame);
/**
* Remove and destroy all property values for all frames.
*/
void DeleteAll();
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
protected:
void SetInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty,
void* aValue);
void* GetInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty,
bool* aFoundResult);
void* RemoveInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty,
bool* aFoundResult);
void DeleteInternal(const nsIFrame* aFrame, UntypedDescriptor aProperty);
template<typename T>
struct ReinterpretHelper
{
static_assert(sizeof(PropertyType<T>) <= sizeof(void*),
"size of the value must never be larger than a pointer");
static void* ToPointer(PropertyType<T> aValue)
{
void* ptr = nullptr;
memcpy(&ptr, &aValue, sizeof(aValue));
return ptr;
}
static PropertyType<T> FromPointer(void* aPtr)
{
PropertyType<T> value;
memcpy(&value, &aPtr, sizeof(value));
return value;
}
};
template<typename T>
struct ReinterpretHelper<T*>
{
static void* ToPointer(T* aValue)
{
return static_cast<void*>(aValue);
}
static T* FromPointer(void* aPtr)
{
return static_cast<T*>(aPtr);
}
};
/**
* Stores a property descriptor/value pair. It can also be used to
* store an nsTArray of PropertyValues.
*/
struct PropertyValue {
PropertyValue() : mProperty(nullptr), mValue(nullptr) {}
PropertyValue(UntypedDescriptor aProperty, void* aValue)
: mProperty(aProperty), mValue(aValue) {}
bool IsArray() { return !mProperty && mValue; }
nsTArray<PropertyValue>* ToArray()
{
NS_ASSERTION(IsArray(), "Must be array");
return reinterpret_cast<nsTArray<PropertyValue>*>(&mValue);
}
void DestroyValueFor(const nsIFrame* aFrame) {
if (mProperty->mDestructor) {
mProperty->mDestructor(mValue);
} else if (mProperty->mDestructorWithFrame) {
mProperty->mDestructorWithFrame(aFrame, mValue);
}
}
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
size_t n = 0;
// We don't need to measure mProperty because it always points to static
// memory. As for mValue: if it's a single value we can't measure it,
// because the type is opaque; if it's an array, we measure the array
// storage, but we can't measure the individual values, again because
// their types are opaque.
if (IsArray()) {
nsTArray<PropertyValue>* array = ToArray();
n += array->ShallowSizeOfExcludingThis(aMallocSizeOf);
}
return n;
}
UntypedDescriptor mProperty;
void* mValue;
};
/**
* Used with an array of PropertyValues to allow lookups that compare
* only on the FramePropertyDescriptor.
*/
class PropertyComparator {
public:
bool Equals(const PropertyValue& a, const PropertyValue& b) const {
return a.mProperty == b.mProperty;
}
bool Equals(UntypedDescriptor a, const PropertyValue& b) const {
return a == b.mProperty;
}
bool Equals(const PropertyValue& a, UntypedDescriptor b) const {
return a.mProperty == b;
}
};
/**
* Our hashtable entry. The key is an nsIFrame*, the value is a
* PropertyValue representing one or more property/value pairs.
*/
class Entry : public nsPtrHashKey<const nsIFrame>
{
public:
explicit Entry(KeyTypePointer aKey) : nsPtrHashKey<const nsIFrame>(aKey) {}
Entry(const Entry &toCopy) :
nsPtrHashKey<const nsIFrame>(toCopy), mProp(toCopy.mProp) {}
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
return mProp.SizeOfExcludingThis(aMallocSizeOf);
}
PropertyValue mProp;
};
static void DeleteAllForEntry(Entry* aEntry);
// Note that mLastEntry points into mEntries, so we need to be careful about
// not triggering a resize of mEntries, e.g. use RawRemoveEntry() instead of
// RemoveEntry() in some places.
nsTHashtable<Entry> mEntries;
const nsIFrame* mLastFrame;
Entry* mLastEntry;
};
/**
* This class encapsulates the properties of a frame.
*/
class FrameProperties {
public:
template<typename T> using Descriptor = FramePropertyTable::Descriptor<T>;
template<typename T> using PropertyType = FramePropertyTable::PropertyType<T>;
FrameProperties(FramePropertyTable* aTable, const nsIFrame* aFrame)
: mTable(aTable), mFrame(aFrame) {}
template<typename T>
void Set(Descriptor<T> aProperty, PropertyType<T> aValue) const
{
mTable->Set(mFrame, aProperty, aValue);
}
template<typename T>
bool Has(Descriptor<T> aProperty) const
{
return mTable->Has(mFrame, aProperty);
}
template<typename T>
PropertyType<T> Get(Descriptor<T> aProperty,
bool* aFoundResult = nullptr) const
{
return mTable->Get(mFrame, aProperty, aFoundResult);
}
template<typename T>
PropertyType<T> Remove(Descriptor<T> aProperty,
bool* aFoundResult = nullptr) const
{
return mTable->Remove(mFrame, aProperty, aFoundResult);
}
template<typename T>
void Delete(Descriptor<T> aProperty)
{
mTable->Delete(mFrame, aProperty);
}
private:
FramePropertyTable* mTable;
const nsIFrame* mFrame;
};
} // namespace mozilla
#endif /* FRAMEPROPERTYTABLE_H_ */

View file

@ -0,0 +1,399 @@
/* -*- Mode: C++; 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/. */
#include "GeometryUtils.h"
#include "mozilla/dom/DOMPointBinding.h"
#include "mozilla/dom/GeometryUtilsBinding.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Text.h"
#include "mozilla/dom/DOMPoint.h"
#include "mozilla/dom/DOMQuad.h"
#include "mozilla/dom/DOMRect.h"
#include "nsContentUtils.h"
#include "nsIFrame.h"
#include "nsGenericDOMDataNode.h"
#include "nsCSSFrameConstructor.h"
#include "nsLayoutUtils.h"
#include "nsSVGUtils.h"
using namespace mozilla;
using namespace mozilla::dom;
namespace mozilla {
enum GeometryNodeType {
GEOMETRY_NODE_ELEMENT,
GEOMETRY_NODE_TEXT,
GEOMETRY_NODE_DOCUMENT
};
static nsIFrame*
GetFrameForNode(nsINode* aNode, GeometryNodeType aType)
{
nsIDocument* doc = aNode->OwnerDoc();
doc->FlushPendingNotifications(Flush_Layout);
switch (aType) {
case GEOMETRY_NODE_ELEMENT:
return aNode->AsContent()->GetPrimaryFrame();
case GEOMETRY_NODE_TEXT: {
nsIPresShell* presShell = doc->GetShell();
if (presShell) {
return presShell->FrameConstructor()->EnsureFrameForTextNode(
static_cast<nsGenericDOMDataNode*>(aNode));
}
return nullptr;
}
case GEOMETRY_NODE_DOCUMENT: {
nsIPresShell* presShell = doc->GetShell();
return presShell ? presShell->GetRootFrame() : nullptr;
}
default:
MOZ_ASSERT(false, "Unknown GeometryNodeType");
return nullptr;
}
}
static nsIFrame*
GetFrameForGeometryNode(const Optional<OwningGeometryNode>& aGeometryNode,
nsINode* aDefaultNode)
{
if (!aGeometryNode.WasPassed()) {
return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT);
}
const OwningGeometryNode& value = aGeometryNode.Value();
if (value.IsElement()) {
return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT);
}
if (value.IsDocument()) {
return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT);
}
return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT);
}
static nsIFrame*
GetFrameForGeometryNode(const GeometryNode& aGeometryNode)
{
if (aGeometryNode.IsElement()) {
return GetFrameForNode(&aGeometryNode.GetAsElement(), GEOMETRY_NODE_ELEMENT);
}
if (aGeometryNode.IsDocument()) {
return GetFrameForNode(&aGeometryNode.GetAsDocument(), GEOMETRY_NODE_DOCUMENT);
}
return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT);
}
static nsIFrame*
GetFrameForNode(nsINode* aNode)
{
if (aNode->IsElement()) {
return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT);
}
if (aNode == aNode->OwnerDoc()) {
return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT);
}
NS_ASSERTION(aNode->IsNodeOfType(nsINode::eTEXT), "Unknown node type");
return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT);
}
static nsIFrame*
GetFirstNonAnonymousFrameForGeometryNode(const Optional<OwningGeometryNode>& aNode,
nsINode* aDefaultNode)
{
nsIFrame* f = GetFrameForGeometryNode(aNode, aDefaultNode);
if (f) {
f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
}
return f;
}
static nsIFrame*
GetFirstNonAnonymousFrameForGeometryNode(const GeometryNode& aNode)
{
nsIFrame* f = GetFrameForGeometryNode(aNode);
if (f) {
f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
}
return f;
}
static nsIFrame*
GetFirstNonAnonymousFrameForNode(nsINode* aNode)
{
nsIFrame* f = GetFrameForNode(aNode);
if (f) {
f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
}
return f;
}
/**
* This can modify aFrame to point to a different frame. This is needed to
* handle SVG, where SVG elements can only compute a rect that's valid with
* respect to the "outer SVG" frame.
*/
static nsRect
GetBoxRectForFrame(nsIFrame** aFrame, CSSBoxType aType)
{
nsRect r;
nsIFrame* f = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r);
if (f && f != *aFrame) {
// For non-outer SVG frames, the BoxType is ignored.
*aFrame = f;
return r;
}
f = *aFrame;
switch (aType) {
case CSSBoxType::Content: r = f->GetContentRectRelativeToSelf(); break;
case CSSBoxType::Padding: r = f->GetPaddingRectRelativeToSelf(); break;
case CSSBoxType::Border: r = nsRect(nsPoint(0, 0), f->GetSize()); break;
case CSSBoxType::Margin: {
r = nsRect(nsPoint(0, 0), f->GetSize());
r.Inflate(f->GetUsedMargin());
break;
}
default: MOZ_ASSERT(false, "unknown box type"); return r;
}
return r;
}
class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback {
public:
AccumulateQuadCallback(nsISupports* aParentObject,
nsTArray<RefPtr<DOMQuad> >& aResult,
nsIFrame* aRelativeToFrame,
const nsPoint& aRelativeToBoxTopLeft,
CSSBoxType aBoxType)
: mParentObject(aParentObject)
, mResult(aResult)
, mRelativeToFrame(aRelativeToFrame)
, mRelativeToBoxTopLeft(aRelativeToBoxTopLeft)
, mBoxType(aBoxType)
{
if (mBoxType == CSSBoxType::Margin) {
// Don't include the caption margin when computing margins for a
// table
mIncludeCaptionBoxForTable = false;
}
}
virtual void AddBox(nsIFrame* aFrame) override
{
nsIFrame* f = aFrame;
if (mBoxType == CSSBoxType::Margin &&
f->GetType() == nsGkAtoms::tableFrame) {
// Margin boxes for table frames should be taken from the table wrapper
// frame, since that has the margin.
f = f->GetParent();
}
nsRect box = GetBoxRectForFrame(&f, mBoxType);
nsPoint appUnits[4] =
{ box.TopLeft(), box.TopRight(), box.BottomRight(), box.BottomLeft() };
CSSPoint points[4];
for (uint32_t i = 0; i < 4; ++i) {
points[i] = CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].x),
nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].y));
}
nsLayoutUtils::TransformResult rv =
nsLayoutUtils::TransformPoints(f, mRelativeToFrame, 4, points);
if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
CSSPoint delta(nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x),
nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y));
for (uint32_t i = 0; i < 4; ++i) {
points[i] -= delta;
}
} else {
PodArrayZero(points);
}
mResult.AppendElement(new DOMQuad(mParentObject, points));
}
nsISupports* mParentObject;
nsTArray<RefPtr<DOMQuad> >& mResult;
nsIFrame* mRelativeToFrame;
nsPoint mRelativeToBoxTopLeft;
CSSBoxType mBoxType;
};
static nsPresContext*
FindTopLevelPresContext(nsPresContext* aPC)
{
bool isChrome = aPC->IsChrome();
nsPresContext* pc = aPC;
for (;;) {
nsPresContext* parent = pc->GetParentPresContext();
if (!parent || parent->IsChrome() != isChrome) {
return pc;
}
pc = parent;
}
}
static bool
CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1, nsIFrame* aFrame2)
{
nsPresContext* pc1 = aFrame1->PresContext();
nsPresContext* pc2 = aFrame2->PresContext();
if (pc1 == pc2) {
return true;
}
if (nsContentUtils::IsCallerChrome()) {
return true;
}
if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) {
return true;
}
return false;
}
void GetBoxQuads(nsINode* aNode,
const dom::BoxQuadOptions& aOptions,
nsTArray<RefPtr<DOMQuad> >& aResult,
ErrorResult& aRv)
{
nsIFrame* frame = GetFrameForNode(aNode);
if (!frame) {
// No boxes to return
return;
}
nsWeakFrame weakFrame(frame);
nsIDocument* ownerDoc = aNode->OwnerDoc();
nsIFrame* relativeToFrame =
GetFirstNonAnonymousFrameForGeometryNode(aOptions.mRelativeTo, ownerDoc);
// The first frame might be destroyed now if the above call lead to an
// EnsureFrameForTextNode call. We need to get the first frame again
// when that happens and re-check it.
if (!weakFrame.IsAlive()) {
frame = GetFrameForNode(aNode);
if (!frame) {
// No boxes to return
return;
}
}
if (!relativeToFrame) {
aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
return;
}
if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame)) {
aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
return;
}
// GetBoxRectForFrame can modify relativeToFrame so call it first.
nsPoint relativeToTopLeft =
GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft();
AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame,
relativeToTopLeft, aOptions.mBox);
nsLayoutUtils::GetAllInFlowBoxes(frame, &callback);
}
static void
TransformPoints(nsINode* aTo, const GeometryNode& aFrom,
uint32_t aPointCount, CSSPoint* aPoints,
const ConvertCoordinateOptions& aOptions, ErrorResult& aRv)
{
nsIFrame* fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom);
nsWeakFrame weakFrame(fromFrame);
nsIFrame* toFrame = GetFirstNonAnonymousFrameForNode(aTo);
// The first frame might be destroyed now if the above call lead to an
// EnsureFrameForTextNode call. We need to get the first frame again
// when that happens.
if (fromFrame && !weakFrame.IsAlive()) {
fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom);
}
if (!fromFrame || !toFrame) {
aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
return;
}
if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame)) {
aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
return;
}
nsPoint fromOffset = GetBoxRectForFrame(&fromFrame, aOptions.mFromBox).TopLeft();
nsPoint toOffset = GetBoxRectForFrame(&toFrame, aOptions.mToBox).TopLeft();
CSSPoint fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.x),
nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.y));
for (uint32_t i = 0; i < aPointCount; ++i) {
aPoints[i] += fromOffsetGfx;
}
nsLayoutUtils::TransformResult rv =
nsLayoutUtils::TransformPoints(fromFrame, toFrame, aPointCount, aPoints);
if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x),
nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y));
for (uint32_t i = 0; i < aPointCount; ++i) {
aPoints[i] -= toOffsetGfx;
}
} else {
PodZero(aPoints, aPointCount);
}
}
already_AddRefed<DOMQuad>
ConvertQuadFromNode(nsINode* aTo, dom::DOMQuad& aQuad,
const GeometryNode& aFrom,
const dom::ConvertCoordinateOptions& aOptions,
ErrorResult& aRv)
{
CSSPoint points[4];
for (uint32_t i = 0; i < 4; ++i) {
DOMPoint* p = aQuad.Point(i);
if (p->W() != 1.0 || p->Z() != 0.0) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
points[i] = CSSPoint(p->X(), p->Y());
}
TransformPoints(aTo, aFrom, 4, points, aOptions, aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
return result.forget();
}
already_AddRefed<DOMQuad>
ConvertRectFromNode(nsINode* aTo, dom::DOMRectReadOnly& aRect,
const GeometryNode& aFrom,
const dom::ConvertCoordinateOptions& aOptions,
ErrorResult& aRv)
{
CSSPoint points[4];
double x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height();
points[0] = CSSPoint(x, y);
points[1] = CSSPoint(x + w, y);
points[2] = CSSPoint(x + w, y + h);
points[3] = CSSPoint(x, y + h);
TransformPoints(aTo, aFrom, 4, points, aOptions, aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
return result.forget();
}
already_AddRefed<DOMPoint>
ConvertPointFromNode(nsINode* aTo, const dom::DOMPointInit& aPoint,
const GeometryNode& aFrom,
const dom::ConvertCoordinateOptions& aOptions,
ErrorResult& aRv)
{
if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
CSSPoint point(aPoint.mX, aPoint.mY);
TransformPoints(aTo, aFrom, 1, &point, aOptions, aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DOMPoint> result = new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y);
return result.forget();
}
} // namespace mozilla

View file

@ -0,0 +1,65 @@
/* -*- Mode: C++; 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/. */
#ifndef MOZILLA_GEOMETRYUTILS_H_
#define MOZILLA_GEOMETRYUTILS_H_
#include "mozilla/ErrorResult.h"
#include "nsTArray.h"
#include "nsCOMPtr.h"
/**
* This file defines utility functions for converting between layout
* coordinate systems.
*/
class nsINode;
namespace mozilla {
namespace dom {
struct BoxQuadOptions;
struct ConvertCoordinateOptions;
class DOMQuad;
class DOMRectReadOnly;
class DOMPoint;
struct DOMPointInit;
class OwningTextOrElementOrDocument;
class TextOrElementOrDocument;
} // namespace dom
typedef dom::TextOrElementOrDocument GeometryNode;
typedef dom::OwningTextOrElementOrDocument OwningGeometryNode;
/**
* Computes quads for aNode using aOptions, according to GeometryUtils.getBoxQuads.
* May set an error in aRv.
*/
void GetBoxQuads(nsINode* aNode,
const dom::BoxQuadOptions& aOptions,
nsTArray<RefPtr<dom::DOMQuad> >& aResult,
ErrorResult& aRv);
already_AddRefed<dom::DOMQuad>
ConvertQuadFromNode(nsINode* aTo, dom::DOMQuad& aQuad,
const GeometryNode& aFrom,
const dom::ConvertCoordinateOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<dom::DOMQuad>
ConvertRectFromNode(nsINode* aTo, dom::DOMRectReadOnly& aRect,
const GeometryNode& aFrom,
const dom::ConvertCoordinateOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<dom::DOMPoint>
ConvertPointFromNode(nsINode* aTo, const dom::DOMPointInit& aPoint,
const GeometryNode& aFrom,
const dom::ConvertCoordinateOptions& aOptions,
ErrorResult& aRv);
} // namespace mozilla
#endif /* MOZILLA_GEOMETRYUTILS_H_ */

26
layout/base/LayerState.h Normal file
View file

@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#ifndef LAYERSTATE_H_
#define LAYERSTATE_H_
namespace mozilla {
enum LayerState {
LAYER_NONE,
LAYER_INACTIVE,
LAYER_ACTIVE,
// Force an active layer even if it causes incorrect rendering, e.g.
// when the layer has rounded rect clips.
LAYER_ACTIVE_FORCE,
// Special layer that is metadata only.
LAYER_ACTIVE_EMPTY,
// Inactive style layer for rendering SVG effects.
LAYER_SVG_EFFECTS
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,33 @@
/* -*- Mode: C++; 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/. */
// Chromium headers must come before Mozilla headers.
#include "base/process_util.h"
#include "LayoutLogging.h"
namespace mozilla {
namespace detail {
void LayoutLogWarning(const char* aStr, const char* aExpr,
const char* aFile, int32_t aLine)
{
if (aExpr) {
MOZ_LOG(sLayoutLog,
mozilla::LogLevel::Warning,
("[%d] WARNING: %s: '%s', file %s, line %d",
base::GetCurrentProcId(),
aStr, aExpr, aFile, aLine));
} else {
MOZ_LOG(sLayoutLog,
mozilla::LogLevel::Warning,
("[%d] WARNING: %s: file %s, line %d",
base::GetCurrentProcId(),
aStr, aFile, aLine));
}
}
} // namespace detail
} // namespace mozilla

View file

@ -0,0 +1,64 @@
/* -*- Mode: C++; 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/. */
#ifndef LayoutLogging_h
#define LayoutLogging_h
#include "mozilla/Logging.h"
/**
* Retrieves the log module to use for layout logging.
*/
static mozilla::LazyLogModule sLayoutLog("layout");
/**
* Use the layout log to warn if a given condition is false.
*
* This is only enabled in debug builds and the logging is only displayed if
* the environmental variable MOZ_LOG includes "layout:2" (or higher).
*/
#ifdef DEBUG
#define LAYOUT_WARN_IF_FALSE(_cond, _msg) \
PR_BEGIN_MACRO \
if (MOZ_LOG_TEST(sLayoutLog, mozilla::LogLevel::Warning) && \
!(_cond)) { \
mozilla::detail::LayoutLogWarning(_msg, #_cond, __FILE__, __LINE__); \
} \
PR_END_MACRO
#else
#define LAYOUT_WARN_IF_FALSE(_cond, _msg) \
PR_BEGIN_MACRO \
PR_END_MACRO
#endif
/**
* Use the layout log to emit a warning with the same format as NS_WARNING.
*
* This is only enabled in debug builds and the logging is only displayed if
* the environmental variable MOZ_LOG includes "layout:2" (or higher).
*/
#ifdef DEBUG
#define LAYOUT_WARNING(_msg) \
PR_BEGIN_MACRO \
if (MOZ_LOG_TEST(sLayoutLog, mozilla::LogLevel::Warning)) { \
mozilla::detail::LayoutLogWarning(_msg, nullptr, __FILE__, __LINE__); \
} \
PR_END_MACRO
#else
#define LAYOUT_WARNING(_msg) \
PR_BEGIN_MACRO \
PR_END_MACRO
#endif
namespace mozilla {
namespace detail {
void LayoutLogWarning(const char* aStr, const char* aExpr,
const char* aFile, int32_t aLine);
} // namespace detail
} // namespace mozilla
#endif // LayoutLogging_h

View file

@ -0,0 +1,70 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#include "MaskLayerImageCache.h"
#include "ImageContainer.h"
using namespace mozilla::layers;
namespace mozilla {
MaskLayerImageCache::MaskLayerImageCache()
{
MOZ_COUNT_CTOR(MaskLayerImageCache);
}
MaskLayerImageCache::~MaskLayerImageCache()
{
MOZ_COUNT_DTOR(MaskLayerImageCache);
}
void
MaskLayerImageCache::Sweep()
{
for (auto iter = mMaskImageContainers.Iter(); !iter.Done(); iter.Next()) {
const MaskLayerImageCache::MaskLayerImageKey* key = iter.Get()->mKey;
if (key->HasZeroLayerCount()) {
iter.Remove();
}
}
}
ImageContainer*
MaskLayerImageCache::FindImageFor(const MaskLayerImageKey** aKey)
{
if (MaskLayerImageEntry* entry = mMaskImageContainers.GetEntry(**aKey)) {
*aKey = entry->mKey.get();
return entry->mContainer;
}
return nullptr;
}
void
MaskLayerImageCache::PutImage(const MaskLayerImageKey* aKey, ImageContainer* aContainer)
{
MaskLayerImageEntry* entry = mMaskImageContainers.PutEntry(*aKey);
entry->mContainer = aContainer;
}
MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey()
: mRoundedClipRects()
, mLayerCount(0)
{
MOZ_COUNT_CTOR(MaskLayerImageKey);
}
MaskLayerImageCache::MaskLayerImageKey::MaskLayerImageKey(const MaskLayerImageKey& aKey)
: mRoundedClipRects(aKey.mRoundedClipRects)
, mLayerCount(aKey.mLayerCount)
{
MOZ_COUNT_CTOR(MaskLayerImageKey);
}
MaskLayerImageCache::MaskLayerImageKey::~MaskLayerImageKey()
{
MOZ_COUNT_DTOR(MaskLayerImageKey);
}
} // namespace mozilla

View file

@ -0,0 +1,289 @@
/* -*- Mode: C++; tab-width: 20; 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/. */
#ifndef MASKLAYERIMAGECACHE_H_
#define MASKLAYERIMAGECACHE_H_
#include "DisplayItemClip.h"
#include "nsAutoPtr.h"
#include "nsPresContext.h"
#include "mozilla/gfx/Matrix.h"
namespace mozilla {
namespace layers {
class ImageContainer;
class ShadowLayerForwarder;
} // namespace layers
/**
* Keeps a record of image containers for mask layers, containers are mapped
* from the rounded rects used to create them.
* The cache stores MaskLayerImageEntries indexed by MaskLayerImageKeys.
* Each MaskLayerImageEntry owns a heap-allocated MaskLayerImageKey
* (heap-allocated so that a mask layer's userdata can keep a pointer to the
* key for its image, in spite of the hashtable moving its entries around).
* The key consists of the rounded rects used to create the mask,
* an nsRefPtr to the ImageContainer containing the image, and a count
* of the number of layers currently using this ImageContainer.
* When the key's layer count is zero, the cache
* may remove the entry, which deletes the key object.
*/
class MaskLayerImageCache
{
typedef mozilla::layers::ImageContainer ImageContainer;
typedef mozilla::layers::ShadowLayerForwarder ShadowLayerForwarder;
public:
MaskLayerImageCache();
~MaskLayerImageCache();
/**
* Representation of a rounded rectangle in device pixel coordinates, in
* contrast to DisplayItemClip::RoundedRect, which uses app units.
* In particular, our internal representation uses a gfxRect, rather than
* an nsRect, so this class is easier to use with transforms.
*/
struct PixelRoundedRect
{
PixelRoundedRect(const DisplayItemClip::RoundedRect& aRRect,
nsPresContext* aPresContext)
: mRect(aPresContext->AppUnitsToGfxUnits(aRRect.mRect.x),
aPresContext->AppUnitsToGfxUnits(aRRect.mRect.y),
aPresContext->AppUnitsToGfxUnits(aRRect.mRect.width),
aPresContext->AppUnitsToGfxUnits(aRRect.mRect.height))
{
MOZ_COUNT_CTOR(PixelRoundedRect);
NS_FOR_CSS_HALF_CORNERS(corner) {
mRadii[corner] = aPresContext->AppUnitsToGfxUnits(aRRect.mRadii[corner]);
}
}
PixelRoundedRect(const PixelRoundedRect& aPRR)
: mRect(aPRR.mRect)
{
MOZ_COUNT_CTOR(PixelRoundedRect);
NS_FOR_CSS_HALF_CORNERS(corner) {
mRadii[corner] = aPRR.mRadii[corner];
}
}
~PixelRoundedRect()
{
MOZ_COUNT_DTOR(PixelRoundedRect);
}
// Applies the scale and translate components of aTransform.
// It is an error to pass a matrix which does more than just scale
// and translate.
void ScaleAndTranslate(const gfx::Matrix& aTransform)
{
NS_ASSERTION(aTransform._12 == 0 && aTransform._21 == 0,
"Transform has a component other than scale and translate");
mRect = aTransform.TransformBounds(mRect);
for (size_t i = 0; i < ArrayLength(mRadii); i += 2) {
mRadii[i] *= aTransform._11;
mRadii[i + 1] *= aTransform._22;
}
}
bool operator==(const PixelRoundedRect& aOther) const {
if (!mRect.IsEqualInterior(aOther.mRect)) {
return false;
}
NS_FOR_CSS_HALF_CORNERS(corner) {
if (mRadii[corner] != aOther.mRadii[corner]) {
return false;
}
}
return true;
}
bool operator!=(const PixelRoundedRect& aOther) const {
return !(*this == aOther);
}
// Create a hash for this object.
PLDHashNumber Hash() const
{
PLDHashNumber hash = HashBytes(&mRect.x, 4*sizeof(gfxFloat));
hash = AddToHash(hash, HashBytes(mRadii, 8*sizeof(gfxFloat)));
return hash;
}
gfx::Rect mRect;
// Indices into mRadii are the NS_CORNER_* constants in nsStyleConsts.h
gfxFloat mRadii[8];
private:
PixelRoundedRect() = delete;
};
struct MaskLayerImageKeyRef;
/**
* A key to identify cached image containers.
* The const-ness of this class is with respect to its use as a key into a
* hashtable, so anything not used to create the hash is mutable.
* mLayerCount counts the number of mask layers which have a reference to
* MaskLayerImageEntry::mContainer; it is maintained by MaskLayerUserData,
* which keeps a reference to the key. There will usually be mLayerCount + 1
* pointers to a key object (the +1 being from the hashtable entry), but this
* invariant may be temporarily broken.
*/
struct MaskLayerImageKey
{
friend struct MaskLayerImageKeyRef;
MaskLayerImageKey();
MaskLayerImageKey(const MaskLayerImageKey& aKey);
~MaskLayerImageKey();
bool HasZeroLayerCount() const {
return mLayerCount == 0;
}
PLDHashNumber Hash() const
{
PLDHashNumber hash = 0;
for (uint32_t i = 0; i < mRoundedClipRects.Length(); ++i) {
hash = AddToHash(hash, mRoundedClipRects[i].Hash());
}
hash = AddToHash(hash, mForwarder.get());
return hash;
}
bool operator==(const MaskLayerImageKey& aOther) const
{
return mForwarder == aOther.mForwarder &&
mRoundedClipRects == aOther.mRoundedClipRects;
}
nsTArray<PixelRoundedRect> mRoundedClipRects;
RefPtr<ShadowLayerForwarder> mForwarder;
private:
void IncLayerCount() const { ++mLayerCount; }
void DecLayerCount() const
{
NS_ASSERTION(mLayerCount > 0, "Inconsistent layer count");
--mLayerCount;
}
mutable uint32_t mLayerCount;
};
/**
* This struct maintains a reference to a MaskLayerImageKey, via a variant on
* refcounting. When a key is passed in via Reset(), we increment the
* passed-in key's mLayerCount, and we decrement its mLayerCount when we're
* destructed (or when the key is replaced via a second Reset() call).
*
* However, unlike standard refcounting smart-pointers, this object does
* *not* delete the tracked MaskLayerImageKey -- instead, deletion happens
* in MaskLayerImageCache::Sweep(), for any keys whose mLayerCount is 0.
*/
struct MaskLayerImageKeyRef
{
~MaskLayerImageKeyRef()
{
if (mRawPtr) {
mRawPtr->DecLayerCount();
}
}
MaskLayerImageKeyRef() : mRawPtr(nullptr) {}
MaskLayerImageKeyRef(const MaskLayerImageKeyRef&) = delete;
void operator=(const MaskLayerImageKeyRef&) = delete;
void Reset(const MaskLayerImageKey* aPtr)
{
MOZ_ASSERT(aPtr, "Cannot initialize a MaskLayerImageKeyRef with a null pointer");
aPtr->IncLayerCount();
if (mRawPtr) {
mRawPtr->DecLayerCount();
}
mRawPtr = aPtr;
}
private:
const MaskLayerImageKey* mRawPtr;
};
// Find an image container for aKey, returns nullptr if there is no suitable
// cached image. If there is an image, then aKey is set to point at the stored
// key for the image.
ImageContainer* FindImageFor(const MaskLayerImageKey** aKey);
// Add an image container with a key to the cache
// The image container used will be set as the container in aKey and aKey
// itself will be linked from this cache
void PutImage(const MaskLayerImageKey* aKey,
ImageContainer* aContainer);
// Sweep the cache for old image containers that can be deleted
void Sweep();
protected:
class MaskLayerImageEntry : public PLDHashEntryHdr
{
public:
typedef const MaskLayerImageKey& KeyType;
typedef const MaskLayerImageKey* KeyTypePointer;
explicit MaskLayerImageEntry(KeyTypePointer aKey)
: mKey(aKey)
{
MOZ_COUNT_CTOR(MaskLayerImageEntry);
}
MaskLayerImageEntry(const MaskLayerImageEntry& aOther)
: mKey(aOther.mKey.get())
{
NS_ERROR("ALLOW_MEMMOVE == true, should never be called");
}
~MaskLayerImageEntry()
{
MOZ_COUNT_DTOR(MaskLayerImageEntry);
}
// KeyEquals(): does this entry match this key?
bool KeyEquals(KeyTypePointer aKey) const
{
return *mKey == *aKey;
}
// KeyToPointer(): Convert KeyType to KeyTypePointer
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
// HashKey(): calculate the hash number
static PLDHashNumber HashKey(KeyTypePointer aKey)
{
return aKey->Hash();
}
// ALLOW_MEMMOVE can we move this class with memmove(), or do we have
// to use the copy constructor?
enum { ALLOW_MEMMOVE = true };
bool operator==(const MaskLayerImageEntry& aOther) const
{
return KeyEquals(aOther.mKey);
}
nsAutoPtr<const MaskLayerImageKey> mKey;
RefPtr<ImageContainer> mContainer;
};
nsTHashtable<MaskLayerImageEntry> mMaskImageContainers;
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,415 @@
/* -*- Mode: C++; 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/. */
#include "MobileViewportManager.h"
#include "gfxPrefs.h"
#include "LayersLogging.h"
#include "nsIDOMEvent.h"
#include "nsIFrame.h"
#include "nsLayoutUtils.h"
#include "nsPresShell.h"
#include "nsViewManager.h"
#include "nsViewportInfo.h"
#include "UnitTransforms.h"
#include "nsIDocument.h"
#define MVM_LOG(...)
// #define MVM_LOG(...) printf_stderr("MVM: " __VA_ARGS__)
NS_IMPL_ISUPPORTS(MobileViewportManager, nsIDOMEventListener, nsIObserver)
static const nsLiteralString DOM_META_ADDED = NS_LITERAL_STRING("DOMMetaAdded");
static const nsLiteralString DOM_META_CHANGED = NS_LITERAL_STRING("DOMMetaChanged");
static const nsLiteralString FULL_ZOOM_CHANGE = NS_LITERAL_STRING("FullZoomChange");
static const nsLiteralString LOAD = NS_LITERAL_STRING("load");
static const nsLiteralCString BEFORE_FIRST_PAINT = NS_LITERAL_CSTRING("before-first-paint");
using namespace mozilla;
using namespace mozilla::layers;
MobileViewportManager::MobileViewportManager(nsIPresShell* aPresShell,
nsIDocument* aDocument)
: mDocument(aDocument)
, mPresShell(aPresShell)
, mIsFirstPaint(false)
, mPainted(false)
{
MOZ_ASSERT(mPresShell);
MOZ_ASSERT(mDocument);
MVM_LOG("%p: creating with presShell %p document %p\n", this, mPresShell, aDocument);
if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
mEventTarget = window->GetChromeEventHandler();
}
if (mEventTarget) {
mEventTarget->AddEventListener(DOM_META_ADDED, this, false);
mEventTarget->AddEventListener(DOM_META_CHANGED, this, false);
mEventTarget->AddEventListener(FULL_ZOOM_CHANGE, this, false);
mEventTarget->AddEventListener(LOAD, this, true);
}
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
if (observerService) {
observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false);
}
}
MobileViewportManager::~MobileViewportManager()
{
}
void
MobileViewportManager::Destroy()
{
MVM_LOG("%p: destroying\n", this);
if (mEventTarget) {
mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false);
mEventTarget->RemoveEventListener(DOM_META_CHANGED, this, false);
mEventTarget->RemoveEventListener(FULL_ZOOM_CHANGE, this, false);
mEventTarget->RemoveEventListener(LOAD, this, true);
mEventTarget = nullptr;
}
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
if (observerService) {
observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
}
mDocument = nullptr;
mPresShell = nullptr;
}
void
MobileViewportManager::SetRestoreResolution(float aResolution,
LayoutDeviceIntSize aDisplaySize)
{
mRestoreResolution = Some(aResolution);
ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>(aDisplaySize,
PixelCastJustification::LayoutDeviceIsScreenForBounds);
mRestoreDisplaySize = Some(restoreDisplaySize);
}
void
MobileViewportManager::RequestReflow()
{
MVM_LOG("%p: got a reflow request\n", this);
RefreshViewportSize(false);
}
void
MobileViewportManager::ResolutionUpdated()
{
MVM_LOG("%p: resolution updated\n", this);
RefreshSPCSPS();
}
NS_IMETHODIMP
MobileViewportManager::HandleEvent(nsIDOMEvent* event)
{
nsAutoString type;
event->GetType(type);
if (type.Equals(DOM_META_ADDED)) {
MVM_LOG("%p: got a dom-meta-added event\n", this);
RefreshViewportSize(mPainted);
} else if (type.Equals(DOM_META_CHANGED)) {
MVM_LOG("%p: got a dom-meta-changed event\n", this);
RefreshViewportSize(mPainted);
} else if (type.Equals(FULL_ZOOM_CHANGE)) {
MVM_LOG("%p: got a full-zoom-change event\n", this);
RefreshViewportSize(false);
} else if (type.Equals(LOAD)) {
MVM_LOG("%p: got a load event\n", this);
if (!mPainted) {
// Load event got fired before the before-first-paint message
SetInitialViewport();
}
}
return NS_OK;
}
NS_IMETHODIMP
MobileViewportManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
{
if (SameCOMIdentity(aSubject, mDocument) && BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) {
MVM_LOG("%p: got a before-first-paint event\n", this);
if (!mPainted) {
// before-first-paint message arrived before load event
SetInitialViewport();
}
}
return NS_OK;
}
void
MobileViewportManager::SetInitialViewport()
{
MVM_LOG("%p: setting initial viewport\n", this);
mIsFirstPaint = true;
mPainted = true;
RefreshViewportSize(false);
}
CSSToScreenScale
MobileViewportManager::ClampZoom(const CSSToScreenScale& aZoom,
const nsViewportInfo& aViewportInfo)
{
CSSToScreenScale zoom = aZoom;
if (zoom < aViewportInfo.GetMinZoom()) {
zoom = aViewportInfo.GetMinZoom();
MVM_LOG("%p: Clamped to %f\n", this, zoom.scale);
}
if (zoom > aViewportInfo.GetMaxZoom()) {
zoom = aViewportInfo.GetMaxZoom();
MVM_LOG("%p: Clamped to %f\n", this, zoom.scale);
}
return zoom;
}
LayoutDeviceToLayerScale
MobileViewportManager::ScaleResolutionWithDisplayWidth(const LayoutDeviceToLayerScale& aRes,
const float& aDisplayWidthChangeRatio,
const CSSSize& aNewViewport,
const CSSSize& aOldViewport)
{
float cssViewportChangeRatio = (aOldViewport.width == 0)
? 1.0f : aNewViewport.width / aOldViewport.width;
LayoutDeviceToLayerScale newRes(aRes.scale * aDisplayWidthChangeRatio
/ cssViewportChangeRatio);
MVM_LOG("%p: Old resolution was %f, changed by %f/%f to %f\n", this, aRes.scale,
aDisplayWidthChangeRatio, cssViewportChangeRatio, newRes.scale);
return newRes;
}
CSSToScreenScale
MobileViewportManager::UpdateResolution(const nsViewportInfo& aViewportInfo,
const ScreenIntSize& aDisplaySize,
const CSSSize& aViewport,
const Maybe<float>& aDisplayWidthChangeRatio)
{
CSSToLayoutDeviceScale cssToDev =
mPresShell->GetPresContext()->CSSToDevPixelScale();
LayoutDeviceToLayerScale res(mPresShell->GetResolution());
if (mIsFirstPaint) {
CSSToScreenScale defaultZoom;
if (mRestoreResolution) {
LayoutDeviceToLayerScale restoreResolution(mRestoreResolution.value());
if (mRestoreDisplaySize) {
CSSSize prevViewport = mDocument->GetViewportInfo(mRestoreDisplaySize.value()).GetSize();
float restoreDisplayWidthChangeRatio = (mRestoreDisplaySize.value().width > 0)
? (float)aDisplaySize.width / (float)mRestoreDisplaySize.value().width : 1.0f;
restoreResolution =
ScaleResolutionWithDisplayWidth(restoreResolution,
restoreDisplayWidthChangeRatio,
aViewport,
prevViewport);
}
defaultZoom = CSSToScreenScale(restoreResolution.scale * cssToDev.scale);
MVM_LOG("%p: restored zoom is %f\n", this, defaultZoom.scale);
defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
} else {
defaultZoom = aViewportInfo.GetDefaultZoom();
MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale);
if (!aViewportInfo.IsDefaultZoomValid()) {
defaultZoom = MaxScaleRatio(ScreenSize(aDisplaySize), aViewport);
MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, defaultZoom.scale);
defaultZoom = ClampZoom(defaultZoom, aViewportInfo);
}
}
MOZ_ASSERT(aViewportInfo.GetMinZoom() <= defaultZoom &&
defaultZoom <= aViewportInfo.GetMaxZoom());
CSSToParentLayerScale zoom = ViewTargetAs<ParentLayerPixel>(defaultZoom,
PixelCastJustification::ScreenIsParentLayerForRoot);
LayoutDeviceToLayerScale resolution = zoom / cssToDev * ParentLayerToLayerScale(1);
MVM_LOG("%p: setting resolution %f\n", this, resolution.scale);
mPresShell->SetResolutionAndScaleTo(resolution.scale);
return defaultZoom;
}
// If this is not a first paint, then in some cases we want to update the pre-
// existing resolution so as to maintain how much actual content is visible
// within the display width. Note that "actual content" may be different with
// respect to CSS pixels because of the CSS viewport size changing.
//
// aDisplayWidthChangeRatio is non-empty if:
// (a) The meta-viewport tag information changes, and so the CSS viewport
// might change as a result. If this happens after the content has been
// painted, we want to adjust the zoom to compensate. OR
// (b) The display size changed from a nonzero value to another nonzero value.
// This covers the case where e.g. the device was rotated, and again we
// want to adjust the zoom to compensate.
// Note in particular that aDisplayWidthChangeRatio will be None if all that
// happened was a change in the full-zoom. In this case, we still want to
// compute a new CSS viewport, but we don't want to update the resolution.
//
// Given the above, the algorithm below accounts for all types of changes I
// can conceive of:
// 1. screen size changes, CSS viewport does not (pages with no meta viewport
// or a fixed size viewport)
// 2. screen size changes, CSS viewport also does (pages with a device-width
// viewport)
// 3. screen size remains constant, but CSS viewport changes (meta viewport
// tag is added or removed)
// 4. neither screen size nor CSS viewport changes
if (aDisplayWidthChangeRatio) {
res = ScaleResolutionWithDisplayWidth(res, aDisplayWidthChangeRatio.value(),
aViewport, mMobileViewportSize);
mPresShell->SetResolutionAndScaleTo(res.scale);
}
return ViewTargetAs<ScreenPixel>(cssToDev * res / ParentLayerToLayerScale(1),
PixelCastJustification::ScreenIsParentLayerForRoot);
}
void
MobileViewportManager::UpdateSPCSPS(const ScreenIntSize& aDisplaySize,
const CSSToScreenScale& aZoom)
{
ScreenSize compositionSize(aDisplaySize);
ScreenMargin scrollbars =
LayoutDeviceMargin::FromAppUnits(
nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(
mPresShell->GetRootScrollFrame()),
mPresShell->GetPresContext()->AppUnitsPerDevPixel())
// Scrollbars are not subject to resolution scaling, so LD pixels =
// Screen pixels for them.
* LayoutDeviceToScreenScale(1.0f);
compositionSize.width -= scrollbars.LeftRight();
compositionSize.height -= scrollbars.TopBottom();
CSSSize compSize = compositionSize / aZoom;
MVM_LOG("%p: Setting SPCSPS %s\n", this, Stringify(compSize).c_str());
nsLayoutUtils::SetScrollPositionClampingScrollPortSize(mPresShell, compSize);
}
void
MobileViewportManager::UpdateDisplayPortMargins()
{
if (nsIFrame* root = mPresShell->GetRootScrollFrame()) {
bool hasDisplayPort = nsLayoutUtils::HasDisplayPort(root->GetContent());
bool hasResolution = mPresShell->ScaleToResolution() &&
mPresShell->GetResolution() != 1.0f;
if (!hasDisplayPort && !hasResolution) {
// We only want to update the displayport if there is one already, or
// add one if there's a resolution on the document (see bug 1225508
// comment 1).
return;
}
nsIScrollableFrame* scrollable = do_QueryFrame(root);
nsLayoutUtils::CalculateAndSetDisplayPortMargins(scrollable,
nsLayoutUtils::RepaintMode::DoNotRepaint);
}
}
void
MobileViewportManager::RefreshSPCSPS()
{
// This function is a subset of RefreshViewportSize, and only updates the
// SPCSPS.
if (!gfxPrefs::APZAllowZooming()) {
return;
}
ScreenIntSize displaySize = ViewAs<ScreenPixel>(
mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
CSSToLayoutDeviceScale cssToDev =
mPresShell->GetPresContext()->CSSToDevPixelScale();
LayoutDeviceToLayerScale res(mPresShell->GetResolution());
CSSToScreenScale zoom = ViewTargetAs<ScreenPixel>(cssToDev * res / ParentLayerToLayerScale(1),
PixelCastJustification::ScreenIsParentLayerForRoot);
UpdateSPCSPS(displaySize, zoom);
}
void
MobileViewportManager::RefreshViewportSize(bool aForceAdjustResolution)
{
// This function gets called by the various triggers that may result in a
// change of the CSS viewport. In some of these cases (e.g. the meta-viewport
// tag changes) we want to update the resolution and in others (e.g. the full
// zoom changing) we don't want to update the resolution. See the comment in
// UpdateResolution for some more detail on this. An important assumption we
// make here is that this RefreshViewportSize function will be called
// separately for each trigger that changes. For instance it should never get
// called such that both the full zoom and the meta-viewport tag have changed;
// instead it would get called twice - once after each trigger changes. This
// assumption is what allows the aForceAdjustResolution parameter to work as
// intended; if this assumption is violated then we will need to add extra
// complicated logic in UpdateResolution to ensure we only do the resolution
// update in the right scenarios.
Maybe<float> displayWidthChangeRatio;
LayoutDeviceIntSize newDisplaySize;
if (nsLayoutUtils::GetContentViewerSize(mPresShell->GetPresContext(), newDisplaySize)) {
// See the comment in UpdateResolution for why we're doing this.
if (mDisplaySize.width > 0) {
if (aForceAdjustResolution || mDisplaySize.width != newDisplaySize.width) {
displayWidthChangeRatio = Some((float)newDisplaySize.width / (float)mDisplaySize.width);
}
} else if (aForceAdjustResolution) {
displayWidthChangeRatio = Some(1.0f);
}
MVM_LOG("%p: Display width change ratio is %f\n", this, displayWidthChangeRatio.valueOr(0.0f));
mDisplaySize = newDisplaySize;
}
MVM_LOG("%p: Computing CSS viewport using %d,%d\n", this,
mDisplaySize.width, mDisplaySize.height);
if (mDisplaySize.width == 0 || mDisplaySize.height == 0) {
// We can't do anything useful here, we should just bail out
return;
}
ScreenIntSize displaySize = ViewAs<ScreenPixel>(
mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
nsViewportInfo viewportInfo = mDocument->GetViewportInfo(displaySize);
CSSSize viewport = viewportInfo.GetSize();
MVM_LOG("%p: Computed CSS viewport %s\n", this, Stringify(viewport).c_str());
if (!mIsFirstPaint && mMobileViewportSize == viewport) {
// Nothing changed, so no need to do a reflow
return;
}
// If it's the first-paint or the viewport changed, we need to update
// various APZ properties (the zoom and some things that might depend on it)
MVM_LOG("%p: Updating properties because %d || %d\n", this,
mIsFirstPaint, mMobileViewportSize != viewport);
if (gfxPrefs::APZAllowZooming()) {
CSSToScreenScale zoom = UpdateResolution(viewportInfo, displaySize, viewport,
displayWidthChangeRatio);
MVM_LOG("%p: New zoom is %f\n", this, zoom.scale);
UpdateSPCSPS(displaySize, zoom);
}
if (gfxPlatform::AsyncPanZoomEnabled()) {
UpdateDisplayPortMargins();
}
CSSSize oldSize = mMobileViewportSize;
// Update internal state.
mIsFirstPaint = false;
mMobileViewportSize = viewport;
// Kick off a reflow.
mPresShell->ResizeReflowIgnoreOverride(
nsPresContext::CSSPixelsToAppUnits(viewport.width),
nsPresContext::CSSPixelsToAppUnits(viewport.height),
nsPresContext::CSSPixelsToAppUnits(oldSize.width),
nsPresContext::CSSPixelsToAppUnits(oldSize.height));
}

View file

@ -0,0 +1,97 @@
/* -*- Mode: C++; 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/. */
#ifndef MobileViewportManager_h_
#define MobileViewportManager_h_
#include "mozilla/Maybe.h"
#include "nsIDOMEventListener.h"
#include "nsIObserver.h"
#include "Units.h"
class nsIDOMEventTarget;
class nsIDocument;
class nsIPresShell;
class MobileViewportManager final : public nsIDOMEventListener
, public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
NS_DECL_NSIOBSERVER
MobileViewportManager(nsIPresShell* aPresShell,
nsIDocument* aDocument);
void Destroy();
/* Provide a resolution to use during the first paint instead of the default
* resolution computed from the viewport info metadata. This is in the same
* "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo.
* Also takes the previous display dimensions as they were at the time the
* resolution was stored in order to correctly adjust the resolution if the
* device was rotated in the meantime. */
void SetRestoreResolution(float aResolution,
mozilla::LayoutDeviceIntSize aDisplaySize);
/* Notify the MobileViewportManager that a reflow was requested in the
* presShell.*/
void RequestReflow();
/* Notify the MobileViewportManager that the resolution on the presShell was
* updated, and the SPCSPS needs to be updated. */
void ResolutionUpdated();
private:
~MobileViewportManager();
/* Called to compute the initial viewport on page load or before-first-paint,
* whichever happens first. */
void SetInitialViewport();
/* Main helper method to update the CSS viewport and any other properties that
* need updating. */
void RefreshViewportSize(bool aForceAdjustResolution);
/* Secondary main helper method to update just the SPCSPS. */
void RefreshSPCSPS();
/* Helper to clamp the given zoom by the min/max in the viewport info. */
mozilla::CSSToScreenScale ClampZoom(const mozilla::CSSToScreenScale& aZoom,
const nsViewportInfo& aViewportInfo);
/* Helper to update the given resolution according to changed display and viewport widths. */
mozilla::LayoutDeviceToLayerScale
ScaleResolutionWithDisplayWidth(const mozilla::LayoutDeviceToLayerScale& aRes,
const float& aDisplayWidthChangeRatio,
const mozilla::CSSSize& aNewViewport,
const mozilla::CSSSize& aOldViewport);
/* Updates the presShell resolution and returns the new zoom. */
mozilla::CSSToScreenScale UpdateResolution(const nsViewportInfo& aViewportInfo,
const mozilla::ScreenIntSize& aDisplaySize,
const mozilla::CSSSize& aViewport,
const mozilla::Maybe<float>& aDisplayWidthChangeRatio);
/* Updates the scroll-position-clamping scrollport size */
void UpdateSPCSPS(const mozilla::ScreenIntSize& aDisplaySize,
const mozilla::CSSToScreenScale& aZoom);
/* Updates the displayport margins for the presShell's root scrollable frame */
void UpdateDisplayPortMargins();
nsCOMPtr<nsIDocument> mDocument;
nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
nsCOMPtr<nsIDOMEventTarget> mEventTarget;
bool mIsFirstPaint;
bool mPainted;
mozilla::LayoutDeviceIntSize mDisplaySize;
mozilla::CSSSize mMobileViewportSize;
mozilla::Maybe<float> mRestoreResolution;
mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize;
};
#endif

View file

@ -0,0 +1,215 @@
/* -*- Mode: C++; 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/. */
#ifndef mozilla_OverflowChangedTracker_h
#define mozilla_OverflowChangedTracker_h
#include "nsIFrame.h"
#include "nsContainerFrame.h"
#include "mozilla/SplayTree.h"
namespace mozilla {
/**
* Helper class that collects a list of frames that need
* UpdateOverflow() called on them, and coalesces them
* to avoid walking up the same ancestor tree multiple times.
*/
class OverflowChangedTracker
{
public:
enum ChangeKind {
/**
* The frame was explicitly added as a result of
* nsChangeHint_UpdatePostTransformOverflow and hence may have had a style
* change that changes its geometry relative to parent, without reflowing.
*/
TRANSFORM_CHANGED,
/**
* The overflow areas of children have changed
* and we need to call UpdateOverflow on the frame.
*/
CHILDREN_CHANGED,
};
OverflowChangedTracker() :
mSubtreeRoot(nullptr)
{}
~OverflowChangedTracker()
{
NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!");
}
/**
* Add a frame that has had a style change, and needs its
* overflow updated.
*
* If there are pre-transform overflow areas stored for this
* frame, then we will call FinishAndStoreOverflow with those
* areas instead of UpdateOverflow().
*
* If the overflow area changes, then UpdateOverflow will also
* be called on the parent.
*/
void AddFrame(nsIFrame* aFrame, ChangeKind aChangeKind) {
uint32_t depth = aFrame->GetDepthInFrameTree();
Entry *entry = nullptr;
if (!mEntryList.empty()) {
entry = mEntryList.find(Entry(aFrame, depth));
}
if (entry == nullptr) {
// Add new entry.
mEntryList.insert(new Entry(aFrame, depth, aChangeKind));
} else {
// Update the existing entry if the new value is stronger.
entry->mChangeKind = std::max(entry->mChangeKind, aChangeKind);
}
}
/**
* Remove a frame.
*/
void RemoveFrame(nsIFrame* aFrame) {
if (mEntryList.empty()) {
return;
}
uint32_t depth = aFrame->GetDepthInFrameTree();
if (mEntryList.find(Entry(aFrame, depth))) {
delete mEntryList.remove(Entry(aFrame, depth));
}
}
/**
* Set the subtree root to limit overflow updates. This must be set if and
* only if currently reflowing aSubtreeRoot, to ensure overflow changes will
* still propagate correctly.
*/
void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) {
mSubtreeRoot = aSubtreeRoot;
}
/**
* Update the overflow of all added frames, and clear the entry list.
*
* Start from those deepest in the frame tree and works upwards. This stops
* us from processing the same frame twice.
*/
void Flush() {
while (!mEntryList.empty()) {
Entry *entry = mEntryList.removeMin();
nsIFrame *frame = entry->mFrame;
bool overflowChanged = false;
if (entry->mChangeKind == CHILDREN_CHANGED) {
// Need to union the overflow areas of the children.
// Only update the parent if the overflow changes.
overflowChanged = frame->UpdateOverflow();
} else {
// Take a faster path that doesn't require unioning the overflow areas
// of our children.
NS_ASSERTION(frame->Properties().Get(
nsIFrame::DebugInitialOverflowPropertyApplied()),
"InitialOverflowProperty must be set first.");
nsOverflowAreas* overflow =
frame->Properties().Get(nsIFrame::InitialOverflowProperty());
if (overflow) {
// FinishAndStoreOverflow will change the overflow areas passed in,
// so make a copy.
nsOverflowAreas overflowCopy = *overflow;
frame->FinishAndStoreOverflow(overflowCopy, frame->GetSize());
} else {
nsRect bounds(nsPoint(0, 0), frame->GetSize());
nsOverflowAreas boundsOverflow;
boundsOverflow.SetAllTo(bounds);
frame->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
}
// We can't tell if the overflow changed, so be conservative
overflowChanged = true;
}
// If the frame style changed (e.g. positioning offsets)
// then we need to update the parent with the overflow areas of its
// children.
if (overflowChanged) {
nsIFrame *parent = frame->GetParent();
while (parent &&
parent != mSubtreeRoot &&
parent->Combines3DTransformWithAncestors()) {
// Passing frames in between the frame and the establisher of
// 3D rendering context.
parent = parent->GetParent();
MOZ_ASSERT(parent, "Root frame should never return true for Combines3DTransformWithAncestors");
}
if (parent && parent != mSubtreeRoot) {
Entry* parentEntry = mEntryList.find(Entry(parent, entry->mDepth - 1));
if (parentEntry) {
parentEntry->mChangeKind = std::max(parentEntry->mChangeKind, CHILDREN_CHANGED);
} else {
mEntryList.insert(new Entry(parent, entry->mDepth - 1, CHILDREN_CHANGED));
}
}
}
delete entry;
}
}
private:
struct Entry : SplayTreeNode<Entry>
{
Entry(nsIFrame* aFrame, uint32_t aDepth, ChangeKind aChangeKind = CHILDREN_CHANGED)
: mFrame(aFrame)
, mDepth(aDepth)
, mChangeKind(aChangeKind)
{}
bool operator==(const Entry& aOther) const
{
return mFrame == aOther.mFrame;
}
/**
* Sort by *reverse* depth in the tree, and break ties with
* the frame pointer.
*/
bool operator<(const Entry& aOther) const
{
if (mDepth == aOther.mDepth) {
return mFrame < aOther.mFrame;
}
return mDepth > aOther.mDepth; /* reverse, want "min" to be deepest */
}
static int compare(const Entry& aOne, const Entry& aTwo)
{
if (aOne == aTwo) {
return 0;
} else if (aOne < aTwo) {
return -1;
} else {
return 1;
}
}
nsIFrame* mFrame;
/* Depth in the frame tree */
uint32_t mDepth;
ChangeKind mChangeKind;
};
/* A list of frames to process, sorted by their depth in the frame tree */
SplayTree<Entry, Entry> mEntryList;
/* Don't update overflow of this frame or its ancestors. */
const nsIFrame* mSubtreeRoot;
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/PaintTracker.h"
namespace mozilla {
int PaintTracker::gPaintTracker;
} // namespace mozilla

View file

@ -0,0 +1,34 @@
/* 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 mozilla_PaintTracker_h
#define mozilla_PaintTracker_h
#include "mozilla/Attributes.h"
#include "nsDebug.h"
namespace mozilla {
class MOZ_STACK_CLASS PaintTracker
{
public:
PaintTracker() {
++gPaintTracker;
}
~PaintTracker() {
NS_ASSERTION(gPaintTracker > 0, "Mismatched constructor/destructor");
--gPaintTracker;
}
static bool IsPainting() {
return !!gPaintTracker;
}
private:
static int gPaintTracker;
};
} // namespace mozilla
#endif // mozilla_PaintTracker_h

View file

@ -0,0 +1,656 @@
/* 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 "PositionedEventTargeting.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Preferences.h"
#include "nsLayoutUtils.h"
#include "nsGkAtoms.h"
#include "nsFontMetrics.h"
#include "nsPrintfCString.h"
#include "mozilla/dom/Element.h"
#include "nsRegion.h"
#include "nsDeviceContext.h"
#include "nsIFrame.h"
#include <algorithm>
#include "LayersLogging.h"
// If debugging this code you may wish to enable this logging, and also
// uncomment the DumpFrameTree call near the bottom of the file.
#define PET_LOG(...)
// #define PET_LOG(...) printf_stderr("PET: " __VA_ARGS__);
namespace mozilla {
/*
* The basic goal of FindFrameTargetedByInputEvent() is to find a good
* target element that can respond to mouse events. Both mouse events and touch
* events are targeted at this element. Note that even for touch events, we
* check responsiveness to mouse events. We assume Web authors
* designing for touch events will take their own steps to account for
* inaccurate touch events.
*
* GetClickableAncestor() encapsulates the heuristic that determines whether an
* element is expected to respond to mouse events. An element is deemed
* "clickable" if it has registered listeners for "click", "mousedown" or
* "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>,
* <select>, <textarea>, <label>), or has role="button", or is a link, or
* is a suitable XUL element.
* Any descendant (in the same document) of a clickable element is also
* deemed clickable since events will propagate to the clickable element from its
* descendant.
*
* If the element directly under the event position is clickable (or
* event radii are disabled), we always use that element. Otherwise we collect
* all frames intersecting a rectangle around the event position (taking CSS
* transforms into account) and choose the best candidate in GetClosest().
* Only GetClickableAncestor() candidates are considered; if none are found,
* then we revert to targeting the element under the event position.
* We ignore candidates outside the document subtree rooted by the
* document of the element directly under the event position. This ensures that
* event listeners in ancestor documents don't make it completely impossible
* to target a non-clickable element in a child document.
*
* When both a frame and its ancestor are in the candidate list, we ignore
* the ancestor. Otherwise a large ancestor element with a mouse event listener
* and some descendant elements that need to be individually targetable would
* disable intelligent targeting of those descendants within its bounds.
*
* GetClosest() computes the transformed axis-aligned bounds of each
* candidate frame, then computes the Manhattan distance from the event point
* to the bounds rect (which can be zero). The frame with the
* shortest distance is chosen. For visited links we multiply the distance
* by a specified constant weight; this can be used to make visited links
* more or less likely to be targeted than non-visited links.
*/
struct EventRadiusPrefs
{
uint32_t mVisitedWeight; // in percent, i.e. default is 100
uint32_t mSideRadii[4]; // TRBL order, in millimetres
bool mEnabled;
bool mRegistered;
bool mTouchOnly;
bool mRepositionEventCoords;
bool mTouchClusterDetectionEnabled;
bool mSimplifiedClusterDetection;
uint32_t mLimitReadableSize;
uint32_t mKeepLimitSizeForCluster;
};
static EventRadiusPrefs sMouseEventRadiusPrefs;
static EventRadiusPrefs sTouchEventRadiusPrefs;
static const EventRadiusPrefs*
GetPrefsFor(EventClassID aEventClassID)
{
EventRadiusPrefs* prefs = nullptr;
const char* prefBranch = nullptr;
if (aEventClassID == eTouchEventClass) {
prefBranch = "touch";
prefs = &sTouchEventRadiusPrefs;
} else if (aEventClassID == eMouseEventClass) {
// Mostly for testing purposes
prefBranch = "mouse";
prefs = &sMouseEventRadiusPrefs;
} else {
return nullptr;
}
if (!prefs->mRegistered) {
prefs->mRegistered = true;
nsPrintfCString enabledPref("ui.%s.radius.enabled", prefBranch);
Preferences::AddBoolVarCache(&prefs->mEnabled, enabledPref.get(), false);
nsPrintfCString visitedWeightPref("ui.%s.radius.visitedWeight", prefBranch);
Preferences::AddUintVarCache(&prefs->mVisitedWeight, visitedWeightPref.get(), 100);
static const char prefNames[4][9] =
{ "topmm", "rightmm", "bottommm", "leftmm" };
for (int32_t i = 0; i < 4; ++i) {
nsPrintfCString radiusPref("ui.%s.radius.%s", prefBranch, prefNames[i]);
Preferences::AddUintVarCache(&prefs->mSideRadii[i], radiusPref.get(), 0);
}
if (aEventClassID == eMouseEventClass) {
Preferences::AddBoolVarCache(&prefs->mTouchOnly,
"ui.mouse.radius.inputSource.touchOnly", true);
} else {
prefs->mTouchOnly = false;
}
nsPrintfCString repositionPref("ui.%s.radius.reposition", prefBranch);
Preferences::AddBoolVarCache(&prefs->mRepositionEventCoords, repositionPref.get(), false);
nsPrintfCString touchClusterPref("ui.zoomedview.enabled", prefBranch);
Preferences::AddBoolVarCache(&prefs->mTouchClusterDetectionEnabled, touchClusterPref.get(), false);
nsPrintfCString simplifiedClusterDetectionPref("ui.zoomedview.simplified", prefBranch);
Preferences::AddBoolVarCache(&prefs->mSimplifiedClusterDetection, simplifiedClusterDetectionPref.get(), false);
nsPrintfCString limitReadableSizePref("ui.zoomedview.limitReadableSize", prefBranch);
Preferences::AddUintVarCache(&prefs->mLimitReadableSize, limitReadableSizePref.get(), 8);
nsPrintfCString keepLimitSize("ui.zoomedview.keepLimitSize", prefBranch);
Preferences::AddUintVarCache(&prefs->mKeepLimitSizeForCluster, keepLimitSize.get(), 16);
}
return prefs;
}
static bool
HasMouseListener(nsIContent* aContent)
{
if (EventListenerManager* elm = aContent->GetExistingListenerManager()) {
return elm->HasListenersFor(nsGkAtoms::onclick) ||
elm->HasListenersFor(nsGkAtoms::onmousedown) ||
elm->HasListenersFor(nsGkAtoms::onmouseup);
}
return false;
}
static bool gTouchEventsRegistered = false;
static int32_t gTouchEventsEnabled = 0;
static bool
HasTouchListener(nsIContent* aContent)
{
EventListenerManager* elm = aContent->GetExistingListenerManager();
if (!elm) {
return false;
}
if (!gTouchEventsRegistered) {
Preferences::AddIntVarCache(&gTouchEventsEnabled,
"dom.w3c_touch_events.enabled", gTouchEventsEnabled);
gTouchEventsRegistered = true;
}
if (!gTouchEventsEnabled) {
return false;
}
return elm->HasListenersFor(nsGkAtoms::ontouchstart) ||
elm->HasListenersFor(nsGkAtoms::ontouchend);
}
static bool
IsDescendant(nsIFrame* aFrame, nsIContent* aAncestor, nsAutoString* aLabelTargetId)
{
for (nsIContent* content = aFrame->GetContent(); content;
content = content->GetFlattenedTreeParent()) {
if (aLabelTargetId && content->IsHTMLElement(nsGkAtoms::label)) {
content->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
}
if (content == aAncestor) {
return true;
}
}
return false;
}
static nsIContent*
GetClickableAncestor(nsIFrame* aFrame, nsIAtom* stopAt = nullptr, nsAutoString* aLabelTargetId = nullptr)
{
// Input events propagate up the content tree so we'll follow the content
// ancestors to look for elements accepting the click.
for (nsIContent* content = aFrame->GetContent(); content;
content = content->GetFlattenedTreeParent()) {
if (stopAt && content->IsHTMLElement(stopAt)) {
break;
}
if (HasTouchListener(content) || HasMouseListener(content)) {
return content;
}
if (content->IsAnyOfHTMLElements(nsGkAtoms::button,
nsGkAtoms::input,
nsGkAtoms::select,
nsGkAtoms::textarea)) {
return content;
}
if (content->IsHTMLElement(nsGkAtoms::label)) {
if (aLabelTargetId) {
content->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, *aLabelTargetId);
}
return content;
}
// Bug 921928: we don't have access to the content of remote iframe.
// So fluffing won't go there. We do an optimistic assumption here:
// that the content of the remote iframe needs to be a target.
if (content->IsHTMLElement(nsGkAtoms::iframe) &&
content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozbrowser,
nsGkAtoms::_true, eIgnoreCase) &&
content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
nsGkAtoms::_true, eIgnoreCase)) {
return content;
}
// See nsCSSFrameConstructor::FindXULTagData. This code is not
// really intended to be used with XUL, though.
if (content->IsAnyOfXULElements(nsGkAtoms::button,
nsGkAtoms::checkbox,
nsGkAtoms::radio,
nsGkAtoms::autorepeatbutton,
nsGkAtoms::menu,
nsGkAtoms::menubutton,
nsGkAtoms::menuitem,
nsGkAtoms::menulist,
nsGkAtoms::scrollbarbutton,
nsGkAtoms::resizer)) {
return content;
}
static nsIContent::AttrValuesArray clickableRoles[] =
{ &nsGkAtoms::button, &nsGkAtoms::key, nullptr };
if (content->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role,
clickableRoles, eIgnoreCase) >= 0) {
return content;
}
if (content->IsEditable()) {
return content;
}
nsCOMPtr<nsIURI> linkURI;
if (content->IsLink(getter_AddRefs(linkURI))) {
return content;
}
}
return nullptr;
}
static nscoord
AppUnitsFromMM(nsIFrame* aFrame, uint32_t aMM)
{
nsPresContext* pc = aFrame->PresContext();
nsIPresShell* presShell = pc->PresShell();
float result = float(aMM) *
(pc->DeviceContext()->AppUnitsPerPhysicalInch() / MM_PER_INCH_FLOAT);
if (presShell->ScaleToResolution()) {
result = result / presShell->GetResolution();
}
return NSToCoordRound(result);
}
/**
* Clip aRect with the bounds of aFrame in the coordinate system of
* aRootFrame. aRootFrame is an ancestor of aFrame.
*/
static nsRect
ClipToFrame(nsIFrame* aRootFrame, nsIFrame* aFrame, nsRect& aRect)
{
nsRect bound = nsLayoutUtils::TransformFrameRectToAncestor(
aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), aRootFrame);
nsRect result = bound.Intersect(aRect);
return result;
}
static nsRect
GetTargetRect(nsIFrame* aRootFrame, const nsPoint& aPointRelativeToRootFrame,
nsIFrame* aRestrictToDescendants, const EventRadiusPrefs* aPrefs,
uint32_t aFlags)
{
nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[0]),
AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[1]),
AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[2]),
AppUnitsFromMM(aRootFrame, aPrefs->mSideRadii[3]));
nsRect r(aPointRelativeToRootFrame, nsSize(0,0));
r.Inflate(m);
if (!(aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)) {
// Don't clip this rect to the root scroll frame if the flag to ignore the
// root scroll frame is set. Note that the GetClosest code will still enforce
// that the target found is a descendant of aRestrictToDescendants.
r = ClipToFrame(aRootFrame, aRestrictToDescendants, r);
}
return r;
}
static float
ComputeDistanceFromRect(const nsPoint& aPoint, const nsRect& aRect)
{
nscoord dx = std::max(0, std::max(aRect.x - aPoint.x, aPoint.x - aRect.XMost()));
nscoord dy = std::max(0, std::max(aRect.y - aPoint.y, aPoint.y - aRect.YMost()));
return float(NS_hypot(dx, dy));
}
static float
ComputeDistanceFromRegion(const nsPoint& aPoint, const nsRegion& aRegion)
{
MOZ_ASSERT(!aRegion.IsEmpty(), "can't compute distance between point and empty region");
float minDist = -1;
for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
float dist = ComputeDistanceFromRect(aPoint, iter.Get());
if (dist < minDist || minDist < 0) {
minDist = dist;
}
}
return minDist;
}
// Subtract aRegion from aExposedRegion as long as that doesn't make the
// exposed region get too complex or removes a big chunk of the exposed region.
static void
SubtractFromExposedRegion(nsRegion* aExposedRegion, const nsRegion& aRegion)
{
if (aRegion.IsEmpty())
return;
nsRegion tmp;
tmp.Sub(*aExposedRegion, aRegion);
// Don't let *aExposedRegion get too complex, but don't let it fluff out to
// its bounds either. Do let aExposedRegion get more complex if by doing so
// we reduce its area by at least half.
if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area()/2) {
*aExposedRegion = tmp;
}
}
// Search in the list of frames aCandidates if the element with the id "aLabelTargetId"
// is present.
static bool IsElementPresent(nsTArray<nsIFrame*>& aCandidates, const nsAutoString& aLabelTargetId)
{
for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
nsIFrame* f = aCandidates[i];
nsIContent* aContent = f->GetContent();
if (aContent && aContent->IsElement()) {
if (aContent->GetID() && aLabelTargetId == nsAtomString(aContent->GetID())) {
return true;
}
}
}
return false;
}
static bool
IsLargeElement(nsIFrame* aFrame, const EventRadiusPrefs* aPrefs)
{
uint32_t keepLimitSizeForCluster = aPrefs->mKeepLimitSizeForCluster;
nsSize frameSize = aFrame->GetSize();
nsPresContext* pc = aFrame->PresContext();
nsIPresShell* presShell = pc->PresShell();
float cumulativeResolution = presShell->GetCumulativeResolution();
if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) > keepLimitSizeForCluster &&
(pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) > keepLimitSizeForCluster) {
return true;
}
return false;
}
static nsIFrame*
GetClosest(nsIFrame* aRoot, const nsPoint& aPointRelativeToRootFrame,
const nsRect& aTargetRect, const EventRadiusPrefs* aPrefs,
nsIFrame* aRestrictToDescendants, nsIContent* aClickableAncestor,
nsTArray<nsIFrame*>& aCandidates, int32_t* aElementsInCluster)
{
std::vector<nsIContent*> mContentsInCluster; // List of content elements in the cluster without duplicate
nsIFrame* bestTarget = nullptr;
// Lower is better; distance is in appunits
float bestDistance = 1e6f;
nsRegion exposedRegion(aTargetRect);
for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
nsIFrame* f = aCandidates[i];
PET_LOG("Checking candidate %p\n", f);
bool preservesAxisAlignedRectangles = false;
nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(f,
nsRect(nsPoint(0, 0), f->GetSize()), aRoot, &preservesAxisAlignedRectangles);
nsRegion region;
region.And(exposedRegion, borderBox);
if (region.IsEmpty()) {
PET_LOG(" candidate %p had empty hit region\n", f);
continue;
}
if (preservesAxisAlignedRectangles) {
// Subtract from the exposed region if we have a transform that won't make
// the bounds include a bunch of area that we don't actually cover.
SubtractFromExposedRegion(&exposedRegion, region);
}
nsAutoString labelTargetId;
if (aClickableAncestor && !IsDescendant(f, aClickableAncestor, &labelTargetId)) {
PET_LOG(" candidate %p is not a descendant of required ancestor\n", f);
continue;
}
nsIContent* clickableContent = GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
if (!aClickableAncestor && !clickableContent) {
PET_LOG(" candidate %p was not clickable\n", f);
continue;
}
// If our current closest frame is a descendant of 'f', skip 'f' (prefer
// the nested frame).
if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(f, bestTarget, aRoot)) {
PET_LOG(" candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
continue;
}
if (!aClickableAncestor && !nsLayoutUtils::IsAncestorFrameCrossDoc(aRestrictToDescendants, f, aRoot)) {
PET_LOG(" candidate %p was not descendant of restrictroot %p\n", f, aRestrictToDescendants);
continue;
}
// If the first clickable ancestor of f is a label element
// and "for" attribute is present in label element, search the frame list for the "for" element
// If this element is present in the current list, do not count the frame in
// the cluster elements counter
if ((labelTargetId.IsEmpty() || !IsElementPresent(aCandidates, labelTargetId)) &&
!IsLargeElement(f, aPrefs)) {
if (std::find(mContentsInCluster.begin(), mContentsInCluster.end(), clickableContent) == mContentsInCluster.end()) {
mContentsInCluster.push_back(clickableContent);
}
}
// distance is in appunits
float distance = ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
nsIContent* content = f->GetContent();
if (content && content->IsElement() &&
content->AsElement()->State().HasState(
EventStates(NS_EVENT_STATE_VISITED))) {
distance *= aPrefs->mVisitedWeight / 100.0f;
}
if (distance < bestDistance) {
PET_LOG(" candidate %p is the new best\n", f);
bestDistance = distance;
bestTarget = f;
}
}
*aElementsInCluster = mContentsInCluster.size();
return bestTarget;
}
/*
* Return always true when touch cluster detection is OFF.
* When cluster detection is ON, return true:
* if the text inside the frame is readable (by human eyes)
* or
* if the structure is too complex to determine the size.
* In both cases, the frame is considered as clickable.
*
* Frames with a too small size will return false.
* In this case, the frame is considered not clickable.
*/
static bool
IsElementClickableAndReadable(nsIFrame* aFrame, WidgetGUIEvent* aEvent, const EventRadiusPrefs* aPrefs)
{
if (!aPrefs->mTouchClusterDetectionEnabled) {
return true;
}
if (aPrefs->mSimplifiedClusterDetection) {
return true;
}
if (aEvent->mClass != eMouseEventClass) {
return true;
}
uint32_t limitReadableSize = aPrefs->mLimitReadableSize;
nsSize frameSize = aFrame->GetSize();
nsPresContext* pc = aFrame->PresContext();
nsIPresShell* presShell = pc->PresShell();
float cumulativeResolution = presShell->GetCumulativeResolution();
if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) < limitReadableSize ||
(pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) < limitReadableSize) {
return false;
}
// We want to detect small clickable text elements using the font size.
// Two common cases are supported for now:
// 1. text node
// 2. any element with only one child of type text node
// All the other cases are currently ignored.
nsIContent *content = aFrame->GetContent();
bool testFontSize = false;
if (content) {
nsINodeList* childNodes = content->ChildNodes();
uint32_t childNodeCount = childNodes->Length();
if ((content->IsNodeOfType(nsINode::eTEXT)) ||
// click occurs on the text inside <a></a> or other clickable tags with text inside
(childNodeCount == 1 && childNodes->Item(0) &&
childNodes->Item(0)->IsNodeOfType(nsINode::eTEXT))) {
// The click occurs on an element with only one text node child. In this case, the font size
// can be tested.
// The number of child nodes is tested to avoid the following cases (See bug 1172488):
// Some jscript libraries transform text elements into Canvas elements but keep the text nodes
// with a very small size (1px) to handle the selection of text.
// With such libraries, the font size of the text elements is not relevant to detect small elements.
testFontSize = true;
}
}
if (testFontSize) {
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
if (fm && fm->EmHeight() > 0 && // See bug 1171731
(pc->AppUnitsToGfxUnits(fm->EmHeight()) * cumulativeResolution) < limitReadableSize) {
return false;
}
}
return true;
}
nsIFrame*
FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
nsIFrame* aRootFrame,
const nsPoint& aPointRelativeToRootFrame,
uint32_t aFlags)
{
uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) ?
nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0;
nsIFrame* target =
nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame, flags);
PET_LOG("Found initial target %p for event class %s point %s relative to root frame %p\n",
target, (aEvent->mClass == eMouseEventClass ? "mouse" :
(aEvent->mClass == eTouchEventClass ? "touch" : "other")),
mozilla::layers::Stringify(aPointRelativeToRootFrame).c_str(), aRootFrame);
const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->mClass);
if (!prefs || !prefs->mEnabled) {
PET_LOG("Retargeting disabled\n");
return target;
}
nsIContent* clickableAncestor = nullptr;
if (target) {
clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
if (clickableAncestor) {
if (!IsElementClickableAndReadable(target, aEvent, prefs)) {
aEvent->AsMouseEventBase()->hitCluster = true;
}
PET_LOG("Target %p is clickable\n", target);
// If the target that was directly hit has a clickable ancestor, that
// means it too is clickable. And since it is the same as or a descendant
// of clickableAncestor, it should become the root for the GetClosest
// search.
clickableAncestor = target->GetContent();
}
}
// Do not modify targeting for actual mouse hardware; only for mouse
// events generated by touch-screen hardware.
if (aEvent->mClass == eMouseEventClass &&
prefs->mTouchOnly &&
aEvent->AsMouseEvent()->inputSource !=
nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
PET_LOG("Mouse input event is not from a touch source\n");
return target;
}
// If the exact target is non-null, only consider candidate targets in the same
// document as the exact target. Otherwise, if an ancestor document has
// a mouse event handler for example, targets that are !GetClickableAncestor can
// never be targeted --- something nsSubDocumentFrame in an ancestor document
// would be targeted instead.
nsIFrame* restrictToDescendants = target ?
target->PresContext()->PresShell()->GetRootFrame() : aRootFrame;
nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
restrictToDescendants, prefs, aFlags);
PET_LOG("Expanded point to target rect %s\n",
mozilla::layers::Stringify(targetRect).c_str());
AutoTArray<nsIFrame*,8> candidates;
nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags);
if (NS_FAILED(rv)) {
return target;
}
int32_t elementsInCluster = 0;
nsIFrame* closestClickable =
GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
restrictToDescendants, clickableAncestor, candidates,
&elementsInCluster);
if (closestClickable) {
if ((prefs->mTouchClusterDetectionEnabled && elementsInCluster > 1) ||
(!IsElementClickableAndReadable(closestClickable, aEvent, prefs))) {
if (aEvent->mClass == eMouseEventClass) {
WidgetMouseEventBase* mouseEventBase = aEvent->AsMouseEventBase();
mouseEventBase->hitCluster = true;
}
}
target = closestClickable;
}
PET_LOG("Final target is %p\n", target);
// Uncomment this to dump the frame tree to help with debugging.
// Note that dumping the frame tree at the top of the function may flood
// logcat on Android devices and cause the PET_LOGs to get dropped.
// aRootFrame->DumpFrameTree();
if (!target || !prefs->mRepositionEventCoords) {
// No repositioning required for this event
return target;
}
// Take the point relative to the root frame, make it relative to the target,
// clamp it to the bounds, and then make it relative to the root frame again.
nsPoint point = aPointRelativeToRootFrame;
if (nsLayoutUtils::TRANSFORM_SUCCEEDED != nsLayoutUtils::TransformPoint(aRootFrame, target, point)) {
return target;
}
point = target->GetRectRelativeToSelf().ClampPoint(point);
if (nsLayoutUtils::TRANSFORM_SUCCEEDED != nsLayoutUtils::TransformPoint(target, aRootFrame, point)) {
return target;
}
// Now we basically undo the operations in GetEventCoordinatesRelativeTo, to
// get back the (now-clamped) coordinates in the event's widget's space.
nsView* view = aRootFrame->GetView();
if (!view) {
return target;
}
LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget(
aRootFrame->PresContext(), view, point, aEvent->mWidget);
if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) {
// If that succeeded, we update the point in the event
aEvent->mRefPoint = widgetPoint;
}
return target;
}
} // namespace mozilla

View file

@ -0,0 +1,32 @@
/* 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 mozilla_PositionedEventTargeting_h
#define mozilla_PositionedEventTargeting_h
#include <stdint.h>
#include "mozilla/EventForwards.h"
class nsIFrame;
struct nsPoint;
namespace mozilla {
enum {
INPUT_IGNORE_ROOT_SCROLL_FRAME = 0x01
};
/**
* Finds the target frame for a pointer event given the event type and location.
* This can look for frames within a rectangle surrounding the actual location
* that are suitable targets, to account for inaccurate pointing devices.
*/
nsIFrame*
FindFrameTargetedByInputEvent(WidgetGUIEvent* aEvent,
nsIFrame* aRootFrame,
const nsPoint& aPointRelativeToRootFrame,
uint32_t aFlags = 0);
} // namespace mozilla
#endif /* mozilla_PositionedEventTargeting_h */

View file

@ -0,0 +1,48 @@
/* -*- Mode: C++; 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/. */
/**
* Macros used to log restyle events.
*/
#ifndef mozilla_RestyleLogging_h
#define mozilla_RestyleLogging_h
#include "mozilla/AutoRestore.h"
#ifdef DEBUG
#define RESTYLE_LOGGING
#endif
#ifdef RESTYLE_LOGGING
#define LOG_RESTYLE_VAR2(prefix_, suffix_) prefix_##suffix_
#define LOG_RESTYLE_VAR(prefix_, suffix_) LOG_RESTYLE_VAR2(prefix_, suffix_)
#define LOG_RESTYLE_DEPTH LOG_RESTYLE_VAR(restyle_depth_, __LINE__)
#define LOG_RESTYLE_IF(object_, cond_, message_, ...) \
PR_BEGIN_MACRO \
if (object_->ShouldLogRestyle() && (cond_)) { \
nsCString line; \
for (int32_t LOG_RESTYLE_VAR(rs_depth_, __LINE__) = 0; \
LOG_RESTYLE_VAR(rs_depth_, __LINE__) < object_->LoggingDepth(); \
LOG_RESTYLE_VAR(rs_depth_, __LINE__)++) { \
line.AppendLiteral(" "); \
} \
line.AppendPrintf(message_, ##__VA_ARGS__); \
printf_stderr("%s\n", line.get()); \
} \
PR_END_MACRO
#define LOG_RESTYLE(message_, ...) \
LOG_RESTYLE_IF(this, true, message_, ##__VA_ARGS__)
// Beware that LOG_RESTYLE_INDENT is two statements not wrapped in a block.
#define LOG_RESTYLE_INDENT() \
AutoRestore<int32_t> LOG_RESTYLE_VAR(ar_depth_, __LINE__)(LoggingDepth()); \
++LoggingDepth();
#else
#define LOG_RESTYLE_IF(cond_, message_, ...) /* nothing */
#define LOG_RESTYLE(message_, ...) /* nothing */
#define LOG_RESTYLE_INDENT() /* nothing */
#endif
#endif /* mozilla_RestyleLogging_h */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,885 @@
/* -*- Mode: C++; 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/. */
/**
* Code responsible for managing style changes: tracking what style
* changes need to happen, scheduling them, and doing them.
*/
#ifndef mozilla_RestyleManager_h
#define mozilla_RestyleManager_h
#include "mozilla/RestyleLogging.h"
#include "mozilla/RestyleManagerBase.h"
#include "nsISupportsImpl.h"
#include "nsChangeHint.h"
#include "RestyleTracker.h"
#include "nsPresContext.h"
#include "nsRefreshDriver.h"
#include "nsRefPtrHashtable.h"
#include "nsTransitionManager.h"
class nsIFrame;
class nsStyleChangeList;
struct TreeMatchContext;
namespace mozilla {
enum class CSSPseudoElementType : uint8_t;
class EventStates;
struct UndisplayedNode;
namespace dom {
class Element;
} // namespace dom
class RestyleManager final : public RestyleManagerBase
{
public:
typedef RestyleManagerBase base_type;
friend class RestyleTracker;
friend class ElementRestyler;
explicit RestyleManager(nsPresContext* aPresContext);
private:
// Private destructor, to discourage deletion outside of Release():
~RestyleManager()
{
MOZ_ASSERT(!mReframingStyleContexts,
"temporary member should be nulled out before destruction");
MOZ_ASSERT(!mAnimationsWithDestroyedFrame,
"leaving dangling pointers from AnimationsWithDestroyedFrame");
}
public:
NS_INLINE_DECL_REFCOUNTING(mozilla::RestyleManager)
// Forwarded nsIDocumentObserver method, to handle restyling (and
// passing the notification to the frame).
nsresult ContentStateChanged(nsIContent* aContent,
EventStates aStateMask);
// Forwarded nsIMutationObserver method, to handle restyling.
void AttributeWillChange(Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aNewValue);
// Forwarded nsIMutationObserver method, to handle restyling (and
// passing the notification to the frame).
void AttributeChanged(Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue);
// Get a counter that increments on every style change, that we use to
// track whether off-main-thread animations are up-to-date.
uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
static uint64_t GetAnimationGenerationForFrame(nsIFrame* aFrame);
// Update the animation generation count to mark that animation state
// has changed.
//
// This is normally performed automatically by ProcessPendingRestyles
// but it is also called when we have out-of-band changes to animations
// such as changes made through the Web Animations API.
void IncrementAnimationGeneration() {
// We update the animation generation at start of each call to
// ProcessPendingRestyles so we should ignore any subsequent (redundant)
// calls that occur while we are still processing restyles.
if (!mIsProcessingRestyles) {
++mAnimationGeneration;
}
}
// Whether rule matching should skip styles associated with animation
bool SkipAnimationRules() const { return mSkipAnimationRules; }
void SetSkipAnimationRules(bool aSkipAnimationRules) {
mSkipAnimationRules = aSkipAnimationRules;
}
/**
* Reparent the style contexts of this frame subtree. The parent frame of
* aFrame must be changed to the new parent before this function is called;
* the new parent style context will be automatically computed based on the
* new position in the frame tree.
*
* @param aFrame the root of the subtree to reparent. Must not be null.
*/
nsresult ReparentStyleContext(nsIFrame* aFrame);
void ClearSelectors() {
mPendingRestyles.ClearSelectors();
}
private:
// Used when restyling an element with a frame.
void ComputeAndProcessStyleChange(nsIFrame* aFrame,
nsChangeHint aMinChange,
RestyleTracker& aRestyleTracker,
nsRestyleHint aRestyleHint,
const RestyleHintData& aRestyleHintData);
// Used when restyling a display:contents element.
void ComputeAndProcessStyleChange(nsStyleContext* aNewContext,
Element* aElement,
nsChangeHint aMinChange,
RestyleTracker& aRestyleTracker,
nsRestyleHint aRestyleHint,
const RestyleHintData& aRestyleHintData);
public:
/**
* In order to start CSS transitions on elements that are being
* reframed, we need to stash their style contexts somewhere during
* the reframing process.
*
* In all cases, the content node in the hash table is the real
* content node, not the anonymous content node we create for ::before
* or ::after. The content node passed to the Get and Put methods is,
* however, the content node to be associate with the frame's style
* context.
*/
typedef nsRefPtrHashtable<nsRefPtrHashKey<nsIContent>, nsStyleContext>
ReframingStyleContextTable;
class MOZ_STACK_CLASS ReframingStyleContexts final {
public:
/**
* Construct a ReframingStyleContexts object. The caller must
* ensure that aRestyleManager lives at least as long as the
* object. (This is generally easy since the caller is typically a
* method of RestyleManager.)
*/
explicit ReframingStyleContexts(RestyleManager* aRestyleManager);
~ReframingStyleContexts();
void Put(nsIContent* aContent, nsStyleContext* aStyleContext) {
MOZ_ASSERT(aContent);
CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType();
if (pseudoType == CSSPseudoElementType::NotPseudo) {
mElementContexts.Put(aContent, aStyleContext);
} else if (pseudoType == CSSPseudoElementType::before) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore);
mBeforePseudoContexts.Put(aContent->GetParent(), aStyleContext);
} else if (pseudoType == CSSPseudoElementType::after) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter);
mAfterPseudoContexts.Put(aContent->GetParent(), aStyleContext);
}
}
nsStyleContext* Get(nsIContent* aContent,
CSSPseudoElementType aPseudoType) {
MOZ_ASSERT(aContent);
if (aPseudoType == CSSPseudoElementType::NotPseudo) {
return mElementContexts.GetWeak(aContent);
}
if (aPseudoType == CSSPseudoElementType::before) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore);
return mBeforePseudoContexts.GetWeak(aContent->GetParent());
}
if (aPseudoType == CSSPseudoElementType::after) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter);
return mAfterPseudoContexts.GetWeak(aContent->GetParent());
}
MOZ_ASSERT(false, "unexpected aPseudoType");
return nullptr;
}
private:
RestyleManager* mRestyleManager;
AutoRestore<ReframingStyleContexts*> mRestorePointer;
ReframingStyleContextTable mElementContexts;
ReframingStyleContextTable mBeforePseudoContexts;
ReframingStyleContextTable mAfterPseudoContexts;
};
/**
* Return the current ReframingStyleContexts struct, or null if we're
* not currently in a restyling operation.
*/
ReframingStyleContexts* GetReframingStyleContexts() {
return mReframingStyleContexts;
}
/**
* Try initiating a transition for an element or a ::before or ::after
* pseudo-element, given an old and new style context. This may
* change the new style context if a transition is started. Returns
* true if it does change aNewStyleContext.
*
* For the pseudo-elements, aContent must be the anonymous content
* that we're creating for that pseudo-element, not the real element.
*/
static bool
TryInitiatingTransition(nsPresContext* aPresContext, nsIContent* aContent,
nsStyleContext* aOldStyleContext,
RefPtr<nsStyleContext>* aNewStyleContext /* inout */);
// AnimationsWithDestroyedFrame is used to stop animations and transitions
// on elements that have no frame at the end of the restyling process.
// It only lives during the restyling process.
class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final {
public:
// Construct a AnimationsWithDestroyedFrame object. The caller must
// ensure that aRestyleManager lives at least as long as the
// object. (This is generally easy since the caller is typically a
// method of RestyleManager.)
explicit AnimationsWithDestroyedFrame(RestyleManager* aRestyleManager);
// This method takes the content node for the generated content for
// animation/transition on ::before and ::after, rather than the
// content node for the real element.
void Put(nsIContent* aContent, nsStyleContext* aStyleContext) {
MOZ_ASSERT(aContent);
CSSPseudoElementType pseudoType = aStyleContext->GetPseudoType();
if (pseudoType == CSSPseudoElementType::NotPseudo) {
mContents.AppendElement(aContent);
} else if (pseudoType == CSSPseudoElementType::before) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore);
mBeforeContents.AppendElement(aContent->GetParent());
} else if (pseudoType == CSSPseudoElementType::after) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter);
mAfterContents.AppendElement(aContent->GetParent());
}
}
void StopAnimationsForElementsWithoutFrames();
private:
void StopAnimationsWithoutFrame(nsTArray<RefPtr<nsIContent>>& aArray,
CSSPseudoElementType aPseudoType);
RestyleManager* mRestyleManager;
AutoRestore<AnimationsWithDestroyedFrame*> mRestorePointer;
// Below three arrays might include elements that have already had their
// animations or transitions stopped.
//
// mBeforeContents and mAfterContents hold the real element rather than
// the content node for the generated content (which might change during
// a reframe)
nsTArray<RefPtr<nsIContent>> mContents;
nsTArray<RefPtr<nsIContent>> mBeforeContents;
nsTArray<RefPtr<nsIContent>> mAfterContents;
};
/**
* Return the current AnimationsWithDestroyedFrame struct, or null if we're
* not currently in a restyling operation.
*/
AnimationsWithDestroyedFrame* GetAnimationsWithDestroyedFrame() {
return mAnimationsWithDestroyedFrame;
}
private:
void RestyleForEmptyChange(Element* aContainer);
public:
// Handle ContentInserted notifications.
void ContentInserted(nsINode* aContainer, nsIContent* aChild)
{
RestyleForInsertOrChange(aContainer, aChild);
}
// Handle ContentAppended notifications.
void ContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent)
{
RestyleForAppend(aContainer, aFirstNewContent);
}
// Handle ContentRemoved notifications.
//
// This would be have the same logic as RestyleForInsertOrChange if we got the
// notification before the removal. However, we get it after, so we need the
// following sibling in addition to the old child. |aContainer| must be
// non-null; when the container is null, no work is needed. aFollowingSibling
// is the sibling that used to come after aOldChild before the removal.
void ContentRemoved(nsINode* aContainer, nsIContent* aOldChild,
nsIContent* aFollowingSibling);
// Restyling for a ContentInserted (notification after insertion) or
// for a CharacterDataChanged. |aContainer| must be non-null; when
// the container is null, no work is needed.
void RestyleForInsertOrChange(nsINode* aContainer, nsIContent* aChild);
// Restyling for a ContentAppended (notification after insertion) or
// for a CharacterDataChanged. |aContainer| must be non-null; when
// the container is null, no work is needed.
void RestyleForAppend(nsIContent* aContainer, nsIContent* aFirstNewContent);
// Process any pending restyles. This should be called after
// CreateNeededFrames.
// Note: It's the caller's responsibility to make sure to wrap a
// ProcessPendingRestyles call in a view update batch and a script blocker.
// This function does not call ProcessAttachedQueue() on the binding manager.
// If the caller wants that to happen synchronously, it needs to handle that
// itself.
void ProcessPendingRestyles();
// Returns whether there are any pending restyles.
bool HasPendingRestyles() { return mPendingRestyles.Count() != 0; }
private:
// ProcessPendingRestyles calls into one of our RestyleTracker
// objects. It then calls back to these functions at the beginning
// and end of its work.
void BeginProcessingRestyles(RestyleTracker& aRestyleTracker);
void EndProcessingRestyles();
public:
// Update styles for animations that are running on the compositor and
// whose updating is suppressed on the main thread (to save
// unnecessary work), while leaving all other aspects of style
// out-of-date.
//
// Performs an animation-only style flush to make styles from
// throttled transitions up-to-date prior to processing an unrelated
// style change, so that any transitions triggered by that style
// change produce correct results.
//
// In more detail: when we're able to run animations on the
// compositor, we sometimes "throttle" these animations by skipping
// updating style data on the main thread. However, whenever we
// process a normal (non-animation) style change, any changes in
// computed style on elements that have transition-* properties set
// may need to trigger new transitions; this process requires knowing
// both the old and new values of the property. To do this correctly,
// we need to have an up-to-date *old* value of the property on the
// primary frame. So the purpose of the mini-flush is to update the
// style for all throttled transitions and animations to the current
// animation state without making any other updates, so that when we
// process the queued style updates we'll have correct old data to
// compare against. When we do this, we don't bother touching frames
// other than primary frames.
void UpdateOnlyAnimationStyles();
// Rebuilds all style data by throwing out the old rule tree and
// building a new one, and additionally applying aExtraHint (which
// must not contain nsChangeHint_ReconstructFrame) to the root frame.
//
// aRestyleHint says which restyle hint to use for the computation;
// the only sensible values to use are eRestyle_Subtree (which says
// that the rebuild must run selector matching) and nsRestyleHint(0)
// (which says that rerunning selector matching is not required. (The
// method adds eRestyle_ForceDescendants internally, and including it
// in the restyle hint is harmless; some callers (e.g.,
// nsPresContext::MediaFeatureValuesChanged) might do this for their
// own reasons.)
void RebuildAllStyleData(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint);
/**
* Notify the frame constructor that an element needs to have its
* style recomputed.
* @param aElement: The element to be restyled.
* @param aRestyleHint: Which nodes need to have selector matching run
* on them.
* @param aMinChangeHint: A minimum change hint for aContent and its
* descendants.
* @param aRestyleHintData: Additional data to go with aRestyleHint.
*/
void PostRestyleEvent(Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint,
const RestyleHintData* aRestyleHintData = nullptr);
void PostRestyleEventForLazyConstruction()
{
PostRestyleEventInternal(true);
}
public:
/**
* Asynchronously clear style data from the root frame downwards and ensure
* it will all be rebuilt. This is safe to call anytime; it will schedule
* a restyle and take effect next time style changes are flushed.
* This method is used to recompute the style data when some change happens
* outside of any style rules, like a color preference change or a change
* in a system font size, or to fix things up when an optimization in the
* style data has become invalid. We assume that the root frame will not
* need to be reframed.
*
* For parameters, see RebuildAllStyleData.
*/
void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint);
#ifdef DEBUG
bool InRebuildAllStyleData() const { return mInRebuildAllStyleData; }
#endif
#ifdef RESTYLE_LOGGING
/**
* Returns whether a restyle event currently being processed by this
* RestyleManager should be logged.
*/
bool ShouldLogRestyle() {
return ShouldLogRestyle(PresContext());
}
/**
* Returns whether a restyle event currently being processed for the
* document with the specified nsPresContext should be logged.
*/
static bool ShouldLogRestyle(nsPresContext* aPresContext) {
return aPresContext->RestyleLoggingEnabled() &&
(!aPresContext->TransitionManager()->
InAnimationOnlyStyleUpdate() ||
AnimationRestyleLoggingEnabled());
}
static bool RestyleLoggingInitiallyEnabled() {
static bool enabled = getenv("MOZ_DEBUG_RESTYLE") != 0;
return enabled;
}
static bool AnimationRestyleLoggingEnabled() {
static bool animations = getenv("MOZ_DEBUG_RESTYLE_ANIMATIONS") != 0;
return animations;
}
// Set MOZ_DEBUG_RESTYLE_STRUCTS to a comma-separated string of
// style struct names -- such as "Font,SVGReset" -- to log the style context
// tree and those cached struct pointers before each restyle. This
// function returns a bitfield of the structs named in the
// environment variable.
static uint32_t StructsToLog();
static nsCString StructNamesToString(uint32_t aSIDs);
int32_t& LoggingDepth() { return mLoggingDepth; }
#endif
private:
inline nsStyleSet* StyleSet() const {
MOZ_ASSERT(PresContext()->StyleSet()->IsGecko(),
"RestyleManager should only be used with a Gecko-flavored "
"style backend");
return PresContext()->StyleSet()->AsGecko();
}
/* aMinHint is the minimal change that should be made to the element */
// XXXbz do we really need the aPrimaryFrame argument here?
void RestyleElement(Element* aElement,
nsIFrame* aPrimaryFrame,
nsChangeHint aMinHint,
RestyleTracker& aRestyleTracker,
nsRestyleHint aRestyleHint,
const RestyleHintData& aRestyleHintData);
void StartRebuildAllStyleData(RestyleTracker& aRestyleTracker);
void FinishRebuildAllStyleData();
bool ShouldStartRebuildAllFor(RestyleTracker& aRestyleTracker) {
// When we process our primary restyle tracker and we have a pending
// rebuild-all, we need to process it.
return mDoRebuildAllStyleData &&
&aRestyleTracker == &mPendingRestyles;
}
void ProcessRestyles(RestyleTracker& aRestyleTracker) {
// Fast-path the common case (esp. for the animation restyle
// tracker) of not having anything to do.
if (aRestyleTracker.Count() || ShouldStartRebuildAllFor(aRestyleTracker)) {
IncrementRestyleGeneration();
aRestyleTracker.DoProcessRestyles();
}
}
private:
// True if we need to reconstruct the rule tree the next time we
// process restyles.
bool mDoRebuildAllStyleData : 1;
// True if we're currently in the process of reconstructing the rule tree.
bool mInRebuildAllStyleData : 1;
// Whether rule matching should skip styles associated with animation
bool mSkipAnimationRules : 1;
bool mHavePendingNonAnimationRestyles : 1;
nsChangeHint mRebuildAllExtraHint;
nsRestyleHint mRebuildAllRestyleHint;
// The total number of animation flushes by this frame constructor.
// Used to keep the layer and animation manager in sync.
uint64_t mAnimationGeneration;
ReframingStyleContexts* mReframingStyleContexts;
AnimationsWithDestroyedFrame* mAnimationsWithDestroyedFrame;
RestyleTracker mPendingRestyles;
// Are we currently in the middle of a call to ProcessRestyles?
// This flag is used both as a debugging aid to assert that we are not
// performing nested calls to ProcessPendingRestyles, as well as to ignore
// redundant calls to IncrementAnimationGeneration.
bool mIsProcessingRestyles;
#ifdef RESTYLE_LOGGING
int32_t mLoggingDepth;
#endif
};
/**
* An ElementRestyler is created for *each* element in a subtree that we
* recompute styles for.
*/
class ElementRestyler final
{
public:
typedef mozilla::dom::Element Element;
struct ContextToClear {
RefPtr<nsStyleContext> mStyleContext;
uint32_t mStructs;
};
// Construct for the root of the subtree that we're restyling.
ElementRestyler(nsPresContext* aPresContext,
nsIFrame* aFrame,
nsStyleChangeList* aChangeList,
nsChangeHint aHintsHandledByAncestors,
RestyleTracker& aRestyleTracker,
nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
TreeMatchContext& aTreeMatchContext,
nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement,
nsTArray<ContextToClear>& aContextsToClear,
nsTArray<RefPtr<nsStyleContext>>& aSwappedStructOwners);
// Construct for an element whose parent is being restyled.
enum ConstructorFlags {
FOR_OUT_OF_FLOW_CHILD = 1<<0
};
ElementRestyler(const ElementRestyler& aParentRestyler,
nsIFrame* aFrame,
uint32_t aConstructorFlags);
// Construct for a frame whose parent is being restyled, but whose
// style context is the parent style context for its parent frame.
// (This is only used for table frames, whose style contexts are used
// as the parent style context for their table wrapper frame. We should
// probably try to get rid of this exception and have the inheritance go
// the other way.)
enum ParentContextFromChildFrame { PARENT_CONTEXT_FROM_CHILD_FRAME };
ElementRestyler(ParentContextFromChildFrame,
const ElementRestyler& aParentFrameRestyler,
nsIFrame* aFrame);
// For restyling undisplayed content only (mFrame==null).
ElementRestyler(nsPresContext* aPresContext,
nsIContent* aContent,
nsStyleChangeList* aChangeList,
nsChangeHint aHintsHandledByAncestors,
RestyleTracker& aRestyleTracker,
nsTArray<nsCSSSelector*>& aSelectorsForDescendants,
TreeMatchContext& aTreeMatchContext,
nsTArray<nsIContent*>& aVisibleKidsOfHiddenElement,
nsTArray<ContextToClear>& aContextsToClear,
nsTArray<RefPtr<nsStyleContext>>& aSwappedStructOwners);
/**
* Restyle our frame's element and its subtree.
*
* Use eRestyle_Self for the aRestyleHint argument to mean
* "reresolve our style context but not kids", use eRestyle_Subtree
* to mean "reresolve our style context and kids", and use
* nsRestyleHint(0) to mean recompute a new style context for our
* current parent and existing rulenode, and the same for kids.
*/
void Restyle(nsRestyleHint aRestyleHint);
/**
* mHintsHandled changes over time; it starts off as the hints that
* have been handled by ancestors, and by the end of Restyle it
* represents the hints that have been handled for this frame. This
* method is intended to be called after Restyle, to find out what
* hints have been handled for this frame.
*/
nsChangeHint HintsHandledForFrame() { return mHintsHandled; }
/**
* Called from RestyleManager::ComputeAndProcessStyleChange to restyle
* children of a display:contents element.
*/
void RestyleChildrenOfDisplayContentsElement(nsIFrame* aParentFrame,
nsStyleContext* aNewContext,
nsChangeHint aMinHint,
RestyleTracker& aRestyleTracker,
nsRestyleHint aRestyleHint,
const RestyleHintData&
aRestyleHintData);
/**
* Re-resolve the style contexts for a frame tree, building aChangeList
* based on the resulting style changes, plus aMinChange applied to aFrame.
*/
static void ComputeStyleChangeFor(nsIFrame* aFrame,
nsStyleChangeList* aChangeList,
nsChangeHint aMinChange,
RestyleTracker& aRestyleTracker,
nsRestyleHint aRestyleHint,
const RestyleHintData& aRestyleHintData,
nsTArray<ContextToClear>& aContextsToClear,
nsTArray<RefPtr<nsStyleContext>>&
aSwappedStructOwners);
#ifdef RESTYLE_LOGGING
bool ShouldLogRestyle() {
return RestyleManager::ShouldLogRestyle(mPresContext);
}
#endif
private:
inline nsStyleSet* StyleSet() const;
// Enum class for the result of RestyleSelf, which indicates whether the
// restyle procedure should continue to the children, and how.
//
// These values must be ordered so that later values imply that all
// the work of the earlier values is also done.
enum class RestyleResult : uint8_t {
// default initial value
eNone,
// we left the old style context on the frame; do not restyle children
eStop,
// we got a new style context on this frame, but we know that children
// do not depend on the changed values; do not restyle children
eStopWithStyleChange,
// continue restyling children
eContinue,
// continue restyling children with eRestyle_ForceDescendants set
eContinueAndForceDescendants
};
struct SwapInstruction
{
RefPtr<nsStyleContext> mOldContext;
RefPtr<nsStyleContext> mNewContext;
uint32_t mStructsToSwap;
};
/**
* First half of Restyle().
*/
RestyleResult RestyleSelf(nsIFrame* aSelf,
nsRestyleHint aRestyleHint,
uint32_t* aSwappedStructs,
nsTArray<SwapInstruction>& aSwaps);
/**
* Restyle the children of this frame (and, in turn, their children).
*
* Second half of Restyle().
*/
void RestyleChildren(nsRestyleHint aChildRestyleHint);
/**
* Returns true iff a selector in mSelectorsForDescendants matches aElement.
* This is called when processing a eRestyle_SomeDescendants restyle hint.
*/
bool SelectorMatchesForRestyle(Element* aElement);
/**
* Returns true iff aRestyleHint indicates that we should be restyling.
* Specifically, this will return true when eRestyle_Self or
* eRestyle_Subtree is present, or if eRestyle_SomeDescendants is
* present and the specified element matches one of the selectors in
* mSelectorsForDescendants.
*/
bool MustRestyleSelf(nsRestyleHint aRestyleHint, Element* aElement);
/**
* Returns true iff aRestyleHint indicates that we can call
* ReparentStyleContext rather than any other restyling method of
* nsStyleSet that looks up a new rule node, and if we are
* not in the process of reconstructing the whole rule tree.
* This is used to check whether it is appropriate to call
* ReparentStyleContext.
*/
bool CanReparentStyleContext(nsRestyleHint aRestyleHint);
/**
* Helpers for Restyle().
*/
void AddLayerChangesForAnimation();
bool MoveStyleContextsForContentChildren(nsIFrame* aParent,
nsStyleContext* aOldContext,
nsTArray<nsStyleContext*>& aContextsToMove);
bool MoveStyleContextsForChildren(nsStyleContext* aOldContext);
/**
* Helpers for RestyleSelf().
*/
void CaptureChange(nsStyleContext* aOldContext,
nsStyleContext* aNewContext,
nsChangeHint aChangeToAssume,
uint32_t* aEqualStructs,
uint32_t* aSamePointerStructs);
void ComputeRestyleResultFromFrame(nsIFrame* aSelf,
RestyleResult& aRestyleResult,
bool& aCanStopWithStyleChange);
void ComputeRestyleResultFromNewContext(nsIFrame* aSelf,
nsStyleContext* aNewContext,
RestyleResult& aRestyleResult,
bool& aCanStopWithStyleChange);
// Helpers for RestyleChildren().
void RestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint);
bool MustCheckUndisplayedContent(nsIFrame* aFrame,
nsIContent*& aUndisplayedParent);
/**
* In the following two methods, aParentStyleContext is either
* mFrame->StyleContext() if we have a frame, or a display:contents
* style context if we don't.
*/
void DoRestyleUndisplayedDescendants(nsRestyleHint aChildRestyleHint,
nsIContent* aParent,
nsStyleContext* aParentStyleContext);
void RestyleUndisplayedNodes(nsRestyleHint aChildRestyleHint,
UndisplayedNode* aUndisplayed,
nsIContent* aUndisplayedParent,
nsStyleContext* aParentStyleContext,
const StyleDisplay aDisplay);
void MaybeReframeForBeforePseudo();
void MaybeReframeForAfterPseudo(nsIFrame* aFrame);
void MaybeReframeForPseudo(CSSPseudoElementType aPseudoType,
nsIFrame* aGenConParentFrame,
nsIFrame* aFrame,
nsIContent* aContent,
nsStyleContext* aStyleContext);
#ifdef DEBUG
bool MustReframeForBeforePseudo();
bool MustReframeForAfterPseudo(nsIFrame* aFrame);
#endif
bool MustReframeForPseudo(CSSPseudoElementType aPseudoType,
nsIFrame* aGenConParentFrame,
nsIFrame* aFrame,
nsIContent* aContent,
nsStyleContext* aStyleContext);
void RestyleContentChildren(nsIFrame* aParent,
nsRestyleHint aChildRestyleHint);
void InitializeAccessibilityNotifications(nsStyleContext* aNewContext);
void SendAccessibilityNotifications();
enum DesiredA11yNotifications {
eSkipNotifications,
eSendAllNotifications,
eNotifyIfShown
};
enum A11yNotificationType {
eDontNotify,
eNotifyShown,
eNotifyHidden
};
// These methods handle the eRestyle_SomeDescendants hint by traversing
// down the frame tree (and then when reaching undisplayed content,
// the flattened content tree) find elements that match a selector
// in mSelectorsForDescendants and call AddPendingRestyle for them.
void ConditionallyRestyleChildren();
void ConditionallyRestyleChildren(nsIFrame* aFrame,
Element* aRestyleRoot);
void ConditionallyRestyleContentChildren(nsIFrame* aFrame,
Element* aRestyleRoot);
void ConditionallyRestyleUndisplayedDescendants(nsIFrame* aFrame,
Element* aRestyleRoot);
void DoConditionallyRestyleUndisplayedDescendants(nsIContent* aParent,
Element* aRestyleRoot);
void ConditionallyRestyleUndisplayedNodes(UndisplayedNode* aUndisplayed,
nsIContent* aUndisplayedParent,
const StyleDisplay aDisplay,
Element* aRestyleRoot);
void ConditionallyRestyleContentDescendants(Element* aElement,
Element* aRestyleRoot);
bool ConditionallyRestyle(nsIFrame* aFrame, Element* aRestyleRoot);
bool ConditionallyRestyle(Element* aElement, Element* aRestyleRoot);
#ifdef RESTYLE_LOGGING
int32_t& LoggingDepth() { return mLoggingDepth; }
#endif
#ifdef DEBUG
static nsCString RestyleResultToString(RestyleResult aRestyleResult);
#endif
private:
nsPresContext* const mPresContext;
nsIFrame* const mFrame;
nsIContent* const mParentContent;
// |mContent| is the node that we used for rule matching of
// normal elements (not pseudo-elements) and for which we generate
// framechange hints if we need them.
nsIContent* const mContent;
nsStyleChangeList* const mChangeList;
// We have already generated change list entries for hints listed in
// mHintsHandled (initially it's those handled by ancestors, but by
// the end of Restyle it is those handled for this frame as well). We
// need to generate a new change list entry for the frame when its
// style comparision returns a hint other than one of these hints.
nsChangeHint mHintsHandled;
// See nsStyleContext::CalcStyleDifference
nsChangeHint mParentFrameHintsNotHandledForDescendants;
nsChangeHint mHintsNotHandledForDescendants;
RestyleTracker& mRestyleTracker;
nsTArray<nsCSSSelector*>& mSelectorsForDescendants;
TreeMatchContext& mTreeMatchContext;
nsIFrame* mResolvedChild; // child that provides our parent style context
// Array of style context subtrees in which we need to clear out cached
// structs at the end of the restyle (after change hints have been
// processed).
nsTArray<ContextToClear>& mContextsToClear;
// Style contexts that had old structs swapped into it and which should
// stay alive until the end of the restyle. (See comment in
// ElementRestyler::Restyle.)
nsTArray<RefPtr<nsStyleContext>>& mSwappedStructOwners;
// Whether this is the root of the restyle.
bool mIsRootOfRestyle;
#ifdef ACCESSIBILITY
const DesiredA11yNotifications mDesiredA11yNotifications;
DesiredA11yNotifications mKidsDesiredA11yNotifications;
A11yNotificationType mOurA11yNotification;
nsTArray<nsIContent*>& mVisibleKidsOfHiddenElement;
bool mWasFrameVisible;
#endif
#ifdef RESTYLE_LOGGING
int32_t mLoggingDepth;
#endif
};
/**
* This pushes any display:contents nodes onto a TreeMatchContext.
* Use it before resolving style for kids of aParent where aParent
* (and further ancestors) may be display:contents nodes which have
* not yet been pushed onto TreeMatchContext.
*/
class MOZ_RAII AutoDisplayContentsAncestorPusher final
{
public:
typedef mozilla::dom::Element Element;
AutoDisplayContentsAncestorPusher(TreeMatchContext& aTreeMatchContext,
nsPresContext* aPresContext,
nsIContent* aParent);
~AutoDisplayContentsAncestorPusher();
bool IsEmpty() const { return mAncestors.Length() == 0; }
private:
TreeMatchContext& mTreeMatchContext;
nsPresContext* const mPresContext;
AutoTArray<mozilla::dom::Element*, 4> mAncestors;
};
} // namespace mozilla
#endif /* mozilla_RestyleManager_h */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,169 @@
/* -*- 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 mozilla_RestyleManagerBase_h
#define mozilla_RestyleManagerBase_h
#include "mozilla/OverflowChangedTracker.h"
#include "nsChangeHint.h"
#include "nsPresContext.h"
class nsCString;
class nsCSSFrameConstructor;
class nsStyleChangeList;
namespace mozilla {
class EventStates;
class RestyleManager;
class ServoRestyleManager;
namespace dom {
class Element;
}
/**
* Class for sharing data and logic common to both RestyleManager and
* ServoRestyleManager.
*/
class RestyleManagerBase
{
protected:
explicit RestyleManagerBase(nsPresContext* aPresContext);
public:
typedef mozilla::dom::Element Element;
// Get an integer that increments every time we process pending restyles.
// The value is never 0.
uint32_t GetRestyleGeneration() const { return mRestyleGeneration; }
// Get an integer that increments every time there is a style change
// as a result of a change to the :hover content state.
uint32_t GetHoverGeneration() const { return mHoverGeneration; }
bool ObservingRefreshDriver() const { return mObservingRefreshDriver; }
void SetObservingRefreshDriver(bool aObserving) {
mObservingRefreshDriver = aObserving;
}
void Disconnect() { mPresContext = nullptr; }
static nsCString RestyleHintToString(nsRestyleHint aHint);
#ifdef DEBUG
static nsCString ChangeHintToString(nsChangeHint aHint);
/**
* DEBUG ONLY method to verify integrity of style tree versus frame tree
*/
static void DebugVerifyStyleTree(nsIFrame* aFrame);
#endif
void FlushOverflowChangedTracker() {
mOverflowChangedTracker.Flush();
}
// Should be called when a frame is going to be destroyed and
// WillDestroyFrameTree hasn't been called yet.
void NotifyDestroyingFrame(nsIFrame* aFrame) {
mOverflowChangedTracker.RemoveFrame(aFrame);
}
// Note: It's the caller's responsibility to make sure to wrap a
// ProcessRestyledFrames call in a view update batch and a script blocker.
// This function does not call ProcessAttachedQueue() on the binding manager.
// If the caller wants that to happen synchronously, it needs to handle that
// itself.
nsresult ProcessRestyledFrames(nsStyleChangeList& aChangeList);
protected:
void ContentStateChangedInternal(Element* aElement,
EventStates aStateMask,
nsChangeHint* aOutChangeHint,
nsRestyleHint* aOutRestyleHint);
bool IsDisconnected() { return mPresContext == nullptr; }
void IncrementHoverGeneration() {
++mHoverGeneration;
}
void IncrementRestyleGeneration() {
if (++mRestyleGeneration == 0) {
// Keep mRestyleGeneration from being 0, since that's what
// nsPresContext::GetRestyleGeneration returns when it no
// longer has a RestyleManager.
++mRestyleGeneration;
}
}
nsPresContext* PresContext() const {
MOZ_ASSERT(mPresContext);
return mPresContext;
}
nsCSSFrameConstructor* FrameConstructor() const {
return PresContext()->FrameConstructor();
}
inline bool IsGecko() const {
return !IsServo();
}
inline bool IsServo() const {
#ifdef MOZ_STYLO
return PresContext()->StyleSet()->IsServo();
#else
return false;
#endif
}
private:
nsPresContext* mPresContext; // weak, can be null after Disconnect().
uint32_t mRestyleGeneration;
uint32_t mHoverGeneration;
// True if we're already waiting for a refresh notification.
bool mObservingRefreshDriver;
protected:
// True if we're in the middle of a nsRefreshDriver refresh
bool mInStyleRefresh;
OverflowChangedTracker mOverflowChangedTracker;
void PostRestyleEventInternal(bool aForLazyConstruction);
/**
* These are protected static methods that help with the change hint
* processing bits of the restyle managers.
*/
static nsIFrame*
GetNearestAncestorFrame(nsIContent* aContent);
static nsIFrame*
GetNextBlockInInlineSibling(FramePropertyTable* aPropTable, nsIFrame* aFrame);
/**
* Get the next continuation or similar ib-split sibling (assuming
* block/inline alternation), conditionally on it having the same style.
*
* Since this is used when deciding to copy the new style context, it
* takes as an argument the old style context to check if the style is
* the same. When it is used in other contexts (i.e., where the next
* continuation would already have the new style context), the current
* style context should be passed.
*/
static nsIFrame*
GetNextContinuationWithSameStyle(nsIFrame* aFrame,
nsStyleContext* aOldStyleContext,
bool* aHaveMoreContinuations = nullptr);
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,218 @@
/* -*- 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 mozilla_RestyleManagerHandle_h
#define mozilla_RestyleManagerHandle_h
#include "mozilla/Assertions.h"
#include "mozilla/EventStates.h"
#include "mozilla/HandleRefPtr.h"
#include "mozilla/RefCountType.h"
#include "mozilla/StyleBackendType.h"
#include "nsChangeHint.h"
namespace mozilla {
class RestyleManager;
class ServoRestyleManager;
namespace dom {
class Element;
} // namespace dom
} // namespace mozilla
class nsAttrValue;
class nsIAtom;
class nsIContent;
class nsIFrame;
class nsStyleChangeList;
namespace mozilla {
#define SERVO_BIT 0x1
/**
* Smart pointer class that can hold a pointer to either a RestyleManager
* or a ServoRestyleManager.
*/
class RestyleManagerHandle
{
public:
typedef HandleRefPtr<RestyleManagerHandle> RefPtr;
// We define this Ptr class with a RestyleManager API that forwards on to the
// wrapped pointer, rather than putting these methods on RestyleManagerHandle
// itself, so that we can have RestyleManagerHandle behave like a smart
// pointer and be dereferenced with operator->.
class Ptr
{
public:
friend class ::mozilla::RestyleManagerHandle;
bool IsGecko() const { return !IsServo(); }
bool IsServo() const
{
MOZ_ASSERT(mValue, "RestyleManagerHandle null pointer dereference");
#ifdef MOZ_STYLO
return mValue & SERVO_BIT;
#else
return false;
#endif
}
StyleBackendType BackendType() const
{
return IsGecko() ? StyleBackendType::Gecko :
StyleBackendType::Servo;
}
RestyleManager* AsGecko()
{
MOZ_ASSERT(IsGecko());
return reinterpret_cast<RestyleManager*>(mValue);
}
ServoRestyleManager* AsServo()
{
MOZ_ASSERT(IsServo());
return reinterpret_cast<ServoRestyleManager*>(mValue & ~SERVO_BIT);
}
RestyleManager* GetAsGecko() { return IsGecko() ? AsGecko() : nullptr; }
ServoRestyleManager* GetAsServo() { return IsServo() ? AsServo() : nullptr; }
const RestyleManager* AsGecko() const
{
return const_cast<Ptr*>(this)->AsGecko();
}
const ServoRestyleManager* AsServo() const
{
MOZ_ASSERT(IsServo());
return const_cast<Ptr*>(this)->AsServo();
}
const RestyleManager* GetAsGecko() const { return IsGecko() ? AsGecko() : nullptr; }
const ServoRestyleManager* GetAsServo() const { return IsServo() ? AsServo() : nullptr; }
// These inline methods are defined in RestyleManagerHandleInlines.h.
inline MozExternalRefCountType AddRef();
inline MozExternalRefCountType Release();
// Restyle manager interface. These inline methods are defined in
// RestyleManagerHandleInlines.h and just forward to the underlying
// RestyleManager or ServoRestyleManager. See corresponding comments in
// RestyleManager.h for descriptions of these methods.
inline void Disconnect();
inline void PostRestyleEvent(dom::Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint);
inline void PostRestyleEventForLazyConstruction();
inline void RebuildAllStyleData(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint);
inline void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint);
inline void ProcessPendingRestyles();
inline void ContentInserted(nsINode* aContainer,
nsIContent* aChild);
inline void ContentAppended(nsIContent* aContainer,
nsIContent* aFirstNewContent);
inline void ContentRemoved(nsINode* aContainer,
nsIContent* aOldChild,
nsIContent* aFollowingSibling);
inline void RestyleForInsertOrChange(nsINode* aContainer,
nsIContent* aChild);
inline void RestyleForAppend(nsIContent* aContainer,
nsIContent* aFirstNewContent);
inline nsresult ContentStateChanged(nsIContent* aContent,
EventStates aStateMask);
inline void AttributeWillChange(dom::Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aNewValue);
inline void AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue);
inline nsresult ReparentStyleContext(nsIFrame* aFrame);
inline bool HasPendingRestyles();
inline uint64_t GetRestyleGeneration() const;
inline uint32_t GetHoverGeneration() const;
inline void SetObservingRefreshDriver(bool aObserving);
inline nsresult ProcessRestyledFrames(nsStyleChangeList& aChangeList);
inline void FlushOverflowChangedTracker();
inline void NotifyDestroyingFrame(nsIFrame* aFrame);
private:
// Stores a pointer to an RestyleManager or a ServoRestyleManager. The least
// significant bit is 0 for the former, 1 for the latter. This is
// valid as the least significant bit will never be used for a pointer
// value on platforms we care about.
uintptr_t mValue;
};
MOZ_IMPLICIT RestyleManagerHandle(decltype(nullptr) = nullptr)
{
mPtr.mValue = 0;
}
RestyleManagerHandle(const RestyleManagerHandle& aOth)
{
mPtr.mValue = aOth.mPtr.mValue;
}
MOZ_IMPLICIT RestyleManagerHandle(RestyleManager* aManager)
{
*this = aManager;
}
MOZ_IMPLICIT RestyleManagerHandle(ServoRestyleManager* aManager)
{
*this = aManager;
}
RestyleManagerHandle& operator=(decltype(nullptr))
{
mPtr.mValue = 0;
return *this;
}
RestyleManagerHandle& operator=(RestyleManager* aManager)
{
MOZ_ASSERT(!(reinterpret_cast<uintptr_t>(aManager) & SERVO_BIT),
"least significant bit shouldn't be set; we use it for state");
mPtr.mValue = reinterpret_cast<uintptr_t>(aManager);
return *this;
}
RestyleManagerHandle& operator=(ServoRestyleManager* aManager)
{
#ifdef MOZ_STYLO
MOZ_ASSERT(!(reinterpret_cast<uintptr_t>(aManager) & SERVO_BIT),
"least significant bit shouldn't be set; we use it for state");
mPtr.mValue =
aManager ? (reinterpret_cast<uintptr_t>(aManager) | SERVO_BIT) : 0;
return *this;
#else
MOZ_CRASH("should not have a ServoRestyleManager object when MOZ_STYLO is "
"disabled");
#endif
}
// Make RestyleManagerHandle usable in boolean contexts.
explicit operator bool() const { return !!mPtr.mValue; }
bool operator!() const { return !mPtr.mValue; }
// Make RestyleManagerHandle behave like a smart pointer.
Ptr* operator->() { return &mPtr; }
const Ptr* operator->() const { return &mPtr; }
private:
Ptr mPtr;
};
#undef SERVO_BIT
} // namespace mozilla
#endif // mozilla_RestyleManagerHandle_h

View file

@ -0,0 +1,195 @@
/* -*- 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 mozilla_RestyleManagerHandleInlines_h
#define mozilla_RestyleManagerHandleInlines_h
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoRestyleManager.h"
#define FORWARD_CONCRETE(method_, geckoargs_, servoargs_) \
if (IsGecko()) { \
return AsGecko()->method_ geckoargs_; \
} else { \
return AsServo()->method_ servoargs_; \
}
#define FORWARD(method_, args_) FORWARD_CONCRETE(method_, args_, args_)
namespace mozilla {
MozExternalRefCountType
RestyleManagerHandle::Ptr::AddRef()
{
FORWARD(AddRef, ());
}
MozExternalRefCountType
RestyleManagerHandle::Ptr::Release()
{
FORWARD(Release, ());
}
void
RestyleManagerHandle::Ptr::Disconnect()
{
FORWARD(Disconnect, ());
}
void
RestyleManagerHandle::Ptr::PostRestyleEvent(dom::Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint)
{
FORWARD(PostRestyleEvent, (aElement, aRestyleHint, aMinChangeHint));
}
void
RestyleManagerHandle::Ptr::PostRestyleEventForLazyConstruction()
{
FORWARD(PostRestyleEventForLazyConstruction, ());
}
void
RestyleManagerHandle::Ptr::RebuildAllStyleData(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint)
{
FORWARD(RebuildAllStyleData, (aExtraHint, aRestyleHint));
}
void
RestyleManagerHandle::Ptr::PostRebuildAllStyleDataEvent(
nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint)
{
FORWARD(PostRebuildAllStyleDataEvent, (aExtraHint, aRestyleHint));
}
void
RestyleManagerHandle::Ptr::ProcessPendingRestyles()
{
FORWARD(ProcessPendingRestyles, ());
}
nsresult
RestyleManagerHandle::Ptr::ProcessRestyledFrames(nsStyleChangeList& aChangeList)
{
FORWARD(ProcessRestyledFrames, (aChangeList));
}
void
RestyleManagerHandle::Ptr::FlushOverflowChangedTracker()
{
FORWARD(FlushOverflowChangedTracker, ());
}
void
RestyleManagerHandle::Ptr::ContentInserted(nsINode* aContainer,
nsIContent* aChild)
{
FORWARD(ContentInserted, (aContainer, aChild));
}
void
RestyleManagerHandle::Ptr::ContentAppended(nsIContent* aContainer,
nsIContent* aFirstNewContent)
{
FORWARD(ContentAppended, (aContainer, aFirstNewContent));
}
void
RestyleManagerHandle::Ptr::ContentRemoved(nsINode* aContainer,
nsIContent* aOldChild,
nsIContent* aFollowingSibling)
{
FORWARD(ContentRemoved, (aContainer, aOldChild, aFollowingSibling));
}
void
RestyleManagerHandle::Ptr::RestyleForInsertOrChange(nsINode* aContainer,
nsIContent* aChild)
{
FORWARD(RestyleForInsertOrChange, (aContainer, aChild));
}
void
RestyleManagerHandle::Ptr::RestyleForAppend(nsIContent* aContainer,
nsIContent* aFirstNewContent)
{
FORWARD(RestyleForAppend, (aContainer, aFirstNewContent));
}
nsresult
RestyleManagerHandle::Ptr::ContentStateChanged(nsIContent* aContent,
EventStates aStateMask)
{
FORWARD(ContentStateChanged, (aContent, aStateMask));
}
void
RestyleManagerHandle::Ptr::AttributeWillChange(dom::Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aNewValue)
{
FORWARD(AttributeWillChange, (aElement, aNameSpaceID, aAttribute, aModType,
aNewValue));
}
void
RestyleManagerHandle::Ptr::AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue)
{
FORWARD(AttributeChanged, (aElement, aNameSpaceID, aAttribute, aModType,
aOldValue));
}
nsresult
RestyleManagerHandle::Ptr::ReparentStyleContext(nsIFrame* aFrame)
{
FORWARD(ReparentStyleContext, (aFrame));
}
bool
RestyleManagerHandle::Ptr::HasPendingRestyles()
{
FORWARD(HasPendingRestyles, ());
}
uint64_t
RestyleManagerHandle::Ptr::GetRestyleGeneration() const
{
FORWARD(GetRestyleGeneration, ());
}
uint32_t
RestyleManagerHandle::Ptr::GetHoverGeneration() const
{
FORWARD(GetHoverGeneration, ());
}
void
RestyleManagerHandle::Ptr::SetObservingRefreshDriver(bool aObserving)
{
FORWARD(SetObservingRefreshDriver, (aObserving));
}
void
RestyleManagerHandle::Ptr::NotifyDestroyingFrame(nsIFrame* aFrame)
{
FORWARD(NotifyDestroyingFrame, (aFrame));
}
} // namespace mozilla
#undef FORWARD
#undef FORWARD_CONCRETE
#endif // mozilla_RestyleManagerHandleInlines_h

View file

@ -0,0 +1,493 @@
/* -*- Mode: C++; 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/. */
/**
* A class which manages pending restyles. This handles keeping track
* of what nodes restyles need to happen on and so forth.
*/
#include "RestyleTracker.h"
#include "GeckoProfiler.h"
#include "nsDocShell.h"
#include "nsFrameManager.h"
#include "nsIDocument.h"
#include "nsStyleChangeList.h"
#include "mozilla/RestyleManager.h"
#include "RestyleTrackerInlines.h"
#include "nsTransitionManager.h"
#include "mozilla/RestyleTimelineMarker.h"
namespace mozilla {
#ifdef RESTYLE_LOGGING
static nsCString
GetDocumentURI(nsIDocument* aDocument)
{
nsCString result;
nsAutoString url;
nsresult rv = aDocument->GetDocumentURI(url);
if (NS_SUCCEEDED(rv)) {
result.Append(NS_ConvertUTF16toUTF8(url).get());
}
return result;
}
static nsCString
FrameTagToString(dom::Element* aElement)
{
nsCString result;
nsIFrame* frame = aElement->GetPrimaryFrame();
if (frame) {
nsFrame::ListTag(result, frame);
} else {
nsAutoString buf;
aElement->NodeInfo()->NameAtom()->ToString(buf);
result.AppendPrintf("(%s@%p)", NS_ConvertUTF16toUTF8(buf).get(), aElement);
}
return result;
}
#endif
inline nsIDocument*
RestyleTracker::Document() const {
return mRestyleManager->PresContext()->Document();
}
#define RESTYLE_ARRAY_STACKSIZE 128
struct RestyleEnumerateData : RestyleTracker::Hints {
RefPtr<dom::Element> mElement;
#if defined(MOZ_ENABLE_PROFILER_SPS)
UniquePtr<ProfilerBacktrace> mBacktrace;
#endif
};
inline void
RestyleTracker::ProcessOneRestyle(Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aChangeHint,
const RestyleHintData& aRestyleHintData)
{
NS_PRECONDITION((aRestyleHint & eRestyle_LaterSiblings) == 0,
"Someone should have handled this before calling us");
NS_PRECONDITION(Document(), "Must have a document");
NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
"Element has unexpected document");
LOG_RESTYLE("aRestyleHint = %s, aChangeHint = %s",
RestyleManager::RestyleHintToString(aRestyleHint).get(),
RestyleManager::ChangeHintToString(aChangeHint).get());
nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
if (aRestyleHint & ~eRestyle_LaterSiblings) {
#ifdef RESTYLE_LOGGING
if (ShouldLogRestyle() && primaryFrame &&
RestyleManager::StructsToLog() != 0) {
LOG_RESTYLE("style context tree before restyle:");
LOG_RESTYLE_INDENT();
primaryFrame->StyleContext()->LogStyleContextTree(
LoggingDepth(), RestyleManager::StructsToLog());
}
#endif
mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint,
*this, aRestyleHint, aRestyleHintData);
} else if (aChangeHint &&
(primaryFrame ||
(aChangeHint & nsChangeHint_ReconstructFrame))) {
// Don't need to recompute style; just apply the hint
nsStyleChangeList changeList;
changeList.AppendChange(primaryFrame, aElement, aChangeHint);
mRestyleManager->ProcessRestyledFrames(changeList);
}
}
void
RestyleTracker::DoProcessRestyles()
{
nsAutoCString docURL("N/A");
if (profiler_is_active()) {
nsIURI *uri = Document()->GetDocumentURI();
if (uri) {
docURL = uri->GetSpecOrDefault();
}
}
PROFILER_LABEL_PRINTF("RestyleTracker", "ProcessRestyles",
js::ProfileEntry::Category::CSS, "(%s)", docURL.get());
nsDocShell* docShell = static_cast<nsDocShell*>(mRestyleManager->PresContext()->GetDocShell());
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
bool isTimelineRecording = timelines && timelines->HasConsumer(docShell);
// Create a AnimationsWithDestroyedFrame during restyling process to
// stop animations and transitions on elements that have no frame at the end
// of the restyling process.
RestyleManager::AnimationsWithDestroyedFrame
animationsWithDestroyedFrame(mRestyleManager);
// Create a ReframingStyleContexts struct on the stack and put it in our
// mReframingStyleContexts for almost all of the remaining scope of
// this function.
//
// It needs to be *in* scope during BeginProcessingRestyles, which
// might (if mDoRebuildAllStyleData is true) do substantial amounts of
// restyle processing.
//
// However, it needs to be *out* of scope during
// EndProcessingRestyles, since we should release the style contexts
// it holds prior to any EndReconstruct call that
// EndProcessingRestyles makes. This is because in EndReconstruct we
// try to destroy the old rule tree using the GC mechanism, which
// means it only gets destroyed if it's unreferenced (and if it's
// referenced, we assert). So we want the ReframingStyleContexts
// (which holds old style contexts) to be destroyed before the
// EndReconstruct so those style contexts go away before
// EndReconstruct.
{
RestyleManager::ReframingStyleContexts
reframingStyleContexts(mRestyleManager);
mRestyleManager->BeginProcessingRestyles(*this);
LOG_RESTYLE("Processing %d pending %srestyles with %d restyle roots for %s",
mPendingRestyles.Count(),
mRestyleManager->PresContext()->TransitionManager()->
InAnimationOnlyStyleUpdate()
? (const char*) "animation " : (const char*) "",
static_cast<int>(mRestyleRoots.Length()),
GetDocumentURI(Document()).get());
LOG_RESTYLE_INDENT();
// loop so that we process any restyle events generated by processing
while (mPendingRestyles.Count()) {
if (mHaveLaterSiblingRestyles) {
// Convert them to individual restyles on all the later siblings
AutoTArray<RefPtr<Element>, RESTYLE_ARRAY_STACKSIZE> laterSiblingArr;
for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
auto element = static_cast<dom::Element*>(iter.Key());
MOZ_ASSERT(!element->IsStyledByServo(),
"Should not have Servo-styled elements here");
// Only collect the entries that actually need restyling by us (and
// haven't, for example, already been restyled).
// It's important to not mess with the flags on entries not in our
// document.
if (element->GetComposedDoc() == Document() &&
element->HasFlag(RestyleBit()) &&
(iter.Data()->mRestyleHint & eRestyle_LaterSiblings)) {
laterSiblingArr.AppendElement(element);
}
}
for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
Element* element = laterSiblingArr[i];
MOZ_ASSERT(!element->IsStyledByServo());
for (nsIContent* sibling = element->GetNextSibling();
sibling;
sibling = sibling->GetNextSibling()) {
if (sibling->IsElement()) {
LOG_RESTYLE("adding pending restyle for %s due to "
"eRestyle_LaterSiblings hint on %s",
FrameTagToString(sibling->AsElement()).get(),
FrameTagToString(element->AsElement()).get());
if (AddPendingRestyle(sibling->AsElement(), eRestyle_Subtree,
nsChangeHint(0))) {
// Nothing else to do here; we'll handle the following
// siblings when we get to |sibling| in laterSiblingArr.
break;
}
}
}
}
// Now remove all those eRestyle_LaterSiblings bits
for (uint32_t i = 0; i < laterSiblingArr.Length(); ++i) {
Element* element = laterSiblingArr[i];
NS_ASSERTION(element->HasFlag(RestyleBit()), "How did that happen?");
RestyleData* data;
#ifdef DEBUG
bool found =
#endif
mPendingRestyles.Get(element, &data);
NS_ASSERTION(found, "Where did our entry go?");
data->mRestyleHint =
nsRestyleHint(data->mRestyleHint & ~eRestyle_LaterSiblings);
}
LOG_RESTYLE("%d pending restyles after expanding out "
"eRestyle_LaterSiblings", mPendingRestyles.Count());
mHaveLaterSiblingRestyles = false;
}
uint32_t rootCount;
while ((rootCount = mRestyleRoots.Length())) {
// Make sure to pop the element off our restyle root array, so
// that we can freely append to the array as we process this
// element.
RefPtr<Element> element;
element.swap(mRestyleRoots[rootCount - 1]);
mRestyleRoots.RemoveElementAt(rootCount - 1);
LOG_RESTYLE("processing style root %s at index %d",
FrameTagToString(element).get(), rootCount - 1);
LOG_RESTYLE_INDENT();
// Do the document check before calling GetRestyleData, since we
// don't want to do the sibling-processing GetRestyleData does if
// the node is no longer relevant.
if (element->GetComposedDoc() != Document()) {
// Content node has been removed from our document; nothing else
// to do here
LOG_RESTYLE("skipping, no longer in the document");
continue;
}
nsAutoPtr<RestyleData> data;
if (!GetRestyleData(element, data)) {
LOG_RESTYLE("skipping, already restyled");
continue;
}
if (isTimelineRecording) {
timelines->AddMarkerForDocShell(docShell, Move(
MakeUnique<RestyleTimelineMarker>(
data->mRestyleHint, MarkerTracingType::START)));
}
#if defined(MOZ_ENABLE_PROFILER_SPS)
Maybe<GeckoProfilerTracingRAII> profilerRAII;
if (profiler_feature_active("restyle")) {
profilerRAII.emplace("Paint", "Styles", Move(data->mBacktrace));
}
#endif
ProcessOneRestyle(element, data->mRestyleHint, data->mChangeHint,
data->mRestyleHintData);
AddRestyleRootsIfAwaitingRestyle(data->mDescendants);
if (isTimelineRecording) {
timelines->AddMarkerForDocShell(docShell, Move(
MakeUnique<RestyleTimelineMarker>(
data->mRestyleHint, MarkerTracingType::END)));
}
}
if (mHaveLaterSiblingRestyles) {
// Keep processing restyles for now
continue;
}
// Now we only have entries with change hints left. To be safe in
// case of reentry from the handing of the change hint, use a
// scratch array instead of calling out to ProcessOneRestyle while
// enumerating the hashtable. Use the stack if we can, otherwise
// fall back on heap-allocation.
AutoTArray<RestyleEnumerateData, RESTYLE_ARRAY_STACKSIZE> restyleArr;
RestyleEnumerateData* restylesToProcess =
restyleArr.AppendElements(mPendingRestyles.Count());
if (restylesToProcess) {
RestyleEnumerateData* restyle = restylesToProcess;
#ifdef RESTYLE_LOGGING
uint32_t count = 0;
#endif
for (auto iter = mPendingRestyles.Iter(); !iter.Done(); iter.Next()) {
auto element = static_cast<dom::Element*>(iter.Key());
RestyleTracker::RestyleData* data = iter.Data();
// Only collect the entries that actually need restyling by us (and
// haven't, for example, already been restyled).
// It's important to not mess with the flags on entries not in our
// document.
if (element->GetComposedDoc() != Document() ||
!element->HasFlag(RestyleBit())) {
LOG_RESTYLE("skipping pending restyle %s, already restyled or no "
"longer in the document",
FrameTagToString(element).get());
continue;
}
NS_ASSERTION(
!element->HasFlag(RootBit()) ||
// Maybe we're just not reachable via the frame tree?
(element->GetFlattenedTreeParent() &&
(!element->GetFlattenedTreeParent()->GetPrimaryFrame() ||
element->GetFlattenedTreeParent()->GetPrimaryFrame()->IsLeaf() ||
element->GetComposedDoc()->GetShell()->FrameManager()
->GetDisplayContentsStyleFor(element))) ||
// Or not reachable due to an async reinsert we have
// pending? If so, we'll have a reframe hint around.
// That incidentally makes it safe that we still have
// the bit, since any descendants that didn't get added
// to the roots list because we had the bits will be
// completely restyled in a moment.
(data->mChangeHint & nsChangeHint_ReconstructFrame),
"Why did this not get handled while processing mRestyleRoots?");
// Unset the restyle bits now, so if they get readded later as we
// process we won't clobber that adding of the bit.
element->UnsetFlags(RestyleBit() |
RootBit() |
ConditionalDescendantsBit());
restyle->mElement = element;
restyle->mRestyleHint = data->mRestyleHint;
restyle->mChangeHint = data->mChangeHint;
// We can move data since we'll be clearing mPendingRestyles after
// we finish enumerating it.
restyle->mRestyleHintData = Move(data->mRestyleHintData);
#if defined(MOZ_ENABLE_PROFILER_SPS)
restyle->mBacktrace = Move(data->mBacktrace);
#endif
#ifdef RESTYLE_LOGGING
count++;
#endif
// Increment to the next slot in the array
restyle++;
}
RestyleEnumerateData* lastRestyle = restyle;
// Clear the hashtable now that we don't need it anymore
mPendingRestyles.Clear();
#ifdef RESTYLE_LOGGING
uint32_t index = 0;
#endif
for (RestyleEnumerateData* currentRestyle = restylesToProcess;
currentRestyle != lastRestyle;
++currentRestyle) {
LOG_RESTYLE("processing pending restyle %s at index %d/%d",
FrameTagToString(currentRestyle->mElement).get(),
index++, count);
LOG_RESTYLE_INDENT();
#if defined(MOZ_ENABLE_PROFILER_SPS)
Maybe<GeckoProfilerTracingRAII> profilerRAII;
if (profiler_feature_active("restyle")) {
profilerRAII.emplace("Paint", "Styles", Move(currentRestyle->mBacktrace));
}
#endif
if (isTimelineRecording) {
timelines->AddMarkerForDocShell(docShell, Move(
MakeUnique<RestyleTimelineMarker>(
currentRestyle->mRestyleHint, MarkerTracingType::START)));
}
ProcessOneRestyle(currentRestyle->mElement,
currentRestyle->mRestyleHint,
currentRestyle->mChangeHint,
currentRestyle->mRestyleHintData);
if (isTimelineRecording) {
timelines->AddMarkerForDocShell(docShell, Move(
MakeUnique<RestyleTimelineMarker>(
currentRestyle->mRestyleHint, MarkerTracingType::END)));
}
}
}
}
}
// mPendingRestyles is now empty.
mHaveSelectors = false;
mRestyleManager->EndProcessingRestyles();
}
bool
RestyleTracker::GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData)
{
NS_PRECONDITION(aElement->GetComposedDoc() == Document(),
"Unexpected document; this will lead to incorrect behavior!");
if (!aElement->HasFlag(RestyleBit())) {
NS_ASSERTION(!aElement->HasFlag(RootBit()), "Bogus root bit?");
return false;
}
mPendingRestyles.RemoveAndForget(aElement, aData);
NS_ASSERTION(aData.get(), "Must have data if restyle bit is set");
if (aData->mRestyleHint & eRestyle_LaterSiblings) {
// Someone readded the eRestyle_LaterSiblings hint for this
// element. Leave it around for now, but remove the other restyle
// hints and the change hint for it. Also unset its root bit,
// since it's no longer a root with the new restyle data.
// During a normal restyle, we should have already processed the
// mDescendants array the last time we processed the restyle
// for this element. But in RebuildAllStyleData, we don't initially
// expand out eRestyle_LaterSiblings, so we can get in here the
// first time we need to process a restyle for this element. In that
// case, it's fine for us to have a non-empty mDescendants, since
// we know that RebuildAllStyleData adds eRestyle_ForceDescendants
// and we're guaranteed we'll restyle the entire tree.
NS_ASSERTION(mRestyleManager->InRebuildAllStyleData() ||
aData->mDescendants.IsEmpty(),
"expected descendants to be handled by now");
RestyleData* newData = new RestyleData;
newData->mChangeHint = nsChangeHint(0);
newData->mRestyleHint = eRestyle_LaterSiblings;
mPendingRestyles.Put(aElement, newData);
aElement->UnsetFlags(RootBit());
aData->mRestyleHint =
nsRestyleHint(aData->mRestyleHint & ~eRestyle_LaterSiblings);
} else {
aElement->UnsetFlags(mRestyleBits);
}
return true;
}
void
RestyleTracker::AddRestyleRootsIfAwaitingRestyle(
const nsTArray<RefPtr<Element>>& aElements)
{
// The RestyleData for a given element has stored in mDescendants
// the list of descendants we need to end up restyling. Since we
// won't necessarily end up restyling them, due to the restyle
// process finishing early (see how RestyleResult::eStop is handled
// in ElementRestyler::Restyle), we add them to the list of restyle
// roots to handle the next time around the
// RestyleTracker::DoProcessRestyles loop.
//
// Note that aElements must maintain the same invariant
// that mRestyleRoots does, i.e. that ancestors appear after descendants.
// Since we call AddRestyleRootsIfAwaitingRestyle only after we have
// removed the restyle root we are currently processing from the end of
// mRestyleRoots, and the only elements we get here in aElements are
// descendants of that restyle root, we are safe to simply append to the
// end of mRestyleRoots to maintain its invariant.
for (size_t i = 0; i < aElements.Length(); i++) {
Element* element = aElements[i];
if (element->HasFlag(RestyleBit())) {
mRestyleRoots.AppendElement(element);
}
}
}
void
RestyleTracker::ClearSelectors()
{
if (!mHaveSelectors) {
return;
}
for (auto it = mPendingRestyles.Iter(); !it.Done(); it.Next()) {
RestyleData* data = it.Data();
if (data->mRestyleHint & eRestyle_SomeDescendants) {
data->mRestyleHint =
(data->mRestyleHint & ~eRestyle_SomeDescendants) | eRestyle_Subtree;
data->mRestyleHintData.mSelectorsForDescendants.Clear();
} else {
MOZ_ASSERT(data->mRestyleHintData.mSelectorsForDescendants.IsEmpty());
}
}
mHaveSelectors = false;
}
} // namespace mozilla

View file

@ -0,0 +1,373 @@
/* -*- Mode: C++; 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/. */
/**
* A class which manages pending restyles. This handles keeping track
* of what nodes restyles need to happen on and so forth.
*/
#ifndef mozilla_RestyleTracker_h
#define mozilla_RestyleTracker_h
#include "mozilla/dom/Element.h"
#include "mozilla/OverflowChangedTracker.h"
#include "nsAutoPtr.h"
#include "nsClassHashtable.h"
#include "nsContainerFrame.h"
#include "nsIContentInlines.h"
#include "mozilla/SplayTree.h"
#include "mozilla/RestyleLogging.h"
#include "GeckoProfiler.h"
#include "mozilla/Maybe.h"
#if defined(MOZ_ENABLE_PROFILER_SPS)
#include "ProfilerBacktrace.h"
#endif
namespace mozilla {
class RestyleManager;
class ElementRestyler;
class RestyleTracker {
public:
typedef mozilla::dom::Element Element;
friend class ElementRestyler; // for AddPendingRestyleToTable
explicit RestyleTracker(Element::FlagsType aRestyleBits)
: mRestyleBits(aRestyleBits)
, mHaveLaterSiblingRestyles(false)
, mHaveSelectors(false)
{
NS_PRECONDITION((mRestyleBits & ~ELEMENT_ALL_RESTYLE_FLAGS) == 0,
"Why do we have these bits set?");
NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
"Must have a restyle flag");
NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) !=
ELEMENT_PENDING_RESTYLE_FLAGS,
"Shouldn't have both restyle flags set");
NS_PRECONDITION((mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS) != 0,
"Must have root flag");
NS_PRECONDITION((mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS) !=
ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS,
"Shouldn't have both root flags");
}
void Init(RestyleManager* aRestyleManager) {
mRestyleManager = aRestyleManager;
}
uint32_t Count() const {
return mPendingRestyles.Count();
}
/**
* Add a restyle for the given element to the tracker. Returns true
* if the element already had eRestyle_LaterSiblings set on it.
*
* aRestyleRoot is the closest restyle root for aElement. If the caller
* does not know what the closest restyle root is, Nothing should be
* passed. A Some(nullptr) restyle root can be passed if there is no
* ancestor element that is a restyle root.
*/
bool AddPendingRestyle(Element* aElement, nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint,
const RestyleHintData* aRestyleHintData = nullptr,
mozilla::Maybe<Element*> aRestyleRoot =
mozilla::Nothing());
Element* FindClosestRestyleRoot(Element* aElement);
/**
* Process the restyles we've been tracking.
*/
void DoProcessRestyles();
// Return our ELEMENT_HAS_PENDING_(ANIMATION_)RESTYLE bit
uint32_t RestyleBit() const {
return mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS;
}
// Return our ELEMENT_IS_POTENTIAL_(ANIMATION_)RESTYLE_ROOT bit
Element::FlagsType RootBit() const {
return mRestyleBits & ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS;
}
// Return our ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR bit if present,
// or 0 if it is not.
Element::FlagsType ConditionalDescendantsBit() const {
return mRestyleBits & ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR;
}
struct Hints {
nsRestyleHint mRestyleHint; // What we want to restyle
nsChangeHint mChangeHint; // The minimal change hint for "self"
RestyleHintData mRestyleHintData; // Data associated with mRestyleHint
};
struct RestyleData : Hints {
RestyleData() {
mRestyleHint = nsRestyleHint(0);
mChangeHint = nsChangeHint(0);
}
RestyleData(nsRestyleHint aRestyleHint, nsChangeHint aChangeHint,
const RestyleHintData* aRestyleHintData) {
mRestyleHint = aRestyleHint;
mChangeHint = aChangeHint;
if (aRestyleHintData) {
mRestyleHintData = *aRestyleHintData;
}
}
// Descendant elements we must check that we ended up restyling, ordered
// with the same invariant as mRestyleRoots. The elements here are those
// that we called AddPendingRestyle for and found the element this is
// the RestyleData for as its nearest restyle root.
nsTArray<RefPtr<Element>> mDescendants;
#if defined(MOZ_ENABLE_PROFILER_SPS)
UniquePtr<ProfilerBacktrace> mBacktrace;
#endif
};
/**
* If the given Element has a restyle pending for it, return the
* relevant restyle data. This function will clear everything other
* than a possible eRestyle_LaterSiblings hint for aElement out of
* our hashtable. The returned aData will never have an
* eRestyle_LaterSiblings hint in it.
*
* The return value indicates whether any restyle data was found for
* the element. aData is set to nullptr iff false is returned.
*/
bool GetRestyleData(Element* aElement, nsAutoPtr<RestyleData>& aData);
/**
* Returns whether there is a RestyleData entry in mPendingRestyles
* for the given element.
*/
bool HasRestyleData(Element* aElement) {
return mPendingRestyles.Contains(aElement);
}
/**
* For each element in aElements, appends it to mRestyleRoots if it
* has its restyle bit set. This is used to ensure we restyle elements
* that we did not add as restyle roots initially (due to there being
* an ancestor with the restyle root bit set), but which we might
* not have got around to restyling due to the restyle process
* terminating early with RestyleResul::eStop (see ElementRestyler::Restyle).
*
* This function must be called with elements in order such that
* appending them to mRestyleRoots maintains its ordering invariant that
* ancestors appear after descendants.
*/
void AddRestyleRootsIfAwaitingRestyle(
const nsTArray<RefPtr<Element>>& aElements);
/**
* Converts any eRestyle_SomeDescendants restyle hints in the pending restyle
* table into eRestyle_Subtree hints and clears out the associated arrays of
* nsCSSSelector pointers. This is called in response to a style sheet change
* that might have cause an nsCSSSelector to be destroyed.
*/
void ClearSelectors();
/**
* The document we're associated with.
*/
inline nsIDocument* Document() const;
#ifdef RESTYLE_LOGGING
// Defined in RestyleTrackerInlines.h.
inline bool ShouldLogRestyle();
inline int32_t& LoggingDepth();
#endif
private:
bool AddPendingRestyleToTable(Element* aElement, nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint,
const RestyleHintData* aRestyleHintData = nullptr);
/**
* Handle a single mPendingRestyles entry. aRestyleHint must not
* include eRestyle_LaterSiblings; that needs to be dealt with
* before calling this function.
*/
inline void ProcessOneRestyle(Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aChangeHint,
const RestyleHintData& aRestyleHintData);
typedef nsClassHashtable<nsISupportsHashKey, RestyleData> PendingRestyleTable;
typedef AutoTArray< RefPtr<Element>, 32> RestyleRootArray;
// Our restyle bits. These will be a subset of ELEMENT_ALL_RESTYLE_FLAGS, and
// will include one flag from ELEMENT_PENDING_RESTYLE_FLAGS, one flag
// from ELEMENT_POTENTIAL_RESTYLE_ROOT_FLAGS, and might also include
// ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR.
Element::FlagsType mRestyleBits;
RestyleManager* mRestyleManager; // Owns us
// A hashtable that maps elements to pointers to RestyleData structs. The
// values only make sense if the element's current document is our
// document and it has our RestyleBit() flag set. In particular,
// said bit might not be set if the element had a restyle posted and
// then was moved around in the DOM.
PendingRestyleTable mPendingRestyles;
// An array that keeps track of our possible restyle roots. This
// maintains the invariant that if A and B are both restyle roots
// and A is an ancestor of B then A will come after B in the array.
// We maintain this invariant by checking whether an element has an
// ancestor with the restyle root bit set before appending it to the
// array.
RestyleRootArray mRestyleRoots;
// True if we have some entries with the eRestyle_LaterSiblings
// flag. We need this to avoid enumerating the hashtable looking
// for such entries when we can't possibly have any.
bool mHaveLaterSiblingRestyles;
// True if we have some entries with selectors in the restyle hint data.
// We use this to skip iterating over mPendingRestyles in ClearSelectors.
bool mHaveSelectors;
};
inline bool
RestyleTracker::AddPendingRestyleToTable(Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint,
const RestyleHintData* aRestyleHintData)
{
RestyleData* existingData;
if (aRestyleHintData &&
!aRestyleHintData->mSelectorsForDescendants.IsEmpty()) {
mHaveSelectors = true;
}
// Check the RestyleBit() flag before doing the hashtable Get, since
// it's possible that the data in the hashtable isn't actually
// relevant anymore (if the flag is not set).
if (aElement->HasFlag(RestyleBit())) {
mPendingRestyles.Get(aElement, &existingData);
} else {
aElement->SetFlags(RestyleBit());
existingData = nullptr;
}
if (aRestyleHint & eRestyle_SomeDescendants) {
NS_ASSERTION(ConditionalDescendantsBit(),
"why are we getting eRestyle_SomeDescendants in an "
"animation-only restyle?");
aElement->SetFlags(ConditionalDescendantsBit());
}
if (!existingData) {
RestyleData* rd =
new RestyleData(aRestyleHint, aMinChangeHint, aRestyleHintData);
#if defined(MOZ_ENABLE_PROFILER_SPS)
if (profiler_feature_active("restyle")) {
rd->mBacktrace.reset(profiler_get_backtrace());
}
#endif
mPendingRestyles.Put(aElement, rd);
return false;
}
bool hadRestyleLaterSiblings =
(existingData->mRestyleHint & eRestyle_LaterSiblings) != 0;
existingData->mRestyleHint =
nsRestyleHint(existingData->mRestyleHint | aRestyleHint);
existingData->mChangeHint |= aMinChangeHint;
if (aRestyleHintData) {
existingData->mRestyleHintData.mSelectorsForDescendants
.AppendElements(aRestyleHintData->mSelectorsForDescendants);
}
return hadRestyleLaterSiblings;
}
inline mozilla::dom::Element*
RestyleTracker::FindClosestRestyleRoot(Element* aElement)
{
Element* cur = aElement;
while (!cur->HasFlag(RootBit())) {
nsIContent* parent = cur->GetFlattenedTreeParent();
// Stop if we have no parent or the parent is not an element or
// we're part of the viewport scrollbars (because those are not
// frametree descendants of the primary frame of the root
// element).
// XXXbz maybe the primary frame of the root should be the root scrollframe?
if (!parent || !parent->IsElement() ||
// If we've hit the root via a native anonymous kid and that
// this native anonymous kid is not obviously a descendant
// of the root's primary frame, assume we're under the root
// scrollbars. Since those don't get reresolved when
// reresolving the root, we need to make sure to add the
// element to mRestyleRoots.
(cur->IsInNativeAnonymousSubtree() && !parent->GetParent() &&
cur->GetPrimaryFrame() &&
cur->GetPrimaryFrame()->GetParent() != parent->GetPrimaryFrame())) {
return nullptr;
}
cur = parent->AsElement();
}
return cur;
}
inline bool
RestyleTracker::AddPendingRestyle(Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint,
const RestyleHintData* aRestyleHintData,
mozilla::Maybe<Element*> aRestyleRoot)
{
bool hadRestyleLaterSiblings =
AddPendingRestyleToTable(aElement, aRestyleHint, aMinChangeHint,
aRestyleHintData);
// We can only treat this element as a restyle root if we would
// actually restyle its descendants (so either call
// ElementRestyler::Restyle on it or just reframe it).
if ((aRestyleHint & ~eRestyle_LaterSiblings) ||
(aMinChangeHint & nsChangeHint_ReconstructFrame)) {
Element* cur =
aRestyleRoot ? *aRestyleRoot : FindClosestRestyleRoot(aElement);
if (!cur) {
mRestyleRoots.AppendElement(aElement);
cur = aElement;
}
// At this point some ancestor of aElement (possibly aElement
// itself) is in mRestyleRoots. Set the root bit on aElement, to
// speed up searching for an existing root on its descendants.
aElement->SetFlags(RootBit());
if (cur != aElement) {
// We are already going to restyle cur, one of aElement's ancestors,
// but we might not end up restyling all the way down to aElement.
// Record it in the RestyleData so we can ensure it does get restyled
// after we deal with cur.
//
// As with the mRestyleRoots array, mDescendants maintains the
// invariant that if two elements appear in the array and one
// is an ancestor of the other, that the ancestor appears after
// the descendant.
RestyleData* curData;
mPendingRestyles.Get(cur, &curData);
NS_ASSERTION(curData, "expected to find a RestyleData for cur");
// If cur has an eRestyle_ForceDescendants restyle hint, then we
// know that we will get to all descendants. Don't bother
// recording the descendant to restyle in that case.
if (curData && !(curData->mRestyleHint & eRestyle_ForceDescendants)) {
curData->mDescendants.AppendElement(aElement);
}
}
}
mHaveLaterSiblingRestyles =
mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
return hadRestyleLaterSiblings;
}
} // namespace mozilla
#endif /* mozilla_RestyleTracker_h */

View file

@ -0,0 +1,23 @@
/* -*- Mode: C++; 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/. */
#ifndef mozilla_RestyleTrackerInlines_h
#define mozilla_RestyleTrackerInlines_h
#ifdef RESTYLE_LOGGING
bool
mozilla::RestyleTracker::ShouldLogRestyle()
{
return mRestyleManager->ShouldLogRestyle();
}
int32_t&
mozilla::RestyleTracker::LoggingDepth()
{
return mRestyleManager->LoggingDepth();
}
#endif
#endif // !defined(mozilla_RestyleTrackerInlines_h)

View file

@ -0,0 +1,32 @@
/* -*- Mode: C++; 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/. */
#include "ScrollbarStyles.h"
#include "nsStyleStruct.h" // for nsStyleDisplay and nsStyleBackground::Position
namespace mozilla {
ScrollbarStyles::ScrollbarStyles(uint8_t aH, uint8_t aV,
const nsStyleDisplay* aDisplay)
: mHorizontal(aH), mVertical(aV),
mScrollBehavior(aDisplay->mScrollBehavior),
mScrollSnapTypeX(aDisplay->mScrollSnapTypeX),
mScrollSnapTypeY(aDisplay->mScrollSnapTypeY),
mScrollSnapPointsX(aDisplay->mScrollSnapPointsX),
mScrollSnapPointsY(aDisplay->mScrollSnapPointsY),
mScrollSnapDestinationX(aDisplay->mScrollSnapDestination.mXPosition),
mScrollSnapDestinationY(aDisplay->mScrollSnapDestination.mYPosition) {}
ScrollbarStyles::ScrollbarStyles(const nsStyleDisplay* aDisplay)
: mHorizontal(aDisplay->mOverflowX), mVertical(aDisplay->mOverflowY),
mScrollBehavior(aDisplay->mScrollBehavior),
mScrollSnapTypeX(aDisplay->mScrollSnapTypeX),
mScrollSnapTypeY(aDisplay->mScrollSnapTypeY),
mScrollSnapPointsX(aDisplay->mScrollSnapPointsX),
mScrollSnapPointsY(aDisplay->mScrollSnapPointsY),
mScrollSnapDestinationX(aDisplay->mScrollSnapDestination.mXPosition),
mScrollSnapDestinationY(aDisplay->mScrollSnapDestination.mYPosition) {}
} // namespace mozilla

View file

@ -0,0 +1,82 @@
/* -*- Mode: C++; 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/. */
#ifndef ScrollbarStyles_h
#define ScrollbarStyles_h
#include <stdint.h>
#include "nsStyleConsts.h" // for NS_STYLE_SCROLL_SNAP_*
#include "nsStyleCoord.h" // for nsStyleCoord
#include "mozilla/dom/WindowBinding.h"
// Forward declarations
struct nsStyleDisplay;
namespace mozilla {
struct ScrollbarStyles
{
// Always one of NS_STYLE_OVERFLOW_SCROLL, NS_STYLE_OVERFLOW_HIDDEN,
// or NS_STYLE_OVERFLOW_AUTO.
uint8_t mHorizontal;
uint8_t mVertical;
// Always one of NS_STYLE_SCROLL_BEHAVIOR_AUTO or
// NS_STYLE_SCROLL_BEHAVIOR_SMOOTH
uint8_t mScrollBehavior;
// Always one of NS_STYLE_SCROLL_SNAP_NONE, NS_STYLE_SCROLL_SNAP_MANDATORY,
// or NS_STYLE_SCROLL_SNAP_PROXIMITY.
uint8_t mScrollSnapTypeX;
uint8_t mScrollSnapTypeY;
nsStyleCoord mScrollSnapPointsX;
nsStyleCoord mScrollSnapPointsY;
nsStyleCoord::CalcValue mScrollSnapDestinationX;
nsStyleCoord::CalcValue mScrollSnapDestinationY;
ScrollbarStyles(uint8_t aH, uint8_t aV)
: mHorizontal(aH), mVertical(aV),
mScrollBehavior(NS_STYLE_SCROLL_BEHAVIOR_AUTO),
mScrollSnapTypeX(NS_STYLE_SCROLL_SNAP_TYPE_NONE),
mScrollSnapTypeY(NS_STYLE_SCROLL_SNAP_TYPE_NONE),
mScrollSnapPointsX(nsStyleCoord(eStyleUnit_None)),
mScrollSnapPointsY(nsStyleCoord(eStyleUnit_None)) {
mScrollSnapDestinationX.mPercent = 0;
mScrollSnapDestinationX.mLength = nscoord(0.0f);
mScrollSnapDestinationX.mHasPercent = false;
mScrollSnapDestinationY.mPercent = 0;
mScrollSnapDestinationY.mLength = nscoord(0.0f);
mScrollSnapDestinationY.mHasPercent = false;
}
explicit ScrollbarStyles(const nsStyleDisplay* aDisplay);
ScrollbarStyles(uint8_t aH, uint8_t aV, const nsStyleDisplay* aDisplay);
ScrollbarStyles() {}
bool operator==(const ScrollbarStyles& aStyles) const {
return aStyles.mHorizontal == mHorizontal && aStyles.mVertical == mVertical &&
aStyles.mScrollBehavior == mScrollBehavior &&
aStyles.mScrollSnapTypeX == mScrollSnapTypeX &&
aStyles.mScrollSnapTypeY == mScrollSnapTypeY &&
aStyles.mScrollSnapPointsX == mScrollSnapPointsX &&
aStyles.mScrollSnapPointsY == mScrollSnapPointsY &&
aStyles.mScrollSnapDestinationX == mScrollSnapDestinationX &&
aStyles.mScrollSnapDestinationY == mScrollSnapDestinationY;
}
bool operator!=(const ScrollbarStyles& aStyles) const {
return !(*this == aStyles);
}
bool IsHiddenInBothDirections() const {
return mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
mVertical == NS_STYLE_OVERFLOW_HIDDEN;
}
bool IsSmoothScroll(dom::ScrollBehavior aBehavior) const {
return aBehavior == dom::ScrollBehavior::Smooth ||
(aBehavior == dom::ScrollBehavior::Auto &&
mScrollBehavior == NS_STYLE_SCROLL_BEHAVIOR_SMOOTH);
}
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,594 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/ServoRestyleManager.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/dom/ChildIterator.h"
#include "nsContentUtils.h"
#include "nsPrintfCString.h"
#include "nsStyleChangeList.h"
using namespace mozilla::dom;
namespace mozilla {
ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
: RestyleManagerBase(aPresContext)
{
}
void
ServoRestyleManager::PostRestyleEvent(Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint)
{
if (MOZ_UNLIKELY(IsDisconnected()) ||
MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
return;
}
if (aRestyleHint == 0 && !aMinChangeHint && !HasPendingRestyles()) {
return; // Nothing to do.
}
// XXX This is a temporary hack to make style attribute change works.
// In the future, we should be able to use this hint directly.
if (aRestyleHint & eRestyle_StyleAttribute) {
aRestyleHint &= ~eRestyle_StyleAttribute;
aRestyleHint |= eRestyle_Self | eRestyle_Subtree;
}
// Note that unlike in Servo, we don't mark elements as dirty until we process
// the restyle hints in ProcessPendingRestyles.
if (aRestyleHint || aMinChangeHint) {
ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
snapshot->AddExplicitRestyleHint(aRestyleHint);
snapshot->AddExplicitChangeHint(aMinChangeHint);
}
PostRestyleEventInternal(false);
}
void
ServoRestyleManager::PostRestyleEventForLazyConstruction()
{
PostRestyleEventInternal(true);
}
void
ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint)
{
NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData not implemented");
}
void
ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint)
{
NS_WARNING("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
}
static void
MarkSelfAndDescendantsAsNotDirtyForServo(nsIContent* aContent)
{
aContent->UnsetIsDirtyForServo();
if (aContent->HasDirtyDescendantsForServo()) {
aContent->UnsetHasDirtyDescendantsForServo();
StyleChildrenIterator it(aContent);
for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
MarkSelfAndDescendantsAsNotDirtyForServo(n);
}
}
}
void
ServoRestyleManager::RecreateStyleContexts(nsIContent* aContent,
nsStyleContext* aParentContext,
ServoStyleSet* aStyleSet,
nsStyleChangeList& aChangeListToProcess)
{
MOZ_ASSERT(aContent->IsElement() || aContent->IsNodeOfType(nsINode::eTEXT));
nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
if (!primaryFrame && !aContent->IsDirtyForServo()) {
// This happens when, for example, a display: none child of a
// HAS_DIRTY_DESCENDANTS content is reached as part of the traversal.
MarkSelfAndDescendantsAsNotDirtyForServo(aContent);
return;
}
// Work on text before.
if (!aContent->IsElement()) {
if (primaryFrame) {
RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
RefPtr<nsStyleContext> newContext =
aStyleSet->ResolveStyleForText(aContent, aParentContext);
for (nsIFrame* f = primaryFrame; f;
f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
f->SetStyleContext(newContext);
}
}
aContent->UnsetIsDirtyForServo();
return;
}
Element* element = aContent->AsElement();
if (element->IsDirtyForServo()) {
RefPtr<ServoComputedValues> computedValues =
Servo_ComputedValues_Get(aContent).Consume();
MOZ_ASSERT(computedValues);
nsChangeHint changeHint = nsChangeHint(0);
// Add an explicit change hint if appropriate.
ServoElementSnapshot* snapshot;
if (mModifiedElements.Get(element, &snapshot)) {
changeHint |= snapshot->ExplicitChangeHint();
}
// Add the stored change hint if there's a frame. If there isn't a frame,
// generate a ReconstructFrame change hint if the new display value
// (which we can get from the ComputedValues stored on the node) is not
// none.
if (primaryFrame) {
changeHint |= primaryFrame->StyleContext()->ConsumeStoredChangeHint();
} else {
const nsStyleDisplay* currentDisplay =
Servo_GetStyleDisplay(computedValues);
if (currentDisplay->mDisplay != StyleDisplay::None) {
changeHint |= nsChangeHint_ReconstructFrame;
}
}
// Add the new change hint to the list of elements to process if
// we need to do any work.
if (changeHint) {
aChangeListToProcess.AppendChange(primaryFrame, element, changeHint);
}
// The frame reconstruction step (if needed) will ask for the descendants'
// style correctly. If not needed, we're done too.
//
// Note that we must leave the old style on an existing frame that is
// about to be reframed, since some frame constructor code wants to
// inspect the old style to work out what to do.
if (changeHint & nsChangeHint_ReconstructFrame) {
// Since we might still have some dirty bits set on descendants,
// inconsistent with the clearing of HasDirtyDescendants we will do as
// we return from these recursive RecreateStyleContexts calls, we
// explicitly clear them here. Otherwise we will trigger assertions
// when we soon process the frame reconstruction.
MarkSelfAndDescendantsAsNotDirtyForServo(element);
return;
}
// If there is no frame, and we didn't generate a ReconstructFrame change
// hint, then we don't need to do any more work.
if (!primaryFrame) {
aContent->UnsetIsDirtyForServo();
return;
}
// Hold the old style context alive, because it could become a dangling
// pointer during the replacement. In practice it's not a huge deal (on
// GetNextContinuationWithSameStyle the pointer is not dereferenced, only
// compared), but better not playing with dangling pointers if not needed.
RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
MOZ_ASSERT(oldStyleContext);
RefPtr<nsStyleContext> newContext =
aStyleSet->GetContext(computedValues.forget(), aParentContext, nullptr,
CSSPseudoElementType::NotPseudo);
// XXX This could not always work as expected: there are kinds of content
// with the first split and the last sharing style, but others not. We
// should handle those properly.
for (nsIFrame* f = primaryFrame; f;
f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
f->SetStyleContext(newContext);
}
// Update pseudo-elements state if appropriate.
const static CSSPseudoElementType pseudosToRestyle[] = {
CSSPseudoElementType::before,
CSSPseudoElementType::after,
};
for (CSSPseudoElementType pseudoType : pseudosToRestyle) {
nsIAtom* pseudoTag = nsCSSPseudoElements::GetPseudoAtom(pseudoType);
if (nsIFrame* pseudoFrame = FrameForPseudoElement(element, pseudoTag)) {
// TODO: we could maybe make this more performant via calling into
// Servo just once to know which pseudo-elements we've got to restyle?
RefPtr<nsStyleContext> pseudoContext =
aStyleSet->ProbePseudoElementStyle(element, pseudoType, newContext);
// If pseudoContext is null here, it means the frame is going away, so
// our change hint computation should have already indicated we need
// to reframe.
MOZ_ASSERT_IF(!pseudoContext,
changeHint & nsChangeHint_ReconstructFrame);
if (pseudoContext) {
pseudoFrame->SetStyleContext(pseudoContext);
// We only care restyling text nodes, since other type of nodes
// (images), are still not supported. If that eventually changes, we
// may have to write more code here... Or not, I don't think too
// many inherited properties can affect those other frames.
StyleChildrenIterator it(pseudoFrame->GetContent());
for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
if (n->IsNodeOfType(nsINode::eTEXT)) {
RefPtr<nsStyleContext> childContext =
aStyleSet->ResolveStyleForText(n, pseudoContext);
MOZ_ASSERT(n->GetPrimaryFrame(),
"How? This node is created at FC time!");
n->GetPrimaryFrame()->SetStyleContext(childContext);
}
}
}
}
}
aContent->UnsetIsDirtyForServo();
}
if (aContent->HasDirtyDescendantsForServo()) {
MOZ_ASSERT(primaryFrame,
"Frame construction should be scheduled, and it takes the "
"correct style for the children, so no need to be here.");
StyleChildrenIterator it(aContent);
for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
if (n->IsElement() || n->IsNodeOfType(nsINode::eTEXT)) {
RecreateStyleContexts(n, primaryFrame->StyleContext(),
aStyleSet, aChangeListToProcess);
}
}
aContent->UnsetHasDirtyDescendantsForServo();
}
}
static void
MarkChildrenAsDirtyForServo(nsIContent* aContent)
{
StyleChildrenIterator it(aContent);
nsIContent* n = it.GetNextChild();
bool hadChildren = bool(n);
for (; n; n = it.GetNextChild()) {
n->SetIsDirtyForServo();
}
if (hadChildren) {
aContent->SetHasDirtyDescendantsForServo();
}
}
/* static */ nsIFrame*
ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
nsIAtom* aPseudoTagOrNull)
{
MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
if (!aPseudoTagOrNull) {
return primaryFrame;
}
if (!primaryFrame) {
return nullptr;
}
// NOTE: we probably need to special-case display: contents here. Gecko's
// RestyleManager passes the primary frame of the parent instead.
if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
}
if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent);
}
MOZ_CRASH("Unkown pseudo-element given to "
"ServoRestyleManager::FrameForPseudoElement");
return nullptr;
}
/* static */ void
ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
{
const nsRestyleHint HANDLED_RESTYLE_HINTS = eRestyle_Self |
eRestyle_Subtree |
eRestyle_LaterSiblings |
eRestyle_SomeDescendants;
// NB: For Servo, at least for now, restyling and running selector-matching
// against the subtree is necessary as part of restyling the element, so
// processing eRestyle_Self will perform at least as much work as
// eRestyle_Subtree.
if (aHint & (eRestyle_Self | eRestyle_Subtree)) {
aElement->SetIsDirtyForServo();
aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
// NB: Servo gives us a eRestyle_SomeDescendants when it expects us to run
// selector matching on all the descendants. There's a bug on Servo to align
// meanings here (#12710) to avoid this potential source of confusion.
} else if (aHint & eRestyle_SomeDescendants) {
MarkChildrenAsDirtyForServo(aElement);
aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
}
if (aHint & eRestyle_LaterSiblings) {
aElement->MarkAncestorsAsHavingDirtyDescendantsForServo();
for (nsIContent* cur = aElement->GetNextSibling(); cur;
cur = cur->GetNextSibling()) {
cur->SetIsDirtyForServo();
}
}
// TODO: Handle all other nsRestyleHint values.
if (aHint & ~HANDLED_RESTYLE_HINTS) {
NS_WARNING(nsPrintfCString("stylo: Unhandled restyle hint %s",
RestyleManagerBase::RestyleHintToString(aHint).get()).get());
}
}
void
ServoRestyleManager::ProcessPendingRestyles()
{
MOZ_ASSERT(PresContext()->Document(), "No document? Pshaw!");
MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
if (MOZ_UNLIKELY(!PresContext()->PresShell()->DidInitialize())) {
// PresShell::FlushPendingNotifications doesn't early-return in the case
// where the PreShell hasn't yet been initialized (and therefore we haven't
// yet done the initial style traversal of the DOM tree). We should arguably
// fix up the callers and assert against this case, but we just detect and
// handle it for now.
return;
}
if (!HasPendingRestyles()) {
return;
}
ServoStyleSet* styleSet = StyleSet();
nsIDocument* doc = PresContext()->Document();
Element* root = doc->GetRootElement();
if (root) {
// ProcessPendingRestyles can generate new restyles (e.g. from the
// frame constructor if it decides that a ReconstructFrame change must
// apply to the parent of the element that generated that hint). So
// we loop while mModifiedElements still has some restyles in it, clearing
// it after each RecreateStyleContexts call below.
while (!mModifiedElements.IsEmpty()) {
for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) {
ServoElementSnapshot* snapshot = iter.UserData();
Element* element = iter.Key();
// The element is no longer in the document, so don't bother computing
// a final restyle hint for it.
//
// XXXheycam RestyleTracker checks that the element's GetComposedDoc()
// matches the document we're restyling. Do we need to do that too?
if (!element->IsInComposedDoc()) {
continue;
}
// TODO: avoid the ComputeRestyleHint call if we already have the highest
// explicit restyle hint?
nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
hint |= snapshot->ExplicitRestyleHint();
if (hint) {
NoteRestyleHint(element, hint);
}
}
if (!root->IsDirtyForServo() && !root->HasDirtyDescendantsForServo()) {
mModifiedElements.Clear();
break;
}
mInStyleRefresh = true;
styleSet->StyleDocument(/* aLeaveDirtyBits = */ true);
// First do any queued-up frame creation. (see bugs 827239 and 997506).
//
// XXXEmilio I'm calling this to avoid random behavior changes, since we
// delay frame construction after styling we should re-check once our
// model is more stable whether we can skip this call.
//
// Note this has to be *after* restyling, because otherwise frame
// construction will find unstyled nodes, and that's not funny.
PresContext()->FrameConstructor()->CreateNeededFrames();
nsStyleChangeList changeList;
RecreateStyleContexts(root, nullptr, styleSet, changeList);
mModifiedElements.Clear();
ProcessRestyledFrames(changeList);
mInStyleRefresh = false;
}
}
MOZ_ASSERT(!doc->IsDirtyForServo());
doc->UnsetHasDirtyDescendantsForServo();
IncrementRestyleGeneration();
}
void
ServoRestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
nsIContent* aChild)
{
//
// XXXbholley: We need the Gecko logic here to correctly restyle for things
// like :empty and positional selectors (though we may not need to post
// restyle events as agressively as the Gecko path does).
//
// Bug 1297899 tracks this work.
//
}
void
ServoRestyleManager::ContentInserted(nsINode* aContainer, nsIContent* aChild)
{
if (aContainer == aContainer->OwnerDoc()) {
// If we're getting this notification for the insertion of a root element,
// that means either:
// (a) We initialized the PresShell before the root element existed, or
// (b) The root element was removed and it or another root is being
// inserted.
//
// Either way the whole tree is dirty, so we should style the document.
MOZ_ASSERT(aChild == aChild->OwnerDoc()->GetRootElement());
MOZ_ASSERT(aChild->IsDirtyForServo());
StyleSet()->StyleDocument(/* aLeaveDirtyBits = */ false);
return;
}
if (!aContainer->HasServoData()) {
// This can happen with display:none. Bug 1297249 tracks more investigation
// and assertions here.
return;
}
// Style the new subtree because we will most likely need it during subsequent
// frame construction. Bug 1298281 tracks deferring this work in the lazy
// frame construction case.
StyleSet()->StyleNewSubtree(aChild);
RestyleForInsertOrChange(aContainer, aChild);
}
void
ServoRestyleManager::RestyleForAppend(nsIContent* aContainer,
nsIContent* aFirstNewContent)
{
//
// XXXbholley: We need the Gecko logic here to correctly restyle for things
// like :empty and positional selectors (though we may not need to post
// restyle events as agressively as the Gecko path does).
//
// Bug 1297899 tracks this work.
//
}
void
ServoRestyleManager::ContentAppended(nsIContent* aContainer,
nsIContent* aFirstNewContent)
{
if (!aContainer->HasServoData()) {
// This can happen with display:none. Bug 1297249 tracks more investigation
// and assertions here.
return;
}
// Style the new subtree because we will most likely need it during subsequent
// frame construction. Bug 1298281 tracks deferring this work in the lazy
// frame construction case.
if (aFirstNewContent->GetNextSibling()) {
aContainer->SetHasDirtyDescendantsForServo();
StyleSet()->StyleNewChildren(aContainer);
} else {
StyleSet()->StyleNewSubtree(aFirstNewContent);
}
RestyleForAppend(aContainer, aFirstNewContent);
}
void
ServoRestyleManager::ContentRemoved(nsINode* aContainer,
nsIContent* aOldChild,
nsIContent* aFollowingSibling)
{
NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented");
}
nsresult
ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
EventStates aChangedBits)
{
if (!aContent->IsElement()) {
return NS_OK;
}
Element* aElement = aContent->AsElement();
nsChangeHint changeHint;
nsRestyleHint restyleHint;
// NOTE: restyleHint here is effectively always 0, since that's what
// ServoStyleSet::HasStateDependentStyle returns. Servo computes on
// ProcessPendingRestyles using the ElementSnapshot, but in theory could
// compute it sequentially easily.
//
// Determine what's the best way to do it, and how much work do we save
// processing the restyle hint early (i.e., computing the style hint here
// sequentially, potentially saving the snapshot), vs lazily (snapshot
// approach).
//
// If we take the sequential approach we need to specialize Servo's restyle
// hints system a bit more, and mesure whether we save something storing the
// restyle hint in the table and deferring the dirtiness setting until
// ProcessPendingRestyles (that's a requirement if we store snapshots though),
// vs processing the restyle hint in-place, dirtying the nodes on
// PostRestyleEvent.
//
// If we definitely take the snapshot approach, we should take rid of
// HasStateDependentStyle, etc (though right now they're no-ops).
ContentStateChangedInternal(aElement, aChangedBits, &changeHint,
&restyleHint);
EventStates previousState = aElement->StyleState() ^ aChangedBits;
ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
snapshot->AddState(previousState);
PostRestyleEvent(aElement, restyleHint, changeHint);
return NS_OK;
}
void
ServoRestyleManager::AttributeWillChange(Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute, int32_t aModType,
const nsAttrValue* aNewValue)
{
ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
snapshot->AddAttrs(aElement);
}
void
ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
nsIAtom* aAttribute, int32_t aModType,
const nsAttrValue* aOldValue)
{
MOZ_ASSERT(SnapshotForElement(aElement)->HasAttrs());
if (aAttribute == nsGkAtoms::style) {
PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0));
}
}
nsresult
ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
{
NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
return NS_OK;
}
ServoElementSnapshot*
ServoRestyleManager::SnapshotForElement(Element* aElement)
{
// NB: aElement is the argument for the construction of the snapshot in the
// not found case.
return mModifiedElements.LookupOrAdd(aElement, aElement);
}
} // namespace mozilla

View file

@ -0,0 +1,133 @@
/* -*- 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 mozilla_ServoRestyleManager_h
#define mozilla_ServoRestyleManager_h
#include "mozilla/EventStates.h"
#include "mozilla/RestyleManagerBase.h"
#include "mozilla/ServoElementSnapshot.h"
#include "nsChangeHint.h"
#include "nsHashKeys.h"
#include "nsINode.h"
#include "nsISupportsImpl.h"
#include "nsPresContext.h"
namespace mozilla {
namespace dom {
class Element;
} // namespace dom
} // namespace mozilla
class nsAttrValue;
class nsIAtom;
class nsIContent;
class nsIFrame;
class nsStyleChangeList;
namespace mozilla {
/**
* Restyle manager for a Servo-backed style system.
*/
class ServoRestyleManager : public RestyleManagerBase
{
friend class ServoStyleSet;
public:
typedef RestyleManagerBase base_type;
NS_INLINE_DECL_REFCOUNTING(ServoRestyleManager)
explicit ServoRestyleManager(nsPresContext* aPresContext);
void PostRestyleEvent(dom::Element* aElement,
nsRestyleHint aRestyleHint,
nsChangeHint aMinChangeHint);
void PostRestyleEventForLazyConstruction();
void RebuildAllStyleData(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint);
void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
nsRestyleHint aRestyleHint);
void ProcessPendingRestyles();
void ContentInserted(nsINode* aContainer, nsIContent* aChild);
void ContentAppended(nsIContent* aContainer,
nsIContent* aFirstNewContent);
void ContentRemoved(nsINode* aContainer,
nsIContent* aOldChild,
nsIContent* aFollowingSibling);
void RestyleForInsertOrChange(nsINode* aContainer,
nsIContent* aChild);
void RestyleForAppend(nsIContent* aContainer,
nsIContent* aFirstNewContent);
nsresult ContentStateChanged(nsIContent* aContent,
EventStates aStateMask);
void AttributeWillChange(dom::Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aNewValue);
void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
nsIAtom* aAttribute, int32_t aModType,
const nsAttrValue* aOldValue);
nsresult ReparentStyleContext(nsIFrame* aFrame);
bool HasPendingRestyles()
{
return !mModifiedElements.IsEmpty() ||
PresContext()->Document()->HasDirtyDescendantsForServo();
}
/**
* Gets the appropriate frame given a content and a pseudo-element tag.
*
* Right now only supports a null tag, before or after. If the pseudo-element
* is not null, the content needs to be an element.
*/
static nsIFrame* FrameForPseudoElement(const nsIContent* aContent,
nsIAtom* aPseudoTagOrNull);
protected:
~ServoRestyleManager() {}
private:
ServoElementSnapshot* SnapshotForElement(Element* aElement);
/**
* The element-to-element snapshot table to compute restyle hints.
*/
nsClassHashtable<nsRefPtrHashKey<Element>, ServoElementSnapshot>
mModifiedElements;
/**
* Traverses a tree of content that Servo has just restyled, recreating style
* contexts for their frames with the new style data.
*/
void RecreateStyleContexts(nsIContent* aContent,
nsStyleContext* aParentContext,
ServoStyleSet* aStyleSet,
nsStyleChangeList& aChangeList);
/**
* Marks the tree with the appropriate dirty flags for the given restyle hint.
*/
static void NoteRestyleHint(Element* aElement, nsRestyleHint aRestyleHint);
inline ServoStyleSet* StyleSet() const
{
MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
"ServoRestyleManager should only be used with a Servo-flavored "
"style backend");
return PresContext()->StyleSet()->AsServo();
}
};
} // namespace mozilla
#endif // mozilla_ServoRestyleManager_h

179
layout/base/StackArena.cpp Normal file
View file

@ -0,0 +1,179 @@
/* 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 "StackArena.h"
#include "nsAlgorithm.h"
#include "nsDebug.h"
namespace mozilla {
// A block of memory that the stack will chop up and hand out.
struct StackBlock {
// Subtract sizeof(StackBlock*) to give space for the |mNext| field.
static const size_t MAX_USABLE_SIZE = 4096 - sizeof(StackBlock*);
// A block of memory.
char mBlock[MAX_USABLE_SIZE];
// Another block of memory that would only be created if our stack
// overflowed.
StackBlock* mNext;
StackBlock() : mNext(nullptr) { }
~StackBlock() { }
};
static_assert(sizeof(StackBlock) == 4096, "StackBlock must be 4096 bytes");
// We hold an array of marks. A push pushes a mark on the stack.
// A pop pops it off.
struct StackMark {
// The block of memory from which we are currently handing out chunks.
StackBlock* mBlock;
// Our current position in the block.
size_t mPos;
};
StackArena* AutoStackArena::gStackArena;
StackArena::StackArena()
{
mMarkLength = 0;
mMarks = nullptr;
// Allocate our stack memory.
mBlocks = new StackBlock();
mCurBlock = mBlocks;
mStackTop = 0;
mPos = 0;
}
StackArena::~StackArena()
{
// Free up our data.
delete [] mMarks;
while (mBlocks) {
StackBlock* toDelete = mBlocks;
mBlocks = mBlocks->mNext;
delete toDelete;
}
}
size_t
StackArena::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t n = 0;
StackBlock *block = mBlocks;
while (block) {
n += aMallocSizeOf(block);
block = block->mNext;
}
n += aMallocSizeOf(mMarks);
return n;
}
static const int STACK_ARENA_MARK_INCREMENT = 50;
void
StackArena::Push()
{
// Resize the mark array if we overrun it. Failure to allocate the
// mark array is not fatal; we just won't free to that mark. This
// allows callers not to worry about error checking.
if (mStackTop >= mMarkLength) {
uint32_t newLength = mStackTop + STACK_ARENA_MARK_INCREMENT;
StackMark* newMarks = new StackMark[newLength];
if (newMarks) {
if (mMarkLength) {
memcpy(newMarks, mMarks, sizeof(StackMark)*mMarkLength);
}
// Fill in any marks that we couldn't allocate during a prior call
// to Push().
for (; mMarkLength < mStackTop; ++mMarkLength) {
NS_NOTREACHED("should only hit this on out-of-memory");
newMarks[mMarkLength].mBlock = mCurBlock;
newMarks[mMarkLength].mPos = mPos;
}
delete [] mMarks;
mMarks = newMarks;
mMarkLength = newLength;
}
}
// Set a mark at the top (if we can).
NS_ASSERTION(mStackTop < mMarkLength, "out of memory");
if (mStackTop < mMarkLength) {
mMarks[mStackTop].mBlock = mCurBlock;
mMarks[mStackTop].mPos = mPos;
}
mStackTop++;
}
void*
StackArena::Allocate(size_t aSize)
{
NS_ASSERTION(mStackTop > 0, "Allocate called without Push");
// Align to a multiple of 8.
aSize = NS_ROUNDUP<size_t>(aSize, 8);
// On stack overflow, grab another block.
if (mPos + aSize >= StackBlock::MAX_USABLE_SIZE) {
NS_ASSERTION(aSize <= StackBlock::MAX_USABLE_SIZE,
"Requested memory is greater that our block size!!");
if (mCurBlock->mNext == nullptr) {
mCurBlock->mNext = new StackBlock();
}
mCurBlock = mCurBlock->mNext;
mPos = 0;
}
// Return the chunk they need.
void *result = mCurBlock->mBlock + mPos;
mPos += aSize;
return result;
}
void
StackArena::Pop()
{
// Pop off the mark.
NS_ASSERTION(mStackTop > 0, "unmatched pop");
mStackTop--;
if (mStackTop >= mMarkLength) {
// We couldn't allocate the marks array at the time of the push, so
// we don't know where we're freeing to.
NS_NOTREACHED("out of memory");
if (mStackTop == 0) {
// But we do know if we've completely pushed the stack.
mCurBlock = mBlocks;
mPos = 0;
}
return;
}
#ifdef DEBUG
// Mark the "freed" memory with 0xdd to help with debugging of memory
// allocation problems.
{
StackBlock *block = mMarks[mStackTop].mBlock, *block_end = mCurBlock;
size_t pos = mMarks[mStackTop].mPos;
for (; block != block_end; block = block->mNext, pos = 0) {
memset(block->mBlock + pos, 0xdd, sizeof(block->mBlock) - pos);
}
memset(block->mBlock + pos, 0xdd, mPos - pos);
}
#endif
mCurBlock = mMarks[mStackTop].mBlock;
mPos = mMarks[mStackTop].mPos;
}
} // namespace mozilla

94
layout/base/StackArena.h Normal file
View file

@ -0,0 +1,94 @@
/* 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 StackArena_h
#define StackArena_h
#include "nsError.h"
#include "mozilla/Assertions.h"
#include "mozilla/MemoryReporting.h"
namespace mozilla {
struct StackBlock;
struct StackMark;
class AutoStackArena;
// Private helper class for AutoStackArena.
class StackArena {
private:
friend class AutoStackArena;
StackArena();
~StackArena();
// Memory management functions.
void* Allocate(size_t aSize);
void Push();
void Pop();
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
// Our current position in memory.
size_t mPos;
// A list of memory blocks. Usually there is only one
// but if we overrun our stack size we can get more memory.
StackBlock* mBlocks;
// The current block.
StackBlock* mCurBlock;
// Our stack of mark where push has been called.
StackMark* mMarks;
// The current top of the mark list.
uint32_t mStackTop;
// The size of the mark array.
uint32_t mMarkLength;
};
// Class for stack scoped arena memory allocations.
//
// Callers who wish to allocate memory whose lifetime corresponds to the
// lifetime of a stack-allocated object can use this class. First,
// declare an AutoStackArena object on the stack. Then all subsequent
// calls to Allocate will allocate memory from an arena pool that will
// be freed when that variable goes out of scope. Nesting is allowed.
//
// Individual allocations cannot exceed StackBlock::MAX_USABLE_SIZE
// bytes.
//
class MOZ_RAII AutoStackArena {
public:
AutoStackArena()
: mOwnsStackArena(false)
{
if (!gStackArena) {
gStackArena = new StackArena();
mOwnsStackArena = true;
}
gStackArena->Push();
}
~AutoStackArena() {
gStackArena->Pop();
if (mOwnsStackArena) {
delete gStackArena;
gStackArena = nullptr;
}
}
static void* Allocate(size_t aSize) {
return gStackArena->Allocate(aSize);
}
private:
static StackArena* gStackArena;
bool mOwnsStackArena;
};
} // namespace mozilla
#endif

View file

@ -0,0 +1,311 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/StaticPresData.h"
#include "mozilla/Preferences.h"
#include "nsPresContext.h"
namespace mozilla {
static StaticPresData* sSingleton = nullptr;
void
StaticPresData::Init()
{
MOZ_ASSERT(!sSingleton);
sSingleton = new StaticPresData();
}
void
StaticPresData::Shutdown()
{
MOZ_ASSERT(sSingleton);
delete sSingleton;
sSingleton = nullptr;
}
StaticPresData*
StaticPresData::Get()
{
MOZ_ASSERT(sSingleton);
return sSingleton;
}
StaticPresData::StaticPresData()
{
mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THIN] = nsPresContext::CSSPixelsToAppUnits(1);
mBorderWidthTable[NS_STYLE_BORDER_WIDTH_MEDIUM] = nsPresContext::CSSPixelsToAppUnits(3);
mBorderWidthTable[NS_STYLE_BORDER_WIDTH_THICK] = nsPresContext::CSSPixelsToAppUnits(5);
}
#define MAKE_FONT_PREF_KEY(_pref, _s0, _s1) \
_pref.Assign(_s0); \
_pref.Append(_s1);
static const char* const kGenericFont[] = {
".variable.",
".fixed.",
".serif.",
".sans-serif.",
".monospace.",
".cursive.",
".fantasy."
};
// These are private, use the list in nsFont.h if you want a public list.
enum {
eDefaultFont_Variable,
eDefaultFont_Fixed,
eDefaultFont_Serif,
eDefaultFont_SansSerif,
eDefaultFont_Monospace,
eDefaultFont_Cursive,
eDefaultFont_Fantasy,
eDefaultFont_COUNT
};
const LangGroupFontPrefs*
StaticPresData::GetFontPrefsForLangHelper(nsIAtom *aLanguage,
const LangGroupFontPrefs* aPrefs) const
{
// Get language group for aLanguage:
MOZ_ASSERT(aLanguage);
MOZ_ASSERT(mLangService);
MOZ_ASSERT(aPrefs);
nsresult rv = NS_OK;
nsIAtom *langGroupAtom = nullptr;
langGroupAtom = mLangService->GetLanguageGroup(aLanguage, &rv);
if (NS_FAILED(rv) || !langGroupAtom) {
langGroupAtom = nsGkAtoms::x_western; // Assume x-western is safe...
}
LangGroupFontPrefs *prefs = const_cast<LangGroupFontPrefs*>(aPrefs);
if (prefs->mLangGroup) { // if initialized
DebugOnly<uint32_t> count = 0;
for (;;) {
NS_ASSERTION(++count < 35, "Lang group count exceeded!!!");
if (prefs->mLangGroup == langGroupAtom) {
return prefs;
}
if (!prefs->mNext) {
break;
}
prefs = prefs->mNext;
}
// nothing cached, so go on and fetch the prefs for this lang group:
prefs = prefs->mNext = new LangGroupFontPrefs;
}
prefs->mLangGroup = langGroupAtom;
/* Fetch the font prefs to be used -- see bug 61883 for details.
Not all prefs are needed upfront. Some are fallback prefs intended
for the GFX font sub-system...
1) unit : assumed to be the same for all language groups -------------
font.size.unit = px | pt XXX could be folded in the size... bug 90440
2) attributes for generic fonts --------------------------------------
font.default.[langGroup] = serif | sans-serif - fallback generic font
font.name.[generic].[langGroup] = current user' selected font on the pref dialog
font.name-list.[generic].[langGroup] = fontname1, fontname2, ... [factory pre-built list]
font.size.[generic].[langGroup] = integer - settable by the user
font.size-adjust.[generic].[langGroup] = "float" - settable by the user
font.minimum-size.[langGroup] = integer - settable by the user
*/
nsAutoCString langGroup;
langGroupAtom->ToUTF8String(langGroup);
prefs->mDefaultVariableFont.size = nsPresContext::CSSPixelsToAppUnits(16);
prefs->mDefaultFixedFont.size = nsPresContext::CSSPixelsToAppUnits(13);
nsAutoCString pref;
// get the current applicable font-size unit
enum {eUnit_unknown = -1, eUnit_px, eUnit_pt};
int32_t unit = eUnit_px;
nsAdoptingCString cvalue =
Preferences::GetCString("font.size.unit");
if (!cvalue.IsEmpty()) {
if (cvalue.EqualsLiteral("px")) {
unit = eUnit_px;
}
else if (cvalue.EqualsLiteral("pt")) {
unit = eUnit_pt;
}
else {
// XXX should really send this warning to the user (Error Console?).
// And just default to unit = eUnit_px?
NS_WARNING("unexpected font-size unit -- expected: 'px' or 'pt'");
unit = eUnit_unknown;
}
}
// get font.minimum-size.[langGroup]
MAKE_FONT_PREF_KEY(pref, "font.minimum-size.", langGroup);
int32_t size = Preferences::GetInt(pref.get());
if (unit == eUnit_px) {
prefs->mMinimumFontSize = nsPresContext::CSSPixelsToAppUnits(size);
}
else if (unit == eUnit_pt) {
prefs->mMinimumFontSize = nsPresContext::CSSPointsToAppUnits(size);
}
nsFont* fontTypes[] = {
&prefs->mDefaultVariableFont,
&prefs->mDefaultFixedFont,
&prefs->mDefaultSerifFont,
&prefs->mDefaultSansSerifFont,
&prefs->mDefaultMonospaceFont,
&prefs->mDefaultCursiveFont,
&prefs->mDefaultFantasyFont
};
static_assert(MOZ_ARRAY_LENGTH(fontTypes) == eDefaultFont_COUNT,
"FontTypes array count is not correct");
// Get attributes specific to each generic font. We do not get the user's
// generic-font-name-to-specific-family-name preferences because its the
// generic name that should be fed into the cascade. It is up to the GFX
// code to look up the font prefs to convert generic names to specific
// family names as necessary.
nsAutoCString generic_dot_langGroup;
for (uint32_t eType = 0; eType < ArrayLength(fontTypes); ++eType) {
generic_dot_langGroup.Assign(kGenericFont[eType]);
generic_dot_langGroup.Append(langGroup);
nsFont* font = fontTypes[eType];
// set the default variable font (the other fonts are seen as 'generic' fonts
// in GFX and will be queried there when hunting for alternative fonts)
if (eType == eDefaultFont_Variable) {
MAKE_FONT_PREF_KEY(pref, "font.name.variable.", langGroup);
nsAdoptingString value = Preferences::GetString(pref.get());
if (!value.IsEmpty()) {
FontFamilyName defaultVariableName = FontFamilyName::Convert(value);
FontFamilyType defaultType = defaultVariableName.mType;
NS_ASSERTION(defaultType == eFamily_serif ||
defaultType == eFamily_sans_serif,
"default type must be serif or sans-serif");
prefs->mDefaultVariableFont.fontlist = FontFamilyList(defaultType);
}
else {
MAKE_FONT_PREF_KEY(pref, "font.default.", langGroup);
value = Preferences::GetString(pref.get());
if (!value.IsEmpty()) {
FontFamilyName defaultVariableName = FontFamilyName::Convert(value);
FontFamilyType defaultType = defaultVariableName.mType;
NS_ASSERTION(defaultType == eFamily_serif ||
defaultType == eFamily_sans_serif,
"default type must be serif or sans-serif");
prefs->mDefaultVariableFont.fontlist = FontFamilyList(defaultType);
}
}
}
else {
if (eType == eDefaultFont_Monospace) {
// This takes care of the confusion whereby people often expect "monospace"
// to have the same default font-size as "-moz-fixed" (this tentative
// size may be overwritten with the specific value for "monospace" when
// "font.size.monospace.[langGroup]" is read -- see below)
prefs->mDefaultMonospaceFont.size = prefs->mDefaultFixedFont.size;
}
else if (eType != eDefaultFont_Fixed) {
// all the other generic fonts are initialized with the size of the
// variable font, but their specific size can supersede later -- see below
font->size = prefs->mDefaultVariableFont.size;
}
}
// Bug 84398: for spec purists, a different font-size only applies to the
// .variable. and .fixed. fonts and the other fonts should get |font-size-adjust|.
// The problem is that only GfxWin has the support for |font-size-adjust|. So for
// parity, we enable the ability to set a different font-size on all platforms.
// get font.size.[generic].[langGroup]
// size=0 means 'Auto', i.e., generic fonts retain the size of the variable font
MAKE_FONT_PREF_KEY(pref, "font.size", generic_dot_langGroup);
size = Preferences::GetInt(pref.get());
if (size > 0) {
if (unit == eUnit_px) {
font->size = nsPresContext::CSSPixelsToAppUnits(size);
}
else if (unit == eUnit_pt) {
font->size = nsPresContext::CSSPointsToAppUnits(size);
}
}
// get font.size-adjust.[generic].[langGroup]
// XXX only applicable on GFX ports that handle |font-size-adjust|
MAKE_FONT_PREF_KEY(pref, "font.size-adjust", generic_dot_langGroup);
cvalue = Preferences::GetCString(pref.get());
if (!cvalue.IsEmpty()) {
font->sizeAdjust = (float)atof(cvalue.get());
}
#ifdef DEBUG_rbs
printf("%s Family-list:%s size:%d sizeAdjust:%.2f\n",
generic_dot_langGroup.get(),
NS_ConvertUTF16toUTF8(font->name).get(), font->size,
font->sizeAdjust);
#endif
}
return prefs;
}
const nsFont*
StaticPresData::GetDefaultFontHelper(uint8_t aFontID, nsIAtom *aLanguage,
const LangGroupFontPrefs* aPrefs) const
{
MOZ_ASSERT(aLanguage);
MOZ_ASSERT(aPrefs);
const nsFont *font;
switch (aFontID) {
// Special (our default variable width font and fixed width font)
case kPresContext_DefaultVariableFont_ID:
font = &aPrefs->mDefaultVariableFont;
break;
case kPresContext_DefaultFixedFont_ID:
font = &aPrefs->mDefaultFixedFont;
break;
// CSS
case kGenericFont_serif:
font = &aPrefs->mDefaultSerifFont;
break;
case kGenericFont_sans_serif:
font = &aPrefs->mDefaultSansSerifFont;
break;
case kGenericFont_monospace:
font = &aPrefs->mDefaultMonospaceFont;
break;
case kGenericFont_cursive:
font = &aPrefs->mDefaultCursiveFont;
break;
case kGenericFont_fantasy:
font = &aPrefs->mDefaultFantasyFont;
break;
default:
font = nullptr;
NS_ERROR("invalid arg");
break;
}
return font;
}
} // namespace mozilla

View file

@ -0,0 +1,160 @@
/* -*- 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 mozilla_StaticPresData_h
#define mozilla_StaticPresData_h
#include "nsAutoPtr.h"
#include "nsCoord.h"
#include "nsCOMPtr.h"
#include "nsFont.h"
#include "nsIAtom.h"
#include "nsILanguageAtomService.h"
namespace mozilla {
struct LangGroupFontPrefs {
// Font sizes default to zero; they will be set in GetFontPreferences
LangGroupFontPrefs()
: mLangGroup(nullptr)
, mMinimumFontSize(0)
, mDefaultVariableFont(mozilla::eFamily_serif, 0)
, mDefaultFixedFont(mozilla::eFamily_monospace, 0)
, mDefaultSerifFont(mozilla::eFamily_serif, 0)
, mDefaultSansSerifFont(mozilla::eFamily_sans_serif, 0)
, mDefaultMonospaceFont(mozilla::eFamily_monospace, 0)
, mDefaultCursiveFont(mozilla::eFamily_cursive, 0)
, mDefaultFantasyFont(mozilla::eFamily_fantasy, 0)
{}
void Reset()
{
// Throw away any other LangGroupFontPrefs objects:
mNext = nullptr;
// Make GetFontPreferences reinitialize mLangGroupFontPrefs:
mLangGroup = nullptr;
}
size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
size_t n = 0;
LangGroupFontPrefs* curr = mNext;
while (curr) {
n += aMallocSizeOf(curr);
// Measurement of the following members may be added later if DMD finds
// it is worthwhile:
// - mLangGroup
// - mDefault*Font
curr = curr->mNext;
}
return n;
}
nsCOMPtr<nsIAtom> mLangGroup;
nscoord mMinimumFontSize;
nsFont mDefaultVariableFont;
nsFont mDefaultFixedFont;
nsFont mDefaultSerifFont;
nsFont mDefaultSansSerifFont;
nsFont mDefaultMonospaceFont;
nsFont mDefaultCursiveFont;
nsFont mDefaultFantasyFont;
nsAutoPtr<LangGroupFontPrefs> mNext;
};
/**
* Some functionality that has historically lived on nsPresContext does not
* actually need to be per-document. This singleton class serves as a host
* for that functionality. We delegate to it from nsPresContext where
* appropriate, and use it standalone in some cases as well.
*/
class StaticPresData
{
public:
// Initialization and shutdown of the singleton. Called exactly once.
static void Init();
static void Shutdown();
// Gets an instance of the singleton. Infallible between the calls to Init
// and Shutdown.
static StaticPresData* Get();
/**
* This table maps border-width enums 'thin', 'medium', 'thick'
* to actual nscoord values.
*/
const nscoord* GetBorderWidthTable() { return mBorderWidthTable; }
/**
* Fetch the user's font preferences for the given aLanguage's
* langugage group.
*
* The original code here is pretty old, and includes an optimization
* whereby language-specific prefs are read per-document, and the
* results are stored in a linked list, which is assumed to be very short
* since most documents only ever use one language.
*
* Storing this per-session rather than per-document would almost certainly
* be fine. But just to be on the safe side, we leave the old mechanism as-is,
* with an additional per-session cache that new callers can use if they don't
* have a PresContext.
*/
const LangGroupFontPrefs* GetFontPrefsForLangHelper(nsIAtom* aLanguage,
const LangGroupFontPrefs* aPrefs) const;
/**
* Get the default font for the given language and generic font ID.
* aLanguage may not be nullptr.
*
* This object is read-only, you must copy the font to modify it.
*
* When aFontID is kPresContext_DefaultVariableFontID or
* kPresContext_DefaultFixedFontID (which equals
* kGenericFont_moz_fixed, which is used for the -moz-fixed generic),
* the nsFont returned has its name as a CSS generic family (serif or
* sans-serif for the former, monospace for the latter), and its size
* as the default font size for variable or fixed fonts for the
* language group.
*
* For aFontID corresponding to a CSS Generic, the nsFont returned has
* its name set to that generic font's name, and its size set to
* the user's preference for font size for that generic and the
* given language.
*/
const nsFont* GetDefaultFontHelper(uint8_t aFontID,
nsIAtom* aLanguage,
const LangGroupFontPrefs* aPrefs) const;
/*
* These versions operate on the font pref cache on StaticPresData.
*/
const nsFont* GetDefaultFont(uint8_t aFontID, nsIAtom* aLanguage) const
{
MOZ_ASSERT(aLanguage);
return GetDefaultFontHelper(aFontID, aLanguage, GetFontPrefsForLang(aLanguage));
}
const LangGroupFontPrefs* GetFontPrefsForLang(nsIAtom* aLanguage) const
{
MOZ_ASSERT(aLanguage);
return GetFontPrefsForLangHelper(aLanguage, &mStaticLangGroupFontPrefs);
}
void ResetCachedFontPrefs() { mStaticLangGroupFontPrefs.Reset(); }
private:
StaticPresData();
~StaticPresData() {}
nsCOMPtr<nsILanguageAtomService> mLangService;
nscoord mBorderWidthTable[3];
LangGroupFontPrefs mStaticLangGroupFontPrefs;
};
} // namespace mozilla
#endif // mozilla_StaticPresData_h

View file

@ -0,0 +1,291 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=2 sw=2 et tw=78:
* 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 "TouchManager.h"
#include "mozilla/dom/EventTarget.h"
#include "nsIFrame.h"
#include "nsPresShell.h"
#include "nsView.h"
namespace mozilla {
nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>* TouchManager::sCaptureTouchList;
/*static*/ void
TouchManager::InitializeStatics()
{
NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
sCaptureTouchList = new nsDataHashtable<nsUint32HashKey, TouchManager::TouchInfo>;
}
/*static*/ void
TouchManager::ReleaseStatics()
{
NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
delete sCaptureTouchList;
sCaptureTouchList = nullptr;
}
void
TouchManager::Init(PresShell* aPresShell, nsIDocument* aDocument)
{
mPresShell = aPresShell;
mDocument = aDocument;
}
void
TouchManager::Destroy()
{
EvictTouches();
mDocument = nullptr;
mPresShell = nullptr;
}
static nsIContent*
GetNonAnonymousAncestor(dom::EventTarget* aTarget)
{
nsCOMPtr<nsIContent> content(do_QueryInterface(aTarget));
if (content && content->IsInNativeAnonymousSubtree()) {
content = content->FindFirstNonChromeOnlyAccessContent();
}
return content;
}
/*static*/ void
TouchManager::EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
nsIDocument* aLimitToDocument)
{
nsCOMPtr<nsINode> node(do_QueryInterface(aTouch->mTarget));
if (node) {
nsIDocument* doc = node->GetUncomposedDoc();
if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
nsIPresShell* presShell = doc->GetShell();
if (presShell) {
nsIFrame* frame = presShell->GetRootFrame();
if (frame) {
nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
if (widget) {
WidgetTouchEvent event(true, eTouchEnd, widget);
event.mTime = PR_IntervalNow();
event.mTouches.AppendElement(aTouch);
nsEventStatus status;
widget->DispatchEvent(&event, status);
}
}
}
}
}
if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
sCaptureTouchList->Remove(aTouch->Identifier());
}
}
/*static*/ void
TouchManager::AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList)
{
for (auto iter = sCaptureTouchList->Iter();
!iter.Done();
iter.Next()) {
RefPtr<dom::Touch>& touch = iter.Data().mTouch;
touch->mChanged = false;
aTouchList->AppendElement(touch);
}
}
void
TouchManager::EvictTouches()
{
WidgetTouchEvent::AutoTouchArray touches;
AppendToTouchList(&touches);
for (uint32_t i = 0; i < touches.Length(); ++i) {
EvictTouchPoint(touches[i], mDocument);
}
}
bool
TouchManager::PreHandleEvent(WidgetEvent* aEvent,
nsEventStatus* aStatus,
bool& aTouchIsNew,
bool& aIsHandlingUserInput,
nsCOMPtr<nsIContent>& aCurrentEventContent)
{
switch (aEvent->mMessage) {
case eTouchStart: {
aIsHandlingUserInput = true;
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
// if there is only one touch in this touchstart event, assume that it is
// the start of a new touch session and evict any old touches in the
// queue
if (touchEvent->mTouches.Length() == 1) {
WidgetTouchEvent::AutoTouchArray touches;
AppendToTouchList(&touches);
for (uint32_t i = 0; i < touches.Length(); ++i) {
EvictTouchPoint(touches[i]);
}
}
// Add any new touches to the queue
for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
dom::Touch* touch = touchEvent->mTouches[i];
int32_t id = touch->Identifier();
if (!sCaptureTouchList->Get(id, nullptr)) {
// If it is not already in the queue, it is a new touch
touch->mChanged = true;
}
touch->mMessage = aEvent->mMessage;
TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget) };
sCaptureTouchList->Put(id, info);
}
break;
}
case eTouchMove: {
// Check for touches that changed. Mark them add to queue
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
bool haveChanged = false;
for (int32_t i = touches.Length(); i; ) {
--i;
dom::Touch* touch = touches[i];
if (!touch) {
continue;
}
int32_t id = touch->Identifier();
touch->mMessage = aEvent->mMessage;
TouchInfo info;
if (!sCaptureTouchList->Get(id, &info)) {
touches.RemoveElementAt(i);
continue;
}
RefPtr<dom::Touch> oldTouch = info.mTouch;
if (!touch->Equals(oldTouch)) {
touch->mChanged = true;
haveChanged = true;
}
nsCOMPtr<dom::EventTarget> targetPtr = oldTouch->mTarget;
if (!targetPtr) {
touches.RemoveElementAt(i);
continue;
}
nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
if (!targetNode->IsInComposedDoc()) {
targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
}
touch->SetTarget(targetPtr);
info.mTouch = touch;
// info.mNonAnonymousTarget is still valid from above
sCaptureTouchList->Put(id, info);
// if we're moving from touchstart to touchmove for this touch
// we allow preventDefault to prevent mouse events
if (oldTouch->mMessage != touch->mMessage) {
aTouchIsNew = true;
}
}
// is nothing has changed, we should just return
if (!haveChanged) {
if (aTouchIsNew) {
// however, if this is the first touchmove after a touchstart,
// it is special in that preventDefault is allowed on it, so
// we must dispatch it to content even if nothing changed. we
// arbitrarily pick the first touch point to be the "changed"
// touch because firing an event with no changed events doesn't
// work.
for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
if (touchEvent->mTouches[i]) {
touchEvent->mTouches[i]->mChanged = true;
break;
}
}
} else {
return false;
}
}
break;
}
case eTouchEnd:
aIsHandlingUserInput = true;
// Fall through to touchcancel code
MOZ_FALLTHROUGH;
case eTouchCancel: {
// Remove the changed touches
// need to make sure we only remove touches that are ending here
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
for (uint32_t i = 0; i < touches.Length(); ++i) {
dom::Touch* touch = touches[i];
if (!touch) {
continue;
}
touch->mMessage = aEvent->mMessage;
touch->mChanged = true;
int32_t id = touch->Identifier();
TouchInfo info;
if (!sCaptureTouchList->Get(id, &info)) {
continue;
}
nsCOMPtr<EventTarget> targetPtr = info.mTouch->mTarget;
nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
if (targetNode && !targetNode->IsInComposedDoc()) {
targetPtr = do_QueryInterface(info.mNonAnonymousTarget);
}
aCurrentEventContent = do_QueryInterface(targetPtr);
touch->SetTarget(targetPtr);
sCaptureTouchList->Remove(id);
}
// add any touches left in the touch list, but ensure changed=false
AppendToTouchList(&touches);
break;
}
default:
break;
}
return true;
}
/*static*/ already_AddRefed<nsIContent>
TouchManager::GetAnyCapturedTouchTarget()
{
nsCOMPtr<nsIContent> result = nullptr;
if (sCaptureTouchList->Count() == 0) {
return result.forget();
}
for (auto iter = sCaptureTouchList->Iter(); !iter.Done(); iter.Next()) {
RefPtr<dom::Touch>& touch = iter.Data().mTouch;
if (touch) {
dom::EventTarget* target = touch->GetTarget();
if (target) {
result = do_QueryInterface(target);
break;
}
}
}
return result.forget();
}
/*static*/ bool
TouchManager::HasCapturedTouch(int32_t aId)
{
return sCaptureTouchList->Contains(aId);
}
/*static*/ already_AddRefed<dom::Touch>
TouchManager::GetCapturedTouch(int32_t aId)
{
RefPtr<dom::Touch> touch;
TouchInfo info;
if (sCaptureTouchList->Get(aId, &info)) {
touch = info.mTouch;
}
return touch.forget();
}
} // namespace mozilla

View file

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=2 sw=2 et tw=78:
* 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/.
*/
/* Description of TouchManager class.
* Incapsulate code related with work of touch events.
*/
#ifndef TouchManager_h_
#define TouchManager_h_
#include "mozilla/BasicEvents.h"
#include "mozilla/dom/Touch.h"
#include "mozilla/TouchEvents.h"
#include "nsRefPtrHashtable.h"
class PresShell;
class nsIDocument;
namespace mozilla {
class TouchManager {
public:
// Initialize and release static variables
static void InitializeStatics();
static void ReleaseStatics();
void Init(PresShell* aPresShell, nsIDocument* aDocument);
void Destroy();
bool PreHandleEvent(mozilla::WidgetEvent* aEvent,
nsEventStatus* aStatus,
bool& aTouchIsNew,
bool& aIsHandlingUserInput,
nsCOMPtr<nsIContent>& aCurrentEventContent);
static already_AddRefed<nsIContent> GetAnyCapturedTouchTarget();
static bool HasCapturedTouch(int32_t aId);
static already_AddRefed<dom::Touch> GetCapturedTouch(int32_t aId);
private:
void EvictTouches();
static void EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
nsIDocument* aLimitToDocument = nullptr);
static void AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList);
RefPtr<PresShell> mPresShell;
nsCOMPtr<nsIDocument> mDocument;
struct TouchInfo
{
RefPtr<mozilla::dom::Touch> mTouch;
nsCOMPtr<nsIContent> mNonAnonymousTarget;
};
static nsDataHashtable<nsUint32HashKey, TouchInfo>* sCaptureTouchList;
};
} // namespace mozilla
#endif /* !defined(TouchManager_h_) */

View file

@ -0,0 +1,294 @@
/* -*- 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 MOZ_UNIT_TRANSFORMS_H_
#define MOZ_UNIT_TRANSFORMS_H_
#include "Units.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/Maybe.h"
#include "nsRegion.h"
namespace mozilla {
// Convenience functions for converting an entity from one strongly-typed
// coordinate system to another without changing the values it stores (this
// can be thought of as a cast).
// To use these functions, you must provide a justification for each use!
// Feel free to add more justifications to PixelCastJustification, along with
// a comment that explains under what circumstances it is appropriate to use.
enum class PixelCastJustification : uint8_t {
// For the root layer, Screen Pixel = Parent Layer Pixel.
ScreenIsParentLayerForRoot,
// On the layout side, Screen Pixel = LayoutDevice at the outer-window level.
LayoutDeviceIsScreenForBounds,
// For the root layer, Render Target Pixel = Parent Layer Pixel.
RenderTargetIsParentLayerForRoot,
// For the root composition size we want to view it as layer pixels in any layer
ParentLayerToLayerForRootComposition,
// The Layer coordinate space for one layer is the ParentLayer coordinate
// space for its children
MovingDownToChildren,
// The transform that is usually used to convert between two coordinate
// systems is not available (for example, because the object that stores it
// is being destroyed), so fall back to the identity.
TransformNotAvailable,
// When an OS event is initially constructed, its reference point is
// technically in screen pixels, as it has not yet accounted for any
// asynchronous transforms. This justification is for viewing the initial
// reference point as a screen point. The reverse is useful when synthetically
// created WidgetEvents need to be converted back to InputData.
LayoutDeviceIsScreenForUntransformedEvent,
// Similar to LayoutDeviceIsScreenForUntransformedEvent, PBrowser handles
// some widget/tab dimension information as the OS does -- in screen units.
LayoutDeviceIsScreenForTabDims,
// A combination of LayoutDeviceIsScreenForBounds and
// ScreenIsParentLayerForRoot, which is how we're using it.
LayoutDeviceIsParentLayerForRCDRSF,
// Used to treat the product of AsyncTransformComponentMatrix objects
// as an AsyncTransformMatrix. See the definitions of these matrices in
// LayersTypes.h for details.
MultipleAsyncTransforms,
// We have reason to believe a layer doesn't have a local transform.
// Should only be used if we've already checked or asserted this.
NoTransformOnLayer
};
template <class TargetUnits, class SourceUnits>
gfx::CoordTyped<TargetUnits> ViewAs(const gfx::CoordTyped<SourceUnits>& aCoord, PixelCastJustification) {
return gfx::CoordTyped<TargetUnits>(aCoord.value);
}
template <class TargetUnits, class SourceUnits>
gfx::SizeTyped<TargetUnits> ViewAs(const gfx::SizeTyped<SourceUnits>& aSize, PixelCastJustification) {
return gfx::SizeTyped<TargetUnits>(aSize.width, aSize.height);
}
template <class TargetUnits, class SourceUnits>
gfx::IntSizeTyped<TargetUnits> ViewAs(const gfx::IntSizeTyped<SourceUnits>& aSize, PixelCastJustification) {
return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
}
template <class TargetUnits, class SourceUnits>
gfx::PointTyped<TargetUnits> ViewAs(const gfx::PointTyped<SourceUnits>& aPoint, PixelCastJustification) {
return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
}
template <class TargetUnits, class SourceUnits>
gfx::IntPointTyped<TargetUnits> ViewAs(const gfx::IntPointTyped<SourceUnits>& aPoint, PixelCastJustification) {
return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
}
template <class TargetUnits, class SourceUnits>
gfx::RectTyped<TargetUnits> ViewAs(const gfx::RectTyped<SourceUnits>& aRect, PixelCastJustification) {
return gfx::RectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
}
template <class TargetUnits, class SourceUnits>
gfx::IntRectTyped<TargetUnits> ViewAs(const gfx::IntRectTyped<SourceUnits>& aRect, PixelCastJustification) {
return gfx::IntRectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
}
template <class TargetUnits, class SourceUnits>
gfx::MarginTyped<TargetUnits> ViewAs(const gfx::MarginTyped<SourceUnits>& aMargin, PixelCastJustification) {
return gfx::MarginTyped<TargetUnits>(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left);
}
template <class TargetUnits, class SourceUnits>
gfx::IntMarginTyped<TargetUnits> ViewAs(const gfx::IntMarginTyped<SourceUnits>& aMargin, PixelCastJustification) {
return gfx::IntMarginTyped<TargetUnits>(aMargin.top, aMargin.right, aMargin.bottom, aMargin.left);
}
template <class TargetUnits, class SourceUnits>
gfx::IntRegionTyped<TargetUnits> ViewAs(const gfx::IntRegionTyped<SourceUnits>& aRegion, PixelCastJustification) {
return gfx::IntRegionTyped<TargetUnits>::FromUnknownRegion(aRegion.ToUnknownRegion());
}
template <class NewTargetUnits, class OldTargetUnits, class SourceUnits>
gfx::ScaleFactor<SourceUnits, NewTargetUnits> ViewTargetAs(
const gfx::ScaleFactor<SourceUnits, OldTargetUnits>& aScaleFactor,
PixelCastJustification) {
return gfx::ScaleFactor<SourceUnits, NewTargetUnits>(aScaleFactor.scale);
}
// Unlike the other functions in this category, this function takes the
// target matrix type, rather than its source and target unit types, as
// the explicit template argument, so an example invocation is:
// ViewAs<ScreenToLayerMatrix4x4>(otherTypedMatrix, justification)
// The reason is that if it took the source and target unit types as two
// template arguments, there may be some confusion as to which is the
// source and which is the target.
template <class TargetMatrix, class SourceMatrixSourceUnits, class SourceMatrixTargetUnits>
TargetMatrix ViewAs(
const gfx::Matrix4x4Typed<SourceMatrixSourceUnits, SourceMatrixTargetUnits>& aMatrix,
PixelCastJustification) {
return TargetMatrix::FromUnknownMatrix(aMatrix.ToUnknownMatrix());
}
// Convenience functions for casting untyped entities to typed entities.
// Using these functions does not require a justification, but once we convert
// all code to use strongly typed units they should not be needed any longer.
template <class TargetUnits>
gfx::PointTyped<TargetUnits> ViewAs(const gfxPoint& aPoint) {
return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
}
template <class TargetUnits>
gfx::PointTyped<TargetUnits> ViewAs(const gfx::Point& aPoint) {
return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
}
template <class TargetUnits>
gfx::RectTyped<TargetUnits> ViewAs(const gfx::Rect& aRect) {
return gfx::RectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
}
template <class TargetUnits>
gfx::IntSizeTyped<TargetUnits> ViewAs(const nsIntSize& aSize) {
return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
}
template <class TargetUnits>
gfx::IntPointTyped<TargetUnits> ViewAs(const nsIntPoint& aPoint) {
return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
}
template <class TargetUnits>
gfx::IntRectTyped<TargetUnits> ViewAs(const nsIntRect& aRect) {
return gfx::IntRectTyped<TargetUnits>(aRect.x, aRect.y, aRect.width, aRect.height);
}
template <class TargetUnits>
gfx::IntRegionTyped<TargetUnits> ViewAs(const nsIntRegion& aRegion) {
return gfx::IntRegionTyped<TargetUnits>::FromUnknownRegion(aRegion);
}
// Unlike the other functions in this category, this function takes the
// target matrix type, rather than its source and target unit types, as
// the template argument, so an example invocation is:
// ViewAs<ScreenToLayerMatrix4x4>(untypedMatrix)
// The reason is that if it took the source and target unit types as two
// template arguments, there may be some confusion as to which is the
// source and which is the target.
template <class TypedMatrix>
TypedMatrix ViewAs(const gfx::Matrix4x4& aMatrix) {
return TypedMatrix::FromUnknownMatrix(aMatrix);
}
// Convenience functions for transforming an entity from one strongly-typed
// coordinate system to another using the provided transformation matrix.
template <typename TargetUnits, typename SourceUnits>
static gfx::PointTyped<TargetUnits>
TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::PointTyped<SourceUnits>& aPoint)
{
return aTransform.TransformPoint(aPoint);
}
template <typename TargetUnits, typename SourceUnits>
static gfx::IntPointTyped<TargetUnits>
TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::IntPointTyped<SourceUnits>& aPoint)
{
return RoundedToInt(TransformBy(aTransform, gfx::PointTyped<SourceUnits>(aPoint)));
}
template <typename TargetUnits, typename SourceUnits>
static gfx::RectTyped<TargetUnits>
TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::RectTyped<SourceUnits>& aRect)
{
return aTransform.TransformBounds(aRect);
}
template <typename TargetUnits, typename SourceUnits>
static gfx::IntRectTyped<TargetUnits>
TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::IntRectTyped<SourceUnits>& aRect)
{
return RoundedToInt(TransformBy(aTransform, gfx::RectTyped<SourceUnits>(aRect)));
}
template <typename TargetUnits, typename SourceUnits>
static gfx::IntRegionTyped<TargetUnits>
TransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::IntRegionTyped<SourceUnits>& aRegion)
{
return ViewAs<TargetUnits>(aRegion.ToUnknownRegion().Transform(
aTransform.ToUnknownMatrix()));
}
// Transform |aVector|, which is anchored at |aAnchor|, by the given transform
// matrix, yielding a point in |TargetUnits|.
// The anchor is necessary because with 3D tranforms, the location of the
// vector can affect the result of the transform.
template <typename TargetUnits, typename SourceUnits>
static gfx::PointTyped<TargetUnits>
TransformVector(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::PointTyped<SourceUnits>& aVector,
const gfx::PointTyped<SourceUnits>& aAnchor)
{
gfx::PointTyped<TargetUnits> transformedStart = TransformBy(aTransform, aAnchor);
gfx::PointTyped<TargetUnits> transformedEnd = TransformBy(aTransform, aAnchor + aVector);
return transformedEnd - transformedStart;
}
// UntransformBy() and UntransformVector() are like TransformBy() and
// TransformVector(), respectively, but are intended for cases where
// the transformation matrix is the inverse of a 3D projection. When
// using such transforms, the resulting Point4D is only meaningful
// if it has a positive w-coordinate. To handle this, these functions
// return a Maybe object which contains a value if and only if the
// result is meaningful
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::PointTyped<TargetUnits>>
UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::PointTyped<SourceUnits>& aPoint)
{
gfx::Point4DTyped<TargetUnits> point = aTransform.ProjectPoint(aPoint);
if (!point.HasPositiveWCoord()) {
return Nothing();
}
return Some(point.As2DPoint());
}
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::IntPointTyped<TargetUnits>>
UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::IntPointTyped<SourceUnits>& aPoint)
{
gfx::PointTyped<SourceUnits> p = aPoint;
gfx::Point4DTyped<TargetUnits> point = aTransform.ProjectPoint(p);
if (!point.HasPositiveWCoord()) {
return Nothing();
}
return Some(RoundedToInt(point.As2DPoint()));
}
// The versions of UntransformBy() that take a rectangle also take a clip,
// which represents the bounds within which the target must fall. The
// result of the transform is intersected with this clip, and is considered
// meaningful if the intersection is not empty.
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::RectTyped<TargetUnits>>
UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::RectTyped<SourceUnits>& aRect,
const gfx::RectTyped<TargetUnits>& aClip)
{
gfx::RectTyped<TargetUnits> rect = aTransform.ProjectRectBounds(aRect, aClip);
if (rect.IsEmpty()) {
return Nothing();
}
return Some(rect);
}
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::IntRectTyped<TargetUnits>>
UntransformBy(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::IntRectTyped<SourceUnits>& aRect,
const gfx::IntRectTyped<TargetUnits>& aClip)
{
gfx::RectTyped<TargetUnits> rect = aTransform.ProjectRectBounds(aRect, aClip);
if (rect.IsEmpty()) {
return Nothing();
}
return Some(RoundedToInt(rect));
}
template <typename TargetUnits, typename SourceUnits>
static Maybe<gfx::PointTyped<TargetUnits>>
UntransformVector(const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
const gfx::PointTyped<SourceUnits>& aVector,
const gfx::PointTyped<SourceUnits>& aAnchor)
{
gfx::Point4DTyped<TargetUnits> projectedAnchor = aTransform.ProjectPoint(aAnchor);
gfx::Point4DTyped<TargetUnits> projectedTarget = aTransform.ProjectPoint(aAnchor + aVector);
if (!projectedAnchor.HasPositiveWCoord() || !projectedTarget.HasPositiveWCoord()){
return Nothing();
}
return Some(projectedTarget.As2DPoint() - projectedAnchor.As2DPoint());
}
} // namespace mozilla
#endif

654
layout/base/Units.h Normal file
View file

@ -0,0 +1,654 @@
/* -*- 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 MOZ_UNITS_H_
#define MOZ_UNITS_H_
#include "mozilla/gfx/Coord.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/gfx/ScaleFactor.h"
#include "mozilla/gfx/ScaleFactors2D.h"
#include "nsMargin.h"
#include "nsRect.h"
#include "nsRegion.h"
#include "mozilla/AppUnits.h"
#include "mozilla/TypeTraits.h"
namespace mozilla {
template <typename T>
struct IsPixel : FalseType {};
// See struct declaration for a description of each unit type.
struct CSSPixel;
struct LayoutDevicePixel;
struct LayerPixel;
struct CSSTransformedLayerPixel;
struct RenderTargetPixel;
struct ScreenPixel;
struct ParentLayerPixel;
struct DesktopPixel;
template<> struct IsPixel<CSSPixel> : TrueType {};
template<> struct IsPixel<LayoutDevicePixel> : TrueType {};
template<> struct IsPixel<LayerPixel> : TrueType {};
template<> struct IsPixel<CSSTransformedLayerPixel> : TrueType {};
template<> struct IsPixel<RenderTargetPixel> : TrueType {};
template<> struct IsPixel<ScreenPixel> : TrueType {};
template<> struct IsPixel<ParentLayerPixel> : TrueType {};
template<> struct IsPixel<DesktopPixel> : TrueType {};
typedef gfx::CoordTyped<CSSPixel> CSSCoord;
typedef gfx::IntCoordTyped<CSSPixel> CSSIntCoord;
typedef gfx::PointTyped<CSSPixel> CSSPoint;
typedef gfx::IntPointTyped<CSSPixel> CSSIntPoint;
typedef gfx::SizeTyped<CSSPixel> CSSSize;
typedef gfx::IntSizeTyped<CSSPixel> CSSIntSize;
typedef gfx::RectTyped<CSSPixel> CSSRect;
typedef gfx::IntRectTyped<CSSPixel> CSSIntRect;
typedef gfx::MarginTyped<CSSPixel> CSSMargin;
typedef gfx::IntMarginTyped<CSSPixel> CSSIntMargin;
typedef gfx::IntRegionTyped<CSSPixel> CSSIntRegion;
typedef gfx::CoordTyped<LayoutDevicePixel> LayoutDeviceCoord;
typedef gfx::IntCoordTyped<LayoutDevicePixel> LayoutDeviceIntCoord;
typedef gfx::PointTyped<LayoutDevicePixel> LayoutDevicePoint;
typedef gfx::IntPointTyped<LayoutDevicePixel> LayoutDeviceIntPoint;
typedef gfx::SizeTyped<LayoutDevicePixel> LayoutDeviceSize;
typedef gfx::IntSizeTyped<LayoutDevicePixel> LayoutDeviceIntSize;
typedef gfx::RectTyped<LayoutDevicePixel> LayoutDeviceRect;
typedef gfx::IntRectTyped<LayoutDevicePixel> LayoutDeviceIntRect;
typedef gfx::MarginTyped<LayoutDevicePixel> LayoutDeviceMargin;
typedef gfx::IntMarginTyped<LayoutDevicePixel> LayoutDeviceIntMargin;
typedef gfx::IntRegionTyped<LayoutDevicePixel> LayoutDeviceIntRegion;
typedef gfx::CoordTyped<LayerPixel> LayerCoord;
typedef gfx::IntCoordTyped<LayerPixel> LayerIntCoord;
typedef gfx::PointTyped<LayerPixel> LayerPoint;
typedef gfx::IntPointTyped<LayerPixel> LayerIntPoint;
typedef gfx::SizeTyped<LayerPixel> LayerSize;
typedef gfx::IntSizeTyped<LayerPixel> LayerIntSize;
typedef gfx::RectTyped<LayerPixel> LayerRect;
typedef gfx::IntRectTyped<LayerPixel> LayerIntRect;
typedef gfx::MarginTyped<LayerPixel> LayerMargin;
typedef gfx::IntMarginTyped<LayerPixel> LayerIntMargin;
typedef gfx::IntRegionTyped<LayerPixel> LayerIntRegion;
typedef gfx::CoordTyped<CSSTransformedLayerPixel> CSSTransformedLayerCoord;
typedef gfx::IntCoordTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntCoord;
typedef gfx::PointTyped<CSSTransformedLayerPixel> CSSTransformedLayerPoint;
typedef gfx::IntPointTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntPoint;
typedef gfx::SizeTyped<CSSTransformedLayerPixel> CSSTransformedLayerSize;
typedef gfx::IntSizeTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntSize;
typedef gfx::RectTyped<CSSTransformedLayerPixel> CSSTransformedLayerRect;
typedef gfx::IntRectTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntRect;
typedef gfx::MarginTyped<CSSTransformedLayerPixel> CSSTransformedLayerMargin;
typedef gfx::IntMarginTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntMargin;
typedef gfx::IntRegionTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntRegion;
typedef gfx::PointTyped<RenderTargetPixel> RenderTargetPoint;
typedef gfx::IntPointTyped<RenderTargetPixel> RenderTargetIntPoint;
typedef gfx::SizeTyped<RenderTargetPixel> RenderTargetSize;
typedef gfx::IntSizeTyped<RenderTargetPixel> RenderTargetIntSize;
typedef gfx::RectTyped<RenderTargetPixel> RenderTargetRect;
typedef gfx::IntRectTyped<RenderTargetPixel> RenderTargetIntRect;
typedef gfx::MarginTyped<RenderTargetPixel> RenderTargetMargin;
typedef gfx::IntMarginTyped<RenderTargetPixel> RenderTargetIntMargin;
typedef gfx::IntRegionTyped<RenderTargetPixel> RenderTargetIntRegion;
typedef gfx::CoordTyped<ScreenPixel> ScreenCoord;
typedef gfx::IntCoordTyped<ScreenPixel> ScreenIntCoord;
typedef gfx::PointTyped<ScreenPixel> ScreenPoint;
typedef gfx::IntPointTyped<ScreenPixel> ScreenIntPoint;
typedef gfx::SizeTyped<ScreenPixel> ScreenSize;
typedef gfx::IntSizeTyped<ScreenPixel> ScreenIntSize;
typedef gfx::RectTyped<ScreenPixel> ScreenRect;
typedef gfx::IntRectTyped<ScreenPixel> ScreenIntRect;
typedef gfx::MarginTyped<ScreenPixel> ScreenMargin;
typedef gfx::IntMarginTyped<ScreenPixel> ScreenIntMargin;
typedef gfx::IntRegionTyped<ScreenPixel> ScreenIntRegion;
typedef gfx::CoordTyped<ParentLayerPixel> ParentLayerCoord;
typedef gfx::IntCoordTyped<ParentLayerPixel> ParentLayerIntCoord;
typedef gfx::PointTyped<ParentLayerPixel> ParentLayerPoint;
typedef gfx::IntPointTyped<ParentLayerPixel> ParentLayerIntPoint;
typedef gfx::SizeTyped<ParentLayerPixel> ParentLayerSize;
typedef gfx::IntSizeTyped<ParentLayerPixel> ParentLayerIntSize;
typedef gfx::RectTyped<ParentLayerPixel> ParentLayerRect;
typedef gfx::IntRectTyped<ParentLayerPixel> ParentLayerIntRect;
typedef gfx::MarginTyped<ParentLayerPixel> ParentLayerMargin;
typedef gfx::IntMarginTyped<ParentLayerPixel> ParentLayerIntMargin;
typedef gfx::IntRegionTyped<ParentLayerPixel> ParentLayerIntRegion;
typedef gfx::CoordTyped<DesktopPixel> DesktopCoord;
typedef gfx::IntCoordTyped<DesktopPixel> DesktopIntCoord;
typedef gfx::PointTyped<DesktopPixel> DesktopPoint;
typedef gfx::IntPointTyped<DesktopPixel> DesktopIntPoint;
typedef gfx::SizeTyped<DesktopPixel> DesktopSize;
typedef gfx::IntSizeTyped<DesktopPixel> DesktopIntSize;
typedef gfx::RectTyped<DesktopPixel> DesktopRect;
typedef gfx::IntRectTyped<DesktopPixel> DesktopIntRect;
typedef gfx::ScaleFactor<CSSPixel, LayoutDevicePixel> CSSToLayoutDeviceScale;
typedef gfx::ScaleFactor<CSSPixel, LayerPixel> CSSToLayerScale;
typedef gfx::ScaleFactor<CSSPixel, ScreenPixel> CSSToScreenScale;
typedef gfx::ScaleFactor<CSSPixel, ParentLayerPixel> CSSToParentLayerScale;
typedef gfx::ScaleFactor<LayoutDevicePixel, CSSPixel> LayoutDeviceToCSSScale;
typedef gfx::ScaleFactor<LayoutDevicePixel, LayerPixel> LayoutDeviceToLayerScale;
typedef gfx::ScaleFactor<LayoutDevicePixel, ScreenPixel> LayoutDeviceToScreenScale;
typedef gfx::ScaleFactor<LayoutDevicePixel, ParentLayerPixel> LayoutDeviceToParentLayerScale;
typedef gfx::ScaleFactor<LayerPixel, CSSPixel> LayerToCSSScale;
typedef gfx::ScaleFactor<LayerPixel, LayoutDevicePixel> LayerToLayoutDeviceScale;
typedef gfx::ScaleFactor<LayerPixel, RenderTargetPixel> LayerToRenderTargetScale;
typedef gfx::ScaleFactor<LayerPixel, ScreenPixel> LayerToScreenScale;
typedef gfx::ScaleFactor<LayerPixel, ParentLayerPixel> LayerToParentLayerScale;
typedef gfx::ScaleFactor<RenderTargetPixel, ScreenPixel> RenderTargetToScreenScale;
typedef gfx::ScaleFactor<ScreenPixel, CSSPixel> ScreenToCSSScale;
typedef gfx::ScaleFactor<ScreenPixel, LayoutDevicePixel> ScreenToLayoutDeviceScale;
typedef gfx::ScaleFactor<ScreenPixel, LayerPixel> ScreenToLayerScale;
typedef gfx::ScaleFactor<ScreenPixel, ParentLayerPixel> ScreenToParentLayerScale;
typedef gfx::ScaleFactor<ParentLayerPixel, LayerPixel> ParentLayerToLayerScale;
typedef gfx::ScaleFactor<ParentLayerPixel, ScreenPixel> ParentLayerToScreenScale;
typedef gfx::ScaleFactor<ParentLayerPixel, ParentLayerPixel> ParentLayerToParentLayerScale;
typedef gfx::ScaleFactor<DesktopPixel, LayoutDevicePixel> DesktopToLayoutDeviceScale;
typedef gfx::ScaleFactors2D<CSSPixel, LayoutDevicePixel> CSSToLayoutDeviceScale2D;
typedef gfx::ScaleFactors2D<CSSPixel, LayerPixel> CSSToLayerScale2D;
typedef gfx::ScaleFactors2D<CSSPixel, ScreenPixel> CSSToScreenScale2D;
typedef gfx::ScaleFactors2D<CSSPixel, ParentLayerPixel> CSSToParentLayerScale2D;
typedef gfx::ScaleFactors2D<LayoutDevicePixel, CSSPixel> LayoutDeviceToCSSScale2D;
typedef gfx::ScaleFactors2D<LayoutDevicePixel, LayerPixel> LayoutDeviceToLayerScale2D;
typedef gfx::ScaleFactors2D<LayoutDevicePixel, ScreenPixel> LayoutDeviceToScreenScale2D;
typedef gfx::ScaleFactors2D<LayoutDevicePixel, ParentLayerPixel> LayoutDeviceToParentLayerScale2D;
typedef gfx::ScaleFactors2D<LayerPixel, CSSPixel> LayerToCSSScale2D;
typedef gfx::ScaleFactors2D<LayerPixel, LayoutDevicePixel> LayerToLayoutDeviceScale2D;
typedef gfx::ScaleFactors2D<LayerPixel, RenderTargetPixel> LayerToRenderTargetScale2D;
typedef gfx::ScaleFactors2D<LayerPixel, ScreenPixel> LayerToScreenScale2D;
typedef gfx::ScaleFactors2D<LayerPixel, ParentLayerPixel> LayerToParentLayerScale2D;
typedef gfx::ScaleFactors2D<RenderTargetPixel, ScreenPixel> RenderTargetToScreenScale2D;
typedef gfx::ScaleFactors2D<ScreenPixel, CSSPixel> ScreenToCSSScale2D;
typedef gfx::ScaleFactors2D<ScreenPixel, LayoutDevicePixel> ScreenToLayoutDeviceScale2D;
typedef gfx::ScaleFactors2D<ScreenPixel, LayerPixel> ScreenToLayerScale2D;
typedef gfx::ScaleFactors2D<ScreenPixel, ParentLayerPixel> ScreenToParentLayerScale2D;
typedef gfx::ScaleFactors2D<ParentLayerPixel, LayerPixel> ParentLayerToLayerScale2D;
typedef gfx::ScaleFactors2D<ParentLayerPixel, ScreenPixel> ParentLayerToScreenScale2D;
typedef gfx::ScaleFactors2D<ParentLayerPixel, ParentLayerPixel> ParentLayerToParentLayerScale2D;
typedef gfx::Matrix4x4Typed<LayoutDevicePixel, LayoutDevicePixel> LayoutDeviceToLayoutDeviceMatrix4x4;
typedef gfx::Matrix4x4Typed<LayerPixel, ParentLayerPixel> LayerToParentLayerMatrix4x4;
typedef gfx::Matrix4x4Typed<ScreenPixel, ScreenPixel> ScreenToScreenMatrix4x4;
typedef gfx::Matrix4x4Typed<ScreenPixel, ParentLayerPixel> ScreenToParentLayerMatrix4x4;
typedef gfx::Matrix4x4Typed<ParentLayerPixel, LayerPixel> ParentLayerToLayerMatrix4x4;
typedef gfx::Matrix4x4Typed<ParentLayerPixel, ScreenPixel> ParentLayerToScreenMatrix4x4;
typedef gfx::Matrix4x4Typed<ParentLayerPixel, ParentLayerPixel> ParentLayerToParentLayerMatrix4x4;
/*
* The pixels that content authors use to specify sizes in.
*/
struct CSSPixel {
// Conversions from app units
static CSSPoint FromAppUnits(const nsPoint& aPoint) {
return CSSPoint(NSAppUnitsToFloatPixels(aPoint.x, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aPoint.y, float(AppUnitsPerCSSPixel())));
}
static CSSSize FromAppUnits(const nsSize& aSize) {
return CSSSize(NSAppUnitsToFloatPixels(aSize.width, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aSize.height, float(AppUnitsPerCSSPixel())));
}
static CSSRect FromAppUnits(const nsRect& aRect) {
return CSSRect(NSAppUnitsToFloatPixels(aRect.x, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aRect.y, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aRect.width, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aRect.height, float(AppUnitsPerCSSPixel())));
}
static CSSMargin FromAppUnits(const nsMargin& aMargin) {
return CSSMargin(NSAppUnitsToFloatPixels(aMargin.top, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aMargin.right, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aMargin.bottom, float(AppUnitsPerCSSPixel())),
NSAppUnitsToFloatPixels(aMargin.left, float(AppUnitsPerCSSPixel())));
}
static CSSIntPoint FromAppUnitsRounded(const nsPoint& aPoint) {
return CSSIntPoint(NSAppUnitsToIntPixels(aPoint.x, float(AppUnitsPerCSSPixel())),
NSAppUnitsToIntPixels(aPoint.y, float(AppUnitsPerCSSPixel())));
}
static CSSIntSize FromAppUnitsRounded(const nsSize& aSize)
{
return CSSIntSize(NSAppUnitsToIntPixels(aSize.width, float(AppUnitsPerCSSPixel())),
NSAppUnitsToIntPixels(aSize.height, float(AppUnitsPerCSSPixel())));
}
static CSSIntRect FromAppUnitsRounded(const nsRect& aRect) {
return CSSIntRect(NSAppUnitsToIntPixels(aRect.x, float(AppUnitsPerCSSPixel())),
NSAppUnitsToIntPixels(aRect.y, float(AppUnitsPerCSSPixel())),
NSAppUnitsToIntPixels(aRect.width, float(AppUnitsPerCSSPixel())),
NSAppUnitsToIntPixels(aRect.height, float(AppUnitsPerCSSPixel())));
}
// Conversions to app units
static nsPoint ToAppUnits(const CSSPoint& aPoint) {
return nsPoint(NSToCoordRoundWithClamp(aPoint.x * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(aPoint.y * float(AppUnitsPerCSSPixel())));
}
static nsPoint ToAppUnits(const CSSIntPoint& aPoint) {
return nsPoint(NSToCoordRoundWithClamp(float(aPoint.x) * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(float(aPoint.y) * float(AppUnitsPerCSSPixel())));
}
static nsSize ToAppUnits(const CSSSize& aSize) {
return nsSize(NSToCoordRoundWithClamp(aSize.width * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(aSize.height * float(AppUnitsPerCSSPixel())));
}
static nsSize ToAppUnits(const CSSIntSize& aSize) {
return nsSize(NSToCoordRoundWithClamp(float(aSize.width) * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(float(aSize.height) * float(AppUnitsPerCSSPixel())));
}
static nsRect ToAppUnits(const CSSRect& aRect) {
return nsRect(NSToCoordRoundWithClamp(aRect.x * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(aRect.y * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(aRect.width * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(aRect.height * float(AppUnitsPerCSSPixel())));
}
static nsRect ToAppUnits(const CSSIntRect& aRect) {
return nsRect(NSToCoordRoundWithClamp(float(aRect.x) * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(float(aRect.y) * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(float(aRect.width) * float(AppUnitsPerCSSPixel())),
NSToCoordRoundWithClamp(float(aRect.height) * float(AppUnitsPerCSSPixel())));
}
};
/*
* The pixels that are referred to as "device pixels" in layout code. In
* general values measured in LayoutDevicePixels are obtained by dividing a
* value in app units by AppUnitsPerDevPixel(). Conversion between CSS pixels
* and LayoutDevicePixels is affected by:
* 1) the "full zoom" (see nsPresContext::SetFullZoom)
* 2) the "widget scale" (see nsIWidget::GetDefaultScale)
*/
struct LayoutDevicePixel {
static LayoutDeviceRect FromAppUnits(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
return LayoutDeviceRect(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerDevPixel)),
NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerDevPixel)),
NSAppUnitsToFloatPixels(aRect.width, float(aAppUnitsPerDevPixel)),
NSAppUnitsToFloatPixels(aRect.height, float(aAppUnitsPerDevPixel)));
}
static LayoutDevicePoint FromAppUnits(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
return LayoutDevicePoint(NSAppUnitsToFloatPixels(aPoint.x, aAppUnitsPerDevPixel),
NSAppUnitsToFloatPixels(aPoint.y, aAppUnitsPerDevPixel));
}
static LayoutDeviceMargin FromAppUnits(const nsMargin& aMargin, nscoord aAppUnitsPerDevPixel) {
return LayoutDeviceMargin(NSAppUnitsToFloatPixels(aMargin.top, aAppUnitsPerDevPixel),
NSAppUnitsToFloatPixels(aMargin.right, aAppUnitsPerDevPixel),
NSAppUnitsToFloatPixels(aMargin.bottom, aAppUnitsPerDevPixel),
NSAppUnitsToFloatPixels(aMargin.left, aAppUnitsPerDevPixel));
}
static LayoutDeviceIntPoint FromAppUnitsRounded(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
return LayoutDeviceIntPoint(NSAppUnitsToIntPixels(aPoint.x, aAppUnitsPerDevPixel),
NSAppUnitsToIntPixels(aPoint.y, aAppUnitsPerDevPixel));
}
static LayoutDeviceIntPoint FromAppUnitsToNearest(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
return LayoutDeviceIntPoint::FromUnknownPoint(aPoint.ToNearestPixels(aAppUnitsPerDevPixel));
}
static LayoutDeviceIntRect FromAppUnitsToNearest(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
return LayoutDeviceIntRect::FromUnknownRect(aRect.ToNearestPixels(aAppUnitsPerDevPixel));
}
static LayoutDeviceIntRect FromAppUnitsToInside(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
return LayoutDeviceIntRect::FromUnknownRect(aRect.ToInsidePixels(aAppUnitsPerDevPixel));
}
static LayoutDeviceIntSize FromAppUnitsRounded(const nsSize& aSize, nscoord aAppUnitsPerDevPixel) {
return LayoutDeviceIntSize(
NSAppUnitsToIntPixels(aSize.width, aAppUnitsPerDevPixel),
NSAppUnitsToIntPixels(aSize.height, aAppUnitsPerDevPixel));
}
static nsPoint ToAppUnits(const LayoutDeviceIntPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
return nsPoint(aPoint.x * aAppUnitsPerDevPixel,
aPoint.y * aAppUnitsPerDevPixel);
}
static nsSize ToAppUnits(const LayoutDeviceIntSize& aSize, nscoord aAppUnitsPerDevPixel) {
return nsSize(aSize.width * aAppUnitsPerDevPixel,
aSize.height * aAppUnitsPerDevPixel);
}
static nsSize ToAppUnits(const LayoutDeviceSize& aSize, nscoord aAppUnitsPerDevPixel) {
return nsSize(NSFloatPixelsToAppUnits(aSize.width, aAppUnitsPerDevPixel),
NSFloatPixelsToAppUnits(aSize.height, aAppUnitsPerDevPixel));
}
static nsRect ToAppUnits(const LayoutDeviceIntRect& aRect, nscoord aAppUnitsPerDevPixel) {
return nsRect(aRect.x * aAppUnitsPerDevPixel,
aRect.y * aAppUnitsPerDevPixel,
aRect.width * aAppUnitsPerDevPixel,
aRect.height * aAppUnitsPerDevPixel);
}
static nsRect ToAppUnits(const LayoutDeviceRect& aRect, nscoord aAppUnitsPerDevPixel) {
return nsRect(NSFloatPixelsToAppUnits(aRect.x, aAppUnitsPerDevPixel),
NSFloatPixelsToAppUnits(aRect.y, aAppUnitsPerDevPixel),
NSFloatPixelsToAppUnits(aRect.width, aAppUnitsPerDevPixel),
NSFloatPixelsToAppUnits(aRect.height, aAppUnitsPerDevPixel));
}
};
/*
* The pixels that layout rasterizes and delivers to the graphics code.
* These also are generally referred to as "device pixels" in layout code.
* Conversion between CSS pixels and LayerPixels is affected by:
* 1) the "display resolution" (see nsIPresShell::SetResolution)
* 2) the "full zoom" (see nsPresContext::SetFullZoom)
* 3) the "widget scale" (see nsIWidget::GetDefaultScale)
* 4) rasterizing at a different scale in the presence of some CSS transforms
*/
struct LayerPixel {
};
/*
* This is Layer coordinates with the Layer's CSS transform applied.
* It can be thought of as intermediate between LayerPixel and
* ParentLayerPixel, as further applying async transforms to this
* yields ParentLayerPixels.
*/
struct CSSTransformedLayerPixel {
};
/*
* Layers are always composited to a render target. This unit
* represents one pixel in the render target. Note that for the
* root render target RenderTargetPixel == ScreenPixel. Also
* any ContainerLayer providing an intermediate surface will
* have RenderTargetPixel == LayerPixel.
*/
struct RenderTargetPixel {
};
/*
* The pixels that are displayed on the screen.
* On non-OMTC platforms this should be equivalent to LayerPixel units.
* On OMTC platforms these may diverge from LayerPixel units temporarily,
* while an asynchronous zoom is happening, but should eventually converge
* back to LayerPixel units. Some variables (such as those representing
* chrome UI element sizes) that are not subject to content zoom should
* generally be represented in ScreenPixel units.
*/
struct ScreenPixel {
};
/* The layer coordinates of the parent frame.
* This can be arrived at in three ways:
* - Start with the CSS coordinates of the parent frame, multiply by the
* device scale and the cumulative resolution of the parent frame.
* - Start with the CSS coordinates of current frame, multiply by the device
* scale, the cumulative resolution of the current frame, and the scales
* from the CSS and async transforms of the current frame.
* - Start with global screen coordinates and unapply all CSS and async
* transforms from the root down to and including the parent.
* It's helpful to look at https://wiki.mozilla.org/Platform/GFX/APZ#Coordinate_systems
* to get a picture of how the various coordinate systems relate to each other.
*/
struct ParentLayerPixel {
};
/*
* Pixels in the coordinate space used by the host OS to manage windows on the
* desktop. What these mean varies between OSs:
* - by default (unless implemented differently in platform-specific widget
* code) they are the same as LayoutDevicePixels
* - on Mac OS X, they're "cocoa points", which correspond to device pixels
* on a non-Retina display, and to 2x device pixels on Retina
* - on Windows *without* per-monitor DPI support, they are Windows "logical
* pixels", i.e. device pixels scaled according to the Windows display DPI
* scaling factor (typically one of 1.25, 1.5, 1.75, 2.0...)
* - on Windows *with* per-monitor DPI support, they are physical device pixels
* on each screen; note that this means the scaling between CSS pixels and
* desktop pixels may vary across multiple displays.
*/
struct DesktopPixel {
};
// Operators to apply ScaleFactors directly to Coords, Points, Rects, Sizes and Margins
template<class src, class dst>
gfx::CoordTyped<dst> operator*(const gfx::CoordTyped<src>& aCoord, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::CoordTyped<dst>(aCoord.value * aScale.scale);
}
template<class src, class dst>
gfx::CoordTyped<dst> operator/(const gfx::CoordTyped<src>& aCoord, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::CoordTyped<dst>(aCoord.value / aScale.scale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator*(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::PointTyped<dst>(aPoint.x * aScale.scale,
aPoint.y * aScale.scale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator/(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::PointTyped<dst>(aPoint.x / aScale.scale,
aPoint.y / aScale.scale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator*(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactors2D<src, dst>& aScale) {
return gfx::PointTyped<dst>(aPoint.x * aScale.xScale,
aPoint.y * aScale.yScale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator/(const gfx::PointTyped<src>& aPoint, const gfx::ScaleFactors2D<dst, src>& aScale) {
return gfx::PointTyped<dst>(aPoint.x / aScale.xScale,
aPoint.y / aScale.yScale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator*(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::PointTyped<dst>(float(aPoint.x) * aScale.scale,
float(aPoint.y) * aScale.scale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator/(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::PointTyped<dst>(float(aPoint.x) / aScale.scale,
float(aPoint.y) / aScale.scale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator*(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactors2D<src, dst>& aScale) {
return gfx::PointTyped<dst>(float(aPoint.x) * aScale.xScale,
float(aPoint.y) * aScale.yScale);
}
template<class src, class dst>
gfx::PointTyped<dst> operator/(const gfx::IntPointTyped<src>& aPoint, const gfx::ScaleFactors2D<dst, src>& aScale) {
return gfx::PointTyped<dst>(float(aPoint.x) / aScale.xScale,
float(aPoint.y) / aScale.yScale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator*(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::RectTyped<dst>(aRect.x * aScale.scale,
aRect.y * aScale.scale,
aRect.width * aScale.scale,
aRect.height * aScale.scale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator/(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::RectTyped<dst>(aRect.x / aScale.scale,
aRect.y / aScale.scale,
aRect.width / aScale.scale,
aRect.height / aScale.scale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator*(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactors2D<src, dst>& aScale) {
return gfx::RectTyped<dst>(aRect.x * aScale.xScale,
aRect.y * aScale.yScale,
aRect.width * aScale.xScale,
aRect.height * aScale.yScale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator/(const gfx::RectTyped<src>& aRect, const gfx::ScaleFactors2D<dst, src>& aScale) {
return gfx::RectTyped<dst>(aRect.x / aScale.xScale,
aRect.y / aScale.yScale,
aRect.width / aScale.xScale,
aRect.height / aScale.yScale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator*(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::RectTyped<dst>(float(aRect.x) * aScale.scale,
float(aRect.y) * aScale.scale,
float(aRect.width) * aScale.scale,
float(aRect.height) * aScale.scale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator/(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::RectTyped<dst>(float(aRect.x) / aScale.scale,
float(aRect.y) / aScale.scale,
float(aRect.width) / aScale.scale,
float(aRect.height) / aScale.scale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator*(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactors2D<src, dst>& aScale) {
return gfx::RectTyped<dst>(float(aRect.x) * aScale.xScale,
float(aRect.y) * aScale.yScale,
float(aRect.width) * aScale.xScale,
float(aRect.height) * aScale.yScale);
}
template<class src, class dst>
gfx::RectTyped<dst> operator/(const gfx::IntRectTyped<src>& aRect, const gfx::ScaleFactors2D<dst, src>& aScale) {
return gfx::RectTyped<dst>(float(aRect.x) / aScale.xScale,
float(aRect.y) / aScale.yScale,
float(aRect.width) / aScale.xScale,
float(aRect.height) / aScale.yScale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator*(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::SizeTyped<dst>(aSize.width * aScale.scale,
aSize.height * aScale.scale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator/(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::SizeTyped<dst>(aSize.width / aScale.scale,
aSize.height / aScale.scale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator*(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactors2D<src, dst>& aScale) {
return gfx::SizeTyped<dst>(aSize.width * aScale.xScale,
aSize.height * aScale.yScale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator/(const gfx::SizeTyped<src>& aSize, const gfx::ScaleFactors2D<dst, src>& aScale) {
return gfx::SizeTyped<dst>(aSize.width / aScale.xScale,
aSize.height / aScale.yScale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator*(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::SizeTyped<dst>(float(aSize.width) * aScale.scale,
float(aSize.height) * aScale.scale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator/(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::SizeTyped<dst>(float(aSize.width) / aScale.scale,
float(aSize.height) / aScale.scale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator*(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactors2D<src, dst>& aScale) {
return gfx::SizeTyped<dst>(float(aSize.width) * aScale.xScale,
float(aSize.height) * aScale.yScale);
}
template<class src, class dst>
gfx::SizeTyped<dst> operator/(const gfx::IntSizeTyped<src>& aSize, const gfx::ScaleFactors2D<dst, src>& aScale) {
return gfx::SizeTyped<dst>(float(aSize.width) / aScale.xScale,
float(aSize.height) / aScale.yScale);
}
template<class src, class dst>
gfx::MarginTyped<dst> operator*(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactor<src, dst>& aScale) {
return gfx::MarginTyped<dst>(aMargin.top * aScale.scale,
aMargin.right * aScale.scale,
aMargin.bottom * aScale.scale,
aMargin.left * aScale.scale);
}
template<class src, class dst>
gfx::MarginTyped<dst> operator/(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactor<dst, src>& aScale) {
return gfx::MarginTyped<dst>(aMargin.top / aScale.scale,
aMargin.right / aScale.scale,
aMargin.bottom / aScale.scale,
aMargin.left / aScale.scale);
}
template<class src, class dst>
gfx::MarginTyped<dst> operator*(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactors2D<src, dst>& aScale) {
return gfx::MarginTyped<dst>(aMargin.top * aScale.yScale,
aMargin.right * aScale.xScale,
aMargin.bottom * aScale.yScale,
aMargin.left * aScale.xScale);
}
template<class src, class dst>
gfx::MarginTyped<dst> operator/(const gfx::MarginTyped<src>& aMargin, const gfx::ScaleFactors2D<dst, src>& aScale) {
return gfx::MarginTyped<dst>(aMargin.top / aScale.yScale,
aMargin.right / aScale.xScale,
aMargin.bottom / aScale.yScale,
aMargin.left / aScale.xScale);
}
// Calculate the max or min or the ratios of the widths and heights of two
// sizes, returning a scale factor in the correct units.
template<class src, class dst>
gfx::ScaleFactor<src, dst> MaxScaleRatio(const gfx::SizeTyped<dst>& aDestSize, const gfx::SizeTyped<src>& aSrcSize) {
return gfx::ScaleFactor<src, dst>(std::max(aDestSize.width / aSrcSize.width,
aDestSize.height / aSrcSize.height));
}
template<class src, class dst>
gfx::ScaleFactor<src, dst> MinScaleRatio(const gfx::SizeTyped<dst>& aDestSize, const gfx::SizeTyped<src>& aSrcSize) {
return gfx::ScaleFactor<src, dst>(std::min(aDestSize.width / aSrcSize.width,
aDestSize.height / aSrcSize.height));
}
} // namespace mozilla
#endif

View file

@ -0,0 +1,14 @@
/* 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 WordMovementType_h___
#define WordMovementType_h___
namespace mozilla {
enum EWordMovementType { eStartWord, eEndWord, eDefaultBehavior };
} // namespace mozilla
#endif

View file

@ -0,0 +1,253 @@
/* -*- Mode: C++; 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/. */
#include "ZoomConstraintsClient.h"
#include <inttypes.h>
#include "FrameMetrics.h"
#include "gfxPrefs.h"
#include "LayersLogging.h"
#include "mozilla/layers/APZCCallbackHelper.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/Event.h"
#include "nsDocument.h"
#include "nsIFrame.h"
#include "nsLayoutUtils.h"
#include "nsPoint.h"
#include "nsPresShell.h"
#include "nsView.h"
#include "nsViewportInfo.h"
#include "Units.h"
#include "UnitTransforms.h"
#define ZCC_LOG(...)
// #define ZCC_LOG(...) printf_stderr("ZCC: " __VA_ARGS__)
NS_IMPL_ISUPPORTS(ZoomConstraintsClient, nsIDOMEventListener, nsIObserver)
static const nsLiteralString DOM_META_ADDED = NS_LITERAL_STRING("DOMMetaAdded");
static const nsLiteralString DOM_META_CHANGED = NS_LITERAL_STRING("DOMMetaChanged");
static const nsLiteralString FULLSCREEN_CHANGED = NS_LITERAL_STRING("fullscreenchange");
static const nsLiteralCString BEFORE_FIRST_PAINT = NS_LITERAL_CSTRING("before-first-paint");
static const nsLiteralCString NS_PREF_CHANGED = NS_LITERAL_CSTRING("nsPref:changed");
using namespace mozilla;
using namespace mozilla::layers;
ZoomConstraintsClient::ZoomConstraintsClient() :
mDocument(nullptr),
mPresShell(nullptr)
{
}
ZoomConstraintsClient::~ZoomConstraintsClient()
{
}
static nsIWidget*
GetWidget(nsIPresShell* aShell)
{
if (!aShell) {
return nullptr;
}
if (nsIFrame* rootFrame = aShell->GetRootFrame()) {
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT)
return rootFrame->GetNearestWidget();
#else
if (nsView* view = rootFrame->GetView()) {
return view->GetWidget();
}
#endif
}
return nullptr;
}
void
ZoomConstraintsClient::Destroy()
{
if (!(mPresShell && mDocument)) {
return;
}
ZCC_LOG("Destroying %p\n", this);
if (mEventTarget) {
mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false);
mEventTarget->RemoveEventListener(DOM_META_CHANGED, this, false);
mEventTarget->RemoveSystemEventListener(FULLSCREEN_CHANGED, this, false);
mEventTarget = nullptr;
}
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
if (observerService) {
observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
}
Preferences::RemoveObserver(this, "browser.ui.zoom.force-user-scalable");
if (mGuid) {
if (nsIWidget* widget = GetWidget(mPresShell)) {
ZCC_LOG("Sending null constraints in %p for { %u, %" PRIu64 " }\n",
this, mGuid->mPresShellId, mGuid->mScrollId);
widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId, Nothing());
mGuid = Nothing();
}
}
mDocument = nullptr;
mPresShell = nullptr;
}
void
ZoomConstraintsClient::Init(nsIPresShell* aPresShell, nsIDocument* aDocument)
{
if (!(aPresShell && aDocument)) {
return;
}
mPresShell = aPresShell;
mDocument = aDocument;
if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
mEventTarget = window->GetParentTarget();
}
if (mEventTarget) {
mEventTarget->AddEventListener(DOM_META_ADDED, this, false);
mEventTarget->AddEventListener(DOM_META_CHANGED, this, false);
mEventTarget->AddSystemEventListener(FULLSCREEN_CHANGED, this, false);
}
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
if (observerService) {
observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false);
}
Preferences::AddStrongObserver(this, "browser.ui.zoom.force-user-scalable");
}
NS_IMETHODIMP
ZoomConstraintsClient::HandleEvent(nsIDOMEvent* event)
{
nsAutoString type;
event->GetType(type);
if (type.Equals(DOM_META_ADDED)) {
ZCC_LOG("Got a dom-meta-added event in %p\n", this);
RefreshZoomConstraints();
} else if (type.Equals(DOM_META_CHANGED)) {
ZCC_LOG("Got a dom-meta-changed event in %p\n", this);
RefreshZoomConstraints();
} else if (type.Equals(FULLSCREEN_CHANGED)) {
ZCC_LOG("Got a fullscreen-change event in %p\n", this);
RefreshZoomConstraints();
}
return NS_OK;
}
NS_IMETHODIMP
ZoomConstraintsClient::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
{
if (SameCOMIdentity(aSubject, mDocument) && BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) {
ZCC_LOG("Got a before-first-paint event in %p\n", this);
RefreshZoomConstraints();
} else if (NS_PREF_CHANGED.EqualsASCII(aTopic)) {
ZCC_LOG("Got a pref-change event in %p\n", this);
// We need to run this later because all the pref change listeners need
// to execute before we can be guaranteed that gfxPrefs::ForceUserScalable()
// returns the updated value.
NS_DispatchToMainThread(NewRunnableMethod(
this, &ZoomConstraintsClient::RefreshZoomConstraints));
}
return NS_OK;
}
void
ZoomConstraintsClient::ScreenSizeChanged()
{
ZCC_LOG("Got a screen-size change notification in %p\n", this);
RefreshZoomConstraints();
}
mozilla::layers::ZoomConstraints
ComputeZoomConstraintsFromViewportInfo(const nsViewportInfo& aViewportInfo)
{
mozilla::layers::ZoomConstraints constraints;
constraints.mAllowZoom = aViewportInfo.IsZoomAllowed() && gfxPrefs::APZAllowZooming();
constraints.mAllowDoubleTapZoom = constraints.mAllowZoom;
if (constraints.mAllowZoom) {
constraints.mMinZoom.scale = aViewportInfo.GetMinZoom().scale;
constraints.mMaxZoom.scale = aViewportInfo.GetMaxZoom().scale;
} else {
constraints.mMinZoom.scale = aViewportInfo.GetDefaultZoom().scale;
constraints.mMaxZoom.scale = aViewportInfo.GetDefaultZoom().scale;
}
return constraints;
}
void
ZoomConstraintsClient::RefreshZoomConstraints()
{
nsIWidget* widget = GetWidget(mPresShell);
if (!widget) {
return;
}
uint32_t presShellId = 0;
FrameMetrics::ViewID viewId = FrameMetrics::NULL_SCROLL_ID;
bool scrollIdentifiersValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
mDocument->GetDocumentElement(),
&presShellId, &viewId);
if (!scrollIdentifiersValid) {
return;
}
LayoutDeviceIntSize screenSize;
if (!nsLayoutUtils::GetContentViewerSize(mPresShell->GetPresContext(), screenSize)) {
return;
}
nsViewportInfo viewportInfo = mDocument->GetViewportInfo(
ViewAs<ScreenPixel>(screenSize, PixelCastJustification::LayoutDeviceIsScreenForBounds));
mozilla::layers::ZoomConstraints zoomConstraints =
ComputeZoomConstraintsFromViewportInfo(viewportInfo);
if (mDocument->Fullscreen()) {
ZCC_LOG("%p is in fullscreen, disallowing zooming\n", this);
zoomConstraints.mAllowZoom = false;
zoomConstraints.mAllowDoubleTapZoom = false;
}
if (zoomConstraints.mAllowDoubleTapZoom) {
// If the CSS viewport is narrower than the screen (i.e. width <= device-width)
// then we disable double-tap-to-zoom behaviour.
CSSToLayoutDeviceScale scale =
mPresShell->GetPresContext()->CSSToDevPixelScale();
if ((viewportInfo.GetSize() * scale).width <= screenSize.width) {
zoomConstraints.mAllowDoubleTapZoom = false;
}
}
// We only ever create a ZoomConstraintsClient for an RCD, so the RSF of
// the presShell must be the RCD-RSF (if it exists).
MOZ_ASSERT(mPresShell->GetPresContext()->IsRootContentDocument());
if (nsIScrollableFrame* rcdrsf = mPresShell->GetRootScrollFrameAsScrollable()) {
ZCC_LOG("Notifying RCD-RSF that it is zoomable: %d\n", zoomConstraints.mAllowZoom);
rcdrsf->SetZoomableByAPZ(zoomConstraints.mAllowZoom);
}
ScrollableLayerGuid newGuid(0, presShellId, viewId);
if (mGuid && mGuid.value() != newGuid) {
ZCC_LOG("Clearing old constraints in %p for { %u, %" PRIu64 " }\n",
this, mGuid->mPresShellId, mGuid->mScrollId);
// If the guid changes, send a message to clear the old one
widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId, Nothing());
}
mGuid = Some(newGuid);
ZCC_LOG("Sending constraints %s in %p for { %u, %" PRIu64 " }\n",
Stringify(zoomConstraints).c_str(), this, presShellId, viewId);
widget->UpdateZoomConstraints(presShellId, viewId, Some(zoomConstraints));
}

View file

@ -0,0 +1,47 @@
/* -*- Mode: C++; 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/. */
#ifndef ZoomConstraintsClient_h_
#define ZoomConstraintsClient_h_
#include "FrameMetrics.h"
#include "mozilla/Maybe.h"
#include "nsIDOMEventListener.h"
#include "nsIObserver.h"
#include "nsWeakPtr.h"
class nsIDOMEventTarget;
class nsIDocument;
class nsIPresShell;
class ZoomConstraintsClient final : public nsIDOMEventListener,
public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMEVENTLISTENER
NS_DECL_NSIOBSERVER
ZoomConstraintsClient();
private:
~ZoomConstraintsClient();
public:
void Init(nsIPresShell* aPresShell, nsIDocument *aDocument);
void Destroy();
void ScreenSizeChanged();
private:
void RefreshZoomConstraints();
nsCOMPtr<nsIDocument> mDocument;
nsIPresShell* MOZ_NON_OWNING_REF mPresShell; // raw ref since the presShell owns this
nsCOMPtr<nsIDOMEventTarget> mEventTarget;
mozilla::Maybe<mozilla::layers::ScrollableLayerGuid> mGuid;
};
#endif

View file

@ -0,0 +1,10 @@
<html>
<body>
<br id="x" style="transform-style: preserve-3d;">
<script>
document.addEventListener("MozReftestInvalidate", function() {
document.getElementById("x").style.transform = "scale(2, 2)";
});
</script>
</body>
</html>

View file

@ -0,0 +1,15 @@
<html>
<head>
<meta charset="UTF-8">
<script>
function boom()
{
var div = document.getElementsByTagName("div")[0];
div.childNodes[1].convertPointFromNode({x:0, y:0}, div.childNodes[0]);
}
</script>
</head>
<body onload="boom();">
<div><span>&#x05D0;C</span> </div>
</body>
</html>

View file

@ -0,0 +1,2 @@
<!DOCTYPE HTML>
<html style="mask: url(#none);"><canvas style="transform: scaleY(-118055395520340);"></canvas></html>

View file

@ -0,0 +1,9 @@
<style>td:first-letter {
</style>
><table border=0>
<td><table id=test1>><td id=test2>
<script>
setTimeout("tCFcrash()", 41);
function tCFcrash() {
test1.appendChild(test2);
}</script>>

View file

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<svg id="s">
<style>
#b { display: none; }
rect { fill:orange; }
</style>
<rect width="10" height="10" fill="lime"/>
</svg>
<style>
#b { display: block; }
rect { fill:blue; }
</style>
<div id="b" style="border:2px solid black">
<svg>
<use xlink:href="#s"/>
</svg>
</div>

View file

@ -0,0 +1,15 @@
<html>
<head>
</head>
<body>
<style>colgroup::after { content:"after"; }</style>
<table>
<colgroup><col style="display: inline;">t</col></colgroup>
</table>
</body>
</html>

View file

@ -0,0 +1,5 @@
<style>.x { } .y { text-transform: uppercase; }</style><span id="I2">a<div id="I3">b</div></span><script>
document.body.offsetTop;
document.querySelector("span").className = "x";
document.querySelector("div").className = "y";
</script>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<script>
function boom()
{
document.documentElement.style.display = "contents";
document.designMode = 'on';
document.documentElement.insertAdjacentHTML("beforeEnd", "<span><optgroup>");
}
</script>
</head>
<body onload="boom();"></body>
</html>

View file

@ -0,0 +1,6 @@
<style>
body::before {
display: ruby;
content: " ";
}
</style>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!--
user_pref("layout.css.grid.enabled", true);
-->
<script>
function boom()
{
document.documentElement.offsetHeight;
document.getElementById("x").style.content = "'x'";
document.documentElement.offsetHeight;
document.getElementById("s").remove();
document.documentElement.offsetHeight;
}
</script>
</head>
<body onload="boom();">
<div style="display: inline-grid; white-space: pre;"><div id="x"><span>
<span>
</span><span id="s"></span></span></div></div>
</body>
</html>

View file

@ -0,0 +1,17 @@
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
<script>
function boom()
{
document.getElementById("l").value="פיל\n";
document.documentElement.removeAttribute("class");
}
window.addEventListener("load", function(){setTimeout(boom, 30)}, 0);
</script>
<hbox dir="rtl">
<label id="l" />
</hbox>
</window>

View file

@ -0,0 +1,14 @@
</body>
<script type="text/javascript">
function convertArrayToStrings(array){array.forEach(function(value,index){array[index]=String.fromCharCode(value);}); return array};
var test0=document.body.appendChild(document.createElement("frame"))
var test1=document.body.appendChild(document.createElement("figure"))
var test2=document.body.appendChild(document.createElement("details"))
var test4=document.body.appendChild(document.createElement("embed"))
for(x=0;x<6;x++){
test0.appendChild(document.createTextNode(convertArrayToStrings([38010,20080,40959,29079,56831,13899,8295]).join('')))
test4.appendChild(test0.cloneNode(true));
}
</script>

View file

@ -0,0 +1,7 @@
<BODY>
<DIV id=container style="POSITION: absolute;"></DIV>
<SCRIPT language=Javascript>
document.getElementById('container').style.position='relative';
</SCRIPT>

View file

@ -0,0 +1,11 @@
<html>
<title>B#121533</title>
<script>function writeSorry() {document.writeln("test");
document.close();
}
</script>
<frameset cols="120,*" onLoad="writeSorry()">
<frame name="topslider" src="#"> <frame name="bottomslider" src="#">
</frameset>
</html>

View file

@ -0,0 +1,12 @@
<html>
<head>
<style>
#myStyle:-moz-display-comboboxcontrol-frame {
-moz-user-input: none !important;
}
#myStyle:-moz-dropdown-list {
-moz-user-input: none !important;
}
</style>
</head>
<body onload="getElementById('mySelect').setAttribute('id', 'myStyle');"><select id="mySelect"></select></body></html>

View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<script>
window.addEventListener("load", function() {
setTimeout(function() {
window.location = "data:text/html,2";
}, 0);
}, false);
window.addEventListener("pagehide", function() {
var x = document.createElement("object");
x.setAttribute("data", "data:text/plain,3");
document.documentElement.appendChild(x);
}, false);
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<body>
<div style="transform: translateY(50%);">
<div style="transform-style: preserve-3d; background-image: -moz-element(#a); position: sticky;" id="a">Q</div>
</div>
</body>
</html>

View file

@ -0,0 +1,10 @@
<html>
<head>
<title>test</title>
</head>
<body>
<div id="test" style="position: absolute;">test</div>
<script type="application/x-javascript">document.getElementById("test").style.position = "fixed";</script>
</body>
</html>

View file

@ -0,0 +1,26 @@
<body>
<script type="application/javascript">
'use strict';
// -sp-context: content
(function () {
let proto = Object.create(HTMLDivElement.prototype);
proto.template = `<style></style>`;
proto.createdCallback = function() {
let shadow = this.createShadowRoot();
if (this.template) {
let te = document.createElement('template');
te.innerHTML = this.template;
shadow.appendChild(document.importNode(te.content, true));
}
};
let UiComponentTest = document.registerElement('ui-component-test', {
prototype: proto,
});
let uic = new UiComponentTest();
document.body.appendChild(uic);
})();
</script>
</body>

View file

@ -0,0 +1,7 @@
<iframe id="iframe" src="1261351-iframe.html"></iframe>
<script type="application/javascript">
let iframe = document.getElementById("iframe");
iframe.addEventListener("load", function() {
document.getElementsByTagName("iframe")[0].marginWidth = "5";
});
</script>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<body>
<div style="isolation:isolate; background-blend-mode:darken; background-image: url(green100x100.jpg)">
<div style="mix-blend-mode: multiply; width: 200px; height: 200px; background-color:red"></div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html style="transform: translateX(3px); display: grid;">
<head>
<!--
user_pref("layout.event-regions.enabled", true);
-->
</head>
<body>
<div style="position: absolute;">Z</div>
</body>
</html>

View file

@ -0,0 +1,2 @@
<!DOCTYPE html>
<rt><span style="background:red;"><li></li>&#x200f;

View file

@ -0,0 +1,8 @@
<HTML><HEAD><TITLE>Testcase for bug 128855</TITLE></HEAD>
<BODY>
<P style="FONT-VARIANT: small-caps">2.3&nbsp;
éÄÅÎÔÉÆÉËÁÔÏÒÙ.........................................................................................................................................................................
</P>
</BODY></HTML>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script>
function boom()
{
document.body.style.overflow = "scroll";
c.style.visibility = "";
}
</script>
</head>
<body onload="boom();">
<div id="c" style="position: relative; transition: 2s; display: table-cell; bottom: 0.1vw;"></div>
</body>
</html>

View file

@ -0,0 +1,6 @@
<body onload="document.documentElement.offsetWidth; document.querySelector('details').style.color = 'green'">
<details style="display: block; overflow: scroll;">
<summary>Some summary</summary>
The details
</details>
</body>

View file

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<title>Testcase, bug 1299736</title>
<div id="A" style="transform: translateX(50px)">
<div id="B">
<div id="C" style="position: fixed">
</div>
</div>
</div>
<script>
document.getElementById("C").offsetLeft; // flush
document.getElementById("B").style.transform = "translateX(50px)";
document.getElementById("A").style.transform = "";
</script>

View file

@ -0,0 +1,31 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<svg xmlns="http://www.w3.org/2000/svg">
<title>Crash test for bug 1308793</title>
<style id="s" type="text/css">
tspan { fill: green;}
.flex { display:flex; }
.grid { display:grid; }
.col { columns: 3; }
</style>
<text class="flex">
<tspan x="100" y="50">A</tspan>
B
</text>
<text class="grid">
<tspan x="100" y="50">A</tspan>
B
</text>
<text class="col">
<tspan x="100" y="50">A</tspan>
B
</text>
</svg>

After

Width:  |  Height:  |  Size: 542 B

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<table><tbody></tbody><tfoot></tfoot></table>
<script>
document.body.offsetTop;
let parent = document.querySelector("table");
let comment = document.createComment("hello");
let footer = document.querySelector("tfoot");
parent.insertBefore(comment, footer);
</script>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<table><tbody></tbody><tfoot></tfoot></table>
<script>
document.body.offsetTop;
let parent = document.querySelector("table");
let pi = document.createProcessingInstruction('xml-stylesheet', 'href="test.css"');
let footer = document.querySelector("tfoot");
parent.insertBefore(pi, footer);
</script>

View file

@ -0,0 +1,27 @@
<html>
<head>
<title>Bug 133410</title>
</head>
<body>
<table>
<tr>
<td>
<form>
<input type="text">
<input type="submit" value="Search">
<!-- note missing form close tag -->
</td>
</tr>
</table>
<table>
<span>
<!-- simple animated gif -->
<img src="../../../testing/crashtest/images/animfish.gif">
</span>
</table>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more