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/. */
7 #include "RenderD3D11TextureHost.h"
11 #include "GLContextEGL.h"
12 #include "GLLibraryEGL.h"
13 #include "ScopedGLHelpers.h"
14 #include "mozilla/DebugOnly.h"
15 #include "mozilla/gfx/Logging.h"
20 RenderDXGITextureHost::RenderDXGITextureHost(WindowsHandle aHandle
,
22 gfx::SurfaceFormat aFormat
,
23 gfx::YUVColorSpace aYUVColorSpace
,
24 gfx::ColorRange aColorRange
,
27 mArrayIndex(aArrayIndex
),
32 mYUVColorSpace(aYUVColorSpace
),
33 mColorRange(aColorRange
),
36 MOZ_COUNT_CTOR_INHERITED(RenderDXGITextureHost
, RenderTextureHost
);
37 MOZ_ASSERT((mFormat
!= gfx::SurfaceFormat::NV12
&&
38 mFormat
!= gfx::SurfaceFormat::P010
&&
39 mFormat
!= gfx::SurfaceFormat::P016
) ||
40 (mSize
.width
% 2 == 0 && mSize
.height
% 2 == 0));
44 RenderDXGITextureHost::~RenderDXGITextureHost() {
45 MOZ_COUNT_DTOR_INHERITED(RenderDXGITextureHost
, RenderTextureHost
);
46 DeleteTextureHandle();
49 ID3D11Texture2D
* RenderDXGITextureHost::GetD3D11Texture2DWithGL() {
55 // SingletonGL is always used on Windows with ANGLE.
56 mGL
= RenderThread::Get()->SingletonGL();
59 if (!EnsureD3D11Texture2DWithGL()) {
66 size_t RenderDXGITextureHost::GetPlaneCount() const {
67 if (mFormat
== gfx::SurfaceFormat::NV12
||
68 mFormat
== gfx::SurfaceFormat::P010
||
69 mFormat
== gfx::SurfaceFormat::P016
) {
76 static bool MapTexture(T
* aHost
, RenderCompositor
* aCompositor
,
77 RefPtr
<ID3D11Texture2D
>& aTexture
,
78 RefPtr
<ID3D11DeviceContext
>& aDeviceContext
,
79 RefPtr
<ID3D11Texture2D
>& aCpuTexture
,
80 D3D11_MAPPED_SUBRESOURCE
& aMappedSubresource
) {
85 RenderCompositorD3D11SWGL
* compositor
=
86 aCompositor
->AsRenderCompositorD3D11SWGL();
91 if (!aHost
->EnsureD3D11Texture2D(compositor
->GetDevice())) {
95 if (!aHost
->LockInternal()) {
99 D3D11_TEXTURE2D_DESC textureDesc
= {0};
100 aTexture
->GetDesc(&textureDesc
);
102 compositor
->GetDevice()->GetImmediateContext(getter_AddRefs(aDeviceContext
));
104 textureDesc
.CPUAccessFlags
= D3D11_CPU_ACCESS_READ
;
105 textureDesc
.Usage
= D3D11_USAGE_STAGING
;
106 textureDesc
.BindFlags
= 0;
107 textureDesc
.MiscFlags
= 0;
108 textureDesc
.MipLevels
= 1;
109 HRESULT hr
= compositor
->GetDevice()->CreateTexture2D(
110 &textureDesc
, nullptr, getter_AddRefs(aCpuTexture
));
115 aDeviceContext
->CopyResource(aCpuTexture
, aTexture
);
118 hr
= aDeviceContext
->Map(aCpuTexture
, 0, D3D11_MAP_READ
, 0,
119 &aMappedSubresource
);
120 return SUCCEEDED(hr
);
123 bool RenderDXGITextureHost::MapPlane(RenderCompositor
* aCompositor
,
124 uint8_t aChannelIndex
,
125 PlaneInfo
& aPlaneInfo
) {
126 // TODO: We currently readback from the GPU texture into a new
127 // staging texture every time this is mapped. We might be better
128 // off retaining the mapped memory to trade performance for memory
130 if (!mCpuTexture
&& !MapTexture(this, aCompositor
, mTexture
, mDeviceContext
,
131 mCpuTexture
, mMappedSubresource
)) {
135 aPlaneInfo
.mSize
= GetSize(aChannelIndex
);
136 aPlaneInfo
.mStride
= mMappedSubresource
.RowPitch
;
137 aPlaneInfo
.mData
= mMappedSubresource
.pData
;
139 // If this is the second plane, then offset the data pointer by the
140 // size of the first plane.
141 if (aChannelIndex
== 1) {
143 (uint8_t*)aPlaneInfo
.mData
+ aPlaneInfo
.mStride
* GetSize(0).height
;
148 void RenderDXGITextureHost::UnmapPlanes() {
149 mMappedSubresource
.pData
= nullptr;
151 mDeviceContext
->Unmap(mCpuTexture
, 0);
152 mCpuTexture
= nullptr;
154 mDeviceContext
= nullptr;
157 bool RenderDXGITextureHost::EnsureD3D11Texture2DWithGL() {
162 const auto& gle
= gl::GLContextEGL::Cast(mGL
);
163 const auto& egl
= gle
->mEgl
;
165 // Fetch the D3D11 device.
166 EGLDeviceEXT eglDevice
= nullptr;
167 egl
->fQueryDisplayAttribEXT(LOCAL_EGL_DEVICE_EXT
, (EGLAttrib
*)&eglDevice
);
168 MOZ_ASSERT(eglDevice
);
169 ID3D11Device
* device
= nullptr;
170 egl
->mLib
->fQueryDeviceAttribEXT(eglDevice
, LOCAL_EGL_D3D11_DEVICE_ANGLE
,
171 (EGLAttrib
*)&device
);
172 // There's a chance this might fail if we end up on d3d9 angle for some
175 gfxCriticalNote
<< "RenderDXGITextureHost device is not available";
179 return EnsureD3D11Texture2D(device
);
182 bool RenderDXGITextureHost::EnsureD3D11Texture2D(ID3D11Device
* aDevice
) {
184 RefPtr
<ID3D11Device
> device
;
185 mTexture
->GetDevice(getter_AddRefs(device
));
186 if (aDevice
!= device
) {
187 gfxCriticalNote
<< "RenderDXGITextureHost uses obsoleted device";
193 // Get the D3D11 texture from shared handle.
194 HRESULT hr
= aDevice
->OpenSharedResource(
195 (HANDLE
)mHandle
, __uuidof(ID3D11Texture2D
),
196 (void**)(ID3D11Texture2D
**)getter_AddRefs(mTexture
));
199 "RenderDXGITextureHost::EnsureLockable(): Failed to open shared "
202 << "RenderDXGITextureHost Failed to open shared texture, hr="
206 MOZ_ASSERT(mTexture
.get());
207 mTexture
->QueryInterface((IDXGIKeyedMutex
**)getter_AddRefs(mKeyedMutex
));
211 bool RenderDXGITextureHost::EnsureLockable(wr::ImageRendering aRendering
) {
212 if (mTextureHandle
[0]) {
213 // Update filter if filter was changed.
214 if (IsFilterUpdateNecessary(aRendering
)) {
215 ActivateBindAndTexParameteri(mGL
, LOCAL_GL_TEXTURE0
,
216 LOCAL_GL_TEXTURE_EXTERNAL_OES
,
217 mTextureHandle
[0], aRendering
);
218 // Cache new rendering filter.
219 mCachedRendering
= aRendering
;
220 // NV12 and P016 uses two handles.
221 if (mFormat
== gfx::SurfaceFormat::NV12
||
222 mFormat
== gfx::SurfaceFormat::P010
||
223 mFormat
== gfx::SurfaceFormat::P016
) {
224 ActivateBindAndTexParameteri(mGL
, LOCAL_GL_TEXTURE1
,
225 LOCAL_GL_TEXTURE_EXTERNAL_OES
,
226 mTextureHandle
[1], aRendering
);
232 const auto& gle
= gl::GLContextEGL::Cast(mGL
);
233 const auto& egl
= gle
->mEgl
;
235 // We use EGLStream to get the converted gl handle from d3d texture. The
236 // NV_stream_consumer_gltexture_yuv and ANGLE_stream_producer_d3d_texture
237 // could support nv12 and rgb d3d texture format.
238 if (!egl
->IsExtensionSupported(
239 gl::EGLExtension::NV_stream_consumer_gltexture_yuv
) ||
240 !egl
->IsExtensionSupported(
241 gl::EGLExtension::ANGLE_stream_producer_d3d_texture
)) {
242 gfxCriticalNote
<< "RenderDXGITextureHost egl extensions are not suppored";
246 // Get the D3D11 texture from shared handle.
247 if (!EnsureD3D11Texture2DWithGL()) {
251 // Create the EGLStream.
252 mStream
= egl
->fCreateStreamKHR(nullptr);
256 if (mFormat
!= gfx::SurfaceFormat::NV12
&&
257 mFormat
!= gfx::SurfaceFormat::P010
&&
258 mFormat
!= gfx::SurfaceFormat::P016
) {
259 // The non-nv12 format.
261 mGL
->fGenTextures(1, mTextureHandle
);
262 ActivateBindAndTexParameteri(mGL
, LOCAL_GL_TEXTURE0
,
263 LOCAL_GL_TEXTURE_EXTERNAL_OES
,
264 mTextureHandle
[0], aRendering
);
265 // Cache new rendering filter.
266 mCachedRendering
= aRendering
;
268 bool(egl
->fStreamConsumerGLTextureExternalAttribsNV(mStream
, nullptr));
269 ok
&= bool(egl
->fCreateStreamProducerD3DTextureANGLE(mStream
, nullptr));
271 // The nv12/p016 format.
273 // Setup the NV12 stream consumer/producer.
274 EGLAttrib consumerAttributes
[] = {
275 LOCAL_EGL_COLOR_BUFFER_TYPE
,
276 LOCAL_EGL_YUV_BUFFER_EXT
,
277 LOCAL_EGL_YUV_NUMBER_OF_PLANES_EXT
,
279 LOCAL_EGL_YUV_PLANE0_TEXTURE_UNIT_NV
,
281 LOCAL_EGL_YUV_PLANE1_TEXTURE_UNIT_NV
,
285 mGL
->fGenTextures(2, mTextureHandle
);
286 ActivateBindAndTexParameteri(mGL
, LOCAL_GL_TEXTURE0
,
287 LOCAL_GL_TEXTURE_EXTERNAL_OES
,
288 mTextureHandle
[0], aRendering
);
289 ActivateBindAndTexParameteri(mGL
, LOCAL_GL_TEXTURE1
,
290 LOCAL_GL_TEXTURE_EXTERNAL_OES
,
291 mTextureHandle
[1], aRendering
);
292 // Cache new rendering filter.
293 mCachedRendering
= aRendering
;
294 ok
&= bool(egl
->fStreamConsumerGLTextureExternalAttribsNV(
295 mStream
, consumerAttributes
));
296 ok
&= bool(egl
->fCreateStreamProducerD3DTextureANGLE(mStream
, nullptr));
299 const EGLAttrib frameAttributes
[] = {
300 LOCAL_EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE
,
301 static_cast<EGLAttrib
>(mArrayIndex
),
305 // Insert the d3d texture.
306 ok
&= bool(egl
->fStreamPostD3DTextureANGLE(mStream
, (void*)mTexture
.get(),
310 gfxCriticalNote
<< "RenderDXGITextureHost init stream failed";
311 DeleteTextureHandle();
315 // Now, we could get the gl handle from the stream.
316 MOZ_ALWAYS_TRUE(egl
->fStreamConsumerAcquireKHR(mStream
));
321 wr::WrExternalImage
RenderDXGITextureHost::Lock(uint8_t aChannelIndex
,
323 wr::ImageRendering aRendering
) {
324 if (mGL
.get() != aGL
) {
325 // Release the texture handle in the previous gl context.
326 DeleteTextureHandle();
331 // XXX Software WebRender is not handled yet.
332 // Software WebRender does not provide GLContext
334 << "Software WebRender is not suppored by RenderDXGITextureHost.";
335 return InvalidToWrExternalImage();
338 if (!EnsureLockable(aRendering
)) {
339 return InvalidToWrExternalImage();
342 if (!LockInternal()) {
343 return InvalidToWrExternalImage();
346 gfx::IntSize size
= GetSize(aChannelIndex
);
347 return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex
), 0, 0,
348 size
.width
, size
.height
);
351 bool RenderDXGITextureHost::LockInternal() {
354 HRESULT hr
= mKeyedMutex
->AcquireSync(0, 10000);
356 gfxCriticalError() << "RenderDXGITextureHost AcquireSync timeout, hr="
366 void RenderDXGITextureHost::Unlock() {
369 mKeyedMutex
->ReleaseSync(0);
375 void RenderDXGITextureHost::ClearCachedResources() {
376 DeleteTextureHandle();
380 void RenderDXGITextureHost::DeleteTextureHandle() {
381 if (mTextureHandle
[0] == 0) {
385 MOZ_ASSERT(mGL
.get());
390 if (mGL
->MakeCurrent()) {
391 mGL
->fDeleteTextures(2, mTextureHandle
);
393 const auto& gle
= gl::GLContextEGL::Cast(mGL
);
394 const auto& egl
= gle
->mEgl
;
396 egl
->fDestroySurface(mSurface
);
399 egl
->fDestroyStreamKHR(mStream
);
403 for (int i
= 0; i
< 2; ++i
) {
404 mTextureHandle
[i
] = 0;
408 mKeyedMutex
= nullptr;
413 GLuint
RenderDXGITextureHost::GetGLHandle(uint8_t aChannelIndex
) const {
414 MOZ_ASSERT(((mFormat
== gfx::SurfaceFormat::NV12
||
415 mFormat
== gfx::SurfaceFormat::P010
||
416 mFormat
== gfx::SurfaceFormat::P016
) &&
417 aChannelIndex
< 2) ||
419 return mTextureHandle
[aChannelIndex
];
422 gfx::IntSize
RenderDXGITextureHost::GetSize(uint8_t aChannelIndex
) const {
423 MOZ_ASSERT(((mFormat
== gfx::SurfaceFormat::NV12
||
424 mFormat
== gfx::SurfaceFormat::P010
||
425 mFormat
== gfx::SurfaceFormat::P016
) &&
426 aChannelIndex
< 2) ||
429 if (aChannelIndex
== 0) {
432 // The CbCr channel size is a half of Y channel size in NV12 format.
437 RenderDXGIYCbCrTextureHost::RenderDXGIYCbCrTextureHost(
438 WindowsHandle (&aHandles
)[3], gfx::YUVColorSpace aYUVColorSpace
,
439 gfx::ColorDepth aColorDepth
, gfx::ColorRange aColorRange
,
440 gfx::IntSize aSizeY
, gfx::IntSize aSizeCbCr
)
441 : mHandles
{aHandles
[0], aHandles
[1], aHandles
[2]},
445 mYUVColorSpace(aYUVColorSpace
),
446 mColorDepth(aColorDepth
),
447 mColorRange(aColorRange
),
449 mSizeCbCr(aSizeCbCr
),
451 MOZ_COUNT_CTOR_INHERITED(RenderDXGIYCbCrTextureHost
, RenderTextureHost
);
452 // Assume the chroma planes are rounded up if the luma plane is odd sized.
453 MOZ_ASSERT((mSizeCbCr
.width
== mSizeY
.width
||
454 mSizeCbCr
.width
== (mSizeY
.width
+ 1) >> 1) &&
455 (mSizeCbCr
.height
== mSizeY
.height
||
456 mSizeCbCr
.height
== (mSizeY
.height
+ 1) >> 1));
457 MOZ_ASSERT(aHandles
[0] && aHandles
[1] && aHandles
[2]);
460 bool RenderDXGIYCbCrTextureHost::MapPlane(RenderCompositor
* aCompositor
,
461 uint8_t aChannelIndex
,
462 PlaneInfo
& aPlaneInfo
) {
463 D3D11_MAPPED_SUBRESOURCE mappedSubresource
;
464 if (!MapTexture(this, aCompositor
, mTextures
[aChannelIndex
], mDeviceContext
,
465 mCpuTexture
[aChannelIndex
], mappedSubresource
)) {
469 aPlaneInfo
.mSize
= GetSize(aChannelIndex
);
470 aPlaneInfo
.mStride
= mappedSubresource
.RowPitch
;
471 aPlaneInfo
.mData
= mappedSubresource
.pData
;
475 void RenderDXGIYCbCrTextureHost::UnmapPlanes() {
476 for (uint32_t i
= 0; i
< 3; i
++) {
477 if (mCpuTexture
[i
]) {
478 mDeviceContext
->Unmap(mCpuTexture
[i
], 0);
479 mCpuTexture
[i
] = nullptr;
482 mDeviceContext
= nullptr;
485 RenderDXGIYCbCrTextureHost::~RenderDXGIYCbCrTextureHost() {
486 MOZ_COUNT_DTOR_INHERITED(RenderDXGIYCbCrTextureHost
, RenderTextureHost
);
487 DeleteTextureHandle();
490 bool RenderDXGIYCbCrTextureHost::EnsureLockable(wr::ImageRendering aRendering
) {
491 if (mTextureHandles
[0]) {
492 // Update filter if filter was changed.
493 if (IsFilterUpdateNecessary(aRendering
)) {
494 for (int i
= 0; i
< 3; ++i
) {
495 ActivateBindAndTexParameteri(mGL
, LOCAL_GL_TEXTURE0
+ i
,
496 LOCAL_GL_TEXTURE_EXTERNAL_OES
,
497 mTextureHandles
[i
], aRendering
);
498 // Cache new rendering filter.
499 mCachedRendering
= aRendering
;
505 const auto& gle
= gl::GLContextEGL::Cast(mGL
);
506 const auto& egl
= gle
->mEgl
;
508 // The eglCreatePbufferFromClientBuffer doesn't support R8 format, so we
509 // use EGLStream to get the converted gl handle from d3d R8 texture.
511 if (!egl
->IsExtensionSupported(
512 gl::EGLExtension::NV_stream_consumer_gltexture_yuv
) ||
513 !egl
->IsExtensionSupported(
514 gl::EGLExtension::ANGLE_stream_producer_d3d_texture
)) {
516 << "RenderDXGIYCbCrTextureHost egl extensions are not suppored";
520 // Fetch the D3D11 device.
521 EGLDeviceEXT eglDevice
= nullptr;
522 egl
->fQueryDisplayAttribEXT(LOCAL_EGL_DEVICE_EXT
, (EGLAttrib
*)&eglDevice
);
523 MOZ_ASSERT(eglDevice
);
524 ID3D11Device
* device
= nullptr;
525 egl
->mLib
->fQueryDeviceAttribEXT(eglDevice
, LOCAL_EGL_D3D11_DEVICE_ANGLE
,
526 (EGLAttrib
*)&device
);
527 // There's a chance this might fail if we end up on d3d9 angle for some
530 gfxCriticalNote
<< "RenderDXGIYCbCrTextureHost device is not available";
534 EnsureD3D11Texture2D(device
);
536 mGL
->fGenTextures(3, mTextureHandles
);
538 for (int i
= 0; i
< 3; ++i
) {
539 ActivateBindAndTexParameteri(mGL
, LOCAL_GL_TEXTURE0
+ i
,
540 LOCAL_GL_TEXTURE_EXTERNAL_OES
,
541 mTextureHandles
[i
], aRendering
);
542 // Cache new rendering filter.
543 mCachedRendering
= aRendering
;
545 // Create the EGLStream.
546 mStreams
[i
] = egl
->fCreateStreamKHR(nullptr);
547 MOZ_ASSERT(mStreams
[i
]);
550 egl
->fStreamConsumerGLTextureExternalAttribsNV(mStreams
[i
], nullptr));
551 ok
&= bool(egl
->fCreateStreamProducerD3DTextureANGLE(mStreams
[i
], nullptr));
553 // Insert the R8 texture.
554 ok
&= bool(egl
->fStreamPostD3DTextureANGLE(
555 mStreams
[i
], (void*)mTextures
[i
].get(), nullptr));
557 // Now, we could get the R8 gl handle from the stream.
558 MOZ_ALWAYS_TRUE(egl
->fStreamConsumerAcquireKHR(mStreams
[i
]));
562 gfxCriticalNote
<< "RenderDXGIYCbCrTextureHost init stream failed";
563 DeleteTextureHandle();
570 bool RenderDXGIYCbCrTextureHost::EnsureD3D11Texture2D(ID3D11Device
* aDevice
) {
572 RefPtr
<ID3D11Device
> device
;
573 mTextures
[0]->GetDevice(getter_AddRefs(device
));
574 if (aDevice
!= device
) {
575 gfxCriticalNote
<< "RenderDXGIYCbCrTextureHost uses obsoleted device";
580 if (mTextureHandles
[0]) {
584 for (int i
= 0; i
< 3; ++i
) {
585 // Get the R8 D3D11 texture from shared handle.
586 HRESULT hr
= aDevice
->OpenSharedResource(
587 (HANDLE
)mHandles
[i
], __uuidof(ID3D11Texture2D
),
588 (void**)(ID3D11Texture2D
**)getter_AddRefs(mTextures
[i
]));
591 "RenderDXGIYCbCrTextureHost::EnsureLockable(): Failed to open "
595 << "RenderDXGIYCbCrTextureHost Failed to open shared texture, hr="
601 for (int i
= 0; i
< 3; ++i
) {
602 mTextures
[i
]->QueryInterface(
603 (IDXGIKeyedMutex
**)getter_AddRefs(mKeyedMutexs
[i
]));
608 bool RenderDXGIYCbCrTextureHost::LockInternal() {
610 if (mKeyedMutexs
[0]) {
611 for (const auto& mutex
: mKeyedMutexs
) {
612 HRESULT hr
= mutex
->AcquireSync(0, 10000);
615 << "RenderDXGIYCbCrTextureHost AcquireSync timeout, hr="
626 wr::WrExternalImage
RenderDXGIYCbCrTextureHost::Lock(
627 uint8_t aChannelIndex
, gl::GLContext
* aGL
, wr::ImageRendering aRendering
) {
628 if (mGL
.get() != aGL
) {
629 // Release the texture handle in the previous gl context.
630 DeleteTextureHandle();
635 // XXX Software WebRender is not handled yet.
636 // Software WebRender does not provide GLContext
637 gfxCriticalNoteOnce
<< "Software WebRender is not suppored by "
638 "RenderDXGIYCbCrTextureHost.";
639 return InvalidToWrExternalImage();
642 if (!EnsureLockable(aRendering
)) {
643 return InvalidToWrExternalImage();
646 if (!LockInternal()) {
647 return InvalidToWrExternalImage();
650 gfx::IntSize size
= GetSize(aChannelIndex
);
651 return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex
), 0, 0,
652 size
.width
, size
.height
);
655 void RenderDXGIYCbCrTextureHost::Unlock() {
657 if (mKeyedMutexs
[0]) {
658 for (const auto& mutex
: mKeyedMutexs
) {
659 mutex
->ReleaseSync(0);
666 void RenderDXGIYCbCrTextureHost::ClearCachedResources() {
667 DeleteTextureHandle();
671 GLuint
RenderDXGIYCbCrTextureHost::GetGLHandle(uint8_t aChannelIndex
) const {
672 MOZ_ASSERT(aChannelIndex
< 3);
674 return mTextureHandles
[aChannelIndex
];
677 gfx::IntSize
RenderDXGIYCbCrTextureHost::GetSize(uint8_t aChannelIndex
) const {
678 MOZ_ASSERT(aChannelIndex
< 3);
680 if (aChannelIndex
== 0) {
687 void RenderDXGIYCbCrTextureHost::DeleteTextureHandle() {
688 if (mTextureHandles
[0] == 0) {
692 MOZ_ASSERT(mGL
.get());
697 if (mGL
->MakeCurrent()) {
698 mGL
->fDeleteTextures(3, mTextureHandles
);
700 const auto& gle
= gl::GLContextEGL::Cast(mGL
);
701 const auto& egl
= gle
->mEgl
;
702 for (int i
= 0; i
< 3; ++i
) {
703 mTextureHandles
[i
] = 0;
704 mTextures
[i
] = nullptr;
705 mKeyedMutexs
[i
] = nullptr;
708 egl
->fDestroySurface(mSurfaces
[i
]);
712 egl
->fDestroyStreamKHR(mStreams
[i
]);
720 } // namespace mozilla