1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* table of images used in a document, for batch locking/unlocking and
8  * animating */
9 
10 #include "ImageTracker.h"
11 
12 #include "imgIContainer.h"
13 #include "imgIRequest.h"
14 #include "mozilla/Preferences.h"
15 #include "nsXULAppAPI.h"
16 
17 namespace mozilla::dom {
18 
ImageTracker()19 ImageTracker::ImageTracker() : mLocking(false), mAnimating(true) {}
20 
~ImageTracker()21 ImageTracker::~ImageTracker() { SetLockingState(false); }
22 
Add(imgIRequest * aImage)23 nsresult ImageTracker::Add(imgIRequest* aImage) {
24   MOZ_ASSERT(aImage);
25 
26   const nsresult rv = mImages.WithEntryHandle(aImage, [&](auto&& entry) {
27     nsresult rv = NS_OK;
28     if (entry) {
29       // The image is already in the hashtable.  Increment its count.
30       uint32_t oldCount = entry.Data();
31       MOZ_ASSERT(oldCount > 0, "Entry in the image tracker with count 0!");
32       entry.Data() = oldCount + 1;
33     } else {
34       // A new entry was inserted - set the count to 1.
35       entry.Insert(1);
36 
37       // If we're locking images, lock this image too.
38       if (mLocking) {
39         rv = aImage->LockImage();
40       }
41 
42       // If we're animating images, request that this image be animated too.
43       if (mAnimating) {
44         nsresult rv2 = aImage->IncrementAnimationConsumers();
45         rv = NS_SUCCEEDED(rv) ? rv2 : rv;
46       }
47     }
48 
49     return rv;
50   });
51 
52   return rv;
53 }
54 
Remove(imgIRequest * aImage,uint32_t aFlags)55 nsresult ImageTracker::Remove(imgIRequest* aImage, uint32_t aFlags) {
56   NS_ENSURE_ARG_POINTER(aImage);
57 
58   // Get the old count. It should exist and be > 0.
59   if (auto entry = mImages.Lookup(aImage)) {
60     MOZ_ASSERT(entry.Data() > 0, "Entry in the image tracker with count 0!");
61     // If the count becomes zero, remove it from the tracker.
62     if (--entry.Data() == 0) {
63       entry.Remove();
64     } else {
65       return NS_OK;
66     }
67   } else {
68     MOZ_ASSERT_UNREACHABLE("Removing image that wasn't in the tracker!");
69     return NS_OK;
70   }
71 
72   nsresult rv = NS_OK;
73 
74   // Now that we're no longer tracking this image, unlock it if we'd
75   // previously locked it.
76   if (mLocking) {
77     rv = aImage->UnlockImage();
78   }
79 
80   // If we're animating images, remove our request to animate this one.
81   if (mAnimating) {
82     nsresult rv2 = aImage->DecrementAnimationConsumers();
83     rv = NS_SUCCEEDED(rv) ? rv2 : rv;
84   }
85 
86   if (aFlags & REQUEST_DISCARD) {
87     // Request that the image be discarded if nobody else holds a lock on it.
88     // Do this even if !mLocking, because even if we didn't just unlock
89     // this image, it might still be a candidate for discarding.
90     aImage->RequestDiscard();
91   }
92 
93   return rv;
94 }
95 
SetLockingState(bool aLocked)96 nsresult ImageTracker::SetLockingState(bool aLocked) {
97   if (XRE_IsContentProcess() &&
98       !Preferences::GetBool("image.mem.allow_locking_in_content_processes",
99                             true)) {
100     return NS_OK;
101   }
102 
103   // If there's no change, there's nothing to do.
104   if (mLocking == aLocked) return NS_OK;
105 
106   // Otherwise, iterate over our images and perform the appropriate action.
107   for (imgIRequest* image : mImages.Keys()) {
108     if (aLocked) {
109       image->LockImage();
110     } else {
111       image->UnlockImage();
112     }
113   }
114 
115   // Update state.
116   mLocking = aLocked;
117 
118   return NS_OK;
119 }
120 
SetAnimatingState(bool aAnimating)121 void ImageTracker::SetAnimatingState(bool aAnimating) {
122   // If there's no change, there's nothing to do.
123   if (mAnimating == aAnimating) return;
124 
125   // Otherwise, iterate over our images and perform the appropriate action.
126   for (imgIRequest* image : mImages.Keys()) {
127     if (aAnimating) {
128       image->IncrementAnimationConsumers();
129     } else {
130       image->DecrementAnimationConsumers();
131     }
132   }
133 
134   // Update state.
135   mAnimating = aAnimating;
136 }
137 
RequestDiscardAll()138 void ImageTracker::RequestDiscardAll() {
139   for (imgIRequest* image : mImages.Keys()) {
140     image->RequestDiscard();
141   }
142 }
143 
MediaFeatureValuesChangedAllDocuments(const MediaFeatureChange & aChange)144 void ImageTracker::MediaFeatureValuesChangedAllDocuments(
145     const MediaFeatureChange& aChange) {
146   // Inform every content image used in the document that media feature values
147   // have changed.  If the same image is used in multiple places, then we can
148   // end up informing them multiple times.  Theme changes are rare though and we
149   // don't bother trying to ensure we only do this once per image.
150   //
151   // Pull the images out into an array and iterate over them, in case the
152   // image notifications do something that ends up modifying the table.
153   nsTArray<nsCOMPtr<imgIContainer>> images;
154   for (imgIRequest* req : mImages.Keys()) {
155     nsCOMPtr<imgIContainer> image;
156     req->GetImage(getter_AddRefs(image));
157     if (!image) {
158       continue;
159     }
160     images.AppendElement(image->Unwrap());
161   }
162   for (imgIContainer* image : images) {
163     image->MediaFeatureValuesChangedAllDocuments(aChange);
164   }
165 }
166 
167 }  // namespace mozilla::dom
168