blob: a26294639353e73295437068d5ea192fcb0fdd69 [file] [log] [blame]
/*
* Copyright (C) 2014-2020 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 "DictionaryLookup.h"
#if PLATFORM(MAC) && !ENABLE(REVEAL)
#import "Document.h"
#import "Editing.h"
#import "FocusController.h"
#import "Frame.h"
#import "FrameSelection.h"
#import "HitTestResult.h"
#import "Page.h"
#import "Range.h"
#import "RenderObject.h"
#import "TextIterator.h"
#import "VisiblePosition.h"
#import "VisibleSelection.h"
#import "VisibleUnits.h"
#import <Quartz/Quartz.h>
#import <pal/spi/mac/LookupSPI.h>
#import <pal/spi/mac/NSImmediateActionGestureRecognizerSPI.h>
#import <wtf/BlockObjCExceptions.h>
#import <wtf/RefPtr.h>
namespace WebCore {
static NSRange tokenRange(const String& string, NSRange range, NSDictionary **options)
{
if (!PAL::getLULookupDefinitionModuleClass())
return NSMakeRange(NSNotFound, 0);
BEGIN_BLOCK_OBJC_EXCEPTIONS
return [PAL::getLULookupDefinitionModuleClass() tokenRangeForString:string range:range options:options];
END_BLOCK_OBJC_EXCEPTIONS
return NSMakeRange(NSNotFound, 0);
}
static bool selectionContainsPosition(const VisiblePosition& position, const VisibleSelection& selection)
{
if (!selection.isRange())
return false;
auto selectedRange = selection.firstRange();
return selectedRange && contains<ComposedTree>(*selectedRange, makeBoundaryPoint(position));
}
std::optional<std::tuple<SimpleRange, NSDictionary *>> DictionaryLookup::rangeForSelection(const VisibleSelection& selection)
{
auto selectedRange = selection.toNormalizedRange();
if (!selectedRange)
return std::nullopt;
// Since we already have the range we want, we just need to grab the returned options.
auto selectionStart = selection.visibleStart();
auto selectionEnd = selection.visibleEnd();
// As context, we are going to use the surrounding paragraphs of text.
auto paragraphRange = makeSimpleRange(startOfParagraph(selectionStart), endOfParagraph(selectionEnd));
if (!paragraphRange)
return std::nullopt;
auto selectionRange = *makeSimpleRange(selectionStart, selectionEnd);
NSDictionary *options = nil;
tokenRange(plainText(*paragraphRange), characterRange(*paragraphRange, selectionRange), &options);
return { { *selectedRange, options } };
}
std::optional<std::tuple<SimpleRange, NSDictionary *>> DictionaryLookup::rangeAtHitTestResult(const HitTestResult& hitTestResult)
{
auto* node = hitTestResult.innerNonSharedNode();
if (!node || !node->renderer())
return std::nullopt;
auto* frame = node->document().frame();
if (!frame)
return std::nullopt;
// Don't do anything if there is no character at the point.
auto framePoint = hitTestResult.roundedPointInInnerNodeFrame();
if (!frame->rangeForPoint(framePoint))
return std::nullopt;
auto position = frame->visiblePositionForPoint(framePoint);
if (position.isNull())
position = firstPositionInOrBeforeNode(node);
// If we hit the selection, use that instead of letting Lookup decide the range.
auto selection = frame->page()->focusController().focusedOrMainFrame().selection().selection();
if (selectionContainsPosition(position, selection))
return rangeForSelection(selection);
VisibleSelection selectionAccountingForLineRules { position };
selectionAccountingForLineRules.expandUsingGranularity(TextGranularity::WordGranularity);
position = selectionAccountingForLineRules.start();
// As context, we are going to use 250 characters of text before and after the point.
auto fullCharacterRange = rangeExpandedAroundPositionByCharacters(position, 250);
if (!fullCharacterRange)
return std::nullopt;
auto rangeToPosition = makeSimpleRange(fullCharacterRange->start, position);
if (!rangeToPosition)
return std::nullopt;
NSRange rangeToPass = NSMakeRange(characterCount(*rangeToPosition), 0);
NSDictionary *options = nil;
auto extractedRange = tokenRange(plainText(*fullCharacterRange), rangeToPass, &options);
// tokenRange sometimes returns {NSNotFound, 0} if it was unable to determine a good string.
// FIXME (159063): We shouldn't need to check for zero length here.
if (extractedRange.location == NSNotFound || !extractedRange.length)
return std::nullopt;
return { { resolveCharacterRange(*fullCharacterRange, extractedRange), options } };
}
static void expandSelectionByCharacters(PDFSelection *selection, NSInteger numberOfCharactersToExpand, NSInteger& charactersAddedBeforeStart, NSInteger& charactersAddedAfterEnd)
{
BEGIN_BLOCK_OBJC_EXCEPTIONS
size_t originalLength = selection.string.length;
[selection extendSelectionAtStart:numberOfCharactersToExpand];
charactersAddedBeforeStart = selection.string.length - originalLength;
[selection extendSelectionAtEnd:numberOfCharactersToExpand];
charactersAddedAfterEnd = selection.string.length - originalLength - charactersAddedBeforeStart;
END_BLOCK_OBJC_EXCEPTIONS
}
std::tuple<NSString *, NSDictionary *> DictionaryLookup::stringForPDFSelection(PDFSelection *selection)
{
BEGIN_BLOCK_OBJC_EXCEPTIONS
// Don't do anything if there is no character at the point.
if (!selection || !selection.string.length)
return { @"", nil };
RetainPtr<PDFSelection> selectionForLookup = adoptNS([selection copy]);
// As context, we are going to use 250 characters of text before and after the point.
auto originalLength = [selectionForLookup string].length;
NSInteger charactersAddedBeforeStart = 0;
NSInteger charactersAddedAfterEnd = 0;
expandSelectionByCharacters(selectionForLookup.get(), 250, charactersAddedBeforeStart, charactersAddedAfterEnd);
auto fullPlainTextString = [selectionForLookup string];
auto rangeToPass = NSMakeRange(charactersAddedBeforeStart, 0);
NSDictionary *options = nil;
auto extractedRange = tokenRange(fullPlainTextString, rangeToPass, &options);
// This function sometimes returns {NSNotFound, 0} if it was unable to determine a good string.
if (extractedRange.location == NSNotFound)
return { selection.string, options };
NSInteger lookupAddedBefore = rangeToPass.location - extractedRange.location;
NSInteger lookupAddedAfter = (extractedRange.location + extractedRange.length) - (rangeToPass.location + originalLength);
[selection extendSelectionAtStart:lookupAddedBefore];
[selection extendSelectionAtEnd:lookupAddedAfter];
ASSERT([selection.string isEqualToString:[fullPlainTextString substringWithRange:extractedRange]]);
return { selection.string, options };
END_BLOCK_OBJC_EXCEPTIONS
return { @"", nil };
}
static id <NSImmediateActionAnimationController> showPopupOrCreateAnimationController(bool createAnimationController, const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback)
{
BEGIN_BLOCK_OBJC_EXCEPTIONS
if (!PAL::getLULookupDefinitionModuleClass())
return nil;
RetainPtr<NSMutableDictionary> mutableOptions = adoptNS([[NSMutableDictionary alloc] init]);
if (NSDictionary *options = dictionaryPopupInfo.options.get())
[mutableOptions addEntriesFromDictionary:options];
auto textIndicator = TextIndicator::create(dictionaryPopupInfo.textIndicator);
if (PAL::canLoad_Lookup_LUTermOptionDisableSearchTermIndicator() && textIndicator.get().contentImage()) {
textIndicatorInstallationCallback(textIndicator.get());
[mutableOptions setObject:@YES forKey:PAL::get_Lookup_LUTermOptionDisableSearchTermIndicator()];
FloatRect firstTextRectInViewCoordinates = textIndicator.get().textRectsInBoundingRectCoordinates()[0];
FloatRect textBoundingRectInViewCoordinates = textIndicator.get().textBoundingRectInRootViewCoordinates();
if (rootViewToViewConversionCallback)
textBoundingRectInViewCoordinates = rootViewToViewConversionCallback(textBoundingRectInViewCoordinates);
firstTextRectInViewCoordinates.moveBy(textBoundingRectInViewCoordinates.location());
if (createAnimationController)
return [PAL::getLULookupDefinitionModuleClass() lookupAnimationControllerForTerm:dictionaryPopupInfo.attributedString.get() relativeToRect:firstTextRectInViewCoordinates ofView:view options:mutableOptions.get()];
[PAL::getLULookupDefinitionModuleClass() showDefinitionForTerm:dictionaryPopupInfo.attributedString.get() relativeToRect:firstTextRectInViewCoordinates ofView:view options:mutableOptions.get()];
return nil;
}
NSPoint textBaselineOrigin = dictionaryPopupInfo.origin;
// Convert to screen coordinates.
textBaselineOrigin = [view convertPoint:textBaselineOrigin toView:nil];
textBaselineOrigin = [view.window convertRectToScreen:NSMakeRect(textBaselineOrigin.x, textBaselineOrigin.y, 0, 0)].origin;
if (createAnimationController)
return [PAL::getLULookupDefinitionModuleClass() lookupAnimationControllerForTerm:dictionaryPopupInfo.attributedString.get() atLocation:textBaselineOrigin options:mutableOptions.get()];
[PAL::getLULookupDefinitionModuleClass() showDefinitionForTerm:dictionaryPopupInfo.attributedString.get() atLocation:textBaselineOrigin options:mutableOptions.get()];
return nil;
END_BLOCK_OBJC_EXCEPTIONS
return nil;
}
void DictionaryLookup::showPopup(const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback, WTF::Function<void()>&& clearTextIndicator)
{
UNUSED_PARAM(clearTextIndicator);
showPopupOrCreateAnimationController(false, dictionaryPopupInfo, view, textIndicatorInstallationCallback, rootViewToViewConversionCallback);
}
void DictionaryLookup::hidePopup()
{
BEGIN_BLOCK_OBJC_EXCEPTIONS
if (!PAL::getLULookupDefinitionModuleClass())
return;
[PAL::getLULookupDefinitionModuleClass() hideDefinition];
END_BLOCK_OBJC_EXCEPTIONS
}
id <NSImmediateActionAnimationController> DictionaryLookup::animationControllerForPopup(const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback, WTF::Function<void()>&& clearTextIndicator)
{
UNUSED_PARAM(clearTextIndicator);
return showPopupOrCreateAnimationController(true, dictionaryPopupInfo, view, textIndicatorInstallationCallback, rootViewToViewConversionCallback);
}
} // namespace WebCore
#endif // PLATFORM(MAC) && !ENABLE(REVEAL)