Bug 1472338: part 1) Add Chrome tests for the async Clipboard API. r=NeilDeakin
[gecko.git] / image / SVGDocumentWrapper.cpp
bloba85176db49e18fc5803b72a878d78e6fb8b34add
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/PresShell.h"
9 #include "mozilla/SMILAnimationController.h"
10 #include "mozilla/SVGObserverUtils.h"
11 #include "mozilla/dom/Animation.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/DocumentTimeline.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/dom/ImageTracker.h"
16 #include "mozilla/dom/SVGDocument.h"
17 #include "mozilla/dom/SVGSVGElement.h"
18 #include "nsICategoryManager.h"
19 #include "nsIChannel.h"
20 #include "nsIContentViewer.h"
21 #include "nsIDocumentLoaderFactory.h"
22 #include "nsIHttpChannel.h"
23 #include "nsIObserverService.h"
24 #include "nsIParser.h"
25 #include "nsIRequest.h"
26 #include "nsIStreamListener.h"
27 #include "nsIXMLContentSink.h"
28 #include "nsNetCID.h"
29 #include "nsComponentManagerUtils.h"
30 #include "nsServiceManagerUtils.h"
31 #include "nsMimeTypes.h"
32 #include "nsRefreshDriver.h"
34 namespace mozilla {
36 using namespace dom;
37 using namespace gfx;
39 namespace image {
41 NS_IMPL_ISUPPORTS(SVGDocumentWrapper, nsIStreamListener, nsIRequestObserver,
42 nsIObserver, nsISupportsWeakReference)
44 SVGDocumentWrapper::SVGDocumentWrapper()
45 : mIgnoreInvalidation(false),
46 mRegisteredForXPCOMShutdown(false),
47 mIsDrawing(false) {}
49 SVGDocumentWrapper::~SVGDocumentWrapper() {
50 DestroyViewer();
51 if (mRegisteredForXPCOMShutdown) {
52 UnregisterForXPCOMShutdown();
56 void SVGDocumentWrapper::DestroyViewer() {
57 if (mViewer) {
58 mViewer->GetDocument()->OnPageHide(false, nullptr);
59 mViewer->Close(nullptr);
60 mViewer->Destroy();
61 mViewer = nullptr;
65 nsIFrame* SVGDocumentWrapper::GetRootLayoutFrame() {
66 Element* rootElem = GetRootSVGElem();
67 return rootElem ? rootElem->GetPrimaryFrame() : nullptr;
70 void SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize) {
71 MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
72 mIgnoreInvalidation = true;
74 nsIntRect currentBounds;
75 mViewer->GetBounds(currentBounds);
77 // If the bounds have changed, we need to do a layout flush.
78 if (currentBounds.Size() != aViewportSize) {
79 mViewer->SetBounds(IntRect(IntPoint(0, 0), aViewportSize));
80 FlushLayout();
83 mIgnoreInvalidation = false;
86 void SVGDocumentWrapper::FlushImageTransformInvalidation() {
87 MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant");
89 SVGSVGElement* svgElem = GetRootSVGElem();
90 if (!svgElem) {
91 return;
94 mIgnoreInvalidation = true;
95 svgElem->FlushImageTransformInvalidation();
96 FlushLayout();
97 mIgnoreInvalidation = false;
100 bool SVGDocumentWrapper::IsAnimated() {
101 // Can be called for animated images during shutdown, after we've
102 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
103 if (!mViewer) {
104 return false;
107 Document* doc = mViewer->GetDocument();
108 if (!doc) {
109 return false;
111 if (doc->Timeline()->HasAnimations()) {
112 // CSS animations (technically HasAnimations() also checks for CSS
113 // transitions and Web animations but since SVG-as-an-image doesn't run
114 // script they will never run in the document that we wrap).
115 return true;
117 if (doc->HasAnimationController() &&
118 doc->GetAnimationController()->HasRegisteredAnimations()) {
119 // SMIL animations
120 return true;
122 return false;
125 void SVGDocumentWrapper::StartAnimation() {
126 // Can be called for animated images during shutdown, after we've
127 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
128 if (!mViewer) {
129 return;
132 Document* doc = mViewer->GetDocument();
133 if (doc) {
134 SMILAnimationController* controller = doc->GetAnimationController();
135 if (controller) {
136 controller->Resume(SMILTimeContainer::PAUSE_IMAGE);
138 doc->ImageTracker()->SetAnimatingState(true);
142 void SVGDocumentWrapper::StopAnimation() {
143 // Can be called for animated images during shutdown, after we've
144 // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
145 if (!mViewer) {
146 return;
149 Document* doc = mViewer->GetDocument();
150 if (doc) {
151 SMILAnimationController* controller = doc->GetAnimationController();
152 if (controller) {
153 controller->Pause(SMILTimeContainer::PAUSE_IMAGE);
155 doc->ImageTracker()->SetAnimatingState(false);
159 void SVGDocumentWrapper::ResetAnimation() {
160 SVGSVGElement* svgElem = GetRootSVGElem();
161 if (!svgElem) {
162 return;
165 svgElem->SetCurrentTime(0.0f);
168 float SVGDocumentWrapper::GetCurrentTimeAsFloat() {
169 SVGSVGElement* svgElem = GetRootSVGElem();
170 return svgElem ? svgElem->GetCurrentTimeAsFloat() : 0.0f;
173 void SVGDocumentWrapper::SetCurrentTime(float aTime) {
174 SVGSVGElement* svgElem = GetRootSVGElem();
175 if (svgElem && svgElem->GetCurrentTimeAsFloat() != aTime) {
176 svgElem->SetCurrentTime(aTime);
180 void SVGDocumentWrapper::TickRefreshDriver() {
181 if (RefPtr<PresShell> presShell = mViewer->GetPresShell()) {
182 if (RefPtr<nsPresContext> presContext = presShell->GetPresContext()) {
183 presContext->RefreshDriver()->DoTick();
188 /** nsIStreamListener methods **/
190 NS_IMETHODIMP
191 SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* inStr,
192 uint64_t sourceOffset, uint32_t count) {
193 return mListener->OnDataAvailable(aRequest, inStr, sourceOffset, count);
196 /** nsIRequestObserver methods **/
198 NS_IMETHODIMP
199 SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest) {
200 nsresult rv = SetupViewer(aRequest, getter_AddRefs(mViewer),
201 getter_AddRefs(mLoadGroup));
203 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mListener->OnStartRequest(aRequest))) {
204 mViewer->GetDocument()->SetIsBeingUsedAsImage();
205 StopAnimation(); // otherwise animations start automatically in helper doc
207 rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0), nullptr);
208 if (NS_SUCCEEDED(rv)) {
209 rv = mViewer->Open(nullptr, nullptr);
212 return rv;
215 NS_IMETHODIMP
216 SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsresult status) {
217 if (mListener) {
218 mListener->OnStopRequest(aRequest, status);
219 mListener = nullptr;
222 return NS_OK;
225 /** nsIObserver Methods **/
226 NS_IMETHODIMP
227 SVGDocumentWrapper::Observe(nsISupports* aSubject, const char* aTopic,
228 const char16_t* aData) {
229 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
230 // Sever ties from rendering observers to helper-doc's root SVG node
231 SVGSVGElement* svgElem = GetRootSVGElem();
232 if (svgElem) {
233 SVGObserverUtils::RemoveAllRenderingObservers(svgElem);
236 // Clean up at XPCOM shutdown time.
237 DestroyViewer();
238 if (mListener) {
239 mListener = nullptr;
241 if (mLoadGroup) {
242 mLoadGroup = nullptr;
245 // Turn off "registered" flag, or else we'll try to unregister when we die.
246 // (No need for that now, and the try would fail anyway -- it's too late.)
247 mRegisteredForXPCOMShutdown = false;
248 } else {
249 NS_ERROR("Unexpected observer topic.");
251 return NS_OK;
254 /** Private helper methods **/
256 // This method is largely cribbed from
257 // nsExternalResourceMap::PendingLoad::SetupViewer.
258 nsresult SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest,
259 nsIContentViewer** aViewer,
260 nsILoadGroup** aLoadGroup) {
261 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
262 NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED);
264 // Check for HTTP error page
265 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
266 if (httpChannel) {
267 bool requestSucceeded;
268 if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) ||
269 !requestSucceeded) {
270 return NS_ERROR_FAILURE;
274 // Give this document its own loadgroup
275 nsCOMPtr<nsILoadGroup> loadGroup;
276 chan->GetLoadGroup(getter_AddRefs(loadGroup));
278 nsCOMPtr<nsILoadGroup> newLoadGroup =
279 do_CreateInstance(NS_LOADGROUP_CONTRACTID);
280 NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
281 newLoadGroup->SetLoadGroup(loadGroup);
283 nsCOMPtr<nsICategoryManager> catMan =
284 do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
285 NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
286 nsCString contractId;
287 nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML,
288 contractId);
289 NS_ENSURE_SUCCESS(rv, rv);
290 nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
291 do_GetService(contractId.get());
292 NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
294 nsCOMPtr<nsIContentViewer> viewer;
295 nsCOMPtr<nsIStreamListener> listener;
296 rv = docLoaderFactory->CreateInstance(
297 "external-resource", chan, newLoadGroup, nsLiteralCString(IMAGE_SVG_XML),
298 nullptr, nullptr, getter_AddRefs(listener), getter_AddRefs(viewer));
299 NS_ENSURE_SUCCESS(rv, rv);
301 NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED);
303 // Create a navigation time object and pass it to the SVG document through
304 // the viewer.
305 // The timeline(DocumentTimeline, used in CSS animation) of this SVG
306 // document needs this navigation timing object for time computation, such
307 // as to calculate current time stamp based on the start time of navigation
308 // time object.
310 // For a root document, DocShell would do these sort of things
311 // automatically. Since there is no DocShell for this wrapped SVG document,
312 // we must set it up manually.
313 RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
314 timing->NotifyNavigationStart(
315 nsDOMNavigationTiming::DocShellState::eInactive);
316 viewer->SetNavigationTiming(timing);
318 nsCOMPtr<nsIParser> parser = do_QueryInterface(listener);
319 NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
321 // XML-only, because this is for SVG content
322 nsCOMPtr<nsIContentSink> sink = parser->GetContentSink();
323 NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED);
325 listener.swap(mListener);
326 viewer.forget(aViewer);
327 newLoadGroup.forget(aLoadGroup);
329 RegisterForXPCOMShutdown();
330 return NS_OK;
333 void SVGDocumentWrapper::RegisterForXPCOMShutdown() {
334 MOZ_ASSERT(!mRegisteredForXPCOMShutdown, "re-registering for XPCOM shutdown");
335 // Listen for xpcom-shutdown so that we can drop references to our
336 // helper-document at that point. (Otherwise, we won't get cleaned up
337 // until imgLoader::Shutdown, which can happen after the JAR service
338 // and RDF service have been unregistered.)
339 nsresult rv;
340 nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
341 if (NS_FAILED(rv) || NS_FAILED(obsSvc->AddObserver(
342 this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true))) {
343 NS_WARNING("Failed to register as observer of XPCOM shutdown");
344 } else {
345 mRegisteredForXPCOMShutdown = true;
349 void SVGDocumentWrapper::UnregisterForXPCOMShutdown() {
350 MOZ_ASSERT(mRegisteredForXPCOMShutdown,
351 "unregistering for XPCOM shutdown w/out being registered");
353 nsresult rv;
354 nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv);
355 if (NS_FAILED(rv) ||
356 NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
357 NS_WARNING("Failed to unregister as observer of XPCOM shutdown");
358 } else {
359 mRegisteredForXPCOMShutdown = false;
363 void SVGDocumentWrapper::FlushLayout() {
364 if (SVGDocument* doc = GetDocument()) {
365 doc->FlushPendingNotifications(FlushType::Layout);
369 SVGDocument* SVGDocumentWrapper::GetDocument() {
370 if (!mViewer) {
371 return nullptr;
373 Document* doc = mViewer->GetDocument();
374 if (!doc) {
375 return nullptr;
377 return doc->AsSVGDocument();
380 SVGSVGElement* SVGDocumentWrapper::GetRootSVGElem() {
381 if (!mViewer) {
382 return nullptr; // Can happen during destruction
385 Document* doc = mViewer->GetDocument();
386 if (!doc) {
387 return nullptr; // Can happen during destruction
390 Element* rootElem = mViewer->GetDocument()->GetRootElement();
391 if (!rootElem || !rootElem->IsSVGElement(nsGkAtoms::svg)) {
392 return nullptr;
395 return static_cast<SVGSVGElement*>(rootElem);
398 } // namespace image
399 } // namespace mozilla