diff options
Diffstat (limited to 'image/SVGDocumentWrapper.cpp')
-rw-r--r-- | image/SVGDocumentWrapper.cpp | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/image/SVGDocumentWrapper.cpp b/image/SVGDocumentWrapper.cpp new file mode 100644 index 000000000..d4b71b907 --- /dev/null +++ b/image/SVGDocumentWrapper.cpp @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SVGDocumentWrapper.h" + +#include "mozilla/dom/DocumentTimeline.h" +#include "mozilla/dom/Element.h" +#include "nsICategoryManager.h" +#include "nsIChannel.h" +#include "nsIContentViewer.h" +#include "nsIDocument.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIDOMSVGLength.h" +#include "nsIHttpChannel.h" +#include "nsIObserverService.h" +#include "nsIParser.h" +#include "nsIPresShell.h" +#include "nsIRequest.h" +#include "nsIStreamListener.h" +#include "nsIXMLContentSink.h" +#include "nsNetCID.h" +#include "nsComponentManagerUtils.h" +#include "nsSMILAnimationController.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/dom/SVGSVGElement.h" +#include "nsSVGEffects.h" +#include "mozilla/dom/SVGAnimatedLength.h" +#include "nsMimeTypes.h" +#include "DOMSVGLength.h" +#include "nsDocument.h" +#include "mozilla/dom/ImageTracker.h" + +// undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK +#undef GetCurrentTime + +namespace mozilla { + +using namespace dom; +using namespace gfx; + +namespace image { + +NS_IMPL_ISUPPORTS(SVGDocumentWrapper, + nsIStreamListener, + nsIRequestObserver, + nsIObserver, + nsISupportsWeakReference) + +SVGDocumentWrapper::SVGDocumentWrapper() + : mIgnoreInvalidation(false), + mRegisteredForXPCOMShutdown(false) +{ } + +SVGDocumentWrapper::~SVGDocumentWrapper() +{ + DestroyViewer(); + if (mRegisteredForXPCOMShutdown) { + UnregisterForXPCOMShutdown(); + } +} + +void +SVGDocumentWrapper::DestroyViewer() +{ + if (mViewer) { + mViewer->GetDocument()->OnPageHide(false, nullptr); + mViewer->Close(nullptr); + mViewer->Destroy(); + mViewer = nullptr; + } +} + +nsIFrame* +SVGDocumentWrapper::GetRootLayoutFrame() +{ + Element* rootElem = GetRootSVGElem(); + return rootElem ? rootElem->GetPrimaryFrame() : nullptr; +} + +void +SVGDocumentWrapper::UpdateViewportBounds(const nsIntSize& aViewportSize) +{ + MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant"); + mIgnoreInvalidation = true; + + nsIntRect currentBounds; + mViewer->GetBounds(currentBounds); + + // If the bounds have changed, we need to do a layout flush. + if (currentBounds.Size() != aViewportSize) { + mViewer->SetBounds(IntRect(IntPoint(0, 0), aViewportSize)); + FlushLayout(); + } + + mIgnoreInvalidation = false; +} + +void +SVGDocumentWrapper::FlushImageTransformInvalidation() +{ + MOZ_ASSERT(!mIgnoreInvalidation, "shouldn't be reentrant"); + + SVGSVGElement* svgElem = GetRootSVGElem(); + if (!svgElem) { + return; + } + + mIgnoreInvalidation = true; + svgElem->FlushImageTransformInvalidation(); + FlushLayout(); + mIgnoreInvalidation = false; +} + +bool +SVGDocumentWrapper::IsAnimated() +{ + // Can be called for animated images during shutdown, after we've + // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. + if (!mViewer) { + return false; + } + + nsIDocument* doc = mViewer->GetDocument(); + if (!doc) { + return false; + } + if (doc->Timeline()->HasAnimations()) { + // CSS animations (technically HasAnimations() also checks for CSS + // transitions and Web animations but since SVG-as-an-image doesn't run + // script they will never run in the document that we wrap). + return true; + } + if (doc->HasAnimationController() && + doc->GetAnimationController()->HasRegisteredAnimations()) { + // SMIL animations + return true; + } + return false; +} + +void +SVGDocumentWrapper::StartAnimation() +{ + // Can be called for animated images during shutdown, after we've + // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. + if (!mViewer) { + return; + } + + nsIDocument* doc = mViewer->GetDocument(); + if (doc) { + nsSMILAnimationController* controller = doc->GetAnimationController(); + if (controller) { + controller->Resume(nsSMILTimeContainer::PAUSE_IMAGE); + } + doc->ImageTracker()->SetAnimatingState(true); + } +} + +void +SVGDocumentWrapper::StopAnimation() +{ + // Can be called for animated images during shutdown, after we've + // already Observe()'d XPCOM shutdown and cleared out our mViewer pointer. + if (!mViewer) { + return; + } + + nsIDocument* doc = mViewer->GetDocument(); + if (doc) { + nsSMILAnimationController* controller = doc->GetAnimationController(); + if (controller) { + controller->Pause(nsSMILTimeContainer::PAUSE_IMAGE); + } + doc->ImageTracker()->SetAnimatingState(false); + } +} + +void +SVGDocumentWrapper::ResetAnimation() +{ + SVGSVGElement* svgElem = GetRootSVGElem(); + if (!svgElem) { + return; + } + + svgElem->SetCurrentTime(0.0f); +} + +float +SVGDocumentWrapper::GetCurrentTime() +{ + SVGSVGElement* svgElem = GetRootSVGElem(); + return svgElem ? svgElem->GetCurrentTime() + : 0.0f; +} + +void +SVGDocumentWrapper::SetCurrentTime(float aTime) +{ + SVGSVGElement* svgElem = GetRootSVGElem(); + if (svgElem && svgElem->GetCurrentTime() != aTime) { + svgElem->SetCurrentTime(aTime); + } +} + +void +SVGDocumentWrapper::TickRefreshDriver() +{ + nsCOMPtr<nsIPresShell> presShell; + mViewer->GetPresShell(getter_AddRefs(presShell)); + if (presShell) { + nsPresContext* presContext = presShell->GetPresContext(); + if (presContext) { + presContext->RefreshDriver()->DoTick(); + } + } +} + +/** nsIStreamListener methods **/ + +NS_IMETHODIMP +SVGDocumentWrapper::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt, + nsIInputStream* inStr, + uint64_t sourceOffset, + uint32_t count) +{ + return mListener->OnDataAvailable(aRequest, ctxt, inStr, + sourceOffset, count); +} + +/** nsIRequestObserver methods **/ + +NS_IMETHODIMP +SVGDocumentWrapper::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt) +{ + nsresult rv = SetupViewer(aRequest, + getter_AddRefs(mViewer), + getter_AddRefs(mLoadGroup)); + + if (NS_SUCCEEDED(rv) && + NS_SUCCEEDED(mListener->OnStartRequest(aRequest, nullptr))) { + mViewer->GetDocument()->SetIsBeingUsedAsImage(); + StopAnimation(); // otherwise animations start automatically in helper doc + + rv = mViewer->Init(nullptr, nsIntRect(0, 0, 0, 0)); + if (NS_SUCCEEDED(rv)) { + rv = mViewer->Open(nullptr, nullptr); + } + } + return rv; +} + + +NS_IMETHODIMP +SVGDocumentWrapper::OnStopRequest(nsIRequest* aRequest, nsISupports* ctxt, + nsresult status) +{ + if (mListener) { + mListener->OnStopRequest(aRequest, ctxt, status); + mListener = nullptr; + } + + return NS_OK; +} + +/** nsIObserver Methods **/ +NS_IMETHODIMP +SVGDocumentWrapper::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + // Sever ties from rendering observers to helper-doc's root SVG node + SVGSVGElement* svgElem = GetRootSVGElem(); + if (svgElem) { + nsSVGEffects::RemoveAllRenderingObservers(svgElem); + } + + // Clean up at XPCOM shutdown time. + DestroyViewer(); + if (mListener) { + mListener = nullptr; + } + if (mLoadGroup) { + mLoadGroup = nullptr; + } + + // Turn off "registered" flag, or else we'll try to unregister when we die. + // (No need for that now, and the try would fail anyway -- it's too late.) + mRegisteredForXPCOMShutdown = false; + } else { + NS_ERROR("Unexpected observer topic."); + } + return NS_OK; +} + +/** Private helper methods **/ + +// This method is largely cribbed from +// nsExternalResourceMap::PendingLoad::SetupViewer. +nsresult +SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest, + nsIContentViewer** aViewer, + nsILoadGroup** aLoadGroup) +{ + nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); + NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); + + // Check for HTTP error page + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest)); + if (httpChannel) { + bool requestSucceeded; + if (NS_FAILED(httpChannel->GetRequestSucceeded(&requestSucceeded)) || + !requestSucceeded) { + return NS_ERROR_FAILURE; + } + } + + // Give this document its own loadgroup + nsCOMPtr<nsILoadGroup> loadGroup; + chan->GetLoadGroup(getter_AddRefs(loadGroup)); + + nsCOMPtr<nsILoadGroup> newLoadGroup = + do_CreateInstance(NS_LOADGROUP_CONTRACTID); + NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY); + newLoadGroup->SetLoadGroup(loadGroup); + + nsCOMPtr<nsICategoryManager> catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE); + nsXPIDLCString contractId; + nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML, + getter_Copies(contractId)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + do_GetService(contractId); + NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr<nsIContentViewer> viewer; + nsCOMPtr<nsIStreamListener> listener; + rv = docLoaderFactory->CreateInstance("external-resource", chan, + newLoadGroup, + NS_LITERAL_CSTRING(IMAGE_SVG_XML), + nullptr, nullptr, + getter_AddRefs(listener), + getter_AddRefs(viewer)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(viewer, NS_ERROR_UNEXPECTED); + + // Create a navigation time object and pass it to the SVG document through + // the viewer. + // The timeline(DocumentTimeline, used in CSS animation) of this SVG + // document needs this navigation timing object for time computation, such + // as to calculate current time stamp based on the start time of navigation + // time object. + // + // For a root document, DocShell would do these sort of things + // automatically. Since there is no DocShell for this wrapped SVG document, + // we must set it up manually. + RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(); + timing->NotifyNavigationStart(nsDOMNavigationTiming::DocShellState::eInactive); + viewer->SetNavigationTiming(timing); + + nsCOMPtr<nsIParser> parser = do_QueryInterface(listener); + NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED); + + // XML-only, because this is for SVG content + nsCOMPtr<nsIContentSink> sink = parser->GetContentSink(); + NS_ENSURE_TRUE(sink, NS_ERROR_UNEXPECTED); + + listener.swap(mListener); + viewer.forget(aViewer); + newLoadGroup.forget(aLoadGroup); + + RegisterForXPCOMShutdown(); + return NS_OK; +} + +void +SVGDocumentWrapper::RegisterForXPCOMShutdown() +{ + MOZ_ASSERT(!mRegisteredForXPCOMShutdown, + "re-registering for XPCOM shutdown"); + // Listen for xpcom-shutdown so that we can drop references to our + // helper-document at that point. (Otherwise, we won't get cleaned up + // until imgLoader::Shutdown, which can happen after the JAR service + // and RDF service have been unregistered.) + nsresult rv; + nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); + if (NS_FAILED(rv) || + NS_FAILED(obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + true))) { + NS_WARNING("Failed to register as observer of XPCOM shutdown"); + } else { + mRegisteredForXPCOMShutdown = true; + } +} + +void +SVGDocumentWrapper::UnregisterForXPCOMShutdown() +{ + MOZ_ASSERT(mRegisteredForXPCOMShutdown, + "unregistering for XPCOM shutdown w/out being registered"); + + nsresult rv; + nsCOMPtr<nsIObserverService> obsSvc = do_GetService(OBSERVER_SVC_CID, &rv); + if (NS_FAILED(rv) || + NS_FAILED(obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) { + NS_WARNING("Failed to unregister as observer of XPCOM shutdown"); + } else { + mRegisteredForXPCOMShutdown = false; + } +} + +void +SVGDocumentWrapper::FlushLayout() +{ + nsCOMPtr<nsIPresShell> presShell; + mViewer->GetPresShell(getter_AddRefs(presShell)); + if (presShell) { + presShell->FlushPendingNotifications(Flush_Layout); + } +} + +nsIDocument* +SVGDocumentWrapper::GetDocument() +{ + if (!mViewer) { + return nullptr; + } + + return mViewer->GetDocument(); // May be nullptr. +} + +SVGSVGElement* +SVGDocumentWrapper::GetRootSVGElem() +{ + if (!mViewer) { + return nullptr; // Can happen during destruction + } + + nsIDocument* doc = mViewer->GetDocument(); + if (!doc) { + return nullptr; // Can happen during destruction + } + + Element* rootElem = mViewer->GetDocument()->GetRootElement(); + if (!rootElem || !rootElem->IsSVGElement(nsGkAtoms::svg)) { + return nullptr; + } + + return static_cast<SVGSVGElement*>(rootElem); +} + +} // namespace image +} // namespace mozilla |