blob: 62a74a430b6cd63e52070f14eab7d91f17ea91bb [file] [log] [blame]
/*
* 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 "AddEventListenerOptionsInlines.h"
#include "AttachmentAssociatedElement.h"
#include "AttachmentElementClient.h"
#include "ContainerNodeInlines.h"
#include "CSSPropertyNames.h"
#include "CSSUnits.h"
#include "DOMRectReadOnly.h"
#include "DOMURL.h"
#include "Document.h"
#include "Editor.h"
#include "ElementInlines.h"
#include "EventNames.h"
#include "File.h"
#include "HTMLButtonElement.h"
#include "HTMLDivElement.h"
#include "HTMLElementTypeHelpers.h"
#include "HTMLImageElement.h"
#include "HTMLNames.h"
#include "HTMLStyleElement.h"
#include "LocalFrame.h"
#include "Logging.h"
#include "MIMETypeRegistry.h"
#include "MouseEvent.h"
#include "NodeName.h"
#include "RenderAttachment.h"
#include "RenderObjectInlines.h"
#include "ShadowRoot.h"
#include "SharedBuffer.h"
#include "UserAgentStyleSheets.h"
#include <pal/FileSizeFormatter.h>
#include <unicode/ubidi.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/UUID.h>
#include <wtf/URLParser.h>
#include <wtf/text/MakeString.h>
#include <wtf/unicode/CharacterNames.h>
#if ENABLE(SERVICE_CONTROLS)
#include "ImageControlsMac.h"
#endif
#if PLATFORM(COCOA)
#include "UTIUtilities.h"
#endif
namespace WebCore {
WTF_MAKE_TZONE_OR_ISO_ALLOCATED_IMPL(HTMLAttachmentElement);
using namespace HTMLNames;
#if PLATFORM(VISION)
constexpr float attachmentIconSize = 40;
#elif PLATFORM(IOS_FAMILY)
constexpr float attachmentIconSize = 72;
#else
constexpr float attachmentIconSize = 52;
#endif
// FIXME: Remove after rdar://99228361 is fixed.
#define ATTACHMENT_LOG_DOCUMENT_TRAFFIC !RELEASE_LOG_DISABLED
#if ATTACHMENT_LOG_DOCUMENT_TRAFFIC
// Given a StackTrace, output one minimally-sized function identifier per line, so that more frames can fit in a log message.
static CString compactStackTrace(StackTrace& stackTrace)
{
StringPrintStream stack;
stackTrace.forEachFrame([&stack](int, void*, const char* fullName) {
constexpr size_t maxWorkLength = 1023;
auto name = StringView::fromLatin1(fullName ? fullName : "?").left(maxWorkLength);
for (const auto& prefix : { "auto void "_s, "auto "_s }) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
break;
}
}
if (name.startsWith("decltype("_s)) {
int depth = 1;
for (unsigned i = "decltype("_s.length(); i < name.length(); ++i) {
auto c = name[i];
if (c == ')') {
if (!--depth) {
name = name.substring(i + 1);
if (name.startsWith(" "_s))
name = name.substring(" "_s.length());
break;
}
} else if (c == '(')
++depth;
}
}
if (name.startsWith("std::"_s))
return;
for (const auto& prefix : { "WebCore::"_s, "WebKit::"_s, "IPC::"_s }) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
break;
}
}
for (unsigned i = 0; i < name.length(); ++i) {
auto c = name[i];
// If we find '(' first, assume it's the function parameter list, drop it and whatever follows.
if (c == '(') {
name = name.left(i);
break;
}
// If we find '[' first, assume it's an Objective C method call, keep everything.
if (c == '[')
break;
}
constexpr unsigned maxLen = 48;
name = name.left(maxLen);
stack.print("\n> "_s, name);
});
return stack.toCString();
}
#endif // ATTACHMENT_LOG_DOCUMENT_TRAFFIC
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)
{
Ref attachment = adoptRef(*new HTMLAttachmentElement(tagName, document));
if (document.settings().attachmentWideLayoutEnabled()) {
ASSERT(attachment->m_implementation == Implementation::NarrowLayout);
ASSERT(!attachment->renderer()); // Switch to wide-layout style *must* be done before renderer is created!
attachment->m_implementation = Implementation::WideLayout;
attachment->ensureUserAgentShadowRoot();
}
return attachment;
}
void HTMLAttachmentElement::didAddUserAgentShadowRoot(ShadowRoot& root)
{
if (m_implementation == Implementation::WideLayout)
ensureWideLayoutShadowTree(root);
}
static const AtomString& attachmentContainerIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-container"_s);
return identifier;
}
static const AtomString& attachmentBackgroundIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-background"_s);
return identifier;
}
static const AtomString& attachmentPreviewAreaIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-preview-area"_s);
return identifier;
}
static const AtomString& attachmentPlaceholderIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-placeholder"_s);
return identifier;
}
static const AtomString& attachmentIconIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-icon"_s);
return identifier;
}
static const AtomString& attachmentProgressIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-progress"_s);
return identifier;
}
static const AtomString& attachmentProgressCSSProperty()
{
static MainThreadNeverDestroyed<const AtomString> property("--progress"_s);
return property;
}
static const AtomString& attachmentInformationAreaIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-information-area"_s);
return identifier;
}
static const AtomString& attachmentInformationBlockIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-information-block"_s);
return identifier;
}
static const AtomString& attachmentActionIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-action"_s);
return identifier;
}
static const AtomString& attachmentTitleIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-title"_s);
return identifier;
}
static const AtomString& attachmentSubtitleIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-subtitle"_s);
return identifier;
}
static const AtomString& attachmentSaveAreaIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-save-area"_s);
return identifier;
}
static const AtomString& attachmentSaveButtonIdentifier()
{
static MainThreadNeverDestroyed<const AtomString> identifier("attachment-save-button"_s);
return identifier;
}
static const AtomString& attachmentIconSizeProperty()
{
static MainThreadNeverDestroyed<const AtomString> identifier("--icon-size"_s);
return identifier;
}
static const AtomString& saveAtom()
{
static MainThreadNeverDestroyed<const AtomString> identifier("save"_s);
return identifier;
}
class AttachmentImageEventsListener final : public EventListener {
public:
static void addToImageForAttachment(HTMLImageElement& image, HTMLAttachmentElement& attachment)
{
auto listener = create(attachment);
image.addEventListener(eventNames().loadEvent, listener, { });
image.addEventListener(eventNames().errorEvent, listener, { });
}
void handleEvent(ScriptExecutionContext&, Event& event) final
{
const auto& type = event.type();
if (type == eventNames().loadEvent || type == eventNames().errorEvent)
m_attachment->dispatchEvent(Event::create(type, Event::CanBubble::No, Event::IsCancelable::No));
else
ASSERT_NOT_REACHED();
}
private:
explicit AttachmentImageEventsListener(HTMLAttachmentElement& attachment)
: EventListener(CPPEventListenerType)
, m_attachment(attachment)
{
}
static Ref<AttachmentImageEventsListener> create(HTMLAttachmentElement& attachment) { return adoptRef(*new AttachmentImageEventsListener(attachment)); }
WeakPtr<HTMLAttachmentElement, WeakPtrImplWithEventTargetData> m_attachment;
};
template <typename ElementType>
static Ref<ElementType> createContainedElement(HTMLElement& container, const AtomString& id, String&& textContent = { })
{
Ref<ElementType> element = ElementType::create(container.document());
element->setIdAttribute(id);
if (!textContent.isEmpty())
element->setTextContent(WTFMove(textContent));
container.appendChild(element);
return element;
}
void HTMLAttachmentElement::ensureWideLayoutShadowTree(ShadowRoot& root)
{
ASSERT(m_implementation == Implementation::WideLayout);
if (m_titleElement)
return;
static MainThreadNeverDestroyed<const String> shadowStyle(StringImpl::createWithoutCopying(attachmentElementShadowUserAgentStyleSheet));
auto style = HTMLStyleElement::create(HTMLNames::styleTag, document(), false);
style->setTextContent(String { shadowStyle });
root.appendChild(WTFMove(style));
m_containerElement = HTMLDivElement::create(document());
m_containerElement->setIdAttribute(attachmentContainerIdentifier());
m_containerElement->setInlineStyleCustomProperty(attachmentIconSizeProperty(), makeString(attachmentIconSize, "px"_s));
root.appendChild(*m_containerElement);
auto background = createContainedElement<HTMLDivElement>(*m_containerElement, attachmentBackgroundIdentifier());
auto previewArea = createContainedElement<HTMLDivElement>(background, attachmentPreviewAreaIdentifier());
m_imageElement = createContainedElement<HTMLImageElement>(previewArea, attachmentIconIdentifier());
AttachmentImageEventsListener::addToImageForAttachment(*m_imageElement, *this);
updateImage();
m_placeholderElement = createContainedElement<HTMLDivElement>(previewArea, attachmentPlaceholderIdentifier());
m_progressElement = createContainedElement<HTMLDivElement>(previewArea, attachmentProgressIdentifier());
updateProgress(attributeWithoutSynchronization(progressAttr));
auto informationArea = createContainedElement<HTMLDivElement>(background, attachmentInformationAreaIdentifier());
m_informationBlock = createContainedElement<HTMLDivElement>(informationArea, attachmentInformationBlockIdentifier());
m_actionTextElement = createContainedElement<HTMLDivElement>(*m_informationBlock, attachmentActionIdentifier(), String { attachmentActionForDisplay() });
m_actionTextElement->setAttributeWithoutSynchronization(HTMLNames::dirAttr, autoAtom());
m_titleElement = createContainedElement<HTMLDivElement>(*m_informationBlock, attachmentTitleIdentifier(), String { attachmentTitleForDisplay() });
m_titleElement->setAttributeWithoutSynchronization(HTMLNames::dirAttr, autoAtom());
m_subtitleElement = createContainedElement<HTMLDivElement>(*m_informationBlock, attachmentSubtitleIdentifier(), String { attachmentSubtitleForDisplay() });
m_subtitleElement->setAttributeWithoutSynchronization(HTMLNames::dirAttr, autoAtom());
updateSaveButton(!attributeWithoutSynchronization(saveAttr).isNull());
}
class AttachmentSaveEventListener final : public EventListener {
public:
static Ref<AttachmentSaveEventListener> create(HTMLAttachmentElement& attachment) { return adoptRef(*new AttachmentSaveEventListener(attachment)); }
void handleEvent(ScriptExecutionContext&, Event& event) final
{
if (isAnyClick(event)) {
auto& mouseEvent = downcast<MouseEvent>(event);
auto copiedEvent = MouseEvent::create(saveAtom(), Event::CanBubble::No, Event::IsCancelable::No, Event::IsComposed::No, MonotonicTime::now(),
mouseEvent.view(), mouseEvent.detail(), mouseEvent.screenX(), mouseEvent.screenY(), mouseEvent.clientX(), mouseEvent.clientY(),
mouseEvent.modifierKeys(), mouseEvent.button(), mouseEvent.buttons(), mouseEvent.syntheticClickType(), nullptr);
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
m_attachment->dispatchEvent(copiedEvent);
} else
ASSERT_NOT_REACHED();
}
private:
explicit AttachmentSaveEventListener(HTMLAttachmentElement& attachment)
: EventListener(CPPEventListenerType)
, m_attachment(attachment)
{
}
WeakPtr<HTMLAttachmentElement, WeakPtrImplWithEventTargetData> m_attachment;
};
void HTMLAttachmentElement::updateProgress(const AtomString& progress)
{
if (!m_progressElement)
return;
bool validProgress = false;
float value = progress.toFloat(&validProgress);
if (validProgress && std::isfinite(value)) {
m_imageElement->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
if (!value) {
m_placeholderElement->removeInlineStyleProperty(CSSPropertyDisplay);
m_progressElement->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
m_progressElement->removeInlineStyleCustomProperty(attachmentProgressCSSProperty());
return;
}
m_placeholderElement->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
m_progressElement->removeInlineStyleProperty(CSSPropertyDisplay);
m_progressElement->setInlineStyleCustomProperty(attachmentProgressCSSProperty(), (value < 0.0) ? "0"_s : (value > 1.0) ? "1"_s : progress);
return;
}
m_imageElement->removeInlineStyleProperty(CSSPropertyDisplay);
m_placeholderElement->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
m_progressElement->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
m_progressElement->removeInlineStyleCustomProperty(attachmentProgressCSSProperty());
}
void HTMLAttachmentElement::updateSaveButton(bool show)
{
if (!show) {
if (m_saveButton) {
m_informationBlock->removeChild(*m_saveArea);
m_saveButton = nullptr;
m_saveArea = nullptr;
}
return;
}
if (!m_saveButton && m_titleElement) {
m_saveArea = createContainedElement<HTMLDivElement>(*m_informationBlock, attachmentSaveAreaIdentifier());
m_saveButton = createContainedElement<HTMLButtonElement>(*m_saveArea, attachmentSaveButtonIdentifier());
m_saveButton->addEventListener(eventNames().clickEvent, AttachmentSaveEventListener::create(*this), { });
m_saveButton->addEventListener(eventNames().auxclickEvent, AttachmentSaveEventListener::create(*this), { });
}
}
DOMRectReadOnly* HTMLAttachmentElement::saveButtonClientRect() const
{
if (!m_saveButton)
return nullptr;
bool unusedIsReplaced;
auto rect = m_saveButton->pixelSnappedAbsoluteBoundingRect(&unusedIsReplaced);
m_saveButtonClientRect = DOMRectReadOnly::create(rect.x(), rect.y(), rect.width(), rect.height());
return m_saveButtonClientRect.get();
}
HTMLElement* HTMLAttachmentElement::wideLayoutImageElement() const
{
return m_imageElement.get();
}
RenderPtr<RenderElement> HTMLAttachmentElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
{
return createRenderer<RenderAttachment>(*this, WTFMove(style));
}
void HTMLAttachmentElement::invalidateRendering()
{
if (auto* renderer = this->renderer()) {
renderer->setNeedsLayout();
renderer->repaint();
}
}
String HTMLAttachmentElement::getAttachmentIdentifier(HTMLElement& element)
{
RefPtr attachmentAssociatedElement = element.asAttachmentAssociatedElement();
if (!attachmentAssociatedElement)
return nullString();
if (RefPtr attachment = attachmentAssociatedElement->attachmentElement())
return attachment->uniqueIdentifier();
Ref document = element.document();
auto attachment = create(HTMLNames::attachmentTag, document);
auto identifier = attachment->ensureUniqueIdentifier();
document->registerAttachmentIdentifier(identifier, *attachmentAssociatedElement);
attachmentAssociatedElement->setAttachmentElement(WTFMove(attachment));
return identifier;
}
void HTMLAttachmentElement::copyNonAttributePropertiesFromElement(const Element& source)
{
m_uniqueIdentifier = downcast<HTMLAttachmentElement>(source).uniqueIdentifier();
HTMLElement::copyNonAttributePropertiesFromElement(source);
}
URL HTMLAttachmentElement::archiveResourceURL(const String& identifier)
{
auto resourceURL = URL({ }, "applewebdata://attachment/"_s);
resourceURL.setPath(identifier);
return resourceURL;
}
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, AtomString { m_file->name() });
setAttributeWithoutSynchronization(subtitleAttr, PAL::fileSizeDescription(m_file->size()));
setAttributeWithoutSynchronization(HTMLNames::typeAttr, AtomString { m_file->type() });
} else {
removeAttribute(HTMLNames::titleAttr);
removeAttribute(HTMLNames::subtitleAttr);
removeAttribute(HTMLNames::typeAttr);
}
}
setNeedsIconRequest();
invalidateRendering();
}
#if ATTACHMENT_LOG_DOCUMENT_TRAFFIC
class AttachmentEvent {
public:
uintptr_t attachment() const { return m_attachment; }
uintptr_t document() const { return m_document; }
String uniqueIdentifier() const { return m_uniqueIdentifier; }
WTF::MonotonicTime time() const { return m_time; }
StackTrace& stackTrace() const { return *m_stackTrace; }
void capture(const HTMLAttachmentElement& a, WTF::MonotonicTime t)
{
m_attachment = reinterpret_cast<uintptr_t>(&a);
m_document = reinterpret_cast<uintptr_t>(&a.document());
m_uniqueIdentifier = a.uniqueIdentifier();
ASSERT(!!t);
m_time = t;
m_stackTrace = StackTrace::captureStackTrace(64);
}
void reset()
{
m_attachment = 0;
m_stackTrace = 0;
}
explicit operator bool() const
{
ASSERT(!m_attachment == !m_stackTrace);
return !!m_attachment;
}
private:
uintptr_t m_attachment { };
uintptr_t m_document { };
String m_uniqueIdentifier;
WTF::MonotonicTime m_time;
std::unique_ptr<StackTrace> m_stackTrace;
};
static AttachmentEvent& lastInsertionInDocument()
{
IGNORE_CLANG_WARNINGS_BEGIN("exit-time-destructors")
static AttachmentEvent event;
IGNORE_CLANG_WARNINGS_END
return event;
}
static AttachmentEvent& lastRemovalFromDocument()
{
IGNORE_CLANG_WARNINGS_BEGIN("exit-time-destructors")
static AttachmentEvent event;
IGNORE_CLANG_WARNINGS_END
return event;
}
static bool shouldMonitorDocumentTraffic(Document& document)
{
static constexpr auto sequenceMaxTime = 1_s .seconds();
return document.monotonicTimestamp() < sequenceMaxTime;
}
#endif // ATTACHMENT_LOG_DOCUMENT_TRAFFIC
Node::InsertedIntoAncestorResult HTMLAttachmentElement::insertedIntoAncestor(InsertionType type, ContainerNode& ancestor)
{
auto result = HTMLElement::insertedIntoAncestor(type, ancestor);
if (isWideLayout()) {
setInlineStyleProperty(CSSPropertyMarginLeft, 1, CSSUnitType::CSS_PX);
setInlineStyleProperty(CSSPropertyMarginRight, 1, CSSUnitType::CSS_PX);
setInlineStyleProperty(CSSPropertyMarginTop, 1, CSSUnitType::CSS_PX);
setInlineStyleProperty(CSSPropertyMarginBottom, 1, CSSUnitType::CSS_PX);
}
#if ATTACHMENT_LOG_DOCUMENT_TRAFFIC
if (type.connectedToDocument && shouldMonitorDocumentTraffic(document())) {
auto& lastInsertion = lastInsertionInDocument();
auto& lastRemoval = lastRemovalFromDocument();
auto now = WTF::MonotonicTime::now();
if (lastInsertion && lastRemoval && lastRemoval.attachment() != reinterpret_cast<uintptr_t>(this) && lastRemoval.document() == reinterpret_cast<uintptr_t>(&document())) {
RELEASE_LOG(Editing, "HTMLAttachmentElement - quick insert(A)-remove(A)-insert(B) within %fs of the first document[%p] load, stacks below:", document().monotonicTimestamp(), reinterpret_cast<const void*>(lastRemoval.document()));
RELEASE_LOG(Editing, "HTMLAttachmentElement[%p uuid=%s] - 1st insertion %fms ago:%s", reinterpret_cast<const void*>(lastInsertion.attachment()), lastInsertion.uniqueIdentifier().utf8().data(), (now - lastInsertion.time()).milliseconds(), compactStackTrace(lastInsertion.stackTrace()).data());
lastInsertion.reset();
RELEASE_LOG(Editing, "HTMLAttachmentElement[%p uuid=%s] - removal %fms ago:%s", reinterpret_cast<const void*>(lastRemoval.attachment()), lastRemoval.uniqueIdentifier().utf8().data(), (now - lastRemoval.time()).milliseconds(), compactStackTrace(lastRemoval.stackTrace()).data());
lastRemoval.reset();
lastInsertion.capture(*this, now);
RELEASE_LOG(Editing, "HTMLAttachmentElement[%p uuid=%s] - 2nd insertion:%s", reinterpret_cast<const void*>(lastInsertion.attachment()), lastInsertion.uniqueIdentifier().utf8().data(), compactStackTrace(lastInsertion.stackTrace()).data());
} else {
lastInsertion.capture(*this, now);
lastRemoval.reset();
}
}
#endif // ATTACHMENT_LOG_DOCUMENT_TRAFFIC
if (type.connectedToDocument)
document().didInsertAttachmentElement(*this);
return result;
}
void HTMLAttachmentElement::removedFromAncestor(RemovalType type, ContainerNode& ancestor)
{
HTMLElement::removedFromAncestor(type, ancestor);
#if ATTACHMENT_LOG_DOCUMENT_TRAFFIC
if (type.disconnectedFromDocument && shouldMonitorDocumentTraffic(document())) {
if (auto& lastInsertion = lastInsertionInDocument(); lastInsertion && lastInsertion.attachment() == reinterpret_cast<uintptr_t>(this))
lastRemovalFromDocument().capture(*this, WTF::MonotonicTime::now());
}
#endif // ATTACHMENT_LOG_DOCUMENT_TRAFFIC
if (type.disconnectedFromDocument)
document().didRemoveAttachmentElement(*this);
}
String HTMLAttachmentElement::ensureUniqueIdentifier()
{
if (m_uniqueIdentifier.isEmpty())
m_uniqueIdentifier = createVersion4UUIDString();
return m_uniqueIdentifier;
}
void HTMLAttachmentElement::setUniqueIdentifier(const String& uniqueIdentifier)
{
if (m_uniqueIdentifier == uniqueIdentifier)
return;
m_uniqueIdentifier = uniqueIdentifier;
if (auto associatedElement = this->associatedElement())
associatedElement->didUpdateAttachmentIdentifier();
}
AttachmentAssociatedElement* HTMLAttachmentElement::associatedElement() const
{
if (RefPtr host = shadowHost())
return host->asAttachmentAssociatedElement();
return nullptr;
}
AttachmentAssociatedElementType HTMLAttachmentElement::associatedElementType() const
{
if (RefPtr associatedElement = this->associatedElement())
return associatedElement->attachmentAssociatedElementType();
return AttachmentAssociatedElementType::None;
}
void HTMLAttachmentElement::attributeChanged(const QualifiedName& name, const AtomString& oldValue, const AtomString& newValue, AttributeModificationReason attributeModificationReason)
{
switch (name.nodeName()) {
case AttributeNames::actionAttr:
case AttributeNames::subtitleAttr:
case AttributeNames::titleAttr:
case AttributeNames::typeAttr:
invalidateRendering();
break;
case AttributeNames::progressAttr:
if (m_implementation == Implementation::NarrowLayout)
invalidateRendering();
break;
default:
break;
}
HTMLElement::attributeChanged(name, oldValue, newValue, attributeModificationReason);
switch (name.nodeName()) {
case AttributeNames::actionAttr:
if (m_actionTextElement)
m_actionTextElement->setTextContent(String(attachmentActionForDisplay()));
break;
case AttributeNames::titleAttr:
if (m_titleElement)
m_titleElement->setTextContent(attachmentTitleForDisplay());
setNeedsIconRequest();
break;
case AttributeNames::subtitleAttr:
if (m_subtitleElement)
m_subtitleElement->setTextContent(String(attachmentSubtitleForDisplay()));
break;
case AttributeNames::progressAttr:
updateProgress(newValue);
break;
case AttributeNames::saveAttr:
updateSaveButton(!newValue.isNull());
break;
case AttributeNames::typeAttr:
#if ENABLE(SERVICE_CONTROLS)
if (attachmentType() == "application/pdf"_s) {
setImageMenuEnabled(true);
ImageControlsMac::updateImageControls(*this);
}
#endif
setNeedsIconRequest();
break;
default:
break;
}
}
String HTMLAttachmentElement::attachmentTitle() const
{
auto& title = attributeWithoutSynchronization(titleAttr);
if (!title.isEmpty())
return title;
return m_file ? m_file->name() : String();
}
const AtomString& HTMLAttachmentElement::attachmentSubtitle() const
{
return attributeWithoutSynchronization(subtitleAttr);
}
const AtomString& HTMLAttachmentElement::attachmentActionForDisplay() const
{
return attributeWithoutSynchronization(actionAttr);
}
String HTMLAttachmentElement::attachmentTitleForDisplay() const
{
auto title = attachmentTitle();
auto indexOfLastDot = title.reverseFind('.');
if (indexOfLastDot == notFound)
return title;
auto filename = StringView(title).left(indexOfLastDot);
auto extension = StringView(title).substring(indexOfLastDot);
if (isWideLayout() && !filename.is8Bit() && ubidi_getBaseDirection(filename.span16().data(), filename.length()) == UBIDI_RTL) {
// The filename is deemed RTL, it should be exposed as RTL overall, but keeping the extension to the right.
return makeString(
rightToLeftMark, // Make this whole text appear as RTL, the element's `dir="auto"` will right-align and put ellipsis on the left (if needed)
leftToRightIsolate, // Isolate the filename+extension, and force LTR to ensure that the extension always stays on the right.
firstStrongIsolate, // Isolate the filename.
filename, // Note: The filename contains its own bidi characters.
popDirectionalIsolate, // End isolation of the filename.
zeroWidthSpace, // Add a preferred breakpoint before the extension when word-wrapping (so the extension doesn't get split).
extension,
popDirectionalIsolate // And end the filename+extension LTR isolation.
);
}
// Non-RTL or narrow layout: Keep the extension to the right, but the overall direction doesn't need to be exposed.
return makeString(
leftToRightMark, // Force LTR to ensure that the extension always stays on the right.
firstStrongIsolate, // Isolate the filename.
filename, // Note: The filename contains its own bidi characters.
popDirectionalIsolate, // End isolation of the filename.
zeroWidthSpace, // Add a preferred breakpoint before the extension when word-wrapping (so the extension doesn't get split).
extension
);
}
const AtomString& HTMLAttachmentElement::attachmentSubtitleForDisplay() const
{
return attachmentSubtitle();
}
String HTMLAttachmentElement::attachmentType() const
{
return attributeWithoutSynchronization(typeAttr);
}
String HTMLAttachmentElement::attachmentPath() const
{
return attributeWithoutSynchronization(webkitattachmentpathAttr);
}
void HTMLAttachmentElement::updateAttributes(std::optional<uint64_t>&& newFileSize, const AtomString& newContentType, const AtomString& newFilename)
{
RefPtr<HTMLImageElement> enclosingImage;
if (auto associatedElement = this->associatedElement())
enclosingImage = dynamicDowncast<HTMLImageElement>(associatedElement->asHTMLElement());
if (!newFilename.isNull()) {
if (enclosingImage)
enclosingImage->setAttributeWithoutSynchronization(HTMLNames::altAttr, newFilename);
setAttributeWithoutSynchronization(HTMLNames::titleAttr, newFilename);
} else {
if (enclosingImage)
enclosingImage->removeAttribute(HTMLNames::altAttr);
removeAttribute(HTMLNames::titleAttr);
}
if (!newContentType.isNull())
setAttributeWithoutSynchronization(HTMLNames::typeAttr, newContentType);
else
removeAttribute(HTMLNames::typeAttr);
if (newFileSize)
setAttributeWithoutSynchronization(subtitleAttr, PAL::fileSizeDescription(*newFileSize));
else
removeAttribute(subtitleAttr);
setNeedsIconRequest();
invalidateRendering();
}
static bool mimeTypeIsSuitableForInlineImageAttachment(const String& mimeType)
{
return MIMETypeRegistry::isSupportedImageMIMEType(mimeType) || MIMETypeRegistry::isPDFMIMEType(mimeType);
}
void HTMLAttachmentElement::updateAssociatedElementWithData(const String& contentType, Ref<FragmentedSharedBuffer>&& buffer)
{
if (buffer->isEmpty())
return;
RefPtr associatedElement = this->associatedElement();
if (!associatedElement)
return;
String mimeType = contentType;
#if PLATFORM(COCOA)
if (isDeclaredUTI(contentType))
mimeType = MIMETypeFromUTI(contentType);
#endif
if (!mimeTypeIsSuitableForInlineImageAttachment(mimeType))
return;
auto associatedElementType = associatedElement->attachmentAssociatedElementType();
associatedElement->asHTMLElement().setAttributeWithoutSynchronization((associatedElementType == AttachmentAssociatedElementType::Source) ? HTMLNames::srcsetAttr : HTMLNames::srcAttr, AtomString { DOMURL::createObjectURL(document(), Blob::create(protectedDocument().ptr(), buffer->extractData(), mimeType)) });
}
void HTMLAttachmentElement::updateImage()
{
if (!m_imageElement)
return;
if (!m_iconForWideLayout.isEmpty()) {
dispatchEvent(Event::create(eventNames().loadeddataEvent, Event::CanBubble::No, Event::IsCancelable::No));
m_imageElement->setAttributeWithoutSynchronization(srcAttr, AtomString { DOMURL::createObjectURL(document(), Blob::create(protectedDocument().ptr(), Vector<uint8_t>(m_iconForWideLayout), "image/png"_s)) });
return;
}
m_imageElement->removeAttribute(srcAttr);
}
void HTMLAttachmentElement::updateIconForNarrowLayout(const RefPtr<Image>& icon, const WebCore::FloatSize& iconSize)
{
ASSERT(!isWideLayout());
if (!icon) {
dispatchEvent(Event::create(eventNames().loadingerrorEvent, Event::CanBubble::No, Event::IsCancelable::No));
return;
}
m_icon = icon;
m_iconSize = iconSize;
invalidateRendering();
dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No));
}
void HTMLAttachmentElement::updateIconForWideLayout(Vector<uint8_t>&& iconSrcData)
{
ASSERT(isWideLayout());
if (iconSrcData.isEmpty()) {
dispatchEvent(Event::create(eventNames().loadingerrorEvent, Event::CanBubble::No, Event::IsCancelable::No));
return;
}
m_iconForWideLayout = WTFMove(iconSrcData);
updateImage();
}
void HTMLAttachmentElement::setNeedsIconRequest()
{
m_needsIconRequest = true;
}
void HTMLAttachmentElement::requestWideLayoutIconIfNeeded()
{
if (!m_needsIconRequest)
return;
if (!document().page() || !document().page()->attachmentElementClient())
return;
m_needsIconRequest = false;
if (!m_imageElement)
return;
// FIXME: Remove after rdar://136373445 is fixed.
#if PLATFORM(MAC)
RELEASE_LOG(Editing, "HTMLAttachmentElement[uuid=%s] requestAttachmentIcon with type='%s'", uniqueIdentifier().utf8().data(), attachmentType().utf8().data());
#endif
dispatchEvent(Event::create(eventNames().beforeloadEvent, Event::CanBubble::No, Event::IsCancelable::No));
document().page()->attachmentElementClient()->requestAttachmentIcon(uniqueIdentifier(), FloatSize(attachmentIconSize, attachmentIconSize));
}
void HTMLAttachmentElement::requestIconIfNeededWithSize(const FloatSize& size)
{
ASSERT(!isWideLayout());
if (!m_needsIconRequest)
return;
if (!document().page() || !document().page()->attachmentElementClient())
return;
m_needsIconRequest = false;
queueTaskToDispatchEvent(TaskSource::InternalAsyncTask, Event::create(eventNames().beforeloadEvent, Event::CanBubble::No, Event::IsCancelable::No));
document().page()->attachmentElementClient()->requestAttachmentIcon(uniqueIdentifier(), size);
}
#if ENABLE(SERVICE_CONTROLS)
bool HTMLAttachmentElement::childShouldCreateRenderer(const Node& child) const
{
return hasShadowRootParent(child) && HTMLElement::childShouldCreateRenderer(child);
}
#endif
} // namespace WebCore
#endif // ENABLE(ATTACHMENT_ELEMENT)