Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/image/Image.cpp
Line
Count
Source (jump to first uncovered line)
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 "Image.h"
7
#include "Layers.h"               // for LayerManager
8
#include "nsRefreshDriver.h"
9
#include "nsContentUtils.h"
10
#include "mozilla/SizeOfState.h"
11
#include "mozilla/TimeStamp.h"
12
#include "mozilla/Tuple.h"        // for Tie
13
14
namespace mozilla {
15
namespace image {
16
17
///////////////////////////////////////////////////////////////////////////////
18
// Memory Reporting
19
///////////////////////////////////////////////////////////////////////////////
20
21
ImageMemoryCounter::ImageMemoryCounter(Image* aImage,
22
                                       SizeOfState& aState,
23
                                       bool aIsUsed)
24
  : mIsUsed(aIsUsed)
25
0
{
26
0
  MOZ_ASSERT(aImage);
27
0
28
0
  // Extract metadata about the image.
29
0
  nsCOMPtr<nsIURI> imageURL(aImage->GetURI());
30
0
  if (imageURL) {
31
0
    imageURL->GetSpec(mURI);
32
0
  }
33
0
34
0
  int32_t width = 0;
35
0
  int32_t height = 0;
36
0
  aImage->GetWidth(&width);
37
0
  aImage->GetHeight(&height);
38
0
  mIntrinsicSize.SizeTo(width, height);
39
0
40
0
  mType = aImage->GetType();
41
0
42
0
  // Populate memory counters for source and decoded data.
43
0
  mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aState));
44
0
  aImage->CollectSizeOfSurfaces(mSurfaces, aState.mMallocSizeOf);
45
0
46
0
  // Compute totals.
47
0
  for (const SurfaceMemoryCounter& surfaceCounter : mSurfaces) {
48
0
    mValues += surfaceCounter.Values();
49
0
  }
50
0
}
51
52
53
///////////////////////////////////////////////////////////////////////////////
54
// Image Base Types
55
///////////////////////////////////////////////////////////////////////////////
56
57
bool
58
ImageResource::GetSpecTruncatedTo1k(nsCString& aSpec) const
59
0
{
60
0
  static const size_t sMaxTruncatedLength = 1024;
61
0
62
0
  mURI->GetSpec(aSpec);
63
0
  if (sMaxTruncatedLength >= aSpec.Length()) {
64
0
    return true;
65
0
  }
66
0
67
0
  aSpec.Truncate(sMaxTruncatedLength);
68
0
  return false;
69
0
}
70
71
void
72
ImageResource::SetCurrentImage(ImageContainer* aContainer,
73
                               SourceSurface* aSurface,
74
                               bool aInTransaction)
75
0
{
76
0
  MOZ_ASSERT(NS_IsMainThread());
77
0
  MOZ_ASSERT(aContainer);
78
0
79
0
  if (!aSurface) {
80
0
    // The OS threw out some or all of our buffer. We'll need to wait for the
81
0
    // redecode (which was automatically triggered by GetFrame) to complete.
82
0
    return;
83
0
  }
84
0
85
0
  // |image| holds a reference to a SourceSurface which in turn holds a lock on
86
0
  // the current frame's data buffer, ensuring that it doesn't get freed as
87
0
  // long as the layer system keeps this ImageContainer alive.
88
0
  RefPtr<layers::Image> image = new layers::SourceSurfaceImage(aSurface);
89
0
90
0
  // We can share the producer ID with other containers because it is only
91
0
  // used internally to validate the frames given to a particular container
92
0
  // so that another object cannot add its own. Similarly the frame ID is
93
0
  // only used internally to ensure it is always increasing, and skipping
94
0
  // IDs from an individual container's perspective is acceptable.
95
0
  AutoTArray<ImageContainer::NonOwningImage, 1> imageList;
96
0
  imageList.AppendElement(ImageContainer::NonOwningImage(image, TimeStamp(),
97
0
                                                         mLastFrameID++,
98
0
                                                         mImageProducerID));
99
0
100
0
  if (aInTransaction) {
101
0
    aContainer->SetCurrentImagesInTransaction(imageList);
102
0
  } else {
103
0
    aContainer->SetCurrentImages(imageList);
104
0
  }
105
0
}
106
107
ImgDrawResult
108
ImageResource::GetImageContainerImpl(LayerManager* aManager,
109
                                     const IntSize& aSize,
110
                                     const Maybe<SVGImageContext>& aSVGContext,
111
                                     uint32_t aFlags,
112
                                     ImageContainer** aOutContainer)
