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