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