mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 20:47:44 +00:00
288 lines
9.2 KiB
C++
288 lines
9.2 KiB
C++
/* -*- 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 "SVGFragmentIdentifier.h"
|
|
|
|
#include "mozilla/dom/SVGSVGElement.h"
|
|
#include "mozilla/dom/SVGViewElement.h"
|
|
#include "nsContentUtils.h" // for nsCharSeparatedTokenizerTemplate
|
|
#include "nsSVGAnimatedTransformList.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
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()) == '(';
|
|
}
|
|
|
|
inline bool
|
|
IgnoreWhitespace(char16_t aChar)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static dom::SVGViewElement*
|
|
GetViewElement(nsIDocument* aDocument, const nsAString& aId)
|
|
{
|
|
dom::Element* element = aDocument->GetElementById(aId);
|
|
return (element && element->IsSVGElement(nsGkAtoms::view)) ?
|
|
static_cast<dom::SVGViewElement*>(element) : nullptr;
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::SaveOldPreserveAspectRatio(dom::SVGSVGElement* root)
|
|
{
|
|
if (root->mPreserveAspectRatio.IsExplicitlySet()) {
|
|
root->SetPreserveAspectRatioProperty(root->mPreserveAspectRatio.GetBaseValue());
|
|
}
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::RestoreOldPreserveAspectRatio(dom::SVGSVGElement* root)
|
|
{
|
|
const SVGPreserveAspectRatio* oldPARPtr = root->GetPreserveAspectRatioProperty();
|
|
if (oldPARPtr) {
|
|
root->mPreserveAspectRatio.SetBaseValue(*oldPARPtr, root);
|
|
} else if (root->mPreserveAspectRatio.IsExplicitlySet()) {
|
|
mozilla::ErrorResult error;
|
|
root->RemoveAttribute(NS_LITERAL_STRING("preserveAspectRatio"), error);
|
|
}
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::SaveOldViewBox(dom::SVGSVGElement* root)
|
|
{
|
|
if (root->mViewBox.IsExplicitlySet()) {
|
|
root->SetViewBoxProperty(root->mViewBox.GetBaseValue());
|
|
}
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::RestoreOldViewBox(dom::SVGSVGElement* root)
|
|
{
|
|
const nsSVGViewBoxRect* oldViewBoxPtr = root->GetViewBoxProperty();
|
|
if (oldViewBoxPtr) {
|
|
root->mViewBox.SetBaseValue(*oldViewBoxPtr, root);
|
|
} else if (root->mViewBox.IsExplicitlySet()) {
|
|
mozilla::ErrorResult error;
|
|
root->RemoveAttribute(NS_LITERAL_STRING("viewBox"), error);
|
|
}
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::SaveOldZoomAndPan(dom::SVGSVGElement* root)
|
|
{
|
|
if (root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].IsExplicitlySet()) {
|
|
root->SetZoomAndPanProperty(root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].GetBaseValue());
|
|
}
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::RestoreOldZoomAndPan(dom::SVGSVGElement* root)
|
|
{
|
|
uint16_t oldZoomAndPan = root->GetZoomAndPanProperty();
|
|
if (oldZoomAndPan != SVG_ZOOMANDPAN_UNKNOWN) {
|
|
root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].SetBaseValue(oldZoomAndPan, root);
|
|
} else if (root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].IsExplicitlySet()) {
|
|
mozilla::ErrorResult error;
|
|
root->RemoveAttribute(NS_LITERAL_STRING("zoomAndPan"), error);
|
|
}
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::SaveOldTransform(dom::SVGSVGElement* root)
|
|
{
|
|
nsSVGAnimatedTransformList* transformList = root->GetAnimatedTransformList();
|
|
|
|
if (transformList && transformList->IsExplicitlySet()) {
|
|
root->SetTransformProperty(transformList->GetBaseValue());
|
|
}
|
|
}
|
|
|
|
void
|
|
SVGFragmentIdentifier::RestoreOldTransform(dom::SVGSVGElement* root)
|
|
{
|
|
const SVGTransformList* oldTransformPtr = root->GetTransformProperty();
|
|
if (oldTransformPtr) {
|
|
root->GetAnimatedTransformList(nsSVGElement::DO_ALLOCATE)->SetBaseValue(*oldTransformPtr);
|
|
} else {
|
|
nsSVGAnimatedTransformList* transformList = root->GetAnimatedTransformList();
|
|
if (transformList && transformList->IsExplicitlySet()) {
|
|
mozilla::ErrorResult error;
|
|
root->RemoveAttribute(NS_LITERAL_STRING("transform"), error);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
SVGFragmentIdentifier::ProcessSVGViewSpec(const nsAString& aViewSpec,
|
|
dom::SVGSVGElement* root)
|
|
{
|
|
if (!IsMatchingParameter(aViewSpec, NS_LITERAL_STRING("svgView"))) {
|
|
return false;
|
|
}
|
|
|
|
// 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.
|
|
|
|
bool viewBoxFound = false;
|
|
bool preserveAspectRatioFound = false;
|
|
bool transformFound = false;
|
|
bool zoomAndPanFound = false;
|
|
|
|
// Each token is a SVGViewAttribute
|
|
int32_t bracketPos = aViewSpec.FindChar('(');
|
|
uint32_t lengthOfViewSpec = aViewSpec.Length() - bracketPos - 2;
|
|
nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> tokenizer(
|
|
Substring(aViewSpec, bracketPos + 1, lengthOfViewSpec), ';');
|
|
|
|
if (!tokenizer.hasMoreTokens()) {
|
|
return false;
|
|
}
|
|
do {
|
|
|
|
nsAutoString token(tokenizer.nextToken());
|
|
|
|
bracketPos = token.FindChar('(');
|
|
if (bracketPos < 1 || token.Last() != ')') {
|
|
// invalid SVGViewAttribute syntax
|
|
return false;
|
|
}
|
|
|
|
const nsAString ¶ms =
|
|
Substring(token, bracketPos + 1, token.Length() - bracketPos - 2);
|
|
|
|
if (IsMatchingParameter(token, NS_LITERAL_STRING("viewBox"))) {
|
|
if (viewBoxFound ||
|
|
NS_FAILED(root->mViewBox.SetBaseValueString(
|
|
params, root, true))) {
|
|
return false;
|
|
}
|
|
viewBoxFound = true;
|
|
} else if (IsMatchingParameter(token, NS_LITERAL_STRING("preserveAspectRatio"))) {
|
|
if (preserveAspectRatioFound ||
|
|
NS_FAILED(root->mPreserveAspectRatio.SetBaseValueString(
|
|
params, root, true))) {
|
|
return false;
|
|
}
|
|
preserveAspectRatioFound = true;
|
|
} else if (IsMatchingParameter(token, NS_LITERAL_STRING("transform"))) {
|
|
if (transformFound ||
|
|
NS_FAILED(root->GetAnimatedTransformList(nsSVGElement::DO_ALLOCATE)->
|
|
SetBaseValueString(params))) {
|
|
return false;
|
|
}
|
|
transformFound = true;
|
|
} else if (IsMatchingParameter(token, NS_LITERAL_STRING("zoomAndPan"))) {
|
|
if (zoomAndPanFound) {
|
|
return false;
|
|
}
|
|
nsIAtom* valAtom = NS_GetStaticAtom(params);
|
|
if (!valAtom) {
|
|
return false;
|
|
}
|
|
const nsSVGEnumMapping* mapping = dom::SVGSVGElement::sZoomAndPanMap;
|
|
while (mapping->mKey) {
|
|
if (valAtom == *(mapping->mKey)) {
|
|
// If we've got a valid zoomAndPan value, then set it on our root element.
|
|
if (NS_FAILED(root->mEnumAttributes[dom::SVGSVGElement::ZOOMANDPAN].SetBaseValue(
|
|
mapping->mVal, root))) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
mapping++;
|
|
}
|
|
if (!mapping->mKey) {
|
|
// Unrecognised zoomAndPan value
|
|
return false;
|
|
}
|
|
zoomAndPanFound = true;
|
|
} else {
|
|
// We don't support viewTarget currently
|
|
return false;
|
|
}
|
|
} while (tokenizer.hasMoreTokens());
|
|
|
|
if (root->mUseCurrentView) {
|
|
// A previous SVGViewSpec may have overridden some attributes.
|
|
// If they are no longer overridden we need to restore the old values.
|
|
if (!transformFound) {
|
|
RestoreOldTransform(root);
|
|
}
|
|
if (!viewBoxFound) {
|
|
RestoreOldViewBox(root);
|
|
}
|
|
if (!preserveAspectRatioFound) {
|
|
RestoreOldPreserveAspectRatio(root);
|
|
}
|
|
if (!zoomAndPanFound) {
|
|
RestoreOldZoomAndPan(root);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SVGFragmentIdentifier::ProcessFragmentIdentifier(nsIDocument* aDocument,
|
|
const nsAString& aAnchorName)
|
|
{
|
|
MOZ_ASSERT(aDocument->GetRootElement()->IsSVGElement(nsGkAtoms::svg),
|
|
"expecting an SVG root element");
|
|
|
|
dom::SVGSVGElement* rootElement =
|
|
static_cast<dom::SVGSVGElement*>(aDocument->GetRootElement());
|
|
|
|
if (!rootElement->mUseCurrentView) {
|
|
SaveOldViewBox(rootElement);
|
|
SaveOldPreserveAspectRatio(rootElement);
|
|
SaveOldZoomAndPan(rootElement);
|
|
}
|
|
|
|
const dom::SVGViewElement* viewElement = GetViewElement(aDocument, aAnchorName);
|
|
|
|
if (viewElement) {
|
|
if (!rootElement->mCurrentViewID) {
|
|
rootElement->mCurrentViewID = new nsString();
|
|
}
|
|
*rootElement->mCurrentViewID = aAnchorName;
|
|
rootElement->mUseCurrentView = true;
|
|
rootElement->InvalidateTransformNotifyFrame();
|
|
// not an svgView()-style fragment identifier, return false so the caller
|
|
// continues processing to match any :target pseudo elements
|
|
return false;
|
|
}
|
|
|
|
bool wasOverridden = !!rootElement->mCurrentViewID;
|
|
rootElement->mCurrentViewID = nullptr;
|
|
|
|
rootElement->mUseCurrentView = ProcessSVGViewSpec(aAnchorName, rootElement);
|
|
if (rootElement->mUseCurrentView) {
|
|
return true;
|
|
}
|
|
RestoreOldViewBox(rootElement);
|
|
rootElement->ClearViewBoxProperty();
|
|
RestoreOldPreserveAspectRatio(rootElement);
|
|
rootElement->ClearPreserveAspectRatioProperty();
|
|
RestoreOldZoomAndPan(rootElement);
|
|
rootElement->ClearZoomAndPanProperty();
|
|
RestoreOldTransform(rootElement);
|
|
rootElement->ClearTransformProperty();
|
|
if (wasOverridden) {
|
|
rootElement->InvalidateTransformNotifyFrame();
|
|
}
|
|
return false;
|
|
}
|