4d160af0fed249402a938e30b642c632af0595b5
[WebKit-https.git] / Source / WebCore / editing / cocoa / DataDetection.mm
1 /*
2  * Copyright (C) 2014-2017 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "DataDetection.h"
28
29 #if ENABLE(DATA_DETECTION)
30
31 #import "Attr.h"
32 #import "CSSStyleDeclaration.h"
33 #import "DataDetectorsSPI.h"
34 #import "DataDetectorsUISPI.h"
35 #import "Editing.h"
36 #import "ElementAncestorIterator.h"
37 #import "ElementTraversal.h"
38 #import "FrameView.h"
39 #import "HTMLAnchorElement.h"
40 #import "HTMLNames.h"
41 #import "HTMLTextFormControlElement.h"
42 #import "HitTestResult.h"
43 #import "Node.h"
44 #import "NodeList.h"
45 #import "NodeTraversal.h"
46 #import "Range.h"
47 #import "RenderObject.h"
48 #import "StyleProperties.h"
49 #import "Text.h"
50 #import "TextIterator.h"
51 #import "VisiblePosition.h"
52 #import "VisibleUnits.h"
53 #import <wtf/text/StringBuilder.h>
54
55 #import "DataDetectorsCoreSoftLink.h"
56
57 namespace WebCore {
58
59 using namespace HTMLNames;
60
61 #if PLATFORM(MAC)
62
63 static RetainPtr<DDActionContext> detectItemAtPositionWithRange(VisiblePosition position, RefPtr<Range> contextRange, FloatRect& detectedDataBoundingBox, RefPtr<Range>& detectedDataRange)
64 {
65     String fullPlainTextString = plainText(contextRange.get());
66     int hitLocation = TextIterator::rangeLength(makeRange(contextRange->startPosition(), position).get());
67
68     RetainPtr<DDScannerRef> scanner = adoptCF(DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
69     RetainPtr<DDScanQueryRef> scanQuery = adoptCF(DDScanQueryCreateFromString(kCFAllocatorDefault, fullPlainTextString.createCFString().get(), CFRangeMake(0, fullPlainTextString.length())));
70
71     if (!DDScannerScanQuery(scanner.get(), scanQuery.get()))
72         return nullptr;
73
74     RetainPtr<CFArrayRef> results = adoptCF(DDScannerCopyResultsWithOptions(scanner.get(), DDScannerCopyResultsOptionsNoOverlap));
75
76     // Find the DDResultRef that intersects the hitTestResult's VisiblePosition.
77     DDResultRef mainResult = nullptr;
78     RefPtr<Range> mainResultRange;
79     CFIndex resultCount = CFArrayGetCount(results.get());
80     for (CFIndex i = 0; i < resultCount; i++) {
81         DDResultRef result = (DDResultRef)CFArrayGetValueAtIndex(results.get(), i);
82         CFRange resultRangeInContext = DDResultGetRange(result);
83         if (hitLocation >= resultRangeInContext.location && (hitLocation - resultRangeInContext.location) < resultRangeInContext.length) {
84             mainResult = result;
85             mainResultRange = TextIterator::subrange(*contextRange, resultRangeInContext.location, resultRangeInContext.length);
86             break;
87         }
88     }
89
90     if (!mainResult)
91         return nullptr;
92
93     RetainPtr<DDActionContext> actionContext = adoptNS([allocDDActionContextInstance() init]);
94     [actionContext setAllResults:@[ (id)mainResult ]];
95     [actionContext setMainResult:mainResult];
96
97     Vector<FloatQuad> quads;
98     mainResultRange->absoluteTextQuads(quads);
99     detectedDataBoundingBox = FloatRect();
100     FrameView* frameView = mainResultRange->ownerDocument().view();
101     for (const auto& quad : quads)
102         detectedDataBoundingBox.unite(frameView->contentsToWindow(quad.enclosingBoundingBox()));
103     
104     detectedDataRange = mainResultRange;
105     
106     return actionContext;
107 }
108
109 RetainPtr<DDActionContext> DataDetection::detectItemAroundHitTestResult(const HitTestResult& hitTestResult, FloatRect& detectedDataBoundingBox, RefPtr<Range>& detectedDataRange)
110 {
111     if (!DataDetectorsLibrary())
112         return nullptr;
113
114     Node* node = hitTestResult.innerNonSharedNode();
115     if (!node)
116         return nullptr;
117     auto renderer = node->renderer();
118     if (!renderer)
119         return nullptr;
120
121     VisiblePosition position;
122     RefPtr<Range> contextRange;
123
124     if (!is<HTMLTextFormControlElement>(*node)) {
125         position = renderer->positionForPoint(hitTestResult.localPoint(), nullptr);
126         if (position.isNull())
127             position = firstPositionInOrBeforeNode(node);
128
129         contextRange = rangeExpandedAroundPositionByCharacters(position, 250);
130         if (!contextRange)
131             return nullptr;
132     } else {
133         Frame* frame = node->document().frame();
134         if (!frame)
135             return nullptr;
136
137         IntPoint framePoint = hitTestResult.roundedPointInInnerNodeFrame();
138         if (!frame->rangeForPoint(framePoint))
139             return nullptr;
140
141         VisiblePosition position = frame->visiblePositionForPoint(framePoint);
142         if (position.isNull())
143             return nullptr;
144
145         contextRange = enclosingTextUnitOfGranularity(position, LineGranularity, DirectionForward);
146         if (!contextRange)
147             return nullptr;
148     }
149
150     return detectItemAtPositionWithRange(position, contextRange, detectedDataBoundingBox, detectedDataRange);
151 }
152 #endif // PLATFORM(MAC)
153
154 #if PLATFORM(IOS)
155 bool DataDetection::isDataDetectorLink(Element& element)
156 {
157     if (!is<HTMLAnchorElement>(element))
158         return false;
159
160 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
161     return [softLink_DataDetectorsCore_DDURLTapAndHoldSchemes() containsObject:(NSString *)downcast<HTMLAnchorElement>(element).href().protocol().toStringWithoutCopying().convertToASCIILowercase()];
162 #else
163     if (equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
164         return true;
165     URL url = downcast<HTMLAnchorElement>(element).href();
166     return url.protocolIs("mailto") || url.protocolIs("tel");
167 #endif
168 }
169
170 bool DataDetection::requiresExtendedContext(Element& element)
171 {
172     return equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectors_typeAttr), "calendar-event");
173 }
174
175 String DataDetection::dataDetectorIdentifier(Element& element)
176 {
177     return element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
178 }
179
180 bool DataDetection::shouldCancelDefaultAction(Element& element)
181 {
182 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
183     if (!isDataDetectorLink(element))
184         return false;
185     
186     if (softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForURL(downcast<HTMLAnchorElement>(element).href()))
187         return true;
188     
189     const AtomicString& resultAttribute = element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
190     if (resultAttribute.isEmpty())
191         return false;
192     NSArray *results = element.document().frame()->dataDetectionResults();
193     if (!results)
194         return false;
195     Vector<String> resultIndices;
196     resultAttribute.string().split('/', resultIndices);
197     DDResultRef result = [[results objectAtIndex:resultIndices[0].toInt()] coreResult];
198     // Handle the case of a signature block, where we need to check the correct subresult.
199     for (size_t i = 1; i < resultIndices.size(); i++) {
200         results = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
201         result = (DDResultRef)[results objectAtIndex:resultIndices[i].toInt()];
202     }
203     return softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForResult(result);
204 #else
205     if (!is<HTMLAnchorElement>(element))
206         return false;
207     if (!equalLettersIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
208         return false;
209     auto& type = element.attributeWithoutSynchronization(x_apple_data_detectors_typeAttr);
210     if (equalLettersIgnoringASCIICase(type, "misc") || equalLettersIgnoringASCIICase(type, "calendar-event") || equalLettersIgnoringASCIICase(type, "telephone"))
211         return true;
212     return false;
213 #endif
214 }
215
216 static BOOL resultIsURL(DDResultRef result)
217 {
218     if (!result)
219         return NO;
220     
221     static NSSet *urlTypes = [[NSSet setWithObjects: (NSString *)get_DataDetectorsCore_DDBinderHttpURLKey(), (NSString *)get_DataDetectorsCore_DDBinderWebURLKey(), (NSString *)get_DataDetectorsCore_DDBinderMailURLKey(), (NSString *)get_DataDetectorsCore_DDBinderGenericURLKey(), (NSString *)get_DataDetectorsCore_DDBinderEmailKey(), nil] retain];
222     return [urlTypes containsObject:(NSString *)softLink_DataDetectorsCore_DDResultGetType(result)];
223 }
224
225 static NSString *constructURLStringForResult(DDResultRef currentResult, NSString *resultIdentifier, NSDate *referenceDate, NSTimeZone *referenceTimeZone, DataDetectorTypes detectionTypes)
226 {
227     if (!softLink_DataDetectorsCore_DDResultHasProperties(currentResult, DDResultPropertyPassiveDisplay))
228         return nil;
229     
230     DDURLifierPhoneNumberDetectionTypes phoneTypes = (detectionTypes & DataDetectorTypePhoneNumber) ? DDURLifierPhoneNumberDetectionRegular : DDURLifierPhoneNumberDetectionNone;
231     DDResultCategory category = softLink_DataDetectorsCore_DDResultGetCategory(currentResult);
232     CFStringRef type = softLink_DataDetectorsCore_DDResultGetType(currentResult);
233     
234     if (((detectionTypes & DataDetectorTypeAddress) && (DDResultCategoryAddress == category))
235         || ((detectionTypes & DataDetectorTypeTrackingNumber) && (CFStringCompare(get_DataDetectorsCore_DDBinderTrackingNumberKey(), type, 0) == kCFCompareEqualTo))
236         || ((detectionTypes & DataDetectorTypeFlightNumber) && (CFStringCompare(get_DataDetectorsCore_DDBinderFlightInformationKey(), type, 0) == kCFCompareEqualTo))
237 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
238         || ((detectionTypes & DataDetectorTypeLookupSuggestion) && (CFStringCompare(get_DataDetectorsCore_DDBinderParsecSourceKey(), type, 0) == kCFCompareEqualTo))
239 #endif
240         || ((detectionTypes & DataDetectorTypePhoneNumber) && (DDResultCategoryPhoneNumber == category))
241         || ((detectionTypes & DataDetectorTypeLink) && resultIsURL(currentResult))) {
242         
243         return softLink_DataDetectorsCore_DDURLStringForResult(currentResult, resultIdentifier, phoneTypes, referenceDate, referenceTimeZone);
244     }
245     if ((detectionTypes & DataDetectorTypeCalendarEvent) && (DDResultCategoryCalendarEvent == category)) {
246         if (!softLink_DataDetectorsCore_DDResultIsPastDate(currentResult, (CFDateRef)referenceDate, (CFTimeZoneRef)referenceTimeZone))
247             return softLink_DataDetectorsCore_DDURLStringForResult(currentResult, resultIdentifier, phoneTypes, referenceDate, referenceTimeZone);
248     }
249     return nil;
250 }
251
252 static void removeResultLinksFromAnchor(Element& element)
253 {
254     // Perform a depth-first search for anchor nodes, which have the DDURLScheme attribute set to true,
255     // take their children and insert them before the anchor, and then remove the anchor.
256
257     // Note that this is not using ElementChildIterator because we potentially prepend children as we iterate over them.
258     for (auto* child = ElementTraversal::firstChild(element); child; child = ElementTraversal::nextSibling(*child))
259         removeResultLinksFromAnchor(*child);
260
261     auto* elementParent = element.parentElement();
262     if (!elementParent)
263         return;
264     
265     bool elementIsDDAnchor = is<HTMLAnchorElement>(element) && equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true");
266     if (!elementIsDDAnchor)
267         return;
268
269     // Iterate over the children and move them all onto the same level as this anchor. Remove the anchor afterwards.
270     while (auto* child = element.firstChild())
271         elementParent->insertBefore(*child, &element);
272
273     elementParent->removeChild(element);
274 }
275
276 static bool searchForLinkRemovingExistingDDLinks(Node& startNode, Node& endNode, bool& didModifyDOM)
277 {
278     didModifyDOM = false;
279     for (Node* node = &startNode; node; node = NodeTraversal::next(*node)) {
280         if (is<HTMLAnchorElement>(*node)) {
281             auto& anchor = downcast<HTMLAnchorElement>(*node);
282             if (!equalIgnoringASCIICase(anchor.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
283                 return true;
284             removeResultLinksFromAnchor(anchor);
285             didModifyDOM = true;
286         }
287         
288         if (node == &endNode) {
289             // If we found the end node and no link, return false unless an ancestor node is a link.
290             // The only ancestors not tested at this point are in the direct line from self's parent to the top.
291             for (auto& anchor : ancestorsOfType<HTMLAnchorElement>(startNode)) {
292                 if (!equalIgnoringASCIICase(anchor.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
293                     return true;
294                 removeResultLinksFromAnchor(anchor);
295                 didModifyDOM = true;
296             }
297             return false;
298         }
299     }
300     return false;
301 }
302
303 static NSString *dataDetectorTypeForCategory(DDResultCategory category)
304 {
305     switch (category) {
306     case DDResultCategoryPhoneNumber:
307         return @"telephone";
308     case DDResultCategoryLink:
309         return @"link";
310     case DDResultCategoryAddress:
311         return @"address";
312     case DDResultCategoryCalendarEvent:
313         return @"calendar-event";
314     case DDResultCategoryMisc:
315         return @"misc";
316     default:
317         return @"";
318     }
319 }
320
321 static String dataDetectorStringForPath(NSIndexPath *path)
322 {
323     NSUInteger length = path.length;
324     
325     switch (length) {
326     case 0:
327         return { };
328     case 1:
329         return String::number((unsigned long)[path indexAtPosition:0]);
330     case 2: {
331         StringBuilder stringBuilder;
332         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:0]);
333         stringBuilder.append('/');
334         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:1]);
335         return stringBuilder.toString();
336     }
337     default: {
338         StringBuilder stringBuilder;
339         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:0]);
340         for (NSUInteger i = 1 ; i < length ; i++) {
341             stringBuilder.append('/');
342             stringBuilder.appendNumber((unsigned long)[path indexAtPosition:i]);
343         }
344
345         return stringBuilder.toString();
346     }
347     }
348 }
349
350 static void buildQuery(DDScanQueryRef scanQuery, Range* contextRange)
351 {
352     // Once we're over this number of fragments, stop at the first hard break.
353     const CFIndex maxFragmentWithHardBreak = 1000;
354     // Once we're over this number of fragments, we stop at the line.
355     const CFIndex maxFragmentWithLinebreak = 5000;
356     // Once we're over this number of fragments, we stop at the space.
357     const CFIndex maxFragmentSpace = 10000;
358
359     CFCharacterSetRef whiteSpacesSet = CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline);
360     CFCharacterSetRef newLinesSet = CFCharacterSetGetPredefined(kCFCharacterSetNewline);
361     
362     RefPtr<Range> endRange;
363     CFIndex iteratorCount = 0;
364     CFIndex fragmentCount = 0;
365     
366     // Build the scan query adding separators.
367     // For each fragment the iterator increment is stored as metadata.
368     for (TextIterator iterator(contextRange); !iterator.atEnd(); iterator.advance(), iteratorCount++) {
369         size_t currentTextLength = iterator.text().length();
370         if (!currentTextLength) {
371             softLink_DataDetectorsCore_DDScanQueryAddSeparator(scanQuery, DDTextCoalescingTypeHardBreak);
372             if (iteratorCount > maxFragmentWithHardBreak)
373                 break;
374             continue;
375         }
376         // Test for white space nodes, we're coalescing them.
377         const UniChar* currentCharPtr = iterator.text().upconvertedCharacters();
378         
379         bool containsOnlyWhiteSpace = true;
380         bool hasTab = false;
381         bool hasNewline = false;
382         int nbspCount = 0;
383         for (NSUInteger i = 0; i < currentTextLength; i++) {
384             if (!CFCharacterSetIsCharacterMember(whiteSpacesSet, *currentCharPtr)) {
385                 containsOnlyWhiteSpace = false;
386                 break;
387             }
388             
389             if (CFCharacterSetIsCharacterMember(newLinesSet, *currentCharPtr))
390                 hasNewline = true;
391             else if (*currentCharPtr == '\t')
392                 hasTab = true;
393             
394             // Multiple consecutive non breakable spaces are most likely simulated tabs.
395             if (*currentCharPtr == 0xa0) {
396                 if (++nbspCount > 2)
397                     hasTab = true;
398             } else
399                 nbspCount = 0;
400
401             currentCharPtr++;
402         }
403         if (containsOnlyWhiteSpace) {
404             if (hasNewline) {
405                 softLink_DataDetectorsCore_DDScanQueryAddLineBreak(scanQuery);
406                 if (iteratorCount > maxFragmentWithLinebreak)
407                     break;
408             } else {
409                 softLink_DataDetectorsCore_DDScanQueryAddSeparator(scanQuery, hasTab ? DDTextCoalescingTypeTab : DDTextCoalescingTypeSpace);
410                 if (iteratorCount > maxFragmentSpace)
411                     break;
412             }
413             continue;
414         }
415         
416         RetainPtr<CFStringRef> currentText = adoptCF(CFStringCreateWithCharacters(kCFAllocatorDefault, iterator.text().upconvertedCharacters(), iterator.text().length()));
417         softLink_DataDetectorsCore_DDScanQueryAddTextFragment(scanQuery, currentText.get(), CFRangeMake(0, currentTextLength), (void *)iteratorCount, (DDTextFragmentMode)0, DDTextCoalescingTypeNone);
418         fragmentCount++;
419     }
420 }
421
422 static inline CFComparisonResult queryOffsetCompare(DDQueryOffset o1, DDQueryOffset o2)
423 {
424     if (o1.queryIndex < o2.queryIndex)
425         return kCFCompareLessThan;
426     if (o1.queryIndex > o2.queryIndex)
427         return kCFCompareGreaterThan;
428     if (o1.offset < o2.offset)
429         return kCFCompareLessThan;
430     if (o1.offset > o2.offset)
431         return kCFCompareGreaterThan;
432     return kCFCompareEqualTo;
433 }
434
435 NSArray *DataDetection::detectContentInRange(RefPtr<Range>& contextRange, DataDetectorTypes types, NSDictionary *context)
436 {
437     RetainPtr<DDScannerRef> scanner = adoptCF(softLink_DataDetectorsCore_DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
438     RetainPtr<DDScanQueryRef> scanQuery = adoptCF(softLink_DataDetectorsCore_DDScanQueryCreate(NULL));
439     buildQuery(scanQuery.get(), contextRange.get());
440     
441 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
442     if (types & DataDetectorTypeLookupSuggestion)
443         softLink_DataDetectorsCore_DDScannerEnableOptionalSource(scanner.get(), DDScannerSourceSpotlight, true);
444 #endif
445     
446     // FIXME: we should add a timeout to this call to make sure it doesn't take too much time.
447     if (!softLink_DataDetectorsCore_DDScannerScanQuery(scanner.get(), scanQuery.get()))
448         return nil;
449
450     RetainPtr<CFArrayRef> scannerResults = adoptCF(softLink_DataDetectorsCore_DDScannerCopyResultsWithOptions(scanner.get(), get_DataDetectorsCore_DDScannerCopyResultsOptionsForPassiveUse() | DDScannerCopyResultsOptionsCoalesceSignatures));
451     if (!scannerResults)
452         return nil;
453
454     CFIndex resultCount = CFArrayGetCount(scannerResults.get());
455     if (!resultCount)
456         return nil;
457
458     Vector<RetainPtr<DDResultRef>> allResults;
459     Vector<RetainPtr<NSIndexPath>> indexPaths;
460     NSInteger currentTopLevelIndex = 0;
461
462     // Iterate through the scanner results to find signatures and extract all the subresults while
463     // populating the array of index paths to use in the href of the anchors being created.
464     for (id resultObject in (NSArray *)scannerResults.get()) {
465         DDResultRef result = (DDResultRef)resultObject;
466         NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:currentTopLevelIndex];
467         if (CFStringCompare(softLink_DataDetectorsCore_DDResultGetType(result), get_DataDetectorsCore_DDBinderSignatureBlockKey(), 0) == kCFCompareEqualTo) {
468             NSArray *subresults = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
469             
470             for (NSUInteger subResultIndex = 0 ; subResultIndex < [subresults count] ; subResultIndex++) {
471                 indexPaths.append([indexPath indexPathByAddingIndex:subResultIndex]);
472                 allResults.append((DDResultRef)[subresults objectAtIndex:subResultIndex]);
473             }
474         } else {
475             allResults.append(result);
476             indexPaths.append(indexPath);
477         }
478         currentTopLevelIndex++;
479     }
480
481     Vector<Vector<RefPtr<Range>>> allResultRanges;
482     TextIterator iterator(contextRange.get());
483     CFIndex iteratorCount = 0;
484
485     // Iterate through the array of the expanded results to create a vector of Range objects that indicate
486     // where the DOM needs to be modified.
487     // Each result can be contained all in one text node or can span multiple text nodes.
488     for (auto& result : allResults) {
489         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(result.get());
490         CFIndex iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), queryRange.start.queryIndex);
491         for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
492             iterator.advance();
493
494         Vector<RefPtr<Range>> fragmentRanges;
495         RefPtr<Range> currentRange = iterator.range();
496         CFIndex fragmentIndex = queryRange.start.queryIndex;
497         if (fragmentIndex == queryRange.end.queryIndex)
498             fragmentRanges.append(TextIterator::subrange(*currentRange, queryRange.start.offset, queryRange.end.offset - queryRange.start.offset));
499         else {
500             if (!queryRange.start.offset)
501                 fragmentRanges.append(currentRange);
502             else
503                 fragmentRanges.append(Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset() + queryRange.start.offset, &currentRange->endContainer(), currentRange->endOffset()));
504         }
505         
506         while (fragmentIndex < queryRange.end.queryIndex) {
507             ++fragmentIndex;
508             iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), fragmentIndex);
509             for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
510                 iterator.advance();
511
512             currentRange = iterator.range();
513             RefPtr<Range> fragmentRange = (fragmentIndex == queryRange.end.queryIndex) ?  Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset(), &currentRange->endContainer(), currentRange->startOffset() + queryRange.end.offset) : currentRange;
514             RefPtr<Range> previousRange = fragmentRanges.last();
515             if (&previousRange->startContainer() == &fragmentRange->startContainer()) {
516                 fragmentRange = Range::create(currentRange->ownerDocument(), &previousRange->startContainer(), previousRange->startOffset(), &fragmentRange->endContainer(), fragmentRange->endOffset());
517                 fragmentRanges.last() = fragmentRange;
518             } else
519                 fragmentRanges.append(fragmentRange);
520         }
521         allResultRanges.append(WTFMove(fragmentRanges));
522     }
523     
524     auto tz = adoptCF(CFTimeZoneCopyDefault());
525     NSDate *referenceDate = [context objectForKey:getkDataDetectorsReferenceDateKey()] ?: [NSDate date];
526     Text* lastTextNodeToUpdate = nullptr;
527     String lastNodeContent;
528     size_t contentOffset = 0;
529     DDQueryOffset lastModifiedQueryOffset = { -1, 0 };
530     
531     // For each result add the link.
532     // Since there could be multiple results in the same text node, the node is only modified when
533     // we are about to process a different text node.
534     resultCount = allResults.size();
535     
536     for (CFIndex resultIndex = 0; resultIndex < resultCount; ++resultIndex) {
537         DDResultRef coreResult = allResults[resultIndex].get();
538         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(coreResult);
539         auto& resultRanges = allResultRanges[resultIndex];
540
541         // Compare the query offsets to make sure we don't go backwards
542         if (queryOffsetCompare(lastModifiedQueryOffset, queryRange.start) >= 0)
543             continue;
544
545         if (resultRanges.isEmpty())
546             continue;
547         
548         // Store the range boundaries as Position, because the DOM could change if we find
549         // old data detector link.
550         Vector<std::pair<Position, Position>> rangeBoundaries;
551         rangeBoundaries.reserveInitialCapacity(resultRanges.size());
552         for (auto& range : resultRanges)
553             rangeBoundaries.uncheckedAppend({ range->startPosition(), range->endPosition() });
554
555         NSString *identifier = dataDetectorStringForPath(indexPaths[resultIndex].get());
556         NSString *correspondingURL = constructURLStringForResult(coreResult, identifier, referenceDate, (NSTimeZone *)tz.get(), types);
557         bool didModifyDOM = false;
558
559         if (!correspondingURL || searchForLinkRemovingExistingDDLinks(resultRanges.first()->startContainer(), resultRanges.last()->endContainer(), didModifyDOM))
560             continue;
561         
562         if (didModifyDOM) {
563             // If the DOM was modified because some old links were removed,
564             // we need to recreate the ranges because they could no longer be valid.
565             ASSERT(resultRanges.size() == rangeBoundaries.size());
566             resultRanges.shrink(0); // Keep capacity as we are going to repopulate the Vector right away with the same number of items.
567             for (auto& rangeBoundary : rangeBoundaries)
568                 resultRanges.uncheckedAppend(Range::create(*rangeBoundary.first.document(), rangeBoundary.first, rangeBoundary.second));
569         }
570         
571         lastModifiedQueryOffset = queryRange.end;
572 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
573         BOOL shouldUseLightLinks = softLink_DataDetectorsCore_DDShouldUseLightLinksForResult(coreResult, [indexPaths[resultIndex] length] > 1);
574 #else
575         BOOL shouldUseLightLinks = NO;
576 #endif
577
578         for (auto& range : resultRanges) {
579             auto* parentNode = range->startContainer().parentNode();
580             if (!parentNode)
581                 continue;
582             if (!is<Text>(range->startContainer()))
583                 continue;
584             auto& currentTextNode = downcast<Text>(range->startContainer());
585             Document& document = currentTextNode.document();
586             String textNodeData;
587             
588             if (lastTextNodeToUpdate != &currentTextNode) {
589                 if (lastTextNodeToUpdate)
590                     lastTextNodeToUpdate->setData(lastNodeContent);
591                 contentOffset = 0;
592                 if (range->startOffset() > 0)
593                     textNodeData = currentTextNode.data().substring(0, range->startOffset());
594             } else
595                 textNodeData = currentTextNode.data().substring(contentOffset, range->startOffset() - contentOffset);
596             
597             if (!textNodeData.isEmpty()) {
598                 parentNode->insertBefore(Text::create(document, textNodeData), &currentTextNode);
599                 contentOffset = range->startOffset();
600             }
601             
602             // Create the actual anchor node and insert it before the current node.
603             textNodeData = currentTextNode.data().substring(range->startOffset(), range->endOffset() - range->startOffset());
604             Ref<Text> newTextNode = Text::create(document, textNodeData);
605             parentNode->insertBefore(newTextNode.copyRef(), &currentTextNode);
606             
607             Ref<HTMLAnchorElement> anchorElement = HTMLAnchorElement::create(document);
608             anchorElement->setHref(correspondingURL);
609             anchorElement->setDir("ltr");
610             if (shouldUseLightLinks) {
611                 document.updateStyleIfNeeded();
612                 auto* renderStyle = parentNode->computedStyle();
613                 if (renderStyle) {
614                     auto textColor = renderStyle->visitedDependentColor(CSSPropertyColor);
615                     if (textColor.isValid()) {
616                         double h = 0;
617                         double s = 0;
618                         double v = 0;
619                         textColor.getHSV(h, s, v);
620
621                         // Set the alpha of the underline to 46% if the text color is white-ish (defined
622                         // as having a saturation of less than 2% and a value/brightness or greater than
623                         // 98%). Otherwise, set the alpha of the underline to 26%.
624                         double overrideAlpha = (s < 0.02 && v > 0.98) ? 0.46 : 0.26;
625                         auto underlineColor = Color(colorWithOverrideAlpha(textColor.rgb(), overrideAlpha));
626
627                         anchorElement->setInlineStyleProperty(CSSPropertyColor, textColor.cssText());
628                         anchorElement->setInlineStyleProperty(CSSPropertyWebkitTextDecorationColor, underlineColor.cssText());
629                     }
630                 }
631             } else if (is<StyledElement>(*parentNode)) {
632                 if (auto* style = downcast<StyledElement>(*parentNode).presentationAttributeStyle()) {
633                     String color = style->getPropertyValue(CSSPropertyColor);
634                     if (!color.isEmpty())
635                         anchorElement->setInlineStyleProperty(CSSPropertyColor, color);
636                 }
637             }
638             anchorElement->appendChild(WTFMove(newTextNode));
639             // Add a special attribute to mark this URLification as the result of data detectors.
640             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectorsAttr, AtomicString("true", AtomicString::ConstructFromLiteral));
641             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_typeAttr, dataDetectorTypeForCategory(softLink_DataDetectorsCore_DDResultGetCategory(coreResult)));
642             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_resultAttr, identifier);
643
644             parentNode->insertBefore(WTFMove(anchorElement), &currentTextNode);
645
646             contentOffset = range->endOffset();
647             
648             lastNodeContent = currentTextNode.data().substring(range->endOffset(), currentTextNode.length() - range->endOffset());
649             lastTextNodeToUpdate = &currentTextNode;
650         }        
651     }
652     if (lastTextNodeToUpdate)
653         lastTextNodeToUpdate->setData(lastNodeContent);
654     
655     return [get_DataDetectorsCore_DDScannerResultClass() resultsFromCoreResults:scannerResults.get()];
656 }
657
658 #else
659 NSArray *DataDetection::detectContentInRange(RefPtr<Range>&, DataDetectorTypes, NSDictionary *)
660 {
661     return nil;
662 }
663 #endif
664
665 const String& DataDetection::dataDetectorURLProtocol()
666 {
667     static NeverDestroyed<String> protocol(MAKE_STATIC_STRING_IMPL("x-apple-data-detectors"));
668     return protocol;
669 }
670
671 bool DataDetection::isDataDetectorURL(const URL& url)
672 {
673     return url.protocolIs(dataDetectorURLProtocol());
674 }
675
676 } // namespace WebCore
677
678 #endif
679