Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/image/SVGDocumentWrapper.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "SVGDocumentWrapper.h"
7
8
#include "mozilla/dom/DocumentTimeline.h"
9
#include "mozilla/dom/Element.h"
10
#include "mozilla/dom/SVGDocument.h"
11
#include "nsICategoryManager.h"
12
#include "nsIChannel.h"
13
#include "nsIContentViewer.h"
14
#include "nsIDocumentLoaderFactory.h"
15
#include "nsIHttpChannel.h"
16
#include "nsIObserverService.h"
17
#include "nsIParser.h"
18
#include "nsIPresShell.h"
19
#include "nsIRequest.h"
20
#include "nsIStreamListener.h"
21
#include "nsIXMLContentSink.h"
22
#include "nsNetCID.h"
23
#include "nsComponentManagerUtils.h"
24
#include "nsSMILAnimationController.h"
25
#include "nsServiceManagerUtils.h"
26
#include "mozilla/dom/SVGSVGElement.h"
27
#include "SVGObserverUtils.h"
28
#include "mozilla/dom/SVGAnimatedLength.h"
29
#include "nsMimeTypes.h"
30
#include "DOMSVGLength.h"
31
#include "nsDocument.h"
32
#include "mozilla/dom/ImageTracker.h"
33
34
// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
35
#undef GetCurrentTime
36
37
namespace mozilla {
38
39
using namespace dom;
40
using namespace gfx;
41
42
namespace image {
43
44
NS_IMPL_ISUPPORTS(SVGDocumentWrapper,
45
                  nsIStreamListener,
46
                  nsIRequestObserver,
47
                  nsIObserver,
48
                  nsISupportsWeakReference)
49
50
SVGDocumentWrapper::SVGDocumentWrapper()
51
  : mIgnoreInvalidation(false),
52
    mRegisteredForXPCOMShutdown(false)
53
0
{ }
54
55
SVGDocumentWrapper::~SVGDocumentWrapper()
56
0
{
57
0
  DestroyViewer();
58
0
  if (mRegisteredForXPCOMShutdown) {
59
0
    UnregisterForXPCOMShutdown();
60
0
  }
61
0
}
62
63
void
64
SVGDocumentWrapper::DestroyViewer()
65
0
{
66
0
  if (mViewer) {
67
0
    mViewer->GetDocument()->OnPageHide(false, nullptr);
68
0
    mViewer->Close(nullptr);
69
0
    mViewer->Destroy();
70
0
    mViewer = nullptr;
71
0
  }
72
0
}
73
74
nsIFrame*
75
SVGDocumentWrapper::GetRootLayoutFrame()
76
0
{
77
0
  Element* rootElem = GetRootSVGElem();
78
0
  return rootElem ? rootElem->GetPrimaryFrame() : nullptr;
79
0
}
80
81
void
82
SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize)
83
0
{
84
0
  MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
85
0
  mIgnoreInvalidation = true;
86
0
87
0
  nsIntRect currentBounds;
88
0
  mViewer->GetBounds(currentBounds);
89
0
90
0
  // If the bounds have changed, we need to do a layout flush.
91
0
  if (currentBounds.Size() != aViewportSize) {
92
0
    mViewer->SetBounds(IntRect(IntPoint(0, 0), aViewportSize));
93
0
    FlushLayout();
94
0
  }
95
0
96
0
  mIgnoreInvalidation = false;
97
0
}
98
99
void
100
SVGDocumentWrapper::FlushImageTransformInvalidation()
101
0
{
102
0
  MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
103
0
104
0
  SVGSVGElement* svgElem = GetRootSVGElem();
105
0
  if (!svgElem) {
106
0
    return;
107
0
  }
108
0
109
0
  mIgnoreInvalidation = true;
110
0
  svgElem->FlushImageTransformInvalidation();
111
0
  FlushLayout();
112
0
  mIgnoreInvalidation = false;
113
0
}
114
115
bool
116
SVGDocumentWrapper::IsAnimated()
117
0
{
118
0
  // Can be called for animated images during shutdown, after we've
119
0
  // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
120
0
  if (!mViewer) {
121
0
    return false;
122
0
  }
123
0
124
0
  nsIDocument* doc = mViewer->GetDocument();
125
0
  if (!doc) {
126
0
    return false;
127
0
  }
128
0
  if (doc->Timeline()->HasAnimations()) {
129
0
    // CSS animations (technically HasAnimations() also checks for CSS
130
0
    // transitions and Web animations but since SVG-as-an-image doesn't run
131
0
    // script they will never run in the document that we wrap).
132
0
    return true;
133
0
  }
134
0
  if (doc->HasAnimationController() &&
135
0
      doc->GetAnimationController()->HasRegisteredAnimations()) {
136
0
    // SMIL animations
137
0
    return true;
138
0
  }
139
0
  return false;
140
0
}
141
142
void
143
SVGDocumentWrapper::StartAnimation()
144
0
{
145
0
  // Can be called for animated images during shutdown, after we've
146
0
  // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
147
0
  if (!mViewer) {
148
0
    return;
149
0
  }
150
0
151
0
  nsIDocument* doc = mViewer->GetDocument();
152
0
  if (doc) {
153
0
    nsSMILAnimationController* controller = doc->GetAnimationController();
154
0
    if (controller) {
155
0
      controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
156
0
    }
157
0
    doc->ImageTracker()->SetAnimatingState(true);
158
0
  }
159
0
}
160
161
void
162
SVGDocumentWrapper::StopAnimation()
163
0
{
164
0
  // Can be called for animated images during shutdown, after we've
165
0
  // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
166
0
  if (!mViewer) {
167
0
    return;
168
0
  }
169
0
170
0
  nsIDocument* doc = mViewer->GetDocument();
171
0
  if (doc) {
172
0
    nsSMILAnimationController* controller = doc->GetAnimationController();
173
0
    if (controller) {
174
0
      controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE);
175
0
    }
176
0
    doc->ImageTracker()->SetAnimatingState(false);
177
0
  }
178
0
}
179
180
void
181
SVGDocumentWrapper::ResetAnimation()
182
0
{
183
0
  SVGSVGElement* svgElem = GetRootSVGElem();
184
0
  if (!svgElem) {
185
0
    return;
186
0
  }
187
0
188
0
  svgElem->SetCurrentTime(0.0f);
189
0
}
190
191
float
192
SVGDocumentWrapper::GetCurrentTime()
193
0
{
194
0
  SVGSVGElement* svgElem = GetRootSVGElem();
195
0
  return svgElem ? svgElem->GetCurrentTime()
196
0
                 : 0.0f;
197
0
}
198
199
void
200
SVGDocumentWrapper::SetCurrentTime(float aTime)
201
0
{
202
0
  SVGSVGElement* svgElem = GetRootSVGElem();
203
0
  if (svgElem && svgElem->GetCurrentTime() != aTime) {
204
0
    svgElem->SetCurrentTime(aTime);
205
0
  }
206
0
}
207
208
void
209
SVGDocumentWrapper::TickRefreshDriver()
210
0
{
211
0
  nsCOMPtr<nsIPresShell> presShell;
212
0
  mViewer->GetPresShell(getter_AddRefs(presShell));
213
0
  if (presShell) {
214
0
    nsPresContext* presContext = presShell->GetPresContext();
215
0
    if (presContext) {
216
0
      presContext->RefreshDriver()->DoTick();
217
0
    }
218
0
  }
219
0
}
220
221
/** nsIStreamListener methods **/
222
223
NS_IMETHODIMP
224
SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt,
225
                                    nsIInputStream* inStr,