113
0
{
114
0
  MOZ_ASSERT(NS_IsMainThread());
115
0
  MOZ_ASSERT(aManager);
116
0
  MOZ_ASSERT((aFlags & ~(FLAG_SYNC_DECODE |
117
0
                         FLAG_SYNC_DECODE_IF_FAST |
118
0
                         FLAG_ASYNC_NOTIFY |
119
0
                         FLAG_HIGH_QUALITY_SCALING))
120
0
               == FLAG_NONE,
121
0
             "Unsupported flag passed to GetImageContainer");
122
0
123
0
  ImgDrawResult drawResult;
124
0
  IntSize size;
125
0
  Tie(drawResult, size) = GetImageContainerSize(aManager, aSize, aFlags);
126
0
  if (drawResult != ImgDrawResult::SUCCESS) {
127
0
    return drawResult;
128
0
  }
129
0
130
0
  MOZ_ASSERT(!size.IsEmpty());
131
0
132
0
  if (mAnimationConsumers == 0) {
133
0
    SendOnUnlockedDraw(aFlags);
134
0
  }
135
0
136
0
  uint32_t flags = (aFlags & ~(FLAG_SYNC_DECODE |
137
0
                               FLAG_SYNC_DECODE_IF_FAST)) | FLAG_ASYNC_NOTIFY;
138
0
  RefPtr<layers::ImageContainer> container;
139
0
  ImageContainerEntry* entry = nullptr;
140
0
  int i = mImageContainers.Length() - 1;
141
0
  for (; i >= 0; --i) {
142
0
    entry = &mImageContainers[i];
143
0
    container = entry->mContainer.get();
144
0
    if (size == entry->mSize && flags == entry->mFlags &&
145
0
        aSVGContext == entry->mSVGContext) {
146
0
      // Lack of a container is handled below.
147
0
      break;
148
0
    } else if (!container) {
149
0
      // Stop tracking if our weak pointer to the image container was freed.
150
0
      mImageContainers.RemoveElementAt(i);
151
0
    } else {
152
0
      // It isn't a match, but still valid. Forget the container so we don't
153
0
      // try to reuse it below.
154
0
      container = nullptr;
155
0
    }
156
0
  }
157
0
158
0
  if (container) {
159
0
    switch (entry->mLastDrawResult) {
160
0
      case ImgDrawResult::SUCCESS:
161
0
      case ImgDrawResult::BAD_IMAGE:
162
0
      case ImgDrawResult::BAD_ARGS:
163
0
      case ImgDrawResult::NOT_SUPPORTED:
164
0
        container.forget(aOutContainer);
165
0
        return entry->mLastDrawResult;
166
0
      case ImgDrawResult::NOT_READY:
167
0
      case ImgDrawResult::INCOMPLETE:
168
0
      case ImgDrawResult::TEMPORARY_ERROR:
169
0
        // Temporary conditions where we need to rerequest the frame to recover.
170
0
        break;
171
0
      case ImgDrawResult::WRONG_SIZE:
172
0
        // Unused by GetFrameInternal
173
0
      default:
174
0
        MOZ_ASSERT_UNREACHABLE("Unhandled ImgDrawResult type!");
175
0
        container.forget(aOutContainer);
176
0
        return entry->mLastDrawResult;
177
0
    }
178
0
  }
179
0
180
#ifdef DEBUG
181
  NotifyDrawingObservers();
182
#endif
183
184
0
  IntSize bestSize;
185
0
  RefPtr<SourceSurface> surface;
186
0
  Tie(drawResult, bestSize, surface) =
187
0
    GetFrameInternal(size, aSVGContext, FRAME_CURRENT,
188
0
                     aFlags | FLAG_ASYNC_NOTIFY);
189
0
190
0
  // The requested size might be refused by the surface cache (i.e. due to
191
0
  // factor-of-2 mode). In that case we don't want to create an entry for this
192
0
  // specific size, but rather re-use the entry for the substituted size.
193
0
  if (bestSize != size) {
194
0
    MOZ_ASSERT(!bestSize.IsEmpty());
195
0
196
0
    // We can only remove the entry if we no longer have a container, because if
197
0
    // there are strong references to it remaining, we need to still update it
198
0
    // in UpdateImageContainer.
199
0
    if (i >= 0 && !container) {
200
0
      mImageContainers.RemoveElementAt(i);
201
0
    }
202
0
203
0
    // Forget about the stale container, if any. This lets the entry creation
204
0
    // logic do its job below, if it turns out there is no existing best entry
205
0
    // or the best entry doesn't have a container.
206
0
    container = nullptr;
207
0
208
0
    // We need to do the entry search again for the new size. We skip pruning
209
0
    // because we did this above once already, but ImageContainer is threadsafe,
210
0
    // so there is a remote possibility it got freed.
211
0
    i = mImageContainers.Length() - 1;
212
0
    for (; i >= 0; --i) {
213
0
      entry = &mImageContainers[i];
214
0
      if (bestSize == entry->mSize && flags == entry->mFlags &&
215
0
          aSVGContext == entry->mSVGContext) {
216
0
        container = entry->mContainer.get();
217
0
        if (container) {
218
0
          switch (entry->mLastDrawResult) {
219
0
            case ImgDrawResult::SUCCESS:
220
0
            case ImgDrawResult::BAD_IMAGE:
221
0
            case ImgDrawResult::BAD_ARGS:
222
0
            case ImgDrawResult::NOT_SUPPORTED:
223
0
              container.forget(aOutContainer);
224
0
              return entry->mLastDrawResult;
225
0
            case ImgDrawResult::NOT_READY:
226
0
            case ImgDrawResult::INCOMPLETE:
227
0
            case ImgDrawResult::TEMPORARY_ERROR:
228
0
              // Temporary conditions where we need to rerequest the frame to
229
0
              // recover. We have already done so!
230
0
              break;
231
0
           case ImgDrawResult::WRONG_SIZE:
232
0
              // Unused by GetFrameInternal
233
0
            default:
234
0
              MOZ_ASSERT_UNREACHABLE("Unhandled DrawResult type!");
235
0
              container.forget(aOutContainer);
236
0
              return entry->mLastDrawResult;
237
0
          }
238
0
        }
239
0
        break;
240
0
      }
241
0
    }
242
0
  }
243
0
244
0
  if (!container) {
245
0
    // We need a new ImageContainer, so create one.
246
0
    container = LayerManager::CreateImageContainer();
247
0
248
0
    if (i >= 0) {
249
0
      entry->mContainer = container;
250
0
    } else {
251
0
      entry = mImageContainers.AppendElement(
252
0
        ImageContainerEntry(bestSize, aSVGContext, container.get(), flags));
253
0
    }
254
0
  }
255
0
256
0
  SetCurrentImage(container, surface, true);
257
0
  entry->mLastDrawResult = drawResult;
258
0
  container.forget(aOutContainer);
259
0
  return drawResult;
260
0
}
261
262
void
263
ImageResource::UpdateImageContainer()
264
0
{
265
0
  MOZ_ASSERT(NS_IsMainThread());
266
0
267
0
  for (int i = mImageContainers.Length() - 1; i >= 0; --i) {
268
0
    ImageContainerEntry& entry = mImageContainers[i];
269
0
    RefPtr<ImageContainer> container = entry.mContainer.get();
270
0
    if (container) {
271
0
      IntSize bestSize;
272
0
      RefPtr<SourceSurface> surface;
273
0
      Tie(entry.mLastDrawResult, bestSize, surface) =
274
0
        GetFrameInternal(entry.mSize, entry.mSVGContext,
275
0
                         FRAME_CURRENT, entry.mFlags);
276
0
277
0
      // It is possible that this is a factor-of-2 substitution. Since we
278
0
      // managed to convert the weak reference into a strong reference, that
279
0
      // means that an imagelib user still is holding onto the container. thus
280
0
      // we cannot consolidate and must keep updating the duplicate container.
281
0
      SetCurrentImage(container, surface, false);
282
0
    } else {
283
0
      // Stop tracking if our weak pointer to the image container was freed.
284
0
      mImageContainers.RemoveElementAt(i);
285
0
    }
286
0
  }
287
0
}
288
289
void
290
ImageResource::ReleaseImageContainer()
291
0
{
292
0
  MOZ_ASSERT(NS_IsMainThread());
293
0
  mImageContainers.Clear();
294
0
}
295
296
// Constructor
297
ImageResource::ImageResource(nsIURI* aURI) :
298
  mURI(aURI),
