Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
02994bdd
Commit
02994bdd
authored
Jul 24, 2023
by
Yuxuan Shui
Committed by
Alexandre Julliard
Jul 31, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
mshtml: Implement window.MutationObserver with MutationObserver stub.
parent
17841cd5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
402 additions
and
2 deletions
+402
-2
htmlwindow.c
dlls/mshtml/htmlwindow.c
+32
-1
mshtml_private.h
dlls/mshtml/mshtml_private.h
+5
-1
mshtml_private_iface.idl
dlls/mshtml/mshtml_private_iface.idl
+14
-0
mutation.c
dlls/mshtml/mutation.c
+293
-0
documentmode.js
dlls/mshtml/tests/documentmode.js
+58
-0
No files found.
dlls/mshtml/htmlwindow.c
View file @
02994bdd
...
...
@@ -309,6 +309,9 @@ static void release_inner_window(HTMLInnerWindow *This)
if
(
This
->
mon
)
IMoniker_Release
(
This
->
mon
);
if
(
This
->
mutation_observer_ctor
)
IDispatch_Release
(
This
->
mutation_observer_ctor
);
free
(
This
);
}
...
...
@@ -3330,6 +3333,26 @@ static HRESULT WINAPI window_private_get_console(IWineHTMLWindowPrivate *iface,
return
S_OK
;
}
static
HRESULT
WINAPI
window_private_get_MutationObserver
(
IWineHTMLWindowPrivate
*
iface
,
IDispatch
**
mutation_observer
)
{
HTMLWindow
*
This
=
impl_from_IWineHTMLWindowPrivateVtbl
(
iface
);
HRESULT
hres
;
TRACE
(
"iface %p, mutation_observer %p.
\n
"
,
iface
,
mutation_observer
);
if
(
!
This
->
inner_window
->
mutation_observer_ctor
)
{
hres
=
create_mutation_observer_ctor
(
dispex_compat_mode
(
&
This
->
inner_window
->
event_target
.
dispex
),
&
This
->
inner_window
->
mutation_observer_ctor
);
if
(
FAILED
(
hres
))
return
hres
;
}
IDispatch_AddRef
(
This
->
inner_window
->
mutation_observer_ctor
);
*
mutation_observer
=
This
->
inner_window
->
mutation_observer_ctor
;
return
S_OK
;
}
static
const
IWineHTMLWindowPrivateVtbl
WineHTMLWindowPrivateVtbl
=
{
window_private_QueryInterface
,
window_private_AddRef
,
...
...
@@ -3343,6 +3366,7 @@ static const IWineHTMLWindowPrivateVtbl WineHTMLWindowPrivateVtbl = {
window_private_get_console
,
window_private_matchMedia
,
window_private_postMessage
,
window_private_get_MutationObserver
};
static
inline
HTMLWindow
*
impl_from_IWineHTMLWindowCompatPrivateVtbl
(
IWineHTMLWindowCompatPrivate
*
iface
)
...
...
@@ -3975,12 +3999,19 @@ static void HTMLWindow_init_dispex_info(dispex_data_t *info, compat_mode_t compa
{
DISPID_UNKNOWN
}
};
/* Hide props not available in IE10 */
static
const
dispex_hook_t
private_ie10_hooks
[]
=
{
{
DISPID_IWINEHTMLWINDOWPRIVATE_MUTATIONOBSERVER
},
{
DISPID_UNKNOWN
}
};
if
(
compat_mode
>=
COMPAT_MODE_IE9
)
dispex_info_add_interface
(
info
,
IHTMLWindow7_tid
,
NULL
);
else
dispex_info_add_interface
(
info
,
IWineHTMLWindowCompatPrivate_tid
,
NULL
);
if
(
compat_mode
>=
COMPAT_MODE_IE10
)
dispex_info_add_interface
(
info
,
IWineHTMLWindowPrivate_tid
,
NULL
);
dispex_info_add_interface
(
info
,
IWineHTMLWindowPrivate_tid
,
compat_mode
>=
COMPAT_MODE_IE11
?
NULL
:
private_ie10_hooks
);
dispex_info_add_interface
(
info
,
IHTMLWindow5_tid
,
NULL
);
dispex_info_add_interface
(
info
,
IHTMLWindow4_tid
,
compat_mode
>=
COMPAT_MODE_IE11
?
window4_ie11_hooks
:
NULL
);
...
...
dlls/mshtml/mshtml_private.h
View file @
02994bdd
...
...
@@ -294,7 +294,8 @@ typedef struct EventTarget EventTarget;
XIID(IWinePageTransitionEvent) \
XIID(IWineXMLHttpRequestPrivate) \
XIID(IWineMSHTMLConsole) \
XIID(IWineMSHTMLMediaQueryList)
XIID(IWineMSHTMLMediaQueryList) \
XIID(IWineMSHTMLMutationObserver)
typedef
enum
{
#define XIID(iface) iface ## _tid,
...
...
@@ -636,6 +637,7 @@ struct HTMLInnerWindow {
LONG
task_magic
;
IMoniker
*
mon
;
IDispatch
*
mutation_observer_ctor
;
nsChannelBSC
*
bscallback
;
struct
list
bindings
;
};
...
...
@@ -1496,3 +1498,5 @@ IInternetSecurityManager *get_security_manager(void);
extern
HINSTANCE
hInst
;
void
create_console
(
compat_mode_t
compat_mode
,
IWineMSHTMLConsole
**
ret
);
HRESULT
create_media_query_list
(
HTMLWindow
*
window
,
BSTR
media_query
,
IDispatch
**
ret
);
HRESULT
create_mutation_observer_ctor
(
compat_mode_t
compat_mode
,
IDispatch
**
ret
);
dlls/mshtml/mshtml_private_iface.idl
View file @
02994bdd
...
...
@@ -81,6 +81,17 @@ interface IWineMSHTMLConsole : IDispatch
oleautomation
,
dual
,
hidden
,
uuid
(
6
ac5491e
-
1758
-
4b82
-
98
a2
-
83
e31a7c8871
)
]
interface
IWineMSHTMLMutationObserver
:
IDispatch
{
}
[
odl
,
oleautomation
,
dual
,
hidden
,
uuid
(
fd55b4b6
-
2813
-
4
fb4
-829d-380099474
ab0
)
]
interface
IWineMSHTMLMediaQueryList
:
IDispatch
...
...
@@ -95,6 +106,7 @@ interface IWineMSHTMLMediaQueryList : IDispatch
HRESULT
removeListener
(
[
in
]
VARIANT
*
listener
)
;
}
const
long
DISPID_IWINEHTMLWINDOWPRIVATE_MUTATIONOBSERVER
=
55
;
[
odl
,
oleautomation
,
...
...
@@ -114,6 +126,8 @@ interface IWineHTMLWindowPrivate : IDispatch
HRESULT
matchMedia
(
[
in
]
BSTR
media_query
,
[
retval
,
out
]
IDispatch
**
media_query_list
)
;
[
id
(
54
)
]
HRESULT
postMessage
(
[
in
]
VARIANT
msg
,
[
in
]
BSTR
targetOrigin
,
[
in
,
optional
]
VARIANT
transfer
)
;
[
propget
,
id
(
DISPID_IWINEHTMLWINDOWPRIVATE_MUTATIONOBSERVER
)
]
HRESULT
MutationObserver
(
[
retval
,
out
]
IDispatch
**
observer_ctor
)
;
}
[
...
...
dlls/mshtml/mutation.c
View file @
02994bdd
...
...
@@ -1076,3 +1076,296 @@ void init_mutation(nsIComponentManager *component_manager)
if
(
NS_FAILED
(
nsres
))
ERR
(
"Could not create nsIContentUtils instance: %08lx
\n
"
,
nsres
);
}
struct
mutation_observer
{
IWineMSHTMLMutationObserver
IWineMSHTMLMutationObserver_iface
;
LONG
ref
;
DispatchEx
dispex
;
IDispatch
*
callback
;
};
static
inline
struct
mutation_observer
*
impl_from_IWineMSHTMLMutationObserver
(
IWineMSHTMLMutationObserver
*
iface
)
{
return
CONTAINING_RECORD
(
iface
,
struct
mutation_observer
,
IWineMSHTMLMutationObserver_iface
);
}
static
HRESULT
WINAPI
MutationObserver_QueryInterface
(
IWineMSHTMLMutationObserver
*
iface
,
REFIID
riid
,
void
**
ppv
)
{
struct
mutation_observer
*
This
=
impl_from_IWineMSHTMLMutationObserver
(
iface
);
TRACE
(
"(%p)->(%s %p)
\n
"
,
This
,
debugstr_mshtml_guid
(
riid
),
ppv
);
if
(
IsEqualGUID
(
&
IID_IUnknown
,
riid
)
||
IsEqualGUID
(
&
IID_IWineMSHTMLMutationObserver
,
riid
))
{
*
ppv
=
&
This
->
IWineMSHTMLMutationObserver_iface
;
}
else
if
(
dispex_query_interface
(
&
This
->
dispex
,
riid
,
ppv
))
{
return
*
ppv
?
S_OK
:
E_NOINTERFACE
;
}
else
{
WARN
(
"(%p)->(%s %p)
\n
"
,
This
,
debugstr_mshtml_guid
(
riid
),
ppv
);
*
ppv
=
NULL
;
return
E_NOINTERFACE
;
}
IUnknown_AddRef
((
IUnknown
*
)
*
ppv
);
return
S_OK
;
}
static
ULONG
WINAPI
MutationObserver_AddRef
(
IWineMSHTMLMutationObserver
*
iface
)
{
struct
mutation_observer
*
This
=
impl_from_IWineMSHTMLMutationObserver
(
iface
);
LONG
ref
=
InterlockedIncrement
(
&
This
->
ref
);
TRACE
(
"(%p) ref=%ld
\n
"
,
This
,
ref
);
return
ref
;
}
static
ULONG
WINAPI
MutationObserver_Release
(
IWineMSHTMLMutationObserver
*
iface
)
{
struct
mutation_observer
*
This
=
impl_from_IWineMSHTMLMutationObserver
(
iface
);
LONG
ref
=
InterlockedDecrement
(
&
This
->
ref
);
TRACE
(
"(%p) ref=%ld
\n
"
,
This
,
ref
);
if
(
!
ref
)
{
release_dispex
(
&
This
->
dispex
);
IDispatch_Release
(
This
->
callback
);
free
(
This
);
}
return
ref
;
}
static
HRESULT
WINAPI
MutationObserver_GetTypeInfoCount
(
IWineMSHTMLMutationObserver
*
iface
,
UINT
*
pctinfo
)
{
struct
mutation_observer
*
This
=
impl_from_IWineMSHTMLMutationObserver
(
iface
);
FIXME
(
"(%p)->(%p)
\n
"
,
This
,
pctinfo
);
return
IDispatchEx_GetTypeInfoCount
(
&
This
->
dispex
.
IDispatchEx_iface
,
pctinfo
);
}
static
HRESULT
WINAPI
MutationObserver_GetTypeInfo
(
IWineMSHTMLMutationObserver
*
iface
,
UINT
iTInfo
,
LCID
lcid
,
ITypeInfo
**
ppTInfo
)
{
struct
mutation_observer
*
This
=
impl_from_IWineMSHTMLMutationObserver
(
iface
);
return
IDispatchEx_GetTypeInfo
(
&
This
->
dispex
.
IDispatchEx_iface
,
iTInfo
,
lcid
,
ppTInfo
);
}
static
HRESULT
WINAPI
MutationObserver_GetIDsOfNames
(
IWineMSHTMLMutationObserver
*
iface
,
REFIID
riid
,
LPOLESTR
*
rgszNames
,
UINT
cNames
,
LCID
lcid
,
DISPID
*
rgDispId
)
{
struct
mutation_observer
*
This
=
impl_from_IWineMSHTMLMutationObserver
(
iface
);
return
IDispatchEx_GetIDsOfNames
(
&
This
->
dispex
.
IDispatchEx_iface
,
riid
,
rgszNames
,
cNames
,
lcid
,
rgDispId
);
}
static
HRESULT
WINAPI
MutationObserver_Invoke
(
IWineMSHTMLMutationObserver
*
iface
,
DISPID
dispIdMember
,
REFIID
riid
,
LCID
lcid
,
WORD
wFlags
,
DISPPARAMS
*
pDispParams
,
VARIANT
*
pVarResult
,
EXCEPINFO
*
pExcepInfo
,
UINT
*
puArgErr
)
{
struct
mutation_observer
*
This
=
impl_from_IWineMSHTMLMutationObserver
(
iface
);
return
IDispatchEx_Invoke
(
&
This
->
dispex
.
IDispatchEx_iface
,
dispIdMember
,
riid
,
lcid
,
wFlags
,
pDispParams
,
pVarResult
,
pExcepInfo
,
puArgErr
);
}
static
const
IWineMSHTMLMutationObserverVtbl
WineMSHTMLMutationObserverVtbl
=
{
MutationObserver_QueryInterface
,
MutationObserver_AddRef
,
MutationObserver_Release
,
MutationObserver_GetTypeInfoCount
,
MutationObserver_GetTypeInfo
,
MutationObserver_GetIDsOfNames
,
MutationObserver_Invoke
,
};
static
const
tid_t
mutation_observer_iface_tids
[]
=
{
IWineMSHTMLMutationObserver_tid
,
0
};
static
dispex_static_data_t
mutation_observer_dispex
=
{
L"MutationObserver"
,
NULL
,
IWineMSHTMLMutationObserver_tid
,
mutation_observer_iface_tids
};
static
HRESULT
create_mutation_observer
(
compat_mode_t
compat_mode
,
IDispatch
*
callback
,
IWineMSHTMLMutationObserver
**
ret
)
{
struct
mutation_observer
*
obj
;
TRACE
(
"(compat_mode = %d, callback = %p, ret = %p)
\n
"
,
compat_mode
,
callback
,
ret
);
obj
=
calloc
(
1
,
sizeof
(
*
obj
));
if
(
!
obj
)
{
ERR
(
"No memory.
\n
"
);
return
E_OUTOFMEMORY
;
}
obj
->
IWineMSHTMLMutationObserver_iface
.
lpVtbl
=
&
WineMSHTMLMutationObserverVtbl
;
obj
->
ref
=
1
;
init_dispatch
(
&
obj
->
dispex
,
(
IUnknown
*
)
&
obj
->
IWineMSHTMLMutationObserver_iface
,
&
mutation_observer_dispex
,
compat_mode
);
IDispatch_AddRef
(
callback
);
obj
->
callback
=
callback
;
*
ret
=
&
obj
->
IWineMSHTMLMutationObserver_iface
;
return
S_OK
;
}
struct
mutation_observer_ctor
{
IUnknown
IUnknown_iface
;
DispatchEx
dispex
;
LONG
ref
;
};
static
inline
struct
mutation_observer_ctor
*
mutation_observer_ctor_from_IUnknown
(
IUnknown
*
iface
)
{
return
CONTAINING_RECORD
(
iface
,
struct
mutation_observer_ctor
,
IUnknown_iface
);
}
static
inline
struct
mutation_observer_ctor
*
mutation_observer_ctor_from_DispatchEx
(
DispatchEx
*
iface
)
{
return
CONTAINING_RECORD
(
iface
,
struct
mutation_observer_ctor
,
dispex
);
}
static
HRESULT
WINAPI
mutation_observer_ctor_QueryInterface
(
IUnknown
*
iface
,
REFIID
riid
,
void
**
ppv
)
{
struct
mutation_observer_ctor
*
This
=
mutation_observer_ctor_from_IUnknown
(
iface
);
TRACE
(
"(%p)->(%s %p)
\n
"
,
This
,
debugstr_mshtml_guid
(
riid
),
ppv
);
if
(
IsEqualGUID
(
&
IID_IUnknown
,
riid
))
{
*
ppv
=
&
This
->
IUnknown_iface
;
}
else
if
(
dispex_query_interface
(
&
This
->
dispex
,
riid
,
ppv
))
{
return
*
ppv
?
S_OK
:
E_NOINTERFACE
;
}
else
{
WARN
(
"(%p)->(%s %p)
\n
"
,
This
,
debugstr_mshtml_guid
(
riid
),
ppv
);
*
ppv
=
NULL
;
return
E_NOINTERFACE
;
}
IUnknown_AddRef
((
IUnknown
*
)
*
ppv
);
return
S_OK
;
}
static
ULONG
WINAPI
mutation_observer_ctor_AddRef
(
IUnknown
*
iface
)
{
struct
mutation_observer_ctor
*
This
=
mutation_observer_ctor_from_IUnknown
(
iface
);
LONG
ref
=
InterlockedIncrement
(
&
This
->
ref
);
TRACE
(
"(%p) ref=%ld
\n
"
,
This
,
ref
);
return
ref
;
}
static
ULONG
WINAPI
mutation_observer_ctor_Release
(
IUnknown
*
iface
)
{
struct
mutation_observer_ctor
*
This
=
mutation_observer_ctor_from_IUnknown
(
iface
);
LONG
ref
=
InterlockedDecrement
(
&
This
->
ref
);
TRACE
(
"(%p) ref=%ld
\n
"
,
This
,
ref
);
if
(
!
ref
)
{
release_dispex
(
&
This
->
dispex
);
free
(
This
);
}
return
ref
;
}
static
const
IUnknownVtbl
mutation_observer_ctor_vtbl
=
{
mutation_observer_ctor_QueryInterface
,
mutation_observer_ctor_AddRef
,
mutation_observer_ctor_Release
,
};
static
HRESULT
mutation_observer_ctor_value
(
DispatchEx
*
dispex
,
LCID
lcid
,
WORD
flags
,
DISPPARAMS
*
params
,
VARIANT
*
res
,
EXCEPINFO
*
ei
,
IServiceProvider
*
caller
)
{
struct
mutation_observer_ctor
*
This
=
mutation_observer_ctor_from_DispatchEx
(
dispex
);
VARIANT
*
callback
;
IWineMSHTMLMutationObserver
*
mutation_observer
;
HRESULT
hres
;
int
argc
=
params
->
cArgs
-
params
->
cNamedArgs
;
TRACE
(
"(%p)->(%lx %x %p %p %p %p)
\n
"
,
This
,
lcid
,
flags
,
params
,
res
,
ei
,
caller
);
switch
(
flags
)
{
case
DISPATCH_METHOD
|
DISPATCH_PROPERTYGET
:
if
(
!
res
)
return
E_INVALIDARG
;
case
DISPATCH_CONSTRUCT
:
case
DISPATCH_METHOD
:
break
;
default:
FIXME
(
"flags %x is not supported
\n
"
,
flags
);
return
E_NOTIMPL
;
}
if
(
argc
<
1
)
return
E_UNEXPECTED
;
callback
=
params
->
rgvarg
+
(
params
->
cArgs
-
1
);
if
(
V_VT
(
callback
)
!=
VT_DISPATCH
)
{
FIXME
(
"Should return TypeMismatchError
\n
"
);
return
E_FAIL
;
}
if
(
!
res
)
return
S_OK
;
hres
=
create_mutation_observer
(
dispex_compat_mode
(
&
This
->
dispex
),
V_DISPATCH
(
callback
),
&
mutation_observer
);
if
(
FAILED
(
hres
))
return
hres
;
V_VT
(
res
)
=
VT_DISPATCH
;
V_DISPATCH
(
res
)
=
(
IDispatch
*
)
mutation_observer
;
return
S_OK
;
}
static
dispex_static_data_vtbl_t
mutation_observer_ctor_dispex_vtbl
=
{
.
value
=
mutation_observer_ctor_value
};
static
const
tid_t
mutation_observer_ctor_iface_tids
[]
=
{
0
};
static
dispex_static_data_t
mutation_observer_ctor_dispex
=
{
L"Function"
,
&
mutation_observer_ctor_dispex_vtbl
,
NULL_tid
,
mutation_observer_ctor_iface_tids
};
HRESULT
create_mutation_observer_ctor
(
compat_mode_t
compat_mode
,
IDispatch
**
ret
)
{
struct
mutation_observer_ctor
*
obj
;
TRACE
(
"(compat_mode = %d, ret = %p)
\n
"
,
compat_mode
,
ret
);
obj
=
calloc
(
1
,
sizeof
(
*
obj
));
if
(
!
obj
)
{
ERR
(
"No memory.
\n
"
);
return
E_OUTOFMEMORY
;
}
obj
->
IUnknown_iface
.
lpVtbl
=
&
mutation_observer_ctor_vtbl
;
obj
->
ref
=
1
;
init_dispatch
(
&
obj
->
dispex
,
(
IUnknown
*
)
&
obj
->
IUnknown_iface
,
&
mutation_observer_ctor_dispex
,
compat_mode
);
*
ret
=
(
IDispatch
*
)
&
obj
->
dispex
.
IDispatchEx_iface
;
return
S_OK
;
}
dlls/mshtml/tests/documentmode.js
View file @
02994bdd
...
...
@@ -343,6 +343,9 @@ sync_test("builtin_toString", function() {
test
(
"console"
,
window
.
console
,
"Console"
);
test
(
"mediaQueryList"
,
window
.
matchMedia
(
"(hover:hover)"
),
"MediaQueryList"
);
}
if
(
v
>=
11
)
{
test
(
"MutationObserver"
,
new
window
.
MutationObserver
(
function
()
{}),
"MutationObserver"
);
}
if
(
v
>=
9
)
{
document
.
body
.
innerHTML
=
"<!--...-->"
;
test
(
"comment"
,
document
.
body
.
firstChild
,
"Comment"
);
...
...
@@ -475,6 +478,7 @@ sync_test("window_props", function() {
test_exposed
(
"performance"
,
true
);
test_exposed
(
"console"
,
v
>=
10
);
test_exposed
(
"matchMedia"
,
v
>=
10
);
test_exposed
(
"MutationObserver"
,
v
>=
11
);
});
sync_test
(
"domimpl_props"
,
function
()
{
...
...
@@ -2853,6 +2857,60 @@ sync_test("__defineSetter__", function() {
ok
(
x
.
setterVal
===
9
,
"x.setterVal after setting bar = "
+
x
.
setterVal
);
});
sync_test
(
"MutationObserver"
,
function
()
{
if
(
!
window
.
MutationObserver
)
{
return
;
}
try
{
window
.
MutationObserver
();
ok
(
false
,
"MutationObserver without args should fail"
);
}
catch
(
e
)
{
ok
(
e
.
number
==
0xffff
-
0x80000000
,
"MutationObserver without new threw exception "
+
e
.
number
);
}
try
{
window
.
MutationObserver
(
42
);
ok
(
false
,
"MutationObserver with non-function should fail"
);
}
catch
(
e
)
{
todo_wine
.
ok
(
e
.
name
==
"TypeMismatchError"
,
"MutationObserver with non-function arg threw exception "
+
e
.
name
);
}
try
{
window
.
MutationObserver
(
function
()
{});
}
catch
(
e
)
{
ok
(
false
,
"MutationObserver without new threw exception "
+
e
.
number
);
}
try
{
new
window
.
MutationObserver
();
ok
(
false
,
"MutationObserver with no args should fail"
);
}
catch
(
e
)
{
ok
(
e
.
number
==
0xffff
-
0x80000000
,
"MutationObserver with no args threw exception "
+
e
.
number
);
}
try
{
new
window
.
MutationObserver
(
1
);
ok
(
false
,
"MutationObserver with non-function arg should fail"
);
}
catch
(
e
)
{
todo_wine
.
ok
(
e
.
name
==
"TypeMismatchError"
,
"MutationObserver with non-function arg threw exception "
+
e
.
name
);
}
try
{
new
window
.
MutationObserver
(
function
()
{});
}
catch
(
e
)
{
ok
(
false
,
"MutationObserver threw exception "
+
e
.
number
);
}
try
{
new
window
.
MutationObserver
(
function
()
{},
1
);
}
catch
(
e
)
{
ok
(
false
,
"MutationObserver with extra args threw exception "
+
e
.
number
);
}
})
async_test
(
"postMessage"
,
function
()
{
var
v
=
document
.
documentMode
;
var
onmessage_called
=
false
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment