summaryrefslogtreecommitdiffstats
path: root/image/MultipartImage.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--image/MultipartImage.cpp363
1 files changed, 363 insertions, 0 deletions
diff --git a/image/MultipartImage.cpp b/image/MultipartImage.cpp
new file mode 100644
index 0000000000..5f0eeff11d
--- /dev/null
+++ b/image/MultipartImage.cpp
@@ -0,0 +1,363 @@
+/* -*- 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 "MultipartImage.h"
+
+#include "imgINotificationObserver.h"
+
+namespace mozilla {
+
+using gfx::IntSize;
+using gfx::SourceSurface;
+
+namespace image {
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers
+///////////////////////////////////////////////////////////////////////////////
+
+class NextPartObserver : public IProgressObserver {
+ public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(NextPartObserver)
+ NS_INLINE_DECL_REFCOUNTING(NextPartObserver, override)
+
+ explicit NextPartObserver(MultipartImage* aOwner) : mOwner(aOwner) {
+ MOZ_ASSERT(mOwner);
+ }
+
+ void BeginObserving(Image* aImage) {
+ MOZ_ASSERT(aImage);
+ mImage = aImage;
+
+ RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+ tracker->AddObserver(this);
+ }
+
+ void BlockUntilDecodedAndFinishObserving() {
+ // Use RequestDecodeForSize() to block until our image finishes decoding.
+ // The size is ignored because we don't pass the FLAG_HIGH_QUALITY_SCALING
+ // flag.
+ mImage->RequestDecodeForSize(gfx::IntSize(0, 0),
+ imgIContainer::FLAG_SYNC_DECODE);
+
+ if (mImage && mImage->GetType() == imgIContainer::TYPE_VECTOR) {
+ // We don't want to make a pending part the current part until it has had
+ // it's load event because when we transition from the current part to the
+ // next part we remove the multipart image as an observer of the current
+ // part and the progress tracker will send a fake load event with the
+ // lastpart bit set whenever an observer is removed without having the
+ // load complete progress. That fake load event with the lastpart bit set
+ // will get out of the multipart image and to the imgRequestProxy and
+ // further, which will make it look like we got the last part of the
+ // multipart image and confuse things. If we get here then we are still
+ // waiting to make mNextPart the current part, but we've gotten
+ // OnDataAvailable for the part after mNextPart. That means we must have
+ // gotten OnStopRequest for mNextPart; OnStopRequest calls
+ // OnImageDataComplete. For raster images that are part of a multipart
+ // image OnImageDataComplete will synchronously fire the load event
+ // because we synchronously perform the metadata decode in that function,
+ // because parts of multipart images have the transient flag set. So for
+ // raster images we are assured that the load event has happened by this
+ // point for mNextPart. For vector images there is no such luck because
+ // OnImageDataComplete needs to wait for the load event in the underlying
+ // svg document, which we can't force to happen synchronously. So we just
+ // send a fake load event for mNextPart. We are the only listener for it
+ // right now so it won't confuse anyone. When the real load event comes,
+ // progress tracker won't send it out because it's already in the
+ // progress. And nobody should be caring about load events on the current
+ // part of multipart images since they might be sent multiple times or
+ // might not be sent at all depending on timing. So this should be okay.
+
+ RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+ if (tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE)) {
+ Progress loadProgress =
+ LoadCompleteProgress(/* aLastPart = */ false, /* aError = */ false,
+ /* aStatus = */ NS_OK);
+ tracker->SyncNotifyProgress(loadProgress | FLAG_SIZE_AVAILABLE);
+ }
+ }
+
+ // RequestDecodeForSize() should've sent synchronous notifications that
+ // would have caused us to call FinishObserving() (and null out mImage)
+ // already. If for some reason it didn't, we should do so here.
+ if (mImage) {
+ FinishObserving();
+ }
+ }
+
+ virtual void Notify(int32_t aType,
+ const nsIntRect* aRect = nullptr) override {
+ if (!mImage) {
+ // We've already finished observing the last image we were given.
+ return;
+ }
+
+ if (aType != imgINotificationObserver::FRAME_COMPLETE) {
+ return;
+ }
+
+ if (mImage && mImage->GetType() == imgIContainer::TYPE_VECTOR) {
+ RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+ if (tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE)) {
+ // Vector images can send frame complete during loading (any mutation in
+ // the underlying svg doc will call OnRenderingChange which sends frame
+ // complete). Whereas raster images can only send frame complete before
+ // the load event if there has been something to trigger a decode, but
+ // we do not request a decode. So we will ignore this for vector images
+ // so as to enforce the invariant described above in
+ // BlockUntilDecodedAndFinishObserving that the current part always has
+ // the load complete progress.
+ return;
+ }
+ }
+
+ FinishObserving();
+ }
+
+ virtual void OnLoadComplete(bool aLastPart) override {
+ if (!mImage) {
+ // We've already finished observing the last image we were given.
+ return;
+ }
+
+ // Retrieve the image's intrinsic size.
+ int32_t width = 0;
+ int32_t height = 0;
+ mImage->GetWidth(&width);
+ mImage->GetHeight(&height);
+
+ // Request decoding at the intrinsic size.
+ mImage->RequestDecodeForSize(IntSize(width, height),
+ imgIContainer::DECODE_FLAGS_DEFAULT |
+ imgIContainer::FLAG_HIGH_QUALITY_SCALING);
+
+ // If there's already an error, we may never get a FRAME_COMPLETE
+ // notification, so go ahead and notify our owner right away.
+ RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+ if (tracker->GetProgress() & FLAG_HAS_ERROR) {
+ FinishObserving();
+ return;
+ }
+
+ if (mImage && mImage->GetType() == imgIContainer::TYPE_VECTOR &&
+ (tracker->GetProgress() & FLAG_FRAME_COMPLETE)) {
+ // It's a vector image that we may have already got a frame complete
+ // before load that we ignored (see Notify above). Now that we have the
+ // load event too, we make this part current.
+ FinishObserving();
+ }
+ }
+
+ // Other notifications are ignored.
+ virtual void SetHasImage() override {}
+ virtual bool NotificationsDeferred() const override { return false; }
+ virtual void MarkPendingNotify() override {}
+ virtual void ClearPendingNotify() override {}
+
+ private:
+ virtual ~NextPartObserver() {}
+
+ void FinishObserving() {
+ MOZ_ASSERT(mImage);
+
+ RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+ tracker->RemoveObserver(this);
+ mImage = nullptr;
+
+ mOwner->FinishTransition();
+ }
+
+ MultipartImage* mOwner;
+ RefPtr<Image> mImage;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Implementation
+///////////////////////////////////////////////////////////////////////////////
+
+MultipartImage::MultipartImage(Image* aFirstPart)
+ : ImageWrapper(aFirstPart), mPendingNotify(false) {
+ mNextPartObserver = new NextPartObserver(this);
+}
+
+void MultipartImage::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mTracker, "Should've called SetProgressTracker() by now");
+
+ // Start observing the first part.
+ RefPtr<ProgressTracker> firstPartTracker = InnerImage()->GetProgressTracker();
+ firstPartTracker->AddObserver(this);
+ InnerImage()->IncrementAnimationConsumers();
+}
+
+MultipartImage::~MultipartImage() {
+ // Ask our ProgressTracker to drop its weak reference to us.
+ mTracker->ResetImage();
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(MultipartImage, ImageWrapper)
+
+void MultipartImage::BeginTransitionToPart(Image* aNextPart) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aNextPart);
+
+ if (mNextPart) {
+ // Let the decoder catch up so we don't drop frames.
+ mNextPartObserver->BlockUntilDecodedAndFinishObserving();
+ MOZ_ASSERT(!mNextPart);
+ }
+
+ mNextPart = aNextPart;
+
+ // Start observing the next part; we'll complete the transition when
+ // NextPartObserver calls FinishTransition.
+ mNextPartObserver->BeginObserving(mNextPart);
+ mNextPart->IncrementAnimationConsumers();
+}
+
+static Progress FilterProgress(Progress aProgress) {
+ // Filter out onload blocking notifications, since we don't want to block
+ // onload for multipart images.
+ // Filter out errors, since we don't want errors in one part to error out
+ // the whole stream.
+ return aProgress & ~FLAG_HAS_ERROR;
+}
+
+void MultipartImage::FinishTransition() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mNextPart, "Should have a next part here");
+
+ RefPtr<ProgressTracker> newCurrentPartTracker =
+ mNextPart->GetProgressTracker();
+ if (newCurrentPartTracker->GetProgress() & FLAG_HAS_ERROR) {
+ // This frame has an error; drop it.
+ mNextPart = nullptr;
+
+ // We still need to notify, though.
+ mTracker->ResetForNewRequest();
+ RefPtr<ProgressTracker> currentPartTracker =
+ InnerImage()->GetProgressTracker();
+ mTracker->SyncNotifyProgress(
+ FilterProgress(currentPartTracker->GetProgress()));
+
+ return;
+ }
+
+ // Stop observing the current part.
+ {
+ RefPtr<ProgressTracker> currentPartTracker =
+ InnerImage()->GetProgressTracker();
+ currentPartTracker->RemoveObserver(this);
+ }
+
+ // Make the next part become the current part.
+ mTracker->ResetForNewRequest();
+ SetInnerImage(mNextPart);
+ mNextPart = nullptr;
+ newCurrentPartTracker->AddObserver(this);
+
+ // Finally, send all the notifications for the new current part and send a
+ // FRAME_UPDATE notification so that observers know to redraw.
+ mTracker->SyncNotifyProgress(
+ FilterProgress(newCurrentPartTracker->GetProgress()),
+ GetMaxSizedIntRect());
+}
+
+already_AddRefed<imgIContainer> MultipartImage::Unwrap() {
+ // Although we wrap another image, we don't allow callers to unwrap as. As far
+ // as external code is concerned, MultipartImage is atomic.
+ nsCOMPtr<imgIContainer> image = this;
+ return image.forget();
+}
+
+already_AddRefed<ProgressTracker> MultipartImage::GetProgressTracker() {
+ MOZ_ASSERT(mTracker);
+ RefPtr<ProgressTracker> tracker = mTracker;
+ return tracker.forget();
+}
+
+void MultipartImage::SetProgressTracker(ProgressTracker* aTracker) {
+ MOZ_ASSERT(aTracker);
+ MOZ_ASSERT(!mTracker);
+ mTracker = aTracker;
+}
+
+nsresult MultipartImage::OnImageDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInStr,
+ uint64_t aSourceOffset,
+ uint32_t aCount) {
+ // Note that this method is special in that we forward it to the next part if
+ // one exists, and *not* the current part.
+
+ // We may trigger notifications that will free mNextPart, so keep it alive.
+ RefPtr<Image> nextPart = mNextPart;
+ if (nextPart) {
+ nextPart->OnImageDataAvailable(aRequest, aInStr, aSourceOffset, aCount);
+ } else {
+ InnerImage()->OnImageDataAvailable(aRequest, aInStr, aSourceOffset, aCount);
+ }
+
+ return NS_OK;
+}
+
+nsresult MultipartImage::OnImageDataComplete(nsIRequest* aRequest,
+ nsresult aStatus, bool aLastPart) {
+ // Note that this method is special in that we forward it to the next part if
+ // one exists, and *not* the current part.
+
+ // We may trigger notifications that will free mNextPart, so keep it alive.
+ RefPtr<Image> nextPart = mNextPart;
+ if (nextPart) {
+ nextPart->OnImageDataComplete(aRequest, aStatus, aLastPart);
+ } else {
+ InnerImage()->OnImageDataComplete(aRequest, aStatus, aLastPart);
+ }
+
+ return NS_OK;
+}
+
+void MultipartImage::Notify(int32_t aType,
+ const nsIntRect* aRect /* = nullptr*/) {
+ if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
+ mTracker->SyncNotifyProgress(FLAG_SIZE_AVAILABLE);
+ } else if (aType == imgINotificationObserver::FRAME_UPDATE) {
+ mTracker->SyncNotifyProgress(NoProgress, *aRect);
+ } else if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ mTracker->SyncNotifyProgress(FLAG_FRAME_COMPLETE);
+ } else if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ mTracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
+ } else if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ mTracker->SyncNotifyProgress(FLAG_DECODE_COMPLETE);
+ } else if (aType == imgINotificationObserver::DISCARD) {
+ mTracker->OnDiscard();
+ } else if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
+ mTracker->OnUnlockedDraw();
+ } else if (aType == imgINotificationObserver::IS_ANIMATED) {
+ mTracker->SyncNotifyProgress(FLAG_IS_ANIMATED);
+ } else if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
+ mTracker->SyncNotifyProgress(FLAG_HAS_TRANSPARENCY);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
+ }
+}
+
+void MultipartImage::OnLoadComplete(bool aLastPart) {
+ Progress progress = FLAG_LOAD_COMPLETE;
+ if (aLastPart) {
+ progress |= FLAG_LAST_PART_COMPLETE;
+ }
+ mTracker->SyncNotifyProgress(progress);
+}
+
+void MultipartImage::SetHasImage() { mTracker->OnImageAvailable(); }
+
+bool MultipartImage::NotificationsDeferred() const { return mPendingNotify; }
+
+void MultipartImage::MarkPendingNotify() { mPendingNotify = true; }
+
+void MultipartImage::ClearPendingNotify() { mPendingNotify = false; }
+
+} // namespace image
+} // namespace mozilla