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 "ProcessingInstruction.h"
23
24#include "CSSStyleSheet.h"
25#include "CachedCSSStyleSheet.h"
26#include "CachedXSLStyleSheet.h"
27#include "Document.h"
28#include "CachedResourceLoader.h"
29#include "ExceptionCode.h"
30#include "Frame.h"
31#include "FrameLoader.h"
32#include "XSLStyleSheet.h"
33#include "XMLDocumentParser.h" // for parseAttributes()
34#include "MediaList.h"
35
36namespace WebCore {
37
38inline ProcessingInstruction::ProcessingInstruction(Document* document, const String& target, const String& data)
39    : ContainerNode(document)
40    , m_target(target)
41    , m_data(data)
42    , m_cachedSheet(0)
43    , m_loading(false)
44    , m_alternate(false)
45    , m_createdByParser(false)
46    , m_isCSS(false)
47#if ENABLE(XSLT)
48    , m_isXSL(false)
49#endif
50{
51}
52
53PassRefPtr<ProcessingInstruction> ProcessingInstruction::create(Document* document, const String& target, const String& data)
54{
55    return adoptRef(new ProcessingInstruction(document, target, data));
56}
57
58ProcessingInstruction::~ProcessingInstruction()
59{
60    if (m_sheet)
61        m_sheet->clearOwnerNode();
62
63    if (m_cachedSheet)
64        m_cachedSheet->removeClient(this);
65}
66
67void ProcessingInstruction::setData(const String& data, ExceptionCode&)
68{
69    int oldLength = m_data.length();
70    m_data = data;
71    document()->textRemoved(this, 0, oldLength);
72    checkStyleSheet();
73}
74
75String ProcessingInstruction::nodeName() const
76{
77    return m_target;
78}
79
80Node::NodeType ProcessingInstruction::nodeType() const
81{
82    return PROCESSING_INSTRUCTION_NODE;
83}
84
85String ProcessingInstruction::nodeValue() const
86{
87    return m_data;
88}
89
90void ProcessingInstruction::setNodeValue(const String& nodeValue, ExceptionCode& ec)
91{
92    // NO_MODIFICATION_ALLOWED_ERR: taken care of by setData()
93    setData(nodeValue, ec);
94}
95
96PassRefPtr<Node> ProcessingInstruction::cloneNode(bool /*deep*/)
97{
98    // FIXME: Is it a problem that this does not copy m_localHref?
99    // What about other data members?
100    return create(document(), m_target, m_data);
101}
102
103// DOM Section 1.1.1
104bool ProcessingInstruction::childTypeAllowed(NodeType) const
105{
106    return false;
107}
108
109void ProcessingInstruction::checkStyleSheet()
110{
111    if (m_target == "xml-stylesheet" && document()->frame() && parentNode() == document()) {
112        // see http://www.w3.org/TR/xml-stylesheet/
113        // ### support stylesheet included in a fragment of this (or another) document
114        // ### make sure this gets called when adding from javascript
115        bool attrsOk;
116        const HashMap<String, String> attrs = parseAttributes(m_data, attrsOk);
117        if (!attrsOk)
118            return;
119        HashMap<String, String>::const_iterator i = attrs.find("type");
120        String type;
121        if (i != attrs.end())
122            type = i->second;
123
124        m_isCSS = type.isEmpty() || type == "text/css";
125#if ENABLE(XSLT)
126        m_isXSL = (type == "text/xml" || type == "text/xsl" || type == "application/xml" ||
127                   type == "application/xhtml+xml" || type == "application/rss+xml" || type == "application/atom+xml");
128        if (!m_isCSS && !m_isXSL)
129#else
130        if (!m_isCSS)
131#endif
132            return;
133
134        String href = attrs.get("href");
135        String alternate = attrs.get("alternate");
136        m_alternate = alternate == "yes";
137        m_title = attrs.get("title");
138        m_media = attrs.get("media");
139
140        if (href.length() > 1 && href[0] == '#') {
141            m_localHref = href.substring(1);
142#if ENABLE(XSLT)
143            // We need to make a synthetic XSLStyleSheet that is embedded.  It needs to be able
144            // to kick off import/include loads that can hang off some parent sheet.
145            if (m_isXSL) {
146                KURL finalURL(ParsedURLString, m_localHref);
147                m_sheet = XSLStyleSheet::createEmbedded(this, finalURL);
148                m_loading = false;
149            }
150#endif
151        } else {
152            if (m_cachedSheet) {
153                m_cachedSheet->removeClient(this);
154                m_cachedSheet = 0;
155            }
156
157            String url = document()->completeURL(href).string();
158            if (!dispatchBeforeLoadEvent(url))
159                return;
160
161            m_loading = true;
162            document()->addPendingSheet();
163
164#if ENABLE(XSLT)
165            if (m_isXSL)
166                m_cachedSheet = document()->cachedResourceLoader()->requestXSLStyleSheet(url);
167            else
168#endif
169            {
170                String charset = attrs.get("charset");
171                if (charset.isEmpty())
172                    charset = document()->charset();
173
174                m_cachedSheet = document()->cachedResourceLoader()->requestCSSStyleSheet(url, charset);
175            }
176            if (m_cachedSheet)
177                m_cachedSheet->addClient(this);
178            else {
179                // The request may have been denied if (for example) the stylesheet is local and the document is remote.
180                m_loading = false;
181                document()->removePendingSheet();
182            }
183        }
184    }
185}
186
187bool ProcessingInstruction::isLoading() const
188{
189    if (m_loading)
190        return true;
191    if (!m_sheet)
192        return false;
193    return m_sheet->isLoading();
194}
195
196bool ProcessingInstruction::sheetLoaded()
197{
198    if (!isLoading()) {
199        document()->removePendingSheet();
200        return true;
201    }
202    return false;
203}
204
205void ProcessingInstruction::setCSSStyleSheet(const String& href, const KURL& baseURL, const String& charset, const CachedCSSStyleSheet* sheet)
206{
207    if (!inDocument()) {
208        ASSERT(!m_sheet);
209        return;
210    }
211
212    ASSERT(m_isCSS);
213    RefPtr<CSSStyleSheet> newSheet = CSSStyleSheet::create(this, href, baseURL, charset);
214    m_sheet = newSheet;
215    // We don't need the cross-origin security check here because we are
216    // getting the sheet text in "strict" mode. This enforces a valid CSS MIME
217    // type.
218    parseStyleSheet(sheet->sheetText(true));
219    newSheet->setTitle(m_title);
220    newSheet->setMedia(MediaList::create(newSheet.get(), m_media));
221    newSheet->setDisabled(m_alternate);
222}
223
224#if ENABLE(XSLT)
225void ProcessingInstruction::setXSLStyleSheet(const String& href, const KURL& baseURL, const String& sheet)
226{
227    ASSERT(m_isXSL);
228    m_sheet = XSLStyleSheet::create(this, href, baseURL);
229    parseStyleSheet(sheet);
230}
231#endif
232
233void ProcessingInstruction::parseStyleSheet(const String& sheet)
234{
235    m_sheet->parseString(sheet, true);
236    if (m_cachedSheet)
237        m_cachedSheet->removeClient(this);
238    m_cachedSheet = 0;
239
240    m_loading = false;
241    m_sheet->checkLoaded();
242}
243
244void ProcessingInstruction::setCSSStyleSheet(PassRefPtr<CSSStyleSheet> sheet)
245{
246    ASSERT(!m_cachedSheet);
247    ASSERT(!m_loading);
248    m_sheet = sheet;
249    m_sheet->setTitle(m_title);
250    m_sheet->setDisabled(m_alternate);
251}
252
253bool ProcessingInstruction::offsetInCharacters() const
254{
255    return true;
256}
257
258int ProcessingInstruction::maxCharacterOffset() const
259{
260    return static_cast<int>(m_data.length());
261}
262
263void ProcessingInstruction::addSubresourceAttributeURLs(ListHashSet<KURL>& urls) const
264{
265    if (!sheet())
266        return;
267
268    addSubresourceURL(urls, sheet()->baseURL());
269}
270
271void ProcessingInstruction::insertedIntoDocument()
272{
273    ContainerNode::insertedIntoDocument();
274    document()->addStyleSheetCandidateNode(this, m_createdByParser);
275    checkStyleSheet();
276}
277
278void ProcessingInstruction::removedFromDocument()
279{
280    ContainerNode::removedFromDocument();
281
282    document()->removeStyleSheetCandidateNode(this);
283
284    if (m_sheet) {
285        ASSERT(m_sheet->ownerNode() == this);
286        m_sheet->clearOwnerNode();
287        m_sheet = 0;
288    }
289
290    if (m_cachedSheet)
291        document()->styleSelectorChanged(DeferRecalcStyle);
292}
293
294void ProcessingInstruction::finishParsingChildren()
295{
296    m_createdByParser = false;
297    ContainerNode::finishParsingChildren();
298}
299
300} // namespace
301