Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : *
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 : #include "ImageLogging.h"
8 : #include "ProgressTracker.h"
9 :
10 : #include "imgIContainer.h"
11 : #include "imgINotificationObserver.h"
12 : #include "imgIRequest.h"
13 : #include "Image.h"
14 : #include "nsNetUtil.h"
15 : #include "nsIObserverService.h"
16 :
17 : #include "mozilla/Assertions.h"
18 : #include "mozilla/Services.h"
19 : #include "mozilla/SystemGroup.h"
20 :
21 : using mozilla::WeakPtr;
22 :
23 : namespace mozilla {
24 : namespace image {
25 :
26 : static void
27 0 : CheckProgressConsistency(Progress aOldProgress, Progress aNewProgress, bool aIsMultipart)
28 : {
29 : // Check preconditions for every progress bit.
30 :
31 : // Error's do not get propagated from the tracker for each image part to the
32 : // tracker for the multipart image because we don't want one bad part to
33 : // prevent the remaining parts from showing. So we need to consider whether
34 : // this is a tracker for a multipart image for these assertions to work.
35 :
36 0 : if (aNewProgress & FLAG_SIZE_AVAILABLE) {
37 : // No preconditions.
38 : }
39 0 : if (aNewProgress & FLAG_DECODE_COMPLETE) {
40 0 : MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
41 0 : MOZ_ASSERT(aIsMultipart || aNewProgress & (FLAG_FRAME_COMPLETE | FLAG_HAS_ERROR));
42 : }
43 0 : if (aNewProgress & FLAG_FRAME_COMPLETE) {
44 0 : MOZ_ASSERT(aNewProgress & FLAG_SIZE_AVAILABLE);
45 : }
46 0 : if (aNewProgress & FLAG_LOAD_COMPLETE) {
47 0 : MOZ_ASSERT(aIsMultipart || aNewProgress & (FLAG_SIZE_AVAILABLE | FLAG_HAS_ERROR));
48 : }
49 : if (aNewProgress & FLAG_IS_ANIMATED) {
50 : // No preconditions; like FLAG_HAS_TRANSPARENCY, we should normally never
51 : // discover this *after* FLAG_SIZE_AVAILABLE, but unfortunately some corrupt
52 : // GIFs may fool us.
53 : }
54 : if (aNewProgress & FLAG_HAS_TRANSPARENCY) {
55 : // XXX We'd like to assert that transparency is only set during metadata
56 : // decode but we don't have any way to assert that until bug 1254892 is fixed.
57 : }
58 0 : if (aNewProgress & FLAG_LAST_PART_COMPLETE) {
59 0 : MOZ_ASSERT(aNewProgress & FLAG_LOAD_COMPLETE);
60 : }
61 : if (aNewProgress & FLAG_HAS_ERROR) {
62 : // No preconditions.
63 : }
64 0 : }
65 :
66 0 : ProgressTracker::ProgressTracker()
67 : : mMutex("ProgressTracker::mMutex")
68 : , mImage(nullptr)
69 0 : , mEventTarget(WrapNotNull(nsCOMPtr<nsIEventTarget>(SystemGroup::EventTargetFor(TaskCategory::Other))))
70 : , mObserversWithTargets(0)
71 0 : , mObservers(new ObserverTable)
72 : , mProgress(NoProgress)
73 0 : , mIsMultipart(false)
74 0 : { }
75 :
76 : void
77 0 : ProgressTracker::SetImage(Image* aImage)
78 : {
79 0 : MutexAutoLock lock(mMutex);
80 0 : MOZ_ASSERT(aImage, "Setting null image");
81 0 : MOZ_ASSERT(!mImage, "Setting image when we already have one");
82 0 : mImage = aImage;
83 0 : }
84 :
85 : void
86 0 : ProgressTracker::ResetImage()
87 : {
88 0 : MutexAutoLock lock(mMutex);
89 0 : MOZ_ASSERT(mImage, "Resetting image when it's already null!");
90 0 : mImage = nullptr;
91 0 : }
92 :
93 : uint32_t
94 0 : ProgressTracker::GetImageStatus() const
95 : {
96 0 : uint32_t status = imgIRequest::STATUS_NONE;
97 :
98 : // Translate our current state to a set of imgIRequest::STATE_* flags.
99 0 : if (mProgress & FLAG_SIZE_AVAILABLE) {
100 0 : status |= imgIRequest::STATUS_SIZE_AVAILABLE;
101 : }
102 0 : if (mProgress & FLAG_DECODE_COMPLETE) {
103 0 : status |= imgIRequest::STATUS_DECODE_COMPLETE;
104 : }
105 0 : if (mProgress & FLAG_FRAME_COMPLETE) {
106 0 : status |= imgIRequest::STATUS_FRAME_COMPLETE;
107 : }
108 0 : if (mProgress & FLAG_LOAD_COMPLETE) {
109 0 : status |= imgIRequest::STATUS_LOAD_COMPLETE;
110 : }
111 0 : if (mProgress & FLAG_IS_ANIMATED) {
112 0 : status |= imgIRequest::STATUS_IS_ANIMATED;
113 : }
114 0 : if (mProgress & FLAG_HAS_TRANSPARENCY) {
115 0 : status |= imgIRequest::STATUS_HAS_TRANSPARENCY;
116 : }
117 0 : if (mProgress & FLAG_HAS_ERROR) {
118 0 : status |= imgIRequest::STATUS_ERROR;
119 : }
120 :
121 0 : return status;
122 : }
123 :
124 : // A helper class to allow us to call SyncNotify asynchronously.
125 0 : class AsyncNotifyRunnable : public Runnable
126 : {
127 : public:
128 0 : AsyncNotifyRunnable(ProgressTracker* aTracker,
129 : IProgressObserver* aObserver)
130 0 : : Runnable("ProgressTracker::AsyncNotifyRunnable")
131 0 : , mTracker(aTracker)
132 : {
133 0 : MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
134 0 : MOZ_ASSERT(aTracker, "aTracker should not be null");
135 0 : MOZ_ASSERT(aObserver, "aObserver should not be null");
136 0 : mObservers.AppendElement(aObserver);
137 0 : }
138 :
139 0 : NS_IMETHOD Run() override
140 : {
141 0 : MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
142 0 : MOZ_ASSERT(mTracker, "mTracker should not be null");
143 0 : for (uint32_t i = 0; i < mObservers.Length(); ++i) {
144 0 : mObservers[i]->ClearPendingNotify();
145 0 : mTracker->SyncNotify(mObservers[i]);
146 : }
147 :
148 0 : mTracker->mRunnable = nullptr;
149 0 : return NS_OK;
150 : }
151 :
152 : void AddObserver(IProgressObserver* aObserver)
153 : {
154 0 : mObservers.AppendElement(aObserver);
155 : }
156 :
157 : void RemoveObserver(IProgressObserver* aObserver)
158 : {
159 0 : mObservers.RemoveElement(aObserver);
160 : }
161 :
162 : private:
163 : friend class ProgressTracker;
164 :
165 : RefPtr<ProgressTracker> mTracker;
166 : nsTArray<RefPtr<IProgressObserver>> mObservers;
167 : };
168 :
169 : void
170 0 : ProgressTracker::Notify(IProgressObserver* aObserver)
171 : {
172 0 : MOZ_ASSERT(NS_IsMainThread());
173 :
174 0 : if (aObserver->NotificationsDeferred()) {
175 : // There is a pending notification, or the observer isn't ready yet.
176 : return;
177 : }
178 :
179 0 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
180 0 : RefPtr<Image> image = GetImage();
181 : LOG_FUNC_WITH_PARAM(gImgLog,
182 0 : "ProgressTracker::Notify async", "uri", image);
183 : }
184 :
185 1 : aObserver->MarkPendingNotify();
186 :
187 : // If we have an existing runnable that we can use, we just append this
188 : // observer to its list of observers to be notified. This ensures we don't
189 : // unnecessarily delay onload.
190 : AsyncNotifyRunnable* runnable =
191 2 : static_cast<AsyncNotifyRunnable*>(mRunnable.get());
192 :
193 0 : if (runnable) {
194 : runnable->AddObserver(aObserver);
195 : } else {
196 1 : mRunnable = new AsyncNotifyRunnable(this, aObserver);
197 3 : mEventTarget->Dispatch(mRunnable, NS_DISPATCH_NORMAL);
198 : }
199 : }
200 :
201 : // A helper class to allow us to call SyncNotify asynchronously for a given,
202 : // fixed, state.
203 0 : class AsyncNotifyCurrentStateRunnable : public Runnable
204 : {
205 : public:
206 0 : AsyncNotifyCurrentStateRunnable(ProgressTracker* aProgressTracker,
207 : IProgressObserver* aObserver)
208 0 : : Runnable("image::AsyncNotifyCurrentStateRunnable")
209 : , mProgressTracker(aProgressTracker)
210 0 : , mObserver(aObserver)
211 : {
212 0 : MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread");
213 0 : MOZ_ASSERT(mProgressTracker, "mProgressTracker should not be null");
214 0 : MOZ_ASSERT(mObserver, "mObserver should not be null");
215 0 : mImage = mProgressTracker->GetImage();
216 0 : }
217 :
218 0 : NS_IMETHOD Run() override
219 : {
220 0 : MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread");
221 0 : mObserver->ClearPendingNotify();
222 :
223 0 : mProgressTracker->SyncNotify(mObserver);
224 0 : return NS_OK;
225 : }
226 :
227 : private:
228 : RefPtr<ProgressTracker> mProgressTracker;
229 : RefPtr<IProgressObserver> mObserver;
230 :
231 : // We have to hold on to a reference to the tracker's image, just in case
232 : // it goes away while we're in the event queue.
233 : RefPtr<Image> mImage;
234 : };
235 :
236 : void
237 0 : ProgressTracker::NotifyCurrentState(IProgressObserver* aObserver)
238 : {
239 0 : MOZ_ASSERT(NS_IsMainThread());
240 :
241 0 : if (aObserver->NotificationsDeferred()) {
242 : // There is a pending notification, or the observer isn't ready yet.
243 0 : return;
244 : }
245 :
246 0 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
247 0 : RefPtr<Image> image = GetImage();
248 : LOG_FUNC_WITH_PARAM(gImgLog,
249 0 : "ProgressTracker::NotifyCurrentState", "uri", image);
250 : }
251 :
252 0 : aObserver->MarkPendingNotify();
253 :
254 : nsCOMPtr<nsIRunnable> ev = new AsyncNotifyCurrentStateRunnable(this,
255 0 : aObserver);
256 0 : mEventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
257 : }
258 :
259 : /**
260 : * ImageObserverNotifier is a helper type that abstracts over the difference
261 : * between sending notifications to all of the observers in an ObserverTable,
262 : * and sending them to a single observer. This allows the same notification code
263 : * to be used for both cases.
264 : */
265 : template <typename T> struct ImageObserverNotifier;
266 :
267 : template <>
268 : struct MOZ_STACK_CLASS ImageObserverNotifier<const ObserverTable*>
269 : {
270 : explicit ImageObserverNotifier(const ObserverTable* aObservers,
271 : bool aIgnoreDeferral = false)
272 61 : : mObservers(aObservers)
273 61 : , mIgnoreDeferral(aIgnoreDeferral)
274 : { }
275 :
276 : template <typename Lambda>
277 135 : void operator()(Lambda aFunc)
278 : {
279 1195 : for (auto iter = mObservers->ConstIter(); !iter.Done(); iter.Next()) {
280 790 : RefPtr<IProgressObserver> observer = iter.Data().get();
281 1185 : if (observer &&
282 738 : (mIgnoreDeferral || !observer->NotificationsDeferred())) {
283 395 : aFunc(observer);
284 : }
285 : }
286 135 : }
287 :
288 : private:
289 : const ObserverTable* mObservers;
290 : const bool mIgnoreDeferral;
291 : };
292 :
293 : template <>
294 : struct MOZ_STACK_CLASS ImageObserverNotifier<IProgressObserver*>
295 : {
296 : explicit ImageObserverNotifier(IProgressObserver* aObserver)
297 40 : : mObserver(aObserver)
298 : { }
299 :
300 : template <typename Lambda>
301 12 : void operator()(Lambda aFunc)
302 : {
303 12 : if (mObserver && !mObserver->NotificationsDeferred()) {
304 12 : aFunc(mObserver);
305 : }
306 12 : }
307 :
308 : private:
309 : IProgressObserver* mObserver;
310 : };
311 :
312 : template <typename T> void
313 0 : SyncNotifyInternal(const T& aObservers,
314 : bool aHasImage,
315 : Progress aProgress,
316 : const nsIntRect& aDirtyRect)
317 : {
318 0 : MOZ_ASSERT(NS_IsMainThread());
319 :
320 : typedef imgINotificationObserver I;
321 170 : ImageObserverNotifier<T> notify(aObservers);
322 :
323 85 : if (aProgress & FLAG_SIZE_AVAILABLE) {
324 76 : notify([](IProgressObserver* aObs) { aObs->Notify(I::SIZE_AVAILABLE); });
325 : }
326 :
327 85 : if (aHasImage) {
328 : // OnFrameUpdate
329 : // If there's any content in this frame at all (always true for
330 : // vector images, true for raster images that have decoded at
331 : // least one frame) then send OnFrameUpdate.
332 94 : if (!aDirtyRect.IsEmpty()) {
333 0 : notify([&](IProgressObserver* aObs) {
334 89 : aObs->Notify(I::FRAME_UPDATE, &aDirtyRect);
335 0 : });
336 : }
337 :
338 47 : if (aProgress & FLAG_FRAME_COMPLETE) {
339 0 : notify([](IProgressObserver* aObs) { aObs->Notify(I::FRAME_COMPLETE); });
340 : }
341 :
342 47 : if (aProgress & FLAG_HAS_TRANSPARENCY) {
343 76 : notify([](IProgressObserver* aObs) { aObs->Notify(I::HAS_TRANSPARENCY); });
344 : }
345 :
346 0 : if (aProgress & FLAG_IS_ANIMATED) {
347 0 : notify([](IProgressObserver* aObs) { aObs->Notify(I::IS_ANIMATED); });
348 : }
349 : }
350 :
351 0 : if (aProgress & FLAG_DECODE_COMPLETE) {
352 18 : MOZ_ASSERT(aHasImage, "Stopped decoding without ever having an image?");
353 70 : notify([](IProgressObserver* aObs) { aObs->Notify(I::DECODE_COMPLETE); });
354 : }
355 :
356 85 : if (aProgress & FLAG_LOAD_COMPLETE) {
357 76 : notify([=](IProgressObserver* aObs) {
358 0 : aObs->OnLoadComplete(aProgress & FLAG_LAST_PART_COMPLETE);
359 0 : });
360 : }
361 85 : }
362 :
363 : void
364 0 : ProgressTracker::SyncNotifyProgress(Progress aProgress,
365 : const nsIntRect& aInvalidRect
366 : /* = nsIntRect() */)
367 : {
368 0 : MOZ_ASSERT(NS_IsMainThread(), "Use mObservers on main thread only");
369 :
370 0 : Progress progress = Difference(aProgress);
371 0 : CheckProgressConsistency(mProgress, mProgress | progress, mIsMultipart);
372 :
373 : // Apply the changes.
374 45 : mProgress |= progress;
375 :
376 : // Send notifications.
377 90 : mObservers.Read([&](const ObserverTable* aTable) {
378 45 : SyncNotifyInternal(aTable, HasImage(), progress, aInvalidRect);
379 135 : });
380 :
381 45 : if (progress & FLAG_HAS_ERROR) {
382 0 : FireFailureNotification();
383 : }
384 45 : }
385 :
386 : void
387 40 : ProgressTracker::SyncNotify(IProgressObserver* aObserver)
388 : {
389 0 : MOZ_ASSERT(NS_IsMainThread());
390 :
391 0 : RefPtr<Image> image = GetImage();
392 80 : LOG_SCOPE_WITH_PARAM(gImgLog,
393 : "ProgressTracker::SyncNotify", "uri", image);
394 :
395 40 : nsIntRect rect;
396 0 : if (image) {
397 : int32_t width, height;
398 4 : if (NS_FAILED(image->GetWidth(&width)) ||
399 0 : NS_FAILED(image->GetHeight(&height))) {
400 : // Either the image has no intrinsic size, or it has an error.
401 0 : rect = GetMaxSizedIntRect();
402 : } else {
403 0 : rect.SizeTo(width, height);
404 : }
405 : }
406 :
407 0 : SyncNotifyInternal(aObserver, !!image, mProgress, rect);
408 40 : }
409 :
410 : void
411 4 : ProgressTracker::EmulateRequestFinished(IProgressObserver* aObserver)
412 : {
413 0 : MOZ_ASSERT(NS_IsMainThread(),
414 : "SyncNotifyState and mObservers are not threadsafe");
415 0 : RefPtr<IProgressObserver> kungFuDeathGrip(aObserver);
416 :
417 4 : if (!(mProgress & FLAG_LOAD_COMPLETE)) {
418 0 : aObserver->OnLoadComplete(true);
419 : }
420 0 : }
421 :
422 : already_AddRefed<nsIEventTarget>
423 13 : ProgressTracker::GetEventTarget() const
424 : {
425 0 : MutexAutoLock lock(mMutex);
426 39 : nsCOMPtr<nsIEventTarget> target = mEventTarget;
427 26 : return target.forget();
428 : }
429 :
430 : void
431 56 : ProgressTracker::AddObserver(IProgressObserver* aObserver)
432 : {
433 56 : MOZ_ASSERT(NS_IsMainThread());
434 0 : RefPtr<IProgressObserver> observer = aObserver;
435 :
436 112 : nsCOMPtr<nsIEventTarget> target = observer->GetEventTarget();
437 0 : if (target) {
438 56 : if (mObserversWithTargets == 0) {
439 : // On the first observer with a target (i.e. listener), always accept its
440 : // event target; this may be for a specific DocGroup, or it may be the
441 : // unlabelled main thread target.
442 0 : MutexAutoLock lock(mMutex);
443 0 : mEventTarget = WrapNotNull(target);
444 0 : } else if (mEventTarget.get() != target.get()) {
445 : // If a subsequent observer comes in with a different target, we need to
446 : // switch to use the unlabelled main thread target, if we haven't already.
447 0 : MutexAutoLock lock(mMutex);
448 0 : nsCOMPtr<nsIEventTarget> mainTarget(do_GetMainThread());
449 0 : mEventTarget = WrapNotNull(mainTarget);
450 : }
451 0 : ++mObserversWithTargets;
452 : }
453 :
454 0 : mObservers.Write([=](ObserverTable* aTable) {
455 0 : MOZ_ASSERT(!aTable->Get(observer, nullptr),
456 : "Adding duplicate entry for image observer");
457 :
458 168 : WeakPtr<IProgressObserver> weakPtr = observer.get();
459 0 : aTable->Put(observer, weakPtr);
460 0 : });
461 :
462 56 : MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
463 56 : }
464 :
465 : bool
466 0 : ProgressTracker::RemoveObserver(IProgressObserver* aObserver)
467 : {
468 0 : MOZ_ASSERT(NS_IsMainThread());
469 16 : RefPtr<IProgressObserver> observer = aObserver;
470 :
471 : // Remove the observer from the list.
472 0 : bool removed = mObservers.Write([observer](ObserverTable* aTable) {
473 16 : return aTable->Remove(observer);
474 16 : });
475 :
476 : // Sometimes once an image is decoded, and all of its observers removed, a new
477 : // document may request the same image. Thus we need to clear our event target
478 : // state when the last observer is removed, so that we select the most
479 : // appropriate event target when a new observer is added. Since the event
480 : // target may have changed (e.g. due to the scheduler group going away before
481 : // we were removed), so we should be cautious comparing this target against
482 : // anything at this stage.
483 0 : if (removed) {
484 8 : nsCOMPtr<nsIEventTarget> target = observer->GetEventTarget();
485 0 : if (target) {
486 0 : MOZ_ASSERT(mObserversWithTargets > 0);
487 4 : --mObserversWithTargets;
488 :
489 0 : if (mObserversWithTargets == 0) {
490 0 : MutexAutoLock lock(mMutex);
491 0 : nsCOMPtr<nsIEventTarget> target(SystemGroup::EventTargetFor(TaskCategory::Other));
492 0 : mEventTarget = WrapNotNull(target);
493 : }
494 : }
495 :
496 4 : MOZ_ASSERT(mObserversWithTargets <= ObserverCount());
497 : }
498 :
499 : // Observers can get confused if they don't get all the proper teardown
500 : // notifications. Part ways on good terms.
501 0 : if (removed && !aObserver->NotificationsDeferred()) {
502 0 : EmulateRequestFinished(aObserver);
503 : }
504 :
505 : // Make sure we don't give callbacks to an observer that isn't interested in
506 : // them any more.
507 : AsyncNotifyRunnable* runnable =
508 0 : static_cast<AsyncNotifyRunnable*>(mRunnable.get());
509 :
510 8 : if (aObserver->NotificationsDeferred() && runnable) {
511 0 : runnable->RemoveObserver(aObserver);
512 0 : aObserver->ClearPendingNotify();
513 : }
514 :
515 8 : return removed;
516 : }
517 :
518 : uint32_t
519 0 : ProgressTracker::ObserverCount() const
520 : {
521 136 : MOZ_ASSERT(NS_IsMainThread());
522 136 : return mObservers.Read([](const ObserverTable* aTable) {
523 136 : return aTable->Count();
524 272 : });
525 : }
526 :
527 : void
528 0 : ProgressTracker::OnUnlockedDraw()
529 : {
530 0 : MOZ_ASSERT(NS_IsMainThread());
531 0 : mObservers.Read([](const ObserverTable* aTable) {
532 0 : ImageObserverNotifier<const ObserverTable*> notify(aTable);
533 0 : notify([](IProgressObserver* aObs) {
534 0 : aObs->Notify(imgINotificationObserver::UNLOCKED_DRAW);
535 0 : });
536 0 : });
537 0 : }
538 :
539 : void
540 0 : ProgressTracker::ResetForNewRequest()
541 : {
542 0 : MOZ_ASSERT(NS_IsMainThread());
543 0 : mProgress = NoProgress;
544 0 : }
545 :
546 : void
547 0 : ProgressTracker::OnDiscard()
548 : {
549 0 : MOZ_ASSERT(NS_IsMainThread());
550 0 : mObservers.Read([](const ObserverTable* aTable) {
551 0 : ImageObserverNotifier<const ObserverTable*> notify(aTable);
552 0 : notify([](IProgressObserver* aObs) {
553 0 : aObs->Notify(imgINotificationObserver::DISCARD);
554 0 : });
555 0 : });
556 0 : }
557 :
558 : void
559 0 : ProgressTracker::OnImageAvailable()
560 : {
561 0 : MOZ_ASSERT(NS_IsMainThread());
562 : // Notify any imgRequestProxys that are observing us that we have an Image.
563 32 : mObservers.Read([](const ObserverTable* aTable) {
564 : ImageObserverNotifier<const ObserverTable*>
565 16 : notify(aTable, /* aIgnoreDeferral = */ true);
566 0 : notify([](IProgressObserver* aObs) {
567 0 : aObs->SetHasImage();
568 0 : });
569 0 : });
570 0 : }
571 :
572 : void
573 0 : ProgressTracker::FireFailureNotification()
574 : {
575 0 : MOZ_ASSERT(NS_IsMainThread());
576 :
577 : // Some kind of problem has happened with image decoding.
578 : // Report the URI to net:failed-to-process-uri-conent observers.
579 0 : RefPtr<Image> image = GetImage();
580 0 : if (image) {
581 : // Should be on main thread, so ok to create a new nsIURI.
582 0 : nsCOMPtr<nsIURI> uri = image->GetURI();
583 0 : if (uri) {
584 0 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
585 0 : if (os) {
586 0 : os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
587 : }
588 : }
589 : }
590 0 : }
591 :
592 : } // namespace image
593 : } // namespace mozilla
|