226
                                    uint64_t sourceOffset,
227
                                    uint32_t count)
228
0
{
229
0
  return mListener->OnDataAvailable(aRequest, ctxt, inStr,
230
0
                                    sourceOffset, count);
231
0
}
232
233
/** nsIRequestObserver methods **/
234
235
NS_IMETHODIMP
236
SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
237
0
{
238
0
  nsresult rv = SetupViewer(aRequest,
239
0
                            getter_AddRefs(mViewer),
240
0
                            getter_AddRefs(mLoadGroup));
241
0
242
0
  if (NS_SUCCEEDED(rv) &&
243
0
      NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) {
244
0
    mViewer->GetDocument()->SetIsBeingUsedAsImage();
245
0
    StopAnimation(); // otherwise animations start automatically in helper doc
246
0
247
0
    rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0));
248
0
    if (NS_SUCCEEDED(rv)) {
249
0
      rv = mViewer->Open(nullptr, nullptr);
250
0
    }
251
0
  }
252
0
  return rv;
253
0
}
254
255
256
NS_IMETHODIMP
257
SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt,
258
                                  nsresult status)
259
0
{
260
0
  if (mListener) {
261
0
    mListener->OnStopRequest(aRequest, ctxt, status);
262
0
    mListener = nullptr;
263
0
  }
264
0
265
0
  return NS_OK;
266
0
}
267
268
/** nsIObserver Methods **/
269
NS_IMETHODIMP
270
SVGDocumentWrapper::Observe(nsISupports* aSubject,
271
                            const char* aTopic,
272
                            const char16_t* aData)
