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