blob: 33ecd7be245cdb5987abe0109570faee2ab9d61d [file] [log] [blame]
/*
* Copyright (C) 2016-2019 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.
*/
#import "config.h"
#import "WebPage.h"
#import "AttributedString.h"
#import "InsertTextOptions.h"
#import "LoadParameters.h"
#import "PluginView.h"
#import "WKAccessibilityWebPageObjectBase.h"
#import "WebPageProxyMessages.h"
#import "WebPaymentCoordinator.h"
#import "WebRemoteObjectRegistry.h"
#import <pal/spi/cocoa/LaunchServicesSPI.h>
#import <WebCore/DictionaryLookup.h>
#import <WebCore/Editing.h>
#import <WebCore/Editor.h>
#import <WebCore/EventHandler.h>
#import <WebCore/EventNames.h>
#import <WebCore/FocusController.h>
#import <WebCore/FrameView.h>
#import <WebCore/HTMLConverter.h>
#import <WebCore/HTMLOListElement.h>
#import <WebCore/HTMLUListElement.h>
#import <WebCore/HitTestResult.h>
#import <WebCore/NodeRenderStyle.h>
#import <WebCore/PaymentCoordinator.h>
#import <WebCore/PlatformMediaSessionManager.h>
#import <WebCore/Range.h>
#import <WebCore/RenderElement.h>
#import <WebCore/SimpleRange.h>
#if PLATFORM(COCOA)
namespace WebKit {
void WebPage::platformDidReceiveLoadParameters(const LoadParameters& loadParameters)
{
m_dataDetectionContext = loadParameters.dataDetectionContext;
}
void WebPage::requestActiveNowPlayingSessionInfo(CallbackID callbackID)
{
bool hasActiveSession = false;
String title = emptyString();
double duration = NAN;
double elapsedTime = NAN;
uint64_t uniqueIdentifier = 0;
bool registeredAsNowPlayingApplication = false;
if (auto* sharedManager = WebCore::PlatformMediaSessionManager::sharedManagerIfExists()) {
hasActiveSession = sharedManager->hasActiveNowPlayingSession();
title = sharedManager->lastUpdatedNowPlayingTitle();
duration = sharedManager->lastUpdatedNowPlayingDuration();
elapsedTime = sharedManager->lastUpdatedNowPlayingElapsedTime();
uniqueIdentifier = sharedManager->lastUpdatedNowPlayingInfoUniqueIdentifier().toUInt64();
registeredAsNowPlayingApplication = sharedManager->registeredAsNowPlayingApplication();
}
send(Messages::WebPageProxy::NowPlayingInfoCallback(hasActiveSession, registeredAsNowPlayingApplication, title, duration, elapsedTime, uniqueIdentifier, callbackID));
}
void WebPage::performDictionaryLookupAtLocation(const FloatPoint& floatPoint)
{
if (auto* pluginView = pluginViewForFrame(&m_page->mainFrame())) {
if (pluginView->performDictionaryLookupAtLocation(floatPoint))
return;
}
// Find the frame the point is over.
constexpr OptionSet<HitTestRequest::RequestType> hitType { HitTestRequest::ReadOnly, HitTestRequest::Active, HitTestRequest::DisallowUserAgentShadowContent, HitTestRequest::AllowChildFrameContent };
HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint(m_page->mainFrame().view()->windowToContents(roundedIntPoint(floatPoint)), hitType);
auto [range, options] = DictionaryLookup::rangeAtHitTestResult(result);
if (!range)
return;
auto* frame = result.innerNonSharedNode() ? result.innerNonSharedNode()->document().frame() : &m_page->focusController().focusedOrMainFrame();
if (!frame)
return;
performDictionaryLookupForRange(*frame, *range, options, TextIndicatorPresentationTransition::Bounce);
}
void WebPage::performDictionaryLookupForSelection(Frame& frame, const VisibleSelection& selection, TextIndicatorPresentationTransition presentationTransition)
{
auto [selectedRange, options] = DictionaryLookup::rangeForSelection(selection);
if (selectedRange)
performDictionaryLookupForRange(frame, *selectedRange, options, presentationTransition);
}
void WebPage::performDictionaryLookupOfCurrentSelection()
{
auto& frame = m_page->focusController().focusedOrMainFrame();
performDictionaryLookupForSelection(frame, frame.selection().selection(), TextIndicatorPresentationTransition::BounceAndCrossfade);
}
void WebPage::performDictionaryLookupForRange(Frame& frame, Range& range, NSDictionary *options, TextIndicatorPresentationTransition presentationTransition)
{
send(Messages::WebPageProxy::DidPerformDictionaryLookup(dictionaryPopupInfoForRange(frame, range, options, presentationTransition)));
}
DictionaryPopupInfo WebPage::dictionaryPopupInfoForRange(Frame& frame, Range& range, NSDictionary *options, TextIndicatorPresentationTransition presentationTransition)
{
Editor& editor = frame.editor();
editor.setIsGettingDictionaryPopupInfo(true);
DictionaryPopupInfo dictionaryPopupInfo;
if (range.text().stripWhiteSpace().isEmpty()) {
editor.setIsGettingDictionaryPopupInfo(false);
return dictionaryPopupInfo;
}
Vector<FloatQuad> quads;
range.absoluteTextQuads(quads);
if (quads.isEmpty()) {
editor.setIsGettingDictionaryPopupInfo(false);
return dictionaryPopupInfo;
}
IntRect rangeRect = frame.view()->contentsToWindow(quads[0].enclosingBoundingBox());
const RenderStyle* style = range.startContainer().renderStyle();
float scaledAscent = style ? style->fontMetrics().ascent() * pageScaleFactor() : 0;
dictionaryPopupInfo.origin = FloatPoint(rangeRect.x(), rangeRect.y() + scaledAscent);
dictionaryPopupInfo.options = options;
#if PLATFORM(MAC)
NSAttributedString *nsAttributedString = editingAttributedStringFromRange(range, IncludeImagesInAttributedString::No);
RetainPtr<NSMutableAttributedString> scaledNSAttributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:[nsAttributedString string]]);
NSFontManager *fontManager = [NSFontManager sharedFontManager];
[nsAttributedString enumerateAttributesInRange:NSMakeRange(0, [nsAttributedString length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
RetainPtr<NSMutableDictionary> scaledAttributes = adoptNS([attributes mutableCopy]);
NSFont *font = [scaledAttributes objectForKey:NSFontAttributeName];
if (font)
font = [fontManager convertFont:font toSize:font.pointSize * pageScaleFactor()];
if (font)
[scaledAttributes setObject:font forKey:NSFontAttributeName];
[scaledNSAttributedString addAttributes:scaledAttributes.get() range:range];
}];
#endif // PLATFORM(MAC)
TextIndicatorOptions indicatorOptions = TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges;
if (presentationTransition == TextIndicatorPresentationTransition::BounceAndCrossfade)
indicatorOptions |= TextIndicatorOptionIncludeSnapshotWithSelectionHighlight;
auto textIndicator = TextIndicator::createWithRange(range, indicatorOptions, presentationTransition);
if (!textIndicator) {
editor.setIsGettingDictionaryPopupInfo(false);
return dictionaryPopupInfo;
}
dictionaryPopupInfo.textIndicator = textIndicator->data();
#if PLATFORM(MAC)
dictionaryPopupInfo.attributedString = scaledNSAttributedString;
#elif PLATFORM(MACCATALYST)
dictionaryPopupInfo.attributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:range.text()]);
#endif
editor.setIsGettingDictionaryPopupInfo(false);
return dictionaryPopupInfo;
}
void WebPage::insertDictatedTextAsync(const String& text, const EditingRange& replacementEditingRange, const Vector<WebCore::DictationAlternative>& dictationAlternativeLocations, InsertTextOptions&& options)
{
auto& frame = m_page->focusController().focusedOrMainFrame();
Ref<Frame> protector { frame };
if (replacementEditingRange.location != notFound) {
auto replacementRange = EditingRange::toRange(frame, replacementEditingRange);
if (replacementRange)
frame.selection().setSelection(VisibleSelection { *replacementRange, SEL_DEFAULT_AFFINITY });
}
if (options.registerUndoGroup)
send(Messages::WebPageProxy::RegisterInsertionUndoGrouping { });
RefPtr<Element> focusedElement = frame.document() ? frame.document()->focusedElement() : nullptr;
if (focusedElement && options.shouldSimulateKeyboardInput)
focusedElement->dispatchEvent(Event::create(eventNames().keydownEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
ASSERT(!frame.editor().hasComposition());
frame.editor().insertDictatedText(text, dictationAlternativeLocations, nullptr /* triggeringEvent */);
if (focusedElement && options.shouldSimulateKeyboardInput) {
focusedElement->dispatchEvent(Event::create(eventNames().keyupEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
focusedElement->dispatchEvent(Event::create(eventNames().changeEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes));
}
}
void WebPage::accessibilityTransferRemoteToken(RetainPtr<NSData> remoteToken)
{
IPC::DataReference dataToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteToken bytes]), [remoteToken length]);
send(Messages::WebPageProxy::RegisterWebProcessAccessibilityToken(dataToken));
}
#if ENABLE(APPLE_PAY)
WebPaymentCoordinator* WebPage::paymentCoordinator()
{
if (!m_page)
return nullptr;
auto& client = m_page->paymentCoordinator().client();
return is<WebPaymentCoordinator>(client) ? downcast<WebPaymentCoordinator>(&client) : nullptr;
}
#endif
void WebPage::getContentsAsAttributedString(CompletionHandler<void(const AttributedString&)>&& completionHandler)
{
auto* documentElement = m_page->mainFrame().document()->documentElement();
if (!documentElement) {
completionHandler({ });
return;
}
NSDictionary* documentAttributes = nil;
auto attributedString = attributedStringFromRange(rangeOfContents(*documentElement), &documentAttributes);
completionHandler({ WTFMove(attributedString), WTFMove(documentAttributes) });
}
void WebPage::setRemoteObjectRegistry(WebRemoteObjectRegistry* registry)
{
m_remoteObjectRegistry = makeWeakPtr(registry);
}
WebRemoteObjectRegistry* WebPage::remoteObjectRegistry()
{
return m_remoteObjectRegistry.get();
}
void WebPage::updateMockAccessibilityElementAfterCommittingLoad()
{
auto* document = mainFrame()->document();
[m_mockAccessibilityElement setHasMainFramePlugin:document ? document->isPluginDocument() : false];
}
RetainPtr<CFDataRef> WebPage::pdfSnapshotAtSize(IntRect rect, IntSize bitmapSize, SnapshotOptions options)
{
Frame* coreFrame = m_mainFrame->coreFrame();
if (!coreFrame)
return nullptr;
FrameView* frameView = coreFrame->view();
if (!frameView)
return nullptr;
auto data = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
auto dataConsumer = adoptCF(CGDataConsumerCreateWithCFData(data.get()));
auto mediaBox = CGRectMake(0, 0, bitmapSize.width(), bitmapSize.height());
auto pdfContext = adoptCF(CGPDFContextCreate(dataConsumer.get(), &mediaBox, nullptr));
int64_t remainingHeight = bitmapSize.height();
int64_t nextRectY = rect.y();
while (remainingHeight > 0) {
// PDFs have a per-page height limit of 200 inches at 72dpi.
// We'll export one PDF page at a time, up to that maximum height.
static const int64_t maxPageHeight = 72 * 200;
bitmapSize.setHeight(std::min(remainingHeight, maxPageHeight));
rect.setHeight(bitmapSize.height());
rect.setY(nextRectY);
CGRect mediaBox = CGRectMake(0, 0, bitmapSize.width(), bitmapSize.height());
auto mediaBoxData = adoptCF(CFDataCreate(NULL, (const UInt8 *)&mediaBox, sizeof(CGRect)));
auto dictionary = (CFDictionaryRef)@{
(NSString *)kCGPDFContextMediaBox : (NSData *)mediaBoxData.get()
};
CGPDFContextBeginPage(pdfContext.get(), dictionary);
GraphicsContext graphicsContext { pdfContext.get() };
graphicsContext.scale({ 1, -1 });
graphicsContext.translate(0, -bitmapSize.height());
paintSnapshotAtSize(rect, bitmapSize, options, *coreFrame, *frameView, graphicsContext);
CGPDFContextEndPage(pdfContext.get());
nextRectY += bitmapSize.height();
remainingHeight -= maxPageHeight;
}
CGPDFContextClose(pdfContext.get());
return data;
}
void WebPage::getProcessDisplayName(CompletionHandler<void(String&&)>&& completionHandler)
{
#if PLATFORM(MAC)
completionHandler(adoptCF((CFStringRef)_LSCopyApplicationInformationItem(kLSDefaultSessionID, _LSGetCurrentApplicationASN(), _kLSDisplayNameKey)).get());
#else
completionHandler({ });
#endif
}
void WebPage::getPlatformEditorStateCommon(const Frame& frame, EditorState& result) const
{
if (result.isMissingPostLayoutData)
return;
const auto& selection = frame.selection().selection();
if (!result.isContentEditable || selection.isNone())
return;
auto& postLayoutData = result.postLayoutData();
if (auto editingStyle = EditingStyle::styleAtSelectionStart(selection)) {
if (editingStyle->hasStyle(CSSPropertyFontWeight, "bold"_s))
postLayoutData.typingAttributes |= AttributeBold;
if (editingStyle->hasStyle(CSSPropertyFontStyle, "italic"_s) || editingStyle->hasStyle(CSSPropertyFontStyle, "oblique"_s))
postLayoutData.typingAttributes |= AttributeItalics;
if (editingStyle->hasStyle(CSSPropertyWebkitTextDecorationsInEffect, "underline"_s))
postLayoutData.typingAttributes |= AttributeUnderline;
if (auto* styleProperties = editingStyle->style()) {
bool isLeftToRight = styleProperties->propertyAsValueID(CSSPropertyDirection) == CSSValueLtr;
switch (styleProperties->propertyAsValueID(CSSPropertyTextAlign)) {
case CSSValueRight:
case CSSValueWebkitRight:
postLayoutData.textAlignment = RightAlignment;
break;
case CSSValueLeft:
case CSSValueWebkitLeft:
postLayoutData.textAlignment = LeftAlignment;
break;
case CSSValueCenter:
case CSSValueWebkitCenter:
postLayoutData.textAlignment = CenterAlignment;
break;
case CSSValueJustify:
postLayoutData.textAlignment = JustifiedAlignment;
break;
case CSSValueStart:
postLayoutData.textAlignment = isLeftToRight ? LeftAlignment : RightAlignment;
break;
case CSSValueEnd:
postLayoutData.textAlignment = isLeftToRight ? RightAlignment : LeftAlignment;
break;
default:
break;
}
if (auto textColor = styleProperties->propertyAsColor(CSSPropertyColor))
postLayoutData.textColor = *textColor;
}
}
if (auto* enclosingListElement = enclosingList(selection.start().containerNode())) {
if (is<HTMLUListElement>(*enclosingListElement))
postLayoutData.enclosingListType = UnorderedList;
else if (is<HTMLOListElement>(*enclosingListElement))
postLayoutData.enclosingListType = OrderedList;
else
ASSERT_NOT_REACHED();
}
postLayoutData.baseWritingDirection = frame.editor().baseWritingDirectionForSelectionStart();
}
} // namespace WebKit
#endif // PLATFORM(COCOA)