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