mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-01 04:47:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
392
layout/base/AccessibleCaret.cpp
Normal file
392
layout/base/AccessibleCaret.cpp
Normal 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
|
||||
257
layout/base/AccessibleCaret.h
Normal file
257
layout/base/AccessibleCaret.h
Normal 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__
|
||||
829
layout/base/AccessibleCaretEventHub.cpp
Normal file
829
layout/base/AccessibleCaretEventHub.cpp
Normal 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
|
||||
226
layout/base/AccessibleCaretEventHub.h
Normal file
226
layout/base/AccessibleCaretEventHub.h
Normal 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
|
||||
27
layout/base/AccessibleCaretLogger.h
Normal file
27
layout/base/AccessibleCaretLogger.h
Normal 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
|
||||
1474
layout/base/AccessibleCaretManager.cpp
Normal file
1474
layout/base/AccessibleCaretManager.cpp
Normal file
File diff suppressed because it is too large
Load diff
347
layout/base/AccessibleCaretManager.h
Normal file
347
layout/base/AccessibleCaretManager.h
Normal 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
|
||||
555
layout/base/ActiveLayerTracker.cpp
Normal file
555
layout/base/ActiveLayerTracker.cpp
Normal 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
|
||||
141
layout/base/ActiveLayerTracker.h
Normal file
141
layout/base/ActiveLayerTracker.h
Normal 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_ */
|
||||
36
layout/base/ArenaObjectID.h
Normal file
36
layout/base/ArenaObjectID.h
Normal 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
167
layout/base/ArenaRefPtr.h
Normal 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
|
||||
47
layout/base/ArenaRefPtrInlines.h
Normal file
47
layout/base/ArenaRefPtrInlines.h
Normal 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
75
layout/base/BorderCache.h
Normal 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_ */
|
||||
28
layout/base/BorderConsts.h
Normal file
28
layout/base/BorderConsts.h
Normal 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_ */
|
||||
22
layout/base/CaretAssociationHint.h
Normal file
22
layout/base/CaretAssociationHint.h
Normal 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
|
||||
427
layout/base/DashedCornerFinder.cpp
Normal file
427
layout/base/DashedCornerFinder.cpp
Normal 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
|
||||
277
layout/base/DashedCornerFinder.h
Normal file
277
layout/base/DashedCornerFinder.h
Normal 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_ */
|
||||
476
layout/base/DisplayItemClip.cpp
Normal file
476
layout/base/DisplayItemClip.cpp
Normal 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
|
||||
191
layout/base/DisplayItemClip.h
Normal file
191
layout/base/DisplayItemClip.h
Normal 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_ */
|
||||
57
layout/base/DisplayItemScrollClip.cpp
Normal file
57
layout/base/DisplayItemScrollClip.cpp
Normal 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
|
||||
126
layout/base/DisplayItemScrollClip.h
Normal file
126
layout/base/DisplayItemScrollClip.h
Normal 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_ */
|
||||
227
layout/base/DisplayListClipState.cpp
Normal file
227
layout/base/DisplayListClipState.cpp
Normal 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
|
||||
468
layout/base/DisplayListClipState.h
Normal file
468
layout/base/DisplayListClipState.h
Normal 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_ */
|
||||
568
layout/base/DottedCornerFinder.cpp
Normal file
568
layout/base/DottedCornerFinder.cpp
Normal 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
|
||||
438
layout/base/DottedCornerFinder.h
Normal file
438
layout/base/DottedCornerFinder.h
Normal 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_ */
|
||||
6378
layout/base/FrameLayerBuilder.cpp
Normal file
6378
layout/base/FrameLayerBuilder.cpp
Normal file
File diff suppressed because it is too large
Load diff
737
layout/base/FrameLayerBuilder.h
Normal file
737
layout/base/FrameLayerBuilder.h
Normal 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_ */
|
||||
239
layout/base/FramePropertyTable.cpp
Normal file
239
layout/base/FramePropertyTable.cpp
Normal 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
|
||||
442
layout/base/FramePropertyTable.h
Normal file
442
layout/base/FramePropertyTable.h
Normal 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_ */
|
||||
399
layout/base/GeometryUtils.cpp
Normal file
399
layout/base/GeometryUtils.cpp
Normal 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
|
||||
65
layout/base/GeometryUtils.h
Normal file
65
layout/base/GeometryUtils.h
Normal 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
26
layout/base/LayerState.h
Normal 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
|
||||
33
layout/base/LayoutLogging.cpp
Normal file
33
layout/base/LayoutLogging.cpp
Normal 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
|
||||
64
layout/base/LayoutLogging.h
Normal file
64
layout/base/LayoutLogging.h
Normal 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
|
||||
70
layout/base/MaskLayerImageCache.cpp
Normal file
70
layout/base/MaskLayerImageCache.cpp
Normal 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
|
||||
289
layout/base/MaskLayerImageCache.h
Normal file
289
layout/base/MaskLayerImageCache.h
Normal 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
|
||||
415
layout/base/MobileViewportManager.cpp
Normal file
415
layout/base/MobileViewportManager.cpp
Normal 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));
|
||||
}
|
||||
97
layout/base/MobileViewportManager.h
Normal file
97
layout/base/MobileViewportManager.h
Normal 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
|
||||
|
||||
215
layout/base/OverflowChangedTracker.h
Normal file
215
layout/base/OverflowChangedTracker.h
Normal 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
|
||||
11
layout/base/PaintTracker.cpp
Normal file
11
layout/base/PaintTracker.cpp
Normal 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
|
||||
34
layout/base/PaintTracker.h
Normal file
34
layout/base/PaintTracker.h
Normal 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
|
||||
656
layout/base/PositionedEventTargeting.cpp
Normal file
656
layout/base/PositionedEventTargeting.cpp
Normal 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
|
||||
32
layout/base/PositionedEventTargeting.h
Normal file
32
layout/base/PositionedEventTargeting.h
Normal 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 */
|
||||
48
layout/base/RestyleLogging.h
Normal file
48
layout/base/RestyleLogging.h
Normal 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 */
|
||||
3974
layout/base/RestyleManager.cpp
Normal file
3974
layout/base/RestyleManager.cpp
Normal file
File diff suppressed because it is too large
Load diff
885
layout/base/RestyleManager.h
Normal file
885
layout/base/RestyleManager.h
Normal 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 */
|
||||
1378
layout/base/RestyleManagerBase.cpp
Normal file
1378
layout/base/RestyleManagerBase.cpp
Normal file
File diff suppressed because it is too large
Load diff
169
layout/base/RestyleManagerBase.h
Normal file
169
layout/base/RestyleManagerBase.h
Normal 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
|
||||
218
layout/base/RestyleManagerHandle.h
Normal file
218
layout/base/RestyleManagerHandle.h
Normal 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
|
||||
195
layout/base/RestyleManagerHandleInlines.h
Normal file
195
layout/base/RestyleManagerHandleInlines.h
Normal 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
|
||||
493
layout/base/RestyleTracker.cpp
Normal file
493
layout/base/RestyleTracker.cpp
Normal 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
|
||||
373
layout/base/RestyleTracker.h
Normal file
373
layout/base/RestyleTracker.h
Normal 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 */
|
||||
23
layout/base/RestyleTrackerInlines.h
Normal file
23
layout/base/RestyleTrackerInlines.h
Normal 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)
|
||||
32
layout/base/ScrollbarStyles.cpp
Normal file
32
layout/base/ScrollbarStyles.cpp
Normal 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
|
||||
82
layout/base/ScrollbarStyles.h
Normal file
82
layout/base/ScrollbarStyles.h
Normal 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
|
||||
594
layout/base/ServoRestyleManager.cpp
Normal file
594
layout/base/ServoRestyleManager.cpp
Normal 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
|
||||
133
layout/base/ServoRestyleManager.h
Normal file
133
layout/base/ServoRestyleManager.h
Normal 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
179
layout/base/StackArena.cpp
Normal 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
94
layout/base/StackArena.h
Normal 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
|
||||
311
layout/base/StaticPresData.cpp
Normal file
311
layout/base/StaticPresData.cpp
Normal 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
|
||||
160
layout/base/StaticPresData.h
Normal file
160
layout/base/StaticPresData.h
Normal 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
|
||||
291
layout/base/TouchManager.cpp
Normal file
291
layout/base/TouchManager.cpp
Normal 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
|
||||
63
layout/base/TouchManager.h
Normal file
63
layout/base/TouchManager.h
Normal 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_) */
|
||||
294
layout/base/UnitTransforms.h
Normal file
294
layout/base/UnitTransforms.h
Normal 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
654
layout/base/Units.h
Normal 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
|
||||
14
layout/base/WordMovementType.h
Normal file
14
layout/base/WordMovementType.h
Normal 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
|
||||
253
layout/base/ZoomConstraintsClient.cpp
Normal file
253
layout/base/ZoomConstraintsClient.cpp
Normal 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));
|
||||
}
|
||||
47
layout/base/ZoomConstraintsClient.h
Normal file
47
layout/base/ZoomConstraintsClient.h
Normal 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
|
||||
|
||||
10
layout/base/crashtests/1001237.html
Normal file
10
layout/base/crashtests/1001237.html
Normal 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>
|
||||
15
layout/base/crashtests/1009036.html
Normal file
15
layout/base/crashtests/1009036.html
Normal 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>אC</span> </div>
|
||||
</body>
|
||||
</html>
|
||||
2
layout/base/crashtests/1043163-1.html
Normal file
2
layout/base/crashtests/1043163-1.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html style="mask: url(#none);"><canvas style="transform: scaleY(-118055395520340);"></canvas></html>
|
||||
9
layout/base/crashtests/1061028.html
Normal file
9
layout/base/crashtests/1061028.html
Normal 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>>
|
||||
18
layout/base/crashtests/1107508-1.html
Normal file
18
layout/base/crashtests/1107508-1.html
Normal 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>
|
||||
15
layout/base/crashtests/1116104.html
Normal file
15
layout/base/crashtests/1116104.html
Normal 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>
|
||||
5
layout/base/crashtests/1127198-1.html
Normal file
5
layout/base/crashtests/1127198-1.html
Normal 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>
|
||||
16
layout/base/crashtests/1140198.html
Normal file
16
layout/base/crashtests/1140198.html
Normal 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>
|
||||
6
layout/base/crashtests/1143535.html
Normal file
6
layout/base/crashtests/1143535.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<style>
|
||||
body::before {
|
||||
display: ruby;
|
||||
content: " ";
|
||||
}
|
||||
</style>
|
||||
29
layout/base/crashtests/1156588.html
Normal file
29
layout/base/crashtests/1156588.html
Normal 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>
|
||||
17
layout/base/crashtests/1162813.xul
Normal file
17
layout/base/crashtests/1162813.xul
Normal 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>
|
||||
14
layout/base/crashtests/1163583.html
Normal file
14
layout/base/crashtests/1163583.html
Normal 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>
|
||||
7
layout/base/crashtests/118931-1.html
Normal file
7
layout/base/crashtests/118931-1.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<BODY>
|
||||
|
||||
<DIV id=container style="POSITION: absolute;"></DIV>
|
||||
|
||||
<SCRIPT language=Javascript>
|
||||
document.getElementById('container').style.position='relative';
|
||||
</SCRIPT>
|
||||
11
layout/base/crashtests/121533-1.html
Normal file
11
layout/base/crashtests/121533-1.html
Normal 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>
|
||||
12
layout/base/crashtests/123049-1.html
Normal file
12
layout/base/crashtests/123049-1.html
Normal 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>
|
||||
17
layout/base/crashtests/1234622-1.html
Normal file
17
layout/base/crashtests/1234622-1.html
Normal 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>
|
||||
8
layout/base/crashtests/1235467-1.html
Normal file
8
layout/base/crashtests/1235467-1.html
Normal 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>
|
||||
10
layout/base/crashtests/123946-1.html
Normal file
10
layout/base/crashtests/123946-1.html
Normal 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>
|
||||
26
layout/base/crashtests/1261351-iframe.html
Normal file
26
layout/base/crashtests/1261351-iframe.html
Normal 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>
|
||||
7
layout/base/crashtests/1261351.html
Normal file
7
layout/base/crashtests/1261351.html
Normal 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>
|
||||
9
layout/base/crashtests/1270797-1.html
Normal file
9
layout/base/crashtests/1270797-1.html
Normal 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>
|
||||
|
||||
BIN
layout/base/crashtests/1270797-1.jpg
Normal file
BIN
layout/base/crashtests/1270797-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
11
layout/base/crashtests/1278455-1.html
Normal file
11
layout/base/crashtests/1278455-1.html
Normal 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>
|
||||
2
layout/base/crashtests/1286889.html
Normal file
2
layout/base/crashtests/1286889.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE html>
|
||||
<rt><span style="background:red;"><li></li>‏
|
||||
8
layout/base/crashtests/128855-1.html
Normal file
8
layout/base/crashtests/128855-1.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<HTML><HEAD><TITLE>Testcase for bug 128855</TITLE></HEAD>
|
||||
<BODY>
|
||||
|
||||
<P style="FONT-VARIANT: small-caps">2.3
|
||||
éÄÅÎÔÉÆÉËÁÔÏÒÙ.........................................................................................................................................................................
|
||||
</P>
|
||||
|
||||
</BODY></HTML>
|
||||
18
layout/base/crashtests/1288608.html
Normal file
18
layout/base/crashtests/1288608.html
Normal 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>
|
||||
6
layout/base/crashtests/1297835.html
Normal file
6
layout/base/crashtests/1297835.html
Normal 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>
|
||||
15
layout/base/crashtests/1299736-1.html
Normal file
15
layout/base/crashtests/1299736-1.html
Normal 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>
|
||||
31
layout/base/crashtests/1308793.svg
Normal file
31
layout/base/crashtests/1308793.svg
Normal 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 |
10
layout/base/crashtests/1308848-1.html
Normal file
10
layout/base/crashtests/1308848-1.html
Normal 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>
|
||||
10
layout/base/crashtests/1308848-2.html
Normal file
10
layout/base/crashtests/1308848-2.html
Normal 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>
|
||||
27
layout/base/crashtests/133410-1.html
Normal file
27
layout/base/crashtests/133410-1.html
Normal 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
Loading…
Add table
Add a link
Reference in a new issue