1/*
2 * Copyright (C) 2000 Peter Kelly (pmk@post.com)
3 * Copyright (C) 2006, 2008, 2009 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB.  If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "core/dom/ProcessingInstruction.h"
23
24#include "core/FetchInitiatorTypeNames.h"
25#include "core/css/CSSStyleSheet.h"
26#include "core/css/MediaList.h"
27#include "core/css/StyleSheetContents.h"
28#include "core/dom/Document.h"
29#include "core/dom/StyleEngine.h"
30#include "core/fetch/CSSStyleSheetResource.h"
31#include "core/fetch/FetchRequest.h"
32#include "core/fetch/ResourceFetcher.h"
33#include "core/fetch/XSLStyleSheetResource.h"
34#include "core/xml/XSLStyleSheet.h"
35#include "core/xml/parser/XMLDocumentParser.h" // for parseAttributes()
36
37namespace blink {
38
39inline ProcessingInstruction::ProcessingInstruction(Document& document, const String& target, const String& data)
40    : CharacterData(document, data, CreateOther)
41    , m_target(target)
42    , m_loading(false)
43    , m_alternate(false)
44    , m_createdByParser(false)
45    , m_isCSS(false)
46    , m_isXSL(false)
47{
48}
49
50PassRefPtrWillBeRawPtr<ProcessingInstruction> ProcessingInstruction::create(Document& document, const String& target, const String& data)
51{
52    return adoptRefWillBeNoop(new ProcessingInstruction(document, target, data));
53}
54
55ProcessingInstruction::~ProcessingInstruction()
56{
57#if !ENABLE(OILPAN)
58    if (m_sheet)
59        clearSheet();
60
61    // FIXME: ProcessingInstruction should not be in document here.
62    // However, if we add ASSERT(!inDocument()), fast/xsl/xslt-entity.xml
63    // crashes. We need to investigate ProcessingInstruction lifetime.
64    if (inDocument()) {
65        if (m_isCSS)
66            document().styleEngine()->removeStyleSheetCandidateNode(this);
67        else if (m_isXSL)
68            document().styleEngine()->removeXSLStyleSheet(this);
69    }
70#endif
71}
72
73String ProcessingInstruction::nodeName() const
74{
75    return m_target;
76}
77
78Node::NodeType ProcessingInstruction::nodeType() const
79{
80    return PROCESSING_INSTRUCTION_NODE;
81}
82
83PassRefPtrWillBeRawPtr<Node> ProcessingInstruction::cloneNode(bool /*deep*/)
84{
85    // FIXME: Is it a problem that this does not copy m_localHref?
86    // What about other data members?
87    return create(document(), m_target, m_data);
88}
89
90void ProcessingInstruction::didAttributeChanged()
91{
92    if (m_sheet)
93        clearSheet();
94
95    String href;
96    String charset;
97    if (!checkStyleSheet(href, charset))
98        return;
99    process(href, charset);
100}
101
102bool ProcessingInstruction::checkStyleSheet(String& href, String& charset)
103{
104    if (m_target != "xml-stylesheet" || !document().frame() || parentNode() != document())
105        return false;
106
107    // see http://www.w3.org/TR/xml-stylesheet/
108    // ### support stylesheet included in a fragment of this (or another) document
109    // ### make sure this gets called when adding from javascript
110    bool attrsOk;
111    const HashMap<String, String> attrs = parseAttributes(m_data, attrsOk);
112    if (!attrsOk)
113        return false;
114    HashMap<String, String>::const_iterator i = attrs.find("type");
115    String type;
116    if (i != attrs.end())
117        type = i->value;
118
119    m_isCSS = type.isEmpty() || type == "text/css";
120    m_isXSL = (type == "text/xml" || type == "text/xsl" || type == "application/xml" || type == "application/xhtml+xml" || type == "application/rss+xml" || type == "application/atom+xml");
121    if (!m_isCSS && !m_isXSL)
122        return false;
123
124    href = attrs.get("href");
125    charset = attrs.get("charset");
126    String alternate = attrs.get("alternate");
127    m_alternate = alternate == "yes";
128    m_title = attrs.get("title");
129    m_media = attrs.get("media");
130
131    return !m_alternate || !m_title.isEmpty();
132}
133
134void ProcessingInstruction::process(const String& href, const String& charset)
135{
136    if (href.length() > 1 && href[0] == '#') {
137        m_localHref = href.substring(1);
138        // We need to make a synthetic XSLStyleSheet that is embedded.
139        // It needs to be able to kick off import/include loads that
140        // can hang off some parent sheet.
141        if (m_isXSL) {
142            KURL finalURL(ParsedURLString, m_localHref);
143            m_sheet = XSLStyleSheet::createEmbedded(this, finalURL);
144            m_loading = false;
145        }
146        return;
147    }
148
149    clearResource();
150
151    String url = document().completeURL(href).string();
152
153    ResourcePtr<StyleSheetResource> resource;
154    FetchRequest request(ResourceRequest(document().completeURL(href)), FetchInitiatorTypeNames::processinginstruction);
155    if (m_isXSL) {
156        resource = document().fetcher()->fetchXSLStyleSheet(request);
157    } else {
158        request.setCharset(charset.isEmpty() ? document().charset() : charset);
159        resource = document().fetcher()->fetchCSSStyleSheet(request);
160    }
161
162    if (resource) {
163        m_loading = true;
164        document().styleEngine()->addPendingSheet();
165        setResource(resource);
166    }
167}
168
169bool ProcessingInstruction::isLoading() const
170{
171    if (m_loading)
172        return true;
173    if (!m_sheet)
174        return false;
175    return m_sheet->isLoading();
176}
177
178bool ProcessingInstruction::sheetLoaded()
179{
180    if (!isLoading()) {
181        document().styleEngine()->removePendingSheet(this);
182        return true;
183    }
184    return false;
185}
186
187void ProcessingInstruction::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CSSStyleSheetResource* sheet)
188{
189    if (!inDocument()) {
190        ASSERT(!m_sheet);
191        return;
192    }
193
194    ASSERT(m_isCSS);
195    CSSParserContext parserContext(document(), 0, baseURL, charset);
196
197    RefPtrWillBeRawPtr<StyleSheetContents> newSheet = StyleSheetContents::create(href, parserContext);
198
199    RefPtrWillBeRawPtr<CSSStyleSheet> cssSheet = CSSStyleSheet::create(newSheet, this);
200    cssSheet->setDisabled(m_alternate);
201    cssSheet->setTitle(m_title);
202    cssSheet->setMediaQueries(MediaQuerySet::create(m_media));
203
204    m_sheet = cssSheet.release();
205
206    // We don't need the cross-origin security check here because we are
207    // getting the sheet text in "strict" mode. This enforces a valid CSS MIME
208    // type.
209    parseStyleSheet(sheet->sheetText(true));
210}
211
212void ProcessingInstruction::setXSLStyleSheet(const String& href, const KURL& baseURL, const String& sheet)
213{
214    if (!inDocument()) {
215        ASSERT(!m_sheet);
216        return;
217    }
218
219    ASSERT(m_isXSL);
220    m_sheet = XSLStyleSheet::create(this, href, baseURL);
221    RefPtrWillBeRawPtr<Document> protect(&document());
222    parseStyleSheet(sheet);
223}
224
225void ProcessingInstruction::parseStyleSheet(const String& sheet)
226{
227    if (m_isCSS)
228        toCSSStyleSheet(m_sheet.get())->contents()->parseString(sheet);
229    else if (m_isXSL)
230        toXSLStyleSheet(m_sheet.get())->parseString(sheet);
231
232    clearResource();
233    m_loading = false;
234
235    if (m_isCSS)
236        toCSSStyleSheet(m_sheet.get())->contents()->checkLoaded();
237    else if (m_isXSL)
238        toXSLStyleSheet(m_sheet.get())->checkLoaded();
239}
240
241void ProcessingInstruction::setCSSStyleSheet(PassRefPtrWillBeRawPtr<CSSStyleSheet> sheet)
242{
243    ASSERT(!resource());
244    ASSERT(!m_loading);
245    m_sheet = sheet;
246    sheet->setTitle(m_title);
247    sheet->setDisabled(m_alternate);
248}
249
250Node::InsertionNotificationRequest ProcessingInstruction::insertedInto(ContainerNode* insertionPoint)
251{
252    CharacterData::insertedInto(insertionPoint);
253    if (!insertionPoint->inDocument())
254        return InsertionDone;
255
256    String href;
257    String charset;
258    bool isValid = checkStyleSheet(href, charset);
259    if (m_isCSS)
260        document().styleEngine()->addStyleSheetCandidateNode(this, m_createdByParser);
261    else if (m_isXSL)
262        document().styleEngine()->addXSLStyleSheet(this, m_createdByParser);
263    if (isValid)
264        process(href, charset);
265    return InsertionDone;
266}
267
268void ProcessingInstruction::removedFrom(ContainerNode* insertionPoint)
269{
270    CharacterData::removedFrom(insertionPoint);
271    if (!insertionPoint->inDocument())
272        return;
273
274    if (m_isCSS)
275        document().styleEngine()->removeStyleSheetCandidateNode(this);
276    else if (m_isXSL)
277        document().styleEngine()->removeXSLStyleSheet(this);
278
279    RefPtrWillBeRawPtr<StyleSheet> removedSheet = m_sheet;
280
281    if (m_sheet) {
282        ASSERT(m_sheet->ownerNode() == this);
283        clearSheet();
284    }
285
286    // If we're in document teardown, then we don't need to do any notification of our sheet's removal.
287    if (document().isActive())
288        document().removedStyleSheet(removedSheet.get());
289}
290
291void ProcessingInstruction::clearSheet()
292{
293    ASSERT(m_sheet);
294    if (m_sheet->isLoading())
295        document().styleEngine()->removePendingSheet(this);
296    m_sheet.release()->clearOwnerNode();
297}
298
299void ProcessingInstruction::trace(Visitor* visitor)
300{
301    visitor->trace(m_sheet);
302    CharacterData::trace(visitor);
303}
304
305} // namespace
306