NeverDestroyed<String>(ASCIILiteral(...)) is not thread safe.
[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 #import "Attr.h"
30 #import "CSSStyleDeclaration.h"
31 #import "DataDetectorsSPI.h"
32 #import "DataDetectorsUISPI.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 "StyleProperties.h"
47 #import "Text.h"
48 #import "TextIterator.h"
49 #import "VisiblePosition.h"
50 #import "VisibleUnits.h"
51 #import <wtf/text/StringBuilder.h>
52
53 #import "DataDetectorsCoreSoftLink.h"
54
55 namespace WebCore {
56
57 using namespace HTMLNames;
58
59 #if PLATFORM(MAC)
60
61 static RetainPtr<DDActionContext> detectItemAtPositionWithRange(VisiblePosition position, RefPtr<Range> contextRange, FloatRect& detectedDataBoundingBox, RefPtr<Range>& detectedDataRange)
62 {
63     String fullPlainTextString = plainText(contextRange.get());
64     int hitLocation = TextIterator::rangeLength(makeRange(contextRange->startPosition(), position).get());
65
66     RetainPtr<DDScannerRef> scanner = adoptCF(DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
67     RetainPtr<DDScanQueryRef> scanQuery = adoptCF(DDScanQueryCreateFromString(kCFAllocatorDefault, fullPlainTextString.createCFString().get(), CFRangeMake(0, fullPlainTextString.length())));
68
69     if (!DDScannerScanQuery(scanner.get(), scanQuery.get()))
70         return nullptr;
71
72     RetainPtr<CFArrayRef> results = adoptCF(DDScannerCopyResultsWithOptions(scanner.get(), DDScannerCopyResultsOptionsNoOverlap));
73
74     // Find the DDResultRef that intersects the hitTestResult's VisiblePosition.
75     DDResultRef mainResult = nullptr;
76     RefPtr<Range> mainResultRange;
77     CFIndex resultCount = CFArrayGetCount(results.get());
78     for (CFIndex i = 0; i < resultCount; i++) {
79         DDResultRef result = (DDResultRef)CFArrayGetValueAtIndex(results.get(), i);
80         CFRange resultRangeInContext = DDResultGetRange(result);
81         if (hitLocation >= resultRangeInContext.location && (hitLocation - resultRangeInContext.location) < resultRangeInContext.length) {
82             mainResult = result;
83             mainResultRange = TextIterator::subrange(contextRange.get(), resultRangeInContext.location, resultRangeInContext.length);
84             break;
85         }
86     }
87
88     if (!mainResult)
89         return nullptr;
90
91     RetainPtr<DDActionContext> actionContext = adoptNS([allocDDActionContextInstance() init]);
92     [actionContext setAllResults:@[ (id)mainResult ]];
93     [actionContext setMainResult:mainResult];
94
95     Vector<FloatQuad> quads;
96     mainResultRange->absoluteTextQuads(quads);
97     detectedDataBoundingBox = FloatRect();
98     FrameView* frameView = mainResultRange->ownerDocument().view();
99     for (const auto& quad : quads)
100         detectedDataBoundingBox.unite(frameView->contentsToWindow(quad.enclosingBoundingBox()));
101     
102     detectedDataRange = mainResultRange;
103     
104     return actionContext;
105 }
106
107 RetainPtr<DDActionContext> DataDetection::detectItemAroundHitTestResult(const HitTestResult& hitTestResult, FloatRect& detectedDataBoundingBox, RefPtr<Range>& detectedDataRange)
108 {
109     if (!DataDetectorsLibrary())
110         return nullptr;
111
112     Node* node = hitTestResult.innerNonSharedNode();
113     if (!node)
114         return nullptr;
115     auto renderer = node->renderer();
116     if (!renderer)
117         return nullptr;
118
119     VisiblePosition position;
120     RefPtr<Range> contextRange;
121
122     if (!is<HTMLTextFormControlElement>(*node)) {
123         position = renderer->positionForPoint(hitTestResult.localPoint(), nullptr);
124         if (position.isNull())
125             position = firstPositionInOrBeforeNode(node);
126
127         contextRange = rangeExpandedAroundPositionByCharacters(position, 250);
128         if (!contextRange)
129             return nullptr;
130     } else {
131         Frame* frame = node->document().frame();
132         if (!frame)
133             return nullptr;
134
135         IntPoint framePoint = hitTestResult.roundedPointInInnerNodeFrame();
136         if (!frame->rangeForPoint(framePoint))
137             return nullptr;
138
139         VisiblePosition position = frame->visiblePositionForPoint(framePoint);
140         if (position.isNull())
141             return nullptr;
142
143         contextRange = enclosingTextUnitOfGranularity(position, LineGranularity, DirectionForward);
144         if (!contextRange)
145             return nullptr;
146     }
147
148     return detectItemAtPositionWithRange(position, contextRange, detectedDataBoundingBox, detectedDataRange);
149 }
150 #endif // PLATFORM(MAC)
151
152 #if PLATFORM(IOS)
153 bool DataDetection::isDataDetectorLink(Element& element)
154 {
155     if (!is<HTMLAnchorElement>(element))
156         return false;
157
158 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
159     return [softLink_DataDetectorsCore_DDURLTapAndHoldSchemes() containsObject:(NSString *)downcast<HTMLAnchorElement>(element).href().protocol().toStringWithoutCopying().convertToASCIILowercase()];
160 #else
161     if (equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
162         return true;
163     URL url = downcast<HTMLAnchorElement>(element).href();
164     return url.protocolIs("mailto") || url.protocolIs("tel");
165 #endif
166 }
167
168 bool DataDetection::requiresExtendedContext(Element& element)
169 {
170     return equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectors_typeAttr), "calendar-event");
171 }
172
173 String DataDetection::dataDetectorIdentifier(Element& element)
174 {
175     return element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
176 }
177
178 bool DataDetection::shouldCancelDefaultAction(Element& element)
179 {
180 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
181     if (!isDataDetectorLink(element))
182         return false;
183     
184     if (softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForURL(downcast<HTMLAnchorElement>(element).href()))
185         return true;
186     
187     const AtomicString& resultAttribute = element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
188     if (resultAttribute.isEmpty())
189         return false;
190     NSArray *results = element.document().frame()->dataDetectionResults();
191     if (!results)
192         return false;
193     Vector<String> resultIndices;
194     resultAttribute.string().split('/', resultIndices);
195     DDResultRef result = [[results objectAtIndex:resultIndices[0].toInt()] coreResult];
196     // Handle the case of a signature block, where we need to check the correct subresult.
197     for (size_t i = 1; i < resultIndices.size(); i++) {
198         results = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
199         result = (DDResultRef)[results objectAtIndex:resultIndices[i].toInt()];
200     }
201     return softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForResult(result);
202 #else
203     if (!is<HTMLAnchorElement>(element))
204         return false;
205     if (!equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
206         return false;
207     String type = element.getAttribute(x_apple_data_detectors_typeAttr).convertToASCIILowercase();
208     if (type == "misc" || type == "calendar-event" || type == "telephone")
209         return true;
210     return false;
211 #endif
212 }
213
214 static BOOL resultIsURL(DDResultRef result)
215 {
216     if (!result)
217         return NO;
218     
219     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];
220     return [urlTypes containsObject:(NSString *)softLink_DataDetectorsCore_DDResultGetType(result)];
221 }
222
223 static NSString *constructURLStringForResult(DDResultRef currentResult, NSString *resultIdentifier, NSDate *referenceDate, NSTimeZone *referenceTimeZone, DataDetectorTypes detectionTypes)
224 {
225     if (!softLink_DataDetectorsCore_DDResultHasProperties(currentResult, DDResultPropertyPassiveDisplay))
226         return nil;
227     
228     DDURLifierPhoneNumberDetectionTypes phoneTypes = (detectionTypes & DataDetectorTypePhoneNumber) ? DDURLifierPhoneNumberDetectionRegular : DDURLifierPhoneNumberDetectionNone;
229     DDResultCategory category = softLink_DataDetectorsCore_DDResultGetCategory(currentResult);
230     CFStringRef type = softLink_DataDetectorsCore_DDResultGetType(currentResult);
231     
232     if (((detectionTypes & DataDetectorTypeAddress) && (DDResultCategoryAddress == category))
233         || ((detectionTypes & DataDetectorTypeTrackingNumber) && (CFStringCompare(get_DataDetectorsCore_DDBinderTrackingNumberKey(), type, 0) == kCFCompareEqualTo))
234         || ((detectionTypes & DataDetectorTypeFlightNumber) && (CFStringCompare(get_DataDetectorsCore_DDBinderFlightInformationKey(), type, 0) == kCFCompareEqualTo))
235 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
236         || ((detectionTypes & DataDetectorTypeLookupSuggestion) && (CFStringCompare(get_DataDetectorsCore_DDBinderParsecSourceKey(), type, 0) == kCFCompareEqualTo))
237 #endif
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 DDURLScheme 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((unsigned long)[path indexAtPosition:0]);
328     case 2: {
329         StringBuilder stringBuilder;
330         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:0]);
331         stringBuilder.append('/');
332         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:1]);
333         return stringBuilder.toString();
334     }
335     default: {
336         StringBuilder stringBuilder;
337         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:0]);
338         for (NSUInteger i = 1 ; i < length ; i++) {
339             stringBuilder.append('/');
340             stringBuilder.appendNumber((unsigned long)[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         size_t currentTextLength = iterator.text().length();
368         if (!currentTextLength) {
369             softLink_DataDetectorsCore_DDScanQueryAddSeparator(scanQuery, DDTextCoalescingTypeHardBreak);
370             if (iteratorCount > maxFragmentWithHardBreak)
371                 break;
372             continue;
373         }
374         // Test for white space nodes, we're coalescing them.
375         const UniChar* currentCharPtr = iterator.text().upconvertedCharacters();
376         
377         bool containsOnlyWhiteSpace = true;
378         bool hasTab = false;
379         bool hasNewline = false;
380         int nbspCount = 0;
381         for (NSUInteger i = 0; i < currentTextLength; i++) {
382             if (!CFCharacterSetIsCharacterMember(whiteSpacesSet, *currentCharPtr)) {
383                 containsOnlyWhiteSpace = false;
384                 break;
385             }
386             
387             if (CFCharacterSetIsCharacterMember(newLinesSet, *currentCharPtr))
388                 hasNewline = true;
389             else if (*currentCharPtr == '\t')
390                 hasTab = true;
391             
392             // Multiple consecutive non breakable spaces are most likely simulated tabs.
393             if (*currentCharPtr == 0xa0) {
394                 if (++nbspCount > 2)
395                     hasTab = true;
396             } else
397                 nbspCount = 0;
398
399             currentCharPtr++;
400         }
401         if (containsOnlyWhiteSpace) {
402             if (hasNewline) {
403                 softLink_DataDetectorsCore_DDScanQueryAddLineBreak(scanQuery);
404                 if (iteratorCount > maxFragmentWithLinebreak)
405                     break;
406             } else {
407                 softLink_DataDetectorsCore_DDScanQueryAddSeparator(scanQuery, hasTab ? DDTextCoalescingTypeTab : DDTextCoalescingTypeSpace);
408                 if (iteratorCount > maxFragmentSpace)
409                     break;
410             }
411             continue;
412         }
413         
414         RetainPtr<CFStringRef> currentText = adoptCF(CFStringCreateWithCharacters(kCFAllocatorDefault, iterator.text().upconvertedCharacters(), iterator.text().length()));
415         softLink_DataDetectorsCore_DDScanQueryAddTextFragment(scanQuery, currentText.get(), CFRangeMake(0, currentTextLength), (void *)iteratorCount, (DDTextFragmentMode)0, DDTextCoalescingTypeNone);
416         fragmentCount++;
417     }
418 }
419
420 static inline CFComparisonResult queryOffsetCompare(DDQueryOffset o1, DDQueryOffset o2)
421 {
422     if (o1.queryIndex < o2.queryIndex)
423         return kCFCompareLessThan;
424     if (o1.queryIndex > o2.queryIndex)
425         return kCFCompareGreaterThan;
426     if (o1.offset < o2.offset)
427         return kCFCompareLessThan;
428     if (o1.offset > o2.offset)
429         return kCFCompareGreaterThan;
430     return kCFCompareEqualTo;
431 }
432
433 NSArray *DataDetection::detectContentInRange(RefPtr<Range>& contextRange, DataDetectorTypes types, NSDictionary *context)
434 {
435     RetainPtr<DDScannerRef> scanner = adoptCF(softLink_DataDetectorsCore_DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
436     RetainPtr<DDScanQueryRef> scanQuery = adoptCF(softLink_DataDetectorsCore_DDScanQueryCreate(NULL));
437     buildQuery(scanQuery.get(), contextRange.get());
438     
439 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
440     if (types & DataDetectorTypeLookupSuggestion)
441         softLink_DataDetectorsCore_DDScannerEnableOptionalSource(scanner.get(), DDScannerSourceSpotlight, true);
442 #endif
443     
444     // FIXME: we should add a timeout to this call to make sure it doesn't take too much time.
445     if (!softLink_DataDetectorsCore_DDScannerScanQuery(scanner.get(), scanQuery.get()))
446         return nil;
447
448     RetainPtr<CFArrayRef> scannerResults = adoptCF(softLink_DataDetectorsCore_DDScannerCopyResultsWithOptions(scanner.get(), get_DataDetectorsCore_DDScannerCopyResultsOptionsForPassiveUse() | DDScannerCopyResultsOptionsCoalesceSignatures));
449     if (!scannerResults)
450         return nil;
451
452     CFIndex resultCount = CFArrayGetCount(scannerResults.get());
453     if (!resultCount)
454         return nil;
455
456     Vector<RetainPtr<DDResultRef>> allResults;
457     Vector<RetainPtr<NSIndexPath>> indexPaths;
458     NSInteger currentTopLevelIndex = 0;
459
460     // Iterate through the scanner results to find signatures and extract all the subresults while
461     // populating the array of index paths to use in the href of the anchors being created.
462     for (id resultObject in (NSArray *)scannerResults.get()) {
463         DDResultRef result = (DDResultRef)resultObject;
464         NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:currentTopLevelIndex];
465         if (CFStringCompare(softLink_DataDetectorsCore_DDResultGetType(result), get_DataDetectorsCore_DDBinderSignatureBlockKey(), 0) == kCFCompareEqualTo) {
466             NSArray *subresults = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
467             
468             for (NSUInteger subResultIndex = 0 ; subResultIndex < [subresults count] ; subResultIndex++) {
469                 indexPaths.append([indexPath indexPathByAddingIndex:subResultIndex]);
470                 allResults.append((DDResultRef)[subresults objectAtIndex:subResultIndex]);
471             }
472         } else {
473             allResults.append(result);
474             indexPaths.append(indexPath);
475         }
476         currentTopLevelIndex++;
477     }
478
479     Vector<Vector<RefPtr<Range>>> allResultRanges;
480     TextIterator iterator(contextRange.get());
481     CFIndex iteratorCount = 0;
482
483     // Iterate through the array of the expanded results to create a vector of Range objects that indicate
484     // where the DOM needs to be modified.
485     // Each result can be contained all in one text node or can span multiple text nodes.
486     for (auto& result : allResults) {
487         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(result.get());
488         CFIndex iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), queryRange.start.queryIndex);
489         for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
490             iterator.advance();
491
492         Vector<RefPtr<Range>> fragmentRanges;
493         RefPtr<Range> currentRange = iterator.range();
494         CFIndex fragmentIndex = queryRange.start.queryIndex;
495         if (fragmentIndex == queryRange.end.queryIndex)
496             fragmentRanges.append(TextIterator::subrange(currentRange.get(), queryRange.start.offset, queryRange.end.offset - queryRange.start.offset));
497         else {
498             if (!queryRange.start.offset)
499                 fragmentRanges.append(currentRange);
500             else
501                 fragmentRanges.append(Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset() + queryRange.start.offset, &currentRange->endContainer(), currentRange->endOffset()));
502         }
503         
504         while (fragmentIndex < queryRange.end.queryIndex) {
505             ++fragmentIndex;
506             iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), fragmentIndex);
507             for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
508                 iterator.advance();
509
510             currentRange = iterator.range();
511             RefPtr<Range> fragmentRange = (fragmentIndex == queryRange.end.queryIndex) ?  Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset(), &currentRange->endContainer(), currentRange->startOffset() + queryRange.end.offset) : currentRange;
512             RefPtr<Range> previousRange = fragmentRanges.last();
513             if (&previousRange->startContainer() == &fragmentRange->startContainer()) {
514                 fragmentRange = Range::create(currentRange->ownerDocument(), &previousRange->startContainer(), previousRange->startOffset(), &fragmentRange->endContainer(), fragmentRange->endOffset());
515                 fragmentRanges.last() = fragmentRange;
516             } else
517                 fragmentRanges.append(fragmentRange);
518         }
519         allResultRanges.append(WTFMove(fragmentRanges));
520     }
521     
522     auto tz = adoptCF(CFTimeZoneCopyDefault());
523     NSDate *referenceDate = [context objectForKey:getkDataDetectorsReferenceDateKey()] ?: [NSDate date];
524     Text* lastTextNodeToUpdate = nullptr;
525     String lastNodeContent;
526     size_t contentOffset = 0;
527     DDQueryOffset lastModifiedQueryOffset = { -1, 0 };
528     
529     // For each result add the link.
530     // Since there could be multiple results in the same text node, the node is only modified when
531     // we are about to process a different text node.
532     resultCount = allResults.size();
533     
534     for (CFIndex resultIndex = 0; resultIndex < resultCount; ++resultIndex) {
535         DDResultRef coreResult = allResults[resultIndex].get();
536         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(coreResult);
537         auto& resultRanges = allResultRanges[resultIndex];
538
539         // Compare the query offsets to make sure we don't go backwards
540         if (queryOffsetCompare(lastModifiedQueryOffset, queryRange.start) >= 0)
541             continue;
542
543         if (resultRanges.isEmpty())
544             continue;
545         
546         // Store the range boundaries as Position, because the DOM could change if we find
547         // old data detector link.
548         Vector<std::pair<Position, Position>> rangeBoundaries;
549         rangeBoundaries.reserveInitialCapacity(resultRanges.size());
550         for (auto& range : resultRanges)
551             rangeBoundaries.uncheckedAppend({ range->startPosition(), range->endPosition() });
552
553         NSString *identifier = dataDetectorStringForPath(indexPaths[resultIndex].get());
554         NSString *correspondingURL = constructURLStringForResult(coreResult, identifier, referenceDate, (NSTimeZone *)tz.get(), types);
555         bool didModifyDOM = false;
556
557         if (!correspondingURL || searchForLinkRemovingExistingDDLinks(resultRanges.first()->startContainer(), resultRanges.last()->endContainer(), didModifyDOM))
558             continue;
559         
560         if (didModifyDOM) {
561             // If the DOM was modified because some old links were removed,
562             // we need to recreate the ranges because they could no longer be valid.
563             ASSERT(resultRanges.size() == rangeBoundaries.size());
564             resultRanges.shrink(0); // Keep capacity as we are going to repopulate the Vector right away with the same number of items.
565             for (auto& rangeBoundary : rangeBoundaries)
566                 resultRanges.uncheckedAppend(Range::create(*rangeBoundary.first.document(), rangeBoundary.first, rangeBoundary.second));
567         }
568         
569         lastModifiedQueryOffset = queryRange.end;
570 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
571         BOOL shouldUseLightLinks = softLink_DataDetectorsCore_DDShouldUseLightLinksForResult(coreResult, [indexPaths[resultIndex] length] > 1);
572 #else
573         BOOL shouldUseLightLinks = NO;
574 #endif
575
576         for (auto& range : resultRanges) {
577             auto* parentNode = range->startContainer().parentNode();
578             if (!parentNode)
579                 continue;
580             if (!is<Text>(range->startContainer()))
581                 continue;
582             auto& currentTextNode = downcast<Text>(range->startContainer());
583             Document& document = currentTextNode.document();
584             String textNodeData;
585             
586             if (lastTextNodeToUpdate != &currentTextNode) {
587                 if (lastTextNodeToUpdate)
588                     lastTextNodeToUpdate->setData(lastNodeContent);
589                 contentOffset = 0;
590                 if (range->startOffset() > 0)
591                     textNodeData = currentTextNode.data().substring(0, range->startOffset());
592             } else
593                 textNodeData = currentTextNode.data().substring(contentOffset, range->startOffset() - contentOffset);
594             
595             if (!textNodeData.isEmpty()) {
596                 parentNode->insertBefore(Text::create(document, textNodeData), &currentTextNode);
597                 contentOffset = range->startOffset();
598             }
599             
600             // Create the actual anchor node and insert it before the current node.
601             textNodeData = currentTextNode.data().substring(range->startOffset(), range->endOffset() - range->startOffset());
602             Ref<Text> newTextNode = Text::create(document, textNodeData);
603             parentNode->insertBefore(newTextNode.copyRef(), &currentTextNode);
604             
605             Ref<HTMLAnchorElement> anchorElement = HTMLAnchorElement::create(document);
606             anchorElement->setHref(correspondingURL);
607             anchorElement->setDir("ltr");
608             if (shouldUseLightLinks) {
609                 document.updateStyleIfNeeded();
610                 auto* renderStyle = parentNode->computedStyle();
611                 if (renderStyle) {
612                     auto textColor = renderStyle->visitedDependentColor(CSSPropertyColor);
613                     if (textColor.isValid()) {
614                         double h = 0;
615                         double s = 0;
616                         double v = 0;
617                         textColor.getHSV(h, s, v);
618
619                         // Set the alpha of the underline to 46% if the text color is white-ish (defined
620                         // as having a saturation of less than 2% and a value/brightness or greater than
621                         // 98%). Otherwise, set the alpha of the underline to 26%.
622                         double overrideAlpha = (s < 0.02 && v > 0.98) ? 0.46 : 0.26;
623                         auto underlineColor = Color(colorWithOverrideAlpha(textColor.rgb(), overrideAlpha));
624
625                         anchorElement->setInlineStyleProperty(CSSPropertyColor, textColor.cssText());
626                         anchorElement->setInlineStyleProperty(CSSPropertyWebkitTextDecorationColor, underlineColor.cssText());
627                     }
628                 }
629             } else if (is<StyledElement>(*parentNode)) {
630                 if (auto* style = downcast<StyledElement>(*parentNode).presentationAttributeStyle()) {
631                     String color = style->getPropertyValue(CSSPropertyColor);
632                     if (!color.isEmpty())
633                         anchorElement->setInlineStyleProperty(CSSPropertyColor, color);
634                 }
635             }
636             anchorElement->appendChild(WTFMove(newTextNode));
637             // Add a special attribute to mark this URLification as the result of data detectors.
638             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectorsAttr, AtomicString("true", AtomicString::ConstructFromLiteral));
639             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_typeAttr, dataDetectorTypeForCategory(softLink_DataDetectorsCore_DDResultGetCategory(coreResult)));
640             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_resultAttr, identifier);
641
642             parentNode->insertBefore(WTFMove(anchorElement), &currentTextNode);
643
644             contentOffset = range->endOffset();
645             
646             lastNodeContent = currentTextNode.data().substring(range->endOffset(), currentTextNode.length() - range->endOffset());
647             lastTextNodeToUpdate = &currentTextNode;
648         }        
649     }
650     if (lastTextNodeToUpdate)
651         lastTextNodeToUpdate->setData(lastNodeContent);
652     
653     return [get_DataDetectorsCore_DDScannerResultClass() resultsFromCoreResults:scannerResults.get()];
654 }
655
656 #else
657 NSArray *DataDetection::detectContentInRange(RefPtr<Range>&, DataDetectorTypes, NSDictionary *)
658 {
659     return nil;
660 }
661 #endif
662
663 const String& DataDetection::dataDetectorURLProtocol()
664 {
665     static NeverDestroyed<String> protocol(MAKE_STATIC_STRING_IMPL("x-apple-data-detectors"));
666     return protocol;
667 }
668
669 bool DataDetection::isDataDetectorURL(const URL& url)
670 {
671     return url.protocolIs(dataDetectorURLProtocol());
672 }
673
674 } // namespace WebCore