mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 15:28:39 +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
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue