diff options
Diffstat (limited to '')
-rw-r--r-- | widget/windows/DirectManipulationOwner.cpp | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/widget/windows/DirectManipulationOwner.cpp b/widget/windows/DirectManipulationOwner.cpp new file mode 100644 index 0000000000..3bda193d3a --- /dev/null +++ b/widget/windows/DirectManipulationOwner.cpp @@ -0,0 +1,732 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DirectManipulationOwner.h" +#include "nsWindow.h" +#include "WinModifierKeyState.h" +#include "InputData.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/SwipeTracker.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/VsyncDispatcher.h" + +// Direct Manipulation is only defined for Win8 and newer. +#if defined(_WIN32_WINNT) +# undef _WIN32_WINNT +# define _WIN32_WINNT _WIN32_WINNT_WIN8 +#endif // defined(_WIN32_WINNT) +#if defined(NTDDI_VERSION) +# undef NTDDI_VERSION +# define NTDDI_VERSION NTDDI_WIN8 +#endif // defined(NTDDI_VERSION) + +#include "directmanipulation.h" + +namespace mozilla { +namespace widget { + +class DManipEventHandler : public IDirectManipulationViewportEventHandler, + public IDirectManipulationInteractionEventHandler { + public: + typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; + + friend class DirectManipulationOwner; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler) + + STDMETHODIMP QueryInterface(REFIID, void**) override; + + friend class DirectManipulationOwner; + + explicit DManipEventHandler(nsWindow* aWindow, + DirectManipulationOwner* aOwner, + const LayoutDeviceIntRect& aBounds); + + HRESULT STDMETHODCALLTYPE OnViewportStatusChanged( + IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current, + DIRECTMANIPULATION_STATUS previous) override; + + HRESULT STDMETHODCALLTYPE + OnViewportUpdated(IDirectManipulationViewport* viewport) override; + + HRESULT STDMETHODCALLTYPE + OnContentUpdated(IDirectManipulationViewport* viewport, + IDirectManipulationContent* content) override; + + HRESULT STDMETHODCALLTYPE + OnInteraction(IDirectManipulationViewport2* viewport, + DIRECTMANIPULATION_INTERACTION_TYPE interaction) override; + + void Update(); + + class VObserver final : public mozilla::VsyncObserver { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DManipEventHandler::VObserver, + override) + + public: + void NotifyVsync(const mozilla::VsyncEvent& aVsync) override { + if (mOwner) { + mOwner->Update(); + } + } + explicit VObserver(DManipEventHandler* aOwner) : mOwner(aOwner) {} + + void ClearOwner() { mOwner = nullptr; } + + private: + virtual ~VObserver() {} + DManipEventHandler* mOwner; + }; + + enum class State { eNone, ePanning, eInertia, ePinching }; + void TransitionToState(State aNewState); + + enum class Phase { eStart, eMiddle, eEnd }; + // Return value indicates if we sent an event or not and hence if we should + // update mLastScale. (We only want to send pinch events if the computed + // deltaY for the corresponding WidgetWheelEvent would be non-zero.) + bool SendPinch(Phase aPhase, float aScale); + void SendPan(Phase aPhase, float x, float y, bool aIsInertia); + static void SendPanCommon(nsWindow* aWindow, Phase aPhase, + ScreenPoint aPosition, double aDeltaX, + double aDeltaY, Modifiers aMods, bool aIsInertia); + + static void SynthesizeNativeTouchpadPan( + nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase, + LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY, + int32_t aModifierFlags); + + private: + virtual ~DManipEventHandler() = default; + + nsWindow* mWindow; + DirectManipulationOwner* mOwner; + RefPtr<VObserver> mObserver; + float mLastScale; + float mLastXOffset; + float mLastYOffset; + LayoutDeviceIntRect mBounds; + bool mShouldSendPanStart; + bool mShouldSendPinchStart; + State mState = State::eNone; +}; + +DManipEventHandler::DManipEventHandler(nsWindow* aWindow, + DirectManipulationOwner* aOwner, + const LayoutDeviceIntRect& aBounds) + : mWindow(aWindow), + mOwner(aOwner), + mLastScale(1.f), + mLastXOffset(0.f), + mLastYOffset(0.f), + mBounds(aBounds), + mShouldSendPanStart(false), + mShouldSendPinchStart(false) {} + +STDMETHODIMP +DManipEventHandler::QueryInterface(REFIID iid, void** ppv) { + const IID IID_IDirectManipulationViewportEventHandler = + __uuidof(IDirectManipulationViewportEventHandler); + const IID IID_IDirectManipulationInteractionEventHandler = + __uuidof(IDirectManipulationInteractionEventHandler); + + if ((IID_IUnknown == iid) || + (IID_IDirectManipulationViewportEventHandler == iid)) { + *ppv = static_cast<IDirectManipulationViewportEventHandler*>(this); + AddRef(); + return S_OK; + } + if (IID_IDirectManipulationInteractionEventHandler == iid) { + *ppv = static_cast<IDirectManipulationInteractionEventHandler*>(this); + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +HRESULT +DManipEventHandler::OnViewportStatusChanged( + IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current, + DIRECTMANIPULATION_STATUS previous) { + if (current == previous) { + return S_OK; + } + + if (current == DIRECTMANIPULATION_INERTIA) { + if (previous != DIRECTMANIPULATION_RUNNING || mState != State::ePanning) { + // xxx transition to none? + return S_OK; + } + + TransitionToState(State::eInertia); + } + + if (current == DIRECTMANIPULATION_RUNNING) { + // INERTIA -> RUNNING, should start a new sequence. + if (previous == DIRECTMANIPULATION_INERTIA) { + TransitionToState(State::eNone); + } + } + + if (current != DIRECTMANIPULATION_ENABLED && + current != DIRECTMANIPULATION_READY) { + return S_OK; + } + + // A session has ended, reset the transform. + if (mLastScale != 1.f || mLastXOffset != 0.f || mLastYOffset != 0.f) { + HRESULT hr = + viewport->ZoomToRect(0, 0, mBounds.width, mBounds.height, false); + if (!SUCCEEDED(hr)) { + NS_WARNING("ZoomToRect failed"); + } + } + mLastScale = 1.f; + mLastXOffset = 0.f; + mLastYOffset = 0.f; + + TransitionToState(State::eNone); + + return S_OK; +} + +HRESULT +DManipEventHandler::OnViewportUpdated(IDirectManipulationViewport* viewport) { + return S_OK; +} + +void DManipEventHandler::TransitionToState(State aNewState) { + if (mState == aNewState) { + return; + } + + State prevState = mState; + mState = aNewState; + + // End the previous sequence. + switch (prevState) { + case State::ePanning: { + // ePanning -> *: PanEnd + SendPan(Phase::eEnd, 0.f, 0.f, false); + break; + } + case State::eInertia: { + // eInertia -> *: MomentumEnd + SendPan(Phase::eEnd, 0.f, 0.f, true); + break; + } + case State::ePinching: { + MOZ_ASSERT(aNewState == State::eNone); + // ePinching -> eNone: PinchEnd. ePinching should only transition to + // eNone. + // Only send a pinch end if we sent a pinch start. + if (!mShouldSendPinchStart) { + SendPinch(Phase::eEnd, 0.f); + } + mShouldSendPinchStart = false; + break; + } + case State::eNone: { + // eNone -> *: no cleanup is needed. + break; + } + default: + MOZ_ASSERT(false); + } + + // Start the new sequence. + switch (aNewState) { + case State::ePanning: { + // eInertia, eNone -> ePanning: PanStart. + // We're being called from OnContentUpdated, it has the coords we need to + // pass to SendPan(Phase::eStart), so set mShouldSendPanStart and when we + // return OnContentUpdated will check it and call SendPan(Phase::eStart). + mShouldSendPanStart = true; + break; + } + case State::eInertia: { + // Only ePanning can transition to eInertia. + MOZ_ASSERT(prevState == State::ePanning); + SendPan(Phase::eStart, 0.f, 0.f, true); + break; + } + case State::ePinching: { + // * -> ePinching: PinchStart. + // Pinch gesture may begin with some scroll events. + // We're being called from OnContentUpdated, it has the scale we need to + // pass to SendPinch(Phase::eStart), so set mShouldSendPinchStart and when + // we return OnContentUpdated will check it and call + // SendPinch(Phase::eStart). + mShouldSendPinchStart = true; + break; + } + case State::eNone: { + // * -> eNone: only cleanup is needed. + break; + } + default: + MOZ_ASSERT(false); + } +} + +HRESULT +DManipEventHandler::OnContentUpdated(IDirectManipulationViewport* viewport, + IDirectManipulationContent* content) { + float transform[6]; + HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform)); + if (!SUCCEEDED(hr)) { + NS_WARNING("GetContentTransform failed"); + return S_OK; + } + + float scale = transform[0]; + float xoffset = transform[4]; + float yoffset = transform[5]; + + // Not different from last time. + if (FuzzyEqualsMultiplicative(scale, mLastScale) && xoffset == mLastXOffset && + yoffset == mLastYOffset) { + return S_OK; + } + + // Consider this is a Scroll when scale factor equals 1.0. + if (FuzzyEqualsMultiplicative(scale, 1.f)) { + if (mState == State::eNone) { + TransitionToState(State::ePanning); + } + } else { + // Pinch gesture may begin with some scroll events. + TransitionToState(State::ePinching); + } + + if (mState == State::ePanning || mState == State::eInertia) { + // Accumulate the offset (by not updating mLastX/YOffset) until we have at + // least one pixel. + float dx = std::abs(mLastXOffset - xoffset); + float dy = std::abs(mLastYOffset - yoffset); + if (dx < 1.f && dy < 1.f) { + return S_OK; + } + } + + bool updateLastScale = true; + if (mState == State::ePanning) { + if (mShouldSendPanStart) { + SendPan(Phase::eStart, mLastXOffset - xoffset, mLastYOffset - yoffset, + false); + mShouldSendPanStart = false; + } else { + SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset, + false); + } + } else if (mState == State::eInertia) { + SendPan(Phase::eMiddle, mLastXOffset - xoffset, mLastYOffset - yoffset, + true); + } else if (mState == State::ePinching) { + if (mShouldSendPinchStart) { + updateLastScale = SendPinch(Phase::eStart, scale); + // Only clear mShouldSendPinchStart if we actually sent the event + // (updateLastScale tells us if we sent an event). + if (updateLastScale) { + mShouldSendPinchStart = false; + } + } else { + updateLastScale = SendPinch(Phase::eMiddle, scale); + } + } + + if (updateLastScale) { + mLastScale = scale; + } + mLastXOffset = xoffset; + mLastYOffset = yoffset; + + return S_OK; +} + +HRESULT +DManipEventHandler::OnInteraction( + IDirectManipulationViewport2* viewport, + DIRECTMANIPULATION_INTERACTION_TYPE interaction) { + if (interaction == DIRECTMANIPULATION_INTERACTION_BEGIN) { + if (!mObserver) { + mObserver = new VObserver(this); + } + + gfxWindowsPlatform::GetPlatform() + ->GetGlobalVsyncDispatcher() + ->AddMainThreadObserver(mObserver); + } + + if (mObserver && interaction == DIRECTMANIPULATION_INTERACTION_END) { + gfxWindowsPlatform::GetPlatform() + ->GetGlobalVsyncDispatcher() + ->RemoveMainThreadObserver(mObserver); + } + + return S_OK; +} + +void DManipEventHandler::Update() { + if (mOwner) { + mOwner->Update(); + } +} + +void DirectManipulationOwner::Update() { + if (mDmUpdateManager) { + mDmUpdateManager->Update(nullptr); + } +} + +DirectManipulationOwner::DirectManipulationOwner(nsWindow* aWindow) + : mWindow(aWindow) {} + +DirectManipulationOwner::~DirectManipulationOwner() { Destroy(); } + +bool DManipEventHandler::SendPinch(Phase aPhase, float aScale) { + if (!mWindow) { + return false; + } + + if (aScale == mLastScale && aPhase != Phase::eEnd) { + return false; + } + + PinchGestureInput::PinchGestureType pinchGestureType = + PinchGestureInput::PINCHGESTURE_SCALE; + switch (aPhase) { + case Phase::eStart: + pinchGestureType = PinchGestureInput::PINCHGESTURE_START; + break; + case Phase::eMiddle: + pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE; + break; + case Phase::eEnd: + pinchGestureType = PinchGestureInput::PINCHGESTURE_END; + break; + default: + MOZ_ASSERT_UNREACHABLE("handle all enum values"); + } + + TimeStamp eventTimeStamp = TimeStamp::Now(); + + ModifierKeyState modifierKeyState; + Modifiers mods = modifierKeyState.GetModifiers(); + + ExternalPoint screenOffset = ViewAs<ExternalPixel>( + mWindow->WidgetToScreenOffset(), + PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); + + POINT cursor_pos; + ::GetCursorPos(&cursor_pos); + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + ::ScreenToClient(wnd, &cursor_pos); + ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y}; + + PinchGestureInput event{pinchGestureType, + PinchGestureInput::TRACKPAD, + eventTimeStamp, + screenOffset, + position, + 100.0 * ((aPhase == Phase::eEnd) ? 1.f : aScale), + 100.0 * ((aPhase == Phase::eEnd) ? 1.f : mLastScale), + mods}; + + if (!event.SetLineOrPageDeltaY(mWindow)) { + return false; + } + + mWindow->SendAnAPZEvent(event); + + return true; +} + +void DManipEventHandler::SendPan(Phase aPhase, float x, float y, + bool aIsInertia) { + if (!mWindow) { + return; + } + + ModifierKeyState modifierKeyState; + Modifiers mods = modifierKeyState.GetModifiers(); + + POINT cursor_pos; + ::GetCursorPos(&cursor_pos); + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + ::ScreenToClient(wnd, &cursor_pos); + ScreenPoint position = {(float)cursor_pos.x, (float)cursor_pos.y}; + + SendPanCommon(mWindow, aPhase, position, x, y, mods, aIsInertia); +} + +/* static */ +void DManipEventHandler::SendPanCommon(nsWindow* aWindow, Phase aPhase, + ScreenPoint aPosition, double aDeltaX, + double aDeltaY, Modifiers aMods, + bool aIsInertia) { + if (!aWindow) { + return; + } + + PanGestureInput::PanGestureType panGestureType = + PanGestureInput::PANGESTURE_PAN; + if (aIsInertia) { + switch (aPhase) { + case Phase::eStart: + panGestureType = PanGestureInput::PANGESTURE_MOMENTUMSTART; + break; + case Phase::eMiddle: + panGestureType = PanGestureInput::PANGESTURE_MOMENTUMPAN; + break; + case Phase::eEnd: + panGestureType = PanGestureInput::PANGESTURE_MOMENTUMEND; + break; + default: + MOZ_ASSERT_UNREACHABLE("handle all enum values"); + } + } else { + switch (aPhase) { + case Phase::eStart: + panGestureType = PanGestureInput::PANGESTURE_START; + break; + case Phase::eMiddle: + panGestureType = PanGestureInput::PANGESTURE_PAN; + break; + case Phase::eEnd: + panGestureType = PanGestureInput::PANGESTURE_END; + break; + default: + MOZ_ASSERT_UNREACHABLE("handle all enum values"); + } + } + + TimeStamp eventTimeStamp = TimeStamp::Now(); + + PanGestureInput event{panGestureType, eventTimeStamp, aPosition, + ScreenPoint(aDeltaX, aDeltaY), aMods}; + + aWindow->SendAnAPZEvent(event); +} + +void DirectManipulationOwner::Init(const LayoutDeviceIntRect& aBounds) { + HRESULT hr = CoCreateInstance( + CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER, + IID_IDirectManipulationManager, getter_AddRefs(mDmManager)); + if (!SUCCEEDED(hr)) { + NS_WARNING("CoCreateInstance(CLSID_DirectManipulationManager failed"); + mDmManager = nullptr; + return; + } + + hr = mDmManager->GetUpdateManager(IID_IDirectManipulationUpdateManager, + getter_AddRefs(mDmUpdateManager)); + if (!SUCCEEDED(hr)) { + NS_WARNING("GetUpdateManager failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + return; + } + + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + + hr = mDmManager->CreateViewport(nullptr, wnd, IID_IDirectManipulationViewport, + getter_AddRefs(mDmViewport)); + if (!SUCCEEDED(hr)) { + NS_WARNING("CreateViewport failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + return; + } + + DIRECTMANIPULATION_CONFIGURATION configuration = + DIRECTMANIPULATION_CONFIGURATION_INTERACTION | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y | + DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA | + DIRECTMANIPULATION_CONFIGURATION_RAILS_X | + DIRECTMANIPULATION_CONFIGURATION_RAILS_Y; + if (StaticPrefs::apz_allow_zooming()) { + configuration |= DIRECTMANIPULATION_CONFIGURATION_SCALING; + } + + hr = mDmViewport->ActivateConfiguration(configuration); + if (!SUCCEEDED(hr)) { + NS_WARNING("ActivateConfiguration failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + return; + } + + hr = mDmViewport->SetViewportOptions( + DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE); + if (!SUCCEEDED(hr)) { + NS_WARNING("SetViewportOptions failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + return; + } + + mDmHandler = new DManipEventHandler(mWindow, this, aBounds); + + hr = mDmViewport->AddEventHandler(wnd, mDmHandler.get(), + &mDmViewportHandlerCookie); + if (!SUCCEEDED(hr)) { + NS_WARNING("AddEventHandler failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + RECT rect = {0, 0, aBounds.Width(), aBounds.Height()}; + hr = mDmViewport->SetViewportRect(&rect); + if (!SUCCEEDED(hr)) { + NS_WARNING("SetViewportRect failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + hr = mDmManager->Activate(wnd); + if (!SUCCEEDED(hr)) { + NS_WARNING("manager Activate failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + hr = mDmViewport->Enable(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Enable failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } + + hr = mDmUpdateManager->Update(nullptr); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmUpdateManager->Update failed"); + mDmManager = nullptr; + mDmUpdateManager = nullptr; + mDmViewport = nullptr; + mDmHandler = nullptr; + return; + } +} + +void DirectManipulationOwner::ResizeViewport( + const LayoutDeviceIntRect& aBounds) { + if (mDmHandler) { + mDmHandler->mBounds = aBounds; + } + + if (mDmViewport) { + RECT rect = {0, 0, aBounds.Width(), aBounds.Height()}; + HRESULT hr = mDmViewport->SetViewportRect(&rect); + if (!SUCCEEDED(hr)) { + NS_WARNING("SetViewportRect failed"); + } + } +} + +void DirectManipulationOwner::Destroy() { + if (mDmHandler) { + mDmHandler->mWindow = nullptr; + mDmHandler->mOwner = nullptr; + if (mDmHandler->mObserver) { + gfxWindowsPlatform::GetPlatform() + ->GetGlobalVsyncDispatcher() + ->RemoveMainThreadObserver(mDmHandler->mObserver); + mDmHandler->mObserver->ClearOwner(); + mDmHandler->mObserver = nullptr; + } + } + + HRESULT hr; + if (mDmViewport) { + hr = mDmViewport->Stop(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Stop() failed"); + } + + hr = mDmViewport->Disable(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Disable() failed"); + } + + hr = mDmViewport->RemoveEventHandler(mDmViewportHandlerCookie); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->RemoveEventHandler() failed"); + } + + hr = mDmViewport->Abandon(); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmViewport->Abandon() failed"); + } + } + + if (mWindow) { + HWND wnd = static_cast<HWND>(mWindow->GetNativeData(NS_NATIVE_WINDOW)); + + if (mDmManager) { + hr = mDmManager->Deactivate(wnd); + if (!SUCCEEDED(hr)) { + NS_WARNING("mDmManager->Deactivate() failed"); + } + } + } + + mDmHandler = nullptr; + mDmViewport = nullptr; + mDmUpdateManager = nullptr; + mDmManager = nullptr; + mWindow = nullptr; +} + +void DirectManipulationOwner::SetContact(UINT aContactId) { + if (mDmViewport) { + mDmViewport->SetContact(aContactId); + } +} + +/*static */ void DirectManipulationOwner::SynthesizeNativeTouchpadPan( + nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase, + LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY, + int32_t aModifierFlags) { + DManipEventHandler::SynthesizeNativeTouchpadPan( + aWindow, aEventPhase, aPoint, aDeltaX, aDeltaY, aModifierFlags); +} + +/*static */ void DManipEventHandler::SynthesizeNativeTouchpadPan( + nsWindow* aWindow, nsIWidget::TouchpadGesturePhase aEventPhase, + LayoutDeviceIntPoint aPoint, double aDeltaX, double aDeltaY, + int32_t aModifierFlags) { + ScreenPoint position = {(float)aPoint.x, (float)aPoint.y}; + Phase phase = Phase::eStart; + if (aEventPhase == nsIWidget::PHASE_UPDATE) { + phase = Phase::eMiddle; + } + + if (aEventPhase == nsIWidget::PHASE_END) { + phase = Phase::eEnd; + } + SendPanCommon(aWindow, phase, position, aDeltaX, aDeltaY, aModifierFlags, + /* aIsInertia = */ false); +} + +} // namespace widget +} // namespace mozilla |