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