See https://www.w3.org/TR/media-frags/#valid-uri-spatial Differential Revision: https://phabricator.services.mozilla.com/D303672
283 lines
8.6 KiB
C++
283 lines
8.6 KiB
C++
/* 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 "SVGFragmentIdentifier.h"
|
|
|
|
#include "SVGAnimatedTransformList.h"
|
|
#include "mozilla/MediaFragmentURIParser.h"
|
|
#include "mozilla/SVGOuterSVGFrame.h"
|
|
#include "mozilla/dom/SVGSVGElement.h"
|
|
#include "mozilla/dom/SVGViewElement.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace dom;
|
|
|
|
static bool IsMatchingParameter(const nsAString& aString,
|
|
const nsAString& aParameterName) {
|
|
// The first two tests ensure aString.Length() > aParameterName.Length()
|
|
// so it's then safe to do the third test
|
|
return StringBeginsWith(aString, aParameterName) && aString.Last() == ')' &&
|
|
aString.CharAt(aParameterName.Length()) == '(';
|
|
}
|
|
|
|
// Handles setting/clearing the root's mSVGView pointer.
|
|
class MOZ_RAII AutoFragmentHandler {
|
|
public:
|
|
explicit AutoFragmentHandler(SVGSVGElement* aRoot) : mRoot(aRoot) {}
|
|
|
|
~AutoFragmentHandler() {
|
|
if (!mValid) {
|
|
mStartTime = Nothing();
|
|
mEndTime = Nothing();
|
|
mCurrentViewID = VoidString();
|
|
mSVGView = nullptr;
|
|
}
|
|
if (mStartTime) {
|
|
mRoot->SetCurrentTime(mStartTime.value());
|
|
}
|
|
if (mEndTime) {
|
|
mRoot->PauseAnimationsAt(mEndTime.value());
|
|
}
|
|
if (mCurrentViewID.IsVoid()) {
|
|
mRoot->SetViewSpec(std::move(mSVGView));
|
|
} else {
|
|
mRoot->SetCurrentView(mCurrentViewID);
|
|
}
|
|
if (nsIFrame* f = mRoot->GetPrimaryFrame()) {
|
|
if (SVGOuterSVGFrame* osf = do_QueryFrame(f)) {
|
|
osf->MaybeSendIntrinsicSizeAndRatioToEmbedder();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool StopProcessing() const {
|
|
// If we're an svgView()-style fragment identifier, return true so the
|
|
// caller knows it doesn't need to match any :target pseudo elements
|
|
return mValid && mViewSpecProcessed;
|
|
}
|
|
|
|
void CreateSVGView(bool aFromViewSpec) {
|
|
if (!mSVGView) {
|
|
mSVGView = std::make_unique<SVGView>();
|
|
}
|
|
if (aFromViewSpec) {
|
|
mViewSpecProcessed = true;
|
|
}
|
|
}
|
|
|
|
void SetCurrentViewID(const nsAString& aCurrentViewID) {
|
|
mCurrentViewID = aCurrentViewID;
|
|
mValid = true;
|
|
}
|
|
|
|
bool SetViewBox(const gfx::Rect& aRect) {
|
|
mValid = mCurrentViewID.IsVoid() && !mViewSpecProcessed;
|
|
if (mValid) {
|
|
CreateSVGView(false);
|
|
SVGViewBox viewBox(aRect.x, aRect.y, aRect.width, aRect.height);
|
|
mSVGView->mViewBox.SetBaseValue(viewBox, mRoot, false);
|
|
}
|
|
mMediaFragmentProcessed = true;
|
|
return mValid;
|
|
}
|
|
|
|
bool SetStartAndEndTime(Maybe<float> aStartTime, Maybe<float> aEndTime) {
|
|
if (!mMediaFragmentProcessed) {
|
|
mValid = true;
|
|
mMediaFragmentProcessed = true;
|
|
}
|
|
if (mValid) {
|
|
mStartTime = aStartTime;
|
|
mEndTime = aEndTime;
|
|
}
|
|
return mValid;
|
|
}
|
|
|
|
bool ProcessViewSpecAttr(const nsAString& aToken, const nsAString& aParams) {
|
|
MOZ_ASSERT(mSVGView, "CreateSVGView should have been called");
|
|
|
|
// SVGViewAttributes may occur in any order, but each type may only occur
|
|
// at most one time in a correctly formed SVGViewSpec.
|
|
// If we encounter any attribute more than once or get any syntax errors
|
|
// we're going to return false and cancel any changes.
|
|
|
|
if (IsMatchingParameter(aToken, u"viewBox"_ns)) {
|
|
if (mSVGView->mViewBox.IsExplicitlySet() ||
|
|
NS_FAILED(
|
|
mSVGView->mViewBox.SetBaseValueString(aParams, mRoot, false))) {
|
|
return false;
|
|
}
|
|
} else if (IsMatchingParameter(aToken, u"preserveAspectRatio"_ns)) {
|
|
if (mSVGView->mPreserveAspectRatio.IsExplicitlySet() ||
|
|
NS_FAILED(mSVGView->mPreserveAspectRatio.SetBaseValueString(
|
|
aParams, mRoot, false))) {
|
|
return false;
|
|
}
|
|
} else if (IsMatchingParameter(aToken, u"transform"_ns)) {
|
|
if (mSVGView->mTransforms) {
|
|
return false;
|
|
}
|
|
mSVGView->mTransforms = std::make_unique<SVGAnimatedTransformList>();
|
|
if (NS_FAILED(
|
|
mSVGView->mTransforms->SetBaseValueString(aParams, mRoot))) {
|
|
return false;
|
|
}
|
|
} else if (IsMatchingParameter(aToken, u"zoomAndPan"_ns)) {
|
|
if (mSVGView->mZoomAndPan.IsExplicitlySet()) {
|
|
return false;
|
|
}
|
|
nsAtom* valAtom = NS_GetStaticAtom(aParams);
|
|
if (!valAtom || !mSVGView->mZoomAndPan.SetBaseValueAtom(valAtom, mRoot)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
SVGSVGElement* RootElement() const { return mRoot; }
|
|
|
|
void SetValid(bool aValid = true) { mValid = aValid; }
|
|
|
|
private:
|
|
RefPtr<SVGSVGElement> mRoot;
|
|
nsString mCurrentViewID = VoidString();
|
|
std::unique_ptr<SVGView> mSVGView;
|
|
Maybe<float> mStartTime, mEndTime;
|
|
bool mViewSpecProcessed = false;
|
|
bool mMediaFragmentProcessed = false;
|
|
bool mValid = false;
|
|
};
|
|
|
|
static bool ProcessCurrentView(Document* aDocument, const nsAString& aID,
|
|
AutoFragmentHandler& aViewHandler) {
|
|
if (!SVGViewElement::FromNodeOrNull(aDocument->GetElementById(aID))) {
|
|
return false;
|
|
}
|
|
aViewHandler.SetCurrentViewID(aID);
|
|
return true;
|
|
}
|
|
|
|
static bool ProcessSVGViewSpec(const nsAString& aViewSpec,
|
|
AutoFragmentHandler& aViewHandler) {
|
|
if (!IsMatchingParameter(aViewSpec, u"svgView"_ns)) {
|
|
return false;
|
|
}
|
|
|
|
// Each token is a SVGViewAttribute
|
|
int32_t bracketPos = aViewSpec.FindChar('(');
|
|
uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2;
|
|
nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing> tokenizer(
|
|
Substring(aViewSpec, bracketPos + 1, lengthOfViewSpec), ';');
|
|
|
|
if (!tokenizer.hasMoreTokens()) {
|
|
return false;
|
|
}
|
|
aViewHandler.CreateSVGView(true);
|
|
|
|
do {
|
|
nsAutoString token(tokenizer.nextToken());
|
|
|
|
bracketPos = token.FindChar('(');
|
|
if (bracketPos < 1 || token.Last() != ')') {
|
|
// invalid SVGViewAttribute syntax
|
|
return false;
|
|
}
|
|
|
|
const nsAString& params =
|
|
Substring(token, bracketPos + 1, token.Length() - bracketPos - 2);
|
|
|
|
if (!aViewHandler.ProcessViewSpecAttr(token, params)) {
|
|
return false;
|
|
}
|
|
|
|
} while (tokenizer.hasMoreTokens());
|
|
|
|
aViewHandler.SetValid();
|
|
return true;
|
|
}
|
|
|
|
static bool ProcessMediaFragment(const nsAString& aMediaFragment,
|
|
AutoFragmentHandler& aViewHandler) {
|
|
NS_ConvertUTF16toUTF8 mediaFragment(aMediaFragment);
|
|
MediaFragmentURIParser parser(mediaFragment);
|
|
|
|
SVGSVGElement* root = aViewHandler.RootElement();
|
|
|
|
Maybe<float> startTime, endTime;
|
|
if (parser.HasStartTime()) {
|
|
startTime = Some(parser.GetStartTime());
|
|
}
|
|
if (parser.HasEndTime()) {
|
|
endTime = Some(parser.GetEndTime());
|
|
}
|
|
if (startTime || endTime) {
|
|
MOZ_ASSERT(!parser.HasClip(), "Clip should be a separate parameter");
|
|
return aViewHandler.SetStartAndEndTime(startTime, endTime);
|
|
}
|
|
if (parser.HasClip()) {
|
|
gfx::Rect rect = IntRectToRect(parser.GetClip());
|
|
gfx::Size size = root->GetIntrinsicSizeWithFallback();
|
|
if (parser.GetClipUnit() == eClipUnit_Percent) {
|
|
rect.Scale(size.width / 100.0f, size.height / 100.0f);
|
|
}
|
|
if (rect.XMost() > size.width) {
|
|
rect.width = size.width - rect.x;
|
|
}
|
|
if (rect.YMost() > size.height) {
|
|
rect.height = size.height - rect.y;
|
|
}
|
|
if (!rect.IsEmpty()) {
|
|
return aViewHandler.SetViewBox(rect);
|
|
}
|
|
}
|
|
|
|
aViewHandler.SetValid(false);
|
|
return false;
|
|
}
|
|
|
|
static bool ProcessFirstParameter(Document* aDocument,
|
|
const nsAString& aParameter,
|
|
AutoFragmentHandler& aViewHandler) {
|
|
if (ProcessCurrentView(aDocument, aParameter, aViewHandler)) {
|
|
return true;
|
|
}
|
|
if (ProcessSVGViewSpec(aParameter, aViewHandler)) {
|
|
return true;
|
|
}
|
|
if (ProcessMediaFragment(aParameter, aViewHandler)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SVGFragmentIdentifier::ProcessFragmentIdentifier(
|
|
Document* aDocument, const nsAString& aAnchorName) {
|
|
MOZ_ASSERT(aDocument->GetSVGRootElement(), "expecting an SVG root element");
|
|
|
|
nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing> specTokenizer(
|
|
aAnchorName, '&');
|
|
if (!specTokenizer.hasMoreTokens()) {
|
|
return false;
|
|
}
|
|
nsAutoString parameter(specTokenizer.nextToken());
|
|
|
|
RefPtr rootElement = SVGSVGElement::FromNode(aDocument->GetRootElement());
|
|
AutoFragmentHandler fragmentHandler(rootElement);
|
|
|
|
if (!ProcessFirstParameter(aDocument, parameter, fragmentHandler)) {
|
|
return false;
|
|
}
|
|
while (specTokenizer.hasMoreTokens()) {
|
|
parameter = specTokenizer.nextToken();
|
|
ProcessMediaFragment(parameter, fragmentHandler);
|
|
}
|
|
return fragmentHandler.StopProcessing();
|
|
}
|
|
|
|
} // namespace mozilla
|