Skip to content

Commit

Permalink
Add IsOpcodeSupported support to WinRT and C++ wrappers (#78)
Browse files Browse the repository at this point in the history
IsOpcodeSupported is an API provided by CoreAutomationRemoteOperation
which determines whether a given remote operation opcode is supported by
the current provider connection. Which opcodes are supported depends on
both the client and provider UIA versions.

This change simply plumbs IsOpcodeSupported through
Microsoft.UI.UIAutomation and UiaOperationAbstraction. A more elegant
design taking advantage of the characteristics of each wrapper can be
made in the future.

For UiaOperationAbstraction, we always return true from
IsOpcodeSupported in the local case.

Note, IsOpcodeSupported will throw E_FAIL on future versions of Windows
when no connection has yet been established. Once such versions of
Windows are released, we should update tests and comments with the
specifics. This is tracked by issue #77.
  • Loading branch information
beweedon authored May 17, 2021
1 parent 734f7a8 commit 451f315
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -2240,5 +2240,81 @@ namespace UiaOperationAbstractionTests
{
TryBlockTest(false);
}

// Test that calling IsOpcodeSupported works after we import an
// element, establishing a connection.
void IsOpcodeSupportedTest_AfterImport(const bool useRemoteOperations)
{
ModernApp app(L"Microsoft.WindowsCalculator_8wekyb3d8bbwe!App");
app.Activate();
auto calc = WaitForElementFocus(L"Display is 0");
UiaElement element = calc;

auto guard = InitializeUiaOperationAbstraction(useRemoteOperations);

auto scope = UiaOperationScope::StartNew();
scope.BindInput(element);

Assert::IsTrue(scope.IsOpcodeSupported(0x0 /*Nop*/));
Assert::IsTrue(scope.IsOpcodeSupported(0x48 /*NewGuid*/));

const auto isNonExistentOpcodeSupported = scope.IsOpcodeSupported(10000000000000);
if (useRemoteOperations)
{
Assert::IsFalse(isNonExistentOpcodeSupported);
}
else
{
// All operations are supported locally.
Assert::IsTrue(isNonExistentOpcodeSupported);
}
}

TEST_METHOD(IsOpcodeSupported_AfterImport_Remote)
{
IsOpcodeSupportedTest_AfterImport(true);
}

TEST_METHOD(IsOpcodeSupported_AfterImport_Local)
{
IsOpcodeSupportedTest_AfterImport(false);
}

// Test that calling IsOpcodeSupported fails for remote ops on certain
// Windows builds if we haven't imported an element yet, thus not
// establishing a connection.
void IsOpcodeSupportedTest_BeforeImport(const bool useRemoteOperations)
{
auto guard = InitializeUiaOperationAbstraction(useRemoteOperations);

auto scope = UiaOperationScope::StartNew();

const auto hr = wil::ResultFromException([&]()
{
scope.IsOpcodeSupported(0x0);
});

if (useRemoteOperations)
{
// TODO #77: Do a Windows version check once a build with this
// failing behavior is released.
// Assert::AreEqual(E_FAIL, hr);
}
else
{
// Calling IsOpcodeSupported never produces an error in the local case.
Assert::AreEqual(S_OK, hr);
}
}

TEST_METHOD(IsOpcodeSupported_BeforeImport_Remote)
{
IsOpcodeSupportedTest_BeforeImport(true);
}

TEST_METHOD(IsOpcodeSupported_BeforeImport_Local)
{
IsOpcodeSupportedTest_BeforeImport(false);
}
};
}
}
33 changes: 33 additions & 0 deletions src/UIAutomation/FunctionalTests/WinRTBuilderTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,5 +520,38 @@ namespace WinRTBuilderTests
Assert::AreEqual(true, winrt::unbox_value<bool>(results.GetResult(isNullToken)));
Assert::IsTrue(results.GetResult(metadataToken) == nullptr);
}

// Test that calling IsOpcodeSupported works after we import an
// element, establishing a connection.
TEST_METHOD(IsOpcodeSupported_AfterImport)
{
ModernApp app(L"Microsoft.WindowsCalculator_8wekyb3d8bbwe!App");
app.Activate();
auto calc = WaitForElementFocus(L"Display is 0");

winrt::AutomationRemoteOperation op;
auto remoteElement = op.ImportElement(calc.as<winrt::AutomationElement>());

Assert::IsTrue(op.IsOpcodeSupported(0x0 /*Nop*/));
Assert::IsTrue(op.IsOpcodeSupported(0x48 /*NewGuid*/));
Assert::IsFalse(op.IsOpcodeSupported(10000000000000));
}

