Bug 1490079 [wpt PR 12936] - Mark tools/wpt Windows test failures as xfail, a=testonly
[gecko.git] / image / SVGDocumentWrapper.cpp
blob76b4e7a4c007eabb1ba57b49f59b5adc3d470e60
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/. */
6 #include "SVGDocumentWrapper.h"
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"
34 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
35 #undef GetCurrentTime
37 namespace mozilla {
39 using namespace dom;
40 using namespace gfx;
42 namespace image {
44 NS_IMPL_ISUPPORTS(SVGDocumentWrapper,
45 nsIStreamListener,
46 nsIRequestObserver,
47 nsIObserver,
48 nsISupportsWeakReference)
50 SVGDocumentWrapper::SVGDocumentWrapper()
51 : mIgnoreInvalidation(false),
52 mRegisteredForXPCOMShutdown(false)
53 { }
55 SVGDocumentWrapper::~SVGDocumentWrapper()
57 DestroyViewer();
58 if (mRegisteredForXPCOMShutdown) {
59 UnregisterForXPCOMShutdown();
63 void
64 SVGDocumentWrapper::DestroyViewer()
66 if (mViewer) {
67 mViewer->GetDocument()->OnPageHide(false, nullptr);
68 mViewer->Close(nullptr);
69 mViewer->Destroy();
70 mViewer = nullptr;
74 nsIFrame*
75 SVGDocumentWrapper::GetRootLayoutFrame()
77 Element* rootElem = GetRootSVGElem();
78 return rootElem ? rootElem->GetPrimaryFrame() : nullptr;
81 void
82 SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize)
84 MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
85 mIgnoreInvalidation = true;
87 nsIntRect currentBounds;
88 mViewer->GetBounds(currentBounds);
90 // If the bounds have changed, we need to do a layout flush.
91 if (currentBounds.Size() != aViewportSize) {
92 mViewer->SetBounds(IntRect(IntPoint(0, 0), aViewportSize));
93 FlushLayout();
96 mIgnoreInvalidation = false;
99 void
100 SVGDocumentWrapper::FlushImageTransformInvalidation()
102 MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
104 SVGSVGElement* svgElem = GetRootSVGElem();
105 if (!svgElem) {
106 return;
109 mIgnoreInvalidation = true;
110 svgElem->FlushImageTransformInvalidation();
111 FlushLayout();
112 mIgnoreInvalidation = false;
115 bool
116 SVGDocumentWrapper::IsAnimated()
118 // Can be called for animated images during shutdown, after we've
119 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
120 if (!mViewer) {
121 return false;
124 nsIDocument* doc = mViewer->GetDocument();
125 if (!doc) {
126 return false;
128 if (doc->Timeline()->HasAnimations()) {
129 // CSS animations (technically HasAnimations() also checks for CSS
130 // transitions and Web animations but since SVG-as-an-image doesn't run
131 // script they will never run in the document that we wrap).
132 return true;
134 if (doc->HasAnimationController() &&
135 doc->GetAnimationController()->HasRegisteredAnimations()) {
136 // SMIL animations
137 return true;
139 return false;
142 void
143 SVGDocumentWrapper::StartAnimation()
145 // Can be called for animated images during shutdown, after we've
146 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
147 if (!mViewer) {
148 return;
151 nsIDocument* doc = mViewer->GetDocument();
152 if (doc) {
153 nsSMILAnimationController* controller = doc->GetAnimationController();
154 if (controller) {
155 controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE);
157 doc->ImageTracker()->SetAnimatingState(true);
161 void
162 SVGDocumentWrapper::StopAnimation()
164 // Can be called for animated images during shutdown, after we've
165 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
166 if (!mViewer) {
167 return;
170 nsIDocument* doc = mViewer->GetDocument();
171 if (doc) {
172 nsSMILAnimationController* controller = doc->GetAnimationController();
173 if (controller) {
174 controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE);
176 doc->ImageTracker()->SetAnimatingState(false);
180 void
181 SVGDocumentWrapper::ResetAnimation()
183 SVGSVGElement* svgElem = GetRootSVGElem();
184 if (!svgElem) {
185 return;
188 svgElem->SetCurrentTime(0.0f);
191 float
192 SVGDocumentWrapper::GetCurrentTime()
194 SVGSVGElement* svgElem = GetRootSVGElem();
195 return svgElem ? svgElem->GetCurrentTime()
196 : 0.0f;
199 void
200 SVGDocumentWrapper::SetCurrentTime(float aTime)
202 SVGSVGElement* svgElem = GetRootSVGElem();
203 if (svgElem && svgElem->GetCurrentTime() != aTime) {
204 svgElem->SetCurrentTime(aTime);
208 void
209 SVGDocumentWrapper::TickRefreshDriver()
211 nsCOMPtr<nsIPresShell> presShell;
212 mViewer->GetPresShell(getter_AddRefs(presShell));
213 if (presShell) {
214 nsPresContext* presContext = presShell->GetPresContext();
215 if (presContext) {
216 presContext->RefreshDriver()->DoTick();
221 /** nsIStreamListener methods **/
223 NS_IMETHODIMP
224 SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt,
225 nsIInputStream* inStr,
226 uint64_t sourceOffset,
227 uint32_t count)
229 return mListener->OnDataAvailable(aRequest, ctxt, inStr,
230 sourceOffset, count);
233 /** nsIRequestObserver methods **/
235 NS_IMETHODIMP
236 SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
238 nsresult rv = SetupViewer(aRequest,
239 getter_AddRefs(mViewer),
240 getter_AddRefs(mLoadGroup));
242 if (NS_SUCCEEDED(rv) &&
243 NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) {
244 mViewer->GetDocument()->SetIsBeingUsedAsImage();
245 StopAnimation(); // otherwise animations start automatically in helper doc
247 rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0));
248 if (NS_SUCCEEDED(rv)) {
249 rv = mViewer->Open(nullptr, nullptr);
252 return rv;
256 NS_IMETHODIMP
257 SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt,
258 nsresult status)
260 if (mListener) {
261 mListener->OnStopRequest(aRequest, ctxt, status);
262 mListener = nullptr;
265 return NS_OK;
268 /** nsIObserver Methods **/
269 NS_IMETHODIMP
270 SVGDocumentWrapper::Observe(nsISupports* aSubject,
271 const char* aTopic,
272 const char16_t* aData)
274 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
275 // Sever ties from rendering observers to helper-doc's root SVG node
276 SVGSVGElement* svgElem = GetRootSVGElem();
277 if (svgElem) {
278 SVGObserverUtils::RemoveAllRenderingObservers(svgElem);
281 // Clean up at XPCOM shutdown time.
282 DestroyViewer();
283 if (mListener) {
284 mListener = nullptr;
286 if (mLoadGroup) {
287 mLoadGroup = nullptr;
290 // Turn off "registered" flag, or else we'll try to unregister when we die.
291 // (No need for that now, and the try would fail anyway -- it's too late.)
292 mRegisteredForXPCOMShutdown = false;
293 } else {
294 NS_ERROR("Unexpected observer topic.");
296 return NS_OK;
299 /** Private helper methods **/
301 // This method is largely cribbed from
302 // nsExternalResourceMap::PendingLoad::SetupViewer.
303 nsresult
304 SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest,
305 nsIContentViewer** aViewer,
306 nsILoadGroup** aLoadGroup)
308 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
309 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
311 // Check for HTTP error page
312 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
313 if (httpChannel) {
314 bool requestSucceeded;
315 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
316 !requestSucceeded) {
317 return NS_ERROR_FAILURE;
321 // Give this document its own loadgroup
322 nsCOMPtr<nsILoadGroup> loadGroup;
323 chan->GetLoadGroup(getter_AddRefs(loadGroup));
325 nsCOMPtr<nsILoadGroup> newLoadGroup =
326 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
327 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
328 newLoadGroup->SetLoadGroup(loadGroup);
330 nsCOMPtr<nsICategoryManager> catMan =
331 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
332 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
333 nsCString contractId;
334 nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML,
335 contractId);
336 NS_ENSURE_SUCCESS(rv, rv);
337 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
338 do_GetService(contractId.get());
339 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
341 nsCOMPtr<nsIContentViewer> viewer;
342 nsCOMPtr<nsIStreamListener> listener;
343 rv = docLoaderFactory->CreateInstance("external-resource", chan,
344 newLoadGroup,
345 NS_LITERAL_CSTRING(IMAGE_SVG_XML),
346 nullptr, nullptr,
347 getter_AddRefs(listener),
348 getter_AddRefs(viewer));
349 NS_ENSURE_SUCCESS(rv, rv);
351 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
353 // Create a navigation time object and pass it to the SVG document through
354 // the viewer.
355 // The timeline(DocumentTimeline, used in CSS animation) of this SVG
356 // document needs this navigation timing object for time computation, such
357 // as to calculate current time stamp based on the start time of navigation
358 // time object.
360 // For a root document, DocShell would do these sort of things
361 // automatically. Since there is no DocShell for this wrapped SVG document,
362 // we must set it up manually.
363 RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
364 timing->NotifyNavigationStart(nsDOMNavigationTiming::DocShellState::eInactive);
365 viewer->SetNavigationTiming(timing);
367 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
368 NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
370 // XML-only, because this is for SVG content
371 nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
372 NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED);
374 listener.swap(mListener);
375 viewer.forget(aViewer);
376 newLoadGroup.forget(aLoadGroup);
378 RegisterForXPCOMShutdown();
379 return NS_OK;
382 void
383 SVGDocumentWrapper::RegisterForXPCOMShutdown()
385 MOZ_ASSERT(!mRegisteredForXPCOMShutdown,
386 "re-registering for XPCOM shutdown");
387 // Listen for xpcom-shutdown so that we can drop references to our
388 // helper-document at that point. (Otherwise, we won't get cleaned up
389 // until imgLoader::Shutdown, which can happen after the JAR service
390 // and RDF service have been unregistered.)
391 nsresult rv;
392 nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
393 if (NS_FAILED(rv) ||
394 NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
395 true))) {
396 NS_WARNING("Failed to register as observer of XPCOM shutdown");
397 } else {
398 mRegisteredForXPCOMShutdown = true;
402 void
403 SVGDocumentWrapper::UnregisterForXPCOMShutdown()
405 MOZ_ASSERT(mRegisteredForXPCOMShutdown,
406 "unregistering for XPCOM shutdown w/out being registered");
408 nsresult rv;
409 nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
410 if (NS_FAILED(rv) ||
411 NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
412 NS_WARNING("Failed to unregister as observer of XPCOM shutdown");
413 } else {
414 mRegisteredForXPCOMShutdown = false;
418 void
419 SVGDocumentWrapper::FlushLayout()
421 if (SVGDocument* doc = GetDocument()) {
422 doc->FlushPendingNotifications(FlushType::Layout);
426 SVGDocument*
427 SVGDocumentWrapper::GetDocument()
429 if (!mViewer) {
430 return nullptr;
432 nsIDocument* doc = mViewer->GetDocument();
433 if (!doc) {
434 return nullptr;
436 return doc->AsSVGDocument();
439 SVGSVGElement*
440 SVGDocumentWrapper::GetRootSVGElem()
442 if (!mViewer) {
443 return nullptr; // Can happen during destruction
446 nsIDocument* doc = mViewer->GetDocument();
447 if (!doc) {
448 return nullptr; // Can happen during destruction
451 Element* rootElem = mViewer->GetDocument()->GetRootElement();
452 if (!rootElem || !rootElem->IsSVGElement(nsGkAtoms::svg)) {
453 return nullptr;
456 return static_cast<SVGSVGElement*>(rootElem);
459 } // namespace image
460 } // namespace mozilla