Fix for row 13 of the Acid2 test. Change HTML comment parsing in strict mode to...
[WebKit-https.git] / WebCore / khtml / html / htmltokenizer.cpp
1 /*
2     This file is part of the KDE libraries
3
4     Copyright (C) 1997 Martin Jones (mjones@kde.org)
5               (C) 1997 Torben Weis (weis@kde.org)
6               (C) 1998 Waldo Bastian (bastian@kde.org)
7               (C) 1999 Lars Knoll (knoll@kde.org)
8               (C) 1999 Antti Koivisto (koivisto@kde.org)
9               (C) 2001 Dirk Mueller (mueller@kde.org)
10     Copyright (C) 2004 Apple Computer, Inc.
11
12     This library is free software; you can redistribute it and/or
13     modify it under the terms of the GNU Library General Public
14     License as published by the Free Software Foundation; either
15     version 2 of the License, or (at your option) any later version.
16
17     This library is distributed in the hope that it will be useful,
18     but WITHOUT ANY WARRANTY; without even the implied warranty of
19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20     Library General Public License for more details.
21
22     You should have received a copy of the GNU Library General Public License
23     along with this library; see the file COPYING.LIB.  If not, write to
24     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25     Boston, MA 02111-1307, USA.
26 */
27 //----------------------------------------------------------------------------
28 //
29 // KDE HTML Widget - Tokenizers
30
31 //#define TOKEN_DEBUG 1
32 //#define TOKEN_DEBUG 2
33
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37
38 //#include <string.h>
39 #include "html/htmltokenizer.h"
40 #include "html/html_documentimpl.h"
41 #include "html/htmlparser.h"
42 #include "html/dtd.h"
43
44 #include "misc/loader.h"
45 #include "misc/htmlhashes.h"
46
47 #include "khtmlview.h"
48 #include "khtml_part.h"
49 #include "xml/dom_docimpl.h"
50 #include "css/csshelper.h"
51 #include "ecma/kjs_proxy.h"
52 #include <kcharsets.h>
53 #include <kglobal.h>
54 #include <ctype.h>
55 #include <assert.h>
56 #include <qvariant.h>
57 #include <kdebug.h>
58 #include <stdlib.h>
59
60 using DOM::AtomicString;
61 using DOM::AttributeImpl;
62 using DOM::DOMString;
63 using DOM::DOMStringImpl;
64 using DOM::DocumentImpl;
65 using DOM::FORBIDDEN;
66 using DOM::Node;
67 using DOM::emptyAtom;
68 using DOM::endTagRequirement;
69
70 // turn off inlining to void warning with newer gcc
71 #undef __inline
72 #define __inline
73 #include "kentities.c"
74 #undef __inline
75
76 // #define INSTRUMENT_LAYOUT_SCHEDULING 1
77
78 #define TOKENIZER_CHUNK_SIZE  4096
79
80 // FIXME: We would like this constant to be 200ms.  Yielding more aggressively results in increased
81 // responsiveness and better incremental rendering.  It slows down overall page-load on slower machines,
82 // though, so for now we set a value of 500.
83 #define TOKENIZER_TIME_DELAY  500
84
85 namespace khtml {
86
87 static const char commentStart [] = "<!--";
88 static const char scriptEnd [] = "</script";
89 static const char xmpEnd [] = "</xmp";
90 static const char styleEnd [] =  "</style";
91 static const char textareaEnd [] = "</textarea";
92 static const char titleEnd [] = "</title";
93
94 #define KHTML_ALLOC_QCHAR_VEC( N ) (QChar*) malloc( sizeof(QChar)*( N ) )
95 #define KHTML_REALLOC_QCHAR_VEC(P, N ) (QChar*) P = realloc(p, sizeof(QChar)*( N ))
96 #define KHTML_DELETE_QCHAR_VEC( P ) free((char*)( P ))
97
98 // Full support for MS Windows extensions to Latin-1.
99 // Technically these extensions should only be activated for pages
100 // marked "windows-1252" or "cp1252", but
101 // in the standard Microsoft way, these extensions infect hundreds of thousands
102 // of web pages.  Note that people with non-latin-1 Microsoft extensions
103 // are SOL.
104 //
105 // See: http://www.microsoft.com/globaldev/reference/WinCP.asp
106 //      http://www.bbsinc.com/iso8859.html
107 //      http://www.obviously.com/
108 //
109 // There may be better equivalents
110
111 #if APPLE_CHANGES
112
113 // Note that we have more Unicode characters than Qt, so we use the
114 // official mapping table from the Unicode 2.0 standard here instead of
115 // one with hacks to avoid certain Unicode characters. Also, we don't
116 // need the unrelated hacks to avoid Unicode characters that are in the
117 // original version.
118
119 // We need this for entities at least. For non-entity text, we could
120 // handle this in the text codec.
121
122 // To cover non-entity text, I think this function would need to be called
123 // in more places. There seem to be many places that don't call fixUpChar.
124
125 inline void fixUpChar(QChar& c) {
126     switch (c.unicode()) {
127         case 0x0080: c = 0x20AC; break;
128         case 0x0081: break;
129         case 0x0082: c = 0x201A; break;
130         case 0x0083: c = 0x0192; break;
131         case 0x0084: c = 0x201E; break;
132         case 0x0085: c = 0x2026; break;
133         case 0x0086: c = 0x2020; break;
134         case 0x0087: c = 0x2021; break;
135         case 0x0088: c = 0x02C6; break;
136         case 0x0089: c = 0x2030; break;
137         case 0x008A: c = 0x0160; break;
138         case 0x008B: c = 0x2039; break;
139         case 0x008C: c = 0x0152; break;
140         case 0x008D: break;
141         case 0x008E: c = 0x017D; break;
142         case 0x008F: break;
143         case 0x0090: break;
144         case 0x0091: c = 0x2018; break;
145         case 0x0092: c = 0x2019; break;
146         case 0x0093: c = 0x201C; break;
147         case 0x0094: c = 0x201D; break;
148         case 0x0095: c = 0x2022; break;
149         case 0x0096: c = 0x2013; break;
150         case 0x0097: c = 0x2014; break;
151         case 0x0098: c = 0x02DC; break;
152         case 0x0099: c = 0x2122; break;
153         case 0x009A: c = 0x0161; break;
154         case 0x009B: c = 0x203A; break;
155         case 0x009C: c = 0x0153; break;
156         case 0x009D: break;
157         case 0x009E: c = 0x017E; break;
158         case 0x009F: c = 0x0178; break;
159     }
160 }
161
162 #else // APPLE_CHANGES
163
164 #define fixUpChar(x) \
165             if (!(x).row() ) { \
166                 switch ((x).cell()) \
167                 { \
168                 /* ALL of these should be changed to Unicode SOON */ \
169                 case 0x80: (x) = 0x20ac; break; \
170                 case 0x82: (x) = ',';    break; \
171                 case 0x83: (x) = 0x0192; break; \
172                 case 0x84: (x) = '"';    break; \
173                 case 0x85: (x) = 0x2026; break; \
174                 case 0x86: (x) = 0x2020; break; \
175                 case 0x87: (x) = 0x2021; break; \
176                 case 0x88: (x) = 0x02C6; break; \
177                 case 0x89: (x) = 0x2030; break; \
178                 case 0x8A: (x) = 0x0160; break; \
179                 case 0x8b: (x) = '<';    break; \
180                 case 0x8C: (x) = 0x0152; break; \
181 \
182                 case 0x8E: (x) = 0x017D; break; \
183 \
184 \
185                 case 0x91: (x) = '\'';   break; \
186                 case 0x92: (x) = '\'';   break; \
187                 case 0x93: (x) = '"';    break; \
188                 case 0x94: (x) = '"';    break; \
189                 case 0x95: (x) = '*';    break; \
190                 case 0x96: (x) = '-';    break; \
191                 case 0x97: (x) = '-';    break; \
192                 case 0x98: (x) = '~';    break; \
193                 case 0x99: (x) = 0x2122; break; \
194                 case 0x9A: (x) = 0x0161; break; \
195                 case 0x9b: (x) = '>';    break; \
196                 case 0x9C: (x) = 0x0153; break; \
197 \
198                 case 0x9E: (x) = 0x017E; break; \
199                 case 0x9F: (x) = 0x0178; break; \
200                 /* This one should die */ \
201                 case 0xb7: (x) = '*';    break; \
202                 default: break; \
203                 } \
204             } \
205             else { \
206                 /* These should all die sooner rather than later */ \
207                 switch( (x).unicode() ) { \
208                 case 0x2013: (x) = '-'; break; \
209                 case 0x2014: (x) = '-'; break; \
210                 case 0x2018: (x) = '\''; break; \
211                 case 0x2019: (x) = '\''; break; \
212                 case 0x201c: (x) = '"'; break; \
213                 case 0x201d: (x) = '"'; break; \
214                 case 0x2022: (x) = '*'; break; \
215                 case 0x2122: (x) = 0x2122; break; \
216                 default: break; \
217                 } \
218             }
219
220 #endif // APPLE_CHANGES
221
222 inline bool tagMatch(const char *s1, const QChar *s2, uint length)
223 {
224     for (uint i = 0; i != length; ++i) {
225         char c1 = s1[i];
226         char uc1 = toupper(c1);
227         QChar c2 = s2[i];
228         if (c1 != c2 && uc1 != c2)
229             return false;
230     }
231     return true;
232 }
233
234 // ----------------------------------------------------------------------------
235
236 HTMLTokenizer::HTMLTokenizer(DOM::DocumentPtr *_doc, KHTMLView *_view, bool includesComments)
237     : inWrite(false)
238 {
239     view = _view;
240     buffer = 0;
241     scriptCode = 0;
242     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
243     charsets = KGlobal::charsets();
244     parser = new KHTMLParser(_view, _doc, includesComments);
245     m_executingScript = 0;
246     loadingExtScript = false;
247     onHold = false;
248     attrNamePresent = false;
249     timerId = 0;
250     includesCommentsInDOM = includesComments;
251     loadStopped = false;
252     
253     begin();
254 }
255
256 HTMLTokenizer::HTMLTokenizer(DOM::DocumentPtr *_doc, DOM::DocumentFragmentImpl *i, bool includesComments)
257     : inWrite(false)
258 {
259     view = 0;
260     buffer = 0;
261     scriptCode = 0;
262     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
263     charsets = KGlobal::charsets();
264     parser = new KHTMLParser(i, _doc, includesComments);
265     m_executingScript = 0;
266     loadingExtScript = false;
267     onHold = false;
268     timerId = 0;
269     includesCommentsInDOM = includesComments;
270     loadStopped = false;
271
272     begin();
273 }
274
275 void HTMLTokenizer::reset()
276 {
277     assert(m_executingScript == 0);
278     assert(onHold == false);
279
280     while (!cachedScript.isEmpty())
281         cachedScript.dequeue()->deref(this);
282
283     if ( buffer )
284         KHTML_DELETE_QCHAR_VEC(buffer);
285     buffer = dest = 0;
286     size = 0;
287
288     if ( scriptCode )
289         KHTML_DELETE_QCHAR_VEC(scriptCode);
290     scriptCode = 0;
291     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
292
293     if (timerId) {
294         killTimer(timerId);
295         timerId = 0;
296     }
297     timerId = 0;
298     allowYield = false;
299     forceSynchronous = false;
300
301     currToken.reset();
302 }
303
304 void HTMLTokenizer::begin()
305 {
306     m_executingScript = 0;
307     loadingExtScript = false;
308     onHold = false;
309     reset();
310     size = 254;
311     buffer = KHTML_ALLOC_QCHAR_VEC( 255 );
312     dest = buffer;
313     tag = NoTag;
314     pending = NonePending;
315     discard = NoneDiscard;
316     pre = false;
317     prePos = 0;
318     plaintext = false;
319     xmp = false;
320     processingInstruction = false;
321     script = false;
322     escaped = false;
323     style = false;
324     skipLF = false;
325     select = false;
326     comment = false;
327     server = false;
328     textarea = false;
329     title = false;
330     startTag = false;
331     tquote = NoQuote;
332     searchCount = 0;
333     Entity = NoEntity;
334     loadingExtScript = false;
335     scriptSrc = QString::null;
336     pendingSrc.clear();
337     currentPrependingSrc = 0;
338     noMoreData = false;
339     brokenComments = false;
340     brokenServer = false;
341     lineno = 0;
342     scriptStartLineno = 0;
343     tagStartLineno = 0;
344     forceSynchronous = false;
345 }
346
347 void HTMLTokenizer::setForceSynchronous(bool force)
348 {
349     forceSynchronous = force;
350 }
351
352 void HTMLTokenizer::processListing(TokenizerString list)
353 {
354     bool old_pre = pre;
355     // This function adds the listing 'list' as
356     // preformatted text-tokens to the token-collection
357     // thereby converting TABs.
358     if(!style) pre = true;
359     prePos = 0;
360
361     while ( !list.isEmpty() )
362     {
363         checkBuffer(3*TAB_SIZE);
364
365         if (skipLF && ( *list != '\n' ))
366         {
367             skipLF = false;
368         }
369
370         if (skipLF)
371         {
372             skipLF = false;
373             ++list;
374         }
375         else if (( *list == '\n' ) || ( *list == '\r' ))
376         {
377             if (discard == LFDiscard)
378             {
379                 // Ignore this LF
380                 discard = NoneDiscard; // We have discarded 1 LF
381             }
382             else
383             {
384                 // Process this LF
385                 if (pending)
386                     addPending();
387                 pending = LFPending;
388             }
389             /* Check for MS-DOS CRLF sequence */
390             if (*list == '\r')
391             {
392                 skipLF = true;
393             }
394             ++list;
395         }
396         else if (( *list == ' ' ) || ( *list == '\t'))
397         {
398             if (pending)
399                 addPending();
400             if (*list == ' ')
401                 pending = SpacePending;
402             else
403                 pending = TabPending;
404
405             ++list;
406         }
407         else
408         {
409             discard = NoneDiscard;
410             if (pending)
411                 addPending();
412
413             prePos++;
414             *dest++ = *list;
415             ++list;
416         }
417
418     }
419
420     if (pending)
421         addPending();
422
423     prePos = 0;
424
425     pre = old_pre;
426 }
427
428 void HTMLTokenizer::parseSpecial(TokenizerString &src)
429 {
430     assert( textarea || title || !Entity );
431     assert( !tag );
432     assert( xmp+textarea+title+style+script == 1 );
433     if (script)
434         scriptStartLineno = lineno+src.lineCount();
435
436     if ( comment ) parseComment( src );
437
438     while ( !src.isEmpty() ) {
439         checkScriptBuffer();
440         unsigned char ch = src->latin1();
441         if ( !scriptCodeResync && !brokenComments && !textarea && !xmp && !title && ch == '-' && scriptCodeSize >= 3 && !src.escaped() && scriptCode[scriptCodeSize-3] == '<' && scriptCode[scriptCodeSize-2] == '!' && scriptCode[scriptCodeSize-1] == '-' ) {
442             comment = true;
443             parseComment( src );
444             continue;
445         }
446         if ( scriptCodeResync && !tquote && ( ch == '>' ) ) {
447             ++src;
448             scriptCodeSize = scriptCodeResync-1;
449             scriptCodeResync = 0;
450             scriptCode[ scriptCodeSize ] = scriptCode[ scriptCodeSize + 1 ] = 0;
451             if ( script )
452                 scriptHandler();
453             else {
454                 processListing(TokenizerString(scriptCode, scriptCodeSize));
455                 processToken();
456                 if ( style )         { currToken.id = ID_STYLE + ID_CLOSE_TAG; }
457                 else if ( textarea ) { currToken.id = ID_TEXTAREA + ID_CLOSE_TAG; }
458                 else if ( title ) { currToken.id = ID_TITLE + ID_CLOSE_TAG; }
459                 else if ( xmp )  { currToken.id = ID_XMP + ID_CLOSE_TAG; }
460                 processToken();
461                 style = script = style = textarea = title = xmp = false;
462                 tquote = NoQuote;
463                 scriptCodeSize = scriptCodeResync = 0;
464             }
465             return;
466         }
467         // possible end of tagname, lets check.
468         if ( !scriptCodeResync && !escaped && !src.escaped() && ( ch == '>' || ch == '/' || ch <= ' ' ) && ch &&
469              scriptCodeSize >= searchStopperLen &&
470              tagMatch( searchStopper, scriptCode+scriptCodeSize-searchStopperLen, searchStopperLen )) {
471             scriptCodeResync = scriptCodeSize-searchStopperLen+1;
472             tquote = NoQuote;
473             continue;
474         }
475         if ( scriptCodeResync && !escaped ) {
476             if(ch == '\"')
477                 tquote = (tquote == NoQuote) ? DoubleQuote : ((tquote == SingleQuote) ? SingleQuote : NoQuote);
478             else if(ch == '\'')
479                 tquote = (tquote == NoQuote) ? SingleQuote : (tquote == DoubleQuote) ? DoubleQuote : NoQuote;
480             else if (tquote != NoQuote && (ch == '\r' || ch == '\n'))
481                 tquote = NoQuote;
482         }
483         escaped = ( !escaped && ch == '\\' );
484         if (!scriptCodeResync && (textarea||title) && !src.escaped() && ch == '&') {
485             QChar *scriptCodeDest = scriptCode+scriptCodeSize;
486             ++src;
487             parseEntity(src,scriptCodeDest,true);
488             scriptCodeSize = scriptCodeDest-scriptCode;
489         }
490         else {
491             scriptCode[scriptCodeSize] = *src;
492             fixUpChar(scriptCode[scriptCodeSize]);
493             ++scriptCodeSize;
494             ++src;
495         }
496     }
497 }
498
499 void HTMLTokenizer::scriptHandler()
500 {
501     // We are inside a <script>
502     bool doScriptExec = false;
503     CachedScript* cs = 0;
504     // don't load external scripts for standalone documents (for now)
505     if (!scriptSrc.isEmpty() && parser->doc()->part()) {
506         // forget what we just got; load from src url instead
507         if ( !parser->skipMode() ) {
508 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
509             if (!parser->doc()->ownerElement())
510                 printf("Requesting script at time %d\n", parser->doc()->elapsedTime());
511 #endif
512             if ( (cs = parser->doc()->docLoader()->requestScript(scriptSrc, scriptSrcCharset) ))
513                 cachedScript.enqueue(cs);
514         }
515         scriptSrc=QString::null;
516     }
517     else {
518 #ifdef TOKEN_DEBUG
519         kdDebug( 6036 ) << "---START SCRIPT---" << endl;
520         kdDebug( 6036 ) << QString(scriptCode, scriptCodeSize) << endl;
521         kdDebug( 6036 ) << "---END SCRIPT---" << endl;
522 #endif
523         // Parse scriptCode containing <script> info
524         doScriptExec = true;
525     }
526     processListing(TokenizerString(scriptCode, scriptCodeSize));
527     QString exScript( buffer, dest-buffer );
528     processToken();
529     currToken.id = ID_SCRIPT + ID_CLOSE_TAG;
530     processToken();
531
532     TokenizerString *savedPrependingSrc = currentPrependingSrc;
533     TokenizerString prependingSrc;
534     currentPrependingSrc = &prependingSrc;
535     if ( !parser->skipMode() ) {
536         if (cs) {
537              //kdDebug( 6036 ) << "cachedscript extern!" << endl;
538              //kdDebug( 6036 ) << "src: *" << QString( src.current(), src.length() ).latin1() << "*" << endl;
539              //kdDebug( 6036 ) << "pending: *" << pendingSrc.latin1() << "*" << endl;
540             if (savedPrependingSrc) {
541                 savedPrependingSrc->append(src);
542             } else {
543                 pendingSrc.prepend(src);
544             }
545             setSrc(TokenizerString());
546             scriptCodeSize = scriptCodeResync = 0;
547             cs->ref(this);
548             // will be 0 if script was already loaded and ref() executed it
549             if (!cachedScript.isEmpty())
550                 loadingExtScript = true;
551         }
552         else if (view && doScriptExec && javascript ) {
553             if (!m_executingScript)
554                 pendingSrc.prepend(src);
555             else
556                 prependingSrc = src;
557             setSrc(TokenizerString());
558             scriptCodeSize = scriptCodeResync = 0;
559             //QTime dt;
560             //dt.start();
561             scriptExecution( exScript, QString::null, scriptStartLineno );
562             //kdDebug( 6036 ) << "script execution time:" << dt.elapsed() << endl;
563         }
564     }
565
566     script = false;
567     scriptCodeSize = scriptCodeResync = 0;
568
569     if ( !m_executingScript && !loadingExtScript ) {
570         // kdDebug( 6036 ) << "adding pending Output to parsed string" << endl;
571         src.append(pendingSrc);
572         pendingSrc.clear();
573     } else if (!prependingSrc.isEmpty()) {
574         // restore first so that the write appends in the right place
575         // (does not hurt to do it again below)
576         currentPrependingSrc = savedPrependingSrc;
577
578         // we need to do this slightly modified bit of one of the write() cases
579         // because we want to prepend to pendingSrc rather than appending
580         // if there's no previous prependingSrc
581         if (loadingExtScript) {
582             if (currentPrependingSrc) {
583                 currentPrependingSrc->append(prependingSrc);
584             } else {
585                 pendingSrc.prepend(prependingSrc);
586             }
587         } else {
588             write(prependingSrc, false);
589         }
590     }
591
592     currentPrependingSrc = savedPrependingSrc;
593 }
594
595 void HTMLTokenizer::scriptExecution( const QString& str, QString scriptURL,
596                                      int baseLine)
597 {
598 #if APPLE_CHANGES
599     if (!view || !view->part())
600         return;
601 #endif
602     bool oldscript = script;
603     m_executingScript++;
604     script = false;
605     QString url;    
606     if (scriptURL.isNull())
607       url = static_cast<DocumentImpl*>(view->part()->document().handle())->URL();
608     else
609       url = scriptURL;
610
611     TokenizerString *savedPrependingSrc = currentPrependingSrc;
612     TokenizerString prependingSrc;
613     currentPrependingSrc = &prependingSrc;
614
615 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
616     if (!parser->doc()->ownerElement())
617         printf("beginning script execution at %d\n", parser->doc()->elapsedTime());
618 #endif
619
620     view->part()->executeScript(url,baseLine,Node(),str);
621
622     allowYield = true;
623
624 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
625     if (!parser->doc()->ownerElement())
626         printf("ending script execution at %d\n", parser->doc()->elapsedTime());
627 #endif
628     
629     m_executingScript--;
630     script = oldscript;
631
632     if ( !m_executingScript && !loadingExtScript ) {
633         // kdDebug( 6036 ) << "adding pending Output to parsed string" << endl;
634         src.append(pendingSrc);
635         pendingSrc.clear();
636     } else if (!prependingSrc.isEmpty()) {
637         // restore first so that the write appends in the right place
638         // (does not hurt to do it again below)
639         currentPrependingSrc = savedPrependingSrc;
640
641         // we need to do this slightly modified bit of one of the write() cases
642         // because we want to prepend to pendingSrc rather than appending
643         // if there's no previous prependingSrc
644         if (loadingExtScript) {
645             if (currentPrependingSrc) {
646                 currentPrependingSrc->append(prependingSrc);
647             } else {
648                 pendingSrc.prepend(prependingSrc);
649             }
650         } else {
651             write(prependingSrc, false);
652         }
653     }
654
655     currentPrependingSrc = savedPrependingSrc;
656 }
657
658 void HTMLTokenizer::parseComment(TokenizerString &src)
659 {
660     bool strict = !parser->doc()->inCompatMode();
661     int delimiterCount = 0;
662     bool canClose = false;
663     checkScriptBuffer(src.length());
664     while ( !src.isEmpty() ) {
665         scriptCode[ scriptCodeSize++ ] = *src;
666 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
667         qDebug("comment is now: *%s*",
668                QConstString((QChar*)src.current(), QMIN(16, src.length())).string().latin1());
669 #endif
670
671         if (strict) {
672             if (src->unicode() == '-') {
673                 delimiterCount++;
674                 if (delimiterCount == 2) {
675                     delimiterCount = 0;
676                     canClose = !canClose;
677                 }
678             }
679             else
680                 delimiterCount = 0;
681         }
682
683         if ((!strict || canClose) && src->unicode() == '>') {
684             bool handleBrokenComments = brokenComments && !(script || style);
685             int endCharsCount = 1; // start off with one for the '>' character
686             if (!strict) {
687                 // In quirks mode just check for -->
688                 if (scriptCodeSize > 2 && scriptCode[scriptCodeSize-3] == '-' && scriptCode[scriptCodeSize-2] == '-') {
689                     endCharsCount = 3;
690                 }
691                 else if (scriptCodeSize > 3 && scriptCode[scriptCodeSize-4] == '-' && scriptCode[scriptCodeSize-3] == '-' && 
692                     scriptCode[scriptCodeSize-2] == '!') {
693                     // Other browsers will accept --!> as a close comment, even though it's
694                     // not technically valid.
695                     endCharsCount = 4;
696                 }
697             }
698             if (canClose || handleBrokenComments || endCharsCount > 1) {
699                 ++src;
700                 if (!( script || xmp || textarea || style)) {
701                     if (includesCommentsInDOM) {
702                         checkScriptBuffer();
703                         scriptCode[ scriptCodeSize ] = 0;
704                         scriptCode[ scriptCodeSize + 1 ] = 0;
705                         currToken.id = ID_COMMENT;
706                         processListing(TokenizerString(scriptCode, scriptCodeSize - endCharsCount));
707                         processToken();
708                         currToken.id = ID_COMMENT + ID_CLOSE_TAG;
709                         processToken();
710                     }
711                     scriptCodeSize = 0;
712                 }
713                 comment = false;
714                 return; // Finished parsing comment
715             }
716         }
717         ++src;
718     }
719 }
720
721 void HTMLTokenizer::parseServer(TokenizerString &src)
722 {
723     checkScriptBuffer(src.length());
724     while ( !src.isEmpty() ) {
725         scriptCode[ scriptCodeSize++ ] = *src;
726         if (src->unicode() == '>' &&
727             scriptCodeSize > 1 && scriptCode[scriptCodeSize-2] == '%') {
728             ++src;
729             server = false;
730             scriptCodeSize = 0;
731             return; // Finished parsing server include
732         }
733         ++src;
734     }
735 }
736
737 void HTMLTokenizer::parseProcessingInstruction(TokenizerString &src)
738 {
739     char oldchar = 0;
740     while ( !src.isEmpty() )
741     {
742         unsigned char chbegin = src->latin1();
743         if(chbegin == '\'') {
744             tquote = tquote == SingleQuote ? NoQuote : SingleQuote;
745         }
746         else if(chbegin == '\"') {
747             tquote = tquote == DoubleQuote ? NoQuote : DoubleQuote;
748         }
749         // Look for '?>'
750         // some crappy sites omit the "?" before it, so
751         // we look for an unquoted '>' instead. (IE compatible)
752         else if ( chbegin == '>' && ( !tquote || oldchar == '?' ) )
753         {
754             // We got a '?>' sequence
755             processingInstruction = false;
756             ++src;
757             discard=LFDiscard;
758             return; // Finished parsing comment!
759         }
760         ++src;
761         oldchar = chbegin;
762     }
763 }
764
765 void HTMLTokenizer::parseText(TokenizerString &src)
766 {
767     while ( !src.isEmpty() )
768     {
769         // do we need to enlarge the buffer?
770         checkBuffer();
771
772         // ascii is okay because we only do ascii comparisons
773         unsigned char chbegin = src->latin1();
774
775         if (skipLF && ( chbegin != '\n' ))
776         {
777             skipLF = false;
778         }
779
780         if (skipLF)
781         {
782             skipLF = false;
783             ++src;
784         }
785         else if (( chbegin == '\n' ) || ( chbegin == '\r' ))
786         {
787             if (chbegin == '\r')
788                 skipLF = true;
789
790             *dest++ = '\n';
791             ++src;
792         }
793         else {
794             *dest = *src;
795             fixUpChar(*dest);
796             ++dest;
797             ++src;
798         }
799     }
800 }
801
802
803 void HTMLTokenizer::parseEntity(TokenizerString &src, QChar *&dest, bool start)
804 {
805     if( start )
806     {
807         cBufferPos = 0;
808         Entity = SearchEntity;
809         EntityUnicodeValue = 0;
810     }
811
812     while( !src.isEmpty() )
813     {
814         ushort cc = src->unicode();
815         switch(Entity) {
816         case NoEntity:
817             assert(Entity != NoEntity);
818             return;
819         
820         case SearchEntity:
821             if(cc == '#') {
822                 cBuffer[cBufferPos++] = cc;
823                 ++src;
824                 Entity = NumericSearch;
825             }
826             else
827                 Entity = EntityName;
828
829             break;
830
831         case NumericSearch:
832             if(cc == 'x' || cc == 'X') {
833                 cBuffer[cBufferPos++] = cc;
834                 ++src;
835                 Entity = Hexadecimal;
836             }
837             else if(cc >= '0' && cc <= '9')
838                 Entity = Decimal;
839             else
840                 Entity = SearchSemicolon;
841
842             break;
843
844         case Hexadecimal:
845         {
846             int ll = kMin(src.length(), 8);
847             while(ll--) {
848                 QChar csrc(src->lower());
849                 cc = csrc.cell();
850
851                 if(csrc.row() || !((cc >= '0' && cc <= '9') || (cc >= 'a' && cc <= 'f'))) {
852                     break;
853                 }
854                 EntityUnicodeValue = EntityUnicodeValue*16 + (cc - ( cc < 'a' ? '0' : 'a' - 10));
855                 cBuffer[cBufferPos++] = cc;
856                 ++src;
857             }
858             Entity = SearchSemicolon;
859             break;
860         }
861         case Decimal:
862         {
863             int ll = kMin(src.length(), 9-cBufferPos);
864             while(ll--) {
865                 cc = src->cell();
866
867                 if(src->row() || !(cc >= '0' && cc <= '9')) {
868                     Entity = SearchSemicolon;
869                     break;
870                 }
871
872                 EntityUnicodeValue = EntityUnicodeValue * 10 + (cc - '0');
873                 cBuffer[cBufferPos++] = cc;
874                 ++src;
875             }
876             if(cBufferPos == 9)  Entity = SearchSemicolon;
877             break;
878         }
879         case EntityName:
880         {
881             int ll = kMin(src.length(), 9-cBufferPos);
882             while(ll--) {
883                 QChar csrc = *src;
884                 cc = csrc.cell();
885
886                 if(csrc.row() || !((cc >= 'a' && cc <= 'z') ||
887                                    (cc >= '0' && cc <= '9') || (cc >= 'A' && cc <= 'Z'))) {
888                     Entity = SearchSemicolon;
889                     break;
890                 }
891
892                 cBuffer[cBufferPos++] = cc;
893                 ++src;
894             }
895             if(cBufferPos == 9) Entity = SearchSemicolon;
896             if(Entity == SearchSemicolon) {
897                 if(cBufferPos > 1) {
898                     const entity *e = findEntity(cBuffer, cBufferPos);
899                     if(e)
900                         EntityUnicodeValue = e->code;
901
902                     // be IE compatible
903                     if(tag && EntityUnicodeValue > 255 && *src != ';')
904                         EntityUnicodeValue = 0;
905                 }
906             }
907             else
908                 break;
909         }
910         case SearchSemicolon:
911
912             //kdDebug( 6036 ) << "ENTITY " << EntityUnicodeValue << ", " << res << endl;
913
914             // Don't allow surrogate code points, or values that are more than 21 bits.
915             if ((EntityUnicodeValue > 0 && EntityUnicodeValue < 0xD800)
916                     || (EntityUnicodeValue >= 0xE000 && EntityUnicodeValue <= 0x1FFFFF)) {
917             
918                 if (*src == ';')
919                     ++src;
920
921                 if (EntityUnicodeValue <= 0xFFFF) {
922                     QChar c(EntityUnicodeValue);
923                     fixUpChar(c);
924                     checkBuffer();
925                     src.push(c);
926                 } else {
927                     // Convert to UTF-16, using surrogate code points.
928                     QChar c1(0xD800 | (((EntityUnicodeValue >> 16) - 1) << 6) | ((EntityUnicodeValue >> 10) & 0x3F));
929                     QChar c2(0xDC00 | (EntityUnicodeValue & 0x3FF));
930                     checkBuffer(2);
931                     src.push(c1);
932                     src.push(c2);
933                 }
934
935             } else {
936 #ifdef TOKEN_DEBUG
937                 kdDebug( 6036 ) << "unknown entity!" << endl;
938 #endif
939                 checkBuffer(10);
940                 // ignore the sequence, add it to the buffer as plaintext
941                 *dest++ = '&';
942                 for(unsigned int i = 0; i < cBufferPos; i++)
943                     dest[i] = cBuffer[i];
944                 dest += cBufferPos;
945                 if (pre)
946                     prePos += cBufferPos+1;
947             }
948
949             Entity = NoEntity;
950             return;
951         }
952     }
953 }
954
955 void HTMLTokenizer::parseTag(TokenizerString &src)
956 {
957     assert(!Entity );
958
959     while ( !src.isEmpty() )
960     {
961         checkBuffer();
962 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
963         uint l = 0;
964         while(l < src.length() && (*(src.current()+l)).latin1() != '>')
965             l++;
966         qDebug("src is now: *%s*, tquote: %d",
967                QConstString((QChar*)src.current(), l).string().latin1(), tquote);
968 #endif
969         switch(tag) {
970         case NoTag:
971         {
972             return;
973         }
974         case TagName:
975         {
976 #if defined(TOKEN_DEBUG) &&  TOKEN_DEBUG > 1
977             qDebug("TagName");
978 #endif
979             if (searchCount > 0)
980             {
981                 if (*src == commentStart[searchCount])
982                 {
983                     searchCount++;
984                     if (searchCount == 4)
985                     {
986 #ifdef TOKEN_DEBUG
987                         kdDebug( 6036 ) << "Found comment" << endl;
988 #endif
989                         // Found '<!--' sequence
990                         ++src;
991                         dest = buffer; // ignore the previous part of this tag
992                         comment = true;
993                         tag = NoTag;
994
995                         // Fix bug 34302 at kde.bugs.org.  Go ahead and treat
996                         // <!--> as a valid comment, since both mozilla and IE on windows
997                         // can handle this case.  Only do this in quirks mode. -dwh
998                         if (!src.isEmpty() && *src == '>' && parser->doc()->inCompatMode()) {
999                           comment = false;
1000                           ++src;
1001                           if (!src.isEmpty())
1002                               cBuffer[cBufferPos++] = src->cell();
1003                         }
1004                         else
1005                           parseComment(src);
1006
1007                         return; // Finished parsing tag!
1008                     }
1009                     // cuts of high part, is okay
1010                     cBuffer[cBufferPos++] = src->cell();
1011                     ++src;
1012                     break;
1013                 }
1014                 else
1015                     searchCount = 0; // Stop looking for '<!--' sequence
1016             }
1017
1018             bool finish = false;
1019             unsigned int ll = kMin(src.length(), CBUFLEN-cBufferPos);
1020             while(ll--) {
1021                 ushort curchar = *src;
1022                 if(curchar <= ' ' || curchar == '>' ) {
1023                     finish = true;
1024                     break;
1025                 }
1026                 // Use tolower() instead of | 0x20 to lowercase the char because there is no 
1027                 // performance gain in using | 0x20 since tolower() is optimized and 
1028                 // | 0x20 turns characters such as '_' into junk.
1029                 cBuffer[cBufferPos++] = tolower(curchar);
1030                 ++src;
1031             }
1032
1033             // Disadvantage: we add the possible rest of the tag
1034             // as attribute names. ### judge if this causes problems
1035             if(finish || CBUFLEN == cBufferPos) {
1036                 bool beginTag;
1037                 char* ptr = cBuffer;
1038                 unsigned int len = cBufferPos;
1039                 cBuffer[cBufferPos] = '\0';
1040                 if ((cBufferPos > 0) && (*ptr == '/'))
1041                 {
1042                     // End Tag
1043                     beginTag = false;
1044                     ptr++;
1045                     len--;
1046                 }
1047                 else
1048                     // Start Tag
1049                     beginTag = true;
1050
1051                 // Accept empty xml tags like <br/>.  We trim off the "/" so that when we call
1052                 // getTagID, we'll look up "br" as the tag name and not "br/".
1053                 if(len > 1 && ptr[len-1] == '/' )
1054                     ptr[--len] = '\0';
1055
1056                 // Look up the tagID for the specified tag name (now that we've shaved off any
1057                 // invalid / that might have followed the name).
1058                 unsigned short tagID = getTagID(ptr, len);
1059                 if (!tagID) {
1060                     DOMString tagName(ptr);
1061                     DocumentImpl *doc = parser->docPtr()->document();
1062                     if (doc->isValidName(tagName))
1063                         tagID = parser->docPtr()->document()->tagId(0, tagName.implementation(), false);
1064                 }
1065                 if (tagID) {
1066 #ifdef TOKEN_DEBUG
1067                     QCString tmp(ptr, len+1);
1068                     kdDebug( 6036 ) << "found tag id=" << tagID << ": " << tmp.data() << endl;
1069 #endif
1070                     currToken.id = beginTag ? tagID : tagID + ID_CLOSE_TAG;
1071                 }
1072                 dest = buffer;
1073                 tag = SearchAttribute;
1074                 cBufferPos = 0;
1075             }
1076             break;
1077         }
1078         case SearchAttribute:
1079         {
1080 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1081                 qDebug("SearchAttribute");
1082 #endif
1083             bool atespace = false;
1084             ushort curchar;
1085             while(!src.isEmpty()) {
1086                 curchar = *src;
1087                 // In this mode just ignore any quotes we encounter and treat them like spaces.
1088                 if (curchar > ' ' && curchar != '\'' && curchar != '"') {
1089                     if (curchar == '<' || curchar == '>')
1090                         tag = SearchEnd;
1091                     else
1092                         tag = AttributeName;
1093
1094                     cBufferPos = 0;
1095                     break;
1096                 }
1097                 atespace = true;
1098                 ++src;
1099             }
1100             break;
1101         }
1102         case AttributeName:
1103         {
1104 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1105                 qDebug("AttributeName");
1106 #endif
1107             ushort curchar;
1108             int ll = kMin(src.length(), CBUFLEN-cBufferPos);
1109
1110             while(ll--) {
1111                 curchar = *src;
1112                 if(curchar <= '>') {
1113                     if(curchar <= ' ' || curchar == '=' || curchar == '>') {
1114                         unsigned int a;
1115                         cBuffer[cBufferPos] = '\0';
1116                         a = getAttrID(cBuffer, cBufferPos);
1117                         if (a)
1118                             attrNamePresent = true;
1119                         else {
1120                             attrName = QString::fromLatin1(QCString(cBuffer, cBufferPos+1).data());
1121                             attrNamePresent = !attrName.isEmpty();
1122
1123                             // This is a deliberate quirk to match Mozilla and Opera.  We have to do this
1124                             // since sites that use the "standards-compliant" path sometimes send
1125                             // <script src="foo.js"/>.  Both Moz and Opera will honor this, despite it
1126                             // being bogus HTML.  They do not honor the "/" for other tags.  This behavior
1127                             // also deviates from WinIE, but in this case we'll just copy Moz and Opera.
1128                             if (currToken.id == ID_SCRIPT && curchar == '>' &&
1129                                 attrName == "/")
1130                                 currToken.flat = true;
1131                         }
1132                         
1133                         dest = buffer;
1134                         *dest++ = a;
1135 #ifdef TOKEN_DEBUG
1136                         if (!a || (cBufferPos && *cBuffer == '!'))
1137                             kdDebug( 6036 ) << "Unknown attribute: *" << QCString(cBuffer, cBufferPos+1).data() << "*" << endl;
1138                         else
1139                             kdDebug( 6036 ) << "Known attribute: " << QCString(cBuffer, cBufferPos+1).data() << endl;
1140 #endif
1141
1142                         tag = SearchEqual;
1143                         break;
1144                     }
1145                 }
1146                 // Use tolower() instead of | 0x20 to lowercase the char because there is no 
1147                 // performance gain in using | 0x20 since tolower() is optimized and 
1148                 // | 0x20 turns characters such as '_' into junk.
1149                 cBuffer[cBufferPos++] = tolower(curchar);
1150                 ++src;
1151             }
1152             if ( cBufferPos == CBUFLEN ) {
1153                 cBuffer[cBufferPos] = '\0';
1154                 attrName = QString::fromLatin1(QCString(cBuffer, cBufferPos+1).data());
1155                 attrNamePresent = !attrName.isEmpty();
1156                 dest = buffer;
1157                 *dest++ = 0;
1158                 tag = SearchEqual;
1159             }
1160             break;
1161         }
1162         case SearchEqual:
1163         {
1164 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1165                 qDebug("SearchEqual");
1166 #endif
1167             ushort curchar;
1168             bool atespace = false;
1169             while(!src.isEmpty()) {
1170                 curchar = src->unicode();
1171                 // In this mode just ignore any quotes we encounter and treat them like spaces.
1172                 if (curchar > ' ' && curchar != '\'' && curchar != '"') {
1173                     if(curchar == '=') {
1174 #ifdef TOKEN_DEBUG
1175                         kdDebug(6036) << "found equal" << endl;
1176 #endif
1177                         tag = SearchValue;
1178                         ++src;
1179                     }
1180                     else {
1181                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, emptyAtom);
1182                         dest = buffer;
1183                         tag = SearchAttribute;
1184                     }
1185                     break;
1186                 }
1187                 atespace = true;
1188                 ++src;
1189             }
1190             break;
1191         }
1192         case SearchValue:
1193         {
1194             ushort curchar;
1195             while(!src.isEmpty()) {
1196                 curchar = src->unicode();
1197                 if(curchar > ' ') {
1198                     if(( curchar == '\'' || curchar == '\"' )) {
1199                         tquote = curchar == '\"' ? DoubleQuote : SingleQuote;
1200                         tag = QuotedValue;
1201                         ++src;
1202                     } else
1203                         tag = Value;
1204
1205                     break;
1206                 }
1207                 ++src;
1208             }
1209             break;
1210         }
1211         case QuotedValue:
1212         {
1213 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1214                 qDebug("QuotedValue");
1215 #endif
1216             ushort curchar;
1217             while(!src.isEmpty()) {
1218                 checkBuffer();
1219
1220                 curchar = src->unicode();
1221                 if (curchar == '>' && !attrNamePresent) {
1222                     // Handle a case like <img '>.  Just go ahead and be willing
1223                     // to close the whole tag.  Don't consume the character and
1224                     // just go back into SearchEnd while ignoring the whole
1225                     // value.
1226                     // FIXME: Note that this is actually not a very good solution. It's
1227                     // an interim hack and doesn't handle the general case of
1228                     // unmatched quotes among attributes that have names. -dwh
1229                     while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1230                         dest--; // remove trailing newlines
1231                     AtomicString v(buffer+1, dest-buffer-1);
1232                     attrName.setUnicode(buffer+1,dest-buffer-1); 
1233                     currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1234                     tag = SearchAttribute;
1235                     dest = buffer;
1236                     tquote = NoQuote;
1237                     break;
1238                 }
1239                 
1240                 if(curchar <= '\'' && !src.escaped()) {
1241                     // ### attributes like '&{blaa....};' are supposed to be treated as jscript.
1242                     if ( curchar == '&' )
1243                     {
1244                         ++src;
1245                         parseEntity(src, dest, true);
1246                         break;
1247                     }
1248                     else if ( (tquote == SingleQuote && curchar == '\'') ||
1249                               (tquote == DoubleQuote && curchar == '\"') )
1250                     {
1251                         // some <input type=hidden> rely on trailing spaces. argh
1252                         while(dest > buffer+1 && (*(dest-1) == '\n' || *(dest-1) == '\r'))
1253                             dest--; // remove trailing newlines
1254                         AtomicString v(buffer+1, dest-buffer-1);
1255                         if (!attrNamePresent)
1256                             attrName.setUnicode(buffer+1,dest-buffer-1); 
1257                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1258
1259                         dest = buffer;
1260                         tag = SearchAttribute;
1261                         tquote = NoQuote;
1262                         ++src;
1263                         break;
1264                     }
1265                 }
1266                 *dest = *src;
1267                 fixUpChar(*dest);
1268                 ++dest;
1269                 ++src;
1270             }
1271             break;
1272         }
1273         case Value:
1274         {
1275 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1276             qDebug("Value");
1277 #endif
1278             ushort curchar;
1279             while(!src.isEmpty()) {
1280                 checkBuffer();
1281                 curchar = src->unicode();
1282                 if(curchar <= '>' && !src.escaped()) {
1283                     // parse Entities
1284                     if ( curchar == '&' )
1285                     {
1286                         ++src;
1287                         parseEntity(src, dest, true);
1288                         break;
1289                     }
1290                     // no quotes. Every space means end of value
1291                     // '/' does not delimit in IE!
1292                     if ( curchar <= ' ' || curchar == '>' )
1293                     {
1294                         AtomicString v(buffer+1, dest-buffer-1);
1295                         currToken.addAttribute(parser->docPtr()->document(), buffer, attrName, v);
1296                         dest = buffer;
1297                         tag = SearchAttribute;
1298                         break;
1299                     }
1300                 }
1301
1302                 *dest = *src;
1303                 fixUpChar(*dest);
1304                 ++dest;
1305                 ++src;
1306             }
1307             break;
1308         }
1309         case SearchEnd:
1310         {
1311 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 1
1312                 qDebug("SearchEnd");
1313 #endif
1314             while(!src.isEmpty()) {
1315                 if (*src == '>' || *src == '<')
1316                     break;
1317
1318                 if (*src == '/')
1319                     currToken.flat = true;
1320
1321                 ++src;
1322             }
1323             if (src.isEmpty()) break;
1324
1325             searchCount = 0; // Stop looking for '<!--' sequence
1326             tag = NoTag;
1327             tquote = NoQuote;
1328
1329             if (*src != '<')
1330                 ++src;
1331
1332             if ( !currToken.id ) //stop if tag is unknown
1333                 return;
1334
1335             uint tagID = currToken.id;
1336 #if defined(TOKEN_DEBUG) && TOKEN_DEBUG > 0
1337             kdDebug( 6036 ) << "appending Tag: " << tagID << endl;
1338 #endif
1339             bool beginTag = !currToken.flat && (tagID <= ID_CLOSE_TAG);
1340
1341             if (tagID > ID_CLOSE_TAG)
1342                 tagID -= ID_CLOSE_TAG;
1343             else if (tagID == ID_SCRIPT) {
1344                 AttributeImpl* a = 0;
1345                 bool foundTypeAttribute = false;
1346                 scriptSrc = QString::null;
1347                 scriptSrcCharset = QString::null;
1348                 if ( currToken.attrs && /* potentially have a ATTR_SRC ? */
1349                      parser->doc()->part() &&
1350                      parser->doc()->part()->jScriptEnabled() && /* jscript allowed at all? */
1351                      view /* are we a regular tokenizer or just for innerHTML ? */
1352                     ) {
1353                     if ( ( a = currToken.attrs->getAttributeItem( ATTR_SRC ) ) )
1354                         scriptSrc = parser->doc()->completeURL(parseURL( a->value() ).string() );
1355                     if ( ( a = currToken.attrs->getAttributeItem( ATTR_CHARSET ) ) )
1356                         scriptSrcCharset = a->value().string().stripWhiteSpace();
1357                     if ( scriptSrcCharset.isEmpty() )
1358                         scriptSrcCharset = parser->doc()->part()->encoding();
1359                     /* Check type before language, since language is deprecated */
1360                     if ((a = currToken.attrs->getAttributeItem(ATTR_TYPE)) != 0 && !a->value().string().isEmpty())
1361                         foundTypeAttribute = true;
1362                     else
1363                         a = currToken.attrs->getAttributeItem(ATTR_LANGUAGE);
1364                 }
1365                 javascript = true;
1366
1367                 if( foundTypeAttribute ) {
1368                     /* 
1369                         Mozilla 1.5 accepts application/x-javascript, and some web references claim it is the only
1370                         correct variation, but WinIE 6 doesn't accept it.
1371                         Neither Mozilla 1.5 nor WinIE 6 accept application/javascript, application/ecmascript, or
1372                         application/x-ecmascript.
1373                         Mozilla 1.5 doesn't accept the text/javascript1.x formats, but WinIE 6 does.
1374                         Mozilla 1.5 doesn't accept text/jscript, text/ecmascript, and text/livescript, but WinIE 6 does.
1375                         Mozilla 1.5 allows leading and trailing whitespace, but WinIE 6 doesn't.
1376                         Mozilla 1.5 and WinIE 6 both accept the empty string, but neither accept a whitespace-only string.
1377                         We want to accept all the values that either of these browsers accept, but not other values.
1378                      */
1379                     QString type = a->value().string().stripWhiteSpace().lower();
1380                     if( type.compare("application/x-javascript") != 0 &&
1381                         type.compare("text/javascript") != 0 &&
1382                         type.compare("text/javascript1.0") != 0 &&
1383                         type.compare("text/javascript1.1") != 0 &&
1384                         type.compare("text/javascript1.2") != 0 &&
1385                         type.compare("text/javascript1.3") != 0 &&
1386                         type.compare("text/javascript1.4") != 0 &&
1387                         type.compare("text/javascript1.5") != 0 &&
1388                         type.compare("text/jscript") != 0 &&
1389                         type.compare("text/ecmascript") != 0 &&
1390                         type.compare("text/livescript") )
1391                         javascript = false;
1392                 } else if( a ) {
1393                     /* 
1394                      Mozilla 1.5 doesn't accept jscript or ecmascript, but WinIE 6 does.
1395                      Mozilla 1.5 accepts javascript1.0, javascript1.4, and javascript1.5, but WinIE 6 accepts only 1.1 - 1.3.
1396                      Neither Mozilla 1.5 nor WinIE 6 accept leading or trailing whitespace.
1397                      We want to accept all the values that either of these browsers accept, but not other values.
1398                      */
1399                     QString lang = a->value().string();
1400                     lang = lang.lower();
1401                     if( lang.compare("") != 0 &&
1402                         lang.compare("javascript") != 0 &&
1403                         lang.compare("javascript1.0") != 0 &&
1404                         lang.compare("javascript1.1") != 0 &&
1405                         lang.compare("javascript1.2") != 0 &&
1406                         lang.compare("javascript1.3") != 0 &&
1407                         lang.compare("javascript1.4") != 0 &&
1408                         lang.compare("javascript1.5") != 0 &&
1409                         lang.compare("ecmascript") != 0 &&
1410                         lang.compare("livescript") != 0 &&
1411                         lang.compare("jscript") )
1412                         javascript = false;
1413                 }
1414             }
1415
1416             processToken();
1417
1418             // we have to take care to close the pre block in
1419             // case we encounter an unallowed element....
1420             if(pre && beginTag && !DOM::checkChild(ID_PRE, tagID, !parser->doc()->inCompatMode())) {
1421                 kdDebug(6036) << " not allowed in <pre> " << (int)tagID << endl;
1422                 pre = false;
1423             }
1424
1425             switch( tagID ) {
1426             case ID_PRE:
1427                 prePos = 0;
1428                 pre = beginTag;
1429                 break;
1430             case ID_SCRIPT:
1431                 if (beginTag) {
1432                     searchStopper = scriptEnd;
1433                     searchStopperLen = 8;
1434                     script = true;
1435                     parseSpecial(src);
1436                 }
1437                 else if (tagID <= ID_CLOSE_TAG) // Handle <script src="foo"/>
1438                     scriptHandler();
1439                 break;
1440             case ID_STYLE:
1441                 if (beginTag) {
1442                     searchStopper = styleEnd;
1443                     searchStopperLen = 7;
1444                     style = true;
1445                     parseSpecial(src);
1446                 }
1447                 break;
1448             case ID_TEXTAREA:
1449                 if(beginTag) {
1450                     searchStopper = textareaEnd;
1451                     searchStopperLen = 10;
1452                     textarea = true;
1453                     parseSpecial(src);
1454                 }
1455                 break;
1456             case ID_TITLE:
1457                 if (beginTag) {
1458                     searchStopper = titleEnd;
1459                     searchStopperLen = 7;
1460                     title = true;
1461                     parseSpecial(src);
1462                 }
1463                 break;
1464             case ID_XMP:
1465                 if (beginTag) {
1466                     searchStopper = xmpEnd;
1467                     searchStopperLen = 5;
1468                     xmp = true;
1469                     parseSpecial(src);
1470                 }
1471                 break;
1472             case ID_SELECT:
1473                 select = beginTag;
1474                 break;
1475             case ID_PLAINTEXT:
1476                 plaintext = beginTag;
1477                 break;
1478             }
1479             
1480             if (beginTag && endTagRequirement(tagID) == FORBIDDEN)
1481                 // Don't discard LFs since this element has no end tag.
1482                 discard = NoneDiscard;
1483                 
1484             return; // Finished parsing tag!
1485         }
1486         } // end switch
1487     }
1488     return;
1489 }
1490
1491 void HTMLTokenizer::addPending()
1492 {
1493     if ( select && !script )
1494     {
1495         *dest++ = ' ';
1496     }
1497     else if ( textarea || script )
1498     {
1499         switch(pending) {
1500         case LFPending:  *dest++ = '\n'; prePos = 0; break;
1501         case SpacePending: *dest++ = ' '; ++prePos; break;
1502         case TabPending: *dest++ = '\t'; prePos += TAB_SIZE - (prePos % TAB_SIZE); break;
1503         case NonePending:
1504             assert(0);
1505         }
1506     }
1507     else
1508     {
1509         int p;
1510
1511         switch (pending)
1512         {
1513         case SpacePending:
1514             // Insert a breaking space
1515             *dest++ = QChar(' ');
1516             prePos++;
1517             break;
1518
1519         case LFPending:
1520             *dest = '\n';
1521             dest++;
1522             prePos = 0;
1523             break;
1524
1525         case TabPending:
1526             p = TAB_SIZE - ( prePos % TAB_SIZE );
1527 #ifdef TOKEN_DEBUG
1528             qDebug("tab pending, prePos: %d, toadd: %d", prePos, p);
1529 #endif
1530
1531             for ( int x = 0; x < p; x++ )
1532                 *dest++ = QChar(' ');
1533             prePos += p;
1534             break;
1535
1536         case NonePending:
1537             assert(0);
1538             break;
1539         }
1540     }
1541     
1542     pending = NonePending;
1543 }
1544
1545 void HTMLTokenizer::write(const TokenizerString &str, bool appendData)
1546 {
1547 #ifdef TOKEN_DEBUG
1548     kdDebug( 6036 ) << this << " Tokenizer::write(\"" << str << "\"," << appendData << ")" << endl;
1549 #endif
1550
1551     if (!buffer)
1552         return;
1553     
1554     if (loadStopped)
1555         return;
1556
1557     if ( ( m_executingScript && appendData ) || !cachedScript.isEmpty() ) {
1558         // don't parse; we will do this later
1559         if (currentPrependingSrc) {
1560             currentPrependingSrc->append(str);
1561         } else {
1562             pendingSrc.append(str);
1563         }
1564         return;
1565     }
1566
1567     if ( onHold ) {
1568         src.append(str);
1569         return;
1570     }
1571     
1572     if (!src.isEmpty())
1573         src.append(str);
1574     else
1575         setSrc(str);
1576
1577     // Once a timer is set, it has control of when the tokenizer continues.
1578     if (timerId)
1579         return;
1580
1581     bool wasInWrite = inWrite;
1582     inWrite = true;
1583     
1584 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1585     if (!parser->doc()->ownerElement())
1586         printf("Beginning write at time %d\n", parser->doc()->elapsedTime());
1587 #endif
1588     
1589 //     if (Entity)
1590 //         parseEntity(src, dest);
1591
1592     int processedCount = 0;
1593     QTime startTime;
1594     startTime.start();
1595     KWQUIEventTime eventTime;
1596
1597     while (!src.isEmpty() && (!parser->doc()->part() || !parser->doc()->part()->isScheduledLocationChangePending())) {
1598         if (!continueProcessing(processedCount, startTime, eventTime))
1599             break;
1600
1601         // do we need to enlarge the buffer?
1602         checkBuffer();
1603
1604         ushort cc = src->unicode();
1605
1606         if (skipLF && (cc != '\n'))
1607             skipLF = false;
1608
1609         if (skipLF) {
1610             skipLF = false;
1611             ++src;
1612         }
1613         else if ( Entity )
1614             parseEntity( src, dest );
1615         else if ( plaintext )
1616             parseText( src );
1617         else if (script)
1618             parseSpecial(src);
1619         else if (style)
1620             parseSpecial(src);
1621         else if (xmp)
1622             parseSpecial(src);
1623         else if (textarea)
1624             parseSpecial(src);
1625         else if (title)
1626             parseSpecial(src);
1627         else if (comment)
1628             parseComment(src);
1629         else if (server)
1630             parseServer(src);
1631         else if (processingInstruction)
1632             parseProcessingInstruction(src);
1633         else if (tag)
1634             parseTag(src);
1635         else if ( startTag )
1636         {
1637             startTag = false;
1638
1639             switch(cc) {
1640             case '/':
1641                 break;
1642             case '!':
1643             {
1644                 // <!-- comment -->
1645                 searchCount = 1; // Look for '<!--' sequence to start comment
1646
1647                 break;
1648             }
1649             case '?':
1650             {
1651                 // xml processing instruction
1652                 processingInstruction = true;
1653                 tquote = NoQuote;
1654                 parseProcessingInstruction(src);
1655                 continue;
1656
1657                 break;
1658             }
1659             case '%':
1660                 if (!brokenServer) {
1661                     // <% server stuff, handle as comment %>
1662                     server = true;
1663                     tquote = NoQuote;
1664                     parseServer(src);
1665                     continue;
1666                 }
1667                 // else fall through
1668             default:
1669             {
1670                 if( ((cc >= 'a') && (cc <= 'z')) || ((cc >= 'A') && (cc <= 'Z')))
1671                 {
1672                     // Start of a Start-Tag
1673                 }
1674                 else
1675                 {
1676                     // Invalid tag
1677                     // Add as is
1678                     if (pending)
1679                         addPending();
1680                     *dest = '<';
1681                     dest++;
1682                     continue;
1683                 }
1684             }
1685             }; // end case
1686
1687             if ( pending ) {
1688                 // pre context always gets its spaces/linefeeds
1689                 if ( pre || script || (!parser->selectMode() &&
1690                              (!parser->noSpaces() || dest > buffer ))) {
1691                     addPending();
1692                     discard = AllDiscard; // So we discard the first LF after the open tag.
1693                 }
1694                 // just forget it
1695                 else
1696                     pending = NonePending;
1697             }
1698
1699             if (cc == '/' && discard == AllDiscard)
1700                 discard = NoneDiscard; // A close tag. No need to discard LF.
1701                     
1702             processToken();
1703
1704             cBufferPos = 0;
1705             tag = TagName;
1706             parseTag(src);
1707         }
1708         else if ( cc == '&' && !src.escaped())
1709         {
1710             ++src;
1711             if ( pending )
1712                 addPending();
1713             parseEntity(src, dest, true);
1714         }
1715         else if ( cc == '<' && !src.escaped())
1716         {
1717             tagStartLineno = lineno+src.lineCount();
1718             ++src;
1719             startTag = true;
1720         }
1721         else if (( cc == '\n' ) || ( cc == '\r' ))
1722         {
1723             if (select && !script)
1724             {
1725                 if (discard == LFDiscard)
1726                 {
1727                     // Ignore this LF
1728                     discard = NoneDiscard; // We have discarded 1 LF
1729                 }
1730                 else if(discard == AllDiscard)
1731                 {
1732                 }
1733                 else
1734                  {
1735                      // Process this LF
1736                     if (pending == NonePending)
1737                          pending = LFPending;
1738                 }
1739             }
1740             else {
1741                 if (discard == LFDiscard || discard == AllDiscard)
1742                 {
1743                     // Ignore this LF
1744                     discard = NoneDiscard; // We have discarded 1 LF
1745                 }
1746                 else
1747                 {
1748                     // Process this LF
1749                     if (pending)
1750                         addPending();
1751                     pending = LFPending;
1752                 }
1753             }
1754             
1755             /* Check for MS-DOS CRLF sequence */
1756             if (cc == '\r')
1757             {
1758                 skipLF = true;
1759             }
1760             ++src;
1761         }
1762         else if (( cc == ' ' ) || ( cc == '\t' ))
1763         {
1764             if (select && !script) {
1765                 if(discard == SpaceDiscard)
1766                     discard = NoneDiscard;
1767                  else if(discard == AllDiscard)
1768                  { }
1769                  else
1770                      pending = SpacePending;
1771             
1772             }
1773             else {
1774                 if (discard == AllDiscard)
1775                     discard = NoneDiscard;
1776             
1777                 if (pending)
1778                     addPending();
1779                 if (cc == ' ')
1780                     pending = SpacePending;
1781                 else
1782                     pending = TabPending;
1783             }
1784             
1785             ++src;
1786         }
1787         else
1788         {
1789             if (pending)
1790                 addPending();
1791
1792             discard = NoneDiscard;
1793             if ( pre )
1794             {
1795                 prePos++;
1796             }
1797 #if QT_VERSION < 300
1798             unsigned char row = src->row();
1799             if ( row > 0x05 && row < 0x10 || row > 0xfd )
1800                     currToken.complexText = true;
1801 #endif
1802             *dest = *src;
1803             fixUpChar( *dest );
1804             ++dest;
1805             ++src;
1806         }
1807     }
1808     
1809 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1810     if (!parser->doc()->ownerElement())
1811         printf("Ending write at time %d\n", parser->doc()->elapsedTime());
1812 #endif
1813     
1814     inWrite = wasInWrite;
1815
1816     if (noMoreData && !inWrite && !loadingExtScript && !m_executingScript && !timerId)
1817         end(); // this actually causes us to be deleted
1818 }
1819
1820 void HTMLTokenizer::stopped()
1821 {
1822     if (timerId) {
1823         killTimer(timerId);
1824         timerId = 0;
1825     }
1826 }
1827
1828 bool HTMLTokenizer::processingData() const
1829 {
1830     return timerId != 0;
1831 }
1832
1833 bool HTMLTokenizer::continueProcessing(int& processedCount, const QTime& startTime, const KWQUIEventTime& eventTime)
1834 {
1835     // We don't want to be checking elapsed time with every character, so we only check after we've
1836     // processed a certain number of characters.
1837     bool allowedYield = allowYield;
1838     allowYield = false;
1839     if (!loadingExtScript && !forceSynchronous && !m_executingScript && (processedCount > TOKENIZER_CHUNK_SIZE || allowedYield)) {
1840         processedCount = 0;
1841         if (startTime.elapsed() > TOKENIZER_TIME_DELAY) {
1842             /* FIXME: We'd like to yield aggressively to give stylesheets the opportunity to
1843                load, but this hurts overall performance on slower machines.  For now turn this
1844                off.
1845             || (!parser->doc()->haveStylesheetsLoaded() && 
1846                 (parser->doc()->documentElement()->id() != ID_HTML || parser->doc()->body()))) {*/
1847             // Schedule the timer to keep processing as soon as possible.
1848             if (!timerId)
1849                 timerId = startTimer(0);
1850 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1851             if (eventTime.uiEventPending())
1852                 printf("Deferring processing of data because of UI event.\n");
1853             else if (startTime.elapsed() > TOKENIZER_TIME_DELAY)
1854                 printf("Deferring processing of data because 200ms elapsed away from event loop.\n");
1855 #endif
1856             return false;
1857         }
1858     }
1859     
1860     processedCount++;
1861     return true;
1862 }
1863
1864 void HTMLTokenizer::timerEvent(QTimerEvent* e)
1865 {
1866     if (e->timerId() == timerId) {
1867         // Kill the timer.
1868         killTimer(timerId);
1869         timerId = 0;
1870
1871 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
1872         if (!parser->doc()->ownerElement())
1873             printf("Beginning timer write at time %d\n", parser->doc()->elapsedTime());
1874 #endif
1875
1876         if (parser->doc()->view() && parser->doc()->view()->layoutPending() && !parser->doc()->minimumLayoutDelay()) {
1877             // Restart the timer and let layout win.  This is basically a way of ensuring that the layout
1878             // timer has higher priority than our timer.
1879             timerId = startTimer(0);
1880             return;
1881         }
1882         
1883         // Invoke write() as though more data came in.
1884         bool oldNoMoreData = noMoreData;
1885         noMoreData = false;  // This prevents write() from deleting the tokenizer.
1886         write(TokenizerString(), true);
1887         noMoreData = oldNoMoreData;
1888         
1889         // If the timer dies (and stays dead after the write),  we need to let WebKit know that we're done processing the data.
1890         allDataProcessed();
1891     }
1892 }
1893
1894 void HTMLTokenizer::allDataProcessed()
1895 {
1896     if (noMoreData && !inWrite && !loadingExtScript && !m_executingScript && !onHold && !timerId) {
1897         QGuardedPtr<KHTMLView> savedView = view;
1898         end();
1899         if (savedView) {
1900             KHTMLPart *part = savedView->part();
1901             if (part) {
1902                 part->tokenizerProcessedData();
1903             }
1904         }
1905     }
1906 }
1907
1908 void HTMLTokenizer::end()
1909 {
1910     assert(timerId == 0);
1911     if (timerId) {
1912         // Clean up anyway.
1913         killTimer(timerId);
1914         timerId = 0;
1915     }
1916
1917     if ( buffer == 0 ) {
1918         parser->finished();
1919         emit finishedParsing();
1920         return;
1921     }
1922
1923     // parseTag is using the buffer for different matters
1924     if ( !tag )
1925         processToken();
1926
1927     if(buffer)
1928         KHTML_DELETE_QCHAR_VEC(buffer);
1929
1930     if(scriptCode)
1931         KHTML_DELETE_QCHAR_VEC(scriptCode);
1932
1933     scriptCode = 0;
1934     scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1935     buffer = 0;
1936     parser->finished();
1937     emit finishedParsing();
1938 }
1939
1940 void HTMLTokenizer::finish()
1941 {
1942     // do this as long as we don't find matching comment ends
1943     while((comment || server) && scriptCode && scriptCodeSize)
1944     {
1945         // we've found an unmatched comment start
1946         if (comment)
1947             brokenComments = true;
1948         else
1949             brokenServer = true;
1950         checkScriptBuffer();
1951         scriptCode[ scriptCodeSize ] = 0;
1952         scriptCode[ scriptCodeSize + 1 ] = 0;
1953         int pos;
1954         QString food;
1955         if (script || style) {
1956             food.setUnicode(scriptCode, scriptCodeSize);
1957         }
1958         else if (server) {
1959             food = "<";
1960             food += QString(scriptCode, scriptCodeSize);
1961         }
1962         else {
1963             pos = QConstString(scriptCode, scriptCodeSize).string().find('>');
1964             food.setUnicode(scriptCode+pos+1, scriptCodeSize-pos-1); // deep copy
1965         }
1966         KHTML_DELETE_QCHAR_VEC(scriptCode);
1967         scriptCode = 0;
1968         scriptCodeSize = scriptCodeMaxSize = scriptCodeResync = 0;
1969         comment = server = false;
1970         if ( !food.isEmpty() )
1971             write(food, true);
1972     }
1973     // this indicates we will not receive any more data... but if we are waiting on
1974     // an external script to load, we can't finish parsing until that is done
1975     noMoreData = true;
1976     if (!inWrite && !loadingExtScript && !m_executingScript && !onHold && !timerId)
1977         end(); // this actually causes us to be deleted
1978 }
1979
1980 void HTMLTokenizer::processToken()
1981 {
1982     KJSProxy *jsProxy = (view && view->part()) ? view->part()->jScript() : 0L;    
1983     if (jsProxy)
1984         jsProxy->setEventHandlerLineno(tagStartLineno);
1985     if ( dest > buffer )
1986     {
1987 #ifdef TOKEN_DEBUG
1988         if(currToken.id) {
1989             qDebug( "unexpected token id: %d, str: *%s*", currToken.id,QConstString( buffer,dest-buffer ).string().latin1() );
1990             assert(0);
1991         }
1992
1993 #endif
1994         currToken.text = new DOMStringImpl( buffer, dest - buffer );
1995         currToken.text->ref();
1996         if (currToken.id != ID_COMMENT)
1997             currToken.id = ID_TEXT;
1998     }
1999     else if(!currToken.id) {
2000         currToken.reset();
2001         if (jsProxy)
2002             jsProxy->setEventHandlerLineno(lineno+src.lineCount());
2003         return;
2004     }
2005
2006     dest = buffer;
2007
2008 #ifdef TOKEN_DEBUG
2009     QString name = getTagName(currToken.id).string();
2010     QString text;
2011     if(currToken.text)
2012         text = QConstString(currToken.text->s, currToken.text->l).string();
2013
2014     kdDebug( 6036 ) << "Token --> " << name << "   id = " << currToken.id << endl;
2015     if (currToken.flat)
2016         kdDebug( 6036 ) << "Token is FLAT!" << endl;
2017     if(!text.isNull())
2018         kdDebug( 6036 ) << "text: \"" << text << "\"" << endl;
2019     unsigned long l = currToken.attrs ? currToken.attrs->length() : 0;
2020     if(l) {
2021         kdDebug( 6036 ) << "Attributes: " << l << endl;
2022         for (unsigned long i = 0; i < l; ++i) {
2023             AttributeImpl* c = currToken.attrs->attributeItem(i);
2024             kdDebug( 6036 ) << "    " << c->id() << " " << parser->doc()->getDocument()->attrName(c->id()).string()
2025                             << "=\"" << c->value().string() << "\"" << endl;
2026         }
2027     }
2028     kdDebug( 6036 ) << endl;
2029 #endif
2030     
2031     if (!loadStopped) {
2032         // pass the token over to the parser, the parser DOES NOT delete the token
2033         parser->parseToken(&currToken);
2034     }
2035     
2036     currToken.reset();
2037     if (jsProxy)
2038         jsProxy->setEventHandlerLineno(0);
2039 }
2040
2041 HTMLTokenizer::~HTMLTokenizer()
2042 {
2043     assert(!inWrite);
2044     reset();
2045     delete parser;
2046 }
2047
2048
2049 void HTMLTokenizer::enlargeBuffer(int len)
2050 {
2051     int newsize = kMax(size*2, size+len);
2052     int oldoffs = (dest - buffer);
2053
2054     buffer = (QChar*)realloc(buffer, newsize*sizeof(QChar));
2055     dest = buffer + oldoffs;
2056     size = newsize;
2057 }
2058
2059 void HTMLTokenizer::enlargeScriptBuffer(int len)
2060 {
2061     int newsize = kMax(scriptCodeMaxSize*2, scriptCodeMaxSize+len);
2062     scriptCode = (QChar*)realloc(scriptCode, newsize*sizeof(QChar));
2063     scriptCodeMaxSize = newsize;
2064 }
2065
2066 void HTMLTokenizer::notifyFinished(CachedObject */*finishedObj*/)
2067 {
2068 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
2069     if (!parser->doc()->ownerElement())
2070         printf("script loaded at %d\n", parser->doc()->elapsedTime());
2071 #endif
2072
2073     assert(!cachedScript.isEmpty());
2074     bool finished = false;
2075     while (!finished && cachedScript.head()->isLoaded()) {
2076 #ifdef TOKEN_DEBUG
2077         kdDebug( 6036 ) << "Finished loading an external script" << endl;
2078 #endif
2079         CachedScript* cs = cachedScript.dequeue();
2080         DOMString scriptSource = cs->script();
2081 #ifdef TOKEN_DEBUG
2082         kdDebug( 6036 ) << "External script is:" << endl << scriptSource.string() << endl;
2083 #endif
2084         setSrc(TokenizerString());
2085
2086         // make sure we forget about the script before we execute the new one
2087         // infinite recursion might happen otherwise
2088         QString cachedScriptUrl( cs->url().string() );
2089         cs->deref(this);
2090
2091 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
2092         if (!parser->doc()->ownerElement())
2093             printf("external script beginning execution at %d\n", parser->doc()->elapsedTime());
2094 #endif
2095
2096         scriptExecution( scriptSource.string(), cachedScriptUrl );
2097
2098         // The state of cachedScript.isEmpty() can change inside the scriptExecution()
2099         // call above, so test afterwards.
2100         finished = cachedScript.isEmpty();
2101         if (finished) {
2102             loadingExtScript = false;
2103 #ifdef INSTRUMENT_LAYOUT_SCHEDULING
2104             if (!parser->doc()->ownerElement())
2105                 printf("external script finished execution at %d\n", parser->doc()->elapsedTime());
2106 #endif
2107         }
2108
2109         // 'script' is true when we are called synchronously from
2110         // parseScript(). In that case parseScript() will take care
2111         // of 'scriptOutput'.
2112         if ( !script ) {
2113             TokenizerString rest = pendingSrc;
2114             pendingSrc.clear();
2115             write(rest, false);
2116             // we might be deleted at this point, do not
2117             // access any members.
2118         }
2119     }
2120 }
2121
2122 bool HTMLTokenizer::isWaitingForScripts() const
2123 {
2124     return loadingExtScript;
2125 }
2126
2127 void HTMLTokenizer::setSrc(const TokenizerString &source)
2128 {
2129     lineno += src.lineCount();
2130     src = source;
2131     src.resetLineCount();
2132 }
2133
2134 void HTMLTokenizer::setOnHold(bool _onHold)
2135 {
2136     onHold = _onHold;
2137 }
2138
2139 }