299
  mInnerWindowId(0),
300
  mAnimationConsumers(0),
301
  mAnimationMode(kNormalAnimMode),
302
  mInitialized(false),
303
  mAnimating(false),
304
  mError(false),
305
  mImageProducerID(ImageContainer::AllocateProducerID()),
306
  mLastFrameID(0)
307
0
{ }
308
309
ImageResource::~ImageResource()
310
0
{
311
0
  // Ask our ProgressTracker to drop its weak reference to us.
312
0
  mProgressTracker->ResetImage();
313
0
}
314
315
void
316
ImageResource::IncrementAnimationConsumers()
317
0
{
318
0
  MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
319
0
                                "with DecrementAnimationConsumers");
320
0
  mAnimationConsumers++;
321
0
}
322
323
void
324
ImageResource::DecrementAnimationConsumers()
325
0
{
326
0
  MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
327
0
                                "with IncrementAnimationConsumers");
328
0
  MOZ_ASSERT(mAnimationConsumers >= 1,
329
0
             "Invalid no. of animation consumers!");
330
0
  mAnimationConsumers--;
331
0
}
332
333
nsresult
334
ImageResource::GetAnimationModeInternal(uint16_t* aAnimationMode)
335
0
{
336
0
  if (mError) {
337
0
    return NS_ERROR_FAILURE;
338
0
  }
339
0
340
0
  NS_ENSURE_ARG_POINTER(aAnimationMode);
341
0
342
0
  *aAnimationMode = mAnimationMode;
343
0
  return NS_OK;
344
0
}
345
346
nsresult
347
ImageResource::SetAnimationModeInternal(uint16_t aAnimationMode)
348
0
{
349
0
  if (mError) {
350
0
    return NS_ERROR_FAILURE;
351
0
  }
352
0
353
0
  NS_ASSERTION(aAnimationMode == kNormalAnimMode ||
354
0
               aAnimationMode == kDontAnimMode ||
355
0
               aAnimationMode == kLoopOnceAnimMode,
356
0
               "Wrong Animation Mode is being set!");
357
0
358
0
  mAnimationMode = aAnimationMode;
359
0
360
0
  return NS_OK;
361
0
}
362
363
bool
364
ImageResource::HadRecentRefresh(const TimeStamp& aTime)
365
0
{
366
0
  // Our threshold for "recent" is 1/2 of the default refresh-driver interval.
367
0
  // This ensures that we allow for frame rates at least as fast as the
368
0
  // refresh driver's default rate.
369
0
  static TimeDuration recentThreshold =
370
0
      TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval() / 2.0);
