DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Implementation

Mercurial (b6d82b1a6b02)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
/* -*- 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 NSSVGEFFECTS_H_
#define NSSVGEFFECTS_H_

#include "mozilla/Attributes.h"
#include "mozilla/dom/IDTracker.h"
#include "FrameProperties.h"
#include "mozilla/dom/Element.h"
#include "nsID.h"
#include "nsIFrame.h"
#include "nsIMutationObserver.h"
#include "nsISupportsBase.h"
#include "nsISupportsImpl.h"
#include "nsIReferrerInfo.h"
#include "nsStringFwd.h"
#include "nsStubMutationObserver.h"
#include "nsSVGUtils.h"
#include "nsCycleCollectionParticipant.h"

class nsAtom;
class nsIURI;
class nsSVGClipPathFrame;
class nsSVGMarkerFrame;
class nsSVGPaintServerFrame;
class nsSVGFilterFrame;
class nsSVGMaskFrame;
namespace mozilla {
namespace dom {
class CanvasRenderingContext2D;
class SVGGeometryElement;
}  // namespace dom
}  // namespace mozilla

namespace mozilla {

/*
 * This class contains URL and referrer information (referrer and referrer
 * policy).
 * We use it to pass to svg system instead of nsIURI. The object brings referrer
 * and referrer policy so we can send correct Referer headers.
 */
class URLAndReferrerInfo {
 public:
  URLAndReferrerInfo(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo)
      : mURI(aURI), mReferrerInfo(aReferrerInfo) {
    MOZ_ASSERT(aURI);
  }

  URLAndReferrerInfo(nsIURI* aURI, const URLExtraData& aExtraData)
      : mURI(aURI), mReferrerInfo(aExtraData.ReferrerInfo()) {
    MOZ_ASSERT(aURI);
  }

  NS_INLINE_DECL_REFCOUNTING(URLAndReferrerInfo)

  nsIURI* GetURI() const { return mURI; }
  nsIReferrerInfo* GetReferrerInfo() const { return mReferrerInfo; }

  bool operator==(const URLAndReferrerInfo& aRHS) const;

 private:
  ~URLAndReferrerInfo() = default;

  nsCOMPtr<nsIURI> mURI;
  nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
};

/**
 * This interface allows us to be notified when a piece of SVG content is
 * re-rendered.
 *
 * Concrete implementations of this base class need to implement
 * GetReferencedElementWithoutObserving to specify the SVG element that
 * they'd like to monitor for rendering changes, and they need to implement
 * OnRenderingChange to specify how we'll react when that content gets
 * re-rendered.  They also need to implement a constructor and destructor,
 * which should call StartObserving and StopObserving, respectively.
 *
 * The referenced element is generally looked up and stored during
 * construction.  If the referenced element is in an extenal SVG resource
 * document, the lookup code will initiate loading of the external resource and
 * OnRenderingChange will be called once the element in the external resource
 * is available.
 *
 * Although the referenced element may be found and stored during construction,
 * observing for rendering changes does not start until requested.
 */
class SVGRenderingObserver : public nsStubMutationObserver {
 protected:
  virtual ~SVGRenderingObserver() = default;

 public:
  typedef mozilla::dom::Element Element;

  SVGRenderingObserver() : mInObserverSet(false) {}

  // nsIMutationObserver
  NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED

  /**
   * Called when non-DOM-mutation changes to the observed element should likely
   * cause the rendering of our observer to change.  This includes changes to
   * CSS computed values, but also changes to rendering observers that the
   * observed element itself may have (for example, when we're being used to
   * observe an SVG pattern, and an element in that pattern references and
   * observes a gradient that has changed).
   */
  void OnNonDOMMutationRenderingChange();

  // When a SVGRenderingObserver list gets forcibly cleared, it uses this
  // callback to notify every observer that's cleared from it, so they can
  // react.
  void NotifyEvictedFromRenderingObserverSet();