273
0
{
274
0
  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
275
0
    // Sever ties from rendering observers to helper-doc's root SVG node
276
0
    SVGSVGElement* svgElem = GetRootSVGElem();
277
0
    if (svgElem) {
278
0
      SVGObserverUtils::RemoveAllRenderingObservers(svgElem);
279
0
    }
280
0
281
0
    // Clean up at XPCOM shutdown time.
282
0
    DestroyViewer();
283
0
    if (mListener) {
284
0
      mListener = nullptr;
285
0
    }
286
0
    if (mLoadGroup) {
287
0
      mLoadGroup = nullptr;
288
0
    }
289
0
290
0
    // Turn off "registered" flag, or else we'll try to unregister when we die.
291
0
    // (No need for that now, and the try would fail anyway -- it's too late.)
292
0
    mRegisteredForXPCOMShutdown = false;
293
0
  } else {
294
0
    NS_ERROR("Unexpected observer topic.");
295
0
  }
296
0
  return NS_OK;
297
0
}
298
299
/** Private helper methods **/
300
301
// This method is largely cribbed from
302
// nsExternalResourceMap::PendingLoad::SetupViewer.
303
nsresult
304
SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest,
305
                                nsIContentViewer** aViewer,
306
                                nsILoadGroup** aLoadGroup)
307
0
{
308
0
  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
309
0
  NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
310
0
311
0
  // Check for HTTP error page
312
0
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
313
0
  if (httpChannel) {
314
0
    bool requestSucceeded;
315
0
    if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
316
0
        !requestSucceeded) {
317
0
      return NS_ERROR_FAILURE;
318
0
    }
319
0
  }
320
0
321
0
  // Give this document its own loadgroup
322
0
  nsCOMPtr<nsILoadGroup> loadGroup;
323
0
  chan->GetLoadGroup(getter_AddRefs(loadGroup));
324
0
325
0
  nsCOMPtr<nsILoadGroup> newLoadGroup =
326
0
        do_CreateInstance(NS_LOADGROUP_CONTRACTID);
327
0
  NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
328
0
  newLoadGroup->SetLoadGroup(loadGroup);
329
0
330
0
  nsCOMPtr<nsICategoryManager> catMan =
331
0
    do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
332
0
  NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
333
0
  nsCString contractId;
334
0
  nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML,
335
0
                                         contractId);
336
0
  NS_ENSURE_SUCCESS(rv, rv);
337
0
  nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
338
0
    do_GetService(contractId.get());
339
0
  NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
340
0
341
0
  nsCOMPtr<nsIContentViewer> viewer;
342
0
  nsCOMPtr<nsIStreamListener> listener;
343
0
  rv = docLoaderFactory->CreateInstance("external-resource", chan,
344
0
                                        newLoadGroup,
345
0
                                        NS_LITERAL_CSTRING(IMAGE_SVG_XML),
346
0
                                        nullptr, nullptr,
347
0
                                        getter_AddRefs(listener),
348
0
                                        getter_AddRefs(viewer));
349
0
  NS_ENSURE_SUCCESS(rv, rv);
