Reviewed by darin.
[WebKit-https.git] / WebCore / khtml / html / html_headimpl.cpp
1 /**
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *           (C) 2001 Dirk Mueller (mueller@kde.org)
7  * Copyright (C) 2003 Apple Computer, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24 // -------------------------------------------------------------------------
25
26 #include "html/html_headimpl.h"
27 #include "html/html_documentimpl.h"
28 #include "xml/dom_textimpl.h"
29
30 #include "khtmlview.h"
31 #include "khtml_part.h"
32 #include "kjs_proxy.h"
33
34 #include "misc/htmlhashes.h"
35 #include "misc/loader.h"
36 #include "misc/helper.h"
37
38 #include "css/cssstyleselector.h"
39 #include "css/css_stylesheetimpl.h"
40 #include "css/csshelper.h"
41
42 #include <kurl.h>
43 #include <kdebug.h>
44
45 using namespace DOM;
46 using namespace khtml;
47
48 HTMLBaseElementImpl::HTMLBaseElementImpl(DocumentPtr *doc)
49     : HTMLElementImpl(doc)
50 {
51 }
52
53 HTMLBaseElementImpl::~HTMLBaseElementImpl()
54 {
55 }
56
57 NodeImpl::Id HTMLBaseElementImpl::id() const
58 {
59     return ID_BASE;
60 }
61
62 void HTMLBaseElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr)
63 {
64     switch(attr->id())
65     {
66     case ATTR_HREF:
67         m_href = khtml::parseURL(attr->value());
68         process();
69         break;
70     case ATTR_TARGET:
71         m_target = attr->value();
72         process();
73         break;
74     default:
75         HTMLElementImpl::parseHTMLAttribute(attr);
76     }
77 }
78
79 void HTMLBaseElementImpl::insertedIntoDocument()
80 {
81     HTMLElementImpl::insertedIntoDocument();
82     process();
83 }
84
85 void HTMLBaseElementImpl::removedFromDocument()
86 {
87     HTMLElementImpl::removedFromDocument();
88
89     // Since the document doesn't have a base element...
90     // (This will break in the case of multiple base elements, but that's not valid anyway (?))
91     getDocument()->setBaseURL( QString::null );
92     getDocument()->setBaseTarget( QString::null );
93 }
94
95 void HTMLBaseElementImpl::process()
96 {
97     if (!inDocument())
98         return;
99
100     if(!m_href.isEmpty() && getDocument()->part())
101         getDocument()->setBaseURL( KURL( getDocument()->part()->url(), m_href.string() ).url() );
102
103     if(!m_target.isEmpty())
104         getDocument()->setBaseTarget( m_target.string() );
105
106     // ### should changing a document's base URL dynamically automatically update all images, stylesheets etc?
107 }
108
109 // -------------------------------------------------------------------------
110
111 HTMLLinkElementImpl::HTMLLinkElementImpl(DocumentPtr *doc)
112     : HTMLElementImpl(doc)
113 {
114     m_sheet = 0;
115     m_loading = false;
116     m_cachedSheet = 0;
117     m_isStyleSheet = m_isIcon = m_alternate = false;
118     m_disabledState = 0;
119 }
120
121 HTMLLinkElementImpl::~HTMLLinkElementImpl()
122 {
123     if(m_sheet) m_sheet->deref();
124     if(m_cachedSheet) m_cachedSheet->deref(this);
125 }
126
127 NodeImpl::Id HTMLLinkElementImpl::id() const
128 {
129     return ID_LINK;
130 }
131
132 void HTMLLinkElementImpl::setDisabledState(bool _disabled)
133 {
134     int oldDisabledState = m_disabledState;
135     m_disabledState = _disabled ? 2 : 1;
136     if (oldDisabledState != m_disabledState) {
137         // If we change the disabled state while the sheet is still loading, then we have to
138         // perform three checks:
139         if (isLoading()) {
140             // Check #1: If the sheet becomes disabled while it was loading, and if it was either
141             // a main sheet or a sheet that was previously enabled via script, then we need
142             // to remove it from the list of pending sheets.
143             if (m_disabledState == 2 && (!m_alternate || oldDisabledState == 1))
144                 getDocument()->stylesheetLoaded();
145
146             // Check #2: An alternate sheet becomes enabled while it is still loading.
147             if (m_alternate && m_disabledState == 1)
148                 getDocument()->addPendingSheet();
149
150             // Check #3: A main sheet becomes enabled while it was still loading and
151             // after it was disabled via script.  It takes really terrible code to make this
152             // happen (a double toggle for no reason essentially).  This happens on
153             // virtualplastic.net, which manages to do about 12 enable/disables on only 3
154             // sheets. :)
155             if (!m_alternate && m_disabledState == 1 && oldDisabledState == 2)
156                 getDocument()->addPendingSheet();
157
158             // If the sheet is already loading just bail.
159             return;
160         }
161
162         // Load the sheet, since it's never been loaded before.
163         if (!m_sheet && m_disabledState == 1)
164             process();
165         else
166             getDocument()->updateStyleSelector(); // Update the style selector.
167     }
168 }
169
170 void HTMLLinkElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr)
171 {
172     switch (attr->id())
173     {
174     case ATTR_REL:
175         tokenizeRelAttribute(attr->value());
176         process();
177         break;
178     case ATTR_HREF:
179         m_url = getDocument()->completeURL( khtml::parseURL(attr->value()).string() );
180         process();
181         break;
182     case ATTR_TYPE:
183         m_type = attr->value();
184         process();
185         break;
186     case ATTR_MEDIA:
187         m_media = attr->value().string().lower();
188         process();
189         break;
190     case ATTR_DISABLED:
191         setDisabledState(!attr->isNull());
192         break;
193     default:
194         HTMLElementImpl::parseHTMLAttribute(attr);
195     }
196 }
197
198 void HTMLLinkElementImpl::tokenizeRelAttribute(const AtomicString& relStr)
199 {
200     m_isStyleSheet = m_isIcon = m_alternate = false;
201     QString rel = relStr.string().lower();
202     if (rel == "stylesheet")
203         m_isStyleSheet = true;
204     else if (rel == "icon" || rel == "shortcut icon")
205         m_isIcon = true;
206     else if (rel == "alternate stylesheet" || rel == "stylesheet alternate")
207         m_isStyleSheet = m_alternate = true;
208     else {
209         // Tokenize the rel attribute and set bits based on specific keywords that we find.
210         rel.replace('\n', ' ');
211         QStringList list = QStringList::split(' ', rel);        
212         for (QStringList::Iterator i = list.begin(); i != list.end(); ++i) {
213             if (*i == "stylesheet")
214                 m_isStyleSheet = true;
215             else if (*i == "alternate")
216                 m_alternate = true;
217             else if (*i == "icon")
218                 m_isIcon = true;
219         }
220     }
221 }
222
223 void HTMLLinkElementImpl::process()
224 {
225     if (!inDocument())
226         return;
227
228     QString type = m_type.string().lower();
229     
230     KHTMLPart* part = getDocument()->part();
231
232     // IE extension: location of small icon for locationbar / bookmarks
233     if (part && m_isIcon && !m_url.isEmpty() && !part->parentPart()) {
234         if (!type.isEmpty()) // Mozilla extension to IE extension: icon specified with type
235             part->browserExtension()->setTypedIconURL(KURL(m_url.string()), type);
236         else 
237             part->browserExtension()->setIconURL(KURL(m_url.string()));
238     }
239
240     // Stylesheet
241     // This was buggy and would incorrectly match <link rel="alternate">, which has a different specified meaning. -dwh
242     if (m_disabledState != 2 && (type.contains("text/css") || m_isStyleSheet) && getDocument()->part()) {
243         // no need to load style sheets which aren't for the screen output
244         // ### there may be in some situations e.g. for an editor or script to manipulate
245         // also, don't load style sheets for standalone documents
246         if (m_media.isNull() || m_media.contains("screen") || m_media.contains("all") || m_media.contains("print")) {
247             m_loading = true;
248
249             // Add ourselves as a pending sheet, but only if we aren't an alternate 
250             // stylesheet.  Alternate stylesheets don't hold up render tree construction.
251             if (!isAlternate())
252                 getDocument()->addPendingSheet();
253             
254             QString chset = getAttribute( ATTR_CHARSET ).string();
255             if (m_cachedSheet)
256                 m_cachedSheet->deref(this);
257             m_cachedSheet = getDocument()->docLoader()->requestStyleSheet(m_url, chset);
258             if (m_cachedSheet)
259                 m_cachedSheet->ref(this);
260         }
261     }
262     else if (m_sheet) {
263         // we no longer contain a stylesheet, e.g. perhaps rel or type was changed
264         m_sheet->deref();
265         m_sheet = 0;
266         getDocument()->updateStyleSelector();
267     }
268 }
269
270 void HTMLLinkElementImpl::insertedIntoDocument()
271 {
272     HTMLElementImpl::insertedIntoDocument();
273     process();
274 }
275
276 void HTMLLinkElementImpl::removedFromDocument()
277 {
278     HTMLElementImpl::removedFromDocument();
279     process();
280 }
281
282 void HTMLLinkElementImpl::setStyleSheet(const DOM::DOMString &url, const DOM::DOMString &sheetStr)
283 {
284 //    kdDebug( 6030 ) << "HTMLLinkElement::setStyleSheet()" << endl;
285 //    kdDebug( 6030 ) << "**** current medium: " << m_media << endl;
286
287     if (m_sheet)
288         m_sheet->deref();
289     m_sheet = new CSSStyleSheetImpl(this, url);
290     kdDebug( 6030 ) << "style sheet parse mode strict = " << ( !getDocument()->inCompatMode() ) << endl;
291     m_sheet->ref();
292     m_sheet->parseString( sheetStr, !getDocument()->inCompatMode() );
293
294     MediaListImpl *media = new MediaListImpl( m_sheet, m_media );
295     m_sheet->setMedia( media );
296
297     m_loading = false;
298
299     // Tell the doc about the sheet.
300     if (!isLoading() && m_sheet && !isDisabled() && !isAlternate())
301         getDocument()->stylesheetLoaded();
302 }
303
304 bool HTMLLinkElementImpl::isLoading() const
305 {
306 //    kdDebug( 6030 ) << "link: checking if loading!" << endl;
307     if(m_loading) return true;
308     if(!m_sheet) return false;
309     //if(!m_sheet->isCSSStyleSheet()) return false;
310     return static_cast<CSSStyleSheetImpl *>(m_sheet)->isLoading();
311 }
312
313 void HTMLLinkElementImpl::sheetLoaded()
314 {
315     if (!isLoading() && !isDisabled() && !isAlternate())
316         getDocument()->stylesheetLoaded();
317 }
318
319 bool HTMLLinkElementImpl::isURLAttribute(AttributeImpl *attr) const
320 {
321     return attr->id() == ATTR_HREF;
322 }
323
324 // -------------------------------------------------------------------------
325
326 HTMLMetaElementImpl::HTMLMetaElementImpl(DocumentPtr *doc) : HTMLElementImpl(doc)
327 {
328 }
329
330 HTMLMetaElementImpl::~HTMLMetaElementImpl()
331 {
332 }
333
334 NodeImpl::Id HTMLMetaElementImpl::id() const
335 {
336     return ID_META;
337 }
338
339 void HTMLMetaElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr)
340 {
341     switch(attr->id())
342     {
343     case ATTR_HTTP_EQUIV:
344         m_equiv = attr->value();
345         process();
346         break;
347     case ATTR_CONTENT:
348         m_content = attr->value();
349         process();
350         break;
351     case ATTR_NAME:
352       break;
353     default:
354         HTMLElementImpl::parseHTMLAttribute(attr);
355     }
356 }
357
358 void HTMLMetaElementImpl::insertedIntoDocument()
359 {
360     HTMLElementImpl::insertedIntoDocument();
361     process();
362 }
363
364 void HTMLMetaElementImpl::process()
365 {
366     // Get the document to process the tag, but only if we're actually part of DOM tree (changing a meta tag while
367     // it's not in the tree shouldn't have any effect on the document)
368     if (inDocument() && !m_equiv.isNull() && !m_content.isNull())
369         getDocument()->processHttpEquiv(m_equiv,m_content);
370 }
371
372 // -------------------------------------------------------------------------
373
374 HTMLScriptElementImpl::HTMLScriptElementImpl(DocumentPtr *doc)
375     : HTMLElementImpl(doc), m_cachedScript(0), m_createdByParser(false)
376 {
377 }
378
379 HTMLScriptElementImpl::~HTMLScriptElementImpl()
380 {
381     if (m_cachedScript)
382         m_cachedScript->deref(this);
383 }
384
385 void HTMLScriptElementImpl::insertedIntoDocument()
386 {
387     HTMLElementImpl::insertedIntoDocument();
388
389     assert(!m_cachedScript);
390
391     if (m_createdByParser)
392         return;
393     
394     QString url = getAttribute(ATTR_SRC).string();
395     if (!url.isEmpty()) {
396         QString charset = getAttribute(ATTR_CHARSET).string();
397         m_cachedScript = getDocument()->docLoader()->requestScript(DOMString(url), charset);
398         m_cachedScript->ref(this);
399         return;
400     }
401
402     DOMString scriptString = "";
403     for (NodeImpl *n = firstChild(); n; n = n->nextSibling())
404         if (n->isTextNode()) 
405             scriptString += static_cast<TextImpl*>(n)->data();
406
407     DocumentImpl *doc = getDocument();
408     KHTMLPart *part = doc->part();
409     if (!part)
410         return;
411     KJSProxy *proxy = KJSProxy::proxy(part);
412     if (!proxy)
413         return;
414
415     proxy->evaluate(doc->URL(), 0, scriptString.string(), Node());
416     DocumentImpl::updateDocumentsRendering();
417 }
418
419 void HTMLScriptElementImpl::removedFromDocument()
420 {
421     HTMLElementImpl::removedFromDocument();
422
423     if (m_cachedScript) {
424         m_cachedScript->deref(this);
425         m_cachedScript = 0;
426     }
427 }
428
429 void HTMLScriptElementImpl::notifyFinished(CachedObject* o)
430 {
431     CachedScript *cs = static_cast<CachedScript *>(o);
432
433     assert(cs == m_cachedScript);
434
435     KHTMLPart *part = getDocument()->part();
436     if (part) {
437         KJSProxy *proxy = KJSProxy::proxy(part);
438         if (proxy) {
439             proxy->evaluate(cs->url().string(), 0, cs->script().string(), Node()); 
440             DocumentImpl::updateDocumentsRendering();
441         }
442     }
443
444     cs->deref(this);
445     m_cachedScript = 0;
446 }
447
448 NodeImpl::Id HTMLScriptElementImpl::id() const
449 {
450     return ID_SCRIPT;
451 }
452
453 bool HTMLScriptElementImpl::isURLAttribute(AttributeImpl *attr) const
454 {
455     return attr->id() == ATTR_SRC;
456 }
457
458 // -------------------------------------------------------------------------
459
460 HTMLStyleElementImpl::HTMLStyleElementImpl(DocumentPtr *doc) : HTMLElementImpl(doc)
461 {
462     m_sheet = 0;
463     m_loading = false;
464 }
465
466 HTMLStyleElementImpl::~HTMLStyleElementImpl()
467 {
468     if(m_sheet) m_sheet->deref();
469 }
470
471 NodeImpl::Id HTMLStyleElementImpl::id() const
472 {
473     return ID_STYLE;
474 }
475
476 // other stuff...
477 void HTMLStyleElementImpl::parseHTMLAttribute(HTMLAttributeImpl *attr)
478 {
479     switch (attr->id())
480     {
481     case ATTR_TYPE:
482         m_type = attr->value().domString().lower();
483         break;
484     case ATTR_MEDIA:
485         m_media = attr->value().string().lower();
486         break;
487     default:
488         HTMLElementImpl::parseHTMLAttribute(attr);
489     }
490 }
491
492 void HTMLStyleElementImpl::insertedIntoDocument()
493 {
494     HTMLElementImpl::insertedIntoDocument();
495     if (m_sheet)
496         getDocument()->updateStyleSelector();
497 }
498
499 void HTMLStyleElementImpl::removedFromDocument()
500 {
501     HTMLElementImpl::removedFromDocument();
502     if (m_sheet)
503         getDocument()->updateStyleSelector();
504 }
505
506 void HTMLStyleElementImpl::childrenChanged()
507 {
508     DOMString text = "";
509
510     for (NodeImpl *c = firstChild(); c != 0; c = c->nextSibling()) {
511         if ((c->nodeType() == Node::TEXT_NODE) ||
512             (c->nodeType() == Node::CDATA_SECTION_NODE) ||
513             (c->nodeType() == Node::COMMENT_NODE))
514             text += c->nodeValue();
515     }
516
517     if (m_sheet) {
518         if (static_cast<CSSStyleSheetImpl *>(m_sheet)->isLoading())
519             getDocument()->stylesheetLoaded(); // Remove ourselves from the sheet list.
520         m_sheet->deref();
521         m_sheet = 0;
522     }
523
524     m_loading = false;
525     if ((m_type.isEmpty() || m_type == "text/css") // Type must be empty or CSS
526          && (m_media.isNull() || m_media.contains("screen") || m_media.contains("all") || m_media.contains("print"))) {
527         getDocument()->addPendingSheet();
528         m_loading = true;
529         m_sheet = new CSSStyleSheetImpl(this);
530         m_sheet->ref();
531         m_sheet->parseString( text, !getDocument()->inCompatMode() );
532         MediaListImpl *media = new MediaListImpl( m_sheet, m_media );
533         m_sheet->setMedia( media );
534         m_loading = false;
535     }
536
537     if (!isLoading() && m_sheet)
538         getDocument()->stylesheetLoaded();
539 }
540
541 bool HTMLStyleElementImpl::isLoading() const
542 {
543     if (m_loading) return true;
544     if(!m_sheet) return false;
545     return static_cast<CSSStyleSheetImpl *>(m_sheet)->isLoading();
546 }
547
548 void HTMLStyleElementImpl::sheetLoaded()
549 {
550     if (!isLoading())
551         getDocument()->stylesheetLoaded();
552 }
553
554 // -------------------------------------------------------------------------
555
556 HTMLTitleElementImpl::HTMLTitleElementImpl(DocumentPtr *doc)
557     : HTMLElementImpl(doc)
558 {
559 }
560
561 HTMLTitleElementImpl::~HTMLTitleElementImpl()
562 {
563 }
564
565 NodeImpl::Id HTMLTitleElementImpl::id() const
566 {
567     return ID_TITLE;
568 }
569
570 void HTMLTitleElementImpl::insertedIntoDocument()
571 {
572     HTMLElementImpl::insertedIntoDocument();
573 #if APPLE_CHANGES
574     // Only allow title to be set by first <title> encountered.
575     if (getDocument()->title().isEmpty())
576         getDocument()->setTitle(m_title);
577 #else
578         getDocument()->setTitle(m_title);
579 #endif
580 }
581
582 void HTMLTitleElementImpl::removedFromDocument()
583 {
584     HTMLElementImpl::removedFromDocument();
585     // Title element removed, so we have no title... we ignore the case of multiple title elements, as it's invalid
586     // anyway (?)
587     getDocument()->setTitle(DOMString());
588 }
589
590 void HTMLTitleElementImpl::childrenChanged()
591 {
592     HTMLElementImpl::childrenChanged();
593     m_title = "";
594     for (NodeImpl *c = firstChild(); c != 0; c = c->nextSibling()) {
595         if ((c->nodeType() == Node::TEXT_NODE) || (c->nodeType() == Node::CDATA_SECTION_NODE))
596             m_title += c->nodeValue();
597     }
598 #if APPLE_CHANGES
599     // Only allow title to be set by first <title> encountered.
600     if (inDocument() && getDocument()->title().isEmpty())
601 #else
602     if (inDocument()))
603 #endif
604         getDocument()->setTitle(m_title);
605 }