[iOS] Remove code only needed when building for iOS 9.x
[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     return [softLink_DataDetectorsCore_DDURLTapAndHoldSchemes() containsObject:(NSString *)downcast<HTMLAnchorElement>(element).href().protocol().toStringWithoutCopying().convertToASCIILowercase()];
161 }
162
163 bool DataDetection::requiresExtendedContext(Element& element)
164 {
165     return equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectors_typeAttr), "calendar-event");
166 }
167
168 String DataDetection::dataDetectorIdentifier(Element& element)
169 {
170     return element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
171 }
172
173 bool DataDetection::shouldCancelDefaultAction(Element& element)
174 {
175     if (!isDataDetectorLink(element))
176         return false;
177     
178     if (softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForURL(downcast<HTMLAnchorElement>(element).href()))
179         return true;
180     
181     const AtomicString& resultAttribute = element.attributeWithoutSynchronization(x_apple_data_detectors_resultAttr);
182     if (resultAttribute.isEmpty())
183         return false;
184     NSArray *results = element.document().frame()->dataDetectionResults();
185     if (!results)
186         return false;
187     Vector<String> resultIndices;
188     resultAttribute.string().split('/', resultIndices);
189     DDResultRef result = [[results objectAtIndex:resultIndices[0].toInt()] coreResult];
190     // Handle the case of a signature block, where we need to check the correct subresult.
191     for (size_t i = 1; i < resultIndices.size(); i++) {
192         results = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
193         result = (DDResultRef)[results objectAtIndex:resultIndices[i].toInt()];
194     }
195     return softLink_DataDetectorsCore_DDShouldImmediatelyShowActionSheetForResult(result);
196 }
197
198 static BOOL resultIsURL(DDResultRef result)
199 {
200     if (!result)
201         return NO;
202     
203     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];
204     return [urlTypes containsObject:(NSString *)softLink_DataDetectorsCore_DDResultGetType(result)];
205 }
206
207 static NSString *constructURLStringForResult(DDResultRef currentResult, NSString *resultIdentifier, NSDate *referenceDate, NSTimeZone *referenceTimeZone, DataDetectorTypes detectionTypes)
208 {
209     if (!softLink_DataDetectorsCore_DDResultHasProperties(currentResult, DDResultPropertyPassiveDisplay))
210         return nil;
211     
212     DDURLifierPhoneNumberDetectionTypes phoneTypes = (detectionTypes & DataDetectorTypePhoneNumber) ? DDURLifierPhoneNumberDetectionRegular : DDURLifierPhoneNumberDetectionNone;
213     DDResultCategory category = softLink_DataDetectorsCore_DDResultGetCategory(currentResult);
214     CFStringRef type = softLink_DataDetectorsCore_DDResultGetType(currentResult);
215     
216     if (((detectionTypes & DataDetectorTypeAddress) && (DDResultCategoryAddress == category))
217         || ((detectionTypes & DataDetectorTypeTrackingNumber) && (CFStringCompare(get_DataDetectorsCore_DDBinderTrackingNumberKey(), type, 0) == kCFCompareEqualTo))
218         || ((detectionTypes & DataDetectorTypeFlightNumber) && (CFStringCompare(get_DataDetectorsCore_DDBinderFlightInformationKey(), type, 0) == kCFCompareEqualTo))
219         || ((detectionTypes & DataDetectorTypeLookupSuggestion) && (CFStringCompare(get_DataDetectorsCore_DDBinderParsecSourceKey(), type, 0) == kCFCompareEqualTo))
220         || ((detectionTypes & DataDetectorTypePhoneNumber) && (DDResultCategoryPhoneNumber == category))
221         || ((detectionTypes & DataDetectorTypeLink) && resultIsURL(currentResult))) {
222         
223         return softLink_DataDetectorsCore_DDURLStringForResult(currentResult, resultIdentifier, phoneTypes, referenceDate, referenceTimeZone);
224     }
225     if ((detectionTypes & DataDetectorTypeCalendarEvent) && (DDResultCategoryCalendarEvent == category)) {
226         if (!softLink_DataDetectorsCore_DDResultIsPastDate(currentResult, (CFDateRef)referenceDate, (CFTimeZoneRef)referenceTimeZone))
227             return softLink_DataDetectorsCore_DDURLStringForResult(currentResult, resultIdentifier, phoneTypes, referenceDate, referenceTimeZone);
228     }
229     return nil;
230 }
231
232 static void removeResultLinksFromAnchor(Element& element)
233 {
234     // Perform a depth-first search for anchor nodes, which have the DDURLScheme attribute set to true,
235     // take their children and insert them before the anchor, and then remove the anchor.
236
237     // Note that this is not using ElementChildIterator because we potentially prepend children as we iterate over them.
238     for (auto* child = ElementTraversal::firstChild(element); child; child = ElementTraversal::nextSibling(*child))
239         removeResultLinksFromAnchor(*child);
240
241     auto* elementParent = element.parentElement();
242     if (!elementParent)
243         return;
244     
245     bool elementIsDDAnchor = is<HTMLAnchorElement>(element) && equalIgnoringASCIICase(element.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true");
246     if (!elementIsDDAnchor)
247         return;
248
249     // Iterate over the children and move them all onto the same level as this anchor. Remove the anchor afterwards.
250     while (auto* child = element.firstChild())
251         elementParent->insertBefore(*child, &element);
252
253     elementParent->removeChild(element);
254 }
255
256 static bool searchForLinkRemovingExistingDDLinks(Node& startNode, Node& endNode, bool& didModifyDOM)
257 {
258     didModifyDOM = false;
259     for (Node* node = &startNode; node; node = NodeTraversal::next(*node)) {
260         if (is<HTMLAnchorElement>(*node)) {
261             auto& anchor = downcast<HTMLAnchorElement>(*node);
262             if (!equalIgnoringASCIICase(anchor.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
263                 return true;
264             removeResultLinksFromAnchor(anchor);
265             didModifyDOM = true;
266         }
267         
268         if (node == &endNode) {
269             // If we found the end node and no link, return false unless an ancestor node is a link.
270             // The only ancestors not tested at this point are in the direct line from self's parent to the top.
271             for (auto& anchor : ancestorsOfType<HTMLAnchorElement>(startNode)) {
272                 if (!equalIgnoringASCIICase(anchor.attributeWithoutSynchronization(x_apple_data_detectorsAttr), "true"))
273                     return true;
274                 removeResultLinksFromAnchor(anchor);
275                 didModifyDOM = true;
276             }
277             return false;
278         }
279     }
280     return false;
281 }
282
283 static NSString *dataDetectorTypeForCategory(DDResultCategory category)
284 {
285     switch (category) {
286     case DDResultCategoryPhoneNumber:
287         return @"telephone";
288     case DDResultCategoryLink:
289         return @"link";
290     case DDResultCategoryAddress:
291         return @"address";
292     case DDResultCategoryCalendarEvent:
293         return @"calendar-event";
294     case DDResultCategoryMisc:
295         return @"misc";
296     default:
297         return @"";
298     }
299 }
300
301 static String dataDetectorStringForPath(NSIndexPath *path)
302 {
303     NSUInteger length = path.length;
304     
305     switch (length) {
306     case 0:
307         return { };
308     case 1:
309         return String::number((unsigned long)[path indexAtPosition:0]);
310     case 2: {
311         StringBuilder stringBuilder;
312         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:0]);
313         stringBuilder.append('/');
314         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:1]);
315         return stringBuilder.toString();
316     }
317     default: {
318         StringBuilder stringBuilder;
319         stringBuilder.appendNumber((unsigned long)[path indexAtPosition:0]);
320         for (NSUInteger i = 1 ; i < length ; i++) {
321             stringBuilder.append('/');
322             stringBuilder.appendNumber((unsigned long)[path indexAtPosition:i]);
323         }
324
325         return stringBuilder.toString();
326     }
327     }
328 }
329
330 static void buildQuery(DDScanQueryRef scanQuery, Range* contextRange)
331 {
332     // Once we're over this number of fragments, stop at the first hard break.
333     const CFIndex maxFragmentWithHardBreak = 1000;
334     // Once we're over this number of fragments, we stop at the line.
335     const CFIndex maxFragmentWithLinebreak = 5000;
336     // Once we're over this number of fragments, we stop at the space.
337     const CFIndex maxFragmentSpace = 10000;
338
339     CFCharacterSetRef whiteSpacesSet = CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline);
340     CFCharacterSetRef newLinesSet = CFCharacterSetGetPredefined(kCFCharacterSetNewline);
341     
342     RefPtr<Range> endRange;
343     CFIndex iteratorCount = 0;
344     CFIndex fragmentCount = 0;
345     
346     // Build the scan query adding separators.
347     // For each fragment the iterator increment is stored as metadata.
348     for (TextIterator iterator(contextRange); !iterator.atEnd(); iterator.advance(), iteratorCount++) {
349         size_t currentTextLength = iterator.text().length();
350         if (!currentTextLength) {
351             softLink_DataDetectorsCore_DDScanQueryAddSeparator(scanQuery, DDTextCoalescingTypeHardBreak);
352             if (iteratorCount > maxFragmentWithHardBreak)
353                 break;
354             continue;
355         }
356         // Test for white space nodes, we're coalescing them.
357         const UniChar* currentCharPtr = iterator.text().upconvertedCharacters();
358         
359         bool containsOnlyWhiteSpace = true;
360         bool hasTab = false;
361         bool hasNewline = false;
362         int nbspCount = 0;
363         for (NSUInteger i = 0; i < currentTextLength; i++) {
364             if (!CFCharacterSetIsCharacterMember(whiteSpacesSet, *currentCharPtr)) {
365                 containsOnlyWhiteSpace = false;
366                 break;
367             }
368             
369             if (CFCharacterSetIsCharacterMember(newLinesSet, *currentCharPtr))
370                 hasNewline = true;
371             else if (*currentCharPtr == '\t')
372                 hasTab = true;
373             
374             // Multiple consecutive non breakable spaces are most likely simulated tabs.
375             if (*currentCharPtr == 0xa0) {
376                 if (++nbspCount > 2)
377                     hasTab = true;
378             } else
379                 nbspCount = 0;
380
381             currentCharPtr++;
382         }
383         if (containsOnlyWhiteSpace) {
384             if (hasNewline) {
385                 softLink_DataDetectorsCore_DDScanQueryAddLineBreak(scanQuery);
386                 if (iteratorCount > maxFragmentWithLinebreak)
387                     break;
388             } else {
389                 softLink_DataDetectorsCore_DDScanQueryAddSeparator(scanQuery, hasTab ? DDTextCoalescingTypeTab : DDTextCoalescingTypeSpace);
390                 if (iteratorCount > maxFragmentSpace)
391                     break;
392             }
393             continue;
394         }
395         
396         RetainPtr<CFStringRef> currentText = adoptCF(CFStringCreateWithCharacters(kCFAllocatorDefault, iterator.text().upconvertedCharacters(), iterator.text().length()));
397         softLink_DataDetectorsCore_DDScanQueryAddTextFragment(scanQuery, currentText.get(), CFRangeMake(0, currentTextLength), (void *)iteratorCount, (DDTextFragmentMode)0, DDTextCoalescingTypeNone);
398         fragmentCount++;
399     }
400 }
401
402 static inline CFComparisonResult queryOffsetCompare(DDQueryOffset o1, DDQueryOffset o2)
403 {
404     if (o1.queryIndex < o2.queryIndex)
405         return kCFCompareLessThan;
406     if (o1.queryIndex > o2.queryIndex)
407         return kCFCompareGreaterThan;
408     if (o1.offset < o2.offset)
409         return kCFCompareLessThan;
410     if (o1.offset > o2.offset)
411         return kCFCompareGreaterThan;
412     return kCFCompareEqualTo;
413 }
414
415 NSArray *DataDetection::detectContentInRange(RefPtr<Range>& contextRange, DataDetectorTypes types, NSDictionary *context)
416 {
417     RetainPtr<DDScannerRef> scanner = adoptCF(softLink_DataDetectorsCore_DDScannerCreate(DDScannerTypeStandard, 0, nullptr));
418     RetainPtr<DDScanQueryRef> scanQuery = adoptCF(softLink_DataDetectorsCore_DDScanQueryCreate(NULL));
419     buildQuery(scanQuery.get(), contextRange.get());
420     
421     if (types & DataDetectorTypeLookupSuggestion)
422         softLink_DataDetectorsCore_DDScannerEnableOptionalSource(scanner.get(), DDScannerSourceSpotlight, true);
423
424     // FIXME: we should add a timeout to this call to make sure it doesn't take too much time.
425     if (!softLink_DataDetectorsCore_DDScannerScanQuery(scanner.get(), scanQuery.get()))
426         return nil;
427
428     RetainPtr<CFArrayRef> scannerResults = adoptCF(softLink_DataDetectorsCore_DDScannerCopyResultsWithOptions(scanner.get(), get_DataDetectorsCore_DDScannerCopyResultsOptionsForPassiveUse() | DDScannerCopyResultsOptionsCoalesceSignatures));
429     if (!scannerResults)
430         return nil;
431
432     CFIndex resultCount = CFArrayGetCount(scannerResults.get());
433     if (!resultCount)
434         return nil;
435
436     Vector<RetainPtr<DDResultRef>> allResults;
437     Vector<RetainPtr<NSIndexPath>> indexPaths;
438     NSInteger currentTopLevelIndex = 0;
439
440     // Iterate through the scanner results to find signatures and extract all the subresults while
441     // populating the array of index paths to use in the href of the anchors being created.
442     for (id resultObject in (NSArray *)scannerResults.get()) {
443         DDResultRef result = (DDResultRef)resultObject;
444         NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:currentTopLevelIndex];
445         if (CFStringCompare(softLink_DataDetectorsCore_DDResultGetType(result), get_DataDetectorsCore_DDBinderSignatureBlockKey(), 0) == kCFCompareEqualTo) {
446             NSArray *subresults = (NSArray *)softLink_DataDetectorsCore_DDResultGetSubResults(result);
447             
448             for (NSUInteger subResultIndex = 0 ; subResultIndex < [subresults count] ; subResultIndex++) {
449                 indexPaths.append([indexPath indexPathByAddingIndex:subResultIndex]);
450                 allResults.append((DDResultRef)[subresults objectAtIndex:subResultIndex]);
451             }
452         } else {
453             allResults.append(result);
454             indexPaths.append(indexPath);
455         }
456         currentTopLevelIndex++;
457     }
458
459     Vector<Vector<RefPtr<Range>>> allResultRanges;
460     TextIterator iterator(contextRange.get());
461     CFIndex iteratorCount = 0;
462
463     // Iterate through the array of the expanded results to create a vector of Range objects that indicate
464     // where the DOM needs to be modified.
465     // Each result can be contained all in one text node or can span multiple text nodes.
466     for (auto& result : allResults) {
467         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(result.get());
468         CFIndex iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), queryRange.start.queryIndex);
469         for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
470             iterator.advance();
471
472         Vector<RefPtr<Range>> fragmentRanges;
473         RefPtr<Range> currentRange = iterator.range();
474         CFIndex fragmentIndex = queryRange.start.queryIndex;
475         if (fragmentIndex == queryRange.end.queryIndex)
476             fragmentRanges.append(TextIterator::subrange(*currentRange, queryRange.start.offset, queryRange.end.offset - queryRange.start.offset));
477         else {
478             if (!queryRange.start.offset)
479                 fragmentRanges.append(currentRange);
480             else
481                 fragmentRanges.append(Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset() + queryRange.start.offset, &currentRange->endContainer(), currentRange->endOffset()));
482         }
483         
484         while (fragmentIndex < queryRange.end.queryIndex) {
485             ++fragmentIndex;
486             iteratorTargetAdvanceCount = (CFIndex)softLink_DataDetectorsCore_DDScanQueryGetFragmentMetaData(scanQuery.get(), fragmentIndex);
487             for (; iteratorCount < iteratorTargetAdvanceCount; ++iteratorCount)
488                 iterator.advance();
489
490             currentRange = iterator.range();
491             RefPtr<Range> fragmentRange = (fragmentIndex == queryRange.end.queryIndex) ?  Range::create(currentRange->ownerDocument(), &currentRange->startContainer(), currentRange->startOffset(), &currentRange->endContainer(), currentRange->startOffset() + queryRange.end.offset) : currentRange;
492             RefPtr<Range> previousRange = fragmentRanges.last();
493             if (&previousRange->startContainer() == &fragmentRange->startContainer()) {
494                 fragmentRange = Range::create(currentRange->ownerDocument(), &previousRange->startContainer(), previousRange->startOffset(), &fragmentRange->endContainer(), fragmentRange->endOffset());
495                 fragmentRanges.last() = fragmentRange;
496             } else
497                 fragmentRanges.append(fragmentRange);
498         }
499         allResultRanges.append(WTFMove(fragmentRanges));
500     }
501     
502     auto tz = adoptCF(CFTimeZoneCopyDefault());
503     NSDate *referenceDate = [context objectForKey:getkDataDetectorsReferenceDateKey()] ?: [NSDate date];
504     Text* lastTextNodeToUpdate = nullptr;
505     String lastNodeContent;
506     size_t contentOffset = 0;
507     DDQueryOffset lastModifiedQueryOffset = { -1, 0 };
508     
509     // For each result add the link.
510     // Since there could be multiple results in the same text node, the node is only modified when
511     // we are about to process a different text node.
512     resultCount = allResults.size();
513     
514     for (CFIndex resultIndex = 0; resultIndex < resultCount; ++resultIndex) {
515         DDResultRef coreResult = allResults[resultIndex].get();
516         DDQueryRange queryRange = softLink_DataDetectorsCore_DDResultGetQueryRangeForURLification(coreResult);
517         auto& resultRanges = allResultRanges[resultIndex];
518
519         // Compare the query offsets to make sure we don't go backwards
520         if (queryOffsetCompare(lastModifiedQueryOffset, queryRange.start) >= 0)
521             continue;
522
523         if (resultRanges.isEmpty())
524             continue;
525         
526         // Store the range boundaries as Position, because the DOM could change if we find
527         // old data detector link.
528         Vector<std::pair<Position, Position>> rangeBoundaries;
529         rangeBoundaries.reserveInitialCapacity(resultRanges.size());
530         for (auto& range : resultRanges)
531             rangeBoundaries.uncheckedAppend({ range->startPosition(), range->endPosition() });
532
533         NSString *identifier = dataDetectorStringForPath(indexPaths[resultIndex].get());
534         NSString *correspondingURL = constructURLStringForResult(coreResult, identifier, referenceDate, (NSTimeZone *)tz.get(), types);
535         bool didModifyDOM = false;
536
537         if (!correspondingURL || searchForLinkRemovingExistingDDLinks(resultRanges.first()->startContainer(), resultRanges.last()->endContainer(), didModifyDOM))
538             continue;
539         
540         if (didModifyDOM) {
541             // If the DOM was modified because some old links were removed,
542             // we need to recreate the ranges because they could no longer be valid.
543             ASSERT(resultRanges.size() == rangeBoundaries.size());
544             resultRanges.shrink(0); // Keep capacity as we are going to repopulate the Vector right away with the same number of items.
545             for (auto& rangeBoundary : rangeBoundaries)
546                 resultRanges.uncheckedAppend(Range::create(*rangeBoundary.first.document(), rangeBoundary.first, rangeBoundary.second));
547         }
548         
549         lastModifiedQueryOffset = queryRange.end;
550         BOOL shouldUseLightLinks = softLink_DataDetectorsCore_DDShouldUseLightLinksForResult(coreResult, [indexPaths[resultIndex] length] > 1);
551
552         for (auto& range : resultRanges) {
553             auto* parentNode = range->startContainer().parentNode();
554             if (!parentNode)
555                 continue;
556             if (!is<Text>(range->startContainer()))
557                 continue;
558             auto& currentTextNode = downcast<Text>(range->startContainer());
559             Document& document = currentTextNode.document();
560             String textNodeData;
561             
562             if (lastTextNodeToUpdate != &currentTextNode) {
563                 if (lastTextNodeToUpdate)
564                     lastTextNodeToUpdate->setData(lastNodeContent);
565                 contentOffset = 0;
566                 if (range->startOffset() > 0)
567                     textNodeData = currentTextNode.data().substring(0, range->startOffset());
568             } else
569                 textNodeData = currentTextNode.data().substring(contentOffset, range->startOffset() - contentOffset);
570             
571             if (!textNodeData.isEmpty()) {
572                 parentNode->insertBefore(Text::create(document, textNodeData), &currentTextNode);
573                 contentOffset = range->startOffset();
574             }
575             
576             // Create the actual anchor node and insert it before the current node.
577             textNodeData = currentTextNode.data().substring(range->startOffset(), range->endOffset() - range->startOffset());
578             Ref<Text> newTextNode = Text::create(document, textNodeData);
579             parentNode->insertBefore(newTextNode.copyRef(), &currentTextNode);
580             
581             Ref<HTMLAnchorElement> anchorElement = HTMLAnchorElement::create(document);
582             anchorElement->setHref(correspondingURL);
583             anchorElement->setDir("ltr");
584             if (shouldUseLightLinks) {
585                 document.updateStyleIfNeeded();
586                 auto* renderStyle = parentNode->computedStyle();
587                 if (renderStyle) {
588                     auto textColor = renderStyle->visitedDependentColor(CSSPropertyColor);
589                     if (textColor.isValid()) {
590                         double h = 0;
591                         double s = 0;
592                         double v = 0;
593                         textColor.getHSV(h, s, v);
594
595                         // Set the alpha of the underline to 46% if the text color is white-ish (defined
596                         // as having a saturation of less than 2% and a value/brightness or greater than
597                         // 98%). Otherwise, set the alpha of the underline to 26%.
598                         double overrideAlpha = (s < 0.02 && v > 0.98) ? 0.46 : 0.26;
599                         auto underlineColor = Color(colorWithOverrideAlpha(textColor.rgb(), overrideAlpha));
600
601                         anchorElement->setInlineStyleProperty(CSSPropertyColor, textColor.cssText());
602                         anchorElement->setInlineStyleProperty(CSSPropertyWebkitTextDecorationColor, underlineColor.cssText());
603                     }
604                 }
605             } else if (is<StyledElement>(*parentNode)) {
606                 if (auto* style = downcast<StyledElement>(*parentNode).presentationAttributeStyle()) {
607                     String color = style->getPropertyValue(CSSPropertyColor);
608                     if (!color.isEmpty())
609                         anchorElement->setInlineStyleProperty(CSSPropertyColor, color);
610                 }
611             }
612             anchorElement->appendChild(WTFMove(newTextNode));
613             // Add a special attribute to mark this URLification as the result of data detectors.
614             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectorsAttr, AtomicString("true", AtomicString::ConstructFromLiteral));
615             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_typeAttr, dataDetectorTypeForCategory(softLink_DataDetectorsCore_DDResultGetCategory(coreResult)));
616             anchorElement->setAttributeWithoutSynchronization(x_apple_data_detectors_resultAttr, identifier);
617
618             parentNode->insertBefore(WTFMove(anchorElement), &currentTextNode);
619
620             contentOffset = range->endOffset();
621             
622             lastNodeContent = currentTextNode.data().substring(range->endOffset(), currentTextNode.length() - range->endOffset());
623             lastTextNodeToUpdate = &currentTextNode;
624         }        
625     }
626     if (lastTextNodeToUpdate)
627         lastTextNodeToUpdate->setData(lastNodeContent);
628     
629     return [get_DataDetectorsCore_DDScannerResultClass() resultsFromCoreResults:scannerResults.get()];
630 }
631
632 #else
633 NSArray *DataDetection::detectContentInRange(RefPtr<Range>&, DataDetectorTypes, NSDictionary *)
634 {
635     return nil;
636 }
637 #endif
638
639 const String& DataDetection::dataDetectorURLProtocol()
640 {
641     static NeverDestroyed<String> protocol(MAKE_STATIC_STRING_IMPL("x-apple-data-detectors"));
642     return protocol;
643 }
644
645 bool DataDetection::isDataDetectorURL(const URL& url)
646 {
647     return url.protocolIs(dataDetectorURLProtocol());
648 }
649
650 } // namespace WebCore
651
652 #endif
653