SVGDocumentWrapper.cpp 13 KB

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