371
0
372
0
  if (!mLastRefreshTime.IsNull() &&
373
0
      aTime - mLastRefreshTime < recentThreshold) {
374
0
    return true;
375
0
  }
376
0
377
0
  // else, we can proceed with a refresh.
378
0
  // But first, update our last refresh time:
379
0
  mLastRefreshTime = aTime;
380
0
  return false;
381
0
}
382
383
void
384
ImageResource::EvaluateAnimation()
385
0
{
386
0
  if (!mAnimating && ShouldAnimate()) {
387
0
    nsresult rv = StartAnimation();
388
0
    mAnimating = NS_SUCCEEDED(rv);
389
0
  } else if (mAnimating && !ShouldAnimate()) {
390
0
    StopAnimation();
391
0
  }
392
0
}
393
394
void
395
ImageResource::SendOnUnlockedDraw(uint32_t aFlags)
396
0
{
397
0
  if (!mProgressTracker) {
398
0
    return;
399
0
  }
400
0
401
0
  if (!(aFlags & FLAG_ASYNC_NOTIFY)) {
402
0
    mProgressTracker->OnUnlockedDraw();
403
0
  } else {
404
0
    NotNull<RefPtr<ImageResource>> image = WrapNotNull(this);
405
0
    nsCOMPtr<nsIEventTarget> eventTarget = mProgressTracker->GetEventTarget();
406
0
    nsCOMPtr<nsIRunnable> ev = NS_NewRunnableFunction(
407
0
      "image::ImageResource::SendOnUnlockedDraw", [=]() -> void {
408
0
        RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
409
0
        if (tracker) {
410
0
          tracker->OnUnlockedDraw();
411
0
        }
412
0
      });
413
0
    eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
414
0
  }
415
0
}
416
417
#ifdef DEBUG
418
void
419
ImageResource::NotifyDrawingObservers()
420
{
421
  if (!mURI || !NS_IsMainThread()) {
422
    return;
423
  }
424
425
  bool match = false;
426
  if ((NS_FAILED(mURI->SchemeIs("resource", &match)) || !match) &&
427
      (NS_FAILED(mURI->SchemeIs("chrome", &match)) || !match)) {
428
    return;
429
  }
430
431
  // Record the image drawing for startup performance testing.
432
  nsCOMPtr<nsIURI> uri = mURI;
433
  nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
434
    "image::ImageResource::NotifyDrawingObservers", [uri]() {
435
      nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
436
      NS_WARNING_ASSERTION(obs, "Can't get an observer service handle");
437
      if (obs) {
438
        nsAutoCString spec;
439
        uri->GetSpec(spec);
440
        obs->NotifyObservers(nullptr, "image-drawing", NS_ConvertUTF8toUTF16(spec).get());
441
      }
442
    }));
443
}
444
#endif
445
446
} // namespace image
447
} // namespace mozilla