350
0
351
0
  NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
352
0
353
0
  // Create a navigation time object and pass it to the SVG document through
354
0
  // the viewer.
355
0
  // The timeline(DocumentTimeline, used in CSS animation) of this SVG
356
0
  // document needs this navigation timing object for time computation, such
357
0
  // as to calculate current time stamp based on the start time of navigation
358
0
  // time object.
359
0
  //
360
0
  // For a root document, DocShell would do these sort of things
361
0
  // automatically. Since there is no DocShell for this wrapped SVG document,
362
0
  // we must set it up manually.
363
0
  RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
364
0
  timing->NotifyNavigationStart(nsDOMNavigationTiming::DocShellState::eInactive);
365
0
  viewer->SetNavigationTiming(timing);
366
0
367
0
  nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
368
0
  NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
369
0
370
0
  // XML-only, because this is for SVG content
371
0
  nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
372
0
  NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED);
373
0
374
0
  listener.swap(mListener);
375
0
  viewer.forget(aViewer);
376
0
  newLoadGroup.forget(aLoadGroup);
377
0
378
0
  RegisterForXPCOMShutdown();
379
0
  return NS_OK;
380
0
}
381
382
void
383
SVGDocumentWrapper::RegisterForXPCOMShutdown()
384
0
{
385
0
  MOZ_ASSERT(!mRegisteredForXPCOMShutdown,
386
0
             "re-registering for XPCOM shutdown");
387
0
  // Listen for xpcom-shutdown so that we can drop references to our
388
0
  // helper-document at that point. (Otherwise, we won't get cleaned up
389
0
  // until imgLoader::Shutdown, which can happen after the JAR service
390
0
  // and RDF service have been unregistered.)
391
0
  nsresult rv;
392
0
  nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
393
0
  if (NS_FAILED(rv) ||
394
0
      NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
395
0
                                    true))) {
396
0
    NS_WARNING("Failed to register as observer of XPCOM shutdown");
397
0
  } else {
398
0
    mRegisteredForXPCOMShutdown = true;
399
0
  }
400
0
}
401
402
void
403
SVGDocumentWrapper::UnregisterForXPCOMShutdown()
404
0
{
405
0
  MOZ_ASSERT(mRegisteredForXPCOMShutdown,
406
0
             "unregistering for XPCOM shutdown w/out being registered");
407
0
408
0
  nsresult rv;
409
0
  nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
410
0
  if (NS_FAILED(rv) ||
411
0
      NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
412
0
    NS_WARNING("Failed to unregister as observer of XPCOM shutdown");
413
0
  } else {
414
0
    mRegisteredForXPCOMShutdown = false;
415
0
  }
416
0
}
417
418
void
419
SVGDocumentWrapper::FlushLayout()
420
0
{
421
0
  if (SVGDocument* doc = GetDocument()) {
422
0
    doc->FlushPendingNotifications(FlushType::Layout);
423
0
  }
424
0
}
425
426
SVGDocument*
427
SVGDocumentWrapper::GetDocument()
428
0
{
429
0
  if (!mViewer) {
430
0
    return nullptr;
431
0
  }
432
0
  nsIDocument* doc = mViewer->GetDocument();
433
0
  if (!doc) {
434
0
    return nullptr;
435
0
  }
436
0
  return doc->AsSVGDocument();
437
0
}
438
439
SVGSVGElement*
440
SVGDocumentWrapper::GetRootSVGElem()
441
0
{
442
0
  if (!mViewer) {
443
0
    return nullptr; // Can happen during destruction
444
0
  }
445
0
446
0
  nsIDocument* doc = mViewer->GetDocument();
447
0
  if (!doc) {
448
0
    return nullptr; // Can happen during destruction
449
0
  }
450
0
451
0
  Element* rootElem = mViewer->GetDocument()->GetRootElement();
452
0
  if (!rootElem || !rootElem->IsSVGElement(nsGkAtoms::svg)) {
453
0
    return nullptr;
454
0
  }
455
0
456
0
  return static_cast<SVGSVGElement*>(rootElem);
457
0
}
458
459
} // namespace image
460
} // namespace mozilla