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