/* -*- Mode: C++; tab-width: 4; 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 "UIABridge.h" #include "MetroUtils.h" #include "UIABridgePrivate.h" #include "MetroWidget.h" #include "WinUtils.h" #include #include #include #ifdef ACCESSIBILITY using namespace mozilla::a11y; #endif using namespace mozilla::widget; using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::UI; using namespace ABI::Windows::UI::Core; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::System; //#define DEBUG_BRIDGE #if !defined(DEBUG_BRIDGE) #undef LogThread #undef LogFunction #define LogThread() #define LogFunction() #define BridgeLog(...) #else #define BridgeLog(...) WinUtils::Log(__VA_ARGS__) #endif #define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} MIDL_DEFINE_GUID(IID, IID_IUIABridge, 0xc78b35b5, 0x5db, 0x43aa, 0xae, 0x73, 0x94, 0xc2, 0x33, 0xa9, 0x3c, 0x98); namespace mozilla { namespace widget { namespace winrt { #define ProviderOptions_UseClientCoordinates (ProviderOptions)0x100 static int gIDIndex = 2; ComPtr gProviderRoot = nullptr; static ComPtr gElement = nullptr; HRESULT UIABridge_CreateInstance(IInspectable **retVal) { HRESULT hr = E_OUTOFMEMORY; *retVal = nullptr; ComPtr spProvider = Make(); if (spProvider != nullptr && SUCCEEDED(hr = spProvider.Get()->QueryInterface(IID_PPV_ARGS(retVal))) && SUCCEEDED(hr = spProvider.Get()->QueryInterface(IID_PPV_ARGS(&gProviderRoot)))) { return S_OK; } return hr; } HRESULT UIATextElement_CreateInstance(IRawElementProviderFragmentRoot* aRoot) { LogFunction(); HRESULT hr = E_OUTOFMEMORY; ComPtr spProvider = Make(); if (spProvider != nullptr && SUCCEEDED(hr = spProvider.Get()->QueryInterface(IID_PPV_ARGS(&gElement)))) { spProvider->SetIndexID(gIDIndex++); return S_OK; } return hr; } // IUIABridge HRESULT UIABridge::Init(IInspectable* aView, IInspectable* aWindow, LONG_PTR aInnerPtr) { LogFunction(); NS_ASSERTION(aView, "invalid framework view pointer"); NS_ASSERTION(aWindow, "invalid window pointer"); NS_ASSERTION(aInnerPtr, "invalid Accessible pointer"); #if defined(ACCESSIBILITY) // init AccessibilityBridge and connect to accessibility mAccBridge = new AccessibilityBridge(); if (!mAccBridge->Init(CastToUnknown(), (Accessible*)aInnerPtr)) { return E_FAIL; } aWindow->QueryInterface(IID_PPV_ARGS(&mWindow)); if (FAILED(UIATextElement_CreateInstance(this))) return E_FAIL; mAccessible = (Accessible*)aInnerPtr; return S_OK; #endif return E_FAIL; } HRESULT UIABridge::Disconnect() { LogFunction(); #if defined(ACCESSIBILITY) mAccBridge->Disconnect(); mAccessible = nullptr; #endif mWindow = nullptr; gElement = nullptr; gProviderRoot = nullptr; return S_OK; } bool UIABridge::Connected() { return !!mAccessible; } // IUIAElement HRESULT UIABridge::SetFocusInternal(LONG_PTR aAccessible) { LogFunction(); return S_OK; } HRESULT UIABridge::ClearFocus() { LogFunction(); return S_OK; } static void DumpChildInfo(nsCOMPtr& aChild) { #ifdef DEBUG if (!aChild) { return; } nsString str; aChild->GetName(str); BridgeLog("name: %ls", str.BeginReading()); aChild->GetDescription(str); BridgeLog("description: %ls", str.BeginReading()); #endif } static bool ChildHasFocus(nsCOMPtr& aChild) { Accessible* access = (Accessible*)aChild.get(); BridgeLog("Focus element flags: editable:%d focusable:%d readonly:%d", ((access->NativeState() & mozilla::a11y::states::EDITABLE) > 0), ((access->NativeState() & mozilla::a11y::states::FOCUSABLE) > 0), ((access->NativeState() & mozilla::a11y::states::READONLY) > 0)); return (((access->NativeState() & mozilla::a11y::states::EDITABLE) > 0) && ((access->NativeState() & mozilla::a11y::states::READONLY) == 0)); } HRESULT UIABridge::FocusChangeEvent() { LogFunction(); if (!Connected()) { return UIA_E_ELEMENTNOTAVAILABLE; } nsCOMPtr child; mAccessible->GetFocusedChild(getter_AddRefs(child)); if (!child) { return S_OK; } if (!ChildHasFocus(child)) { ComPtr element; gElement.As(&element); if (!element) { return S_OK; } element->ClearFocus(); UiaRaiseAutomationEvent(this, UIA_AutomationFocusChangedEventId); } return S_OK; } // IRawElementProviderFragmentRoot HRESULT UIABridge::ElementProviderFromPoint(double x, double y, IRawElementProviderFragment ** retVal) { LogFunction(); *retVal = nullptr; if (!Connected()) { return UIA_E_ELEMENTNOTAVAILABLE; } gElement.Get()->QueryInterface(IID_PPV_ARGS(retVal)); return S_OK; } // Windows calls this looking for the current focus element. Windows // will call here before accessible sends us any observer events through // the accessibility bridge, so update child focus information. HRESULT UIABridge::GetFocus(IRawElementProviderFragment ** retVal) { LogFunction(); if (!Connected()) { return UIA_E_ELEMENTNOTAVAILABLE; } nsCOMPtr child; nsresult rv = mAccessible->GetFocusedChild(getter_AddRefs(child)); if (!child) { BridgeLog("mAccessible->GetFocusedChild failed."); return S_OK; } DumpChildInfo(child); ComPtr element; gElement.As(&element); if (!element) { BridgeLog("gElement as IUIAElement failed."); return S_OK; } if (!ChildHasFocus(child)) { element->ClearFocus(); } else { element->SetFocusInternal((LONG_PTR)child.get()); element.Get()->QueryInterface(IID_PPV_ARGS(retVal)); } return S_OK; } // IRawElementProviderFragment HRESULT UIABridge::Navigate(NavigateDirection direction, IRawElementProviderFragment ** retVal) { LogFunction(); if (!Connected()) { return UIA_E_ELEMENTNOTAVAILABLE; } *retVal = nullptr; switch(direction) { case NavigateDirection_Parent: BridgeLog("UIABridge::Navigate NavigateDirection_Parent"); break; case NavigateDirection_NextSibling: BridgeLog("UIABridge::Navigate NavigateDirection_NextSibling"); break; case NavigateDirection_PreviousSibling: BridgeLog("UIABridge::Navigate NavigateDirection_PreviousSibling"); break; case NavigateDirection_FirstChild: BridgeLog("UIABridge::Navigate NavigateDirection_FirstChild"); gElement.Get()->QueryInterface(IID_PPV_ARGS(retVal)); break; case NavigateDirection_LastChild: BridgeLog("UIABridge::Navigate NavigateDirection_LastChild"); gElement.Get()->QueryInterface(IID_PPV_ARGS(retVal)); break; } // For the other directions (parent, next, previous) the default of nullptr is correct return S_OK; } HRESULT UIABridge::GetRuntimeId(SAFEARRAY ** retVal) { LogFunction(); if (!Connected()) { return UIA_E_ELEMENTNOTAVAILABLE; } int runtimeId[2] = { UiaAppendRuntimeId, 1 }; // always 1 *retVal = SafeArrayCreateVector(VT_I4, 0, ARRAYSIZE(runtimeId)); if (*retVal != nullptr) { for (long index = 0; index < ARRAYSIZE(runtimeId); ++index) { SafeArrayPutElement(*retVal, &index, &runtimeId[index]); } } else { return E_OUTOFMEMORY; } return S_OK; } HRESULT UIABridge::get_BoundingRectangle(UiaRect * retVal) { LogFunction(); if (!Connected() || !mWindow) { return UIA_E_ELEMENTNOTAVAILABLE; } // returns logical pixels Rect bounds; mWindow->get_Bounds(&bounds); // we need to return physical pixels retVal->left = WinUtils::LogToPhys(bounds.X); retVal->top = WinUtils::LogToPhys(bounds.Y); retVal->width = WinUtils::LogToPhys(bounds.Width); retVal->height = WinUtils::LogToPhys(bounds.Height); return S_OK; } HRESULT UIABridge::GetEmbeddedFragmentRoots(SAFEARRAY ** retVal) { if (!Connected()) { return UIA_E_ELEMENTNOTAVAILABLE; } // doesn't apply according to msdn. *retVal = nullptr; return S_OK; } HRESULT UIABridge::SetFocus() { LogFunction(); if (!Connected()) { return UIA_E_ELEMENTNOTAVAILABLE; } return S_OK; } HRESULT UIABridge::get_FragmentRoot(IRawElementProviderFragmentRoot ** retVal) { // we are the fragment root. Our children return us for this call. return QueryInterface(IID_PPV_ARGS(retVal)); } // IRawElementProviderSimple HRESULT UIABridge::get_ProviderOptions(ProviderOptions * pRetVal) { LogFunction(); if (!Connected()) { return E_FAIL; } *pRetVal = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading | ProviderOptions_UseClientCoordinates; return S_OK; } HRESULT UIABridge::GetPatternProvider(PATTERNID patternId, IUnknown **ppRetVal) { LogFunction(); BridgeLog("UIABridge::GetPatternProvider=%d", patternId); // The root window doesn't support any specific pattern *ppRetVal = nullptr; return S_OK; } HRESULT UIABridge::GetPropertyValue(PROPERTYID idProp, VARIANT * pRetVal) { pRetVal->vt = VT_EMPTY; switch (idProp) { case UIA_AutomationIdPropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_AutomationIdPropertyId"); break; case UIA_ControlTypePropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_ControlTypePropertyId"); break; case UIA_IsKeyboardFocusablePropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_IsKeyboardFocusablePropertyId"); break; case UIA_IsContentElementPropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_IsContentElementPropertyId"); break; case UIA_IsControlElementPropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_IsControlElementPropertyId"); break; case UIA_IsEnabledPropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_IsEnabledPropertyId"); break; case UIA_HasKeyboardFocusPropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_HasKeyboardFocusPropertyId"); break; case UIA_NamePropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_NamePropertyId"); break; case UIA_IsPasswordPropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_IsPasswordPropertyId"); break; case UIA_NativeWindowHandlePropertyId: BridgeLog("UIABridge::GetPropertyValue: idProp=UIA_NativeWindowHandlePropertyId"); break; default: BridgeLog("UIABridge::GetPropertyValue: idProp=%d", idProp); break; } if (!Connected()) { return E_FAIL; } switch (idProp) { case UIA_AutomationIdPropertyId: pRetVal->bstrVal = SysAllocString(L"MozillaAccessibilityBridge0001"); pRetVal->vt = VT_BSTR; break; case UIA_ControlTypePropertyId: pRetVal->vt = VT_I4; pRetVal->lVal = UIA_WindowControlTypeId; break; case UIA_IsKeyboardFocusablePropertyId: case UIA_IsContentElementPropertyId: case UIA_IsControlElementPropertyId: case UIA_IsEnabledPropertyId: pRetVal->boolVal = VARIANT_TRUE; pRetVal->vt = VT_BOOL; break; case UIA_HasKeyboardFocusPropertyId: pRetVal->vt = VT_BOOL; pRetVal->boolVal = VARIANT_FALSE; break; case UIA_NamePropertyId: pRetVal->bstrVal = SysAllocString(L"MozillaAccessibilityBridge"); pRetVal->vt = VT_BSTR; break; case UIA_IsPasswordPropertyId: pRetVal->vt = VT_BOOL; pRetVal->boolVal = VARIANT_FALSE; break; case UIA_NativeWindowHandlePropertyId: pRetVal->vt = VT_I4; pRetVal->lVal = (LONG)MetroWidget::GetICoreWindowHWND(); break; default: BridgeLog("UIABridge: Unhandled property"); break; } return S_OK; } HRESULT UIABridge::get_HostRawElementProvider(IRawElementProviderSimple **ppRetVal) { // We only have this in the root bridge - this is our parent ICoreWindow. *ppRetVal = nullptr; if (mWindow != nullptr) { IInspectable *pHostAsInspectable = nullptr; if (SUCCEEDED(mWindow->get_AutomationHostProvider(&pHostAsInspectable))) { pHostAsInspectable->QueryInterface(ppRetVal); pHostAsInspectable->Release(); } } return S_OK; } /////////////////////////////////////////////////////////////////////////////// // Element HRESULT UIATextElement::SetFocusInternal(LONG_PTR aAccessible) { LogFunction(); #if defined(ACCESSIBILITY) NS_ASSERTION(mAccessItem, "Bad accessible pointer"); if (mAccessItem == (nsIAccessible*)aAccessible) { return E_UNEXPECTED; } mAccessItem = (nsIAccessible*)aAccessible; return S_OK; #endif return E_FAIL; } HRESULT UIATextElement::ClearFocus() { LogFunction(); mAccessItem = nullptr; return S_OK; } // IRawElementProviderFragment HRESULT UIATextElement::Navigate(NavigateDirection direction, IRawElementProviderFragment ** retVal) { LogFunction(); *retVal = nullptr; switch(direction) { case NavigateDirection_Parent: gProviderRoot.Get()->QueryInterface(IID_PPV_ARGS(retVal)); break; case NavigateDirection_NextSibling: break; case NavigateDirection_PreviousSibling: break; case NavigateDirection_FirstChild: break; case NavigateDirection_LastChild: break; } return S_OK; } HRESULT UIATextElement::GetRuntimeId(SAFEARRAY ** retVal) { LogFunction(); int runtimeId[2] = { UiaAppendRuntimeId, mIndexID }; *retVal = SafeArrayCreateVector(VT_I4, 0, ARRAYSIZE(runtimeId)); if (*retVal != nullptr) { for (long index = 0; index < ARRAYSIZE(runtimeId); ++index) { SafeArrayPutElement(*retVal, &index, &runtimeId[index]); } } else { return E_OUTOFMEMORY; } return S_OK; } HRESULT UIATextElement::get_BoundingRectangle(UiaRect * retVal) { LogFunction(); if (!mAccessItem) { return UIA_E_ELEMENTNOTAVAILABLE; } // bounds are in physical pixels int32_t docX = 0, docY = 0, docWidth = 0, docHeight = 0; mAccessItem->GetBounds(&docX, &docY, &docWidth, &docHeight); retVal->left = (float)docX; retVal->top = (float)docY; retVal->width = (float)docWidth; retVal->height = (float)docHeight; BridgeLog("get_BoundingRectangle: left=%d top=%d right=%d bottom=%d", docX, docY, docX + docWidth, docY + docHeight); return S_OK; } HRESULT UIATextElement::GetEmbeddedFragmentRoots(SAFEARRAY ** retVal) { *retVal = nullptr; return S_OK; } HRESULT UIATextElement::SetFocus() { LogFunction(); return S_OK; } HRESULT UIATextElement::get_FragmentRoot(IRawElementProviderFragmentRoot ** retVal) { return gProviderRoot.Get()->QueryInterface(IID_PPV_ARGS(retVal)); } // IRawElementProviderSimple HRESULT UIATextElement::get_ProviderOptions(ProviderOptions * pRetVal) { *pRetVal = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading | ProviderOptions_UseClientCoordinates; return S_OK; } HRESULT UIATextElement::GetPatternProvider(PATTERNID patternId, IUnknown **ppRetVal) { LogFunction(); BridgeLog("UIATextElement::GetPatternProvider=%d", patternId); // UIA_ValuePatternId - 10002 // UIA_TextPatternId - 10014 // UIA_TextChildPatternId - 10029 *ppRetVal = nullptr; if (patternId == UIA_TextPatternId) { BridgeLog("** TextPattern requested from element."); *ppRetVal = static_cast(this); AddRef(); return S_OK; } else if (patternId == UIA_ValuePatternId) { BridgeLog("** ValuePattern requested from element."); *ppRetVal = static_cast(this); AddRef(); return S_OK; } return S_OK; } HRESULT UIATextElement::GetPropertyValue(PROPERTYID idProp, VARIANT * pRetVal) { pRetVal->vt = VT_EMPTY; // native hwnd, we don't have one for elements if (idProp == 30020) { return S_OK; } switch (idProp) { case UIA_AutomationIdPropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_AutomationIdPropertyId"); break; case UIA_ControlTypePropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_ControlTypePropertyId"); break; case UIA_IsKeyboardFocusablePropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_IsKeyboardFocusablePropertyId"); break; case UIA_IsContentElementPropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_IsContentElementPropertyId"); break; case UIA_IsControlElementPropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_IsControlElementPropertyId"); break; case UIA_IsEnabledPropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_IsEnabledPropertyId"); break; case UIA_HasKeyboardFocusPropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_HasKeyboardFocusPropertyId"); break; case UIA_NamePropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_NamePropertyId"); break; case UIA_IsPasswordPropertyId: BridgeLog("UIATextElement::GetPropertyValue: idProp=UIA_IsPasswordPropertyId"); break; default: BridgeLog("UIATextElement::GetPropertyValue: idProp=%d", idProp); break; } switch (idProp) { case UIA_AutomationIdPropertyId: pRetVal->bstrVal = SysAllocString(L"MozillaDocument0001"); pRetVal->vt = VT_BSTR; break; case UIA_ControlTypePropertyId: pRetVal->vt = VT_I4; pRetVal->lVal = UIA_EditControlTypeId; break; case UIA_IsTextPatternAvailablePropertyId: case UIA_IsKeyboardFocusablePropertyId: case UIA_IsContentElementPropertyId: case UIA_IsControlElementPropertyId: case UIA_IsEnabledPropertyId: pRetVal->boolVal = VARIANT_TRUE; pRetVal->vt = VT_BOOL; break; case UIA_LocalizedControlTypePropertyId: case UIA_LabeledByPropertyId: break; case UIA_HasKeyboardFocusPropertyId: { if (mAccessItem) { uint32_t state, extraState; if (NS_SUCCEEDED(mAccessItem->GetState(&state, &extraState)) && (state & nsIAccessibleStates::STATE_FOCUSED)) { pRetVal->vt = VT_BOOL; pRetVal->boolVal = VARIANT_TRUE; return S_OK; } } pRetVal->vt = VT_BOOL; pRetVal->boolVal = VARIANT_FALSE; break; } case UIA_NamePropertyId: pRetVal->bstrVal = SysAllocString(L"MozillaDocument"); pRetVal->vt = VT_BSTR; break; case UIA_IsPasswordPropertyId: pRetVal->vt = VT_BOOL; pRetVal->boolVal = VARIANT_FALSE; break; default: BridgeLog("UIATextElement: Unhandled property"); break; } return S_OK; } HRESULT UIATextElement::get_HostRawElementProvider(IRawElementProviderSimple **ppRetVal) { *ppRetVal = nullptr; return S_OK; } // ITextProvider HRESULT UIATextElement::GetSelection(SAFEARRAY * *pRetVal) { LogFunction(); return E_NOTIMPL; } HRESULT UIATextElement::GetVisibleRanges(SAFEARRAY * *pRetVal) { LogFunction(); return E_NOTIMPL; } HRESULT UIATextElement::RangeFromChild(IRawElementProviderSimple *childElement, ITextRangeProvider **pRetVal) { LogFunction(); return E_NOTIMPL; } HRESULT UIATextElement::RangeFromPoint(UiaPoint point, ITextRangeProvider **pRetVal) { LogFunction(); return E_NOTIMPL; } HRESULT UIATextElement::get_DocumentRange(ITextRangeProvider **pRetVal) { LogFunction(); return E_NOTIMPL; } HRESULT UIATextElement::get_SupportedTextSelection(SupportedTextSelection *pRetVal) { LogFunction(); return E_NOTIMPL; } // IValueProvider IFACEMETHODIMP UIATextElement::SetValue(LPCWSTR val) { LogFunction(); return E_NOTIMPL; } IFACEMETHODIMP UIATextElement::get_Value(BSTR *pRetVal) { LogFunction(); return E_NOTIMPL; } IFACEMETHODIMP UIATextElement::get_IsReadOnly(BOOL *pRetVal) { LogFunction(); *pRetVal = FALSE; return S_OK; } } } }