SQLiteDatabase::open is constantly printing "SQLite database failed to checkpoint...
[WebKit-https.git] / Source / WebCore / editing / mac / DictionaryLookup.mm
1 /*
2  * Copyright (C) 2014-2018 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) && ENABLE(REVEAL)
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 <pal/spi/mac/RevealSPI.h>
49 #import <wtf/BlockObjCExceptions.h>
50 #import <wtf/RefPtr.h>
51
52 @interface WebRevealHighlight <RVPresenterHighlightDelegate> : NSObject {
53 @private
54     Function<void()> _clearTextIndicator;
55     NSRect _highlightRect;
56     BOOL _useDefaultHighlight;
57     NSAttributedString *_attributedString;
58 }
59
60 @property (nonatomic, readonly) NSRect highlightRect;
61 @property (nonatomic, readonly) BOOL useDefaultHighlight;
62 @property (nonatomic, readonly) NSAttributedString *attributedString;
63
64 - (instancetype)initWithHighlightRect:(NSRect)highlightRect useDefaultHighlight:(BOOL)useDefaultHighlight attributedString:(NSAttributedString *) attributedString;
65 - (void)setClearTextIndicator:(Function<void()>&&)clearTextIndicator;
66
67 @end
68
69 @implementation WebRevealHighlight
70
71 @synthesize highlightRect=_highlightRect;
72 @synthesize useDefaultHighlight=_useDefaultHighlight;
73 @synthesize attributedString=_attributedString;
74
75 - (instancetype)initWithHighlightRect:(NSRect)highlightRect useDefaultHighlight:(BOOL)useDefaultHighlight attributedString:(NSAttributedString *) attributedString
76 {
77     if (!(self = [super init]))
78         return nil;
79     
80     _highlightRect = highlightRect;
81     _useDefaultHighlight = useDefaultHighlight;
82     _attributedString = attributedString;
83     
84     return self;
85 }
86
87 - (void)setClearTextIndicator:(Function<void()>&&)clearTextIndicator
88 {
89     _clearTextIndicator = WTFMove(clearTextIndicator);
90 }
91
92 - (NSArray<NSValue *> *)revealContext:(RVPresentingContext *)context rectsForItem:(RVItem *)item
93 {
94     UNUSED_PARAM(context);
95     UNUSED_PARAM(item);
96     return @[[NSValue valueWithRect:self.highlightRect]];
97 }
98
99 - (void)revealContext:(RVPresentingContext *)context drawRectsForItem:(RVItem *)item
100 {
101     UNUSED_PARAM(item);
102     
103     for (NSValue *rectVal in context.itemRectsInView) {
104         NSRect rect = rectVal.rectValue;
105
106         // Get current font attributes from the attributed string above, and add paragraph style attribute in order to center text.
107         RetainPtr<NSMutableDictionary> attributes = adoptNS([[NSMutableDictionary alloc] initWithDictionary:[self.attributedString fontAttributesInRange:NSMakeRange(0, [self.attributedString length])]]);
108         RetainPtr<NSMutableParagraphStyle> paragraph = adoptNS([[NSMutableParagraphStyle alloc] init]);
109         [paragraph setAlignment:NSTextAlignmentCenter];
110         [attributes setObject:paragraph.get() forKey:NSParagraphStyleAttributeName];
111     
112         RetainPtr<NSAttributedString> string = adoptNS([[NSAttributedString alloc] initWithString:[self.attributedString string] attributes:attributes.get()]);
113         [string drawInRect:rect];
114     }
115 }
116
117 - (BOOL)revealContext:(RVPresentingContext *)context shouldUseDefaultHighlightForItem:(RVItem *)item
118 {
119     UNUSED_PARAM(context);
120     UNUSED_PARAM(item);
121     return self.useDefaultHighlight;
122 }
123
124 - (void)revealContext:(RVPresentingContext *)context stopHighlightingItem:(RVItem *)item
125 {
126     UNUSED_PARAM(context);
127     UNUSED_PARAM(item);
128     auto block = WTFMove(_clearTextIndicator);
129     if (block)
130         block();
131 }
132
133 @end
134
135 namespace WebCore {
136
137 std::tuple<RefPtr<Range>, NSDictionary *> DictionaryLookup::rangeForSelection(const VisibleSelection& selection)
138 {
139     BEGIN_BLOCK_OBJC_EXCEPTIONS;
140     
141     if (!getRVItemClass())
142         return { nullptr, nil };
143     
144     auto selectedRange = selection.toNormalizedRange();
145     if (!selectedRange)
146         return { nullptr, nil };
147
148     // Since we already have the range we want, we just need to grab the returned options.
149     auto selectionStart = selection.visibleStart();
150     auto selectionEnd = selection.visibleEnd();
151
152     // As context, we are going to use the surrounding paragraphs of text.
153     auto paragraphStart = startOfParagraph(selectionStart);
154     auto paragraphEnd = endOfParagraph(selectionEnd);
155
156     int lengthToSelectionStart = TextIterator::rangeLength(makeRange(paragraphStart, selectionStart).get());
157     int lengthToSelectionEnd = TextIterator::rangeLength(makeRange(paragraphStart, selectionEnd).get());
158     NSRange rangeToPass = NSMakeRange(lengthToSelectionStart, lengthToSelectionEnd - lengthToSelectionStart);
159     
160     RefPtr<Range> fullCharacterRange = makeRange(paragraphStart, paragraphEnd);
161     String itemString = plainText(fullCharacterRange.get());
162     RetainPtr<RVItem> item = adoptNS([allocRVItemInstance() initWithText:itemString selectedRange:rangeToPass]);
163     NSRange highlightRange = item.get().highlightRange;
164     
165     return { TextIterator::subrange(*fullCharacterRange, highlightRange.location, highlightRange.length), nil };
166     
167     END_BLOCK_OBJC_EXCEPTIONS;
168     
169     return { nullptr, nil };
170 }
171
172 std::tuple<RefPtr<Range>, NSDictionary *> DictionaryLookup::rangeAtHitTestResult(const HitTestResult& hitTestResult)
173 {
174     BEGIN_BLOCK_OBJC_EXCEPTIONS;
175     
176     if (!getRVItemClass())
177         return { nullptr, nil };
178     
179     auto* node = hitTestResult.innerNonSharedNode();
180     if (!node || !node->renderer())
181         return { nullptr, nil };
182
183     auto* frame = node->document().frame();
184     if (!frame)
185         return { nullptr, nil };
186
187     // Don't do anything if there is no character at the point.
188     auto framePoint = hitTestResult.roundedPointInInnerNodeFrame();
189     if (!frame->rangeForPoint(framePoint))
190         return { nullptr, nil };
191
192     auto position = frame->visiblePositionForPoint(framePoint);
193     if (position.isNull())
194         position = firstPositionInOrBeforeNode(node);
195
196     auto selection = frame->page()->focusController().focusedOrMainFrame().selection().selection();
197     NSRange selectionRange;
198     int hitIndex;
199     RefPtr<Range> fullCharacterRange;
200     
201     if (selection.selectionType() == VisibleSelection::RangeSelection) {
202         auto selectionStart = selection.visibleStart();
203         auto selectionEnd = selection.visibleEnd();
204         
205         // As context, we are going to use the surrounding paragraphs of text.
206         auto paragraphStart = startOfParagraph(selectionStart);
207         auto paragraphEnd = endOfParagraph(selectionEnd);
208         
209         auto rangeToSelectionStart = makeRange(paragraphStart, selectionStart);
210         auto rangeToSelectionEnd = makeRange(paragraphStart, selectionEnd);
211         
212         fullCharacterRange = makeRange(paragraphStart, paragraphEnd);
213         
214         selectionRange = NSMakeRange(TextIterator::rangeLength(rangeToSelectionStart.get()), TextIterator::rangeLength(makeRange(selectionStart, selectionEnd).get()));
215         
216         hitIndex = TextIterator::rangeLength(makeRange(paragraphStart, position).get());
217     } else {
218         VisibleSelection selectionAccountingForLineRules { position };
219         selectionAccountingForLineRules.expandUsingGranularity(WordGranularity);
220         position = selectionAccountingForLineRules.start();
221         // As context, we are going to use 250 characters of text before and after the point.
222         fullCharacterRange = rangeExpandedAroundPositionByCharacters(position, 250);
223         
224         if (!fullCharacterRange)
225             return { nullptr, nil };
226         
227         selectionRange = NSMakeRange(NSNotFound, 0);
228         hitIndex = TextIterator::rangeLength(makeRange(fullCharacterRange->startPosition(), position).get());
229     }
230     
231     NSRange selectedRange = [getRVSelectionClass() revealRangeAtIndex:hitIndex selectedRanges:@[[NSValue valueWithRange:selectionRange]] shouldUpdateSelection:nil];
232     
233     String itemString = plainText(fullCharacterRange.get());
234     RetainPtr<RVItem> item = adoptNS([allocRVItemInstance() initWithText:itemString selectedRange:selectedRange]);
235     NSRange highlightRange = item.get().highlightRange;
236
237     if (highlightRange.location == NSNotFound || !highlightRange.length)
238         return { nullptr, nil };
239     
240     return { TextIterator::subrange(*fullCharacterRange, highlightRange.location, highlightRange.length), nil };
241     
242     END_BLOCK_OBJC_EXCEPTIONS;
243     
244     return { nullptr, nil };
245     
246 }
247
248 static void expandSelectionByCharacters(PDFSelection *selection, NSInteger numberOfCharactersToExpand, NSInteger& charactersAddedBeforeStart, NSInteger& charactersAddedAfterEnd)
249 {
250     BEGIN_BLOCK_OBJC_EXCEPTIONS;
251
252     size_t originalLength = selection.string.length;
253     [selection extendSelectionAtStart:numberOfCharactersToExpand];
254     
255     charactersAddedBeforeStart = selection.string.length - originalLength;
256     
257     [selection extendSelectionAtEnd:numberOfCharactersToExpand];
258     charactersAddedAfterEnd = selection.string.length - originalLength - charactersAddedBeforeStart;
259
260     END_BLOCK_OBJC_EXCEPTIONS;
261 }
262
263 std::tuple<NSString *, NSDictionary *> DictionaryLookup::stringForPDFSelection(PDFSelection *selection)
264 {
265     BEGIN_BLOCK_OBJC_EXCEPTIONS;
266     
267     if (!getRVItemClass())
268         return { nullptr, nil };
269
270     // Don't do anything if there is no character at the point.
271     if (!selection || !selection.string.length)
272         return { @"", nil };
273
274     RetainPtr<PDFSelection> selectionForLookup = adoptNS([selection copy]);
275
276     // As context, we are going to use 250 characters of text before and after the point.
277     auto originalLength = [selectionForLookup string].length;
278     NSInteger charactersAddedBeforeStart = 0;
279     NSInteger charactersAddedAfterEnd = 0;
280     expandSelectionByCharacters(selectionForLookup.get(), 250, charactersAddedBeforeStart, charactersAddedAfterEnd);
281
282     auto fullPlainTextString = [selectionForLookup string];
283     auto rangeToPass = NSMakeRange(charactersAddedBeforeStart, 0);
284
285     RetainPtr<RVItem> item = adoptNS([allocRVItemInstance() initWithText:fullPlainTextString selectedRange:rangeToPass]);
286     NSRange extractedRange = item.get().highlightRange;
287     
288     if (extractedRange.location == NSNotFound)
289         return { selection.string, nil };
290
291     NSInteger lookupAddedBefore = rangeToPass.location - extractedRange.location;
292     NSInteger lookupAddedAfter = (extractedRange.location + extractedRange.length) - (rangeToPass.location + originalLength);
293
294     [selection extendSelectionAtStart:lookupAddedBefore];
295     [selection extendSelectionAtEnd:lookupAddedAfter];
296
297     ASSERT([selection.string isEqualToString:[fullPlainTextString substringWithRange:extractedRange]]);
298     return { selection.string, nil };
299
300     END_BLOCK_OBJC_EXCEPTIONS;
301
302     return { @"", nil };
303 }
304
305 static id <NSImmediateActionAnimationController> showPopupOrCreateAnimationController(bool createAnimationController, const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback, WTF::Function<void()>&& clearTextIndicator)
306 {
307     BEGIN_BLOCK_OBJC_EXCEPTIONS;
308     
309     if (!getRVItemClass())
310         return nil;
311
312     RetainPtr<NSMutableDictionary> mutableOptions = adoptNS([[NSMutableDictionary alloc] init]);
313     if (NSDictionary *options = dictionaryPopupInfo.options.get())
314         [mutableOptions addEntriesFromDictionary:options];
315
316     auto textIndicator = TextIndicator::create(dictionaryPopupInfo.textIndicator);
317     
318     RetainPtr<RVPresenter> presenter = adoptNS([allocRVPresenterInstance() init]);
319     
320     NSRect highlightRect;
321     NSPoint pointerLocation;
322     
323     if (textIndicator.get().contentImage()) {
324         textIndicatorInstallationCallback(textIndicator.get());
325
326         FloatRect firstTextRectInViewCoordinates = textIndicator.get().textRectsInBoundingRectCoordinates()[0];
327         FloatRect textBoundingRectInViewCoordinates = textIndicator.get().textBoundingRectInRootViewCoordinates();
328         FloatRect selectionBoundingRectInViewCoordinates = textIndicator.get().selectionRectInRootViewCoordinates();
329         
330         if (rootViewToViewConversionCallback) {
331             textBoundingRectInViewCoordinates = rootViewToViewConversionCallback(textBoundingRectInViewCoordinates);
332             selectionBoundingRectInViewCoordinates = rootViewToViewConversionCallback(selectionBoundingRectInViewCoordinates);
333         }
334         
335         firstTextRectInViewCoordinates.moveBy(textBoundingRectInViewCoordinates.location());
336         highlightRect = selectionBoundingRectInViewCoordinates;
337         pointerLocation = firstTextRectInViewCoordinates.location();
338         
339     } else {
340         NSPoint textBaselineOrigin = dictionaryPopupInfo.origin;
341         
342         highlightRect = textIndicator->selectionRectInRootViewCoordinates();
343         pointerLocation = [view convertPoint:textBaselineOrigin toView:nil];
344     }
345     
346     RetainPtr<WebRevealHighlight> webHighlight =  adoptNS([[WebRevealHighlight alloc] initWithHighlightRect: highlightRect useDefaultHighlight:!textIndicator.get().contentImage() attributedString:dictionaryPopupInfo.attributedString.get()]);
347     RetainPtr<RVPresentingContext> context = adoptNS([allocRVPresentingContextInstance() initWithPointerLocationInView:pointerLocation inView:view highlightDelegate:(id<RVPresenterHighlightDelegate>) webHighlight.get()]);
348     
349     RetainPtr<RVItem> item = adoptNS([allocRVItemInstance() initWithText:dictionaryPopupInfo.attributedString.get().string selectedRange:NSMakeRange(0, 0)]);
350     
351     [webHighlight setClearTextIndicator:[webHighlight = WTFMove(webHighlight), clearTextIndicator = WTFMove(clearTextIndicator)] {
352         if (clearTextIndicator)
353             clearTextIndicator();
354     }];
355     
356     if (createAnimationController)
357         return [presenter animationControllerForItem:item.get() documentContext:nil presentingContext:context.get() options:nil];
358     [presenter revealItem:item.get() documentContext:nil presentingContext:context.get() options:nil];
359     return nil;
360
361     END_BLOCK_OBJC_EXCEPTIONS;
362     return nil;
363 }
364
365 void DictionaryLookup::showPopup(const DictionaryPopupInfo& dictionaryPopupInfo, NSView *view, const WTF::Function<void(TextIndicator&)>& textIndicatorInstallationCallback, const WTF::Function<FloatRect(FloatRect)>& rootViewToViewConversionCallback, WTF::Function<void()>&& clearTextIndicator)
366 {
367     showPopupOrCreateAnimationController(false, dictionaryPopupInfo, view, textIndicatorInstallationCallback, rootViewToViewConversionCallback, WTFMove(clearTextIndicator));
368 }
369
370 void DictionaryLookup::hidePopup()
371 {
372     BEGIN_BLOCK_OBJC_EXCEPTIONS;
373
374     if (!getLULookupDefinitionModuleClass())
375         return;
376     [getLULookupDefinitionModuleClass() hideDefinition];
377
378     END_BLOCK_OBJC_EXCEPTIONS;
379 }
380
381 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)
382 {
383     return showPopupOrCreateAnimationController(true, dictionaryPopupInfo, view, textIndicatorInstallationCallback, rootViewToViewConversionCallback, WTFMove(clearTextIndicator));
384 }
385
386 } // namespace WebCore
387
388 #endif // PLATFORM(MAC)