Bumping manifests a=b2g-bump
[gecko.git] / image / src / Decoder.cpp
blob7f5ce8b155f6beb6530d1976c1d94286e4c9fa7d
2 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "Decoder.h"
9 #include "mozilla/gfx/2D.h"
10 #include "DecodePool.h"
11 #include "GeckoProfiler.h"
12 #include "imgIContainer.h"
13 #include "nsIConsoleService.h"
14 #include "nsIScriptError.h"
15 #include "nsProxyRelease.h"
16 #include "nsServiceManagerUtils.h"
17 #include "nsComponentManagerUtils.h"
19 using mozilla::gfx::IntSize;
20 using mozilla::gfx::SurfaceFormat;
22 namespace mozilla {
23 namespace image {
25 Decoder::Decoder(RasterImage* aImage)
26 : mImage(aImage)
27 , mProgress(NoProgress)
28 , mImageData(nullptr)
29 , mColormap(nullptr)
30 , mChunkCount(0)
31 , mFlags(0)
32 , mBytesDecoded(0)
33 , mSendPartialInvalidations(false)
34 , mDataDone(false)
35 , mDecodeDone(false)
36 , mDataError(false)
37 , mDecodeAborted(false)
38 , mShouldReportError(false)
39 , mImageIsTransient(false)
40 , mImageIsLocked(false)
41 , mFrameCount(0)
42 , mFailCode(NS_OK)
43 , mNeedsNewFrame(false)
44 , mNeedsToFlushData(false)
45 , mInitialized(false)
46 , mSizeDecode(false)
47 , mInFrame(false)
48 , mIsAnimated(false)
49 { }
51 Decoder::~Decoder()
53 MOZ_ASSERT(mProgress == NoProgress,
54 "Destroying Decoder without taking all its progress changes");
55 MOZ_ASSERT(mInvalidRect.IsEmpty(),
56 "Destroying Decoder without taking all its invalidations");
57 mInitialized = false;
59 if (!NS_IsMainThread()) {
60 // Dispatch mImage to main thread to prevent it from being destructed by the
61 // decode thread.
62 nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
63 NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
64 if (mainThread) {
65 // Handle ambiguous nsISupports inheritance.
66 RasterImage* rawImg = nullptr;
67 mImage.swap(rawImg);
68 DebugOnly<nsresult> rv =
69 NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
70 MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
76 * Common implementation of the decoder interface.
79 void
80 Decoder::Init()
82 // No re-initializing
83 MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
85 // Fire OnStartDecode at init time to support bug 512435.
86 if (!IsSizeDecode()) {
87 mProgress |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED;
90 // Implementation-specific initialization
91 InitInternal();
93 mInitialized = true;
96 // Initializes a decoder whose image and observer is already being used by a
97 // parent decoder
98 void
99 Decoder::InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength,
100 uint32_t* aColormap, uint32_t aColormapSize,
101 RawAccessFrameRef&& aFrameRef)
103 // No re-initializing
104 NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
106 mImageData = aImageData;
107 mImageDataLength = aImageDataLength;
108 mColormap = aColormap;
109 mColormapSize = aColormapSize;
110 mCurrentFrame = Move(aFrameRef);
112 // We have all the frame data, so we've started the frame.
113 if (!IsSizeDecode()) {
114 mFrameCount++;
115 PostFrameStart();
118 // Implementation-specific initialization
119 InitInternal();
120 mInitialized = true;
123 nsresult
124 Decoder::Decode()
126 MOZ_ASSERT(mInitialized, "Should be initialized here");
127 MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
129 // We keep decoding chunks until the decode completes or there are no more
130 // chunks available.
131 while (!GetDecodeDone() && !HasError()) {
132 auto newState = mIterator->AdvanceOrScheduleResume(this);
134 if (newState == SourceBufferIterator::WAITING) {
135 // We can't continue because the rest of the data hasn't arrived from the
136 // network yet. We don't have to do anything special; the
137 // SourceBufferIterator will ensure that Decode() gets called again on a
138 // DecodePool thread when more data is available.
139 return NS_OK;
142 if (newState == SourceBufferIterator::COMPLETE) {
143 mDataDone = true;
145 nsresult finalStatus = mIterator->CompletionStatus();
146 if (NS_FAILED(finalStatus)) {
147 PostDataError();
150 CompleteDecode();
151 return finalStatus;
154 MOZ_ASSERT(newState == SourceBufferIterator::READY);
156 Write(mIterator->Data(), mIterator->Length());
159 CompleteDecode();
160 return HasError() ? NS_ERROR_FAILURE : NS_OK;
163 void
164 Decoder::Resume()
166 DecodePool* decodePool = DecodePool::Singleton();
167 MOZ_ASSERT(decodePool);
169 nsCOMPtr<nsIEventTarget> target = decodePool->GetEventTarget();
170 if (MOZ_UNLIKELY(!target)) {
171 // We're shutting down and the DecodePool's thread pool has been destroyed.
172 return;
175 nsCOMPtr<nsIRunnable> worker = decodePool->CreateDecodeWorker(this);
176 target->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
179 bool
180 Decoder::ShouldSyncDecode(size_t aByteLimit)
182 MOZ_ASSERT(aByteLimit > 0);
183 MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
185 return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
188 void
189 Decoder::Write(const char* aBuffer, uint32_t aCount)
191 PROFILER_LABEL("ImageDecoder", "Write",
192 js::ProfileEntry::Category::GRAPHICS);
194 // We're strict about decoder errors
195 MOZ_ASSERT(!HasDecoderError(),
196 "Not allowed to make more decoder calls after error!");
198 // Begin recording telemetry data.
199 TimeStamp start = TimeStamp::Now();
200 mChunkCount++;
202 // Keep track of the total number of bytes written.
203 mBytesDecoded += aCount;
205 // If we're flushing data, clear the flag.
206 if (aBuffer == nullptr && aCount == 0) {
207 MOZ_ASSERT(mNeedsToFlushData, "Flushing when we don't need to");
208 mNeedsToFlushData = false;
211 // If a data error occured, just ignore future data.
212 if (HasDataError())
213 return;
215 if (IsSizeDecode() && HasSize()) {
216 // More data came in since we found the size. We have nothing to do here.
217 return;
220 MOZ_ASSERT(!NeedsNewFrame() || HasDataError(),
221 "Should not need a new frame before writing anything");
222 MOZ_ASSERT(!NeedsToFlushData() || HasDataError(),
223 "Should not need to flush data before writing anything");
225 // Pass the data along to the implementation.
226 WriteInternal(aBuffer, aCount);
228 // If we need a new frame to proceed, let's create one and call it again.
229 while (NeedsNewFrame() && !HasDataError()) {
230 MOZ_ASSERT(!IsSizeDecode(), "Shouldn't need new frame for size decode");
232 nsresult rv = AllocateFrame();
234 if (NS_SUCCEEDED(rv)) {
235 // Use the data we saved when we asked for a new frame.
236 WriteInternal(nullptr, 0);
239 mNeedsToFlushData = false;
242 // Finish telemetry.
243 mDecodeTime += (TimeStamp::Now() - start);
246 void
247 Decoder::CompleteDecode()
249 // Implementation-specific finalization
250 if (!HasError())
251 FinishInternal();
253 // If the implementation left us mid-frame, finish that up.
254 if (mInFrame && !HasError())
255 PostFrameStop();
257 // If PostDecodeDone() has not been called, and this decoder wasn't aborted
258 // early because of low-memory conditions or losing a race with another
259 // decoder, we need to send teardown notifications (and report an error to the
260 // console later).
261 if (!IsSizeDecode() && !mDecodeDone && !WasAborted()) {
262 mShouldReportError = true;
264 // If we only have a data error, we're usable if we have at least one
265 // complete frame.
266 if (!HasDecoderError() && GetCompleteFrameCount() > 0) {
267 // We're usable, so do exactly what we should have when the decoder
268 // completed.
270 // Not writing to the entire frame may have left us transparent.
271 PostHasTransparency();
273 if (mInFrame) {
274 PostFrameStop();
276 PostDecodeDone();
277 } else {
278 // We're not usable. Record some final progress indicating the error.
279 if (!IsSizeDecode()) {
280 mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
282 mProgress |= FLAG_HAS_ERROR;
287 void
288 Decoder::Finish()
290 MOZ_ASSERT(NS_IsMainThread());
292 MOZ_ASSERT(HasError() || !mInFrame, "Finishing while we're still in a frame");
294 // If we detected an error in CompleteDecode(), log it to the error console.
295 if (mShouldReportError && !WasAborted()) {
296 nsCOMPtr<nsIConsoleService> consoleService =
297 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
298 nsCOMPtr<nsIScriptError> errorObject =
299 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
301 if (consoleService && errorObject && !HasDecoderError()) {
302 nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated: ") +
303 NS_ConvertUTF8toUTF16(mImage->GetURIString()));
305 if (NS_SUCCEEDED(errorObject->InitWithWindowID(
306 msg,
307 NS_ConvertUTF8toUTF16(mImage->GetURIString()),
308 EmptyString(), 0, 0, nsIScriptError::errorFlag,
309 "Image", mImage->InnerWindowID()
310 ))) {
311 consoleService->LogMessage(errorObject);
316 // Set image metadata before calling DecodingComplete, because
317 // DecodingComplete calls Optimize().
318 mImageMetadata.SetOnImage(mImage);
320 if (HasSize()) {
321 SetSizeOnImage();
324 if (mDecodeDone && !IsSizeDecode()) {
325 MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
327 // If this image wasn't animated and isn't a transient image, mark its frame
328 // as optimizable. We don't support optimizing animated images and
329 // optimizing transient images isn't worth it.
330 if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) {
331 mCurrentFrame->SetOptimizable();
334 mImage->OnDecodingComplete();
338 void
339 Decoder::FinishSharedDecoder()
341 if (!HasError()) {
342 FinishInternal();
346 nsresult
347 Decoder::AllocateFrame(const nsIntSize& aTargetSize /* = nsIntSize() */)
349 MOZ_ASSERT(mNeedsNewFrame);
351 nsIntSize targetSize = aTargetSize;
352 if (targetSize == nsIntSize()) {
353 MOZ_ASSERT(HasSize());
354 targetSize = mImageMetadata.GetSize();
357 mCurrentFrame = EnsureFrame(mNewFrameData.mFrameNum,
358 targetSize,
359 mNewFrameData.mFrameRect,
360 GetDecodeFlags(),
361 mNewFrameData.mFormat,
362 mNewFrameData.mPaletteDepth,
363 mCurrentFrame.get());
365 if (mCurrentFrame) {
366 // Gather the raw pointers the decoders will use.
367 mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
368 mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
370 if (mNewFrameData.mFrameNum + 1 == mFrameCount) {
371 PostFrameStart();
373 } else {
374 PostDataError();
377 // Mark ourselves as not needing another frame before talking to anyone else
378 // so they can tell us if they need yet another.
379 mNeedsNewFrame = false;
381 // If we've received any data at all, we may have pending data that needs to
382 // be flushed now that we have a frame to decode into.
383 if (mBytesDecoded > 0) {
384 mNeedsToFlushData = true;
387 return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
390 RawAccessFrameRef
391 Decoder::EnsureFrame(uint32_t aFrameNum,
392 const nsIntSize& aTargetSize,
393 const nsIntRect& aFrameRect,
394 uint32_t aDecodeFlags,
395 SurfaceFormat aFormat,
396 uint8_t aPaletteDepth,
397 imgFrame* aPreviousFrame)
399 if (mDataError || NS_FAILED(mFailCode)) {
400 return RawAccessFrameRef();
403 MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
404 if (aFrameNum > mFrameCount) {
405 return RawAccessFrameRef();
408 // Adding a frame that doesn't already exist. This is the normal case.
409 if (aFrameNum == mFrameCount) {
410 return InternalAddFrame(aFrameNum, aTargetSize, aFrameRect, aDecodeFlags,
411 aFormat, aPaletteDepth, aPreviousFrame);
414 // We're replacing a frame. It must be the first frame; there's no reason to
415 // ever replace any other frame, since the first frame is the only one we
416 // speculatively allocate without knowing what the decoder really needs.
417 // XXX(seth): I'm not convinced there's any reason to support this at all. We
418 // should figure out how to avoid triggering this and rip it out.
419 MOZ_ASSERT(aFrameNum == 0, "Replacing a frame other than the first?");
420 MOZ_ASSERT(mFrameCount == 1, "Should have only one frame");
421 MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace");
422 if (aFrameNum != 0 || !aPreviousFrame || mFrameCount != 1) {
423 return RawAccessFrameRef();
426 MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
427 aPreviousFrame->GetFormat() != aFormat ||
428 aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
429 "Replacing first frame with the same kind of frame?");
431 // Reset our state.
432 mInFrame = false;
433 RawAccessFrameRef ref = Move(mCurrentFrame);
435 MOZ_ASSERT(ref, "No ref to current frame?");
437 // Reinitialize the old frame.
438 nsIntSize oldSize = ThebesIntSize(aPreviousFrame->GetImageSize());
439 bool nonPremult =
440 aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
441 if (NS_FAILED(aPreviousFrame->ReinitForDecoder(oldSize, aFrameRect, aFormat,
442 aPaletteDepth, nonPremult))) {
443 NS_WARNING("imgFrame::ReinitForDecoder should succeed");
444 mFrameCount = 0;
445 aPreviousFrame->Abort();
446 return RawAccessFrameRef();
449 return ref;
452 RawAccessFrameRef
453 Decoder::InternalAddFrame(uint32_t aFrameNum,
454 const nsIntSize& aTargetSize,
455 const nsIntRect& aFrameRect,
456 uint32_t aDecodeFlags,
457 SurfaceFormat aFormat,
458 uint8_t aPaletteDepth,
459 imgFrame* aPreviousFrame)
461 MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
462 if (aFrameNum > mFrameCount) {
463 return RawAccessFrameRef();
466 if (aTargetSize.width <= 0 || aTargetSize.height <= 0 ||
467 aFrameRect.width <= 0 || aFrameRect.height <= 0) {
468 NS_WARNING("Trying to add frame with zero or negative size");
469 return RawAccessFrameRef();
472 if (!SurfaceCache::CanHold(aTargetSize.ToIntSize())) {
473 NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
474 return RawAccessFrameRef();
477 nsRefPtr<imgFrame> frame = new imgFrame();
478 bool nonPremult =
479 aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
480 if (NS_FAILED(frame->InitForDecoder(aTargetSize, aFrameRect, aFormat,
481 aPaletteDepth, nonPremult))) {
482 NS_WARNING("imgFrame::Init should succeed");
483 return RawAccessFrameRef();
486 RawAccessFrameRef ref = frame->RawAccessRef();
487 if (!ref) {
488 frame->Abort();
489 return RawAccessFrameRef();
492 InsertOutcome outcome =
493 SurfaceCache::Insert(frame, ImageKey(mImage.get()),
494 RasterSurfaceKey(aTargetSize.ToIntSize(),
495 aDecodeFlags,
496 aFrameNum),
497 Lifetime::Persistent);
498 if (outcome != InsertOutcome::SUCCESS) {
499 // We either hit InsertOutcome::FAILURE, which is a temporary failure due to
500 // low memory (we know it's not permanent because we checked CanHold()
501 // above), or InsertOutcome::FAILURE_ALREADY_PRESENT, which means that
502 // another decoder beat us to decoding this frame. Either way, we should
503 // abort this decoder rather than treat this as a real error.
504 mDecodeAborted = true;
505 ref->Abort();
506 return RawAccessFrameRef();
509 nsIntRect refreshArea;
511 if (aFrameNum == 1) {
512 MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
513 aPreviousFrame->SetRawAccessOnly();
515 // If we dispose of the first frame by clearing it, then the first frame's
516 // refresh area is all of itself.
517 // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
518 AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
519 if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
520 previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
521 previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
522 refreshArea = previousFrameData.mRect;
526 if (aFrameNum > 0) {
527 ref->SetRawAccessOnly();
529 // Some GIFs are huge but only have a small area that they animate. We only
530 // need to refresh that small area when frame 0 comes around again.
531 refreshArea.UnionRect(refreshArea, frame->GetRect());
534 mFrameCount++;
535 mImage->OnAddedFrame(mFrameCount, refreshArea);
537 return ref;
540 void
541 Decoder::SetSizeOnImage()
543 MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size");
544 MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation");
546 nsresult rv = mImage->SetSize(mImageMetadata.GetWidth(),
547 mImageMetadata.GetHeight(),
548 mImageMetadata.GetOrientation());
549 if (NS_FAILED(rv)) {
550 PostResizeError();
555 * Hook stubs. Override these as necessary in decoder implementations.
558 void Decoder::InitInternal() { }
559 void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount) { }
560 void Decoder::FinishInternal() { }
563 * Progress Notifications
566 void
567 Decoder::PostSize(int32_t aWidth,
568 int32_t aHeight,
569 Orientation aOrientation /* = Orientation()*/)
571 // Validate
572 NS_ABORT_IF_FALSE(aWidth >= 0, "Width can't be negative!");
573 NS_ABORT_IF_FALSE(aHeight >= 0, "Height can't be negative!");
575 // Tell the image
576 mImageMetadata.SetSize(aWidth, aHeight, aOrientation);
578 // Record this notification.
579 mProgress |= FLAG_SIZE_AVAILABLE;
582 void
583 Decoder::PostHasTransparency()
585 mProgress |= FLAG_HAS_TRANSPARENCY;
588 void
589 Decoder::PostFrameStart()
591 // We shouldn't already be mid-frame
592 NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!");
594 // Update our state to reflect the new frame
595 mInFrame = true;
597 // If we just became animated, record that fact.
598 if (mFrameCount > 1) {
599 mIsAnimated = true;
600 mProgress |= FLAG_IS_ANIMATED;
604 void
605 Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
606 DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
607 int32_t aTimeout /* = 0 */,
608 BlendMethod aBlendMethod /* = BlendMethod::OVER */)
610 // We should be mid-frame
611 MOZ_ASSERT(!IsSizeDecode(), "Stopping frame during a size decode");
612 MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
613 MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
615 // Update our state
616 mInFrame = false;
618 mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
620 mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
622 // If we're not sending partial invalidations, then we send an invalidation
623 // here when the first frame is complete.
624 if (!mSendPartialInvalidations && !mIsAnimated) {
625 mInvalidRect.UnionRect(mInvalidRect,
626 nsIntRect(nsIntPoint(0, 0), GetSize()));
630 void
631 Decoder::PostInvalidation(const nsIntRect& aRect,
632 const Maybe<nsIntRect>& aRectAtTargetSize
633 /* = Nothing() */)
635 // We should be mid-frame
636 NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!");
637 NS_ABORT_IF_FALSE(mCurrentFrame, "Can't invalidate when not mid-frame!");
639 // Record this invalidation, unless we're not sending partial invalidations
640 // or we're past the first frame.
641 if (mSendPartialInvalidations && !mIsAnimated) {
642 mInvalidRect.UnionRect(mInvalidRect, aRect);
643 mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect));
647 void
648 Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
650 NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!");
651 NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!");
652 NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
653 mDecodeDone = true;
655 mImageMetadata.SetLoopCount(aLoopCount);
657 mProgress |= FLAG_DECODE_COMPLETE;
660 void
661 Decoder::PostDataError()
663 mDataError = true;
665 if (mInFrame && mCurrentFrame) {
666 mCurrentFrame->Abort();
670 void
671 Decoder::PostDecoderError(nsresult aFailureCode)
673 NS_ABORT_IF_FALSE(NS_FAILED(aFailureCode), "Not a failure code!");
675 mFailCode = aFailureCode;
677 // XXXbholley - we should report the image URI here, but imgContainer
678 // needs to know its URI first
679 NS_WARNING("Image decoding error - This is probably a bug!");
681 if (mInFrame && mCurrentFrame) {
682 mCurrentFrame->Abort();
686 void
687 Decoder::NeedNewFrame(uint32_t framenum, uint32_t x_offset, uint32_t y_offset,
688 uint32_t width, uint32_t height,
689 gfx::SurfaceFormat format,
690 uint8_t palette_depth /* = 0 */)
692 // Decoders should never call NeedNewFrame without yielding back to Write().
693 MOZ_ASSERT(!mNeedsNewFrame);
695 // We don't want images going back in time or skipping frames.
696 MOZ_ASSERT(framenum == mFrameCount || framenum == (mFrameCount - 1));
698 mNewFrameData = NewFrameData(framenum,
699 nsIntRect(x_offset, y_offset, width, height),
700 format, palette_depth);
701 mNeedsNewFrame = true;
704 } // namespace image
705 } // namespace mozilla