1
0
Fork 0
mirror of synced 2025-03-07 03:53:26 +01:00

uiautomationcore: Implement IRawElementProviderFragmentRoot::GetFocus for MSAA providers.

Signed-off-by: Connor McAdams <cmcadams@codeweavers.com>
This commit is contained in:
Connor McAdams 2023-09-06 08:54:11 -04:00 committed by Alexandre Julliard
parent ef1d5a0e0a
commit c7e976f819
2 changed files with 419 additions and 3 deletions

View file

@ -510,6 +510,8 @@ static struct Accessible
LONG left, top, width, height;
BOOL enable_ia2;
LONG unique_id;
INT focus_child_id;
IAccessible *focus_acc;
DEFINE_ACC_METHOD_EXPECTS;
} Accessible, Accessible2, Accessible_child, Accessible_child2;
@ -674,6 +676,9 @@ static HRESULT WINAPI Accessible_get_accChild(IAccessible *iface, VARIANT child_
case 4:
return IAccessible_QueryInterface(&Accessible_child2.IAccessible_iface, &IID_IDispatch, (void **)out_child);
case 7:
return IAccessible_QueryInterface(&Accessible.IAccessible_iface, &IID_IDispatch, (void **)out_child);
default:
break;
@ -808,8 +813,28 @@ static HRESULT WINAPI Accessible_get_accKeyboardShortcut(IAccessible *iface, VAR
static HRESULT WINAPI Accessible_get_accFocus(IAccessible *iface, VARIANT *pchild_id)
{
struct Accessible *This = impl_from_Accessible(iface);
CHECK_ACC_METHOD_EXPECT(This, get_accFocus);
ACC_METHOD_TRACE(This, get_accFocus);
VariantInit(pchild_id);
if (This->focus_acc)
{
HRESULT hr;
hr = IAccessible_QueryInterface(This->focus_acc, &IID_IDispatch, (void **)&V_DISPATCH(pchild_id));
if (SUCCEEDED(hr))
V_VT(pchild_id) = VT_DISPATCH;
return hr;
}
else if (This->focus_child_id >= 0)
{
V_VT(pchild_id) = VT_I4;
V_I4(pchild_id) = This->focus_child_id;
return S_OK;
}
return E_NOTIMPL;
}
@ -1457,6 +1482,7 @@ static struct Accessible Accessible =
0, 0, 0, NULL,
0, 0, 0, 0,
FALSE, 0,
CHILDID_SELF, NULL,
};
static struct Accessible Accessible2 =
@ -1472,6 +1498,7 @@ static struct Accessible Accessible2 =
0, 0, 0, NULL,
0, 0, 0, 0,
FALSE, 0,
CHILDID_SELF, NULL,
};
static struct Accessible Accessible_child =
@ -1487,6 +1514,7 @@ static struct Accessible Accessible_child =
0, 0, 0, NULL,
0, 0, 0, 0,
FALSE, 0,
CHILDID_SELF, NULL,
};
static struct Accessible Accessible_child2 =
@ -1502,6 +1530,7 @@ static struct Accessible Accessible_child2 =
0, 0, 0, NULL,
0, 0, 0, 0,
FALSE, 0,
CHILDID_SELF, NULL,
};
struct Provider_prop_override
@ -3440,14 +3469,17 @@ static void check_msaa_prov_host_elem_prov_(IUnknown *elem, BOOL exp_host_prov,
static void set_accessible_props(struct Accessible *acc, INT role, INT state,
LONG child_count, LPCWSTR name, LONG left, LONG top, LONG width, LONG height);
static void set_accessible_ia2_props(struct Accessible *acc, BOOL enable_ia2, LONG unique_id);
static void test_uia_prov_from_acc_fragment_root(HWND hwnd)
{
IRawElementProviderFragmentRoot *elroot, *elroot2;
IRawElementProviderFragment *elfrag, *elfrag2;
IRawElementProviderSimple *elprov;
ULONG old_ref;
HRESULT hr;
set_accessible_props(&Accessible, ROLE_SYSTEM_DOCUMENT, 0, 0, L"acc_name", 0, 0, 0, 0);
set_accessible_props(&Accessible, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSED, 0, L"acc_name", 0, 0, 0, 0);
set_accessible_ia2_props(&Accessible, FALSE, 0);
Accessible.ow_hwnd = hwnd;
elprov = NULL;
@ -3545,6 +3577,217 @@ static void test_uia_prov_from_acc_fragment_root(HWND hwnd)
IRawElementProviderFragmentRoot_Release(elroot);
IRawElementProviderFragment_Release(elfrag);
/*
* IRawElementProviderFragmentRoot::GetFocus will call get_accFocus.
*/
hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragmentRoot, (void **)&elroot);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elroot, "elroot == NULL\n");
/* Focus is CHILDID_SELF, returns NULL. */
elfrag = (void *)0xdeadbeef;
Accessible.focus_child_id = CHILDID_SELF;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag, "elfrag != NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
/*
* get_accFocus returns child ID 1, which is a simple child element.
* get_accState for child ID 1 returns STATE_SYSTEM_INVISIBLE, so no
* element will be returned.
*/
elfrag = (void *)0xdeadbeef;
Accessible.focus_child_id = 1;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible, get_accChild);
SET_ACC_METHOD_EXPECT(&Accessible, get_accState);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag, "elfrag != NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accState);
/*
* get_accFocus returns child ID 3, which is another simple child
* element. get_accState for child ID 3 does not have
* STATE_SYSTEM_INVISIBLE set, so it will return an element.
*/
elfrag = (void *)0xdeadbeef;
Accessible.focus_child_id = 3;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible, get_accChild);
SET_ACC_METHOD_EXPECT(&Accessible, get_accState);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elfrag, "elfrag == NULL\n");
check_msaa_prov_acc(elfrag, &Accessible.IAccessible_iface, 3);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accState);
IRawElementProviderFragment_Release(elfrag);
/*
* get_accFocus returns child ID 2 which is a full IAccessible,
* Accessible_child.
*/
elfrag = (void *)0xdeadbeef;
Accessible.focus_child_id = 2;
Accessible_child.state = STATE_SYSTEM_FOCUSABLE | STATE_SYSTEM_OFFSCREEN;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible, get_accChild);
SET_ACC_METHOD_EXPECT(&Accessible_child, accNavigate);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accParent);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accState);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accFocus);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elfrag, "elfrag == NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild);
CHECK_ACC_METHOD_CALLED(&Accessible_child, accNavigate);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accParent);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accState);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accFocus);
check_msaa_prov_acc(elfrag, &Accessible_child.IAccessible_iface, CHILDID_SELF);
IRawElementProviderFragment_Release(elfrag);
/*
* get_accFocus returns child ID 2 which is a full IAccessible,
* Accessible_child. It returns failure from get_accState so it isn't
* returned.
*/
elfrag = (void *)0xdeadbeef;
Accessible.focus_child_id = 2;
Accessible_child.state = 0;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible, get_accChild);
SET_ACC_METHOD_EXPECT(&Accessible_child, accNavigate);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accParent);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accState);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag, "elfrag != NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild);
CHECK_ACC_METHOD_CALLED(&Accessible_child, accNavigate);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accParent);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accState);
/*
* get_accFocus returns child ID 7 which is a full IAccessible,
* Accessible. This is the same IAccessible interface as the one we called
* get_accFocus on, so it is ignored. Same behavior as CHILDID_SELF.
*/
elfrag = (void *)0xdeadbeef;
Accessible.focus_child_id = 7;
Accessible.state = STATE_SYSTEM_FOCUSABLE;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible, get_accChild);
SET_ACC_METHOD_EXPECT(&Accessible, get_accState);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag, "elfrag != NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accChild);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accState);
/*
* Return E_NOTIMPL from get_accFocus, returns a new provider representing
* the same IAccessible.
*/
elfrag = (void *)0xdeadbeef;
old_ref = Accessible.ref;
Accessible.focus_child_id = -1;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elfrag, "elfrag == NULL\n");
ok(Accessible.ref > old_ref, "Unexpected ref %ld\n", Accessible.ref);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
/* Two unique COM objects that represent the same IAccessible. */
ok(!iface_cmp((IUnknown *)elroot, (IUnknown *)elfrag), "elroot == elfrag\n");
check_msaa_prov_acc(elfrag, &Accessible.IAccessible_iface, CHILDID_SELF);
IRawElementProviderFragment_Release(elfrag);
ok(Accessible.ref == old_ref, "Unexpected ref %ld\n", Accessible.ref);
Accessible.focus_child_id = CHILDID_SELF;
/*
* Similar to CHILDID_SELF, if the same IAccessible interface is returned
* as a VT_DISPATCH, we'll get no element.
*/
elfrag = (void *)0xdeadbeef;
Accessible.focus_acc = &Accessible.IAccessible_iface;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible, get_accState);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag, "elfrag != NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible, get_accState);
/*
* Return Accessible_child as a VT_DISPATCH - will get an element.
*/
elfrag = (void *)0xdeadbeef;
Accessible.focus_acc = &Accessible_child.IAccessible_iface;
Accessible_child.state = STATE_SYSTEM_FOCUSABLE;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible_child, accNavigate);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accParent);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accState);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accFocus);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elfrag, "elfrag == NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible_child, accNavigate);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accParent);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accState);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accFocus);
check_msaa_prov_acc(elfrag, &Accessible_child.IAccessible_iface, CHILDID_SELF);
IRawElementProviderFragment_Release(elfrag);
/*
* Fail get_accFocus on child.
*/
Accessible_child.focus_child_id = -1;
Accessible_child.state = STATE_SYSTEM_FOCUSABLE;
elfrag = (void *)0xdeadbeef;
SET_ACC_METHOD_EXPECT(&Accessible, get_accFocus);
SET_ACC_METHOD_EXPECT(&Accessible_child, accNavigate);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accParent);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accState);
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accFocus);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elfrag, "elfrag == NULL\n");
CHECK_ACC_METHOD_CALLED(&Accessible, get_accFocus);
CHECK_ACC_METHOD_CALLED(&Accessible_child, accNavigate);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accParent);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accState);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accFocus);
check_msaa_prov_acc(elfrag, &Accessible_child.IAccessible_iface, CHILDID_SELF);
IRawElementProviderFragment_Release(elfrag);
IRawElementProviderFragmentRoot_Release(elroot);
IRawElementProviderSimple_Release(elprov);
/*
@ -3572,12 +3815,28 @@ static void test_uia_prov_from_acc_fragment_root(HWND hwnd)
IRawElementProviderFragmentRoot_Release(elroot);
IRawElementProviderFragment_Release(elfrag);
/*
* IRawElementProviderFragmentRoot::GetFocus will not call get_accFocus
* on a simple child IAccessible.
*/
hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragmentRoot, (void **)&elroot);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elroot, "elroot == NULL\n");
elfrag = (void *)0xdeadbeef;
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag, "elfrag != NULL\n");
IRawElementProviderFragmentRoot_Release(elroot);
IRawElementProviderSimple_Release(elprov);
/*
* Test child of root HWND IAccessible.
*/
set_accessible_props(&Accessible_child, ROLE_SYSTEM_TEXT, 0, 0, L"acc_child_name", 0, 0, 0, 0);
set_accessible_ia2_props(&Accessible_child, FALSE, 0);
elprov = NULL;
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accParent); /* Gets HWND from parent IAccessible. */
@ -3606,8 +3865,38 @@ static void test_uia_prov_from_acc_fragment_root(HWND hwnd)
IRawElementProviderFragmentRoot_Release(elroot);
IRawElementProviderFragment_Release(elfrag);
/*
* GetFocus tests.
*/
hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragmentRoot, (void **)&elroot);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elroot, "elroot == NULL\n");
/*
* get_accFocus returns E_NOTIMPL, returns new provider for same
* IAccessible.
*/
elfrag = (void *)0xdeadbeef;
old_ref = Accessible_child.ref;
Accessible_child.focus_child_id = -1;
SET_ACC_METHOD_EXPECT(&Accessible_child, get_accFocus);
hr = IRawElementProviderFragmentRoot_GetFocus(elroot, &elfrag);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elfrag, "elfrag == NULL\n");
ok(Accessible_child.ref > old_ref, "Unexpected ref %ld\n", Accessible.ref);
CHECK_ACC_METHOD_CALLED(&Accessible_child, get_accFocus);
/* Again, two unique COM objects that represent the same IAccessible. */
ok(!iface_cmp((IUnknown *)elroot, (IUnknown *)elfrag), "elroot == elfrag\n");
check_msaa_prov_acc(elfrag, &Accessible_child.IAccessible_iface, CHILDID_SELF);
IRawElementProviderFragment_Release(elfrag);
ok(Accessible_child.ref == old_ref, "Unexpected ref %ld\n", Accessible.ref);
IRawElementProviderFragmentRoot_Release(elroot);
IRawElementProviderSimple_Release(elprov);
Accessible.focus_child_id = Accessible_child.focus_child_id = CHILDID_SELF;
ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
acc_client = NULL;

