1/*
2 * Copyright (C) 2000 Peter Kelly (pmk@post.com)
3 * Copyright (C) 2006-2018 Apple Inc. All rights reserved.
4 * Copyright (C) 2013 Samsung Electronics. All rights reserved.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "ProcessingInstruction.h"
24
25#include "CSSStyleSheet.h"
26#include "CachedCSSStyleSheet.h"
27#include "CachedResourceLoader.h"
28#include "CachedResourceRequest.h"
29#include "CachedXSLStyleSheet.h"
30#include "Document.h"
31#include "Frame.h"
32#include "FrameLoader.h"
33#include "MediaList.h"
34#include "MediaQueryParser.h"
35#include "StyleScope.h"
36#include "StyleSheetContents.h"
37#include "XMLDocumentParser.h"
38#include "XSLStyleSheet.h"
39#include <wtf/IsoMallocInlines.h>
40#include <wtf/SetForScope.h>
41
42namespace WebCore {
43
44WTF_MAKE_ISO_ALLOCATED_IMPL(ProcessingInstruction);
45
46inline ProcessingInstruction::ProcessingInstruction(Document& document, const String& target, const String& data)
47 : CharacterData(document, data, CreateOther)
48 , m_target(target)
49{
50}
51
52Ref<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_sheet)
60 m_sheet->clearOwnerNode();
61
62 if (m_cachedSheet)
63 m_cachedSheet->removeClient(*this);
64
65 if (isConnected())
66 document().styleScope().removeStyleSheetCandidateNode(*this);
67}
68
69String ProcessingInstruction::nodeName() const
70{
71 return m_target;
72}
73
74Node::NodeType ProcessingInstruction::nodeType() const
75{
76 return PROCESSING_INSTRUCTION_NODE;
77}
78
79Ref<Node> ProcessingInstruction::cloneNodeInternal(Document& targetDocument, CloningOperation)
80{
81 // FIXME: Is it a problem that this does not copy m_localHref?
82 // What about other data members?
83 return create(targetDocument, m_target, data());
84}
85
86void ProcessingInstruction::checkStyleSheet()
87{
88 // Prevent recursive loading of stylesheet.
89 if (m_isHandlingBeforeLoad)
90 return;
91
92 if (m_target == "xml-stylesheet" && document().frame() && parentNode() == &document()) {
93 // see http://www.w3.org/TR/xml-stylesheet/
94 // ### support stylesheet included in a fragment of this (or another) document
95 // ### make sure this gets called when adding from javascript
96 auto attributes = parseAttributes(data());
97 if (!attributes)
98 return;
99 String type = attributes->get("type");
100
101 m_isCSS = type.isEmpty() || type == "text/css";
102#if ENABLE(XSLT)
103 m_isXSL = type == "text/xml" || type == "text/xsl" || type == "application/xml" || type == "application/xhtml+xml" || type == "application/rss+xml" || type == "application/atom+xml";
104 if (!m_isCSS && !m_isXSL)
105#else
106 if (!m_isCSS)
107#endif
108 return;
109
110 String href = attributes->get("href");
111 String alternate = attributes->get("alternate");
112 m_alternate = alternate == "yes";
113 m_title = attributes->get("title");
114 m_media = attributes->get("media");
115
116 if (m_alternate && m_title.isEmpty())
117 return;
118
119 if (href.length() > 1 && href[0] == '#') {
120 m_localHref = href.substring(1);
121#if ENABLE(XSLT)
122 // We need to make a synthetic XSLStyleSheet that is embedded. It needs to be able
123 // to kick off import/include loads that can hang off some parent sheet.
124 if (m_isXSL) {
125 URL finalURL({ }, m_localHref);
126 m_sheet = XSLStyleSheet::createEmbedded(this, finalURL);
127 m_loading = false;
128 document().scheduleToApplyXSLTransforms();
129 }
130#endif
131 } else {
132 if (m_cachedSheet) {
133 m_cachedSheet->removeClient(*this);
134 m_cachedSheet = nullptr;
135 }
136
137 if (m_loading) {
138 m_loading = false;
139 document().styleScope().removePendingSheet(*this);
140 }
141
142 Ref<Document> originalDocument = document();
143
144 String url = document().completeURL(href).string();
145
146 {
147 SetForScope<bool> change(m_isHandlingBeforeLoad, true);
148 if (!dispatchBeforeLoadEvent(url))
149 return;
150 }
151
152 bool didEventListenerDisconnectThisElement = !isConnected() || &document() != originalDocument.ptr();
153 if (didEventListenerDisconnectThisElement)
154 return;
155
156 m_loading = true;
157 document().styleScope().addPendingSheet(*this);
158
159 ASSERT_WITH_SECURITY_IMPLICATION(!m_cachedSheet);
160
161#if ENABLE(XSLT)
162 if (m_isXSL) {
163 auto options = CachedResourceLoader::defaultCachedResourceOptions();
164 options.mode = FetchOptions::Mode::SameOrigin;
165 m_cachedSheet = document().cachedResourceLoader().requestXSLStyleSheet({ResourceRequest(document().completeURL(href)), options}).value_or(nullptr);
166 } else
167#endif
168 {
169 String charset = attributes->get("charset");
170 CachedResourceRequest request(document().completeURL(href), CachedResourceLoader::defaultCachedResourceOptions(), WTF::nullopt, charset.isEmpty() ? document().charset() : WTFMove(charset));
171
172 m_cachedSheet = document().cachedResourceLoader().requestCSSStyleSheet(WTFMove(request)).value_or(nullptr);
173 }
174 if (m_cachedSheet)
175 m_cachedSheet->addClient(*this);
176 else {
177 // The request may have been denied if (for example) the stylesheet is local and the document is remote.
178 m_loading = false;
179 document().styleScope().removePendingSheet(*this);
180#if ENABLE(XSLT)
181 if (m_isXSL)
182 document().scheduleToApplyXSLTransforms();
183#endif
184 }
185 }
186 }
187}
188
189bool ProcessingInstruction::isLoading() const
190{
191 if (m_loading)
192 return true;
193 if (!m_sheet)
194 return false;
195 return m_sheet->isLoading();
196}
197
198bool ProcessingInstruction::sheetLoaded()
199{
200 if (!isLoading()) {
201 if (document().styleScope().hasPendingSheet(*this))
202 document().styleScope().removePendingSheet(*this);
203#if ENABLE(XSLT)
204 if (m_isXSL)
205 document().scheduleToApplyXSLTransforms();
206#endif
207 return true;
208 }
209 return false;
210}
211
212void ProcessingInstruction::setCSSStyleSheet(const String& href, const URL& baseURL, const String& charset, const CachedCSSStyleSheet* sheet)
213{
214 if (!isConnected()) {
215 ASSERT(!m_sheet);
216 return;
217 }
218
219 ASSERT(m_isCSS);
220 CSSParserContext parserContext(document(), baseURL, charset);
221
222 auto cssSheet = CSSStyleSheet::create(StyleSheetContents::create(href, parserContext), *this);
223 cssSheet.get().setDisabled(m_alternate);
224 cssSheet.get().setTitle(m_title);
225 cssSheet.get().setMediaQueries(MediaQuerySet::create(m_media, MediaQueryParserContext(document())));
226
227 m_sheet = WTFMove(cssSheet);
228
229 // We don't need the cross-origin security check here because we are
230 // getting the sheet text in "strict" mode. This enforces a valid CSS MIME
231 // type.
232 Ref<Document> protect(document());
233 parseStyleSheet(sheet->sheetText());
234}
235
236#if ENABLE(XSLT)
237void ProcessingInstruction::setXSLStyleSheet(const String& href, const URL& baseURL, const String& sheet)
238{
239 ASSERT(m_isXSL);
240 m_sheet = XSLStyleSheet::create(this, href, baseURL);
241 Ref<Document> protect(document());
242 parseStyleSheet(sheet);
243}
244#endif
245
246void ProcessingInstruction::parseStyleSheet(const String& sheet)
247{
248 if (m_isCSS)
249 downcast<CSSStyleSheet>(*m_sheet).contents().parseString(sheet);
250#if ENABLE(XSLT)
251 else if (m_isXSL)
252 downcast<XSLStyleSheet>(*m_sheet).parseString(sheet);
253#endif
254
255 if (m_cachedSheet)
256 m_cachedSheet->removeClient(*this);
257 m_cachedSheet = nullptr;
258
259 m_loading = false;
260
261 if (m_isCSS)
262 downcast<CSSStyleSheet>(*m_sheet).contents().checkLoaded();
263#if ENABLE(XSLT)
264 else if (m_isXSL)
265 downcast<XSLStyleSheet>(*m_sheet).checkLoaded();
266#endif
267}
268
269void ProcessingInstruction::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
270{
271 if (!sheet())
272 return;
273
274 addSubresourceURL(urls, sheet()->baseURL());
275}
276
277Node::InsertedIntoAncestorResult ProcessingInstruction::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree)
278{
279 CharacterData::insertedIntoAncestor(insertionType, parentOfInsertedTree);
280 if (!insertionType.connectedToDocument)
281 return InsertedIntoAncestorResult::Done;
282 document().styleScope().addStyleSheetCandidateNode(*this, m_createdByParser);
283 return InsertedIntoAncestorResult::NeedsPostInsertionCallback;
284}
285
286void ProcessingInstruction::didFinishInsertingNode()
287{
288 checkStyleSheet();
289}
290
291void ProcessingInstruction::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree)
292{
293 CharacterData::removedFromAncestor(removalType, oldParentOfRemovedTree);
294 if (!removalType.disconnectedFromDocument)
295 return;
296
297 document().styleScope().removeStyleSheetCandidateNode(*this);
298
299 if (m_sheet) {
300 ASSERT(m_sheet->ownerNode() == this);
301 m_sheet->clearOwnerNode();
302 m_sheet = nullptr;
303 }
304
305 if (m_loading) {
306 m_loading = false;
307 document().styleScope().removePendingSheet(*this);
308 }
309
310 document().styleScope().didChangeActiveStyleSheetCandidates();
311}
312
313void ProcessingInstruction::finishParsingChildren()
314{
315 m_createdByParser = false;
316 CharacterData::finishParsingChildren();
317}
318
319} // namespace
320