1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "core/html/imports/HTMLImportLoader.h"
33
34#include "core/dom/Document.h"
35#include "core/dom/DocumentParser.h"
36#include "core/dom/StyleEngine.h"
37#include "core/dom/custom/CustomElementSyncMicrotaskQueue.h"
38#include "core/html/HTMLDocument.h"
39#include "core/html/imports/HTMLImportChild.h"
40#include "core/html/imports/HTMLImportsController.h"
41#include "core/loader/DocumentWriter.h"
42#include "platform/network/ContentSecurityPolicyResponseHeaders.h"
43
44
45namespace blink {
46
47HTMLImportLoader::HTMLImportLoader(HTMLImportsController* controller)
48    : m_controller(controller)
49    , m_state(StateLoading)
50    , m_microtaskQueue(CustomElementSyncMicrotaskQueue::create())
51{
52}
53
54HTMLImportLoader::~HTMLImportLoader()
55{
56#if !ENABLE(OILPAN)
57    clear();
58#endif
59}
60
61#if !ENABLE(OILPAN)
62void HTMLImportLoader::importDestroyed()
63{
64    clear();
65}
66
67void HTMLImportLoader::clear()
68{
69    m_controller = nullptr;
70    if (m_document) {
71        m_document->setImportsController(0);
72        m_document->cancelParsing();
73        m_document.clear();
74    }
75}
76#endif
77
78void HTMLImportLoader::startLoading(const ResourcePtr<RawResource>& resource)
79{
80    setResource(resource);
81}
82
83void HTMLImportLoader::responseReceived(Resource* resource, const ResourceResponse& response)
84{
85    // Resource may already have been loaded with the import loader
86    // being added as a client later & now being notified. Fail early.
87    if (resource->loadFailedOrCanceled() || response.httpStatusCode() >= 400) {
88        setState(StateError);
89        return;
90    }
91    setState(startWritingAndParsing(response));
92}
93
94void HTMLImportLoader::dataReceived(Resource*, const char* data, int length)
95{
96    RefPtrWillBeRawPtr<DocumentWriter> protectingWriter(m_writer.get());
97    m_writer->addData(data, length);
98}
99
100void HTMLImportLoader::notifyFinished(Resource* resource)
101{
102    // The writer instance indicates that a part of the document can be already loaded.
103    // We don't take such a case as an error because the partially-loaded document has been visible from script at this point.
104    if (resource->loadFailedOrCanceled() && !m_writer) {
105        setState(StateError);
106        return;
107    }
108
109    setState(finishWriting());
110}
111
112HTMLImportLoader::State HTMLImportLoader::startWritingAndParsing(const ResourceResponse& response)
113{
114    ASSERT(!m_imports.isEmpty());
115    DocumentInit init = DocumentInit(response.url(), 0, m_controller->master()->contextDocument(), m_controller)
116        .withRegistrationContext(m_controller->master()->registrationContext());
117    m_document = HTMLDocument::create(init);
118    m_writer = DocumentWriter::create(m_document.get(), response.mimeType(), "UTF-8");
119
120    DocumentParser* parser = m_document->parser();
121    ASSERT(parser);
122    parser->addClient(this);
123
124    return StateLoading;
125}
126
127HTMLImportLoader::State HTMLImportLoader::finishWriting()
128{
129    return StateWritten;
130}
131
132HTMLImportLoader::State HTMLImportLoader::finishParsing()
133{
134    return StateParsed;
135}
136
137HTMLImportLoader::State HTMLImportLoader::finishLoading()
138{
139    return StateLoaded;
140}
141
142void HTMLImportLoader::setState(State state)
143{
144    if (m_state == state)
145        return;
146
147    m_state = state;
148
149    if (m_state == StateParsed || m_state == StateError || m_state == StateWritten) {
150        if (RefPtrWillBeRawPtr<DocumentWriter> writer = m_writer.release())
151            writer->end();
152    }
153
154    // Since DocumentWriter::end() can let setState() reenter, we shouldn't refer to m_state here.
155    if (state == StateLoaded || state == StateError)
156        didFinishLoading();
157}
158
159void HTMLImportLoader::notifyParserStopped()
160{
161    setState(finishParsing());
162    if (!hasPendingResources())
163        setState(finishLoading());
164
165    DocumentParser* parser = m_document->parser();
166    ASSERT(parser);
167    parser->removeClient(this);
168}
169
170void HTMLImportLoader::didRemoveAllPendingStylesheet()
171{
172    if (m_state == StateParsed)
173        setState(finishLoading());
174}
175
176bool HTMLImportLoader::hasPendingResources() const
177{
178    return m_document && m_document->styleEngine()->hasPendingSheets();
179}
180
181void HTMLImportLoader::didFinishLoading()
182{
183    for (size_t i = 0; i < m_imports.size(); ++i)
184        m_imports[i]->didFinishLoading();
185
186    clearResource();
187
188    ASSERT(!m_document || !m_document->parsing());
189}
190
191void HTMLImportLoader::moveToFirst(HTMLImportChild* import)
192{
193    size_t position = m_imports.find(import);
194    ASSERT(kNotFound != position);
195    m_imports.remove(position);
196    m_imports.insert(0, import);
197}
198
199void HTMLImportLoader::addImport(HTMLImportChild* import)
200{
201    ASSERT(kNotFound == m_imports.find(import));
202
203    m_imports.append(import);
204    import->normalize();
205    if (isDone())
206        import->didFinishLoading();
207}
208
209#if !ENABLE(OILPAN)
210void HTMLImportLoader::removeImport(HTMLImportChild* client)
211{
212    ASSERT(kNotFound != m_imports.find(client));
213    m_imports.remove(m_imports.find(client));
214}
215#endif
216
217bool HTMLImportLoader::shouldBlockScriptExecution() const
218{
219    return firstImport()->state().shouldBlockScriptExecution();
220}
221
222PassRefPtrWillBeRawPtr<CustomElementSyncMicrotaskQueue> HTMLImportLoader::microtaskQueue() const
223{
224    return m_microtaskQueue;
225}
226
227void HTMLImportLoader::trace(Visitor* visitor)
228{
229    visitor->trace(m_controller);
230#if ENABLE(OILPAN)
231    visitor->trace(m_imports);
232#endif
233    visitor->trace(m_document);
234    visitor->trace(m_writer);
235    visitor->trace(m_microtaskQueue);
236}
237
238} // namespace blink
239