2 * This file is part of the DOM implementation for KDE.
4 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
5 * (C) 2004 Apple Computer, Inc.
6 * (C) 2008 Germain Garand <germain@ebooksfrance.org>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
25 //#define CSS_STYLESHEET_DEBUG
27 #include "css_stylesheetimpl.h"
28 #include "css_ruleimpl.h"
29 #include "css_valueimpl.h"
30 #include "cssparser.h"
31 #include "css_mediaquery.h"
33 #include <dom/dom_string.h>
34 #include <dom/dom_exception.h>
35 #include <dom/css_stylesheet.h>
36 #include <dom/css_rule.h>
37 #include <dom/dom_exception.h>
39 #include <xml/dom_nodeimpl.h>
40 #include <html/html_documentimpl.h>
41 #include <misc/loader.h>
46 using namespace khtml
;
47 // --------------------------------------------------------------------------------
49 StyleSheetImpl::StyleSheetImpl(StyleSheetImpl
*parentSheet
, DOMString href
)
50 : StyleListImpl(parentSheet
)
59 StyleSheetImpl::StyleSheetImpl(DOM::NodeImpl
*parentNode
, DOMString href
)
62 m_parentNode
= parentNode
;
68 StyleSheetImpl::StyleSheetImpl(StyleBaseImpl
*owner
, DOMString href
)
69 : StyleListImpl(owner
)
77 StyleSheetImpl::~StyleSheetImpl()
80 m_media
->setParent( 0 );
85 StyleSheetImpl
*StyleSheetImpl::parentStyleSheet() const
87 if( !m_parent
) return 0;
88 if( m_parent
->isStyleSheet() ) return static_cast<StyleSheetImpl
*>(m_parent
);
89 if( m_parent
->isRule() ) return m_parent
->stylesheet();
93 void StyleSheetImpl::setMedia( MediaListImpl
*media
)
98 m_media
->setParent( 0 );
103 m_media
->setParent( this );
106 void StyleSheetImpl::setDisabled( bool disabled
)
108 bool updateStyle
= isCSSStyleSheet() && m_parentNode
&& disabled
!= m_disabled
;
109 m_disabled
= disabled
;
111 m_parentNode
->document()->updateStyleSelector();
114 // -----------------------------------------------------------------------
117 CSSStyleSheetImpl::CSSStyleSheetImpl(CSSStyleSheetImpl
*parentSheet
, DOMString href
)
118 : StyleSheetImpl(parentSheet
, href
)
120 m_lstChildren
= new QList
<StyleBaseImpl
*>;
121 m_doc
= parentSheet
? parentSheet
->doc() : 0;
124 m_defaultNamespace
= NamespaceName::fromId(anyNamespace
);
125 m_loadedHint
= false;
128 CSSStyleSheetImpl::CSSStyleSheetImpl(DOM::NodeImpl
*parentNode
, DOMString href
, bool _implicit
)
129 : StyleSheetImpl(parentNode
, href
)
131 m_lstChildren
= new QList
<StyleBaseImpl
*>;
132 m_doc
= parentNode
->document();
133 m_implicit
= _implicit
;
135 m_defaultNamespace
= NamespaceName::fromId(anyNamespace
);
136 m_loadedHint
= false;
139 CSSStyleSheetImpl::CSSStyleSheetImpl(CSSRuleImpl
*ownerRule
, DOMString href
)
140 : StyleSheetImpl(ownerRule
, href
)
142 m_lstChildren
= new QList
<StyleBaseImpl
*>;
143 m_doc
= static_cast<CSSStyleSheetImpl
*>(ownerRule
->stylesheet())->doc();
146 m_defaultNamespace
= NamespaceName::fromId(anyNamespace
);
147 m_loadedHint
= false;
150 CSSStyleSheetImpl::CSSStyleSheetImpl(DOM::NodeImpl
*parentNode
, CSSStyleSheetImpl
*orig
)
151 : StyleSheetImpl(parentNode
, orig
->m_strHref
)
153 m_lstChildren
= new QList
<StyleBaseImpl
*>;
155 QListIterator
<StyleBaseImpl
*> it( *orig
->m_lstChildren
);
156 while ( it
.hasNext() )
159 m_lstChildren
->append(rule
);
160 rule
->setParent(this);
162 m_doc
= parentNode
->document();
165 m_defaultNamespace
= NamespaceName::fromId(anyNamespace
);
166 m_loadedHint
= false;
169 CSSStyleSheetImpl::CSSStyleSheetImpl(CSSRuleImpl
*ownerRule
, CSSStyleSheetImpl
*orig
)
170 : StyleSheetImpl(ownerRule
, orig
->m_strHref
)
172 // m_lstChildren is deleted in StyleListImpl
173 m_lstChildren
= new QList
<StyleBaseImpl
*>;
175 QListIterator
<StyleBaseImpl
*> it( *orig
->m_lstChildren
);
176 while ( it
.hasNext() )
179 m_lstChildren
->append(rule
);
180 rule
->setParent(this);
182 m_doc
= static_cast<CSSStyleSheetImpl
*>(ownerRule
->stylesheet())->doc();
185 m_defaultNamespace
= NamespaceName::fromId(anyNamespace
);
186 m_loadedHint
= false;
189 CSSRuleImpl
*CSSStyleSheetImpl::ownerRule() const
191 if( !m_parent
) return 0;
192 if( m_parent
->isRule() ) return static_cast<CSSRuleImpl
*>(m_parent
);
196 unsigned long CSSStyleSheetImpl::insertRule( const DOMString
&rule
, unsigned long index
, int &exceptioncode
)
199 if (index
> (unsigned) m_lstChildren
->count()) {
200 exceptioncode
= DOMException::INDEX_SIZE_ERR
;
203 CSSParser
p( strictParsing
);
204 CSSRuleImpl
*r
= p
.parseRule( this, rule
);
207 exceptioncode
= CSSException::SYNTAX_ERR
+ CSSException::_EXCEPTION_OFFSET
;
211 // HIERARCHY_REQUEST_ERR: Raised if the rule cannot be inserted at the specified index e.g. if an
212 //@import rule is inserted after a standard rule set or other at-rule.
213 m_lstChildren
->insert(index
, r
);
215 m_doc
->updateStyleSelector(true /*shallow*/);
219 CSSRuleListImpl
*CSSStyleSheetImpl::cssRules(bool omitCharsetRules
)
221 return new CSSRuleListImpl(this, omitCharsetRules
);
224 void CSSStyleSheetImpl::deleteRule( unsigned long index
, int &exceptioncode
)
227 if (index
+1 > (unsigned) m_lstChildren
->count()) {
228 exceptioncode
= DOMException::INDEX_SIZE_ERR
;
231 StyleBaseImpl
*b
= m_lstChildren
->takeAt(index
);
233 // TreeShared requires delete not deref when removed from tree
235 if( !b
->refCount() ) delete b
;
237 m_doc
->updateStyleSelector(true /*shallow*/);
240 void CSSStyleSheetImpl::addNamespace(CSSParser
* /*p*/, const DOM::DOMString
& prefix
, const DOM::DOMString
& uri
)
245 m_namespaces
= new CSSNamespace(prefix
, uri
, m_namespaces
);
247 if (prefix
.isEmpty()) {
248 Q_ASSERT(m_doc
!= 0);
250 m_defaultNamespace
= NamespaceName::fromString(uri
);
254 void CSSStyleSheetImpl::determineNamespace(NamespaceName
& namespacename
, const DOM::DOMString
& prefix
)
256 // If the stylesheet has no namespaces we can just return. There won't be any need to ever check
257 // namespace values in selectors.
261 if (prefix
.isEmpty())
262 namespacename
= NamespaceName::fromId(emptyNamespace
); // No namespace. If an element/attribute has a namespace, we won't match it.
263 else if (prefix
== "*")
264 namespacename
= NamespaceName::fromId(anyNamespace
); // We'll match any namespace.
268 CSSNamespace
* ns
= m_namespaces
->namespaceForPrefix(prefix
);
270 Q_ASSERT(m_doc
!= 0);
272 // Look up the id for this namespace URI.
273 namespacename
= NamespaceName::fromString(ns
->uri());
278 bool CSSStyleSheetImpl::parseString(const DOMString
&string
, bool strict
)
280 #ifdef CSS_STYLESHEET_DEBUG
281 kDebug( 6080 ) << "parsing sheet, len=" << string
.length() << ", sheet is " << string
.string();
284 strictParsing
= strict
;
285 CSSParser
p( strict
);
286 p
.parseSheet( this, string
);
290 bool CSSStyleSheetImpl::isLoading() const
293 QListIterator
<StyleBaseImpl
*> it( *m_lstChildren
);
294 while ( it
.hasNext() )
297 if(rule
->isImportRule())
299 CSSImportRuleImpl
*import
= static_cast<CSSImportRuleImpl
*>(rule
);
300 #ifdef CSS_STYLESHEET_DEBUG
301 kDebug( 6080 ) << "found import";
303 if(import
->isLoading())
305 #ifdef CSS_STYLESHEET_DEBUG
306 kDebug( 6080 ) << "--> not loaded";
308 m_loadedHint
= false;
317 void CSSStyleSheetImpl::checkLoaded() const
322 m_parent
->checkLoaded();
324 m_loadedHint
= m_parentNode
->checkRemovePendingSheet();
325 else if (parentStyleSheet() && parentStyleSheet()->isCSSStyleSheet())
326 m_loadedHint
= static_cast<CSSStyleSheetImpl
*>(parentStyleSheet())->loadedHint();
331 void CSSStyleSheetImpl::checkPending() const
336 m_parent
->checkPending();
337 else if (m_parentNode
)
338 m_parentNode
->checkAddPendingSheet();
341 // ---------------------------------------------------------------------------
344 StyleSheetListImpl::~StyleSheetListImpl()
346 foreach (StyleSheetImpl
* sh
, styleSheets
)
350 void StyleSheetListImpl::add( StyleSheetImpl
* s
)
352 if ( !styleSheets
.contains( s
) ) {
354 styleSheets
.append( s
);
358 void StyleSheetListImpl::remove( StyleSheetImpl
* s
)
360 if ( styleSheets
.removeAll( s
) )
364 unsigned long StyleSheetListImpl::length() const
366 // hack so implicit BODY stylesheets don't get counted here
368 foreach (StyleSheetImpl
* sh
, styleSheets
) {
369 if (!sh
->isCSSStyleSheet() || !static_cast<CSSStyleSheetImpl
*>(sh
)->implicit())
375 StyleSheetImpl
*StyleSheetListImpl::item ( unsigned long index
)
378 foreach (StyleSheetImpl
* sh
, styleSheets
) {
379 if (!sh
->isCSSStyleSheet() || !static_cast<CSSStyleSheetImpl
*>(sh
)->implicit()) {
388 // --------------------------------------------------------------------------------------------
390 /* MediaList is used to store 3 types of media related entities which mean the same:
391 * Media Queries, Media Types and Media Descriptors.
392 * Currently MediaList always tries to parse media queries and if parsing fails,
393 * tries to fallback to Media Descriptors if m_fallback flag is set.
394 * Slight problem with syntax error handling:
395 * CSS 2.1 Spec (http://www.w3.org/TR/CSS21/media.html)
396 * specifies that failing media type parsing is a syntax error
397 * CSS 3 Media Queries Spec (http://www.w3.org/TR/css3-mediaqueries/)
398 * specifies that failing media query is a syntax error
399 * HTML 4.01 spec (http://www.w3.org/TR/REC-html40/present/styles.html#adef-media)
400 * specifies that Media Descriptors should be parsed with forward-compatible syntax
401 * DOM Level 2 Style Sheet spec (http://www.w3.org/TR/DOM-Level-2-Style/)
402 * talks about MediaList.mediaText and refers
403 * - to Media Descriptors of HTML 4.0 in context of StyleSheet
404 * - to Media Types of CSS 2.0 in context of CSSMediaRule and CSSImportRule
406 * These facts create situation where same (illegal) media specification may result in
407 * different parses depending on whether it is media attr of style element or part of
409 * <style media="screen and resolution > 40dpi"> ..</style> will be enabled on screen devices where as
410 * @media screen and resolution > 40dpi {..} will not.
411 * This gets more counter-intuitive in JavaScript:
412 * document.styleSheets[0].media.mediaText = "screen and resolution > 40dpi" will be ok and
414 * document.styleSheets[0].cssRules[0].media.mediaText = "screen and resolution > 40dpi" will
415 * throw SYNTAX_ERR exception.
418 MediaListImpl::MediaListImpl( CSSStyleSheetImpl
*parentSheet
,
419 const DOMString
&media
, bool fallbackToDescriptor
)
420 : StyleBaseImpl( parentSheet
)
421 , m_fallback(fallbackToDescriptor
)
424 setMediaText(media
, ec
);
425 // FIXME: parsing can fail. The problem with failing constructor is that
426 // we would need additional flag saying MediaList is not valid
427 // Parse can fail only when fallbackToDescriptor == false, i.e when HTML4 media descriptor
428 // forward-compatible syntax is not in use.
429 // DOMImplementationCSS seems to mandate that media descriptors are used
430 // for both html and svg, even though svg:style doesn't use media descriptors
431 // Currently the only places where parsing can fail are
432 // creating <svg:style>, creating css media / import rules from js
434 setMediaText("invalid", ec
);
437 MediaListImpl::MediaListImpl( CSSRuleImpl
*parentRule
, const DOMString
&media
, bool fallbackToDescriptor
)
438 : StyleBaseImpl(parentRule
)
439 , m_fallback(fallbackToDescriptor
)
442 setMediaText(media
, ec
);
443 // FIXME: parsing can fail. The problem with failing constructor is that
444 // we would need additional flag saying MediaList is not valid
445 // Parse can fail only when fallbackToDescriptor == false, i.e when HTML4 media descriptor
446 // forward-compatible syntax is not in use.
447 // DOMImplementationCSS seems to mandate that media descriptors are used
448 // for both html and svg, even though svg:style doesn't use media descriptors
449 // Currently the only places where parsing can fail are
450 // creating <svg:style>, creating css media / import rules from js
452 setMediaText("invalid", ec
);
455 MediaListImpl::~MediaListImpl()
457 qDeleteAll(m_queries
);
460 CSSStyleSheetImpl
*MediaListImpl::parentStyleSheet() const
462 if( m_parent
->isCSSStyleSheet() ) return static_cast<CSSStyleSheetImpl
*>(m_parent
);
466 CSSRuleImpl
*MediaListImpl::parentRule() const
468 if( m_parent
->isRule() ) return static_cast<CSSRuleImpl
*>(m_parent
);
472 static DOMString
parseMediaDescriptor(const DOMString
& s
)
474 int len
= s
.length();
476 // http://www.w3.org/TR/REC-html40/types.html#type-media-descriptors
477 // "Each entry is truncated just before the first character that isn't a
478 // US ASCII letter [a-zA-Z] (ISO 10646 hex 41-5a, 61-7a), digit [0-9] (hex 30-39),
479 // or hyphen (hex 2d)."
482 for (i
= 0; i
< len
; ++i
) {
484 if (! ((c
>= 'a' && c
<= 'z')
485 || (c
>= 'A' && c
<= 'Z')
486 || (c
>= '1' && c
<= '9')
490 return s
.implementation()->substring(0, len
);
493 void MediaListImpl::deleteMedium(const DOMString
& oldMedium
, int& ec
)
495 MediaListImpl tempMediaList
;
498 MediaQuery
* oldQuery
= 0;
499 bool deleteOldQuery
= false;
501 if (p
.parseMediaQuery(&tempMediaList
, oldMedium
)) {
502 if (tempMediaList
.m_queries
.size() > 0)
503 oldQuery
= tempMediaList
.m_queries
[0];
504 } else if (m_fallback
) {
505 DOMString medium
= parseMediaDescriptor(oldMedium
);
506 if (!medium
.isNull()) {
507 oldQuery
= new MediaQuery(MediaQuery::None
, medium
, 0);
508 deleteOldQuery
= true;
512 // DOM Style Sheets spec doesn't allow SYNTAX_ERR to be thrown in deleteMedium
513 ec
= DOMException::NOT_FOUND_ERR
;
516 for(int i
= 0; i
< m_queries
.size(); ++i
) {
517 MediaQuery
* a
= m_queries
[i
];
518 if (*a
== *oldQuery
) {
519 m_queries
.removeAt(i
);
530 DOM::DOMString
MediaListImpl::mediaText() const
534 const QList
<MediaQuery
*>::ConstIterator itEnd
= m_queries
.end();
536 for ( QList
<MediaQuery
*>::ConstIterator it
= m_queries
.begin(); it
!= itEnd
; ++it
) {
539 text
+= (*it
)->cssText();
545 void MediaListImpl::setMediaText(const DOM::DOMString
&value
, int& ec
)
547 MediaListImpl tempMediaList
;
550 const QString val
= value
.string();
551 const QStringList list
= val
.split( ',' );
553 const QStringList::ConstIterator itEnd
= list
.end();
555 for ( QStringList::ConstIterator it
= list
.begin(); it
!= itEnd
; ++it
)
557 const DOMString medium
= (*it
).trimmed();
558 if( !medium
.isEmpty() ) {
559 if (!p
.parseMediaQuery(&tempMediaList
, medium
)) {
561 DOMString mediaDescriptor
= parseMediaDescriptor(medium
);
562 if (!mediaDescriptor
.isNull())
563 tempMediaList
.m_queries
.append(new MediaQuery(MediaQuery::None
, mediaDescriptor
, 0));
565 ec
= CSSException::SYNTAX_ERR
;
569 } else if (!m_fallback
) {
570 ec
= CSSException::SYNTAX_ERR
;
574 // ",,,," falls straight through, but is not valid unless fallback
575 if (!m_fallback
&& list
.begin() == list
.end()) {
576 DOMString s
= value
.string().trimmed();
578 ec
= CSSException::SYNTAX_ERR
;
584 qDeleteAll(m_queries
);
585 m_queries
= tempMediaList
.m_queries
;
586 tempMediaList
.m_queries
.clear();
590 DOMString
MediaListImpl::item(unsigned long index
) const
592 if (index
< (unsigned)m_queries
.size()) {
593 MediaQuery
* query
= m_queries
[index
];
594 return query
->cssText();
600 void MediaListImpl::appendMedium(const DOMString
& newMedium
, int& ec
)
602 ec
= DOMException::INVALID_CHARACTER_ERR
;
604 if (p
.parseMediaQuery(this, newMedium
)) {
606 } else if (m_fallback
) {
607 DOMString medium
= parseMediaDescriptor(newMedium
);
608 if (!medium
.isNull()) {
609 m_queries
.append(new MediaQuery(MediaQuery::None
, medium
, 0));
615 void MediaListImpl::appendMediaQuery(MediaQuery
* mediaQuery
)
617 m_queries
.append(mediaQuery
);