imagehlp: Use the IMAGE_FIRST_SECTION helper macro.
[wine.git] / dlls / mshtml / htmlstorage.c
blobd3bbb30580e709b12892fba79b7ecc986e77b959
1 /*
2 * Copyright 2012 Jacek Caban for CodeWeavers
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #include <stdarg.h>
21 #define COBJMACROS
23 #include "windef.h"
24 #include "winbase.h"
25 #include "winuser.h"
26 #include "ole2.h"
27 #include "shlobj.h"
29 #include "wine/debug.h"
31 #include "mshtml_private.h"
32 #include "htmlevent.h"
34 WINE_DEFAULT_DEBUG_CHANNEL(mshtml);
36 /* Native defaults to 5 million chars per origin */
37 enum { MAX_QUOTA = 5000000 };
39 typedef struct {
40 DispatchEx dispex;
41 IHTMLStorage IHTMLStorage_iface;
42 unsigned num_props;
43 BSTR *props;
44 HTMLInnerWindow *window;
45 struct session_map_entry *session_storage;
46 WCHAR *filename;
47 HANDLE mutex;
48 } HTMLStorage;
50 struct session_map_entry {
51 struct wine_rb_entry entry;
52 struct wine_rb_tree data_map;
53 struct list data_list; /* for key() */
54 UINT ref;
55 UINT quota;
56 UINT num_keys;
57 UINT origin_len;
58 WCHAR origin[1];
61 int session_storage_map_cmp(const void *key, const struct wine_rb_entry *entry)
63 struct session_map_entry *p = WINE_RB_ENTRY_VALUE(entry, struct session_map_entry, entry);
64 UINT len = SysStringLen((BSTR)key);
66 return (len != p->origin_len) ? (len - p->origin_len) : memcmp(key, p->origin, len * sizeof(WCHAR));
69 struct session_entry
71 struct wine_rb_entry entry;
72 struct list list_entry;
73 BSTR value;
74 WCHAR key[1];
77 static int session_entry_cmp(const void *key, const struct wine_rb_entry *entry)
79 struct session_entry *data = WINE_RB_ENTRY_VALUE(entry, struct session_entry, entry);
80 return wcscmp(key, data->key);
83 static struct session_map_entry *grab_session_map_entry(BSTR origin)
85 struct session_map_entry *entry;
86 struct wine_rb_entry *rb_entry;
87 thread_data_t *thread_data;
88 UINT origin_len;
90 thread_data = get_thread_data(TRUE);
91 if(!thread_data)
92 return NULL;
94 rb_entry = wine_rb_get(&thread_data->session_storage_map, origin);
95 if(rb_entry) {
96 entry = WINE_RB_ENTRY_VALUE(rb_entry, struct session_map_entry, entry);
97 entry->ref++;
98 return entry;
101 origin_len = SysStringLen(origin);
102 entry = malloc(FIELD_OFFSET(struct session_map_entry, origin[origin_len]));
103 if(!entry)
104 return NULL;
105 wine_rb_init(&entry->data_map, session_entry_cmp);
106 list_init(&entry->data_list);
107 entry->ref = 1;
108 entry->quota = MAX_QUOTA;
109 entry->num_keys = 0;
110 entry->origin_len = origin_len;
111 memcpy(entry->origin, origin, origin_len * sizeof(WCHAR));
113 wine_rb_put(&thread_data->session_storage_map, origin, &entry->entry);
114 return entry;
117 static void release_session_map_entry(struct session_map_entry *entry)
119 if(!entry || --entry->ref || entry->num_keys)
120 return;
122 wine_rb_remove(&get_thread_data(FALSE)->session_storage_map, &entry->entry);
123 free(entry);
126 static HRESULT get_session_entry(struct session_map_entry *entry, const WCHAR *name, BOOL create, struct session_entry **ret)
128 const WCHAR *key = name ? name : L"";
129 struct wine_rb_entry *rb_entry;
130 struct session_entry *data;
131 UINT key_len;
133 rb_entry = wine_rb_get(&entry->data_map, key);
134 if(rb_entry) {
135 *ret = WINE_RB_ENTRY_VALUE(rb_entry, struct session_entry, entry);
136 return S_OK;
139 if(!create) {
140 *ret = NULL;
141 return S_OK;
144 key_len = wcslen(key);
145 if(entry->quota < key_len)
146 return E_OUTOFMEMORY; /* native returns this when quota is exceeded */
147 if(!(data = malloc(FIELD_OFFSET(struct session_entry, key[key_len + 1]))))
148 return E_OUTOFMEMORY;
149 data->value = NULL;
150 memcpy(data->key, key, (key_len + 1) * sizeof(WCHAR));
152 entry->quota -= key_len;
153 entry->num_keys++;
154 list_add_tail(&entry->data_list, &data->list_entry);
155 wine_rb_put(&entry->data_map, key, &data->entry);
156 *ret = data;
157 return S_OK;
160 static void clear_session_storage(struct session_map_entry *entry)
162 struct session_entry *iter, *iter2;
164 LIST_FOR_EACH_ENTRY_SAFE(iter, iter2, &entry->data_list, struct session_entry, list_entry) {
165 SysFreeString(iter->value);
166 free(iter);
168 wine_rb_destroy(&entry->data_map, NULL, NULL);
169 list_init(&entry->data_list);
170 entry->quota = MAX_QUOTA;
171 entry->num_keys = 0;
174 void destroy_session_storage(thread_data_t *thread_data)
176 struct session_map_entry *iter, *iter2;
178 WINE_RB_FOR_EACH_ENTRY_DESTRUCTOR(iter, iter2, &thread_data->session_storage_map, struct session_map_entry, entry) {
179 clear_session_storage(iter);
180 free(iter);
184 static void release_props(HTMLStorage *This)
186 BSTR *prop = This->props, *end = prop + This->num_props;
187 while(prop != end) {
188 SysFreeString(*prop);
189 prop++;
191 free(This->props);
194 static inline HTMLStorage *impl_from_IHTMLStorage(IHTMLStorage *iface)
196 return CONTAINING_RECORD(iface, HTMLStorage, IHTMLStorage_iface);
199 static HRESULT build_session_origin(IUri*,BSTR,BSTR*);
201 struct storage_event_task {
202 event_task_t header;
203 DOMEvent *event;
206 static void storage_event_proc(event_task_t *_task)
208 struct storage_event_task *task = (struct storage_event_task*)_task;
209 HTMLInnerWindow *window = task->header.window;
210 DOMEvent *event = task->event;
211 VARIANT_BOOL cancelled;
213 if(event->event_id == EVENTID_STORAGE && dispex_compat_mode(&window->event_target.dispex) >= COMPAT_MODE_IE9) {
214 dispatch_event(&window->event_target, event);
215 if(window->doc)
216 fire_event(&window->doc->node, L"onstorage", NULL, &cancelled);
217 }else if(window->doc) {
218 dispatch_event(&window->doc->node.event_target, event);
222 static void storage_event_destr(event_task_t *_task)
224 struct storage_event_task *task = (struct storage_event_task*)_task;
225 IDOMEvent_Release(&task->event->IDOMEvent_iface);
228 struct send_storage_event_ctx {
229 HTMLInnerWindow *skip_window;
230 const WCHAR *origin;
231 UINT origin_len;
232 BSTR key;
233 BSTR old_value;
234 BSTR new_value;
235 BSTR url;
238 static HRESULT push_storage_event_task(struct send_storage_event_ctx *ctx, HTMLInnerWindow *window, BOOL commit)
240 struct storage_event_task *task;
241 DOMEvent *event;
242 HRESULT hres;
244 hres = create_storage_event(window->doc, ctx->key, ctx->old_value, ctx->new_value, ctx->url, commit, &event);
245 if(FAILED(hres))
246 return hres;
248 if(!(task = malloc(sizeof(*task)))) {
249 IDOMEvent_Release(&event->IDOMEvent_iface);
250 return E_OUTOFMEMORY;
253 task->event = event;
254 return push_event_task(&task->header, window, storage_event_proc, storage_event_destr, window->task_magic);
257 static HRESULT send_storage_event_impl(struct send_storage_event_ctx *ctx, HTMLInnerWindow *window)
259 HTMLOuterWindow *child;
260 const WCHAR *origin;
261 UINT origin_len;
262 BOOL matches;
263 HRESULT hres;
264 BSTR bstr;
266 if(!window)
267 return S_OK;
269 LIST_FOR_EACH_ENTRY(child, &window->children, HTMLOuterWindow, sibling_entry) {
270 hres = send_storage_event_impl(ctx, child->base.inner_window);
271 if(FAILED(hres))
272 return hres;
275 if(window == ctx->skip_window)
276 return S_OK;
278 /* Try it quick from session storage first, if available */
279 if(window->session_storage) {
280 HTMLStorage *storage = impl_from_IHTMLStorage(window->session_storage);
281 origin = storage->session_storage->origin;
282 origin_len = ctx->skip_window ? wcslen(origin) : storage->session_storage->origin_len;
283 bstr = NULL;
284 }else if(!window->base.outer_window->uri) {
285 return S_OK;
286 }else {
287 hres = IUri_GetHost(window->base.outer_window->uri, &bstr);
288 if(hres != S_OK) {
289 if(SUCCEEDED(hres))
290 SysFreeString(bstr);
291 return S_OK;
293 if(ctx->skip_window)
294 _wcslwr(bstr);
295 else {
296 BSTR tmp = bstr;
297 hres = build_session_origin(window->base.outer_window->uri, tmp, &bstr);
298 SysFreeString(tmp);
299 if(hres != S_OK) {
300 if(SUCCEEDED(hres))
301 SysFreeString(bstr);
302 return S_OK;
305 origin = bstr;
306 origin_len = SysStringLen(bstr);
309 matches = (origin_len == ctx->origin_len && !memcmp(origin, ctx->origin, origin_len * sizeof(WCHAR)));
310 SysFreeString(bstr);
312 return matches ? push_storage_event_task(ctx, window, FALSE) : S_OK;
315 /* Takes ownership of old_value */
316 static HRESULT send_storage_event(HTMLStorage *storage, BSTR key, BSTR old_value, BSTR new_value)
318 HTMLInnerWindow *window = storage->window;
319 struct send_storage_event_ctx ctx;
320 HTMLOuterWindow *top_window;
321 BSTR hostname = NULL;
322 HRESULT hres = S_OK;
324 ctx.url = NULL;
325 if(!window)
326 goto done;
327 if(window->base.outer_window->uri_nofrag) {
328 hres = IUri_GetDisplayUri(window->base.outer_window->uri_nofrag, &ctx.url);
329 if(hres != S_OK)
330 goto done;
333 get_top_window(window->base.outer_window, &top_window);
334 ctx.key = key;
335 ctx.old_value = old_value;
336 ctx.new_value = new_value;
337 if(!storage->filename) {
338 ctx.origin = storage->session_storage->origin;
339 ctx.origin_len = storage->session_storage->origin_len;
340 ctx.skip_window = NULL;
341 }else {
342 hres = IUri_GetHost(window->base.outer_window->uri, &hostname);
343 if(hres != S_OK)
344 goto done;
345 _wcslwr(hostname);
346 ctx.origin = hostname;
347 ctx.origin_len = SysStringLen(hostname);
348 ctx.skip_window = top_window->base.inner_window; /* localStorage on native skips top window */
350 hres = send_storage_event_impl(&ctx, top_window->base.inner_window);
352 if(ctx.skip_window && hres == S_OK)
353 hres = push_storage_event_task(&ctx, window, TRUE);
355 done:
356 SysFreeString(ctx.url);
357 SysFreeString(hostname);
358 SysFreeString(old_value);
359 return FAILED(hres) ? hres : S_OK;
362 static HRESULT WINAPI HTMLStorage_QueryInterface(IHTMLStorage *iface, REFIID riid, void **ppv)
364 HTMLStorage *This = impl_from_IHTMLStorage(iface);
365 return IDispatchEx_QueryInterface(&This->dispex.IDispatchEx_iface, riid, ppv);
368 static ULONG WINAPI HTMLStorage_AddRef(IHTMLStorage *iface)
370 HTMLStorage *This = impl_from_IHTMLStorage(iface);
371 return IDispatchEx_AddRef(&This->dispex.IDispatchEx_iface);
374 static ULONG WINAPI HTMLStorage_Release(IHTMLStorage *iface)
376 HTMLStorage *This = impl_from_IHTMLStorage(iface);
377 return IDispatchEx_Release(&This->dispex.IDispatchEx_iface);
380 static HRESULT WINAPI HTMLStorage_GetTypeInfoCount(IHTMLStorage *iface, UINT *pctinfo)
382 HTMLStorage *This = impl_from_IHTMLStorage(iface);
383 return IDispatchEx_GetTypeInfoCount(&This->dispex.IDispatchEx_iface, pctinfo);
386 static HRESULT WINAPI HTMLStorage_GetTypeInfo(IHTMLStorage *iface, UINT iTInfo,
387 LCID lcid, ITypeInfo **ppTInfo)
389 HTMLStorage *This = impl_from_IHTMLStorage(iface);
391 return IDispatchEx_GetTypeInfo(&This->dispex.IDispatchEx_iface, iTInfo, lcid, ppTInfo);
394 static HRESULT WINAPI HTMLStorage_GetIDsOfNames(IHTMLStorage *iface, REFIID riid, LPOLESTR *rgszNames, UINT cNames,
395 LCID lcid, DISPID *rgDispId)
397 HTMLStorage *This = impl_from_IHTMLStorage(iface);
399 return IDispatchEx_GetIDsOfNames(&This->dispex.IDispatchEx_iface, riid, rgszNames, cNames,
400 lcid, rgDispId);
403 static HRESULT WINAPI HTMLStorage_Invoke(IHTMLStorage *iface, DISPID dispIdMember, REFIID riid, LCID lcid,
404 WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
406 HTMLStorage *This = impl_from_IHTMLStorage(iface);
408 return IDispatchEx_Invoke(&This->dispex.IDispatchEx_iface, dispIdMember, riid, lcid, wFlags,
409 pDispParams, pVarResult, pExcepInfo, puArgErr);
412 static BOOL create_path(const WCHAR *path)
414 BOOL ret = TRUE;
415 WCHAR *new_path;
416 int len;
418 new_path = malloc((wcslen(path) + 1) * sizeof(WCHAR));
419 if(!new_path)
420 return FALSE;
421 wcscpy(new_path, path);
423 while((len = wcslen(new_path)) && new_path[len - 1] == '\\')
424 new_path[len - 1] = 0;
426 while(!CreateDirectoryW(new_path, NULL)) {
427 WCHAR *slash;
428 DWORD error = GetLastError();
429 if(error == ERROR_ALREADY_EXISTS) break;
430 if(error != ERROR_PATH_NOT_FOUND) {
431 ret = FALSE;
432 break;
434 slash = wcsrchr(new_path, '\\');
435 if(!slash) {
436 ret = FALSE;
437 break;
439 len = slash - new_path;
440 new_path[len] = 0;
441 if(!create_path(new_path)) {
442 ret = FALSE;
443 break;
445 new_path[len] = '\\';
447 free(new_path);
448 return ret;
451 static HRESULT open_document(const WCHAR *filename, IXMLDOMDocument **ret)
453 IXMLDOMDocument *doc = NULL;
454 HRESULT hres = E_OUTOFMEMORY;
455 WCHAR *ptr, *path;
456 VARIANT var;
457 VARIANT_BOOL success;
459 path = wcsdup(filename);
460 if(!path)
461 return E_OUTOFMEMORY;
463 *(ptr = wcsrchr(path, '\\')) = 0;
464 if(!create_path(path))
465 goto done;
467 if(GetFileAttributesW(filename) == INVALID_FILE_ATTRIBUTES) {
468 DWORD count;
469 HANDLE file = CreateFileW(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL);
470 if(file == INVALID_HANDLE_VALUE) {
471 hres = HRESULT_FROM_WIN32(GetLastError());
472 goto done;
474 if(!WriteFile(file, "<root/>", sizeof("<root/>") - 1, &count, NULL)) {
475 CloseHandle(file);
476 hres = HRESULT_FROM_WIN32(GetLastError());
477 goto done;
479 CloseHandle(file);
482 hres = CoCreateInstance(&CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument, (void**)&doc);
483 if(hres != S_OK)
484 goto done;
486 V_VT(&var) = VT_BSTR;
487 V_BSTR(&var) = SysAllocString(filename);
488 if(!V_BSTR(&var)) {
489 hres = E_OUTOFMEMORY;
490 goto done;
493 hres = IXMLDOMDocument_load(doc, var, &success);
494 if(hres == S_FALSE || success == VARIANT_FALSE)
495 hres = E_FAIL;
497 SysFreeString(V_BSTR(&var));
499 done:
500 free(path);
501 if(hres == S_OK)
502 *ret = doc;
503 else if(doc)
504 IXMLDOMDocument_Release(doc);
506 return hres;
509 static HRESULT get_root_node(IXMLDOMDocument *doc, IXMLDOMNode **root)
511 HRESULT hres;
512 BSTR str;
514 str = SysAllocString(L"root");
515 if(!str)
516 return E_OUTOFMEMORY;
518 hres = IXMLDOMDocument_selectSingleNode(doc, str, root);
519 SysFreeString(str);
520 return hres;
523 static HRESULT get_node_list(const WCHAR *filename, IXMLDOMNodeList **node_list)
525 IXMLDOMDocument *doc;
526 IXMLDOMNode *root;
527 HRESULT hres;
528 BSTR query;
530 hres = open_document(filename, &doc);
531 if(hres != S_OK)
532 return hres;
534 hres = get_root_node(doc, &root);
535 IXMLDOMDocument_Release(doc);
536 if(hres != S_OK)
537 return hres;
539 if(!(query = SysAllocString(L"item")))
540 hres = E_OUTOFMEMORY;
541 else {
542 hres = IXMLDOMNode_selectNodes(root, query, node_list);
543 SysFreeString(query);
545 IXMLDOMNode_Release(root);
546 return hres;
549 static HRESULT WINAPI HTMLStorage_get_length(IHTMLStorage *iface, LONG *p)
551 HTMLStorage *This = impl_from_IHTMLStorage(iface);
552 IXMLDOMNodeList *node_list;
553 HRESULT hres;
555 TRACE("(%p)->(%p)\n", This, p);
557 if(!This->filename) {
558 *p = This->session_storage->num_keys;
559 return S_OK;
562 WaitForSingleObject(This->mutex, INFINITE);
563 hres = get_node_list(This->filename, &node_list);
564 if(SUCCEEDED(hres)) {
565 hres = IXMLDOMNodeList_get_length(node_list, p);
566 IXMLDOMNodeList_Release(node_list);
568 ReleaseMutex(This->mutex);
570 return hres;
573 static HRESULT WINAPI HTMLStorage_get_remainingSpace(IHTMLStorage *iface, LONG *p)
575 HTMLStorage *This = impl_from_IHTMLStorage(iface);
577 TRACE("(%p)->(%p)\n", This, p);
579 if(!This->filename) {
580 *p = This->session_storage->quota;
581 return S_OK;
584 FIXME("local storage not supported\n");
585 return E_NOTIMPL;
588 static HRESULT get_key(const WCHAR *filename, LONG index, BSTR *ret)
590 IXMLDOMNodeList *node_list;
591 IXMLDOMElement *elem;
592 IXMLDOMNode *node;
593 HRESULT hres;
594 VARIANT key;
596 hres = get_node_list(filename, &node_list);
597 if(FAILED(hres))
598 return hres;
600 hres = IXMLDOMNodeList_get_item(node_list, index, &node);
601 IXMLDOMNodeList_Release(node_list);
602 if(hres != S_OK)
603 return FAILED(hres) ? hres : E_INVALIDARG;
605 hres = IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void**)&elem);
606 IXMLDOMNode_Release(node);
607 if(hres != S_OK)
608 return E_INVALIDARG;
610 hres = IXMLDOMElement_getAttribute(elem, (BSTR)L"name", &key);
611 IXMLDOMElement_Release(elem);
612 if(FAILED(hres))
613 return hres;
615 if(V_VT(&key) != VT_BSTR) {
616 FIXME("non-string key %s\n", debugstr_variant(&key));
617 VariantClear(&key);
618 return E_NOTIMPL;
621 *ret = V_BSTR(&key);
622 return S_OK;
625 static HRESULT WINAPI HTMLStorage_key(IHTMLStorage *iface, LONG lIndex, BSTR *p)
627 HTMLStorage *This = impl_from_IHTMLStorage(iface);
628 struct session_entry *session_entry;
629 HRESULT hres;
631 TRACE("(%p)->(%ld %p)\n", This, lIndex, p);
633 if(!This->filename) {
634 struct list *entry = &This->session_storage->data_list;
635 unsigned i = 0;
637 if(lIndex >= This->session_storage->num_keys)
638 return E_INVALIDARG;
640 do entry = entry->next; while(i++ < lIndex);
641 session_entry = LIST_ENTRY(entry, struct session_entry, list_entry);
643 *p = SysAllocString(session_entry->key);
644 return *p ? S_OK : E_OUTOFMEMORY;
647 WaitForSingleObject(This->mutex, INFINITE);
648 hres = get_key(This->filename, lIndex, p);
649 ReleaseMutex(This->mutex);
651 return hres;
654 static BSTR build_query(const WCHAR *key)
656 static const WCHAR fmt[] = L"item[@name='%s']";
657 const WCHAR *str = key ? key : L"";
658 UINT len = ARRAY_SIZE(fmt) + wcslen(str);
659 BSTR ret = SysAllocStringLen(NULL, len);
661 if(ret) swprintf(ret, len, fmt, str);
662 return ret;
665 static HRESULT get_item(const WCHAR *filename, BSTR key, VARIANT *value)
667 IXMLDOMDocument *doc;
668 BSTR query = NULL;
669 IXMLDOMNode *root = NULL, *node = NULL;
670 IXMLDOMElement *elem = NULL;
671 HRESULT hres;
673 hres = open_document(filename, &doc);
674 if(hres != S_OK)
675 return hres;
677 hres = get_root_node(doc, &root);
678 if(hres != S_OK)
679 goto done;
681 query = build_query(key);
682 if(!query) {
683 hres = E_OUTOFMEMORY;
684 goto done;
687 hres = IXMLDOMNode_selectSingleNode(root, query, &node);
688 if(hres == S_OK) {
689 hres = IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void**)&elem);
690 if(hres != S_OK)
691 goto done;
693 hres = IXMLDOMElement_getAttribute(elem, (BSTR)L"value", value);
694 }else {
695 V_VT(value) = VT_NULL;
696 hres = S_OK;
699 done:
700 SysFreeString(query);
701 if(root)
702 IXMLDOMNode_Release(root);
703 if(node)
704 IXMLDOMNode_Release(node);
705 if(elem)
706 IXMLDOMElement_Release(elem);
707 IXMLDOMDocument_Release(doc);
708 return hres;
711 static HRESULT WINAPI HTMLStorage_getItem(IHTMLStorage *iface, BSTR bstrKey, VARIANT *value)
713 HTMLStorage *This = impl_from_IHTMLStorage(iface);
714 struct session_entry *session_entry;
715 HRESULT hres;
717 TRACE("(%p)->(%s %p)\n", This, debugstr_w(bstrKey), value);
719 if(!value)
720 return E_POINTER;
722 if(!This->filename) {
723 hres = get_session_entry(This->session_storage, bstrKey, FALSE, &session_entry);
724 if(SUCCEEDED(hres)) {
725 if(!session_entry || !session_entry->value)
726 V_VT(value) = VT_NULL;
727 else {
728 V_VT(value) = VT_BSTR;
729 V_BSTR(value) = SysAllocStringLen(session_entry->value, SysStringLen(session_entry->value));
730 hres = V_BSTR(value) ? S_OK : E_OUTOFMEMORY;
733 return hres;
736 WaitForSingleObject(This->mutex, INFINITE);
737 hres = get_item(This->filename, bstrKey, value);
738 ReleaseMutex(This->mutex);
740 return hres;
743 static HRESULT set_attribute(IXMLDOMElement *elem, const WCHAR *name, BSTR value)
745 BSTR str;
746 VARIANT var;
747 HRESULT hres;
749 str = SysAllocString(name);
750 if(!str)
751 return E_OUTOFMEMORY;
752 V_VT(&var) = VT_BSTR;
753 V_BSTR(&var) = value;
755 hres = IXMLDOMElement_setAttribute(elem, str, var);
756 SysFreeString(str);
757 return hres;
760 static HRESULT save_document(IXMLDOMDocument *doc, const WCHAR *filename)
762 VARIANT var;
763 HRESULT hres;
765 V_VT(&var) = VT_BSTR;
766 V_BSTR(&var) = SysAllocString(filename);
767 if(!V_BSTR(&var))
768 return E_OUTOFMEMORY;
770 hres = IXMLDOMDocument_save(doc, var);
771 SysFreeString(V_BSTR(&var));
772 return hres;
775 static HRESULT set_item(const WCHAR *filename, BSTR key, BSTR value, BSTR *old_value)
777 IXMLDOMDocument *doc;
778 IXMLDOMNode *root = NULL, *node = NULL;
779 IXMLDOMElement *elem = NULL;
780 BSTR query = NULL;
781 HRESULT hres;
783 *old_value = NULL;
784 hres = open_document(filename, &doc);
785 if(hres != S_OK)
786 return hres;
788 hres = get_root_node(doc, &root);
789 if(hres != S_OK)
790 goto done;
792 query = build_query(key);
793 if(!query) {
794 hres = E_OUTOFMEMORY;
795 goto done;
798 hres = IXMLDOMNode_selectSingleNode(root, query, &node);
799 if(hres == S_OK) {
800 VARIANT old;
802 hres = IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void**)&elem);
803 if(hres != S_OK)
804 goto done;
806 hres = IXMLDOMElement_getAttribute(elem, (BSTR)L"value", &old);
807 if(hres == S_OK) {
808 if(V_VT(&old) == VT_BSTR)
809 *old_value = V_BSTR(&old);
810 else
811 VariantClear(&old);
814 hres = set_attribute(elem, L"value", value);
815 if(hres != S_OK)
816 goto done;
817 }else {
818 BSTR str = SysAllocString(L"item");
819 hres = IXMLDOMDocument_createElement(doc, str, &elem);
820 SysFreeString(str);
821 if(hres != S_OK)
822 goto done;
824 hres = set_attribute(elem, L"name", key);
825 if(hres != S_OK)
826 goto done;
828 hres = set_attribute(elem, L"value", value);
829 if(hres != S_OK)
830 goto done;
832 hres = IXMLDOMNode_appendChild(root, (IXMLDOMNode*)elem, NULL);
833 if(hres != S_OK)
834 goto done;
837 hres = save_document(doc, filename);
839 done:
840 SysFreeString(query);
841 if(root)
842 IXMLDOMNode_Release(root);
843 if(node)
844 IXMLDOMNode_Release(node);
845 if(elem)
846 IXMLDOMElement_Release(elem);
847 IXMLDOMDocument_Release(doc);
848 if(hres != S_OK)
849 SysFreeString(*old_value);
850 return hres;
853 static HRESULT WINAPI HTMLStorage_setItem(IHTMLStorage *iface, BSTR bstrKey, BSTR bstrValue)
855 HTMLStorage *This = impl_from_IHTMLStorage(iface);
856 struct session_entry *session_entry;
857 BSTR old_value;
858 HRESULT hres;
860 TRACE("(%p)->(%s %s)\n", This, debugstr_w(bstrKey), debugstr_w(bstrValue));
862 if(!This->filename) {
863 UINT value_len = bstrValue ? wcslen(bstrValue) : 0;
864 BSTR value = SysAllocStringLen(bstrValue, value_len);
865 if(!value)
866 return E_OUTOFMEMORY;
868 hres = get_session_entry(This->session_storage, bstrKey, TRUE, &session_entry);
869 if(FAILED(hres))
870 SysFreeString(value);
871 else {
872 UINT old_len = SysStringLen(session_entry->value);
873 if(old_len < value_len && This->session_storage->quota < value_len - old_len) {
874 SysFreeString(value);
875 return E_OUTOFMEMORY; /* native returns this when quota is exceeded */
877 This->session_storage->quota -= value_len - old_len;
878 old_value = session_entry->value;
879 session_entry->value = value;
881 hres = send_storage_event(This, bstrKey, old_value, value);
883 return hres;
886 WaitForSingleObject(This->mutex, INFINITE);
887 hres = set_item(This->filename, bstrKey, bstrValue, &old_value);
888 ReleaseMutex(This->mutex);
890 if(hres == S_OK)
891 hres = send_storage_event(This, bstrKey, old_value, bstrValue);
892 return hres;
895 static HRESULT remove_item(const WCHAR *filename, BSTR key, BSTR *old_value, BOOL *changed)
897 IXMLDOMDocument *doc;
898 IXMLDOMNode *root = NULL, *node = NULL;
899 BSTR query = NULL;
900 HRESULT hres;
902 *old_value = NULL;
903 *changed = FALSE;
904 hres = open_document(filename, &doc);
905 if(hres != S_OK)
906 return hres;
908 hres = get_root_node(doc, &root);
909 if(hres != S_OK)
910 goto done;
912 query = build_query(key);
913 if(!query) {
914 hres = E_OUTOFMEMORY;
915 goto done;
918 hres = IXMLDOMNode_selectSingleNode(root, query, &node);
919 if(hres == S_OK) {
920 IXMLDOMElement *elem;
921 VARIANT old;
923 hres = IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void**)&elem);
924 if(hres == S_OK) {
925 hres = IXMLDOMElement_getAttribute(elem, (BSTR)L"value", &old);
926 if(hres == S_OK) {
927 if(V_VT(&old) == VT_BSTR)
928 *old_value = V_BSTR(&old);
929 else
930 VariantClear(&old);
932 IXMLDOMElement_Release(elem);
933 *changed = TRUE;
936 hres = IXMLDOMNode_removeChild(root, node, NULL);
937 if(hres != S_OK)
938 goto done;
941 hres = save_document(doc, filename);
943 done:
944 SysFreeString(query);
945 if(root)
946 IXMLDOMNode_Release(root);
947 if(node)
948 IXMLDOMNode_Release(node);
949 IXMLDOMDocument_Release(doc);
950 if(hres != S_OK || !changed)
951 SysFreeString(*old_value);
952 return hres;
955 static HRESULT WINAPI HTMLStorage_removeItem(IHTMLStorage *iface, BSTR bstrKey)
957 HTMLStorage *This = impl_from_IHTMLStorage(iface);
958 struct session_entry *session_entry;
959 BSTR old_value;
960 HRESULT hres;
961 BOOL changed;
963 TRACE("(%p)->(%s)\n", This, debugstr_w(bstrKey));
965 if(!This->filename) {
966 hres = get_session_entry(This->session_storage, bstrKey, FALSE, &session_entry);
967 if(SUCCEEDED(hres) && session_entry) {
968 This->session_storage->quota += wcslen(session_entry->key) + SysStringLen(session_entry->value);
969 This->session_storage->num_keys--;
970 list_remove(&session_entry->list_entry);
971 wine_rb_remove(&This->session_storage->data_map, &session_entry->entry);
972 old_value = session_entry->value;
973 free(session_entry);
975 hres = send_storage_event(This, bstrKey, old_value, NULL);
977 return hres;
980 WaitForSingleObject(This->mutex, INFINITE);
981 hres = remove_item(This->filename, bstrKey, &old_value, &changed);
982 ReleaseMutex(This->mutex);
984 if(hres == S_OK && changed)
985 hres = send_storage_event(This, bstrKey, old_value, NULL);
986 return hres;
989 static HRESULT WINAPI HTMLStorage_clear(IHTMLStorage *iface)
991 HTMLStorage *This = impl_from_IHTMLStorage(iface);
992 HRESULT hres = S_OK;
994 if(!This->filename) {
995 UINT num_keys = This->session_storage->num_keys;
996 clear_session_storage(This->session_storage);
997 return num_keys ? send_storage_event(This, NULL, NULL, NULL) : S_OK;
1000 WaitForSingleObject(This->mutex, INFINITE);
1001 if(!DeleteFileW(This->filename)) {
1002 DWORD error = GetLastError();
1003 if(error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND)
1004 hres = HRESULT_FROM_WIN32(error);
1006 ReleaseMutex(This->mutex);
1007 if(hres == S_OK)
1008 hres = send_storage_event(This, NULL, NULL, NULL);
1009 return hres;
1012 static const IHTMLStorageVtbl HTMLStorageVtbl = {
1013 HTMLStorage_QueryInterface,
1014 HTMLStorage_AddRef,
1015 HTMLStorage_Release,
1016 HTMLStorage_GetTypeInfoCount,
1017 HTMLStorage_GetTypeInfo,
1018 HTMLStorage_GetIDsOfNames,
1019 HTMLStorage_Invoke,
1020 HTMLStorage_get_length,
1021 HTMLStorage_get_remainingSpace,
1022 HTMLStorage_key,
1023 HTMLStorage_getItem,
1024 HTMLStorage_setItem,
1025 HTMLStorage_removeItem,
1026 HTMLStorage_clear
1029 static inline HTMLStorage *impl_from_DispatchEx(DispatchEx *iface)
1031 return CONTAINING_RECORD(iface, HTMLStorage, dispex);
1034 static void *HTMLStorage_query_interface(DispatchEx *dispex, REFIID riid)
1036 HTMLStorage *This = impl_from_DispatchEx(dispex);
1038 if(IsEqualGUID(&IID_IHTMLStorage, riid))
1039 return &This->IHTMLStorage_iface;
1041 return NULL;
1044 static void HTMLStorage_destructor(DispatchEx *dispex)
1046 HTMLStorage *This = impl_from_DispatchEx(dispex);
1047 release_session_map_entry(This->session_storage);
1048 free(This->filename);
1049 CloseHandle(This->mutex);
1050 release_props(This);
1051 free(This);
1054 static HRESULT check_item(HTMLStorage *This, const WCHAR *key)
1056 struct session_entry *session_entry;
1057 IXMLDOMNode *root, *node;
1058 IXMLDOMDocument *doc;
1059 HRESULT hres;
1060 BSTR query;
1062 if(!This->filename) {
1063 hres = get_session_entry(This->session_storage, key, FALSE, &session_entry);
1064 if(SUCCEEDED(hres))
1065 hres = (session_entry && session_entry->value) ? S_OK : S_FALSE;
1066 return hres;
1069 WaitForSingleObject(This->mutex, INFINITE);
1071 hres = open_document(This->filename, &doc);
1072 if(hres == S_OK) {
1073 hres = get_root_node(doc, &root);
1074 IXMLDOMDocument_Release(doc);
1075 if(hres == S_OK) {
1076 if(!(query = build_query(key)))
1077 hres = E_OUTOFMEMORY;
1078 else {
1079 hres = IXMLDOMNode_selectSingleNode(root, query, &node);
1080 SysFreeString(query);
1081 if(hres == S_OK)
1082 IXMLDOMNode_Release(node);
1084 IXMLDOMNode_Release(root);
1088 ReleaseMutex(This->mutex);
1090 return hres;
1093 static HRESULT get_prop(HTMLStorage *This, const WCHAR *name, DISPID *dispid)
1095 UINT name_len = wcslen(name);
1096 BSTR p, *prop, *end;
1098 for(prop = This->props, end = prop + This->num_props; prop != end; prop++) {
1099 if(SysStringLen(*prop) == name_len && !memcmp(*prop, name, name_len * sizeof(WCHAR))) {
1100 *dispid = MSHTML_DISPID_CUSTOM_MIN + (prop - This->props);
1101 return S_OK;
1105 if(is_power_of_2(This->num_props)) {
1106 BSTR *new_props = realloc(This->props, max(This->num_props * 2, 1) * sizeof(*This->props));
1107 if(!new_props)
1108 return E_OUTOFMEMORY;
1109 This->props = new_props;
1112 if(!(p = SysAllocStringLen(name, name_len)))
1113 return E_OUTOFMEMORY;
1115 This->props[This->num_props] = p;
1116 *dispid = MSHTML_DISPID_CUSTOM_MIN + This->num_props++;
1117 return S_OK;
1120 static HRESULT HTMLStorage_get_dispid(DispatchEx *dispex, BSTR name, DWORD flags, DISPID *dispid)
1122 HTMLStorage *This = impl_from_DispatchEx(dispex);
1123 HRESULT hres;
1125 if(flags & fdexNameCaseInsensitive)
1126 FIXME("case insensitive not supported\n");
1128 if(!(flags & fdexNameEnsure)) {
1129 hres = check_item(This, name);
1130 if(hres != S_OK)
1131 return FAILED(hres) ? hres : DISP_E_UNKNOWNNAME;
1134 return get_prop(This, name, dispid);
1137 static HRESULT HTMLStorage_get_name(DispatchEx *dispex, DISPID id, BSTR *name)
1139 HTMLStorage *This = impl_from_DispatchEx(dispex);
1140 DWORD idx = id - MSHTML_DISPID_CUSTOM_MIN;
1142 if(idx >= This->num_props)
1143 return DISP_E_MEMBERNOTFOUND;
1145 return (*name = SysAllocString(This->props[idx])) ? S_OK : E_OUTOFMEMORY;
1148 static HRESULT HTMLStorage_invoke(DispatchEx *dispex, DISPID id, LCID lcid, WORD flags, DISPPARAMS *params,
1149 VARIANT *res, EXCEPINFO *ei, IServiceProvider *caller)
1151 HTMLStorage *This = impl_from_DispatchEx(dispex);
1152 DWORD idx = id - MSHTML_DISPID_CUSTOM_MIN;
1153 HRESULT hres;
1154 BSTR bstr;
1156 if(idx >= This->num_props)
1157 return DISP_E_MEMBERNOTFOUND;
1159 switch(flags) {
1160 case DISPATCH_PROPERTYGET:
1161 hres = HTMLStorage_getItem(&This->IHTMLStorage_iface, This->props[idx], res);
1162 if(FAILED(hres))
1163 return hres;
1164 if(V_VT(res) == VT_NULL)
1165 return DISP_E_MEMBERNOTFOUND;
1166 break;
1167 case DISPATCH_PROPERTYPUTREF:
1168 case DISPATCH_PROPERTYPUT:
1169 if(params->cArgs != 1 || (params->cNamedArgs && params->rgdispidNamedArgs[0] != DISPID_PROPERTYPUT)) {
1170 FIXME("unimplemented args %u %u\n", params->cArgs, params->cNamedArgs);
1171 return E_NOTIMPL;
1174 bstr = V_BSTR(params->rgvarg);
1175 if(V_VT(params->rgvarg) != VT_BSTR) {
1176 VARIANT var;
1177 hres = change_type(&var, params->rgvarg, VT_BSTR, caller);
1178 if(FAILED(hres))
1179 return hres;
1180 bstr = V_BSTR(&var);
1183 hres = HTMLStorage_setItem(&This->IHTMLStorage_iface, This->props[idx], bstr);
1185 if(V_VT(params->rgvarg) != VT_BSTR)
1186 SysFreeString(bstr);
1187 return hres;
1189 default:
1190 FIXME("unimplemented flags %x\n", flags);
1191 return E_NOTIMPL;
1194 return S_OK;
1197 static HRESULT HTMLStorage_delete(DispatchEx *dispex, DISPID id)
1199 HTMLStorage *This = impl_from_DispatchEx(dispex);
1200 DWORD idx = id - MSHTML_DISPID_CUSTOM_MIN;
1202 if(idx >= This->num_props)
1203 return DISP_E_MEMBERNOTFOUND;
1205 if(dispex_compat_mode(dispex) < COMPAT_MODE_IE8)
1206 return MSHTML_E_INVALID_ACTION;
1208 return HTMLStorage_removeItem(&This->IHTMLStorage_iface, This->props[idx]);
1211 static HRESULT HTMLStorage_next_dispid(DispatchEx *dispex, DISPID id, DISPID *pid)
1213 DWORD idx = (id == DISPID_STARTENUM) ? 0 : id - MSHTML_DISPID_CUSTOM_MIN + 1;
1214 HTMLStorage *This = impl_from_DispatchEx(dispex);
1215 HRESULT hres;
1216 DISPID tmp;
1218 if(idx > MSHTML_CUSTOM_DISPID_CNT)
1219 return S_FALSE;
1221 while(idx < This->num_props) {
1222 hres = check_item(This, This->props[idx]);
1223 if(hres == S_OK) {
1224 *pid = idx + MSHTML_DISPID_CUSTOM_MIN;
1225 return S_OK;
1227 if(FAILED(hres))
1228 return hres;
1229 idx++;
1232 /* Populate possibly missing DISPIDs */
1233 if(!This->filename) {
1234 struct session_entry *session_entry;
1236 LIST_FOR_EACH_ENTRY(session_entry, &This->session_storage->data_list, struct session_entry, list_entry) {
1237 hres = get_prop(This, session_entry->key, &tmp);
1238 if(FAILED(hres))
1239 return hres;
1241 }else {
1242 IXMLDOMNodeList *node_list;
1243 IXMLDOMElement *elem;
1244 IXMLDOMNode *node;
1245 LONG index = 0;
1246 HRESULT hres;
1247 VARIANT key;
1249 hres = get_node_list(This->filename, &node_list);
1250 if(FAILED(hres))
1251 return hres;
1253 for(;;) {
1254 hres = IXMLDOMNodeList_get_item(node_list, index++, &node);
1255 if(hres != S_OK) {
1256 IXMLDOMNodeList_Release(node_list);
1257 if(FAILED(hres))
1258 return hres;
1259 break;
1262 hres = IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void**)&elem);
1263 IXMLDOMNode_Release(node);
1264 if(hres != S_OK)
1265 continue;
1267 hres = IXMLDOMElement_getAttribute(elem, (BSTR)L"name", &key);
1268 IXMLDOMElement_Release(elem);
1269 if(hres != S_OK) {
1270 if(SUCCEEDED(hres))
1271 continue;
1272 IXMLDOMNodeList_Release(node_list);
1273 return hres;
1276 if(V_VT(&key) != VT_BSTR) {
1277 FIXME("non-string key %s\n", debugstr_variant(&key));
1278 VariantClear(&key);
1279 continue;
1282 hres = get_prop(This, V_BSTR(&key), &tmp);
1283 SysFreeString(V_BSTR(&key));
1284 if(FAILED(hres)) {
1285 IXMLDOMNodeList_Release(node_list);
1286 return hres;
1291 if(idx >= This->num_props)
1292 return S_FALSE;
1294 *pid = idx + MSHTML_DISPID_CUSTOM_MIN;
1295 return S_OK;
1298 static const dispex_static_data_vtbl_t HTMLStorage_dispex_vtbl = {
1299 .query_interface = HTMLStorage_query_interface,
1300 .destructor = HTMLStorage_destructor,
1301 .get_dispid = HTMLStorage_get_dispid,
1302 .get_name = HTMLStorage_get_name,
1303 .invoke = HTMLStorage_invoke,
1304 .delete = HTMLStorage_delete,
1305 .next_dispid = HTMLStorage_next_dispid,
1308 static const tid_t HTMLStorage_iface_tids[] = {
1309 IHTMLStorage_tid,
1312 static dispex_static_data_t HTMLStorage_dispex = {
1313 "Storage",
1314 &HTMLStorage_dispex_vtbl,
1315 IHTMLStorage_tid,
1316 HTMLStorage_iface_tids
1319 static HRESULT build_session_origin(IUri *uri, BSTR hostname, BSTR *ret)
1321 UINT host_len, scheme_len;
1322 BSTR scheme, origin;
1323 HRESULT hres;
1325 hres = IUri_GetSchemeName(uri, &scheme);
1326 if(FAILED(hres))
1327 return hres;
1328 if(hres != S_OK) {
1329 SysFreeString(scheme);
1330 scheme = NULL;
1333 /* Since it's only used for lookup, we can apply transformations to
1334 keep the lookup itself simple and fast. First, we convert `https`
1335 to `http` because they are equal for lookup. Next, we place the
1336 scheme after the hostname, separated by NUL, to compare the host
1337 first, since it tends to differ more often. Lastly, we lowercase
1338 the whole thing since lookup must be case-insensitive. */
1339 scheme_len = SysStringLen(scheme);
1340 host_len = SysStringLen(hostname);
1342 if(scheme_len == 5 && !wcsicmp(scheme, L"https"))
1343 scheme_len--;
1345 origin = SysAllocStringLen(NULL, host_len + 1 + scheme_len);
1346 if(origin) {
1347 WCHAR *p = origin;
1348 memcpy(p, hostname, host_len * sizeof(WCHAR));
1349 p += host_len;
1350 *p = ' '; /* for wcslwr */
1351 memcpy(p + 1, scheme, scheme_len * sizeof(WCHAR));
1352 p[1 + scheme_len] = '\0';
1353 _wcslwr(origin);
1354 *p = '\0';
1356 SysFreeString(scheme);
1358 if(!origin)
1359 return E_OUTOFMEMORY;
1361 *ret = origin;
1362 return S_OK;
1365 static WCHAR *build_filename(BSTR hostname)
1367 static const WCHAR store[] = L"\\Microsoft\\Internet Explorer\\DOMStore\\";
1368 WCHAR path[MAX_PATH], *ret;
1369 int len;
1371 if(!SHGetSpecialFolderPathW(NULL, path, CSIDL_LOCAL_APPDATA, TRUE)) {
1372 ERR("Can't get folder path %lu\n", GetLastError());
1373 return NULL;
1376 len = wcslen(path);
1377 if(len + ARRAY_SIZE(store) > ARRAY_SIZE(path)) {
1378 ERR("Path too long\n");
1379 return NULL;
1381 memcpy(path + len, store, sizeof(store));
1383 len += ARRAY_SIZE(store);
1384 ret = malloc((len + wcslen(hostname) + ARRAY_SIZE(L".xml")) * sizeof(WCHAR));
1385 if(!ret) {
1386 return NULL;
1389 wcscpy(ret, path);
1390 wcscat(ret, hostname);
1391 wcscat(ret, L".xml");
1393 return ret;
1396 static WCHAR *build_mutexname(const WCHAR *filename)
1398 WCHAR *ret, *ptr;
1399 ret = wcsdup(filename);
1400 if(!ret)
1401 return NULL;
1402 for(ptr = ret; *ptr; ptr++)
1403 if(*ptr == '\\') *ptr = '_';
1404 return ret;
1407 HRESULT create_html_storage(HTMLInnerWindow *window, BOOL local, IHTMLStorage **p)
1409 IUri *uri = window->base.outer_window->uri;
1410 BSTR origin, hostname = NULL;
1411 HTMLStorage *storage;
1412 HRESULT hres;
1414 if(!uri)
1415 return S_FALSE;
1417 hres = IUri_GetHost(uri, &hostname);
1418 if(hres != S_OK) {
1419 SysFreeString(hostname);
1420 return hres;
1423 storage = calloc(1, sizeof(*storage));
1424 if(!storage) {
1425 SysFreeString(hostname);
1426 return E_OUTOFMEMORY;
1429 if(local) {
1430 WCHAR *mutexname;
1431 storage->filename = build_filename(hostname);
1432 SysFreeString(hostname);
1433 if(!storage->filename) {
1434 free(storage);
1435 return E_OUTOFMEMORY;
1437 mutexname = build_mutexname(storage->filename);
1438 if(!mutexname) {
1439 free(storage->filename);
1440 free(storage);
1441 return E_OUTOFMEMORY;
1443 storage->mutex = CreateMutexW(NULL, FALSE, mutexname);
1444 free(mutexname);
1445 if(!storage->mutex) {
1446 free(storage->filename);
1447 free(storage);
1448 return HRESULT_FROM_WIN32(GetLastError());
1450 }else {
1451 hres = build_session_origin(uri, hostname, &origin);
1452 SysFreeString(hostname);
1453 if(hres != S_OK) {
1454 free(storage);
1455 return hres;
1457 storage->session_storage = grab_session_map_entry(origin);
1458 SysFreeString(origin);
1459 if(!storage->session_storage) {
1460 free(storage);
1461 return E_OUTOFMEMORY;
1465 storage->IHTMLStorage_iface.lpVtbl = &HTMLStorageVtbl;
1466 storage->window = window;
1468 init_dispatch(&storage->dispex, &HTMLStorage_dispex, dispex_compat_mode(&window->event_target.dispex));
1470 *p = &storage->IHTMLStorage_iface;
1471 return S_OK;
1474 void detach_html_storage(IHTMLStorage *iface)
1476 HTMLStorage *storage = impl_from_IHTMLStorage(iface);
1477 storage->window = NULL;