/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 |