mshtml: Implement IHTMLStyle get/put wordSpacing.
[wine] / dlls / mshtml / mutation.c
1 /*
2  * Copyright 2008 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
23 #define COBJMACROS
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winuser.h"
28 #include "winreg.h"
29 #include "ole2.h"
30
31 #include "mshtml_private.h"
32 #include "htmlevent.h"
33
34 #include "wine/debug.h"
35
36 WINE_DEFAULT_DEBUG_CHANNEL(mshtml);
37
38 enum {
39     MUTATION_COMMENT,
40     MUTATION_SCRIPT
41 };
42
43 void set_mutation_observer(NSContainer *nscontainer, nsIDOMHTMLDocument *nshtmldoc)
44 {
45     nsIDOMNSDocument *nsdoc;
46     nsresult nsres;
47
48     nsres = nsIDOMHTMLDocument_QueryInterface(nshtmldoc, &IID_nsIDOMNSDocument, (void**)&nsdoc);
49     if(NS_FAILED(nsres)) {
50         ERR("Could not get nsIDOMNSDocument: %08x\n", nsres);
51         return;
52     }
53
54     nsIDOMNSDocument_WineAddObserver(nsdoc, NSDOCOBS(nscontainer));
55     nsIDOMNSDocument_Release(nsdoc);
56 }
57
58 void remove_mutation_observer(NSContainer *nscontainer, nsIDOMHTMLDocument *nshtmldoc)
59 {
60     nsIDOMNSDocument *nsdoc;
61     nsresult nsres;
62
63     nsres = nsIDOMHTMLDocument_QueryInterface(nshtmldoc, &IID_nsIDOMNSDocument, (void**)&nsdoc);
64     if(NS_FAILED(nsres)) {
65         ERR("Could not get nsIDOMNSDocument: %08x\n", nsres);
66         return;
67     }
68
69     nsIDOMNSDocument_WineRemoveObserver(nsdoc, NSDOCOBS(nscontainer));
70     nsIDOMNSDocument_Release(nsdoc);
71 }
72
73 #define IE_MAJOR_VERSION 7
74 #define IE_MINOR_VERSION 0
75
76 static BOOL handle_insert_comment(HTMLDocument *doc, const PRUnichar *comment)
77 {
78     DWORD len;
79     int majorv = 0, minorv = 0;
80     const PRUnichar *ptr, *end;
81     nsAString nsstr;
82     PRUnichar *buf;
83     nsresult nsres;
84
85     enum {
86         CMP_EQ,
87         CMP_LT,
88         CMP_LTE,
89         CMP_GT,
90         CMP_GTE
91     } cmpt = CMP_EQ;
92
93     static const PRUnichar endifW[] = {'<','!','[','e','n','d','i','f',']'};
94
95     if(comment[0] != '[' || comment[1] != 'i' || comment[2] != 'f')
96         return FALSE;
97
98     ptr = comment+3;
99     while(isspaceW(*ptr))
100         ptr++;
101
102     if(ptr[0] == 'l' && ptr[1] == 't') {
103         ptr += 2;
104         if(*ptr == 'e') {
105             cmpt = CMP_LTE;
106             ptr++;
107         }else {
108             cmpt = CMP_LT;
109         }
110     }else if(ptr[0] == 'g' && ptr[1] == 't') {
111         ptr += 2;
112         if(*ptr == 'e') {
113             cmpt = CMP_GTE;
114             ptr++;
115         }else {
116             cmpt = CMP_GT;
117         }
118     }
119
120     if(!isspaceW(*ptr++))
121         return FALSE;
122     while(isspaceW(*ptr))
123         ptr++;
124
125     if(ptr[0] != 'I' || ptr[1] != 'E')
126         return FALSE;
127
128     ptr +=2;
129     if(!isspaceW(*ptr++))
130         return FALSE;
131     while(isspaceW(*ptr))
132         ptr++;
133
134     if(!isdigitW(*ptr))
135         return FALSE;
136     while(isdigitW(*ptr))
137         majorv = majorv*10 + (*ptr++ - '0');
138
139     if(*ptr == '.') {
140         ptr++;
141         if(!isdigitW(*ptr))
142             return FALSE;
143         while(isdigitW(*ptr))
144             minorv = minorv*10 + (*ptr++ - '0');
145     }
146
147     while(isspaceW(*ptr))
148         ptr++;
149     if(ptr[0] != ']' || ptr[1] != '>')
150         return FALSE;
151     ptr += 2;
152
153     len = strlenW(ptr);
154     if(len < sizeof(endifW)/sizeof(WCHAR))
155         return FALSE;
156
157     end = ptr + len-sizeof(endifW)/sizeof(WCHAR);
158     if(memcmp(end, endifW, sizeof(endifW)))
159         return FALSE;
160
161     switch(cmpt) {
162     case CMP_EQ:
163         if(majorv == IE_MAJOR_VERSION && minorv == IE_MINOR_VERSION)
164             break;
165         return FALSE;
166     case CMP_LT:
167         if(majorv > IE_MAJOR_VERSION)
168             break;
169         if(majorv == IE_MAJOR_VERSION && minorv > IE_MINOR_VERSION)
170             break;
171         return FALSE;
172     case CMP_LTE:
173         if(majorv > IE_MAJOR_VERSION)
174             break;
175         if(majorv == IE_MAJOR_VERSION && minorv >= IE_MINOR_VERSION)
176             break;
177         return FALSE;
178     case CMP_GT:
179         if(majorv < IE_MAJOR_VERSION)
180             break;
181         if(majorv == IE_MAJOR_VERSION && minorv < IE_MINOR_VERSION)
182             break;
183         return FALSE;
184     case CMP_GTE:
185         if(majorv < IE_MAJOR_VERSION)
186             break;
187         if(majorv == IE_MAJOR_VERSION && minorv <= IE_MINOR_VERSION)
188             break;
189         return FALSE;
190     }
191
192     buf = heap_alloc((end-ptr+1)*sizeof(WCHAR));
193     if(!buf)
194         return FALSE;
195
196     memcpy(buf, ptr, (end-ptr)*sizeof(WCHAR));
197     buf[end-ptr] = 0;
198     nsAString_Init(&nsstr, buf);
199     heap_free(buf);
200
201     /* FIXME: Find better way to insert HTML to document. */
202     nsres = nsIDOMHTMLDocument_Write(doc->nsdoc, &nsstr);
203     nsAString_Finish(&nsstr);
204     if(NS_FAILED(nsres)) {
205         ERR("Write failed: %08x\n", nsres);
206         return FALSE;
207     }
208
209     return TRUE;
210 }
211
212 static void add_script_runner(NSContainer *This)
213 {
214     nsIDOMNSDocument *nsdoc;
215     nsresult nsres;
216
217     nsres = nsIDOMHTMLDocument_QueryInterface(This->doc->nsdoc, &IID_nsIDOMNSDocument, (void**)&nsdoc);
218     if(NS_FAILED(nsres)) {
219         ERR("Could not get nsIDOMNSDocument: %08x\n", nsres);
220         return;
221     }
222
223     nsIDOMNSDocument_WineAddScriptRunner(nsdoc, NSRUNNABLE(This));
224     nsIDOMNSDocument_Release(nsdoc);
225 }
226
227 #define NSRUNNABLE_THIS(iface) DEFINE_THIS(NSContainer, Runnable, iface)
228
229 static nsresult NSAPI nsRunnable_QueryInterface(nsIRunnable *iface,
230         nsIIDRef riid, nsQIResult result)
231 {
232     NSContainer *This = NSRUNNABLE_THIS(iface);
233
234     if(IsEqualGUID(riid, &IID_nsISupports)) {
235         TRACE("(%p)->(IID_nsISupports %p)\n", This, result);
236         *result = NSRUNNABLE(This);
237     }else if(IsEqualGUID(riid, &IID_nsIRunnable)) {
238         TRACE("(%p)->(IID_nsIRunnable %p)\n", This, result);
239         *result = NSRUNNABLE(This);
240     }else {
241         *result = NULL;
242         WARN("(%p)->(%s %p)\n", This, debugstr_guid(riid), result);
243         return NS_NOINTERFACE;
244     }
245
246     nsISupports_AddRef((nsISupports*)*result);
247     return NS_OK;
248 }
249
250 static nsrefcnt NSAPI nsRunnable_AddRef(nsIRunnable *iface)
251 {
252     NSContainer *This = NSRUNNABLE_THIS(iface);
253     return nsIWebBrowserChrome_AddRef(NSWBCHROME(This));
254 }
255
256 static nsrefcnt NSAPI nsRunnable_Release(nsIRunnable *iface)
257 {
258     NSContainer *This = NSRUNNABLE_THIS(iface);
259     return nsIWebBrowserChrome_Release(NSWBCHROME(This));
260 }
261
262 static void pop_mutation_queue(NSContainer *nscontainer)
263 {
264     mutation_queue_t *tmp = nscontainer->mutation_queue;
265
266     if(!tmp)
267         return;
268
269     nscontainer->mutation_queue = tmp->next;
270     if(!tmp->next)
271         nscontainer->mutation_queue_tail = NULL;
272
273     nsISupports_Release(tmp->nsiface);
274     heap_free(tmp);
275 }
276
277 static nsresult NSAPI nsRunnable_Run(nsIRunnable *iface)
278 {
279     NSContainer *This = NSRUNNABLE_THIS(iface);
280     nsresult nsres;
281
282     TRACE("(%p)\n", This);
283
284     while(This->mutation_queue) {
285         switch(This->mutation_queue->type) {
286         case MUTATION_COMMENT: {
287             nsIDOMComment *nscomment;
288             nsAString comment_str;
289             BOOL remove_comment = FALSE;
290
291             nsres = nsISupports_QueryInterface(This->mutation_queue->nsiface, &IID_nsIDOMComment, (void**)&nscomment);
292             if(NS_FAILED(nsres)) {
293                 ERR("Could not get nsIDOMComment iface:%08x\n", nsres);
294                 return NS_OK;
295             }
296
297             nsAString_Init(&comment_str, NULL);
298             nsres = nsIDOMComment_GetData(nscomment, &comment_str);
299             if(NS_SUCCEEDED(nsres)) {
300                 const PRUnichar *comment;
301
302                 nsAString_GetData(&comment_str, &comment);
303                 remove_comment = handle_insert_comment(This->doc, comment);
304             }
305
306             nsAString_Finish(&comment_str);
307
308             if(remove_comment) {
309                 nsIDOMNode *nsparent, *tmp;
310                 nsAString magic_str;
311
312                 static const PRUnichar remove_comment_magicW[] =
313                     {'#','!','w','i','n','e', 'r','e','m','o','v','e','!','#',0};
314
315                 nsAString_Init(&magic_str, remove_comment_magicW);
316                 nsres = nsIDOMComment_SetData(nscomment, &magic_str);
317                 nsAString_Finish(&magic_str);
318                 if(NS_FAILED(nsres))
319                     ERR("SetData failed: %08x\n", nsres);
320
321                 nsIDOMComment_GetParentNode(nscomment, &nsparent);
322                 if(nsparent) {
323                     nsIDOMNode_RemoveChild(nsparent, (nsIDOMNode*)nscomment, &tmp);
324                     nsIDOMNode_Release(nsparent);
325                     nsIDOMNode_Release(tmp);
326                 }
327             }
328
329             nsIDOMComment_Release(nscomment);
330             break;
331         }
332
333         case MUTATION_SCRIPT: {
334             nsIDOMHTMLScriptElement *nsscript;
335
336             nsres = nsISupports_QueryInterface(This->mutation_queue->nsiface, &IID_nsIDOMHTMLScriptElement,
337                                                (void**)&nsscript);
338             if(NS_FAILED(nsres)) {
339                 ERR("Could not get nsIDOMHTMLScriptElement: %08x\n", nsres);
340                 break;
341             }
342
343             doc_insert_script(This->doc, nsscript);
344             nsIDOMHTMLScriptElement_Release(nsscript);
345             break;
346         }
347
348         default:
349             ERR("invalid type %d\n", This->mutation_queue->type);
350         }
351
352         pop_mutation_queue(This);
353     }
354
355     return S_OK;
356 }
357
358 #undef NSRUNNABLE_THIS
359
360 static const nsIRunnableVtbl nsRunnableVtbl = {
361     nsRunnable_QueryInterface,
362     nsRunnable_AddRef,
363     nsRunnable_Release,
364     nsRunnable_Run
365 };
366
367 #define NSDOCOBS_THIS(iface) DEFINE_THIS(NSContainer, DocumentObserver, iface)
368
369 static nsresult NSAPI nsDocumentObserver_QueryInterface(nsIDocumentObserver *iface,
370         nsIIDRef riid, nsQIResult result)
371 {
372     NSContainer *This = NSDOCOBS_THIS(iface);
373
374     if(IsEqualGUID(&IID_nsISupports, riid)) {
375         TRACE("(%p)->(IID_nsISupports, %p)\n", This, result);
376         *result = NSDOCOBS(This);
377     }else if(IsEqualGUID(&IID_nsIMutationObserver, riid)) {
378         TRACE("(%p)->(IID_nsIMutationObserver %p)\n", This, result);
379         *result = NSDOCOBS(This);
380     }else if(IsEqualGUID(&IID_nsIDocumentObserver, riid)) {
381         TRACE("(%p)->(IID_nsIDocumentObserver %p)\n", This, result);
382         *result = NSDOCOBS(This);
383     }else {
384         *result = NULL;
385         TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), result);
386         return NS_NOINTERFACE;
387     }
388
389     nsIWebBrowserChrome_AddRef(NSWBCHROME(This));
390     return NS_OK;
391 }
392
393 static nsrefcnt NSAPI nsDocumentObserver_AddRef(nsIDocumentObserver *iface)
394 {
395     NSContainer *This = NSDOCOBS_THIS(iface);
396     return nsIWebBrowserChrome_AddRef(NSWBCHROME(This));
397 }
398
399 static nsrefcnt NSAPI nsDocumentObserver_Release(nsIDocumentObserver *iface)
400 {
401     NSContainer *This = NSDOCOBS_THIS(iface);
402     return nsIWebBrowserChrome_Release(NSWBCHROME(This));
403 }
404
405 static void NSAPI nsDocumentObserver_CharacterDataWillChange(nsIDocumentObserver *iface,
406         nsIDocument *aDocument, nsIContent *aContent, void /*CharacterDataChangeInfo*/ *aInfo)
407 {
408 }
409
410 static void NSAPI nsDocumentObserver_CharacterDataChanged(nsIDocumentObserver *iface,
411         nsIDocument *aDocument, nsIContent *aContent, void /*CharacterDataChangeInfo*/ *aInfo)
412 {
413 }
414
415 static void NSAPI nsDocumentObserver_AttributeWillChange(nsIDocumentObserver *iface, nsIDocument *aDocument,
416         nsIContent *aContent, PRInt32 aNameSpaceID, nsIAtom *aAttribute, PRInt32 aModType)
417 {
418 }
419
420 static void NSAPI nsDocumentObserver_AttributeChanged(nsIDocumentObserver *iface, nsIDocument *aDocument,
421         nsIContent *aContent, PRInt32 aNameSpaceID, nsIAtom *aAttribute, PRInt32 aModType, PRUint32 aStateMask)
422 {
423 }
424
425 static void NSAPI nsDocumentObserver_ContentAppended(nsIDocumentObserver *iface, nsIDocument *aDocument,
426         nsIContent *aContainer, PRInt32 aNewIndexInContainer)
427 {
428 }
429
430 static void NSAPI nsDocumentObserver_ContentInserted(nsIDocumentObserver *iface, nsIDocument *aDocument,
431         nsIContent *aContainer, nsIContent *aChild, PRInt32 aIndexInContainer)
432 {
433 }
434
435 static void NSAPI nsDocumentObserver_ContentRemoved(nsIDocumentObserver *iface, nsIDocument *aDocument,
436         nsIContent *aContainer, nsIContent *aChild, PRInt32 aIndexInContainer)
437 {
438 }
439
440 static void NSAPI nsDocumentObserver_NodeWillBeDestroyed(nsIDocumentObserver *iface, const nsINode *aNode)
441 {
442 }
443
444 static void NSAPI nsDocumentObserver_ParentChainChanged(nsIDocumentObserver *iface, nsIContent *aContent)
445 {
446 }
447
448 static void NSAPI nsDocumentObserver_BeginUpdate(nsIDocumentObserver *iface, nsIDocument *aDocument,
449         nsUpdateType aUpdateType)
450 {
451 }
452
453 static void NSAPI nsDocumentObserver_EndUpdate(nsIDocumentObserver *iface, nsIDocument *aDocument,
454         nsUpdateType aUpdateType)
455 {
456 }
457
458 static void NSAPI nsDocumentObserver_BeginLoad(nsIDocumentObserver *iface, nsIDocument *aDocument)
459 {
460 }
461
462 static void NSAPI nsDocumentObserver_EndLoad(nsIDocumentObserver *iface, nsIDocument *aDocument)
463 {
464     NSContainer *This = NSDOCOBS_THIS(iface);
465     task_t *task;
466
467     TRACE("\n");
468
469     task = heap_alloc(sizeof(task_t));
470
471     task->doc = This->doc;
472     task->task_id = TASK_PARSECOMPLETE;
473     task->next = NULL;
474
475     /*
476      * This should be done in the worker thread that parses HTML,
477      * but we don't have such thread (Gecko parses HTML for us).
478      */
479     push_task(task);
480 }
481
482 static void NSAPI nsDocumentObserver_ContentStatesChanged(nsIDocumentObserver *iface, nsIDocument *aDocument,
483         nsIContent *aContent1, nsIContent *aContent2, PRInt32 aStateMask)
484 {
485 }
486
487 static void NSAPI nsDocumentObserver_StyleSheetAdded(nsIDocumentObserver *iface, nsIDocument *aDocument,
488         nsIStyleSheet *aStyleSheet, PRBool aDocumentSheet)
489 {
490 }
491
492 static void NSAPI nsDocumentObserver_StyleSheetRemoved(nsIDocumentObserver *iface, nsIDocument *aDocument,
493         nsIStyleSheet *aStyleSheet, PRBool aDocumentSheet)
494 {
495 }
496
497 static void NSAPI nsDocumentObserver_StyleSheetApplicableStateChanged(nsIDocumentObserver *iface,
498         nsIDocument *aDocument, nsIStyleSheet *aStyleSheet, PRBool aApplicable)
499 {
500 }
501
502 static void NSAPI nsDocumentObserver_StyleRuleChanged(nsIDocumentObserver *iface, nsIDocument *aDocument,
503         nsIStyleSheet *aStyleSheet, nsIStyleRule *aOldStyleRule, nsIStyleSheet *aNewStyleRule)
504 {
505 }
506
507 static void NSAPI nsDocumentObserver_StyleRuleAdded(nsIDocumentObserver *iface, nsIDocument *aDocument,
508         nsIStyleSheet *aStyleSheet, nsIStyleRule *aStyleRule)
509 {
510 }
511
512 static void NSAPI nsDocumentObserver_StyleRuleRemoved(nsIDocumentObserver *iface, nsIDocument *aDocument,
513         nsIStyleSheet *aStyleSheet, nsIStyleRule *aStyleRule)
514 {
515 }
516
517 static void push_mutation_queue(NSContainer *nscontainer, DWORD type, nsISupports *nsiface)
518 {
519     mutation_queue_t *elem;
520
521     elem = heap_alloc(sizeof(mutation_queue_t));
522     if(!elem)
523         return;
524
525     elem->next = NULL;
526     elem->type = type;
527     elem->nsiface = nsiface;
528     nsISupports_AddRef(nsiface);
529
530     if(nscontainer->mutation_queue_tail)
531         nscontainer->mutation_queue_tail = nscontainer->mutation_queue_tail->next = elem;
532     else
533         nscontainer->mutation_queue = nscontainer->mutation_queue_tail = elem;
534 }
535
536 static void NSAPI nsDocumentObserver_BindToDocument(nsIDocumentObserver *iface, nsIDocument *aDocument,
537         nsIContent *aContent)
538 {
539     NSContainer *This = NSDOCOBS_THIS(iface);
540     nsIDOMComment *nscomment;
541     nsIDOMElement *nselem;
542     nsresult nsres;
543
544     TRACE("(%p)\n", This);
545
546     nsres = nsISupports_QueryInterface(aContent, &IID_nsIDOMElement, (void**)&nselem);
547     if(NS_SUCCEEDED(nsres)) {
548         check_event_attr(This->doc, nselem);
549         nsIDOMElement_Release(nselem);
550     }
551
552     nsres = nsISupports_QueryInterface(aContent, &IID_nsIDOMComment, (void**)&nscomment);
553     if(NS_SUCCEEDED(nsres)) {
554         TRACE("comment node\n");
555
556         push_mutation_queue(This, MUTATION_COMMENT, (nsISupports*)nscomment);
557         nsIDOMComment_Release(nscomment);
558         add_script_runner(This);
559     }
560 }
561
562 static void NSAPI nsDocumentObserver_DoneAddingChildren(nsIDocumentObserver *iface, nsIContent *aContent,
563         PRBool aHaveNotified)
564 {
565     NSContainer *This = NSDOCOBS_THIS(iface);
566     nsIDOMHTMLScriptElement *nsscript;
567     nsresult nsres;
568
569     TRACE("(%p)->(%p %x)\n", This, aContent, aHaveNotified);
570
571     nsres = nsISupports_QueryInterface(aContent, &IID_nsIDOMHTMLScriptElement, (void**)&nsscript);
572     if(NS_SUCCEEDED(nsres)) {
573         push_mutation_queue(This, MUTATION_SCRIPT, (nsISupports*)nsscript);
574         nsIDOMHTMLScriptElement_Release(nsscript);
575         add_script_runner(This);
576     }
577 }
578
579 #undef NSMUTATIONOBS_THIS
580
581 static const nsIDocumentObserverVtbl nsDocumentObserverVtbl = {
582     nsDocumentObserver_QueryInterface,
583     nsDocumentObserver_AddRef,
584     nsDocumentObserver_Release,
585     nsDocumentObserver_CharacterDataWillChange,
586     nsDocumentObserver_CharacterDataChanged,
587     nsDocumentObserver_AttributeWillChange,
588     nsDocumentObserver_AttributeChanged,
589     nsDocumentObserver_ContentAppended,
590     nsDocumentObserver_ContentInserted,
591     nsDocumentObserver_ContentRemoved,
592     nsDocumentObserver_NodeWillBeDestroyed,
593     nsDocumentObserver_ParentChainChanged,
594     nsDocumentObserver_BeginUpdate,
595     nsDocumentObserver_EndUpdate,
596     nsDocumentObserver_BeginLoad,
597     nsDocumentObserver_EndLoad,
598     nsDocumentObserver_ContentStatesChanged,
599     nsDocumentObserver_StyleSheetAdded,
600     nsDocumentObserver_StyleSheetRemoved,
601     nsDocumentObserver_StyleSheetApplicableStateChanged,
602     nsDocumentObserver_StyleRuleChanged,
603     nsDocumentObserver_StyleRuleAdded,
604     nsDocumentObserver_StyleRuleRemoved,
605     nsDocumentObserver_BindToDocument,
606     nsDocumentObserver_DoneAddingChildren
607 };
608
609 void init_mutation(NSContainer *nscontainer)
610 {
611     nscontainer->lpDocumentObserverVtbl  = &nsDocumentObserverVtbl;
612     nscontainer->lpRunnableVtbl          = &nsRunnableVtbl;
613 }