View file

@ -178,6 +178,25 @@ static HRESULT msaa_acc_prop_match(IAccessible *acc, IAccessible *acc2)
return S_FALSE;
}
static BOOL msaa_acc_iface_cmp(IAccessible *acc, IAccessible *acc2)
{
IUnknown *unk, *unk2;
BOOL matched;
acc = msaa_acc_da_unwrap(acc);
acc2 = msaa_acc_da_unwrap(acc2);
IAccessible_QueryInterface(acc, &IID_IUnknown, (void**)&unk);
IAccessible_QueryInterface(acc2, &IID_IUnknown, (void**)&unk2);
matched = (unk == unk2);
IAccessible_Release(acc);
IUnknown_Release(unk);
IAccessible_Release(acc2);
IUnknown_Release(unk2);
return matched;
}
static BOOL msaa_acc_compare(IAccessible *acc, IAccessible *acc2)
{
IAccessible2 *ia2[2] = { NULL, NULL };
@ -1068,12 +1087,120 @@ static HRESULT WINAPI msaa_fragment_root_ElementProviderFromPoint(IRawElementPro
return E_NOTIMPL;
}
static HRESULT msaa_acc_get_focus(struct msaa_provider *prov, struct msaa_provider **out_prov)
{
IRawElementProviderSimple *elprov;
IAccessible *focus_acc = NULL;
INT focus_cid = CHILDID_SELF;
HWND hwnd = NULL;
HRESULT hr;
VARIANT v;
*out_prov = NULL;
if (V_I4(&prov->cid) != CHILDID_SELF)
return S_OK;
VariantInit(&v);
hr = IAccessible_get_accFocus(prov->acc, &v);
if (FAILED(hr) || (V_VT(&v) != VT_I4 && V_VT(&v) != VT_DISPATCH))
{
VariantClear(&v);
return hr;
}
if (V_VT(&v) == VT_I4)
{
IDispatch *disp = NULL;
if (V_I4(&v) == CHILDID_SELF)
return S_OK;
hr = IAccessible_get_accChild(prov->acc, v, &disp);
if (FAILED(hr))
return hr;
if (hr == S_FALSE)
{
hwnd = prov->hwnd;
focus_acc = prov->acc;
IAccessible_AddRef(focus_acc);
focus_cid = V_I4(&v);
}
else if (disp)
{
V_VT(&v) = VT_DISPATCH;
V_DISPATCH(&v) = disp;
}
else
return E_FAIL;
}
if (V_VT(&v) == VT_DISPATCH)
{
hr = IDispatch_QueryInterface(V_DISPATCH(&v), &IID_IAccessible, (void **)&focus_acc);
VariantClear(&v);
if (FAILED(hr))
return hr;
hr = WindowFromAccessibleObject(focus_acc, &hwnd);
if (FAILED(hr) || !hwnd)
{
IAccessible_Release(focus_acc);
return hr;
}
}
hr = create_msaa_provider(focus_acc, focus_cid, hwnd, FALSE, &elprov);
IAccessible_Release(focus_acc);
if (SUCCEEDED(hr))
*out_prov = impl_from_msaa_provider(elprov);
return hr;
}
static HRESULT WINAPI msaa_fragment_root_GetFocus(IRawElementProviderFragmentRoot *iface,
IRawElementProviderFragment **ret_val)
{
FIXME("%p, %p: stub!\n", iface, ret_val);
struct msaa_provider *msaa_prov = impl_from_msaa_fragment_root(iface);
struct msaa_provider *prov, *prov2;
IRawElementProviderSimple *elprov;
HRESULT hr;
TRACE("%p, %p\n", iface, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
return S_OK;
hr = create_msaa_provider(msaa_prov->acc, CHILDID_SELF, msaa_prov->hwnd, FALSE, &elprov);
if (FAILED(hr))
return hr;
prov = impl_from_msaa_provider(elprov);
while (SUCCEEDED(msaa_acc_get_focus(prov, &prov2)))
{
if (!prov2 || (msaa_check_acc_state_hres(prov2->acc, prov2->cid, STATE_SYSTEM_INVISIBLE) != S_FALSE) ||
((V_I4(&prov2->cid) == CHILDID_SELF) && msaa_acc_iface_cmp(prov->acc, prov2->acc)))
{
if (prov2)
IRawElementProviderSimple_Release(&prov2->IRawElementProviderSimple_iface);
if (msaa_acc_iface_cmp(prov->acc, msaa_prov->acc) && V_I4(&prov->cid) == CHILDID_SELF)
{
IRawElementProviderSimple_Release(&prov->IRawElementProviderSimple_iface);
return S_OK;
}
break;
}
IRawElementProviderSimple_Release(&prov->IRawElementProviderSimple_iface);
prov = prov2;
}
hr = IRawElementProviderSimple_QueryInterface(&prov->IRawElementProviderSimple_iface, &IID_IRawElementProviderFragment, (void **)ret_val);
IRawElementProviderSimple_Release(&prov->IRawElementProviderSimple_iface);
return hr;
}
static const IRawElementProviderFragmentRootVtbl msaa_fragment_root_vtbl = {