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