// Test that calling IsOpcodeSupported fails on certain Windows builds
// if we haven't imported an element yet, thus not establishing a
// connection.
TEST_METHOD(IsOpcodeSupported_BeforeImport)
{
winrt::AutomationRemoteOperation op;

const auto hr = wil::ResultFromException([&]()
{
op.IsOpcodeSupported(0x0);
});

// TODO #77: Do a Windows version check once a build with this
// failing behavior is released.
// Assert::AreEqual(E_FAIL, hr);
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ namespace winrt::Microsoft::UI::UIAutomation::implementation
return make<AutomationRemoteAnyObject>(newId, *this);
}

bool AutomationRemoteOperation::IsOpcodeSupported(const uint32_t opcode) const
{
return m_remoteOperation.IsOpcodeSupported(opcode);
}

winrt::AutomationRemoteElement AutomationRemoteOperation::ImportElement(winrt::AutomationElement const& element)
{
const auto elementId = GetNextId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ namespace winrt::Microsoft::UI::UIAutomation::implementation
winrt::AutomationRemoteStringMap NewStringMap();
winrt::AutomationRemoteAnyObject NewNull();

// Returns whether the given opcode is supported in the current remote
// operation connection. Calls directly into the corresponding
// CoreAutomationRemoteOperation API. Throws E_FAIL if no connection is
// currently active.
// TODO #77: Specify which Windows release we start throwing that error.
bool IsOpcodeSupported(uint32_t opcode) const;

winrt::AutomationRemoteElement ImportElement(winrt::Windows::UI::UIAutomation::AutomationElement const& element);
winrt::AutomationRemoteTextRange ImportTextRange(winrt::Windows::UI::UIAutomation::AutomationTextRange const& textRange);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,8 @@ namespace Microsoft.UI.UIAutomation
AutomationRemoteStringMap NewStringMap();
AutomationRemoteAnyObject NewNull();

Boolean IsOpcodeSupported(UInt32 opcode);

AutomationRemoteElement ImportElement(Windows.UI.UIAutomation.AutomationElement element);
AutomationRemoteTextRange ImportTextRange(Windows.UI.UIAutomation.AutomationTextRange textRange);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,20 @@ namespace UiaOperationAbstraction
return m_useRemoteApi;
}

bool UiaOperationDelegator::IsOpcodeSupported(const uint32_t opcode) const
{
if (m_useRemoteApi)
{
return m_remoteOperation.IsOpcodeSupported(opcode);
}
else
{
// If we're not in a remote operation we'll just be using classic
// UIA, in which everything is supported.
return true;
}
}

void UiaOperationDelegator::AbortOperationWithHresult(HRESULT hr)
{
if (m_useRemoteApi && m_remoteOperation)
Expand Down
11 changes: 11 additions & 0 deletions src/UIAutomation/UiaOperationAbstraction/UiaOperationAbstraction.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ namespace UiaOperationAbstraction

bool GetUseRemoteApi() const;

// Returns whether the given opcode is supported in the current remote
// operation connection if remote, returns true if local. Throws E_FAIL
// if remote and no connection is currently active.
// TODO #77: Specify which Windows release we start throwing that error.
bool IsOpcodeSupported(uint32_t opcode) const;

template<class OnTrue, class OnFalse>
void If(UiaBool conditionBool, OnTrue&& onTrue, OnFalse&& onFalse) const
{
Expand Down Expand Up @@ -2430,6 +2436,11 @@ namespace UiaOperationAbstraction
return GetCurrentDelegator()->GetUseRemoteApi();
}

bool IsOpcodeSupported(const uint32_t opcode) const
{
return GetCurrentDelegator()->IsOpcodeSupported(opcode);
}

template<class OnTrue, class OnFalse>
inline void If(UiaBool conditionBool, OnTrue&& onTrue, OnFalse&& onFalse) const
{
Expand Down

0 comments on commit 451f315

Please sign in to comment.