mshtml: Fixed ref count leak.
[wine] / dlls / mshtml / txtrange.c
1 /*
2  * Copyright 2006-2007 Jacek Caban for CodeWeavers
3  *
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.
8  *
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.
13  *
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
17  */
18
19 #include "config.h"
20
21 #include <stdarg.h>
22 #include <stdio.h>
23
24 #define COBJMACROS
25
26 #include "windef.h"
27 #include "winbase.h"
28 #include "winuser.h"
29 #include "winnls.h"
30 #include "ole2.h"
31
32 #include "wine/debug.h"
33 #include "wine/unicode.h"
34
35 #include "mshtml_private.h"
36
37 WINE_DEFAULT_DEBUG_CHANNEL(mshtml);
38
39 static const WCHAR brW[] = {'b','r',0};
40
41 typedef struct {
42     const IHTMLTxtRangeVtbl *lpHTMLTxtRangeVtbl;
43
44     LONG ref;
45
46     nsIDOMRange *nsrange;
47     HTMLDocument *doc;
48
49     struct list entry;
50 } HTMLTxtRange;
51
52 #define HTMLTXTRANGE(x)  ((IHTMLTxtRange*)  &(x)->lpHTMLTxtRangeVtbl)
53
54 typedef struct {
55     WCHAR *buf;
56     DWORD len;
57     DWORD size;
58 } wstrbuf_t;
59
60 typedef struct {
61     PRUint16 type;
62     nsIDOMNode *node;
63     PRUint32 off;
64     nsAString str;
65     const PRUnichar *p;
66 } dompos_t;
67
68 typedef enum {
69     RU_UNKNOWN,
70     RU_CHAR,
71     RU_WORD,
72     RU_SENTENCE,
73     RU_TEXTEDIT
74 } range_unit_t;
75
76 static HTMLTxtRange *get_range_object(HTMLDocument *doc, IHTMLTxtRange *iface)
77 {
78     HTMLTxtRange *iter;
79
80     LIST_FOR_EACH_ENTRY(iter, &doc->range_list, HTMLTxtRange, entry) {
81         if(HTMLTXTRANGE(iter) == iface)
82             return iter;
83     }
84
85     ERR("Could not find range in document\n");
86     return NULL;
87 }
88
89 static range_unit_t string_to_unit(LPCWSTR str)
90 {
91     static const WCHAR characterW[] =
92         {'c','h','a','r','a','c','t','e','r',0};
93     static const WCHAR wordW[] =
94         {'w','o','r','d',0};
95     static const WCHAR sentenceW[] =
96         {'s','e','n','t','e','n','c','e',0};
97     static const WCHAR texteditW[] =
98         {'t','e','x','t','e','d','i','t',0};
99
100     if(!strcmpiW(str, characterW))  return RU_CHAR;
101     if(!strcmpiW(str, wordW))       return RU_WORD;
102     if(!strcmpiW(str, sentenceW))   return RU_SENTENCE;
103     if(!strcmpiW(str, texteditW))   return RU_TEXTEDIT;
104
105     return RU_UNKNOWN;
106 }
107
108 static int string_to_nscmptype(LPCWSTR str)
109 {
110     static const WCHAR seW[] = {'S','t','a','r','t','T','o','E','n','d',0};
111     static const WCHAR ssW[] = {'S','t','a','r','t','T','o','S','t','a','r','t',0};
112     static const WCHAR esW[] = {'E','n','d','T','o','S','t','a','r','t',0};
113     static const WCHAR eeW[] = {'E','n','d','T','o','E','n','d',0};
114
115     if(!strcmpiW(str, seW))  return NS_START_TO_END;
116     if(!strcmpiW(str, ssW))  return NS_START_TO_START;
117     if(!strcmpiW(str, esW))  return NS_END_TO_START;
118     if(!strcmpiW(str, eeW))  return NS_END_TO_END;
119
120     return -1;
121 }
122
123 static PRUint16 get_node_type(nsIDOMNode *node)
124 {
125     PRUint16 type = 0xfff;
126
127     if(node)
128         nsIDOMNode_GetNodeType(node, &type);
129
130     return type;
131 }
132
133 static BOOL is_br_node(nsIDOMNode *node)
134 {
135     nsIDOMElement *elem;
136     nsAString tag_str;
137     const PRUnichar *tag;
138     BOOL ret = FALSE;
139     nsresult nsres;
140
141     nsres = nsIDOMNode_QueryInterface(node, &IID_nsIDOMElement, (void**)&elem);
142     if(NS_FAILED(nsres))
143         return FALSE;
144
145     nsAString_Init(&tag_str, NULL);
146     nsIDOMElement_GetTagName(elem, &tag_str);
147     nsIDOMElement_Release(elem);
148     nsAString_GetData(&tag_str, &tag, 0);
149
150     if(!strcmpiW(tag, brW))
151         ret = TRUE;
152
153     nsAString_Finish(&tag_str);
154
155     return ret;
156 }
157
158 static inline void wstrbuf_init(wstrbuf_t *buf)
159 {
160     buf->len = 0;
161     buf->size = 16;
162     buf->buf = mshtml_alloc(buf->size * sizeof(WCHAR));
163     *buf->buf = 0;
164 }
165
166 static inline void wstrbuf_finish(wstrbuf_t *buf)
167 {
168     mshtml_free(buf->buf);
169 }
170
171 static void wstrbuf_append_len(wstrbuf_t *buf, LPCWSTR str, int len)
172 {
173     if(buf->len+len >= buf->size) {
174         buf->size = 2*buf->len+len;
175         buf->buf = mshtml_realloc(buf->buf, buf->size * sizeof(WCHAR));
176     }
177
178     memcpy(buf->buf+buf->len, str, len*sizeof(WCHAR));
179     buf->len += len;
180     buf->buf[buf->len] = 0;
181 }
182
183 static inline void wstrbuf_append(wstrbuf_t *buf, LPCWSTR str)
184 {
185     wstrbuf_append_len(buf, str, strlenW(str));
186 }
187
188 static void wstrbuf_append_node(wstrbuf_t *buf, nsIDOMNode *node)
189 {
190
191     switch(get_node_type(node)) {
192     case TEXT_NODE: {
193         nsIDOMText *nstext;
194         nsAString data_str;
195         const PRUnichar *data;
196
197         nsIDOMNode_QueryInterface(node, &IID_nsIDOMText, (void**)&nstext);
198
199         nsAString_Init(&data_str, NULL);
200         nsIDOMText_GetData(nstext, &data_str);
201         nsAString_GetData(&data_str, &data, NULL);
202         wstrbuf_append(buf, data);
203         nsAString_Finish(&data_str);
204
205        nsIDOMText_Release(nstext);
206
207         break;
208     }
209     case ELEMENT_NODE:
210         if(is_br_node(node)) {
211             static const WCHAR endlW[] = {'\r','\n'};
212             wstrbuf_append_len(buf, endlW, 2);
213         }
214     }
215 }
216
217 static BOOL fill_nodestr(dompos_t *pos)
218 {
219     nsIDOMText *text;
220     nsresult nsres;
221
222     if(pos->type != TEXT_NODE)
223         return FALSE;
224
225     nsres = nsIDOMNode_QueryInterface(pos->node, &IID_nsIDOMText, (void**)&text);
226     if(NS_FAILED(nsres))
227         return FALSE;
228
229     nsAString_Init(&pos->str, NULL);
230     nsIDOMText_GetData(text, &pos->str);
231     nsIDOMText_Release(text);
232     nsAString_GetData(&pos->str, &pos->p, NULL);
233
234     return TRUE;
235 }
236
237 static nsIDOMNode *next_node(nsIDOMNode *iter)
238 {
239     nsIDOMNode *ret, *tmp;
240     nsresult nsres;
241
242     if(!iter)
243         return NULL;
244
245     nsres = nsIDOMNode_GetFirstChild(iter, &ret);
246     if(NS_SUCCEEDED(nsres) && ret)
247         return ret;
248
249     nsIDOMNode_AddRef(iter);
250
251     do {
252         nsres = nsIDOMNode_GetNextSibling(iter, &ret);
253         if(NS_SUCCEEDED(nsres) && ret) {
254             nsIDOMNode_Release(iter);
255             return ret;
256         }
257
258         nsres = nsIDOMNode_GetParentNode(iter, &tmp);
259         nsIDOMNode_Release(iter);
260         iter = tmp;
261     }while(NS_SUCCEEDED(nsres) && iter);
262
263     return NULL;
264 }
265
266 static nsIDOMNode *prev_node(HTMLTxtRange *This, nsIDOMNode *iter)
267 {
268     nsIDOMNode *ret, *tmp;
269     nsresult nsres;
270
271     if(!iter) {
272         nsIDOMHTMLDocument *nshtmldoc;
273         nsIDOMHTMLElement *nselem;
274         nsIDOMDocument *nsdoc;
275
276         nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
277         nsIDOMDocument_QueryInterface(nsdoc, &IID_nsIDOMHTMLDocument, (void**)&nshtmldoc);
278         nsIDOMDocument_Release(nsdoc);
279         nsIDOMHTMLDocument_GetBody(nshtmldoc, &nselem);
280         nsIDOMHTMLDocument_Release(nshtmldoc);
281
282         nsIDOMElement_GetLastChild(nselem, &tmp);
283         if(!tmp)
284             return (nsIDOMNode*)nselem;
285
286         while(tmp) {
287             ret = tmp;
288             nsIDOMNode_GetLastChild(ret, &tmp);
289         }
290
291         nsIDOMElement_Release(nselem);
292
293         return ret;
294     }
295
296     nsres = nsIDOMNode_GetLastChild(iter, &ret);
297     if(NS_SUCCEEDED(nsres) && ret)
298         return ret;
299
300     nsIDOMNode_AddRef(iter);
301
302     do {
303         nsres = nsIDOMNode_GetPreviousSibling(iter, &ret);
304         if(NS_SUCCEEDED(nsres) && ret) {
305             nsIDOMNode_Release(iter);
306             return ret;
307         }
308
309         nsres = nsIDOMNode_GetParentNode(iter, &tmp);
310         nsIDOMNode_Release(iter);
311         iter = tmp;
312     }while(NS_SUCCEEDED(nsres) && iter);
313
314     return NULL;
315 }
316
317 static nsIDOMNode *get_child_node(nsIDOMNode *node, PRUint32 off)
318 {
319     nsIDOMNodeList *node_list;
320     nsIDOMNode *ret = NULL;
321
322     nsIDOMNode_GetChildNodes(node, &node_list);
323     nsIDOMNodeList_Item(node_list, off, &ret);
324     nsIDOMNodeList_Release(node_list);
325
326     return ret;
327 }
328
329 static void get_cur_pos(HTMLTxtRange *This, BOOL start, dompos_t *pos)
330 {
331     nsIDOMNode *node;
332     PRInt32 off;
333
334     pos->p = NULL;
335
336     if(!start) {
337         PRBool collapsed;
338         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
339         start = collapsed;
340     }
341
342     if(start) {
343         nsIDOMRange_GetStartContainer(This->nsrange, &node);
344         nsIDOMRange_GetStartOffset(This->nsrange, &off);
345     }else {
346         nsIDOMRange_GetEndContainer(This->nsrange, &node);
347         nsIDOMRange_GetEndOffset(This->nsrange, &off);
348     }
349
350     pos->type = get_node_type(node);
351     if(pos->type == ELEMENT_NODE) {
352         if(start) {
353             pos->node = get_child_node(node, off);
354             pos->off = 0;
355         }else {
356             pos->node = off ? get_child_node(node, off-1) : prev_node(This, node);
357             pos->off = -1;
358         }
359
360         pos->type = get_node_type(pos->node);
361         nsIDOMNode_Release(node);
362     }else if(start) {
363         pos->node = node;
364         pos->off = off;
365     }else if(off) {
366         pos->node = node;
367         pos->off = off-1;
368     }else {
369         pos->node = prev_node(This, node);
370         pos->off = -1;
371         nsIDOMNode_Release(node);
372     }
373
374     if(pos->type == TEXT_NODE)
375         fill_nodestr(pos);
376 }
377
378 static void set_range_pos(HTMLTxtRange *This, BOOL start, dompos_t *pos)
379 {
380     nsresult nsres;
381
382     if(start) {
383         if(pos->type == TEXT_NODE)
384             nsres = nsIDOMRange_SetStart(This->nsrange, pos->node, pos->off);
385         else
386             nsres = nsIDOMRange_SetStartBefore(This->nsrange, pos->node);
387     }else {
388         if(pos->type == TEXT_NODE)
389             nsres = nsIDOMRange_SetEnd(This->nsrange, pos->node, pos->off+1);
390         else
391             nsres = nsIDOMRange_SetEndAfter(This->nsrange, pos->node);
392     }
393
394     if(NS_FAILED(nsres))
395         ERR("failed: %p %08x\n", pos->node, nsres);
396 }
397
398 static inline void dompos_release(dompos_t *pos)
399 {
400     if(pos->node)
401         nsIDOMNode_Release(pos->node);
402
403     if(pos->p)
404         nsAString_Finish(&pos->str);
405 }
406
407 static inline void dompos_addref(dompos_t *pos)
408 {
409     if(pos->node)
410         nsIDOMNode_AddRef(pos->node);
411
412     if(pos->type == TEXT_NODE)
413         fill_nodestr(pos);
414 }
415
416 static void range_to_string(HTMLTxtRange *This, wstrbuf_t *buf)
417 {
418     nsIDOMNode *iter, *tmp;
419     dompos_t start_pos, end_pos;
420     PRBool collapsed;
421
422     nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
423     if(collapsed) {
424         wstrbuf_finish(buf);
425         buf->buf = NULL;
426         buf->size = 0;
427         return;
428     }
429
430     get_cur_pos(This, FALSE, &end_pos);
431     get_cur_pos(This, TRUE, &start_pos);
432
433     if(start_pos.type == TEXT_NODE) {
434         if(start_pos.node == end_pos.node) {
435             wstrbuf_append_len(buf, start_pos.p+start_pos.off, end_pos.off-start_pos.off+1);
436             iter = start_pos.node;
437             nsIDOMNode_AddRef(iter);
438         }else {
439             wstrbuf_append(buf, start_pos.p+start_pos.off);
440             iter = next_node(start_pos.node);
441         }
442     }else {
443         iter = start_pos.node;
444         nsIDOMNode_AddRef(iter);
445     }
446
447     while(iter != end_pos.node) {
448         wstrbuf_append_node(buf, iter);
449         tmp = next_node(iter);
450         nsIDOMNode_Release(iter);
451         iter = tmp;
452     }
453
454     nsIDOMNode_AddRef(end_pos.node);
455
456     if(start_pos.node != end_pos.node && !is_br_node(end_pos.node))
457         wstrbuf_append_len(buf, end_pos.p, end_pos.off+1);
458
459     nsIDOMNode_Release(iter);
460     dompos_release(&start_pos);
461     dompos_release(&end_pos);
462 }
463
464 static WCHAR get_pos_char(const dompos_t *pos)
465 {
466     switch(pos->type) {
467     case TEXT_NODE:
468         return pos->p[pos->off];
469     case ELEMENT_NODE:
470         if(is_br_node(pos->node))
471             return '\n';
472     }
473
474     return 0;
475 }
476
477 static WCHAR next_char(const dompos_t *pos, dompos_t *new_pos)
478 {
479     nsIDOMNode *iter, *tmp;
480
481     if(pos->type == TEXT_NODE && pos->off != -1 && pos->p[pos->off+1]) {
482         *new_pos = *pos;
483         new_pos->off++;
484         dompos_addref(new_pos);
485         return new_pos->p[new_pos->off];
486     }
487
488     iter = next_node(pos->node);
489     if(!iter)
490         return 0;
491
492     while(1) {
493         switch(get_node_type(iter)) {
494         case TEXT_NODE:
495             new_pos->node = iter;
496             new_pos->type = TEXT_NODE;
497             new_pos->off = 0;
498             fill_nodestr(new_pos);
499             return *new_pos->p;
500
501         case ELEMENT_NODE:
502             if(!is_br_node(iter))
503                 break;
504
505             new_pos->node = iter;
506             new_pos->type = ELEMENT_NODE;
507             new_pos->off = 0;
508             new_pos->p = NULL;
509             return '\n';
510         }
511
512         tmp = iter;
513         iter = next_node(iter);
514         nsIDOMNode_Release(tmp);
515
516         if(!iter)
517             break;
518     }
519
520     return 0;
521 }
522
523 static WCHAR prev_char(HTMLTxtRange *This, const dompos_t *pos, dompos_t *new_pos)
524 {
525     nsIDOMNode *iter, *tmp;
526
527     if(pos->type == TEXT_NODE && pos->off > 0) {
528         *new_pos = *pos;
529         new_pos->off--;
530         dompos_addref(new_pos);
531         return new_pos->p[new_pos->off];
532     }
533
534     iter = prev_node(This, pos->node);
535     if(!iter)
536         return 0;
537
538     while(1) {
539         switch(get_node_type(iter)) {
540         case TEXT_NODE:
541             new_pos->node = iter;
542             new_pos->type = TEXT_NODE;
543             fill_nodestr(new_pos);
544             new_pos->off = strlenW(new_pos->p)-1;
545             return new_pos->p[new_pos->off];
546
547         case ELEMENT_NODE:
548             if(!is_br_node(iter))
549                 break;
550
551             new_pos->node = iter;
552             new_pos->type = ELEMENT_NODE;
553             new_pos->off = 0;
554             new_pos->p = NULL;
555             return '\n';
556         }
557
558         tmp = iter;
559         iter = prev_node(This, iter);
560         nsIDOMNode_Release(tmp);
561
562         if(!iter)
563             break;
564     }
565
566     *new_pos = *pos;
567     dompos_addref(new_pos);
568     return 0;
569 }
570
571 static long move_next_chars(long cnt, const dompos_t *pos, BOOL col, dompos_t *new_pos)
572 {
573     dompos_t iter, tmp;
574     long ret = 0;
575     WCHAR c;
576
577     if(col)
578         ret++;
579
580     if(ret >= cnt) {
581         *new_pos = *pos;
582         dompos_addref(new_pos);
583         return ret;
584     }
585
586     c = next_char(pos, &iter);
587     ret++;
588
589     while(ret < cnt) {
590         tmp = iter;
591         c = next_char(&tmp, &iter);
592         if(!c) {
593             iter = tmp;
594             break;
595         }
596         ret++;
597         dompos_release(&tmp);
598     }
599
600     *new_pos = iter;
601     return ret;
602 }
603
604 static long move_prev_chars(HTMLTxtRange *This, long cnt, const dompos_t *pos, BOOL end, dompos_t *new_pos)
605 {
606     dompos_t iter, tmp;
607     long ret = 0;
608     WCHAR c;
609
610     c = prev_char(This, pos, &iter);
611     if(c)
612         ret++;
613
614     while(c && ret < cnt) {
615         tmp = iter;
616         c = prev_char(This, &tmp, &iter);
617         if(!c) {
618             iter = tmp;
619             if(end)
620                 ret++;
621             break;
622         }
623         ret++;
624         dompos_release(&tmp);
625     }
626
627     *new_pos = iter;
628     return ret;
629 }
630
631 static BOOL find_next_space(const dompos_t *pos, BOOL first_space, dompos_t *ret)
632 {
633     dompos_t iter, tmp;
634     WCHAR c;
635
636     if(first_space) {
637         c = get_pos_char(pos);
638         if(c && isspaceW(c)) {
639             *ret = *pos;
640             dompos_addref(ret);
641             return FALSE;
642         }
643     }
644
645     c = next_char(pos, &iter);
646     if(!c) {
647         *ret = *pos;
648         dompos_addref(ret);
649         return FALSE;
650     }
651
652     while(!isspaceW(c)) {
653         tmp = iter;
654         c = next_char(&tmp, &iter);
655         if(!c) {
656             iter = tmp;
657             break;
658         }
659         dompos_release(&tmp);
660     }
661
662     *ret = iter;
663     return TRUE;
664 }
665
666 static long find_prev_space(HTMLTxtRange *This, const dompos_t *pos, BOOL first_space, dompos_t *ret)
667 {
668     dompos_t iter, tmp;
669     WCHAR c;
670
671     c = prev_char(This, pos, &iter);
672     if(!c || (first_space && isspaceW(c))) {
673         *ret = *pos;
674         dompos_addref(ret);
675         return FALSE;
676     }
677
678     while(1) {
679         tmp = iter;
680         c = prev_char(This, &tmp, &iter);
681         if(!c || isspaceW(c)) {
682             dompos_release(&iter);
683             break;
684         }
685         dompos_release(&tmp);
686     }
687
688     *ret = tmp;
689     return TRUE;
690 }
691
692 static long move_next_words(long cnt, const dompos_t *pos, dompos_t *new_pos)
693 {
694     dompos_t iter, tmp;
695     long ret = 0;
696
697     iter = *pos;
698     dompos_addref(&iter);
699
700     while(ret < cnt) {
701         if(!find_next_space(&iter, FALSE, &tmp))
702             break;
703
704         ret++;
705         dompos_release(&iter);
706         iter = tmp;
707     }
708
709     *new_pos = iter;
710     return ret;
711 }
712
713 static long move_prev_words(HTMLTxtRange *This, long cnt, const dompos_t *pos, dompos_t *new_pos)
714 {
715     dompos_t iter, tmp;
716     long ret = 0;
717
718     iter = *pos;
719     dompos_addref(&iter);
720
721     while(ret < cnt) {
722         if(!find_prev_space(This, &iter, FALSE, &tmp))
723             break;
724
725         dompos_release(&iter);
726         iter = tmp;
727         ret++;
728     }
729
730     *new_pos = iter;
731     return ret;
732 }
733
734 #define HTMLTXTRANGE_THIS(iface) DEFINE_THIS(HTMLTxtRange, HTMLTxtRange, iface)
735
736 static HRESULT WINAPI HTMLTxtRange_QueryInterface(IHTMLTxtRange *iface, REFIID riid, void **ppv)
737 {
738     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
739
740     *ppv = NULL;
741
742     if(IsEqualGUID(&IID_IUnknown, riid)) {
743         TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
744         *ppv = HTMLTXTRANGE(This);
745     }else if(IsEqualGUID(&IID_IDispatch, riid)) {
746         TRACE("(%p)->(IID_IDispatch %p)\n", This, ppv);
747         *ppv = HTMLTXTRANGE(This);
748     }else if(IsEqualGUID(&IID_IHTMLTxtRange, riid)) {
749         TRACE("(%p)->(IID_IHTMLTxtRange %p)\n", This, ppv);
750         *ppv = HTMLTXTRANGE(This);
751     }
752
753     if(*ppv) {
754         IUnknown_AddRef((IUnknown*)*ppv);
755         return S_OK;
756     }
757
758     WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
759     return E_NOINTERFACE;
760 }
761
762 static ULONG WINAPI HTMLTxtRange_AddRef(IHTMLTxtRange *iface)
763 {
764     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
765     LONG ref = InterlockedIncrement(&This->ref);
766
767     TRACE("(%p) ref=%d\n", This, ref);
768
769     return ref;
770 }
771
772 static ULONG WINAPI HTMLTxtRange_Release(IHTMLTxtRange *iface)
773 {
774     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
775     LONG ref = InterlockedDecrement(&This->ref);
776
777     TRACE("(%p) ref=%d\n", This, ref);
778
779     if(!ref) {
780         if(This->nsrange)
781             nsISelection_Release(This->nsrange);
782         if(This->doc)
783             list_remove(&This->entry);
784         mshtml_free(This);
785     }
786
787     return ref;
788 }
789
790 static HRESULT WINAPI HTMLTxtRange_GetTypeInfoCount(IHTMLTxtRange *iface, UINT *pctinfo)
791 {
792     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
793     FIXME("(%p)->(%p)\n", This, pctinfo);
794     return E_NOTIMPL;
795 }
796
797 static HRESULT WINAPI HTMLTxtRange_GetTypeInfo(IHTMLTxtRange *iface, UINT iTInfo,
798                                                LCID lcid, ITypeInfo **ppTInfo)
799 {
800     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
801     FIXME("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
802     return E_NOTIMPL;
803 }
804
805 static HRESULT WINAPI HTMLTxtRange_GetIDsOfNames(IHTMLTxtRange *iface, REFIID riid,
806                                                  LPOLESTR *rgszNames, UINT cNames,
807                                                  LCID lcid, DISPID *rgDispId)
808 {
809     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
810     FIXME("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
811           lcid, rgDispId);
812     return E_NOTIMPL;
813 }
814
815 static HRESULT WINAPI HTMLTxtRange_Invoke(IHTMLTxtRange *iface, DISPID dispIdMember,
816                             REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
817                             VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
818 {
819     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
820     FIXME("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
821           lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
822     return E_NOTIMPL;
823 }
824
825 static HRESULT WINAPI HTMLTxtRange_get_htmlText(IHTMLTxtRange *iface, BSTR *p)
826 {
827     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
828
829     TRACE("(%p)->(%p)\n", This, p);
830
831     *p = NULL;
832
833     if(This->nsrange) {
834         nsIDOMDocumentFragment *fragment;
835         nsresult nsres;
836
837         nsres = nsIDOMRange_CloneContents(This->nsrange, &fragment);
838         if(NS_SUCCEEDED(nsres)) {
839             const PRUnichar *nstext;
840             nsAString nsstr;
841
842             nsAString_Init(&nsstr, NULL);
843             nsnode_to_nsstring((nsIDOMNode*)fragment, &nsstr);
844             nsIDOMDocumentFragment_Release(fragment);
845
846             nsAString_GetData(&nsstr, &nstext, NULL);
847             *p = SysAllocString(nstext);
848
849             nsAString_Finish(&nsstr);
850         }
851     }
852
853     if(!*p) {
854         const WCHAR emptyW[] = {0};
855         *p = SysAllocString(emptyW);
856     }
857
858     TRACE("return %s\n", debugstr_w(*p));
859     return S_OK;
860 }
861
862 static HRESULT WINAPI HTMLTxtRange_put_text(IHTMLTxtRange *iface, BSTR v)
863 {
864     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
865     nsIDOMDocument *nsdoc;
866     nsIDOMText *text_node;
867     nsAString text_str;
868     nsresult nsres;
869
870     TRACE("(%p)->(%s)\n", This, debugstr_w(v));
871
872     if(!This->doc)
873         return MSHTML_E_NODOC;
874
875     nsres = nsIWebNavigation_GetDocument(This->doc->nscontainer->navigation, &nsdoc);
876     if(NS_FAILED(nsres)) {
877         ERR("GetDocument failed: %08x\n", nsres);
878         return S_OK;
879     }
880
881     nsAString_Init(&text_str, v);
882     nsres = nsIDOMDocument_CreateTextNode(nsdoc, &text_str, &text_node);
883     nsIDOMDocument_Release(nsdoc);
884     nsAString_Finish(&text_str);
885     if(NS_FAILED(nsres)) {
886         ERR("CreateTextNode failed: %08x\n", nsres);
887         return S_OK;
888     }
889     nsres = nsIDOMRange_DeleteContents(This->nsrange);
890     if(NS_FAILED(nsres))
891         ERR("DeleteContents failed: %08x\n", nsres);
892
893     nsres = nsIDOMRange_InsertNode(This->nsrange, (nsIDOMNode*)text_node);
894     if(NS_FAILED(nsres))
895         ERR("InsertNode failed: %08x\n", nsres);
896
897     nsres = nsIDOMRange_SetEndAfter(This->nsrange, (nsIDOMNode*)text_node);
898     if(NS_FAILED(nsres))
899         ERR("SetEndAfter failed: %08x\n", nsres);
900
901     return IHTMLTxtRange_collapse(HTMLTXTRANGE(This), VARIANT_FALSE);
902 }
903
904 static HRESULT WINAPI HTMLTxtRange_get_text(IHTMLTxtRange *iface, BSTR *p)
905 {
906     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
907     wstrbuf_t buf;
908
909     TRACE("(%p)->(%p)\n", This, p);
910
911     *p = NULL;
912     if(!This->nsrange)
913         return S_OK;
914
915     wstrbuf_init(&buf);
916     range_to_string(This, &buf);
917     if(buf.buf)
918         *p = SysAllocString(buf.buf);
919     wstrbuf_finish(&buf);
920
921     TRACE("ret %s\n", debugstr_w(*p));
922     return S_OK;
923 }
924
925 static HRESULT WINAPI HTMLTxtRange_parentElement(IHTMLTxtRange *iface, IHTMLElement **parent)
926 {
927     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
928     nsIDOMNode *nsnode, *tmp;
929     HTMLDOMNode *node;
930
931     TRACE("(%p)->(%p)\n", This, parent);
932
933     nsIDOMRange_GetCommonAncestorContainer(This->nsrange, &nsnode);
934     while(nsnode && get_node_type(nsnode) != ELEMENT_NODE) {
935         nsIDOMNode_GetParentNode(nsnode, &tmp);
936         nsIDOMNode_Release(nsnode);
937         nsnode = tmp;
938     }
939
940     if(!nsnode) {
941         *parent = NULL;
942         return S_OK;
943     }
944
945     node = get_node(This->doc, nsnode);
946     nsIDOMNode_Release(nsnode);
947
948     return IHTMLDOMNode_QueryInterface(HTMLDOMNODE(node), &IID_IHTMLElement, (void**)parent);
949 }
950
951 static HRESULT WINAPI HTMLTxtRange_duplicate(IHTMLTxtRange *iface, IHTMLTxtRange **Duplicate)
952 {
953     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
954     nsIDOMRange *nsrange = NULL;
955
956     TRACE("(%p)->(%p)\n", This, Duplicate);
957
958     nsIDOMRange_CloneRange(This->nsrange, &nsrange);
959     *Duplicate = HTMLTxtRange_Create(This->doc, nsrange);
960     nsIDOMRange_Release(nsrange);
961
962     return S_OK;
963 }
964
965 static HRESULT WINAPI HTMLTxtRange_inRange(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
966         VARIANT_BOOL *InRange)
967 {
968     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
969     HTMLTxtRange *src_range;
970     PRInt16 nsret = 0;
971     nsresult nsres;
972
973     TRACE("(%p)->(%p %p)\n", This, Range, InRange);
974
975     *InRange = VARIANT_FALSE;
976
977     src_range = get_range_object(This->doc, Range);
978     if(!src_range)
979         return E_FAIL;
980
981     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
982             src_range->nsrange, &nsret);
983     if(NS_SUCCEEDED(nsres) && nsret <= 0) {
984         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
985                 src_range->nsrange, &nsret);
986         if(NS_SUCCEEDED(nsres) && nsret >= 0)
987             *InRange = VARIANT_TRUE;
988     }
989
990     if(NS_FAILED(nsres))
991         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
992
993     return S_OK;
994 }
995
996 static HRESULT WINAPI HTMLTxtRange_isEqual(IHTMLTxtRange *iface, IHTMLTxtRange *Range,
997         VARIANT_BOOL *IsEqual)
998 {
999     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1000     HTMLTxtRange *src_range;
1001     PRInt16 nsret = 0;
1002     nsresult nsres;
1003
1004     TRACE("(%p)->(%p %p)\n", This, Range, IsEqual);
1005
1006     *IsEqual = VARIANT_FALSE;
1007
1008     src_range = get_range_object(This->doc, Range);
1009     if(!src_range)
1010         return E_FAIL;
1011
1012     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_START_TO_START,
1013             src_range->nsrange, &nsret);
1014     if(NS_SUCCEEDED(nsres) && !nsret) {
1015         nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, NS_END_TO_END,
1016                 src_range->nsrange, &nsret);
1017         if(NS_SUCCEEDED(nsres) && !nsret)
1018             *IsEqual = VARIANT_TRUE;
1019     }
1020
1021     if(NS_FAILED(nsres))
1022         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1023
1024     return S_OK;
1025 }
1026
1027 static HRESULT WINAPI HTMLTxtRange_scrollIntoView(IHTMLTxtRange *iface, VARIANT_BOOL fStart)
1028 {
1029     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1030     FIXME("(%p)->(%x)\n", This, fStart);
1031     return E_NOTIMPL;
1032 }
1033
1034 static HRESULT WINAPI HTMLTxtRange_collapse(IHTMLTxtRange *iface, VARIANT_BOOL Start)
1035 {
1036     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1037
1038     TRACE("(%p)->(%x)\n", This, Start);
1039
1040     nsIDOMRange_Collapse(This->nsrange, Start != VARIANT_FALSE);
1041     return S_OK;
1042 }
1043
1044 static HRESULT WINAPI HTMLTxtRange_expand(IHTMLTxtRange *iface, BSTR Unit, VARIANT_BOOL *Success)
1045 {
1046     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1047     range_unit_t unit;
1048
1049     TRACE("(%p)->(%s %p)\n", This, debugstr_w(Unit), Success);
1050
1051     unit = string_to_unit(Unit);
1052     if(unit == RU_UNKNOWN)
1053         return E_INVALIDARG;
1054
1055     switch(unit) {
1056     case RU_WORD: {
1057         dompos_t end_pos, start_pos, new_pos;
1058
1059         *Success = VARIANT_FALSE;
1060
1061         get_cur_pos(This, TRUE, &start_pos);
1062         get_cur_pos(This, FALSE, &end_pos);
1063         if(find_next_space(&end_pos, TRUE, &new_pos)) {
1064             set_range_pos(This, FALSE, &new_pos);
1065             *Success = VARIANT_TRUE;
1066         }
1067         dompos_release(&new_pos);
1068
1069         if(find_prev_space(This, &start_pos, TRUE, &new_pos)) {
1070             set_range_pos(This, TRUE, &new_pos);
1071             *Success = VARIANT_TRUE;
1072         }
1073
1074         dompos_release(&new_pos);
1075         dompos_release(&end_pos);
1076
1077         break;
1078     }
1079     default:
1080         FIXME("Unimplemented unit %s\n", debugstr_w(Unit));
1081     }
1082
1083     return S_OK;
1084 }
1085
1086 static HRESULT WINAPI HTMLTxtRange_move(IHTMLTxtRange *iface, BSTR Unit,
1087         long Count, long *ActualCount)
1088 {
1089     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1090     range_unit_t unit;
1091
1092     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1093
1094     unit = string_to_unit(Unit);
1095     if(unit == RU_UNKNOWN)
1096         return E_INVALIDARG;
1097
1098     IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1099
1100     if(!Count) {
1101         *ActualCount = 0;
1102         return S_OK;
1103     }
1104
1105     switch(unit) {
1106     case RU_CHAR: {
1107         dompos_t cur_pos, new_pos;
1108
1109         get_cur_pos(This, TRUE, &cur_pos);
1110
1111         if(Count > 0) {
1112             *ActualCount = move_next_chars(Count, &cur_pos, TRUE, &new_pos);
1113             set_range_pos(This, FALSE, &new_pos);
1114             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1115             dompos_release(&new_pos);
1116         }else {
1117             *ActualCount = -move_prev_chars(This, -Count, &cur_pos, FALSE, &new_pos);
1118             set_range_pos(This, TRUE, &new_pos);
1119             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1120             dompos_release(&new_pos);
1121         }
1122
1123         dompos_release(&cur_pos);
1124         break;
1125     }
1126
1127     case RU_WORD: {
1128         dompos_t cur_pos, new_pos;
1129
1130         get_cur_pos(This, TRUE, &cur_pos);
1131
1132         if(Count > 0) {
1133             *ActualCount = move_next_words(Count, &cur_pos, &new_pos);
1134             set_range_pos(This, FALSE, &new_pos);
1135             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), FALSE);
1136             dompos_release(&new_pos);
1137         }else {
1138             *ActualCount = -move_prev_words(This, -Count, &cur_pos, &new_pos);
1139             set_range_pos(This, TRUE, &new_pos);
1140             IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1141             dompos_release(&new_pos);
1142         }
1143
1144         dompos_release(&cur_pos);
1145         break;
1146     }
1147
1148     default:
1149         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1150     }
1151
1152     TRACE("ret %ld\n", *ActualCount);
1153     return S_OK;
1154 }
1155
1156 static HRESULT WINAPI HTMLTxtRange_moveStart(IHTMLTxtRange *iface, BSTR Unit,
1157         long Count, long *ActualCount)
1158 {
1159     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1160     FIXME("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1161     return E_NOTIMPL;
1162 }
1163
1164 static HRESULT WINAPI HTMLTxtRange_moveEnd(IHTMLTxtRange *iface, BSTR Unit,
1165         long Count, long *ActualCount)
1166 {
1167     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1168     range_unit_t unit;
1169
1170     TRACE("(%p)->(%s %ld %p)\n", This, debugstr_w(Unit), Count, ActualCount);
1171
1172     unit = string_to_unit(Unit);
1173     if(unit == RU_UNKNOWN)
1174         return E_INVALIDARG;
1175
1176     if(!Count) {
1177         *ActualCount = 0;
1178         return S_OK;
1179     }
1180
1181     switch(unit) {
1182     case RU_CHAR: {
1183         dompos_t cur_pos, new_pos;
1184         PRBool collapsed;
1185
1186         get_cur_pos(This, FALSE, &cur_pos);
1187         nsIDOMRange_GetCollapsed(This->nsrange, &collapsed);
1188
1189         if(Count > 0) {
1190             *ActualCount = move_next_chars(Count, &cur_pos, collapsed, &new_pos);
1191             set_range_pos(This, FALSE, &new_pos);
1192         }else {
1193             *ActualCount = -move_prev_chars(This, -Count, &cur_pos, TRUE,  &new_pos);
1194             if(*ActualCount == Count)
1195                 set_range_pos(This, FALSE, &new_pos);
1196             else
1197                 IHTMLTxtRange_collapse(HTMLTXTRANGE(This), TRUE);
1198         }
1199
1200         dompos_release(&cur_pos);
1201         dompos_release(&new_pos);
1202         break;
1203     }
1204
1205     default:
1206         FIXME("unimplemented unit %s\n", debugstr_w(Unit));
1207     }
1208
1209     return S_OK;
1210 }
1211
1212 static HRESULT WINAPI HTMLTxtRange_select(IHTMLTxtRange *iface)
1213 {
1214     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1215
1216     TRACE("(%p)\n", This);
1217
1218     if(This->doc->nscontainer) {
1219         nsIDOMWindow *dom_window = NULL;
1220         nsISelection *nsselection;
1221
1222         nsIWebBrowser_GetContentDOMWindow(This->doc->nscontainer->webbrowser, &dom_window);
1223         nsIDOMWindow_GetSelection(dom_window, &nsselection);
1224         nsIDOMWindow_Release(dom_window);
1225
1226         nsISelection_RemoveAllRanges(nsselection);
1227         nsISelection_AddRange(nsselection, This->nsrange);
1228
1229         nsISelection_Release(nsselection);
1230     }
1231
1232     return S_OK;
1233 }
1234
1235 static HRESULT WINAPI HTMLTxtRange_pasteHTML(IHTMLTxtRange *iface, BSTR html)
1236 {
1237     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1238     FIXME("(%p)->(%s)\n", This, debugstr_w(html));
1239     return E_NOTIMPL;
1240 }
1241
1242 static HRESULT WINAPI HTMLTxtRange_moveToElementText(IHTMLTxtRange *iface, IHTMLElement *element)
1243 {
1244     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1245     FIXME("(%p)->(%p)\n", This, element);
1246     return E_NOTIMPL;
1247 }
1248
1249 static HRESULT WINAPI HTMLTxtRange_setEndPoint(IHTMLTxtRange *iface, BSTR how,
1250         IHTMLTxtRange *SourceRange)
1251 {
1252     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1253     FIXME("(%p)->(%s %p)\n", This, debugstr_w(how), SourceRange);
1254     return E_NOTIMPL;
1255 }
1256
1257 static HRESULT WINAPI HTMLTxtRange_compareEndPoints(IHTMLTxtRange *iface, BSTR how,
1258         IHTMLTxtRange *SourceRange, long *ret)
1259 {
1260     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1261     HTMLTxtRange *src_range;
1262     PRInt16 nsret = 0;
1263     int nscmpt;
1264     nsresult nsres;
1265
1266     TRACE("(%p)->(%s %p %p)\n", This, debugstr_w(how), SourceRange, ret);
1267
1268     nscmpt = string_to_nscmptype(how);
1269     if(nscmpt == -1)
1270         return E_INVALIDARG;
1271
1272     src_range = get_range_object(This->doc, SourceRange);
1273     if(!src_range)
1274         return E_FAIL;
1275
1276     nsres = nsIDOMRange_CompareBoundaryPoints(This->nsrange, nscmpt, src_range->nsrange, &nsret);
1277     if(NS_FAILED(nsres))
1278         ERR("CompareBoundaryPoints failed: %08x\n", nsres);
1279
1280     *ret = nsret;
1281     return S_OK;
1282 }
1283
1284 static HRESULT WINAPI HTMLTxtRange_findText(IHTMLTxtRange *iface, BSTR String,
1285         long count, long Flags, VARIANT_BOOL *Success)
1286 {
1287     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1288     FIXME("(%p)->(%s %ld %08lx %p)\n", This, debugstr_w(String), count, Flags, Success);
1289     return E_NOTIMPL;
1290 }
1291
1292 static HRESULT WINAPI HTMLTxtRange_moveToPoint(IHTMLTxtRange *iface, long x, long y)
1293 {
1294     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1295     FIXME("(%p)->(%ld %ld)\n", This, x, y);
1296     return E_NOTIMPL;
1297 }
1298
1299 static HRESULT WINAPI HTMLTxtRange_getBookmark(IHTMLTxtRange *iface, BSTR *Bookmark)
1300 {
1301     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1302     FIXME("(%p)->(%p)\n", This, Bookmark);
1303     return E_NOTIMPL;
1304 }
1305
1306 static HRESULT WINAPI HTMLTxtRange_moveToBookmark(IHTMLTxtRange *iface, BSTR Bookmark,
1307         VARIANT_BOOL *Success)
1308 {
1309     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1310     FIXME("(%p)->(%s %p)\n", This, debugstr_w(Bookmark), Success);
1311     return E_NOTIMPL;
1312 }
1313
1314 static HRESULT WINAPI HTMLTxtRange_queryCommandSupported(IHTMLTxtRange *iface, BSTR cmdID,
1315         VARIANT_BOOL *pfRet)
1316 {
1317     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1318     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1319     return E_NOTIMPL;
1320 }
1321
1322 static HRESULT WINAPI HTMLTxtRange_queryCommandEnabled(IHTMLTxtRange *iface, BSTR cmdID,
1323         VARIANT_BOOL *pfRet)
1324 {
1325     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1326     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1327     return E_NOTIMPL;
1328 }
1329
1330 static HRESULT WINAPI HTMLTxtRange_queryCommandState(IHTMLTxtRange *iface, BSTR cmdID,
1331         VARIANT_BOOL *pfRet)
1332 {
1333     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1334     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1335     return E_NOTIMPL;
1336 }
1337
1338 static HRESULT WINAPI HTMLTxtRange_queryCommandIndeterm(IHTMLTxtRange *iface, BSTR cmdID,
1339         VARIANT_BOOL *pfRet)
1340 {
1341     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1342     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1343     return E_NOTIMPL;
1344 }
1345
1346 static HRESULT WINAPI HTMLTxtRange_queryCommandText(IHTMLTxtRange *iface, BSTR cmdID,
1347         BSTR *pcmdText)
1348 {
1349     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1350     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdText);
1351     return E_NOTIMPL;
1352 }
1353
1354 static HRESULT WINAPI HTMLTxtRange_queryCommandValue(IHTMLTxtRange *iface, BSTR cmdID,
1355         VARIANT *pcmdValue)
1356 {
1357     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1358     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pcmdValue);
1359     return E_NOTIMPL;
1360 }
1361
1362 static HRESULT WINAPI HTMLTxtRange_execCommand(IHTMLTxtRange *iface, BSTR cmdID,
1363         VARIANT_BOOL showUI, VARIANT value, VARIANT_BOOL *pfRet)
1364 {
1365     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1366     FIXME("(%p)->(%s %x v %p)\n", This, debugstr_w(cmdID), showUI, pfRet);
1367     return E_NOTIMPL;
1368 }
1369
1370 static HRESULT WINAPI HTMLTxtRange_execCommandShowHelp(IHTMLTxtRange *iface, BSTR cmdID,
1371         VARIANT_BOOL *pfRet)
1372 {
1373     HTMLTxtRange *This = HTMLTXTRANGE_THIS(iface);
1374     FIXME("(%p)->(%s %p)\n", This, debugstr_w(cmdID), pfRet);
1375     return E_NOTIMPL;
1376 }
1377
1378 #undef HTMLTXTRANGE_THIS
1379
1380 static const IHTMLTxtRangeVtbl HTMLTxtRangeVtbl = {
1381     HTMLTxtRange_QueryInterface,
1382     HTMLTxtRange_AddRef,
1383     HTMLTxtRange_Release,
1384     HTMLTxtRange_GetTypeInfoCount,
1385     HTMLTxtRange_GetTypeInfo,
1386     HTMLTxtRange_GetIDsOfNames,
1387     HTMLTxtRange_Invoke,
1388     HTMLTxtRange_get_htmlText,
1389     HTMLTxtRange_put_text,
1390     HTMLTxtRange_get_text,
1391     HTMLTxtRange_parentElement,
1392     HTMLTxtRange_duplicate,
1393     HTMLTxtRange_inRange,
1394     HTMLTxtRange_isEqual,
1395     HTMLTxtRange_scrollIntoView,
1396     HTMLTxtRange_collapse,
1397     HTMLTxtRange_expand,
1398     HTMLTxtRange_move,
1399     HTMLTxtRange_moveStart,
1400     HTMLTxtRange_moveEnd,
1401     HTMLTxtRange_select,
1402     HTMLTxtRange_pasteHTML,
1403     HTMLTxtRange_moveToElementText,
1404     HTMLTxtRange_setEndPoint,
1405     HTMLTxtRange_compareEndPoints,
1406     HTMLTxtRange_findText,
1407     HTMLTxtRange_moveToPoint,
1408     HTMLTxtRange_getBookmark,
1409     HTMLTxtRange_moveToBookmark,
1410     HTMLTxtRange_queryCommandSupported,
1411     HTMLTxtRange_queryCommandEnabled,
1412     HTMLTxtRange_queryCommandState,
1413     HTMLTxtRange_queryCommandIndeterm,
1414     HTMLTxtRange_queryCommandText,
1415     HTMLTxtRange_queryCommandValue,
1416     HTMLTxtRange_execCommand,
1417     HTMLTxtRange_execCommandShowHelp
1418 };
1419
1420 IHTMLTxtRange *HTMLTxtRange_Create(HTMLDocument *doc, nsIDOMRange *nsrange)
1421 {
1422     HTMLTxtRange *ret = mshtml_alloc(sizeof(HTMLTxtRange));
1423
1424     ret->lpHTMLTxtRangeVtbl = &HTMLTxtRangeVtbl;
1425     ret->ref = 1;
1426
1427     if(nsrange)
1428         nsIDOMRange_AddRef(nsrange);
1429     ret->nsrange = nsrange;
1430
1431     ret->doc = doc;
1432     list_add_head(&doc->range_list, &ret->entry);
1433
1434     return HTMLTXTRANGE(ret);
1435 }
1436
1437 void detach_ranges(HTMLDocument *This)
1438 {
1439     HTMLTxtRange *iter;
1440
1441     LIST_FOR_EACH_ENTRY(iter, &This->range_list, HTMLTxtRange, entry) {
1442         iter->doc = NULL;
1443     }
1444 }