  nsIFrame* GetAndObserveReferencedFrame();
  /**
   * @param aOK this is only for the convenience of callers. We set *aOK to
   * false if the frame is the wrong type
   */
  nsIFrame* GetAndObserveReferencedFrame(mozilla::LayoutFrameType aFrameType,
                                         bool* aOK);

  Element* GetAndObserveReferencedElement();

  virtual bool ObservesReflow() { return false; }

 protected:
  void StartObserving();
  void StopObserving();

  /**
   * Called whenever the rendering of the observed element may have changed.
   *
   * More specifically, this method is called whenever DOM mutation occurs in
   * the observed element's subtree, or whenever
   * SVGObserverUtils::InvalidateRenderingObservers or
   * SVGObserverUtils::InvalidateDirectRenderingObservers is called for the
   * observed element's frame.
   *
   * Subclasses should override this method to handle rendering changes
   * appropriately.
   */
  virtual void OnRenderingChange() = 0;

  virtual Element* GetReferencedElementWithoutObserving() = 0;

#ifdef DEBUG
  void DebugObserverSet();
#endif

  // Whether we're in our observed element's observer set at this time.
  bool mInObserverSet;
};

class SVGObserverUtils {
 public:
  typedef mozilla::dom::CanvasRenderingContext2D CanvasRenderingContext2D;
  typedef mozilla::dom::Element Element;
  typedef dom::SVGGeometryElement SVGGeometryElement;
  using HrefToTemplateCallback = const std::function<void(nsAString&)>&;

  /**
   * Ensures that that if the given frame requires any resources that are in
   * SVG resource documents that the loading of those documents is initiated.
   * This does not make aFrame start to observe any elements that it
   * references.
   */
  static void InitiateResourceDocLoads(nsIFrame* aFrame);

  /**
   * Called when changes to an element (e.g. CSS property changes) cause its
   * frame to start/stop referencing (or reference different) SVG resource
   * elements. (_Not_ called for changes to referenced resource elements.)
   *
   * This function handles such changes by discarding _all_ the frame's SVG
   * effects frame properties (causing those properties to stop watching their
   * target element). It also synchronously (re)creates the filter and marker
   * frame properties (XXX why not the other properties?), which makes it
   * useful for initializing those properties during first reflow.
   *
   * XXX rename to something more meaningful like RefreshResourceReferences?
   */
  static void UpdateEffects(nsIFrame* aFrame);

  /**
   * @param aFrame must be a first-continuation.
   */
  static void AddRenderingObserver(Element* aElement,
                                   SVGRenderingObserver* aObserver);
  /**
   * @param aFrame must be a first-continuation.
   */
  static void RemoveRenderingObserver(Element* aElement,
                                      SVGRenderingObserver* aObserver);

  /**
   * Removes all rendering observers from aElement.
   */
  static void RemoveAllRenderingObservers(Element* aElement);

  /**
   * This can be called on any frame. We invalidate the observers of aFrame's
   * element, if any, or else walk up to the nearest observable SVG parent
   * frame with observers and invalidate them instead.
   *
   * Note that this method is very different to e.g.
   * nsNodeUtils::AttributeChanged which walks up the content node tree all the
   * way to the root node (not stopping if it encounters a non-container SVG
   * node) invalidating all mutation observers (not just
   * nsSVGRenderingObservers) on all nodes along the way (not just the first
   * node it finds with observers). In other words, by doing all the
   * things in parentheses in the preceding sentence, this method uses
   * knowledge about our implementation and what can be affected by SVG effects
   * to make invalidation relatively lightweight when an SVG effect changes.
   */
  static void InvalidateRenderingObservers(nsIFrame* aFrame);

  enum { INVALIDATE_REFLOW = 1 };

  enum ReferenceState {
    /// Has no references to SVG filters (may still have CSS filter functions!)
    eHasNoRefs,
    eHasRefsAllValid,
    eHasRefsSomeInvalid,
  };

