gecko-dev/dom/svg/SVGUseElement.h
Emilio Cobos Álvarez c2bb091273 Bug 1579181 - Don't keep <use> shadow trees which we know we'll never render. r=longsonr
This partially addresses the regression, but not fully. With this patch we don't
maintain shadow trees for nodes that we know won't get rendered.

This works fast in WebKit / Blink because of a bug in their implementation which
doesn't synchronize style attributes, introduced in [1].

You can see this clearly if you click on the bug's test-case and inspect the
<use> shadow trees (there's no style="stroke:orange" whatsoever).

They can kinda get away with it because they don't properly implement SVG 2. In
particular, in Blink / WebKit, the style of the element in the <use> shadow tree
is the style of the referenced element, which means that even if the style
attribute isn't properly synced it's ~ok since it doesn't end up mattering for
styling.

Easiest test-case for the behavior difference is:

```
<!doctype html>
<style>
  rect:hover {
    fill: green;
  }
</style>
<svg width=300 height=300>
  <g id="canvas">
    <rect fill=red width=100 height=100></rect>
  </g>
  <g>
    <use x=200 href="#canvas"></use>
  </g>
</svg>
```

Where Firefox will properly update each square independently when hovered, but
Blink / WebKit won't.

This used to work faster because in this particular test-case we have 3 hidden
<use> elements whose href is the #canvas, which is basically everything.

Before moving to shadow trees we'd do it using anonymous content, and since we
never got a frame we'd never clone the subtree in the first case.

This case was faster before bug 1450250, but this approach makes other cases
slow that were fixed by that bug, like bug 1485402.

So I'll try to optimize shadow tree syncing instead, I guess, but there's no
good reason not to land this in the meantime IMHO.

[1]: f4b022e64b%5E%21/third_party/WebKit/WebCore/svg/SVGElement.cpp

Differential Revision: https://phabricator.services.mozilla.com/D45953

--HG--
extra : moz-landing-system : lando
2019-09-15 16:09:28 +00:00

172 lines
5.8 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/. */
#ifndef mozilla_dom_SVGUseElement_h
#define mozilla_dom_SVGUseElement_h
#include "mozilla/dom/FromParser.h"
#include "mozilla/dom/IDTracker.h"
#include "mozilla/dom/SVGGraphicsElement.h"
#include "mozilla/RefPtr.h"
#include "nsCOMPtr.h"
#include "nsStubMutationObserver.h"
#include "SVGAnimatedLength.h"
#include "SVGAnimatedString.h"
#include "nsTArray.h"
class nsIContent;
class nsSVGUseFrame;
nsresult NS_NewSVGSVGElement(
nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
mozilla::dom::FromParser aFromParser);
nsresult NS_NewSVGUseElement(
nsIContent** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
namespace mozilla {
struct URLExtraData;
namespace dom {
typedef SVGGraphicsElement SVGUseElementBase;
class SVGUseElement final : public SVGUseElementBase,
public nsStubMutationObserver {
friend class ::nsSVGUseFrame;
protected:
friend nsresult(::NS_NewSVGUseElement(
nsIContent** aResult,
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
explicit SVGUseElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
virtual ~SVGUseElement();
virtual JSObject* WrapNode(JSContext* cx,
JS::Handle<JSObject*> aGivenProto) override;
public:
NS_IMPL_FROMNODE_WITH_TAG(SVGUseElement, kNameSpaceID_SVG, use)
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(bool aNullParent = true) override;
// interfaces:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SVGUseElement, SVGUseElementBase)
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
// SVGElement specializations:
virtual gfxMatrix PrependLocalTransformsTo(
const gfxMatrix& aMatrix,
SVGTransformTypes aWhich = eAllTransforms) const override;
virtual bool HasValidDimensions() const override;
// nsIContent interface
virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
// WebIDL
already_AddRefed<DOMSVGAnimatedString> Href();
already_AddRefed<DOMSVGAnimatedLength> X();
already_AddRefed<DOMSVGAnimatedLength> Y();
already_AddRefed<DOMSVGAnimatedLength> Width();
already_AddRefed<DOMSVGAnimatedLength> Height();
nsIURI* GetSourceDocURI();
URLExtraData* GetContentURLData() const { return mContentURLData; }
// Updates the internal shadow tree to be an up-to-date clone of the
// referenced element.
void UpdateShadowTree();
// Shared code between AfterSetAttr and nsSVGUseFrame::AttributeChanged.
//
// This is needed because SMIL doesn't go through AfterSetAttr unfortunately.
void ProcessAttributeChange(int32_t aNamespaceID, nsAtom* aAttribute);
nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aAttribute,
const nsAttrValue* aValue, const nsAttrValue* aOldValue,
nsIPrincipal* aSubjectPrincipal, bool aNotify) final;
protected:
// Information from walking our ancestors and a given target.
enum class ScanResult {
// Nothing that should stop us from rendering the shadow tree.
Ok,
// We're never going to be displayed, so no point in updating the shadow
// tree.
//
// However if we're referenced from another tree that tree may need to be
// rendered.
Invisible,
// We're a cyclic reference to either an ancestor or another shadow tree. We
// shouldn't render this <use> element.
CyclicReference,
};
ScanResult ScanAncestors(const Element& aTarget) const;
/**
* Helper that provides a reference to the element with the ID that is
* referenced by the 'use' element's 'href' attribute, and that will update
* the 'use' element if the element that that ID identifies changes to a
* different element (or none).
*/
class ElementTracker final : public IDTracker {
public:
explicit ElementTracker(SVGUseElement* aOwningUseElement)
: mOwningUseElement(aOwningUseElement) {}
private:
void ElementChanged(Element* aFrom, Element* aTo) override {
IDTracker::ElementChanged(aFrom, aTo);
if (aFrom) {
aFrom->RemoveMutationObserver(mOwningUseElement);
}
mOwningUseElement->TriggerReclone();
}
SVGUseElement* mOwningUseElement;
};
nsSVGUseFrame* GetFrame() const;
virtual LengthAttributesInfo GetLengthInfo() override;
virtual StringAttributesInfo GetStringInfo() override;
/**
* Returns true if our width and height should be used, or false if they
* should be ignored. As per the spec, this depends on the type of the
* element that we're referencing.
*/
bool OurWidthAndHeightAreUsed() const;
void SyncWidthOrHeight(nsAtom* aName);
void LookupHref();
void TriggerReclone();
void UnlinkSource();
enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT };
SVGAnimatedLength mLengthAttributes[4];
static LengthInfo sLengthInfo[4];
enum { HREF, XLINK_HREF };
SVGAnimatedString mStringAttributes[2];
static StringInfo sStringInfo[2];
RefPtr<SVGUseElement> mOriginal; // if we've been cloned, our "real" copy
ElementTracker mReferencedElementTracker;
RefPtr<URLExtraData> mContentURLData; // URL data for its anonymous content
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_SVGUseElement_h