[PATCH 03/11] jscript: Implement proxy objects along with a private interface.

Gabriel Ivăncescu gabrielopcode at gmail.com
Mon Sep 20 09:46:09 CDT 2021


Native mshtml objects act like JS objects in IE9 modes and up (the JS engine
is implemented in mshtml). This implements proxy JS objects for certain
dispatch objects that export a new private (internal to Wine) interface in
versions ES5 and up, to achieve the same behavior. It delegates as much as
possible to mshtml to implement all quirks there. Props are queried by the
underlying dispatch and accesses to them forwarded to the dispatch object.

Since it's purely internal, the private interface is not part of any typelib,
and for simplicity, it extends (inherits from) IDispatchEx, so we can just
cast it as needed.

Given certain conditions, we query IDispatch objects on demand for the
interface. If it's available, we either create a JS proxy object that is
associated with the dispatch object exposing the interface, or re-acquire
another one (possibly dangling). It is also associated on the other end, so
that we keep a 1:1 mapping between the JS proxy object and the actual dispatch
object, to avoid having multiple JS proxy objects for the same mshtml object
(which would result in different dispids on them, and subtle bugs).

This keeps them both in sync at all times, on demand, without having to
pre-create any objects, and deals with dynamic props and such automatically.

Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---

There's only one caveat here: since the JS proxy object references the
dispatch object, while the dispatch object is also associated to the JS
proxy object, the JS proxy object must be kept alive even when its refcount
is zero. We can't hold a reference from the dispatch object, because that
would create a circular reference.

In effect, we *need* to know *when* the JS proxy object has no references
from jscript part, to release the dispatch object. But the dispatch object
itself might still remain alive (if kept alive by mshtml refs), and the
JS proxy object must also remain associated in this case, hence we keep it
alive even when refcount == 0, but remove the proxy association on its side.

This is slightly ugly, but it is the best approach I could think of, to
avoid bugs or having to rely on more complicated hash table lookups.

Keep in mind that we have to keep the JS proxy object alive as long as it
is associated with the dispatch object, because it could have proxy props
retrieved, and they must always match among multiple references of this
object, if they refer to the same underlying mshtml object (we can't have
different dispids or "reset" them).

 dlls/jscript/dispex.c     | 207 +++++++++++++++++++++++++++++++++++---
 dlls/jscript/engine.c     |  17 +++-
 dlls/jscript/enumerator.c |  10 +-
 dlls/jscript/function.c   |  98 +++++++++++++++++-
 dlls/jscript/jscript.h    |  26 +++++
 dlls/jscript/jsutils.c    |  21 ++--
 dlls/jscript/jsval.h      |   2 +-
 dlls/jscript/object.c     |   7 ++
 dlls/jscript/vbarray.c    |   4 +-
 9 files changed, 356 insertions(+), 36 deletions(-)

diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c
index c7e4ba9..1ea0f19 100644
--- a/dlls/jscript/dispex.c
+++ b/dlls/jscript/dispex.c
@@ -33,6 +33,7 @@ static const GUID GUID_JScriptTypeInfo = {0xc59c6b12,0xf6c1,0x11cf,{0x88,0x35,0x
 typedef enum {
     PROP_JSVAL,
     PROP_BUILTIN,
+    PROP_PROXY,
     PROP_PROTREF,
     PROP_ACCESSOR,
     PROP_DELETED,
@@ -50,6 +51,10 @@ struct _dispex_prop_t {
         const builtin_prop_t *p;
         DWORD ref;
         unsigned idx;
+        struct {
+            DISPID id;
+            DWORD flags;
+        } proxy;
         struct {
             jsdisp_t *getter;
             jsdisp_t *setter;
@@ -199,6 +204,30 @@ static inline dispex_prop_t* alloc_prop(jsdisp_t *This, const WCHAR *name, prop_
     return prop;
 }
 
+static inline dispex_prop_t* alloc_proxy_prop(jsdisp_t *This, dispex_prop_t *prop, const WCHAR *name, DISPID id)
+{
+    unsigned flags, prop_flags;
+
+    flags = prop_flags = This->proxy->lpVtbl->GetPropFlags(This->proxy, id);
+
+    if(flags & PROPF_METHOD)
+        flags |= PROPF_WRITABLE | PROPF_CONFIGURABLE;
+    flags &= PROPF_ALL;
+
+    if(prop) {
+        prop->type = PROP_PROXY;
+        prop->flags = flags;
+    }else {
+        prop = alloc_prop(This, name, PROP_PROXY, flags);
+        if(!prop)
+            return NULL;
+    }
+
+    prop->u.proxy.id = id;
+    prop->u.proxy.flags = prop_flags;
+    return prop;
+}
+
 static dispex_prop_t *alloc_protref(jsdisp_t *This, const WCHAR *name, DWORD ref)
 {
     dispex_prop_t *ret;
@@ -235,6 +264,23 @@ static HRESULT find_prop_name(jsdisp_t *This, unsigned hash, const WCHAR *name,
         pos = This->props[pos].bucket_next;
     }
 
+    if(This->proxy) {
+        DISPID id;
+
+        if(FAILED(IDispatchEx_GetDispID((IDispatchEx*)This->proxy, (WCHAR*)name, make_grfdex(This->ctx, fdexNameCaseSensitive), &id)))
+            id = DISPID_UNKNOWN;
+        if(id == DISPID_UNKNOWN) {
+            *ret = NULL;
+            return S_OK;
+        }
+        prop = alloc_proxy_prop(This, NULL, name, id);
+        if(!prop)
+            return E_OUTOFMEMORY;
+
+        *ret = prop;
+        return S_OK;
+    }
+
     builtin = find_builtin_prop(This, name);
     if(builtin) {
         unsigned flags = builtin->flags;
@@ -314,11 +360,28 @@ static HRESULT find_prop_name_prot(jsdisp_t *This, unsigned hash, const WCHAR *n
 
 static HRESULT ensure_prop_name(jsdisp_t *This, const WCHAR *name, DWORD create_flags, dispex_prop_t **ret)
 {
+    unsigned hash = string_hash(name);
     dispex_prop_t *prop;
     HRESULT hres;
 
-    hres = find_prop_name_prot(This, string_hash(name), name, &prop);
+    hres = find_prop_name_prot(This, hash, name, &prop);
     if(SUCCEEDED(hres) && (!prop || prop->type == PROP_DELETED)) {
+        if(This->proxy) {
+            DWORD fdex = make_grfdex(This->ctx, fdexNameEnsure | fdexNameCaseSensitive);
+            DISPID id;
+
+            TRACE("creating proxy prop %s\n", debugstr_w(name));
+
+            hres = IDispatchEx_GetDispID((IDispatchEx*)This->proxy, (WCHAR*)name, fdex, &id);
+            if(FAILED(hres))
+                return hres;
+            prop = alloc_proxy_prop(This, prop, name, id);
+            if(!prop)
+                return E_OUTOFMEMORY;
+            *ret = prop;
+            return S_OK;
+        }
+
         TRACE("creating prop %s flags %x\n", debugstr_w(name), create_flags);
 
         if(prop) {
@@ -356,7 +419,7 @@ static IDispatch *get_this(DISPPARAMS *dp)
     return NULL;
 }
 
-static HRESULT convert_params(const DISPPARAMS *dp, jsval_t *buf, unsigned *argc, jsval_t **ret)
+static HRESULT convert_params(script_ctx_t *ctx, const DISPPARAMS *dp, jsval_t *buf, unsigned *argc, jsval_t **ret)
 {
     jsval_t *argv;
     unsigned cnt;
@@ -374,7 +437,7 @@ static HRESULT convert_params(const DISPPARAMS *dp, jsval_t *buf, unsigned *argc
     }
 
     for(i = 0; i < cnt; i++) {
-        hres = variant_to_jsval(dp->rgvarg+dp->cArgs-i-1, argv+i);
+        hres = variant_to_jsval(ctx, dp->rgvarg+dp->cArgs-i-1, argv+i);
         if(FAILED(hres)) {
             while(i--)
                 jsval_release(argv[i]);
@@ -419,6 +482,23 @@ static HRESULT prop_get(jsdisp_t *This, dispex_prop_t *prop,  jsval_t *r)
             *r = jsval_obj(obj);
         }
         break;
+    case PROP_PROXY:
+        if(!(prop->u.proxy.flags & PROPF_METHOD)) {
+            hres = disp_propget(This->ctx, (IDispatch*)prop_obj->proxy, prop->u.proxy.id, r);
+        }else {
+            jsdisp_t *obj;
+
+            hres = create_proxy_function(This->ctx, (IDispatchEx*)prop_obj->proxy, prop->u.proxy.id, prop->u.proxy.flags, &obj);
+            if(FAILED(hres))
+                break;
+
+            prop->type = PROP_JSVAL;
+            prop->u.val = jsval_obj(obj);
+
+            jsdisp_addref(obj);
+            *r = jsval_obj(obj);
+        }
+        break;
     case PROP_JSVAL:
         hres = jsval_copy(prop->u.val, r);
         break;
@@ -438,6 +518,8 @@ static HRESULT prop_get(jsdisp_t *This, dispex_prop_t *prop,  jsval_t *r)
         ERR("type %d\n", prop->type);
         return E_FAIL;
     }
+    if(SUCCEEDED(hres))
+        hres = convert_to_proxy(This->ctx, r);
 
     if(FAILED(hres)) {
         TRACE("fail %08x\n", hres);
@@ -472,6 +554,10 @@ static HRESULT prop_put(jsdisp_t *This, dispex_prop_t *prop, jsval_t val)
             return S_OK;
         }
         return prop->u.p->setter(This->ctx, This, val);
+    case PROP_PROXY:
+        if(!(prop->flags & PROPF_WRITABLE))
+            return S_OK;
+        return disp_propput(This->ctx, (IDispatch*)This->proxy, prop->u.proxy.id, val);
     case PROP_PROTREF:
     case PROP_DELETED:
         if(!This->extensible)
@@ -521,7 +607,8 @@ static HRESULT invoke_prop_func(jsdisp_t *This, IDispatch *jsthis, dispex_prop_t
     HRESULT hres;
 
     switch(prop->type) {
-    case PROP_BUILTIN: {
+    case PROP_BUILTIN:
+    case PROP_PROXY:
         if(flags == DISPATCH_CONSTRUCT && (prop->flags & PROPF_METHOD)) {
             WARN("%s is not a constructor\n", debugstr_w(prop->name));
             return E_INVALIDARG;
@@ -530,6 +617,17 @@ static HRESULT invoke_prop_func(jsdisp_t *This, IDispatch *jsthis, dispex_prop_t
         if(prop->name || This->builtin_info->class != JSCLASS_FUNCTION) {
             vdisp_t vthis;
 
+            if(prop->type == PROP_PROXY) {
+                jsdisp_t *jsdisp = to_jsdisp(jsthis);
+                if(jsdisp)
+                    jsthis = (IDispatch*)jsdisp->proxy;
+                if(jsthis != (IDispatch*)This->proxy) {
+                    WARN("Incompatible this %p for proxy %p, id %d\n", jsthis, This->proxy, prop->u.proxy.id);
+                    return E_UNEXPECTED;
+                }
+                return disp_call(This->ctx, jsthis, prop->u.proxy.id, flags & ~DISPATCH_JSCRIPT_INTERNAL_MASK, argc, argv, r);
+            }
+
             if(This->builtin_info->class != JSCLASS_FUNCTION && prop->u.p->invoke != JSGlobal_eval)
                 flags &= ~DISPATCH_JSCRIPT_INTERNAL_MASK;
             if(jsthis)
@@ -542,8 +640,9 @@ static HRESULT invoke_prop_func(jsdisp_t *This, IDispatch *jsthis, dispex_prop_t
             /* Function object calls are special case */
             hres = Function_invoke(This, jsthis, flags, argc, argv, r);
         }
+        if(SUCCEEDED(hres))
+            hres = convert_to_proxy(This->ctx, r);
         return hres;
-    }
     case PROP_PROTREF:
         return invoke_prop_func(This->prototype, jsthis ? jsthis : (IDispatch *)&This->IDispatchEx_iface,
                                 This->prototype->props+prop->u.ref, flags, argc, argv, r, caller);
@@ -1563,7 +1662,7 @@ static HRESULT WINAPI DispatchEx_InvokeEx(IDispatchEx *iface, DISPID id, LCID lc
         jsval_t *argv, buf[6], r;
         unsigned argc;
 
-        hres = convert_params(pdp, buf, &argc, &argv);
+        hres = convert_params(This->ctx, pdp, buf, &argc, &argv);
         if(FAILED(hres))
             break;
 
@@ -1601,7 +1700,7 @@ static HRESULT WINAPI DispatchEx_InvokeEx(IDispatchEx *iface, DISPID id, LCID lc
             break;
         }
 
-        hres = variant_to_jsval(pdp->rgvarg+i, &val);
+        hres = variant_to_jsval(This->ctx, pdp->rgvarg+i, &val);
         if(FAILED(hres))
             break;
 
@@ -1618,8 +1717,16 @@ static HRESULT WINAPI DispatchEx_InvokeEx(IDispatchEx *iface, DISPID id, LCID lc
     return leave_script(This->ctx, hres);
 }
 
-static HRESULT delete_prop(dispex_prop_t *prop, BOOL *ret)
+static HRESULT delete_prop(jsdisp_t *prop_obj, dispex_prop_t *prop, BOOL *ret)
 {
+    if(prop->type == PROP_PROXY) {
+        BOOL deleted;
+        HRESULT hres = prop_obj->proxy->lpVtbl->DeleteProp(prop_obj->proxy, prop->u.proxy.id, &deleted);
+        if(SUCCEEDED(hres) && deleted)
+            prop->type = PROP_DELETED;
+        return hres;
+    }
+
     if(!(prop->flags & PROPF_CONFIGURABLE)) {
         *ret = FALSE;
         return S_OK;
@@ -1656,7 +1763,7 @@ static HRESULT WINAPI DispatchEx_DeleteMemberByName(IDispatchEx *iface, BSTR bst
         return S_OK;
     }
 
-    return delete_prop(prop, &b);
+    return delete_prop(This, prop, &b);
 }
 
 static HRESULT WINAPI DispatchEx_DeleteMemberByDispID(IDispatchEx *iface, DISPID id)
@@ -1673,7 +1780,7 @@ static HRESULT WINAPI DispatchEx_DeleteMemberByDispID(IDispatchEx *iface, DISPID
         return DISP_E_MEMBERNOTFOUND;
     }
 
-    return delete_prop(prop, &b);
+    return delete_prop(This, prop, &b);
 }
 
 static HRESULT WINAPI DispatchEx_GetMemberProperties(IDispatchEx *iface, DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
@@ -1808,10 +1915,73 @@ HRESULT create_dispex(script_ctx_t *ctx, const builtin_info_t *builtin_info, jsd
     return S_OK;
 }
 
+static const builtin_info_t proxy_dispex_info = {
+    JSCLASS_OBJECT,
+    {NULL, NULL, 0},
+    0, NULL,
+    NULL,
+    NULL
+};
+
+HRESULT convert_to_proxy(script_ctx_t *ctx, jsval_t *val)
+{
+    IWineDispatchProxyPrivate *proxy;
+    jsdisp_t **jsdisp_ref;
+    IDispatch *obj;
+    HRESULT hres;
+
+    if(ctx->version < SCRIPTLANGUAGEVERSION_ES5 || !val || !is_object_instance(*val))
+        return S_OK;
+    obj = get_object(*val);
+    if(!obj || to_jsdisp(obj))
+        return S_OK;
+
+    if(FAILED(IDispatch_QueryInterface(obj, &IID_IWineDispatchProxyPrivate, (void**)&proxy)) || !proxy)
+        return S_OK;
+    IDispatch_Release(obj);
+
+    jsdisp_ref = proxy->lpVtbl->GetProxyFieldRef(proxy);
+
+    if(*jsdisp_ref) {
+        /* Re-acquire the proxy if it's an old dangling proxy */
+        if((*jsdisp_ref)->ref == 0)
+            (*jsdisp_ref)->proxy = proxy;
+        else
+            IDispatchEx_Release((IDispatchEx*)proxy);
+
+        *val = jsval_obj(jsdisp_addref(*jsdisp_ref));
+        return S_OK;
+    }
+
+    hres = create_dispex(ctx, &proxy_dispex_info, ctx->object_prototype, jsdisp_ref);
+    if(FAILED(hres)) {
+        IDispatchEx_Release((IDispatchEx*)proxy);
+        return hres;
+    }
+
+    (*jsdisp_ref)->props[0].type = PROP_PROXY;
+    (*jsdisp_ref)->props[0].u.proxy.id = DISPID_VALUE;
+    (*jsdisp_ref)->props[0].u.proxy.flags = proxy->lpVtbl->GetPropFlags(proxy, DISPID_VALUE);
+    (*jsdisp_ref)->proxy = proxy;
+
+    *val = jsval_obj(*jsdisp_ref);
+    return S_OK;
+}
+
 void jsdisp_free(jsdisp_t *obj)
 {
     dispex_prop_t *prop;
 
+    /* If we are a proxy, stay alive and keep it associated with the disp, since
+       we can be re-acquired at some later point. When the underlying disp is
+       actually destroyed, it should let go and destroy us for real. */
+    if(obj->proxy) {
+        IDispatchEx *disp = (IDispatchEx*)obj->proxy;
+        obj->proxy = NULL;
+        IDispatchEx_Release(disp);
+        return;
+    }
+
     TRACE("(%p)\n", obj);
 
     for(prop = obj->props; prop < obj->props+obj->prop_cnt; prop++) {
@@ -1931,6 +2101,8 @@ HRESULT jsdisp_call_value(jsdisp_t *jsfunc, IDispatch *jsthis, WORD flags, unsig
 
     if(is_class(jsfunc, JSCLASS_FUNCTION)) {
         hres = Function_invoke(jsfunc, jsthis, flags, argc, argv, r);
+    }else if(jsfunc->proxy) {
+        hres = disp_call_value(jsfunc->ctx, (IDispatch*)jsfunc->proxy, jsthis, flags, argc, argv, r);
     }else {
         vdisp_t vdisp;
 
@@ -1944,6 +2116,8 @@ HRESULT jsdisp_call_value(jsdisp_t *jsfunc, IDispatch *jsthis, WORD flags, unsig
         hres = jsfunc->builtin_info->value_prop.invoke(jsfunc->ctx, &vdisp, flags, argc, argv, r);
         vdisp_release(&vdisp);
     }
+    if(SUCCEEDED(hres))
+        hres = convert_to_proxy(jsfunc->ctx, r);
     return hres;
 }
 
@@ -2082,7 +2256,7 @@ HRESULT disp_call(script_ctx_t *ctx, IDispatch *disp, DISPID id, WORD flags, uns
         heap_free(dp.rgvarg);
 
     if(SUCCEEDED(hres) && ret)
-        hres = variant_to_jsval(&retv, ret);
+        hres = variant_to_jsval(ctx, &retv, ret);
     VariantClear(&retv);
     return hres;
 }
@@ -2148,7 +2322,7 @@ HRESULT disp_call_value(script_ctx_t *ctx, IDispatch *disp, IDispatch *jsthis, W
     if(!r)
         return S_OK;
 
-    hres = variant_to_jsval(&retv, r);
+    hres = variant_to_jsval(ctx, &retv, r);
     VariantClear(&retv);
     return hres;
 }
@@ -2316,7 +2490,7 @@ HRESULT disp_propget(script_ctx_t *ctx, IDispatch *disp, DISPID id, jsval_t *val
     V_VT(&var) = VT_EMPTY;
     hres = disp_invoke(ctx, disp, id, INVOKE_PROPERTYGET, &dp, &var);
     if(SUCCEEDED(hres)) {
-        hres = variant_to_jsval(&var, val);
+        hres = variant_to_jsval(ctx, &var, val);
         VariantClear(&var);
     }
     return hres;
@@ -2335,7 +2509,7 @@ HRESULT jsdisp_delete_idx(jsdisp_t *obj, DWORD idx)
     if(FAILED(hres) || !prop)
         return hres;
 
-    hres = delete_prop(prop, &b);
+    hres = delete_prop(obj, prop, &b);
     if(FAILED(hres))
         return hres;
     return b ? S_OK : JS_E_INVALID_ACTION;
@@ -2353,7 +2527,7 @@ HRESULT disp_delete(IDispatch *disp, DISPID id, BOOL *ret)
 
         prop = get_prop(jsdisp, id);
         if(prop)
-            hres = delete_prop(prop, ret);
+            hres = delete_prop(jsdisp, prop, ret);
         else
             hres = DISP_E_MEMBERNOTFOUND;
 
@@ -2424,7 +2598,7 @@ HRESULT disp_delete_name(script_ctx_t *ctx, IDispatch *disp, jsstr_t *name, BOOL
 
         hres = find_prop_name(jsdisp, string_hash(ptr), ptr, &prop);
         if(prop) {
-            hres = delete_prop(prop, ret);
+            hres = delete_prop(jsdisp, prop, ret);
         }else {
             *ret = TRUE;
             hres = S_OK;
@@ -2480,6 +2654,7 @@ HRESULT jsdisp_get_own_property(jsdisp_t *obj, const WCHAR *name, BOOL flags_onl
 
     switch(prop->type) {
     case PROP_BUILTIN:
+    case PROP_PROXY:
     case PROP_JSVAL:
         desc->mask |= PROPF_WRITABLE;
         desc->explicit_value = TRUE;
diff --git a/dlls/jscript/engine.c b/dlls/jscript/engine.c
index 47b4e56..4ff4297 100644
--- a/dlls/jscript/engine.c
+++ b/dlls/jscript/engine.c
@@ -491,15 +491,24 @@ static HRESULT disp_cmp(IDispatch *disp1, IDispatch *disp2, BOOL *ret)
 {
     IObjectIdentity *identity;
     IUnknown *unk1, *unk2;
+    jsdisp_t *jsdisp;
     HRESULT hres;
 
-    if(disp1 == disp2) {
-        *ret = TRUE;
+    if(!disp1 || !disp2) {
+        *ret = (disp1 == disp2);
         return S_OK;
     }
 
-    if(!disp1 || !disp2) {
-        *ret = FALSE;
+    jsdisp = to_jsdisp(disp1);
+    if(jsdisp && jsdisp->proxy)
+        disp1 = (IDispatch*)jsdisp->proxy;
+
+    jsdisp = to_jsdisp(disp2);
+    if(jsdisp && jsdisp->proxy)
+        disp2 = (IDispatch*)jsdisp->proxy;
+
+    if(disp1 == disp2) {
+        *ret = TRUE;
         return S_OK;
     }
 
diff --git a/dlls/jscript/enumerator.c b/dlls/jscript/enumerator.c
index dea1940..eb6baf6 100644
--- a/dlls/jscript/enumerator.c
+++ b/dlls/jscript/enumerator.c
@@ -48,7 +48,7 @@ static inline EnumeratorInstance *enumerator_this(vdisp_t *jsthis)
     return is_vclass(jsthis, JSCLASS_ENUMERATOR) ? enumerator_from_vdisp(jsthis) : NULL;
 }
 
-static inline HRESULT enumvar_get_next_item(EnumeratorInstance *This)
+static inline HRESULT enumvar_get_next_item(EnumeratorInstance *This, script_ctx_t *ctx)
 {
     HRESULT hres;
     VARIANT nextitem;
@@ -64,7 +64,7 @@ static inline HRESULT enumvar_get_next_item(EnumeratorInstance *This)
     hres = IEnumVARIANT_Next(This->enumvar, 1, &nextitem, NULL);
     if (hres == S_OK)
     {
-        hres = variant_to_jsval(&nextitem, &This->item);
+        hres = variant_to_jsval(ctx, &nextitem, &This->item);
         VariantClear(&nextitem);
         if (FAILED(hres))
         {
@@ -138,7 +138,7 @@ static HRESULT Enumerator_moveFirst(script_ctx_t *ctx, vdisp_t *jsthis, WORD fla
             return hres;
 
         This->atend = FALSE;
-        hres = enumvar_get_next_item(This);
+        hres = enumvar_get_next_item(This, ctx);
         if(FAILED(hres))
             return hres;
     }
@@ -161,7 +161,7 @@ static HRESULT Enumerator_moveNext(script_ctx_t *ctx, vdisp_t *jsthis, WORD flag
 
     if (This->enumvar)
     {
-        hres = enumvar_get_next_item(This);
+        hres = enumvar_get_next_item(This, ctx);
         if (FAILED(hres))
             return hres;
     }
@@ -276,7 +276,7 @@ static HRESULT create_enumerator(script_ctx_t *ctx, jsval_t *argv, jsdisp_t **re
 
     enumerator->enumvar = enumvar;
     enumerator->atend = !enumvar;
-    hres = enumvar_get_next_item(enumerator);
+    hres = enumvar_get_next_item(enumerator, ctx);
     if (FAILED(hres))
     {
         jsdisp_release(&enumerator->dispex);
diff --git a/dlls/jscript/function.c b/dlls/jscript/function.c
index 318d6be..7cd83a3 100644
--- a/dlls/jscript/function.c
+++ b/dlls/jscript/function.c
@@ -54,6 +54,12 @@ typedef struct {
     const WCHAR *name;
 } NativeFunction;
 
+typedef struct {
+    FunctionInstance function;
+    IDispatchEx *proxy;
+    DISPID id;
+} ProxyFunction;
+
 typedef struct {
     FunctionInstance function;
     FunctionInstance *target;
@@ -340,7 +346,7 @@ static HRESULT Function_apply(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, un
 
     TRACE("\n");
 
-    if(!(function = function_this(jsthis)) && (jsthis->flags & VDISP_JSDISP))
+    if(!(function = function_this(jsthis)) && is_jsdisp(jsthis) && !get_jsdisp(jsthis)->proxy)
         return JS_E_FUNCTION_EXPECTED;
 
     if(argc) {
@@ -707,6 +713,96 @@ HRESULT create_builtin_constructor(script_ctx_t *ctx, builtin_invoke_t value_pro
     return S_OK;
 }
 
+static HRESULT ProxyFunction_call(script_ctx_t *ctx, FunctionInstance *func, IDispatch *this_disp, unsigned flags,
+        unsigned argc, jsval_t *argv, jsval_t *r)
+{
+    ProxyFunction *function = (ProxyFunction*)func;
+    jsdisp_t *jsdisp;
+
+    if(!this_disp)
+        this_disp = lookup_global_host(ctx);
+
+    jsdisp = to_jsdisp(this_disp);
+    if(jsdisp)
+        this_disp = (IDispatch*)jsdisp->proxy;
+    if(this_disp != (IDispatch*)function->proxy) {
+        WARN("Incompatible this %p for proxy %p, id %d\n", this_disp, function->proxy, function->id);
+        return E_UNEXPECTED;
+    }
+
+    return disp_call(ctx, this_disp, function->id, flags & ~DISPATCH_JSCRIPT_INTERNAL_MASK, argc, argv, r);
+}
+
+static HRESULT ProxyFunction_toString(FunctionInstance *func, jsstr_t **ret)
+{
+    ProxyFunction *function = (ProxyFunction*)func;
+    BSTR name = NULL;
+    DWORD name_len;
+    jsstr_t *str;
+    WCHAR *ptr;
+
+    static const WCHAR proxy_prefixW[] = L"\nfunction ";
+    static const WCHAR proxy_suffixW[] = L"() {\n    [native code]\n}\n";
+
+    if(FAILED(IDispatchEx_GetMemberName(function->proxy, function->id, &name)))
+        name = NULL;
+
+    name_len = name ? SysStringLen(name) : 0;
+    str = jsstr_alloc_buf(ARRAY_SIZE(proxy_prefixW) + ARRAY_SIZE(proxy_suffixW) + name_len - 2, &ptr);
+    if(!str)
+        return E_OUTOFMEMORY;
+
+    memcpy(ptr, proxy_prefixW, sizeof(proxy_prefixW));
+    ptr += ARRAY_SIZE(proxy_prefixW) - 1;
+    memcpy(ptr, name, name_len*sizeof(WCHAR));
+    ptr += name_len;
+    memcpy(ptr, proxy_suffixW, sizeof(proxy_suffixW));
+
+    SysFreeString(name);
+    *ret = str;
+    return S_OK;
+}
+
+static function_code_t *ProxyFunction_get_code(FunctionInstance *func)
+{
+    return NULL;
+}
+
+static void ProxyFunction_destructor(FunctionInstance *func)
+{
+    ProxyFunction *function = (ProxyFunction*)func;
+    IDispatchEx_Release(function->proxy);
+}
+
+static const function_vtbl_t ProxyFunctionVtbl = {
+    ProxyFunction_call,
+    ProxyFunction_toString,
+    ProxyFunction_get_code,
+    ProxyFunction_destructor
+};
+
+HRESULT create_proxy_function(script_ctx_t *ctx, IDispatchEx *proxy, DISPID id, DWORD flags, jsdisp_t **ret)
+{
+    ProxyFunction *function;
+    HRESULT hres;
+
+    hres = create_function(ctx, NULL, &ProxyFunctionVtbl, sizeof(ProxyFunction), flags, FALSE, NULL, (void**)&function);
+    if(FAILED(hres))
+        return hres;
+
+    if(FAILED(hres)) {
+        jsdisp_release(&function->function.dispex);
+        return hres;
+    }
+
+    function->proxy = proxy;
+    function->id = id;
+    IDispatchEx_AddRef(function->proxy);
+
+    *ret = &function->function.dispex;
+    return S_OK;
+}
+
 static HRESULT InterpretedFunction_call(script_ctx_t *ctx, FunctionInstance *func, IDispatch *this_obj, unsigned flags,
          unsigned argc, jsval_t *argv, jsval_t *r)
 {
diff --git a/dlls/jscript/jscript.h b/dlls/jscript/jscript.h
index 90a5e15..1d14271 100644
--- a/dlls/jscript/jscript.h
+++ b/dlls/jscript/jscript.h
@@ -47,6 +47,28 @@
 #define SCRIPTLANGUAGEVERSION_ES5  0x102
 #define SCRIPTLANGUAGEVERSION_ES6  0x103
 
+/*
+ * This is Wine jscript extension, used for mshtml objects so they act like JS objects.
+ * Extends IDispatchEx.
+ *
+ * NOTE: Keep in sync with mshtml_private.h in mshtml.dll
+ */
+DEFINE_GUID(IID_IWineDispatchProxyPrivate, 0xd359f2fe,0x5531,0x741b,0xa4,0x1a,0x5c,0xf9,0x2e,0xdc,0x97,0x1b);
+typedef struct _IWineDispatchProxyPrivate IWineDispatchProxyPrivate;
+
+typedef struct {
+    IDispatchExVtbl dispex;
+    void* (STDMETHODCALLTYPE *GetProxyFieldRef)(IWineDispatchProxyPrivate *This);
+    DWORD (STDMETHODCALLTYPE *GetPropFlags)(IWineDispatchProxyPrivate *This, DISPID id);
+    HRESULT (STDMETHODCALLTYPE *DeleteProp)(IWineDispatchProxyPrivate *This, DISPID id, BOOL *deleted);
+} IWineDispatchProxyPrivateVtbl;
+
+struct _IWineDispatchProxyPrivate {
+    const IWineDispatchProxyPrivateVtbl *lpVtbl;
+};
+
+
+
 typedef struct _jsval_t jsval_t;
 typedef struct _jsstr_t jsstr_t;
 typedef struct _jsexcept_t jsexcept_t;
@@ -91,6 +113,7 @@ typedef struct jsdisp_t jsdisp_t;
 extern HINSTANCE jscript_hinstance DECLSPEC_HIDDEN;
 HRESULT get_dispatch_typeinfo(ITypeInfo**) DECLSPEC_HIDDEN;
 
+/* NOTE: Keep in sync with mshtml_private.h in mshtml.dll */
 #define PROPF_ARGMASK       0x00ff
 #define PROPF_METHOD        0x0100
 #define PROPF_CONSTR        0x0200
@@ -253,6 +276,7 @@ struct jsdisp_t {
     BOOL extensible;
 
     jsdisp_t *prototype;
+    IWineDispatchProxyPrivate *proxy;
 
     const builtin_info_t *builtin_info;
 };
@@ -302,6 +326,7 @@ enum jsdisp_enum_type {
 HRESULT create_dispex(script_ctx_t*,const builtin_info_t*,jsdisp_t*,jsdisp_t**) DECLSPEC_HIDDEN;
 HRESULT init_dispex(jsdisp_t*,script_ctx_t*,const builtin_info_t*,jsdisp_t*) DECLSPEC_HIDDEN;
 HRESULT init_dispex_from_constr(jsdisp_t*,script_ctx_t*,const builtin_info_t*,jsdisp_t*) DECLSPEC_HIDDEN;
+HRESULT convert_to_proxy(script_ctx_t*,jsval_t*) DECLSPEC_HIDDEN;
 
 HRESULT disp_call(script_ctx_t*,IDispatch*,DISPID,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
 HRESULT disp_call_value(script_ctx_t*,IDispatch*,IDispatch*,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
@@ -333,6 +358,7 @@ HRESULT create_builtin_function(script_ctx_t*,builtin_invoke_t,const WCHAR*,cons
         jsdisp_t*,jsdisp_t**) DECLSPEC_HIDDEN;
 HRESULT create_builtin_constructor(script_ctx_t*,builtin_invoke_t,const WCHAR*,const builtin_info_t*,DWORD,
         jsdisp_t*,jsdisp_t**) DECLSPEC_HIDDEN;
+HRESULT create_proxy_function(script_ctx_t*,IDispatchEx*,DISPID,DWORD,jsdisp_t**) DECLSPEC_HIDDEN;
 HRESULT Function_invoke(jsdisp_t*,IDispatch*,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
 
 HRESULT Function_value(script_ctx_t*,vdisp_t*,WORD,unsigned,jsval_t*,jsval_t*) DECLSPEC_HIDDEN;
diff --git a/dlls/jscript/jsutils.c b/dlls/jscript/jsutils.c
index 3c3cd01..1b1b904 100644
--- a/dlls/jscript/jsutils.c
+++ b/dlls/jscript/jsutils.c
@@ -246,7 +246,7 @@ HRESULT jsval_copy(jsval_t v, jsval_t *r)
     return E_FAIL;
 }
 
-HRESULT variant_to_jsval(VARIANT *var, jsval_t *r)
+HRESULT variant_to_jsval(script_ctx_t *ctx, VARIANT *var, jsval_t *r)
 {
     if(V_VT(var) == (VT_VARIANT|VT_BYREF))
         var = V_VARIANTREF(var);
@@ -285,7 +285,7 @@ HRESULT variant_to_jsval(VARIANT *var, jsval_t *r)
         if(V_DISPATCH(var))
             IDispatch_AddRef(V_DISPATCH(var));
         *r = jsval_disp(V_DISPATCH(var));
-        return S_OK;
+        return convert_to_proxy(ctx, r);
     }
     case VT_I1:
         *r = jsval_number(V_I1(var));
@@ -329,7 +329,7 @@ HRESULT variant_to_jsval(VARIANT *var, jsval_t *r)
             hres = IUnknown_QueryInterface(V_UNKNOWN(var), &IID_IDispatch, (void**)&disp);
             if(SUCCEEDED(hres)) {
                 *r = jsval_disp(disp);
-                return S_OK;
+                return convert_to_proxy(ctx, r);
             }
         }else {
             *r = jsval_disp(NULL);
@@ -343,6 +343,8 @@ HRESULT variant_to_jsval(VARIANT *var, jsval_t *r)
 
 HRESULT jsval_to_variant(jsval_t val, VARIANT *retv)
 {
+    IDispatch *disp;
+
     switch(jsval_type(val)) {
     case JSV_UNDEFINED:
         V_VT(retv) = VT_EMPTY;
@@ -352,9 +354,14 @@ HRESULT jsval_to_variant(jsval_t val, VARIANT *retv)
         return S_OK;
     case JSV_OBJECT:
         V_VT(retv) = VT_DISPATCH;
-        if(get_object(val))
-            IDispatch_AddRef(get_object(val));
-        V_DISPATCH(retv) = get_object(val);
+        disp = get_object(val);
+        if(disp) {
+            jsdisp_t *jsdisp = to_jsdisp(disp);
+            if(jsdisp && jsdisp->proxy)
+                disp = (IDispatch*)jsdisp->proxy;
+            IDispatch_AddRef(disp);
+        }
+        V_DISPATCH(retv) = disp;
         return S_OK;
     case JSV_STRING:
         V_VT(retv) = VT_BSTR;
@@ -884,7 +891,7 @@ HRESULT variant_change_type(script_ctx_t *ctx, VARIANT *dst, VARIANT *src, VARTY
     jsval_t val;
     HRESULT hres;
 
-    hres = variant_to_jsval(src, &val);
+    hres = variant_to_jsval(ctx, src, &val);
     if(FAILED(hres))
         return hres;
 
diff --git a/dlls/jscript/jsval.h b/dlls/jscript/jsval.h
index 9f451b1..c79bef4 100644
--- a/dlls/jscript/jsval.h
+++ b/dlls/jscript/jsval.h
@@ -241,7 +241,7 @@ static inline BOOL get_bool(jsval_t v)
     return __JSVAL_BOOL(v);
 }
 
-HRESULT variant_to_jsval(VARIANT*,jsval_t*) DECLSPEC_HIDDEN;
+HRESULT variant_to_jsval(script_ctx_t*,VARIANT*,jsval_t*) DECLSPEC_HIDDEN;
 HRESULT jsval_to_variant(jsval_t,VARIANT*) DECLSPEC_HIDDEN;
 void jsval_release(jsval_t) DECLSPEC_HIDDEN;
 HRESULT jsval_copy(jsval_t,jsval_t*) DECLSPEC_HIDDEN;
diff --git a/dlls/jscript/object.c b/dlls/jscript/object.c
index 169b47c..cf14a89 100644
--- a/dlls/jscript/object.c
+++ b/dlls/jscript/object.c
@@ -57,6 +57,13 @@ static HRESULT Object_toString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, u
     jsdisp = get_jsdisp(jsthis);
     if(!jsdisp) {
         str = L"[object Object]";
+    }else if(jsdisp->proxy) {
+        DISPID id;
+        if(FAILED(IDispatchEx_GetDispID((IDispatchEx*)jsdisp->proxy, (WCHAR*)L"toString", make_grfdex(ctx, fdexNameCaseSensitive), &id)))
+            id = DISPID_UNKNOWN;
+        if(id != DISPID_UNKNOWN)
+            return disp_call(ctx, (IDispatch*)jsdisp->proxy, id, DISPATCH_METHOD, 0, NULL, r);
+        str = L"[object Object]";
     }else if(names[jsdisp->builtin_info->class]) {
         str = names[jsdisp->builtin_info->class];
     }else {
diff --git a/dlls/jscript/vbarray.c b/dlls/jscript/vbarray.c
index 69a77f1..3294ee6 100644
--- a/dlls/jscript/vbarray.c
+++ b/dlls/jscript/vbarray.c
@@ -96,7 +96,7 @@ static HRESULT VBArray_getItem(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, un
         return hres;
 
     if(r) {
-        hres = variant_to_jsval(&out, r);
+        hres = variant_to_jsval(ctx, &out, r);
         VariantClear(&out);
     }
     return hres;
@@ -166,7 +166,7 @@ static HRESULT VBArray_toArray(script_ctx_t *ctx, vdisp_t *vthis, WORD flags, un
     }
 
     for(i=0; i<size; i++) {
-        hres = variant_to_jsval(v, &val);
+        hres = variant_to_jsval(ctx, v, &val);
         if(SUCCEEDED(hres)) {
             hres = jsdisp_propput_idx(array, i, val);
             jsval_release(val);
-- 
2.31.1




More information about the wine-devel mailing list