|
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/Element.h" |
|
9 #include "nsICategoryManager.h" |
|
10 #include "nsIChannel.h" |
|
11 #include "nsIContentViewer.h" |
|
12 #include "nsIDocument.h" |
|
13 #include "nsIDocumentLoaderFactory.h" |
|
14 #include "nsIDOMSVGLength.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 "nsSVGEffects.h" |
|
28 #include "mozilla/dom/SVGAnimatedLength.h" |
|
29 #include "nsMimeTypes.h" |
|
30 #include "DOMSVGLength.h" |
|
31 |
|
32 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK |
|
33 #undef GetCurrentTime |
|
34 |
|
35 using namespace mozilla::dom; |
|
36 |
|
37 namespace mozilla { |
|
38 namespace image { |
|
39 |
|
40 NS_IMPL_ISUPPORTS(SVGDocumentWrapper, |
|
41 nsIStreamListener, |
|
42 nsIRequestObserver, |
|
43 nsIObserver, |
|
44 nsISupportsWeakReference) |
|
45 |
|
46 SVGDocumentWrapper::SVGDocumentWrapper() |
|
47 : mIgnoreInvalidation(false), |
|
48 mRegisteredForXPCOMShutdown(false) |
|
49 { |
|
50 } |
|
51 |
|
52 SVGDocumentWrapper::~SVGDocumentWrapper() |
|
53 { |
|
54 DestroyViewer(); |
|
55 if (mRegisteredForXPCOMShutdown) { |
|
56 UnregisterForXPCOMShutdown(); |
|
57 } |
|
58 } |
|
59 |
|
60 void |
|
61 SVGDocumentWrapper::DestroyViewer() |
|
62 { |
|
63 if (mViewer) { |
|
64 mViewer->GetDocument()->OnPageHide(false, nullptr); |
|
65 mViewer->Close(nullptr); |
|
66 mViewer->Destroy(); |
|
67 mViewer = nullptr; |
|
68 } |
|
69 } |
|
70 |
|
71 bool |
|
72 SVGDocumentWrapper::GetWidthOrHeight(Dimension aDimension, |
|
73 int32_t& aResult) |
|
74 { |
|
75 SVGSVGElement* rootElem = GetRootSVGElem(); |
|
76 NS_ABORT_IF_FALSE(rootElem, "root elem missing or of wrong type"); |
|
77 |
|
78 // Get the width or height SVG object |
|
79 nsRefPtr<SVGAnimatedLength> domAnimLength; |
|
80 if (aDimension == eWidth) { |
|
81 domAnimLength = rootElem->Width(); |
|
82 } else { |
|
83 NS_ABORT_IF_FALSE(aDimension == eHeight, "invalid dimension"); |
|
84 domAnimLength = rootElem->Height(); |
|
85 } |
|
86 NS_ENSURE_TRUE(domAnimLength, false); |
|
87 |
|
88 // Get the animated value from the object |
|
89 nsRefPtr<DOMSVGLength> domLength = domAnimLength->AnimVal(); |
|
90 NS_ENSURE_TRUE(domLength, false); |
|
91 |
|
92 // Check if it's a percent value (and fail if so) |
|
93 uint16_t unitType; |
|
94 nsresult rv = domLength->GetUnitType(&unitType); |
|
95 NS_ENSURE_SUCCESS(rv, false); |
|
96 if (unitType == nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) { |
|
97 return false; |
|
98 } |
|
99 |
|
100 // Non-percent value - woot! Grab it & return it. |
|
101 float floatLength; |
|
102 rv = domLength->GetValue(&floatLength); |
|
103 NS_ENSURE_SUCCESS(rv, false); |
|
104 |
|
105 aResult = nsSVGUtils::ClampToInt(floatLength); |
|
106 |
|
107 return true; |
|
108 } |
|
109 |
|
110 nsIFrame* |
|
111 SVGDocumentWrapper::GetRootLayoutFrame() |
|
112 { |
|
113 Element* rootElem = GetRootSVGElem(); |
|
114 return rootElem ? rootElem->GetPrimaryFrame() : nullptr; |
|
115 } |
|
116 |
|
117 void |
|
118 SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize) |
|
119 { |
|
120 NS_ABORT_IF_FALSE(!mIgnoreInvalidation, "shouldn't be reentrant"); |
|
121 mIgnoreInvalidation = true; |
|
122 |
|
123 nsIntRect currentBounds; |
|
124 mViewer->GetBounds(currentBounds); |
|
125 |
|
126 // If the bounds have changed, we need to do a layout flush. |
|
127 if (currentBounds.Size() != aViewportSize) { |
|
128 mViewer->SetBounds(nsIntRect(nsIntPoint(0, 0), aViewportSize)); |
|
129 FlushLayout(); |
|
130 } |
|
131 |
|
132 mIgnoreInvalidation = false; |
|
133 } |
|
134 |
|
135 void |
|
136 SVGDocumentWrapper::FlushImageTransformInvalidation() |
|
137 { |
|
138 NS_ABORT_IF_FALSE(!mIgnoreInvalidation, "shouldn't be reentrant"); |
|
139 |
|
140 SVGSVGElement* svgElem = GetRootSVGElem(); |
|
141 if (!svgElem) |
|
142 return; |
|
143 |
|
144 mIgnoreInvalidation = true; |
|
145 svgElem->FlushImageTransformInvalidation(); |
|
146 FlushLayout(); |
|
147 mIgnoreInvalidation = false; |
|
148 } |
|
149 |
|
150 bool |
|
151 SVGDocumentWrapper::IsAnimated() |
|
152 { |
|
153 nsIDocument* doc = mViewer->GetDocument(); |
|
154 return doc && doc->HasAnimationController() && |
|
155 doc->GetAnimationController()->HasRegisteredAnimations(); |
|
156 } |
|
157 |
|
158 void |
|
159 SVGDocumentWrapper::StartAnimation() |
|
160 { |
|
161 // Can be called for animated images during shutdown, after we've |
|
162 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. |
|
163 if (!mViewer) |
|
164 return; |
|
165 |
|
166 nsIDocument* doc = mViewer->GetDocument(); |
|
167 if (doc) { |
|
168 nsSMILAnimationController* controller = doc->GetAnimationController(); |
|
169 if (controller) { |
|
170 controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE); |
|
171 } |
|
172 doc->SetImagesNeedAnimating(true); |
|
173 } |
|
174 } |
|
175 |
|
176 void |
|
177 SVGDocumentWrapper::StopAnimation() |
|
178 { |
|
179 // Can be called for animated images during shutdown, after we've |
|
180 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. |
|
181 if (!mViewer) |
|
182 return; |
|
183 |
|
184 nsIDocument* doc = mViewer->GetDocument(); |
|
185 if (doc) { |
|
186 nsSMILAnimationController* controller = doc->GetAnimationController(); |
|
187 if (controller) { |
|
188 controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE); |
|
189 } |
|
190 doc->SetImagesNeedAnimating(false); |
|
191 } |
|
192 } |
|
193 |
|
194 void |
|
195 SVGDocumentWrapper::ResetAnimation() |
|
196 { |
|
197 SVGSVGElement* svgElem = GetRootSVGElem(); |
|
198 if (!svgElem) |
|
199 return; |
|
200 |
|
201 svgElem->SetCurrentTime(0.0f); |
|
202 } |
|
203 |
|
204 float |
|
205 SVGDocumentWrapper::GetCurrentTime() |
|
206 { |
|
207 SVGSVGElement* svgElem = GetRootSVGElem(); |
|
208 return svgElem ? svgElem->GetCurrentTime() |
|
209 : 0.0f; |
|
210 } |
|
211 |
|
212 void |
|
213 SVGDocumentWrapper::SetCurrentTime(float aTime) |
|
214 { |
|
215 SVGSVGElement* svgElem = GetRootSVGElem(); |
|
216 if (svgElem && svgElem->GetCurrentTime() != aTime) { |
|
217 svgElem->SetCurrentTime(aTime); |
|
218 } |
|
219 } |
|
220 |
|
221 /** nsIStreamListener methods **/ |
|
222 |
|
223 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, |
|
224 in nsIInputStream inStr, in unsigned long sourceOffset, |
|
225 in unsigned long count); */ |
|
226 NS_IMETHODIMP |
|
227 SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt, |
|
228 nsIInputStream* inStr, |
|
229 uint64_t sourceOffset, |
|
230 uint32_t count) |
|
231 { |
|
232 return mListener->OnDataAvailable(aRequest, ctxt, inStr, |
|
233 sourceOffset, count); |
|
234 } |
|
235 |
|
236 /** nsIRequestObserver methods **/ |
|
237 |
|
238 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ |
|
239 NS_IMETHODIMP |
|
240 SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) |
|
241 { |
|
242 nsresult rv = SetupViewer(aRequest, |
|
243 getter_AddRefs(mViewer), |
|
244 getter_AddRefs(mLoadGroup)); |
|
245 |
|
246 if (NS_SUCCEEDED(rv) && |
|
247 NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) { |
|
248 mViewer->GetDocument()->SetIsBeingUsedAsImage(); |
|
249 StopAnimation(); // otherwise animations start automatically in helper doc |
|
250 |
|
251 rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0)); |
|
252 if (NS_SUCCEEDED(rv)) { |
|
253 rv = mViewer->Open(nullptr, nullptr); |
|
254 } |
|
255 } |
|
256 return rv; |
|
257 } |
|
258 |
|
259 |
|
260 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, |
|
261 in nsresult status); */ |
|
262 NS_IMETHODIMP |
|
263 SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt, |
|
264 nsresult status) |
|
265 { |
|
266 if (mListener) { |
|
267 mListener->OnStopRequest(aRequest, ctxt, status); |
|
268 mListener = nullptr; |
|
269 } |
|
270 |
|
271 return NS_OK; |
|
272 } |
|
273 |
|
274 /** nsIObserver Methods **/ |
|
275 NS_IMETHODIMP |
|
276 SVGDocumentWrapper::Observe(nsISupports* aSubject, |
|
277 const char* aTopic, |
|
278 const char16_t *aData) |
|
279 { |
|
280 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { |
|
281 // Sever ties from rendering observers to helper-doc's root SVG node |
|
282 SVGSVGElement* svgElem = GetRootSVGElem(); |
|
283 if (svgElem) { |
|
284 nsSVGEffects::RemoveAllRenderingObservers(svgElem); |
|
285 } |
|
286 |
|
287 // Clean up at XPCOM shutdown time. |
|
288 DestroyViewer(); |
|
289 if (mListener) |
|
290 mListener = nullptr; |
|
291 if (mLoadGroup) |
|
292 mLoadGroup = nullptr; |
|
293 |
|
294 // Turn off "registered" flag, or else we'll try to unregister when we die. |
|
295 // (No need for that now, and the try would fail anyway -- it's too late.) |
|
296 mRegisteredForXPCOMShutdown = false; |
|
297 } else { |
|
298 NS_ERROR("Unexpected observer topic."); |
|
299 } |
|
300 return NS_OK; |
|
301 } |
|
302 |
|
303 /** Private helper methods **/ |
|
304 |
|
305 // This method is largely cribbed from |
|
306 // nsExternalResourceMap::PendingLoad::SetupViewer. |
|
307 nsresult |
|
308 SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest, |
|
309 nsIContentViewer** aViewer, |
|
310 nsILoadGroup** aLoadGroup) |
|
311 { |
|
312 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); |
|
313 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); |
|
314 |
|
315 // Check for HTTP error page |
|
316 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest)); |
|
317 if (httpChannel) { |
|
318 bool requestSucceeded; |
|
319 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) || |
|
320 !requestSucceeded) { |
|
321 return NS_ERROR_FAILURE; |
|
322 } |
|
323 } |
|
324 |
|
325 // Give this document its own loadgroup |
|
326 nsCOMPtr<nsILoadGroup> loadGroup; |
|
327 chan->GetLoadGroup(getter_AddRefs(loadGroup)); |
|
328 |
|
329 nsCOMPtr<nsILoadGroup> newLoadGroup = |
|
330 do_CreateInstance(NS_LOADGROUP_CONTRACTID); |
|
331 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); |
|
332 newLoadGroup->SetLoadGroup(loadGroup); |
|
333 |
|
334 nsCOMPtr<nsICategoryManager> catMan = |
|
335 do_GetService(NS_CATEGORYMANAGER_CONTRACTID); |
|
336 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE); |
|
337 nsXPIDLCString contractId; |
|
338 nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML, |
|
339 getter_Copies(contractId)); |
|
340 NS_ENSURE_SUCCESS(rv, rv); |
|
341 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = |
|
342 do_GetService(contractId); |
|
343 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); |
|
344 |
|
345 nsCOMPtr<nsIContentViewer> viewer; |
|
346 nsCOMPtr<nsIStreamListener> listener; |
|
347 rv = docLoaderFactory->CreateInstance("external-resource", chan, |
|
348 newLoadGroup, |
|
349 IMAGE_SVG_XML, nullptr, nullptr, |
|
350 getter_AddRefs(listener), |
|
351 getter_AddRefs(viewer)); |
|
352 NS_ENSURE_SUCCESS(rv, rv); |
|
353 |
|
354 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED); |
|
355 |
|
356 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener); |
|
357 NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED); |
|
358 |
|
359 // XML-only, because this is for SVG content |
|
360 nsIContentSink* sink = parser->GetContentSink(); |
|
361 nsCOMPtr<nsIXMLContentSink> xmlSink = do_QueryInterface(sink); |
|
362 NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED); |
|
363 |
|
364 listener.swap(mListener); |
|
365 viewer.forget(aViewer); |
|
366 newLoadGroup.forget(aLoadGroup); |
|
367 |
|
368 RegisterForXPCOMShutdown(); |
|
369 return NS_OK; |
|
370 } |
|
371 |
|
372 void |
|
373 SVGDocumentWrapper::RegisterForXPCOMShutdown() |
|
374 { |
|
375 NS_ABORT_IF_FALSE(!mRegisteredForXPCOMShutdown, |
|
376 "re-registering for XPCOM shutdown"); |
|
377 // Listen for xpcom-shutdown so that we can drop references to our |
|
378 // helper-document at that point. (Otherwise, we won't get cleaned up |
|
379 // until imgLoader::Shutdown, which can happen after the JAR service |
|
380 // and RDF service have been unregistered.) |
|
381 nsresult rv; |
|
382 nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); |
|
383 if (NS_FAILED(rv) || |
|
384 NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, |
|
385 true))) { |
|
386 NS_WARNING("Failed to register as observer of XPCOM shutdown"); |
|
387 } else { |
|
388 mRegisteredForXPCOMShutdown = true; |
|
389 } |
|
390 } |
|
391 |
|
392 void |
|
393 SVGDocumentWrapper::UnregisterForXPCOMShutdown() |
|
394 { |
|
395 NS_ABORT_IF_FALSE(mRegisteredForXPCOMShutdown, |
|
396 "unregistering for XPCOM shutdown w/out being registered"); |
|
397 |
|
398 nsresult rv; |
|
399 nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); |
|
400 if (NS_FAILED(rv) || |
|
401 NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { |
|
402 NS_WARNING("Failed to unregister as observer of XPCOM shutdown"); |
|
403 } else { |
|
404 mRegisteredForXPCOMShutdown = false; |
|
405 } |
|
406 } |
|
407 |
|
408 void |
|
409 SVGDocumentWrapper::FlushLayout() |
|
410 { |
|
411 nsCOMPtr<nsIPresShell> presShell; |
|
412 mViewer->GetPresShell(getter_AddRefs(presShell)); |
|
413 if (presShell) { |
|
414 presShell->FlushPendingNotifications(Flush_Layout); |
|
415 } |
|
416 } |
|
417 |
|
418 nsIDocument* |
|
419 SVGDocumentWrapper::GetDocument() |
|
420 { |
|
421 if (!mViewer) |
|
422 return nullptr; |
|
423 |
|
424 return mViewer->GetDocument(); // May be nullptr. |
|
425 } |
|
426 |
|
427 SVGSVGElement* |
|
428 SVGDocumentWrapper::GetRootSVGElem() |
|
429 { |
|
430 if (!mViewer) |
|
431 return nullptr; // Can happen during destruction |
|
432 |
|
433 nsIDocument* doc = mViewer->GetDocument(); |
|
434 if (!doc) |
|
435 return nullptr; // Can happen during destruction |
|
436 |
|
437 Element* rootElem = mViewer->GetDocument()->GetRootElement(); |
|
438 if (!rootElem || !rootElem->IsSVG(nsGkAtoms::svg)) { |
|
439 return nullptr; |
|
440 } |
|
441 |
|
442 return static_cast<SVGSVGElement*>(rootElem); |
|
443 } |
|
444 |
|
445 } // namespace image |
|
446 } // namespace mozilla |