  /**
   * This can be called on any element or frame. Only direct observers of this
   * (frame's) element, if any, are invalidated.
   */
  static void InvalidateDirectRenderingObservers(Element* aElement,
                                                 uint32_t aFlags = 0);
  static void InvalidateDirectRenderingObservers(nsIFrame* aFrame,
                                                 uint32_t aFlags = 0);

  /**
   * Get the paint server for aPaintedFrame.
   */
  static nsSVGPaintServerFrame* GetAndObservePaintServer(
      nsIFrame* aPaintedFrame, mozilla::StyleSVGPaint nsStyleSVG::*aPaint);

  /**
   * Get the start/mid/end-markers for the given frame, and add the frame as
   * an observer to those markers.  Returns true if at least one marker type is
   * found, false otherwise.
   */
  static bool GetAndObserveMarkers(nsIFrame* aMarkedFrame,
                                   nsSVGMarkerFrame* (*aFrames)[3]);

  /**
   * Get the frames of the SVG filters applied to the given frame, and add the
   * frame as an observer to those filter frames.
   *
   * NOTE! A return value of eHasNoRefs does NOT mean that there are no filters
   * to be applied, only that there are no references to SVG filter elements.
   *
   * XXX Callers other than ComputePostEffectsVisualOverflowRect and
   * nsSVGUtils::GetPostFilterVisualOverflowRect should not need to initiate
   * observing.  If we have a bug that causes invalidation (which would remove
   * observers) between reflow and painting, then we don't really want to
   * re-add abservers during painting.  That has the potential to hide logic
   * bugs, or cause later invalidation problems.  However, let's not change
   * that behavior just yet due to the regression potential.
   */
  static ReferenceState GetAndObserveFilters(
      nsIFrame* aFilteredFrame, nsTArray<nsSVGFilterFrame*>* aFilterFrames);

  /**
   * If the given frame is already observing SVG filters, this function gets
   * those filters.  If the frame is not already observing filters this
   * function assumes that it doesn't have anything to observe.
   */
  static ReferenceState GetFiltersIfObserving(
      nsIFrame* aFilteredFrame, nsTArray<nsSVGFilterFrame*>* aFilterFrames);

  /**
   * Starts observing filters for a <canvas> element's CanvasRenderingContext2D.
   *
   * Returns a RAII object that the caller should make sure is released once
   * the CanvasRenderingContext2D is no longer using them (that is, when the
   * CanvasRenderingContext2D "drawing style state" on which the filters were
   * set is destroyed or has its filter style reset).
   *
   * XXXjwatt: It's a bit unfortunate that both we and
   * CanvasRenderingContext2D::UpdateFilter process the list of StyleFilter
   * objects separately.  It would be better to refactor things so that we only
   * do that work once.
   */
  static already_AddRefed<nsISupports> ObserveFiltersForCanvasContext(
      CanvasRenderingContext2D* aContext, Element* aCanvasElement,
      Span<const StyleFilter> aFilters);

  /**
   * Called when cycle collecting CanvasRenderingContext2D, and requires the
   * RAII object returned from ObserveFiltersForCanvasContext to be passed in.
   *
   * XXXjwatt: I don't think this is doing anything useful.  All we do under
   * this function is clear a raw C-style (i.e. not strong) pointer.  That's
   * clearly not helping in breaking any cycles.  The fact that we MOZ_CRASH
   * in OnRenderingChange if that pointer is null indicates that this isn't
   * even doing anything useful in terms of preventing further invalidation
   * from any observed filters.
   */
  static void DetachFromCanvasContext(nsISupports* aAutoObserver);

