tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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