| /* |
| * Copyright (C) 2015-2018 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "HTMLAttachmentElement.h" |
| |
| #if ENABLE(ATTACHMENT_ELEMENT) |
| |
| #include "DOMURL.h" |
| #include "Document.h" |
| #include "Editor.h" |
| #include "File.h" |
| #include "FileReaderLoader.h" |
| #include "FileReaderLoaderClient.h" |
| #include "Frame.h" |
| #include "HTMLImageElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLVideoElement.h" |
| #include "MIMETypeRegistry.h" |
| #include "RenderAttachment.h" |
| #include "RenderBlockFlow.h" |
| #include "ShadowRoot.h" |
| #include "SharedBuffer.h" |
| #include <pal/FileSizeFormatter.h> |
| #include <wtf/IsoMallocInlines.h> |
| |
| #if PLATFORM(COCOA) |
| #include "UTIUtilities.h" |
| #endif |
| |
| namespace WebCore { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAttachmentElement); |
| |
| using namespace HTMLNames; |
| |
| class AttachmentDataReader : public FileReaderLoaderClient { |
| public: |
| static std::unique_ptr<AttachmentDataReader> create(HTMLAttachmentElement& attachment, Function<void(RefPtr<SharedBuffer>&&)>&& callback) |
| { |
| return std::make_unique<AttachmentDataReader>(attachment, WTFMove(callback)); |
| } |
| |
| AttachmentDataReader(HTMLAttachmentElement& attachment, Function<void(RefPtr<SharedBuffer>&&)>&& callback) |
| : m_attachment(attachment) |
| , m_callback(std::make_unique<Function<void(RefPtr<SharedBuffer>&&)>>(WTFMove(callback))) |
| , m_loader(std::make_unique<FileReaderLoader>(FileReaderLoader::ReadType::ReadAsArrayBuffer, this)) |
| { |
| m_loader->start(&attachment.document(), *attachment.file()); |
| } |
| |
| ~AttachmentDataReader(); |
| |
| private: |
| void didStartLoading() final { } |
| void didReceiveData() final { } |
| void didFinishLoading() final; |
| void didFail(int error) final; |
| |
| void invokeCallbackAndFinishReading(RefPtr<SharedBuffer>&&); |
| |
| HTMLAttachmentElement& m_attachment; |
| std::unique_ptr<Function<void(RefPtr<SharedBuffer>&&)>> m_callback; |
| std::unique_ptr<FileReaderLoader> m_loader; |
| }; |
| |
| HTMLAttachmentElement::HTMLAttachmentElement(const QualifiedName& tagName, Document& document) |
| : HTMLElement(tagName, document) |
| { |
| ASSERT(hasTagName(attachmentTag)); |
| } |
| |
| HTMLAttachmentElement::~HTMLAttachmentElement() = default; |
| |
| Ref<HTMLAttachmentElement> HTMLAttachmentElement::create(const QualifiedName& tagName, Document& document) |
| { |
| return adoptRef(*new HTMLAttachmentElement(tagName, document)); |
| } |
| |
| RenderPtr<RenderElement> HTMLAttachmentElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
| { |
| if (!style.hasAppearance()) { |
| // If this attachment element doesn't have an appearance, defer rendering to child elements. |
| return createRenderer<RenderBlockFlow>(*this, WTFMove(style)); |
| } |
| |
| return createRenderer<RenderAttachment>(*this, WTFMove(style)); |
| } |
| |
| File* HTMLAttachmentElement::file() const |
| { |
| return m_file.get(); |
| } |
| |
| URL HTMLAttachmentElement::blobURL() const |
| { |
| return { { }, attributeWithoutSynchronization(HTMLNames::webkitattachmentbloburlAttr).string() }; |
| } |
| |
| void HTMLAttachmentElement::setFile(RefPtr<File>&& file, UpdateDisplayAttributes updateAttributes) |
| { |
| m_file = WTFMove(file); |
| |
| if (updateAttributes == UpdateDisplayAttributes::Yes) { |
| if (m_file) { |
| setAttributeWithoutSynchronization(HTMLNames::titleAttr, m_file->name()); |
| setAttributeWithoutSynchronization(HTMLNames::subtitleAttr, fileSizeDescription(m_file->size())); |
| setAttributeWithoutSynchronization(HTMLNames::typeAttr, m_file->type()); |
| } else { |
| removeAttribute(HTMLNames::titleAttr); |
| removeAttribute(HTMLNames::subtitleAttr); |
| removeAttribute(HTMLNames::typeAttr); |
| } |
| } |
| |
| if (auto* renderAttachment = attachmentRenderer()) |
| renderAttachment->invalidate(); |
| |
| invalidateShadowRootChildrenIfNecessary(); |
| populateShadowRootIfNecessary(); |
| } |
| |
| void HTMLAttachmentElement::invalidateShadowRootChildrenIfNecessary() |
| { |
| if (auto image = innerImage()) { |
| image->setAttributeWithoutSynchronization(srcAttr, emptyString()); |
| image->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true); |
| } |
| if (auto video = innerVideo()) { |
| video->setAttributeWithoutSynchronization(srcAttr, emptyString()); |
| video->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true); |
| } |
| } |
| |
| RenderAttachment* HTMLAttachmentElement::attachmentRenderer() const |
| { |
| auto* renderer = this->renderer(); |
| return is<RenderAttachment>(renderer) ? downcast<RenderAttachment>(renderer) : nullptr; |
| } |
| |
| Node::InsertedIntoAncestorResult HTMLAttachmentElement::insertedIntoAncestor(InsertionType type, ContainerNode& ancestor) |
| { |
| auto result = HTMLElement::insertedIntoAncestor(type, ancestor); |
| if (type.connectedToDocument) |
| document().didInsertAttachmentElement(*this); |
| return result; |
| } |
| |
| void HTMLAttachmentElement::removedFromAncestor(RemovalType type, ContainerNode& ancestor) |
| { |
| HTMLElement::removedFromAncestor(type, ancestor); |
| if (type.disconnectedFromDocument) |
| document().didRemoveAttachmentElement(*this); |
| } |
| |
| void HTMLAttachmentElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
| { |
| if (name == progressAttr || name == subtitleAttr || name == titleAttr || name == typeAttr) { |
| if (auto* renderAttachment = attachmentRenderer()) |
| renderAttachment->invalidate(); |
| } |
| |
| HTMLElement::parseAttribute(name, value); |
| } |
| |
| String HTMLAttachmentElement::attachmentTitle() const |
| { |
| auto& title = attributeWithoutSynchronization(titleAttr); |
| if (!title.isEmpty()) |
| return title; |
| return m_file ? m_file->name() : String(); |
| } |
| |
| String HTMLAttachmentElement::attachmentType() const |
| { |
| return attributeWithoutSynchronization(typeAttr); |
| } |
| |
| String HTMLAttachmentElement::attachmentPath() const |
| { |
| return attributeWithoutSynchronization(webkitattachmentpathAttr); |
| } |
| |
| void HTMLAttachmentElement::updateDisplayMode(AttachmentDisplayMode mode) |
| { |
| mode = mode == AttachmentDisplayMode::Auto ? defaultDisplayMode() : mode; |
| |
| switch (mode) { |
| case AttachmentDisplayMode::InPlace: |
| populateShadowRootIfNecessary(); |
| setInlineStyleProperty(CSSPropertyWebkitAppearance, CSSValueNone, true); |
| setInlineStyleProperty(CSSPropertyDisplay, CSSValueInlineBlock, true); |
| break; |
| case AttachmentDisplayMode::AsIcon: |
| removeInlineStyleProperty(CSSPropertyWebkitAppearance); |
| removeInlineStyleProperty(CSSPropertyDisplay); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| invalidateStyleAndRenderersForSubtree(); |
| } |
| |
| void HTMLAttachmentElement::updateFileWithData(Ref<SharedBuffer>&& data, std::optional<String>&& newContentType, std::optional<String>&& newFilename) |
| { |
| auto filename = newFilename ? *newFilename : attachmentTitle(); |
| auto contentType = newContentType ? *newContentType : File::contentTypeForFile(filename); |
| auto file = File::create(Blob::create(WTFMove(data), contentType), filename); |
| setFile(WTFMove(file), UpdateDisplayAttributes::Yes); |
| } |
| |
| Ref<HTMLImageElement> HTMLAttachmentElement::ensureInnerImage() |
| { |
| if (auto image = innerImage()) |
| return *image; |
| |
| auto image = HTMLImageElement::create(document()); |
| ensureUserAgentShadowRoot().appendChild(image); |
| return image; |
| } |
| |
| Ref<HTMLVideoElement> HTMLAttachmentElement::ensureInnerVideo() |
| { |
| if (auto video = innerVideo()) |
| return *video; |
| |
| auto video = HTMLVideoElement::create(document()); |
| ensureUserAgentShadowRoot().appendChild(video); |
| return video; |
| } |
| |
| RefPtr<HTMLImageElement> HTMLAttachmentElement::innerImage() const |
| { |
| if (auto root = userAgentShadowRoot()) |
| return childrenOfType<HTMLImageElement>(*root).first(); |
| return nullptr; |
| } |
| |
| RefPtr<HTMLVideoElement> HTMLAttachmentElement::innerVideo() const |
| { |
| if (auto root = userAgentShadowRoot()) |
| return childrenOfType<HTMLVideoElement>(*root).first(); |
| return nullptr; |
| } |
| |
| void HTMLAttachmentElement::populateShadowRootIfNecessary() |
| { |
| if (!m_file) |
| return; |
| |
| auto mimeType = attachmentType(); |
| |
| #if PLATFORM(COCOA) |
| if (isDeclaredUTI(mimeType)) |
| mimeType = MIMETypeFromUTI(mimeType); |
| #endif |
| |
| if (mimeType.isEmpty()) |
| return; |
| |
| if (MIMETypeRegistry::isSupportedImageMIMEType(mimeType) || MIMETypeRegistry::isPDFMIMEType(mimeType)) { |
| auto image = ensureInnerImage(); |
| if (image->attributeWithoutSynchronization(srcAttr).isEmpty()) { |
| image->setAttributeWithoutSynchronization(srcAttr, DOMURL::createObjectURL(document(), *m_file)); |
| image->setAttributeWithoutSynchronization(draggableAttr, AtomicString("false")); |
| image->setInlineStyleProperty(CSSPropertyDisplay, CSSValueInline, true); |
| image->setInlineStyleProperty(CSSPropertyMaxWidth, 100, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE, true); |
| } |
| |
| } else if (MIMETypeRegistry::isSupportedMediaMIMEType(mimeType)) { |
| auto video = ensureInnerVideo(); |
| if (video->attributeWithoutSynchronization(srcAttr).isEmpty()) { |
| video->setAttributeWithoutSynchronization(srcAttr, DOMURL::createObjectURL(document(), *m_file)); |
| video->setAttributeWithoutSynchronization(controlsAttr, emptyString()); |
| video->setInlineStyleProperty(CSSPropertyDisplay, CSSValueInline, true); |
| video->setInlineStyleProperty(CSSPropertyMaxWidth, 100, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE, true); |
| } |
| } |
| } |
| |
| void HTMLAttachmentElement::requestInfo(Function<void(const AttachmentInfo&)>&& callback) |
| { |
| if (!m_file) { |
| callback({ }); |
| return; |
| } |
| |
| AttachmentInfo infoWithoutData { m_file->type(), m_file->name(), m_file->path(), nullptr }; |
| if (!m_file->path().isEmpty()) { |
| callback(infoWithoutData); |
| return; |
| } |
| |
| m_attachmentReaders.append(AttachmentDataReader::create(*this, [infoWithoutData = WTFMove(infoWithoutData), protectedFile = makeRef(*m_file), callback = WTFMove(callback)] (RefPtr<SharedBuffer>&& data) { |
| callback({ infoWithoutData.contentType, infoWithoutData.name, infoWithoutData.filePath, WTFMove(data) }); |
| })); |
| } |
| |
| void HTMLAttachmentElement::destroyReader(AttachmentDataReader& finishedReader) |
| { |
| m_attachmentReaders.removeFirstMatching([&] (const std::unique_ptr<AttachmentDataReader>& reader) -> bool { |
| return reader.get() == &finishedReader; |
| }); |
| } |
| |
| AttachmentDataReader::~AttachmentDataReader() |
| { |
| invokeCallbackAndFinishReading(nullptr); |
| } |
| |
| void AttachmentDataReader::didFinishLoading() |
| { |
| if (auto arrayBuffer = m_loader->arrayBufferResult()) |
| invokeCallbackAndFinishReading(SharedBuffer::create(reinterpret_cast<uint8_t*>(arrayBuffer->data()), arrayBuffer->byteLength())); |
| else |
| invokeCallbackAndFinishReading(nullptr); |
| m_attachment.destroyReader(*this); |
| } |
| |
| void AttachmentDataReader::didFail(int) |
| { |
| invokeCallbackAndFinishReading(nullptr); |
| m_attachment.destroyReader(*this); |
| } |
| |
| void AttachmentDataReader::invokeCallbackAndFinishReading(RefPtr<SharedBuffer>&& data) |
| { |
| auto callback = WTFMove(m_callback); |
| if (!callback) |
| return; |
| |
| m_loader->cancel(); |
| m_loader = nullptr; |
| (*callback)(WTFMove(data)); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(ATTACHMENT_ELEMENT) |