c61e3565827db4eeeffa76708d6a0941943e80ea
[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 "Editing.h"
34 #import "ElementAncestorIterator.h"
35 #import "ElementTraversal.h"
36 #import "FrameView.h"
37 #import "HTMLAnchorElement.h"
38 #import "HTMLNames.h"
39 #import "HTMLTextFormControlElement.h"
40 #import "HitTestResult.h"
41 #import "Node.h"
42 #import "NodeList.h"
43 #import "NodeTraversal.h"
44 #import "Range.h"
45 #import "RenderObject.h"
46 #import "SimpleRange.h"
47 #import "StyleProperties.h"
48 #import "Text.h"
49 #import "TextIterator.h"
50 #import "VisiblePosition.h"
51 #import "VisibleUnits.h"
52 #import <pal/spi/ios/DataDetectorsUISPI.h>
53 #import <pal/spi/mac/DataDetectorsSPI.h>
54 #import <wtf/cf/TypeCastsCF.h>
55 #import <wtf/text/StringBuilder.h>
56
57 #import "DataDetectorsCoreSoftLink.h"
58
59 #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
60 template <>
61 struct WTF::CFTypeTrait<DDResultRef> {
62     static inline CFTypeID typeID(void) { return DDResultGetCFTypeID(); }
63 };
64 #endif
65
66 namespace WebCore {
67
68 using namespace HTMLNames;
69
70 #if PLATFORM(MAC)
71
72 static RetainPtr<DDActionContext> detectItemAtPositionWithRange(VisiblePosition position, RefPtr<Range> contextRange, FloatRect& detectedDataBoundingBox, RefPtr<Range>& detectedDataRange)
73 {
74     String fullPlainTextString = plainText(contextRange.get());
75     int hitLocation = TextIterator::rangeLength(makeRange(contextRange->startPosition(), position).get());
76
77     RetainPtr<DDScannerRef> scanner = adoptCF(DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
78     RetainPtr<DDScanQueryRef> scanQuery = adoptCF(DDScanQueryCreateFromString(kCFAllocatorDefault, fullPlainTextString.createCFString().get(), CFRangeMake(0, fullPlainTextString.length())));
79
80     if (!DDScannerScanQuery(scanner.get(), scanQuery.get()))
81         return nullptr;
82
83     RetainPtr<CFArrayRef> results = adoptCF(DDScannerCopyResultsWithOptions(scanner.get(), DDScannerCopyResultsOptionsNoOverlap));
84
85     // Find the DDResultRef that intersects the hitTestResult's VisiblePosition.
86     DDResultRef mainResult = nullptr;
87     RefPtr<Range> mainResultRange;
88     CFIndex resultCount = CFArrayGetCount(results.get());
89     for (CFIndex i = 0; i < resultCount; i++) {
90 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
91         DDResultRef result = checked_cf_cast<DDResultRef>(CFArrayGetValueAtIndex(results.get(), i));
92 #else
93         DDResultRef result = static_cast<DDResultRef>(const_cast<CF_BRIDGED_TYPE(id) void*>(CFArrayGetValueAtIndex(results.get(), i)));
94 #endif
95         CFRange resultRangeInContext = DDResultGetRange(result);
96         if (hitLocation >= resultRangeInContext.location && (hitLocation - resultRangeInContext.location) < resultRangeInContext.length) {
97             mainResult = result;
98             mainResultRange = TextIterator::subrange(*contextRange, resultRangeInContext.location, resultRangeInContext.length);
99             break;
100         }
101     }
102
103     if (!mainResult)
104         return nullptr;
105
106     RetainPtr<DDActionContext> actionContext = adoptNS([allocDDActionContextInstance() init]);
107     [actionContext setAllResults:@[ (__bridge id)mainResult ]];
108     [actionContext setMainResult:mainResult];
109
110     Vector<FloatQuad> quads;
111     mainResultRange->absoluteTextQuads(quads);
112     detectedDataBoundingBox = FloatRect();
113     FrameView* frameView = mainResultRange->ownerDocument().view();
114     for (const auto& quad : quads)
115         detectedDataBoundingBox.unite(frameView->contentsToWindow(quad.enclosingBoundingBox()));
116     
117     detectedDataRange = mainResultRange;
118     
119     return actionContext;
120 }
121
122 RetainPtr<DDActionContext> DataDetection::detectItemAroundHitTestResult(const HitTestResult& hitTestResult, FloatRect& detectedDataBoundingBox, RefPtr<Range>& detectedDataRange)
123 {
124     if (!DataDetectorsLibrary())
125         return nullptr;
126
127     Node* node = hitTestResult.innerNonSharedNode();
128     if (!node)
129         return nullptr;
130     auto renderer = node->renderer();
131     if (!renderer)
132         return nullptr;
133
134     VisiblePosition position;
135     RefPtr<Range> contextRange;
136
137     if (!is<HTMLTextFormControlElement>(*node)) {
138         position = renderer->positionForPoint(hitTestResult.localPoint(), nullptr);
139         if (position.isNull())
140             position = firstPositionInOrBeforeNode(node);
141
142         contextRange = rangeExpandedAroundPositionByCharacters(position, 250);
143         if (!contextRange)
144             return nullptr;
145     } else {
146         Frame* frame = node->document().frame();
147         if (!frame)
148             return nullptr;
149
150         IntPoint framePoint = hitTestResult.roundedPointInInnerNodeFrame();
151         if (!frame->rangeForPoint(framePoint))
152             return nullptr;
153
154         VisiblePosition position = frame->visiblePositionForPoint(framePoint);
155         if (position.isNull())
156             return nullptr;
157
158         contextRange = enclosingTextUnitOfGranularity(position, LineGranularity, DirectionForward);
159         if (!contextRange)
160             return nullptr;
161     }
162
163     return detectItemAtPositionWithRange(position, contextRange, detectedDataBoundingBox, detectedDataRange);
164 }
165 #endif // PLATFORM(MAC)
166
167 #if PLATFORM(IOS_FAMILY)
168
169 bool DataDetection::canBePresentedByDataDetectors(const URL& url)
170 {
171     return [softLink_DataDetectorsCore_DDURLTapAndHoldSchemes() containsObject:(NSString *)url.protocol().toStringWithoutCopying().convertToASCIILowercase()];
172 }
173
174 bool DataDetection::isDataDetectorLink(Element& element)
175 {
176     if (!is<HTMLAnchorElement>(element))
177         return false;
178
179     return canBePresentedByDataDetectors(downcast<HTMLAnchorElement>(element).href());
180 }
181
182 bool DataDetection::requiresExtendedContext(Element& element)
183 {
184     return equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectors_typeAttr), "calendar-event");
185 }
186
187 String DataDetection::dataDetectorIdentifier(Element& element)
188 {
189     return element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
190 }
191
192 bool DataDetection::shouldCancelDefaultAction(Element& element)
193 {
194     if (!isDataDetectorLink(element))
195         return false;
196     
197     if (softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForURL(downcast<HTMLAnchorElement>(element).href()))
198         return true;
199     
200     const AtomString& resultAttribute = element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
201     if (resultAttribute.isEmpty())
202         return false;
203     NSArray *results = element.document().frame()->dataDetectionResults();
204     if (!results)
205         return false;
206     Vector<String> resultIndices = resultAttribute.string().split('/');
207     DDResultRef result = [[results objectAtIndex:resultIndices[0].toInt()] coreResult];
208     // Handle the case of a signature block, where we need to check the correct subresult.
209     for (size_t i = 1; i < resultIndices.size(); i++) {
210         results = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
211         result = (DDResultRef)[results objectAtIndex:resultIndices[i].toInt()];
212     }
213     return softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForResult(result);
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         || ((detectionTypes & DataDetectorTypeLookupSuggestion) && (CFStringCompare(get_DataDetectorsCore_DDBinderParsecSourceKey(), type, 0) == kCFCompareEqualTo))
238         || ((detectionTypes & DataDetectorTypePhoneNumber) && (DDResultCategoryPhoneNumber == category))
239         || ((detectionTypes & DataDetectorTypeLink) && resultIsURL(currentResult))) {
240         
241         return softLink_DataDetectorsCore_DDURLStringForResult(currentResult, resultIdentifier, phoneTypes, referenceDate, referenceTimeZone);
242     }
243     if ((detectionTypes & DataDetectorTypeCalendarEvent) && (DDResultCategoryCalendarEvent == category)) {
244         if (!softLink_DataDetectorsCore_DDResultIsPastDate(currentResult, (CFDateRef)referenceDate, (CFTimeZoneRef)referenceTimeZone))
245             return softLink_DataDetectorsCore_DDURLStringForResult(currentResult, resultIdentifier, phoneTypes, referenceDate, referenceTimeZone);
246     }
247     return nil;
248 }
249
250 static void removeResultLinksFromAnchor(Element& element)
251 {
252     // Perform a depth-first search for anchor nodes, which have the data detectors attribute set to true,
253     // take their children and insert them before the anchor, and then remove the anchor.
254
255     // Note that this is not using ElementChildIterator because we potentially prepend children as we iterate over them.
256     for (auto* child = ElementTraversal::firstChild(element); child; child = ElementTraversal::nextSibling(*child))
257         removeResultLinksFromAnchor(*child);
258
259     auto* elementParent = element.parentElement();
260     if (!elementParent)
261         return;
262     
263     bool elementIsDDAnchor = is<HTMLAnchorElement>(element) && equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true");
264     if (!elementIsDDAnchor)
265         return;
266
267     // Iterate over the children and move them all onto the same level as this anchor. Remove the anchor afterwards.
268     while (auto* child = element.firstChild())
269         elementParent->insertBefore(*child, &element);
270
271     elementParent->removeChild(element);
272 }
273
274 static bool searchForLinkRemovingExistingDDLinks(Node& startNode, Node& endNode, bool& didModifyDOM)
275 {
276     didModifyDOM = false;
277     for (Node* node = &startNode; node; node = NodeTraversal::next(*node)) {
278         if (is<HTMLAnchorElement>(*node)) {
279             auto& anchor = downcast<HTMLAnchorElement>(*node);
280             if (!equalIgnoringASCIICase(anchor.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
281                 return true;
282             removeResultLinksFromAnchor(anchor);
283             didModifyDOM = true;
284         }
285         
286         if (node == &endNode) {
287             // If we found the end node and no link, return false unless an ancestor node is a link.
288             // The only ancestors not tested at this point are in the direct line from self's parent to the top.
289             for (auto& anchor : ancestorsOfType<HTMLAnchorElement>(startNode)) {
290                 if (!equalIgnoringASCIICase(anchor.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
291                     return true;
292                 removeResultLinksFromAnchor(anchor);
293                 didModifyDOM = true;
294             }
295             return false;
296         }
297     }
298     return false;
299 }
300
301 static NSString *dataDetectorTypeForCategory(DDResultCategory category)
302 {
303     switch (category) {
304     case DDResultCategoryPhoneNumber:
305         return @"telephone";
306     case DDResultCategoryLink:
307         return @"link";
308     case DDResultCategoryAddress:
309         return @"address";
310     case DDResultCategoryCalendarEvent:
311         return @"calendar-event";
312     case DDResultCategoryMisc:
313         return @"misc";
314     default:
315         return @"";
316     }
317 }
318
319 static String dataDetectorStringForPath(NSIndexPath *path)
320 {
321     NSUInteger length = path.length;
322     
323     switch (length) {
324     case 0:
325         return { };
326     case 1:
327         return String::number([path indexAtPosition:0]);
328     case 2: {
329         StringBuilder stringBuilder;
330         stringBuilder.appendNumber([path indexAtPosition:0]);
331         stringBuilder.append('/');
332         stringBuilder.appendNumber([path indexAtPosition:1]);
333         return stringBuilder.toString();
334     }
335     default: {
336         StringBuilder stringBuilder;
337         stringBuilder.appendNumber([path indexAtPosition:0]);
338         for (NSUInteger i = 1 ; i < length ; i++) {
339             stringBuilder.append('/');
340             stringBuilder.appendNumber([path indexAtPosition:i]);
341         }
342
343         return stringBuilder.toString();
344     }
345     }
346 }
347
348 static void buildQuery(DDScanQueryRef scanQuery, Range* contextRange)
349 {
350     // Once we're over this number of fragments, stop at the first hard break.
351     const CFIndex maxFragmentWithHardBreak = 1000;
352     // Once we're over this number of fragments, we stop at the line.
353     const CFIndex maxFragmentWithLinebreak = 5000;
354     // Once we're over this number of fragments, we stop at the space.
355     const CFIndex maxFragmentSpace = 10000;
356
357     CFCharacterSetRef whiteSpacesSet = CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline);
358     CFCharacterSetRef newLinesSet = CFCharacterSetGetPredefined(kCFCharacterSetNewline);
359     
360     RefPtr<Range> endRange;
361     CFIndex iteratorCount = 0;
362     CFIndex fragmentCount = 0;
363     
364     // Build the scan query adding separators.
365     // For each fragment the iterator increment is stored as metadata.
366     for (TextIterator iterator(contextRange); !iterator.atEnd(); iterator.advance(), iteratorCount++) {
367         StringView currentText = iterator.text();
368         size_t currentTextLength = currentText.length();
369         if (!currentTextLength) {
370             softLink_DataDetectorsCore_DDScanQueryAddSeparator(scanQuery, DDTextCoalescingTypeHardBreak);
371             if (iteratorCount > maxFragmentWithHardBreak)
372                 break;
373             continue;
374         }
375         // Test for white space nodes, we're coalescing them.
376         auto currentTextUpconvertedCharacters = currentText.upconvertedCharacters();
377         const UniChar* currentCharPtr = currentTextUpconvertedCharacters.get();
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         auto currentTextCFString = adoptCF(CFStringCreateWithCharacters(kCFAllocatorDefault, currentTextUpconvertedCharacters.get(), currentTextLength));
417         softLink_DataDetectorsCore_DDScanQueryAddTextFragment(scanQuery, currentTextCFString.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 void DataDetection::removeDataDetectedLinksInDocument(Document& document)
436 {
437     Vector<Ref<HTMLAnchorElement>> allAnchorElements;
438     for (auto& anchor : descendantsOfType<HTMLAnchorElement>(document))
439         allAnchorElements.append(anchor);
440
441     for (auto& anchor : allAnchorElements)
442         removeResultLinksFromAnchor(anchor.get());
443 }
444
445 NSArray *DataDetection::detectContentInRange(RefPtr<Range>& contextRange, DataDetectorTypes types, NSDictionary *context)
446 {
447     RetainPtr<DDScannerRef> scanner = adoptCF(softLink_DataDetectorsCore_DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
448     RetainPtr<DDScanQueryRef> scanQuery = adoptCF(softLink_DataDetectorsCore_DDScanQueryCreate(NULL));
449     buildQuery(scanQuery.get(), contextRange.get());
450     
451     if (types & DataDetectorTypeLookupSuggestion)
452         softLink_DataDetectorsCore_DDScannerEnableOptionalSource(scanner.get(), DDScannerSourceSpotlight, true);
453
454     // FIXME: we should add a timeout to this call to make sure it doesn't take too much time.
455     if (!softLink_DataDetectorsCore_DDScannerScanQuery(scanner.get(), scanQuery.get()))
456         return nil;
457
458     RetainPtr<CFArrayRef> scannerResults = adoptCF(softLink_DataDetectorsCore_DDScannerCopyResultsWithOptions(scanner.get(), get_DataDetectorsCore_DDScannerCopyResultsOptionsForPassiveUse() | DDScannerCopyResultsOptionsCoalesceSignatures));
459     if (!scannerResults)
460         return nil;
461
462     CFIndex resultCount = CFArrayGetCount(scannerResults.get());
463     if (!resultCount)
464         return nil;
465
466     Vector<RetainPtr<DDResultRef>> allResults;
467     Vector<RetainPtr<NSIndexPath>> indexPaths;
468     NSInteger currentTopLevelIndex = 0;
469
470     // Iterate through the scanner results to find signatures and extract all the subresults while
471     // populating the array of index paths to use in the href of the anchors being created.
472     for (id resultObject in (NSArray *)scannerResults.get()) {
473         DDResultRef result = (DDResultRef)resultObject;
474         NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:currentTopLevelIndex];
475         if (CFStringCompare(softLink_DataDetectorsCore_DDResultGetType(result), get_DataDetectorsCore_DDBinderSignatureBlockKey(), 0) == kCFCompareEqualTo) {
476             NSArray *subresults = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
477             
478             for (NSUInteger subResultIndex = 0 ; subResultIndex < [subresults count] ; subResultIndex++) {
479                 indexPaths.append([indexPath indexPathByAddingIndex:subResultIndex]);
480                 allResults.append((DDResultRef)[subresults objectAtIndex:subResultIndex]);
481             }
482         } else {
483             allResults.append(result);
484             indexPaths.append(indexPath);
485         }
486         currentTopLevelIndex++;
487     }
488
489     Vector<Vector<RefPtr<Range>>> allResultRanges;
490     TextIterator iterator(contextRange.get());
491     CFIndex iteratorCount = 0;
492
493     // Iterate through the array of the expanded results to create a vector of Range objects that indicate
494     // where the DOM needs to be modified.
495     // Each result can be contained all in one text node or can span multiple text nodes.
496     for (auto& result : allResults) {
497         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(result.get());
498         CFIndex iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), queryRange.start.queryIndex);
499         for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
500             iterator.advance();
501
502         Vector<RefPtr<Range>> fragmentRanges;
503         RefPtr<Range> currentRange = createLiveRange(iterator.range());
504         CFIndex fragmentIndex = queryRange.start.queryIndex;
505         if (fragmentIndex == queryRange.end.queryIndex)
506             fragmentRanges.append(TextIterator::subrange(*currentRange, queryRange.start.offset, queryRange.end.offset - queryRange.start.offset));
507         else {
508             if (!queryRange.start.offset)
509                 fragmentRanges.append(currentRange);
510             else
511                 fragmentRanges.append(Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset() + queryRange.start.offset, &currentRange->endContainer(), currentRange->endOffset()));
512         }
513         
514         while (fragmentIndex < queryRange.end.queryIndex) {
515             ++fragmentIndex;
516             iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), fragmentIndex);
517             for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
518                 iterator.advance();
519
520             currentRange = createLiveRange(iterator.range());
521             RefPtr<Range> fragmentRange = (fragmentIndex == queryRange.end.queryIndex) ?  Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset(), &currentRange->endContainer(), currentRange->startOffset() + queryRange.end.offset) : currentRange;
522             RefPtr<Range> previousRange = fragmentRanges.last();
523             if (&previousRange->startContainer() == &fragmentRange->startContainer()) {
524                 fragmentRange = Range::create(currentRange->ownerDocument(), &previousRange->startContainer(), previousRange->startOffset(), &fragmentRange->endContainer(), fragmentRange->endOffset());
525                 fragmentRanges.last() = fragmentRange;
526             } else
527                 fragmentRanges.append(fragmentRange);
528         }
529         allResultRanges.append(WTFMove(fragmentRanges));
530     }
531     
532     auto tz = adoptCF(CFTimeZoneCopyDefault());
533     NSDate *referenceDate = [context objectForKey:getkDataDetectorsReferenceDateKey()] ?: [NSDate date];
534     Text* lastTextNodeToUpdate = nullptr;
535     String lastNodeContent;
536     size_t contentOffset = 0;
537     DDQueryOffset lastModifiedQueryOffset = { -1, 0 };
538     
539     // For each result add the link.
540     // Since there could be multiple results in the same text node, the node is only modified when
541     // we are about to process a different text node.
542     resultCount = allResults.size();
543     
544     for (CFIndex resultIndex = 0; resultIndex < resultCount; ++resultIndex) {
545         DDResultRef coreResult = allResults[resultIndex].get();
546         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(coreResult);
547         auto& resultRanges = allResultRanges[resultIndex];
548
549         // Compare the query offsets to make sure we don't go backwards
550         if (queryOffsetCompare(lastModifiedQueryOffset, queryRange.start) >= 0)
551             continue;
552
553         if (resultRanges.isEmpty())
554             continue;
555         
556         // Store the range boundaries as Position, because the DOM could change if we find
557         // old data detector link.
558         Vector<std::pair<Position, Position>> rangeBoundaries;
559         rangeBoundaries.reserveInitialCapacity(resultRanges.size());
560         for (auto& range : resultRanges)
561             rangeBoundaries.uncheckedAppend({ range->startPosition(), range->endPosition() });
562
563         NSString *identifier = dataDetectorStringForPath(indexPaths[resultIndex].get());
564         NSString *correspondingURL = constructURLStringForResult(coreResult, identifier, referenceDate, (NSTimeZone *)tz.get(), types);
565         bool didModifyDOM = false;
566
567         if (!correspondingURL || searchForLinkRemovingExistingDDLinks(resultRanges.first()->startContainer(), resultRanges.last()->endContainer(), didModifyDOM))
568             continue;
569         
570         if (didModifyDOM) {
571             // If the DOM was modified because some old links were removed,
572             // we need to recreate the ranges because they could no longer be valid.
573             ASSERT(resultRanges.size() == rangeBoundaries.size());
574             resultRanges.shrink(0); // Keep capacity as we are going to repopulate the Vector right away with the same number of items.
575             for (auto& rangeBoundary : rangeBoundaries)
576                 resultRanges.uncheckedAppend(Range::create(*rangeBoundary.first.document(), rangeBoundary.first, rangeBoundary.second));
577         }
578         
579         lastModifiedQueryOffset = queryRange.end;
580         BOOL shouldUseLightLinks = softLink_DataDetectorsCore_DDShouldUseLightLinksForResult(coreResult, [indexPaths[resultIndex] length] > 1);
581
582         for (auto& range : resultRanges) {
583             auto* parentNode = range->startContainer().parentNode();
584             if (!parentNode)
585                 continue;
586
587             if (!is<Text>(range->startContainer()))
588                 continue;
589
590             auto& currentTextNode = downcast<Text>(range->startContainer());
591             Document& document = currentTextNode.document();
592             String textNodeData;
593
594             if (lastTextNodeToUpdate != &currentTextNode) {
595                 if (lastTextNodeToUpdate)
596                     lastTextNodeToUpdate->setData(lastNodeContent);
597                 contentOffset = 0;
598                 if (range->startOffset() > 0)
599                     textNodeData = currentTextNode.data().substring(0, range->startOffset());
600             } else
601                 textNodeData = currentTextNode.data().substring(contentOffset, range->startOffset() - contentOffset);
602
603             if (!textNodeData.isEmpty()) {
604                 parentNode->insertBefore(Text::create(document, textNodeData), &currentTextNode);
605                 contentOffset = range->startOffset();
606             }
607
608             // Create the actual anchor node and insert it before the current node.
609             textNodeData = currentTextNode.data().substring(range->startOffset(), range->endOffset() - range->startOffset());
610             Ref<Text> newTextNode = Text::create(document, textNodeData);
611             parentNode->insertBefore(newTextNode.copyRef(), &currentTextNode);
612             
613             Ref<HTMLAnchorElement> anchorElement = HTMLAnchorElement::create(document);
614             anchorElement->setHref(correspondingURL);
615             anchorElement->setDir("ltr");
616
617             if (shouldUseLightLinks) {
618                 document.updateStyleIfNeeded();
619
620                 auto* renderStyle = parentNode->computedStyle();
621                 if (renderStyle) {
622                     auto textColor = renderStyle->visitedDependentColor(CSSPropertyColor);
623                     if (textColor.isValid()) {
624                         double hue, saturation, lightness;
625                         textColor.getHSL(hue, saturation, lightness);
626
627                         // Force the lightness of the underline color to the middle, and multiply the alpha by 38%,
628                         // so the color will appear on light and dark backgrounds, since only one color can be specified.
629                         double overrideLightness = 0.5;
630                         double overrideAlphaMultiplier = 0.38;
631                         auto underlineColor = Color(makeRGBAFromHSLA(hue, saturation, overrideLightness, overrideAlphaMultiplier * textColor.alphaAsFloat()));
632
633                         anchorElement->setInlineStyleProperty(CSSPropertyColor, CSSValueCurrentcolor);
634                         anchorElement->setInlineStyleProperty(CSSPropertyTextDecorationColor, underlineColor.cssText());
635                     }
636                 }
637             }
638
639             anchorElement->appendChild(WTFMove(newTextNode));
640
641             // Add a special attribute to mark this URLification as the result of data detectors.
642             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectorsAttr, AtomString("true", AtomString::ConstructFromLiteral));
643             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_typeAttr, dataDetectorTypeForCategory(softLink_DataDetectorsCore_DDResultGetCategory(coreResult)));
644             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_resultAttr, identifier);
645
646             parentNode->insertBefore(WTFMove(anchorElement), &currentTextNode);
647
648             contentOffset = range->endOffset();
649
650             lastNodeContent = currentTextNode.data().substring(range->endOffset(), currentTextNode.length() - range->endOffset());
651             lastTextNodeToUpdate = &currentTextNode;
652         }        
653     }
654
655     if (lastTextNodeToUpdate)
656         lastTextNodeToUpdate->setData(lastNodeContent);
657     
658     return [getDDScannerResultClass() resultsFromCoreResults:scannerResults.get()];
659 }
660
661 #else
662
663 NSArray *DataDetection::detectContentInRange(RefPtr<Range>&, DataDetectorTypes, NSDictionary *)
664 {
665     return nil;
666 }
667
668 void DataDetection::removeDataDetectedLinksInDocument(Document&)
669 {
670 }
671
672 #endif
673
674 const String& DataDetection::dataDetectorURLProtocol()
675 {
676     static NeverDestroyed<String> protocol(MAKE_STATIC_STRING_IMPL("x-apple-data-detectors"));
677     return protocol;
678 }
679
680 bool DataDetection::isDataDetectorURL(const URL& url)
681 {
682     return url.protocolIs(dataDetectorURLProtocol());
683 }
684
685 } // namespace WebCore
686
687 #endif
688