michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "SVGDocumentWrapper.h" michael@0: michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsICategoryManager.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDocumentLoaderFactory.h" michael@0: #include "nsIDOMSVGLength.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIParser.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsIRequest.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsIXMLContentSink.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsSMILAnimationController.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "mozilla/dom/SVGSVGElement.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "mozilla/dom/SVGAnimatedLength.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "DOMSVGLength.h" michael@0: michael@0: // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK michael@0: #undef GetCurrentTime michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: NS_IMPL_ISUPPORTS(SVGDocumentWrapper, michael@0: nsIStreamListener, michael@0: nsIRequestObserver, michael@0: nsIObserver, michael@0: nsISupportsWeakReference) michael@0: michael@0: SVGDocumentWrapper::SVGDocumentWrapper() michael@0: : mIgnoreInvalidation(false), michael@0: mRegisteredForXPCOMShutdown(false) michael@0: { michael@0: } michael@0: michael@0: SVGDocumentWrapper::~SVGDocumentWrapper() michael@0: { michael@0: DestroyViewer(); michael@0: if (mRegisteredForXPCOMShutdown) { michael@0: UnregisterForXPCOMShutdown(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::DestroyViewer() michael@0: { michael@0: if (mViewer) { michael@0: mViewer->GetDocument()->OnPageHide(false, nullptr); michael@0: mViewer->Close(nullptr); michael@0: mViewer->Destroy(); michael@0: mViewer = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: SVGDocumentWrapper::GetWidthOrHeight(Dimension aDimension, michael@0: int32_t& aResult) michael@0: { michael@0: SVGSVGElement* rootElem = GetRootSVGElem(); michael@0: NS_ABORT_IF_FALSE(rootElem, "root elem missing or of wrong type"); michael@0: michael@0: // Get the width or height SVG object michael@0: nsRefPtr domAnimLength; michael@0: if (aDimension == eWidth) { michael@0: domAnimLength = rootElem->Width(); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(aDimension == eHeight, "invalid dimension"); michael@0: domAnimLength = rootElem->Height(); michael@0: } michael@0: NS_ENSURE_TRUE(domAnimLength, false); michael@0: michael@0: // Get the animated value from the object michael@0: nsRefPtr domLength = domAnimLength->AnimVal(); michael@0: NS_ENSURE_TRUE(domLength, false); michael@0: michael@0: // Check if it's a percent value (and fail if so) michael@0: uint16_t unitType; michael@0: nsresult rv = domLength->GetUnitType(&unitType); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: if (unitType == nsIDOMSVGLength::SVG_LENGTHTYPE_PERCENTAGE) { michael@0: return false; michael@0: } michael@0: michael@0: // Non-percent value - woot! Grab it & return it. michael@0: float floatLength; michael@0: rv = domLength->GetValue(&floatLength); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: aResult = nsSVGUtils::ClampToInt(floatLength); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsIFrame* michael@0: SVGDocumentWrapper::GetRootLayoutFrame() michael@0: { michael@0: Element* rootElem = GetRootSVGElem(); michael@0: return rootElem ? rootElem->GetPrimaryFrame() : nullptr; michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize) michael@0: { michael@0: NS_ABORT_IF_FALSE(!mIgnoreInvalidation, "shouldn't be reentrant"); michael@0: mIgnoreInvalidation = true; michael@0: michael@0: nsIntRect currentBounds; michael@0: mViewer->GetBounds(currentBounds); michael@0: michael@0: // If the bounds have changed, we need to do a layout flush. michael@0: if (currentBounds.Size() != aViewportSize) { michael@0: mViewer->SetBounds(nsIntRect(nsIntPoint(0, 0), aViewportSize)); michael@0: FlushLayout(); michael@0: } michael@0: michael@0: mIgnoreInvalidation = false; michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::FlushImageTransformInvalidation() michael@0: { michael@0: NS_ABORT_IF_FALSE(!mIgnoreInvalidation, "shouldn't be reentrant"); michael@0: michael@0: SVGSVGElement* svgElem = GetRootSVGElem(); michael@0: if (!svgElem) michael@0: return; michael@0: michael@0: mIgnoreInvalidation = true; michael@0: svgElem->FlushImageTransformInvalidation(); michael@0: FlushLayout(); michael@0: mIgnoreInvalidation = false; michael@0: } michael@0: michael@0: bool michael@0: SVGDocumentWrapper::IsAnimated() michael@0: { michael@0: nsIDocument* doc = mViewer->GetDocument(); michael@0: return doc && doc->HasAnimationController() && michael@0: doc->GetAnimationController()->HasRegisteredAnimations(); michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::StartAnimation() michael@0: { michael@0: // Can be called for animated images during shutdown, after we've michael@0: // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. michael@0: if (!mViewer) michael@0: return; michael@0: michael@0: nsIDocument* doc = mViewer->GetDocument(); michael@0: if (doc) { michael@0: nsSMILAnimationController* controller = doc->GetAnimationController(); michael@0: if (controller) { michael@0: controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE); michael@0: } michael@0: doc->SetImagesNeedAnimating(true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::StopAnimation() michael@0: { michael@0: // Can be called for animated images during shutdown, after we've michael@0: // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. michael@0: if (!mViewer) michael@0: return; michael@0: michael@0: nsIDocument* doc = mViewer->GetDocument(); michael@0: if (doc) { michael@0: nsSMILAnimationController* controller = doc->GetAnimationController(); michael@0: if (controller) { michael@0: controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE); michael@0: } michael@0: doc->SetImagesNeedAnimating(false); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::ResetAnimation() michael@0: { michael@0: SVGSVGElement* svgElem = GetRootSVGElem(); michael@0: if (!svgElem) michael@0: return; michael@0: michael@0: svgElem->SetCurrentTime(0.0f); michael@0: } michael@0: michael@0: float michael@0: SVGDocumentWrapper::GetCurrentTime() michael@0: { michael@0: SVGSVGElement* svgElem = GetRootSVGElem(); michael@0: return svgElem ? svgElem->GetCurrentTime() michael@0: : 0.0f; michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::SetCurrentTime(float aTime) michael@0: { michael@0: SVGSVGElement* svgElem = GetRootSVGElem(); michael@0: if (svgElem && svgElem->GetCurrentTime() != aTime) { michael@0: svgElem->SetCurrentTime(aTime); michael@0: } michael@0: } michael@0: michael@0: /** nsIStreamListener methods **/ michael@0: michael@0: /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, michael@0: in nsIInputStream inStr, in unsigned long sourceOffset, michael@0: in unsigned long count); */ michael@0: NS_IMETHODIMP michael@0: SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt, michael@0: nsIInputStream* inStr, michael@0: uint64_t sourceOffset, michael@0: uint32_t count) michael@0: { michael@0: return mListener->OnDataAvailable(aRequest, ctxt, inStr, michael@0: sourceOffset, count); michael@0: } michael@0: michael@0: /** nsIRequestObserver methods **/ michael@0: michael@0: /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ michael@0: NS_IMETHODIMP michael@0: SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) michael@0: { michael@0: nsresult rv = SetupViewer(aRequest, michael@0: getter_AddRefs(mViewer), michael@0: getter_AddRefs(mLoadGroup)); michael@0: michael@0: if (NS_SUCCEEDED(rv) && michael@0: NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) { michael@0: mViewer->GetDocument()->SetIsBeingUsedAsImage(); michael@0: StopAnimation(); // otherwise animations start automatically in helper doc michael@0: michael@0: rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mViewer->Open(nullptr, nullptr); michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, michael@0: in nsresult status); */ michael@0: NS_IMETHODIMP michael@0: SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt, michael@0: nsresult status) michael@0: { michael@0: if (mListener) { michael@0: mListener->OnStopRequest(aRequest, ctxt, status); michael@0: mListener = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** nsIObserver Methods **/ michael@0: NS_IMETHODIMP michael@0: SVGDocumentWrapper::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { michael@0: // Sever ties from rendering observers to helper-doc's root SVG node michael@0: SVGSVGElement* svgElem = GetRootSVGElem(); michael@0: if (svgElem) { michael@0: nsSVGEffects::RemoveAllRenderingObservers(svgElem); michael@0: } michael@0: michael@0: // Clean up at XPCOM shutdown time. michael@0: DestroyViewer(); michael@0: if (mListener) michael@0: mListener = nullptr; michael@0: if (mLoadGroup) michael@0: mLoadGroup = nullptr; michael@0: michael@0: // Turn off "registered" flag, or else we'll try to unregister when we die. michael@0: // (No need for that now, and the try would fail anyway -- it's too late.) michael@0: mRegisteredForXPCOMShutdown = false; michael@0: } else { michael@0: NS_ERROR("Unexpected observer topic."); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** Private helper methods **/ michael@0: michael@0: // This method is largely cribbed from michael@0: // nsExternalResourceMap::PendingLoad::SetupViewer. michael@0: nsresult michael@0: SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest, michael@0: nsIContentViewer** aViewer, michael@0: nsILoadGroup** aLoadGroup) michael@0: { michael@0: nsCOMPtr chan(do_QueryInterface(aRequest)); michael@0: NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); michael@0: michael@0: // Check for HTTP error page michael@0: nsCOMPtr httpChannel(do_QueryInterface(aRequest)); michael@0: if (httpChannel) { michael@0: bool requestSucceeded; michael@0: if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) || michael@0: !requestSucceeded) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // Give this document its own loadgroup michael@0: nsCOMPtr loadGroup; michael@0: chan->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: michael@0: nsCOMPtr newLoadGroup = michael@0: do_CreateInstance(NS_LOADGROUP_CONTRACTID); michael@0: NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); michael@0: newLoadGroup->SetLoadGroup(loadGroup); michael@0: michael@0: nsCOMPtr catMan = michael@0: do_GetService(NS_CATEGORYMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE); michael@0: nsXPIDLCString contractId; michael@0: nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML, michael@0: getter_Copies(contractId)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr docLoaderFactory = michael@0: do_GetService(contractId); michael@0: NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsCOMPtr viewer; michael@0: nsCOMPtr listener; michael@0: rv = docLoaderFactory->CreateInstance("external-resource", chan, michael@0: newLoadGroup, michael@0: IMAGE_SVG_XML, nullptr, nullptr, michael@0: getter_AddRefs(listener), michael@0: getter_AddRefs(viewer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCOMPtr parser = do_QueryInterface(listener); michael@0: NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED); michael@0: michael@0: // XML-only, because this is for SVG content michael@0: nsIContentSink* sink = parser->GetContentSink(); michael@0: nsCOMPtr xmlSink = do_QueryInterface(sink); michael@0: NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED); michael@0: michael@0: listener.swap(mListener); michael@0: viewer.forget(aViewer); michael@0: newLoadGroup.forget(aLoadGroup); michael@0: michael@0: RegisterForXPCOMShutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::RegisterForXPCOMShutdown() michael@0: { michael@0: NS_ABORT_IF_FALSE(!mRegisteredForXPCOMShutdown, michael@0: "re-registering for XPCOM shutdown"); michael@0: // Listen for xpcom-shutdown so that we can drop references to our michael@0: // helper-document at that point. (Otherwise, we won't get cleaned up michael@0: // until imgLoader::Shutdown, which can happen after the JAR service michael@0: // and RDF service have been unregistered.) michael@0: nsresult rv; michael@0: nsCOMPtr obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); michael@0: if (NS_FAILED(rv) || michael@0: NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, michael@0: true))) { michael@0: NS_WARNING("Failed to register as observer of XPCOM shutdown"); michael@0: } else { michael@0: mRegisteredForXPCOMShutdown = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::UnregisterForXPCOMShutdown() michael@0: { michael@0: NS_ABORT_IF_FALSE(mRegisteredForXPCOMShutdown, michael@0: "unregistering for XPCOM shutdown w/out being registered"); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); michael@0: if (NS_FAILED(rv) || michael@0: NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { michael@0: NS_WARNING("Failed to unregister as observer of XPCOM shutdown"); michael@0: } else { michael@0: mRegisteredForXPCOMShutdown = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: SVGDocumentWrapper::FlushLayout() michael@0: { michael@0: nsCOMPtr presShell; michael@0: mViewer->GetPresShell(getter_AddRefs(presShell)); michael@0: if (presShell) { michael@0: presShell->FlushPendingNotifications(Flush_Layout); michael@0: } michael@0: } michael@0: michael@0: nsIDocument* michael@0: SVGDocumentWrapper::GetDocument() michael@0: { michael@0: if (!mViewer) michael@0: return nullptr; michael@0: michael@0: return mViewer->GetDocument(); // May be nullptr. michael@0: } michael@0: michael@0: SVGSVGElement* michael@0: SVGDocumentWrapper::GetRootSVGElem() michael@0: { michael@0: if (!mViewer) michael@0: return nullptr; // Can happen during destruction michael@0: michael@0: nsIDocument* doc = mViewer->GetDocument(); michael@0: if (!doc) michael@0: return nullptr; // Can happen during destruction michael@0: michael@0: Element* rootElem = mViewer->GetDocument()->GetRootElement(); michael@0: if (!rootElem || !rootElem->IsSVG(nsGkAtoms::svg)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return static_cast(rootElem); michael@0: } michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla