Large in-place attachment elements cause the document width to expand when inserted
[WebKit-https.git] / Source / WebCore / html / HTMLAttachmentElement.cpp
1 /*
2  * Copyright (C) 2015 Apple 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
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "HTMLAttachmentElement.h"
28
29 #if ENABLE(ATTACHMENT_ELEMENT)
30
31 #include "DOMURL.h"
32 #include "Document.h"
33 #include "Editor.h"
34 #include "File.h"
35 #include "FileReaderLoader.h"
36 #include "FileReaderLoaderClient.h"
37 #include "Frame.h"
38 #include "HTMLImageElement.h"
39 #include "HTMLNames.h"
40 #include "HTMLVideoElement.h"
41 #include "MIMETypeRegistry.h"
42 #include "RenderAttachment.h"
43 #include "RenderBlockFlow.h"
44 #include "ShadowRoot.h"
45 #include "SharedBuffer.h"
46 #include <pal/FileSizeFormatter.h>
47
48 #if PLATFORM(COCOA)
49 #include "UTIUtilities.h"
50 #endif
51
52 namespace WebCore {
53
54 using namespace HTMLNames;
55
56 class AttachmentDataReader : public FileReaderLoaderClient {
57 public:
58     static std::unique_ptr<AttachmentDataReader> create(HTMLAttachmentElement& attachment, Function<void(RefPtr<SharedBuffer>&&)>&& callback)
59     {
60         return std::make_unique<AttachmentDataReader>(attachment, WTFMove(callback));
61     }
62
63     AttachmentDataReader(HTMLAttachmentElement& attachment, Function<void(RefPtr<SharedBuffer>&&)>&& callback)
64         : m_attachment(attachment)
65         , m_callback(std::make_unique<Function<void(RefPtr<SharedBuffer>&&)>>(WTFMove(callback)))
66         , m_loader(std::make_unique<FileReaderLoader>(FileReaderLoader::ReadType::ReadAsArrayBuffer, this))
67     {
68         m_loader->start(&attachment.document(), *attachment.file());
69     }
70
71     ~AttachmentDataReader();
72
73 private:
74     void didStartLoading() final { }
75     void didReceiveData() final { }
76     void didFinishLoading() final;
77     void didFail(int error) final;
78
79     void invokeCallbackAndFinishReading(RefPtr<SharedBuffer>&&);
80
81     HTMLAttachmentElement& m_attachment;
82     std::unique_ptr<Function<void(RefPtr<SharedBuffer>&&)>> m_callback;
83     std::unique_ptr<FileReaderLoader> m_loader;
84 };
85
86 HTMLAttachmentElement::HTMLAttachmentElement(const QualifiedName& tagName, Document& document)
87     : HTMLElement(tagName, document)
88 {
89     ASSERT(hasTagName(attachmentTag));
90 }
91
92 HTMLAttachmentElement::~HTMLAttachmentElement() = default;
93
94 Ref<HTMLAttachmentElement> HTMLAttachmentElement::create(const QualifiedName& tagName, Document& document)
95 {
96     return adoptRef(*new HTMLAttachmentElement(tagName, document));
97 }
98
99 RenderPtr<RenderElement> HTMLAttachmentElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
100 {
101     if (!style.hasAppearance()) {
102         // If this attachment element doesn't have an appearance, defer rendering to child elements.
103         return createRenderer<RenderBlockFlow>(*this, WTFMove(style));
104     }
105
106     return createRenderer<RenderAttachment>(*this, WTFMove(style));
107 }
108
109 File* HTMLAttachmentElement::file() const
110 {
111     return m_file.get();
112 }
113
114 URL HTMLAttachmentElement::blobURL() const
115 {
116     return { { }, attributeWithoutSynchronization(HTMLNames::webkitattachmentbloburlAttr).string() };
117 }
118
119 void HTMLAttachmentElement::setFile(RefPtr<File>&& file, UpdateDisplayAttributes updateAttributes)
120 {
121     m_file = WTFMove(file);
122
123     if (updateAttributes == UpdateDisplayAttributes::Yes) {
124         if (m_file) {
125             setAttributeWithoutSynchronization(HTMLNames::titleAttr, m_file->name());
126             setAttributeWithoutSynchronization(HTMLNames::subtitleAttr, fileSizeDescription(m_file->size()));
127             setAttributeWithoutSynchronization(HTMLNames::typeAttr, m_file->type());
128         } else {
129             removeAttribute(HTMLNames::titleAttr);
130             removeAttribute(HTMLNames::subtitleAttr);
131             removeAttribute(HTMLNames::typeAttr);
132         }
133     }
134
135     if (auto* renderAttachment = attachmentRenderer())
136         renderAttachment->invalidate();
137
138     invalidateShadowRootChildrenIfNecessary();
139     populateShadowRootIfNecessary();
140 }
141
142 void HTMLAttachmentElement::invalidateShadowRootChildrenIfNecessary()
143 {
144     if (auto image = innerImage()) {
145         image->setAttributeWithoutSynchronization(srcAttr, emptyString());
146         image->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
147     }
148     if (auto video = innerVideo()) {
149         video->setAttributeWithoutSynchronization(srcAttr, emptyString());
150         video->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
151     }
152 }
153
154 RenderAttachment* HTMLAttachmentElement::attachmentRenderer() const
155 {
156     auto* renderer = this->renderer();
157     return is<RenderAttachment>(renderer) ? downcast<RenderAttachment>(renderer) : nullptr;
158 }
159
160 Node::InsertedIntoAncestorResult HTMLAttachmentElement::insertedIntoAncestor(InsertionType type, ContainerNode& ancestor)
161 {
162     auto result = HTMLElement::insertedIntoAncestor(type, ancestor);
163     if (type.connectedToDocument)
164         document().didInsertAttachmentElement(*this);
165     return result;
166 }
167
168 void HTMLAttachmentElement::removedFromAncestor(RemovalType type, ContainerNode& ancestor)
169 {
170     HTMLElement::removedFromAncestor(type, ancestor);
171     if (type.disconnectedFromDocument)
172         document().didRemoveAttachmentElement(*this);
173 }
174
175 void HTMLAttachmentElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
176 {
177     if (name == progressAttr || name == subtitleAttr || name == titleAttr || name == typeAttr) {
178         if (auto* renderAttachment = attachmentRenderer())
179             renderAttachment->invalidate();
180     }
181
182     HTMLElement::parseAttribute(name, value);
183 }
184
185 String HTMLAttachmentElement::attachmentTitle() const
186 {
187     auto& title = attributeWithoutSynchronization(titleAttr);
188     if (!title.isEmpty())
189         return title;
190     return m_file ? m_file->name() : String();
191 }
192
193 String HTMLAttachmentElement::attachmentType() const
194 {
195     return attributeWithoutSynchronization(typeAttr);
196 }
197
198 String HTMLAttachmentElement::attachmentPath() const
199 {
200     return attributeWithoutSynchronization(webkitattachmentpathAttr);
201 }
202
203 void HTMLAttachmentElement::updateDisplayMode(AttachmentDisplayMode mode)
204 {
205     mode = mode == AttachmentDisplayMode::Auto ? defaultDisplayMode() : mode;
206
207     switch (mode) {
208     case AttachmentDisplayMode::InPlace:
209         populateShadowRootIfNecessary();
210         setInlineStyleProperty(CSSPropertyWebkitAppearance, CSSValueNone, true);
211         setInlineStyleProperty(CSSPropertyDisplay, CSSValueInlineBlock, true);
212         break;
213     case AttachmentDisplayMode::AsIcon:
214         removeInlineStyleProperty(CSSPropertyWebkitAppearance);
215         removeInlineStyleProperty(CSSPropertyDisplay);
216         break;
217     default:
218         ASSERT_NOT_REACHED();
219         break;
220     }
221
222     invalidateStyleAndRenderersForSubtree();
223 }
224
225 void HTMLAttachmentElement::updateFileWithData(Ref<SharedBuffer>&& data, std::optional<String>&& newContentType, std::optional<String>&& newFilename)
226 {
227     auto filename = newFilename ? *newFilename : attachmentTitle();
228     auto contentType = newContentType ? *newContentType : File::contentTypeForFile(filename);
229     auto file = File::create(Blob::create(WTFMove(data), contentType), filename);
230     setFile(WTFMove(file), UpdateDisplayAttributes::Yes);
231 }
232
233 Ref<HTMLImageElement> HTMLAttachmentElement::ensureInnerImage()
234 {
235     if (auto image = innerImage())
236         return *image;
237
238     auto image = HTMLImageElement::create(document());
239     ensureUserAgentShadowRoot().appendChild(image);
240     return image;
241 }
242
243 Ref<HTMLVideoElement> HTMLAttachmentElement::ensureInnerVideo()
244 {
245     if (auto video = innerVideo())
246         return *video;
247
248     auto video = HTMLVideoElement::create(document());
249     ensureUserAgentShadowRoot().appendChild(video);
250     return video;
251 }
252
253 RefPtr<HTMLImageElement> HTMLAttachmentElement::innerImage() const
254 {
255     if (auto root = userAgentShadowRoot())
256         return childrenOfType<HTMLImageElement>(*root).first();
257     return nullptr;
258 }
259
260 RefPtr<HTMLVideoElement> HTMLAttachmentElement::innerVideo() const
261 {
262     if (auto root = userAgentShadowRoot())
263         return childrenOfType<HTMLVideoElement>(*root).first();
264     return nullptr;
265 }
266
267 void HTMLAttachmentElement::populateShadowRootIfNecessary()
268 {
269     if (!m_file)
270         return;
271
272     auto mimeType = attachmentType();
273
274 #if PLATFORM(COCOA)
275     if (isDeclaredUTI(mimeType))
276         mimeType = MIMETypeFromUTI(mimeType);
277 #endif
278
279     if (mimeType.isEmpty())
280         return;
281
282     if (MIMETypeRegistry::isSupportedImageMIMEType(mimeType) || MIMETypeRegistry::isPDFMIMEType(mimeType)) {
283         auto image = ensureInnerImage();
284         if (image->attributeWithoutSynchronization(srcAttr).isEmpty()) {
285             image->setAttributeWithoutSynchronization(srcAttr, DOMURL::createObjectURL(document(), *m_file));
286             image->setAttributeWithoutSynchronization(draggableAttr, AtomicString("false"));
287             image->setInlineStyleProperty(CSSPropertyDisplay, CSSValueInline, true);
288             image->setInlineStyleProperty(CSSPropertyMaxWidth, 100, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE, true);
289         }
290
291     } else if (MIMETypeRegistry::isSupportedMediaMIMEType(mimeType)) {
292         auto video = ensureInnerVideo();
293         if (video->attributeWithoutSynchronization(srcAttr).isEmpty()) {
294             video->setAttributeWithoutSynchronization(srcAttr, DOMURL::createObjectURL(document(), *m_file));
295             video->setAttributeWithoutSynchronization(controlsAttr, emptyString());
296             video->setInlineStyleProperty(CSSPropertyDisplay, CSSValueInline, true);
297             video->setInlineStyleProperty(CSSPropertyMaxWidth, 100, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE, true);
298         }
299     }
300 }
301
302 void HTMLAttachmentElement::requestInfo(Function<void(const AttachmentInfo&)>&& callback)
303 {
304     if (!m_file) {
305         callback({ });
306         return;
307     }
308
309     AttachmentInfo infoWithoutData { m_file->type(), m_file->name(), m_file->path(), nullptr };
310     if (!m_file->path().isEmpty()) {
311         callback(infoWithoutData);
312         return;
313     }
314
315     m_attachmentReaders.append(AttachmentDataReader::create(*this, [infoWithoutData = WTFMove(infoWithoutData), protectedFile = makeRef(*m_file), callback = WTFMove(callback)] (RefPtr<SharedBuffer>&& data) {
316         callback({ infoWithoutData.contentType, infoWithoutData.name, infoWithoutData.filePath, WTFMove(data) });
317     }));
318 }
319
320 void HTMLAttachmentElement::destroyReader(AttachmentDataReader& finishedReader)
321 {
322     m_attachmentReaders.removeFirstMatching([&] (const std::unique_ptr<AttachmentDataReader>& reader) -> bool {
323         return reader.get() == &finishedReader;
324     });
325 }
326
327 AttachmentDataReader::~AttachmentDataReader()
328 {
329     invokeCallbackAndFinishReading(nullptr);
330 }
331
332 void AttachmentDataReader::didFinishLoading()
333 {
334     if (auto arrayBuffer = m_loader->arrayBufferResult())
335         invokeCallbackAndFinishReading(SharedBuffer::create(reinterpret_cast<uint8_t*>(arrayBuffer->data()), arrayBuffer->byteLength()));
336     else
337         invokeCallbackAndFinishReading(nullptr);
338     m_attachment.destroyReader(*this);
339 }
340
341 void AttachmentDataReader::didFail(int)
342 {
343     invokeCallbackAndFinishReading(nullptr);
344     m_attachment.destroyReader(*this);
345 }
346
347 void AttachmentDataReader::invokeCallbackAndFinishReading(RefPtr<SharedBuffer>&& data)
348 {
349     auto callback = WTFMove(m_callback);
350     if (!callback)
351         return;
352
353     m_loader->cancel();
354     m_loader = nullptr;
355     (*callback)(WTFMove(data));
356 }
357
358 } // namespace WebCore
359
360 #endif // ENABLE(ATTACHMENT_ELEMENT)