Remove WebCoreSystemInterface
[WebKit-https.git] / Source / WebCore / editing / mac / DictionaryLookup.mm
1 /*
2  * Copyright (C) 2014 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 #import "config.h"
27 #import "DictionaryLookup.h"
28
29 #if PLATFORM(MAC)
30
31 #import "Document.h"
32 #import "Editing.h"
33 #import "FocusController.h"
34 #import "Frame.h"
35 #import "FrameSelection.h"
36 #import "HTMLConverter.h"
37 #import "HitTestResult.h"
38 #import "Page.h"
39 #import "Range.h"
40 #import "RenderObject.h"
41 #import "TextIterator.h"
42 #import "VisiblePosition.h"
43 #import "VisibleSelection.h"
44 #import "VisibleUnits.h"
45 #import <PDFKit/PDFKit.h>
46 #import <pal/spi/mac/LookupSPI.h>
47 #import <pal/spi/mac/NSImmediateActionGestureRecognizerSPI.h>
48 #import <wtf/BlockObjCExceptions.h>
49 #import <wtf/RefPtr.h>
50
51 SOFT_LINK_CONSTANT_MAY_FAIL(Lookup, LUTermOptionDisableSearchTermIndicator, NSString *)
52
53 namespace WebCore {
54
55 static bool selectionContainsPosition(const VisiblePosition& position, const VisibleSelection& selection)
56 {
57     if (!selection.isRange())
58         return false;
59
60     RefPtr<Range> selectedRange = selection.toNormalizedRange();
61     if (!selectedRange)
62         return false;
63
64     return selectedRange->contains(position);
65 }
66
67 RefPtr<Range> DictionaryLookup::rangeForSelection(const VisibleSelection& selection, NSDictionary **options)
68 {
69     RefPtr<Range> selectedRange = selection.toNormalizedRange();
70     if (!selectedRange)
71         return nullptr;
72
73     VisiblePosition selectionStart = selection.visibleStart();
74     VisiblePosition selectionEnd = selection.visibleEnd();
75
76     // As context, we are going to use the surrounding paragraphs of text.
77     VisiblePosition paragraphStart = startOfParagraph(selectionStart);
78     VisiblePosition paragraphEnd = endOfParagraph(selectionEnd);
79
80     int lengthToSelectionStart = TextIterator::rangeLength(makeRange(paragraphStart, selectionStart).get());
81     int lengthToSelectionEnd = TextIterator::rangeLength(makeRange(paragraphStart, selectionEnd).get());
82     NSRange rangeToPass = NSMakeRange(lengthToSelectionStart, lengthToSelectionEnd - lengthToSelectionStart);
83
84     String fullPlainTextString = plainText(makeRange(paragraphStart, paragraphEnd).get());
85
86     BEGIN_BLOCK_OBJC_EXCEPTIONS;
87     // Since we already have the range we want, we just need to grab the returned options.
88     if (Class luLookupDefinitionModule = getLULookupDefinitionModuleClass())
89         [luLookupDefinitionModule tokenRangeForString:fullPlainTextString range:rangeToPass options:options];
90     END_BLOCK_OBJC_EXCEPTIONS;
91
92     return selectedRange;
93 }
94
95 RefPtr<Range> DictionaryLookup::rangeAtHitTestResult(const HitTestResult& hitTestResult, NSDictionary **options)
96 {
97     Node* node = hitTestResult.innerNonSharedNode();
98     if (!node)
99         return nullptr;
100
101     auto renderer = node->renderer();
102     if (!renderer)
103         return nullptr;
104
105     Frame* frame = node->document().frame();
106     if (!frame)
107         return nullptr;
108
109     // Don't do anything if there is no character at the point.
110     IntPoint framePoint = hitTestResult.roundedPointInInnerNodeFrame();
111     if (!frame->rangeForPoint(framePoint))
112         return nullptr;
113
114     VisiblePosition position = frame->visiblePositionForPoint(framePoint);
115     if (position.isNull())
116         position = firstPositionInOrBeforeNode(node);
117
118     // If we hit the selection, use that instead of letting Lookup decide the range.
119     VisibleSelection selection = frame->page()->focusController().focusedOrMainFrame().selection().selection();
120     if (selectionContainsPosition(position, selection))
121         return DictionaryLookup::rangeForSelection(selection, options);
122
123     VisibleSelection selectionAccountingForLineRules = VisibleSelection(position);
124     selectionAccountingForLineRules.expandUsingGranularity(WordGranularity);
125     position = selectionAccountingForLineRules.start();
126
127     // As context, we are going to use 250 characters of text before and after the point.
128     RefPtr<Range> fullCharacterRange = rangeExpandedAroundPositionByCharacters(position, 250);
129     if (!fullCharacterRange)
130         return nullptr;
131
132     BEGIN_BLOCK_OBJC_EXCEPTIONS;
133
134     NSRange rangeToPass = NSMakeRange(TextIterator::rangeLength(makeRange(fullCharacterRange->startPosition(), position).get()), 0);
135
136     String fullPlainTextString = plainText(fullCharacterRange.get());
137
138     NSRange extractedRange = NSMakeRange(rangeToPass.location, 0);
139     if (Class luLookupDefinitionModule = getLULookupDefinitionModuleClass())
140         extractedRange = [luLookupDefinitionModule tokenRangeForString:fullPlainTextString range:rangeToPass options:options];
141
142     // This function sometimes returns {NSNotFound, 0} if it was unable to determine a good string.
143     // FIXME (159063): We shouldn't need to check for zero length here.
144     if (extractedRange.location == NSNotFound || extractedRange.length == 0)
145         return nullptr;
146
147     return TextIterator::subrange(*fullCharacterRange, extractedRange.location, extractedRange.length);
148
149     END_BLOCK_OBJC_EXCEPTIONS;
150     return nullptr;
151 }
152
153 static void expandSelectionByCharacters(PDFSelection *selection, NSInteger numberOfCharactersToExpand, NSInteger& charactersAddedBeforeStart, NSInteger& charactersAddedAfterEnd)
154 {
155     BEGIN_BLOCK_OBJC_EXCEPTIONS;
156
157     size_t originalLength = selection.string.length;
158     [selection extendSelectionAtStart:numberOfCharactersToExpand];
159     
160     charactersAddedBeforeStart = selection.string.length - originalLength;
161     
162     [selection extendSelectionAtEnd:numberOfCharactersToExpand];
163     charactersAddedAfterEnd = selection.string.length - originalLength - charactersAddedBeforeStart;
164
165     END_BLOCK_OBJC_EXCEPTIONS;
166 }
167
168 NSString *DictionaryLookup::stringForPDFSelection(PDFSelection *selection, NSDictionary **options)
169 {
170     BEGIN_BLOCK_OBJC_EXCEPTIONS;
171
172     // Don't do anything if there is no character at the point.
173     if (!selection || !selection.string.length)
174         return @"";
175     
176     RetainPtr<PDFSelection> selectionForLookup = adoptNS([selection copy]);
177     
178     // As context, we are going to use 250 characters of text before and after the point.
179     NSInteger originalLength = [selectionForLookup string].length;
180     NSInteger charactersAddedBeforeStart = 0;
181     NSInteger charactersAddedAfterEnd = 0;
182     expandSelectionByCharacters(selectionForLookup.get(), 250, charactersAddedBeforeStart, charactersAddedAfterEnd);
183     
184     NSString *fullPlainTextString = [selectionForLookup string];
185     NSRange rangeToPass = NSMakeRange(charactersAddedBeforeStart, 0);
186     
187     NSRange extractedRange = NSMakeRange(rangeToPass.location, 0);
188     if (Class luLookupDefinitionModule = getLULookupDefinitionModuleClass())
189         extractedRange = [luLookupDefinitionModule tokenRangeForString:fullPlainTextString range:rangeToPass options:options];
190     
191     // This function sometimes returns {NSNotFound, 0} if it was unable to determine a good string.
192     if (extractedRange.location == NSNotFound)
193         return selection.string;
194     
195     NSInteger lookupAddedBefore = rangeToPass.location - extractedRange.location;
196     NSInteger lookupAddedAfter = (extractedRange.location + extractedRange.length) - (rangeToPass.location + originalLength);
197     
198     [selection extendSelectionAtStart:lookupAddedBefore];
199     [selection extendSelectionAtEnd:lookupAddedAfter];
200     
201     ASSERT([selection.string isEqualToString:[fullPlainTextString substringWithRange:extractedRange]]);
202     return selection.string;
203
204     END_BLOCK_OBJC_EXCEPTIONS;
205     return nil;
206 }
207
208 static PlatformAnimationController showPopupOrCreateAnimationController(bool createAnimationController, const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback)
209 {
210     BEGIN_BLOCK_OBJC_EXCEPTIONS;
211
212     if (!getLULookupDefinitionModuleClass())
213         return nil;
214
215     RetainPtr<NSMutableDictionary> mutableOptions = adoptNS([(NSDictionary *)dictionaryPopupInfo.options.get() mutableCopy]);
216
217     auto textIndicator = TextIndicator::create(dictionaryPopupInfo.textIndicator);
218
219     if (canLoadLUTermOptionDisableSearchTermIndicator() && textIndicator.get().contentImage()) {
220         textIndicatorInstallationCallback(textIndicator.get());
221         [mutableOptions setObject:@YES forKey:getLUTermOptionDisableSearchTermIndicator()];
222
223         FloatRect firstTextRectInViewCoordinates = textIndicator.get().textRectsInBoundingRectCoordinates()[0];
224         FloatRect textBoundingRectInViewCoordinates = textIndicator.get().textBoundingRectInRootViewCoordinates();
225         if (rootViewToViewConversionCallback)
226             textBoundingRectInViewCoordinates = rootViewToViewConversionCallback(textBoundingRectInViewCoordinates);
227         firstTextRectInViewCoordinates.moveBy(textBoundingRectInViewCoordinates.location());
228         if (createAnimationController)
229             return [getLULookupDefinitionModuleClass() lookupAnimationControllerForTerm:dictionaryPopupInfo.attributedString.get() relativeToRect:firstTextRectInViewCoordinates ofView:view options:mutableOptions.get()];
230
231         [getLULookupDefinitionModuleClass() showDefinitionForTerm:dictionaryPopupInfo.attributedString.get() relativeToRect:firstTextRectInViewCoordinates ofView:view options:mutableOptions.get()];
232         return nil;
233     }
234
235     NSPoint textBaselineOrigin = dictionaryPopupInfo.origin;
236
237     // Convert to screen coordinates.
238     textBaselineOrigin = [view convertPoint:textBaselineOrigin toView:nil];
239     textBaselineOrigin = [view.window convertRectToScreen:NSMakeRect(textBaselineOrigin.x, textBaselineOrigin.y, 0, 0)].origin;
240
241     if (createAnimationController)
242         return [getLULookupDefinitionModuleClass() lookupAnimationControllerForTerm:dictionaryPopupInfo.attributedString.get() atLocation:textBaselineOrigin options:mutableOptions.get()];
243
244     [getLULookupDefinitionModuleClass() showDefinitionForTerm:dictionaryPopupInfo.attributedString.get() atLocation:textBaselineOrigin options:mutableOptions.get()];
245     return nil;
246
247     END_BLOCK_OBJC_EXCEPTIONS;
248     return nil;
249 }
250
251 void DictionaryLookup::showPopup(const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback)
252 {
253     showPopupOrCreateAnimationController(false, dictionaryPopupInfo, view, textIndicatorInstallationCallback, rootViewToViewConversionCallback);
254 }
255
256 void DictionaryLookup::hidePopup()
257 {
258     BEGIN_BLOCK_OBJC_EXCEPTIONS;
259
260     if (!getLULookupDefinitionModuleClass())
261         return;
262     [getLULookupDefinitionModuleClass() hideDefinition];
263
264     END_BLOCK_OBJC_EXCEPTIONS;
265 }
266
267 PlatformAnimationController DictionaryLookup::animationControllerForPopup(const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback)
268 {
269     return showPopupOrCreateAnimationController(true, dictionaryPopupInfo, view, textIndicatorInstallationCallback, rootViewToViewConversionCallback);
270 }
271
272 } // namespace WebCore
273
274 #endif // PLATFORM(MAC)
275