  /**
   * Get the frame of the SVG clipPath applied to aClippedFrame, if any, and
   * set up aClippedFrame as a rendering observer of the clipPath's frame, to
   * be invalidated if it changes.
   *
   * Currently we only have support for 'clip-path' with a single item, but the
   * spec. now says 'clip-path' can be set to an arbitrary number of items.
   * Once we support that, aClipPathFrame will need to be an nsTArray as it
   * is for 'filter' and 'mask'.  Currently a return value of eHasNoRefs means
   * that there is no clipping at all, but once we support more than one item
   * then - as for filter and mask - we could still have basic shape clipping
   * to apply even if there are no references to SVG clipPath elements.
   *
   * Note that, unlike for filters, a reference to an ID that doesn't exist
   * is not invalid for clip-path or mask.  We will return eHasNoRefs in that
   * case.
   */
  static ReferenceState GetAndObserveClipPath(
      nsIFrame* aClippedFrame, nsSVGClipPathFrame** aClipPathFrame);

  /**
   * If masking is applied to aMaskedFrame, gets an array of any SVG masks
   * that are referenced, setting up aMaskFrames as a rendering observer of
   * those masks (if any).
   *
   * NOTE! A return value of eHasNoRefs does NOT mean that there are no masks
   * to be applied, only that there are no references to SVG mask elements.
   *
   * Note that, unlike for filters, a reference to an ID that doesn't exist
   * is not invalid for clip-path or mask.  We will return eHasNoRefs in that
   * case.
   */
  static ReferenceState GetAndObserveMasks(
      nsIFrame* aMaskedFrame, nsTArray<nsSVGMaskFrame*>* aMaskFrames);

  /**
   * Get the SVGGeometryElement that is referenced by aTextPathFrame, and make
   * aTextPathFrame start observing rendering changes to that element.
   */
  static SVGGeometryElement* GetAndObserveTextPathsPath(
      nsIFrame* aTextPathFrame);

  /**
   * Make aTextPathFrame stop observing rendering changes to the
   * SVGGeometryElement that it references, if any.
   */
  static void RemoveTextPathObserver(nsIFrame* aTextPathFrame);

  /**
   * Gets the nsIFrame of a referenced SVG "template" element, if any, and
   * makes aFrame start observing rendering changes to the template element.
   *
   * Template elements: some elements like gradients, pattern or filter can
   * reference another element of the same type using their 'href' attribute,
   * and use that element as a template that provides attributes or content
   * that is missing from the referring element.
   *
   * The frames that this function is called for do not have a common base
   * class, which is why it is necessary to pass in a function that can be
   * used as a callback to lazily get the href value, if necessary.
   */
  static nsIFrame* GetAndObserveTemplate(nsIFrame* aFrame,
                                         HrefToTemplateCallback aGetHref);

  static void RemoveTemplateObserver(nsIFrame* aFrame);

  /**
   * Gets an arbitrary element and starts observing it.  Used to implement
   * '-moz-element'.
   *
   * Note that bug 1496065 has been filed to remove support for referencing
   * arbitrary elements using '-moz-element'.
   */
  static Element* GetAndObserveBackgroundImage(nsIFrame* aFrame,
                                               const nsAtom* aHref);

  /**
   * Gets an arbitrary element and starts observing it.  Used to detect
   * invalidation changes for background-clip:text.
   */
  static Element* GetAndObserveBackgroundClip(nsIFrame* aFrame);

  /**
   * A helper function to resolve filter URL.
   */
  static already_AddRefed<URLAndReferrerInfo> GetFilterURI(
      nsIFrame* aFrame, const StyleFilter& aFilter);

  /**
   * Return a baseURL for resolving a local-ref URL.
   *
   * @param aContent an element which uses a local-ref property. Here are some
   *                 examples:
   *                   <rect fill=url(#foo)>
   *                   <circle clip-path=url(#foo)>
   *                   <use xlink:href="#foo">
   */
  static already_AddRefed<nsIURI> GetBaseURLForLocalRef(nsIContent* aContent,
                                                        nsIURI* aDocURI);
};

}  // namespace mozilla

#endif /*NSSVGEFFECTS_H_*/