summaryrefslogtreecommitdiffstats
path: root/layout/svg/SVGObserverUtils.h
blob: 17bd012078ea323213507db45d2808c0080c02e9 (plain)
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
418
419
420
/* -*- 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 LAYOUT_SVG_SVGOBSERVERUTILS_H_
#define LAYOUT_SVG_SVGOBSERVERUTILS_H_

#include "mozilla/Attributes.h"
#include "mozilla/dom/IDTracker.h"
#include "FrameProperties.h"
#include "nsID.h"
#include "nsIFrame.h"  // only for LayoutFrameType
#include "nsIMutationObserver.h"
#include "nsISupports.h"
#include "nsISupportsImpl.h"
#include "nsIReferrerInfo.h"
#include "nsStringFwd.h"
#include "nsStubMutationObserver.h"
#include "nsStyleStruct.h"
#include "nsCycleCollectionParticipant.h"

class nsAtom;
class nsIFrame;
class nsIURI;

namespace mozilla {
class SVGClipPathFrame;
class SVGFilterFrame;
class SVGMarkerFrame;
class SVGMaskFrame;
class SVGPaintServerFrame;

namespace dom {
class CanvasRenderingContext2D;
class Element;
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:
  using Element = dom::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:
  using CanvasRenderingContext2D = dom::CanvasRenderingContext2D;
  using Element = dom::Element;
  using SVGGeometryElement = dom::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.
   * MutationObservers::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
   * SVGRenderingObservers) 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 SVGPaintServerFrame* 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,
                                   SVGMarkerFrame* (*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 ComputePostEffectsInkOverflowRect and
   * SVGUtils::GetPostFilterInkOverflowRect 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<SVGFilterFrame*>* 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<SVGFilterFrame*>* 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, SVGClipPathFrame** 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<SVGMaskFrame*>* 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  // LAYOUT_SVG_SVGOBSERVERUTILS_H_