[iOS] Implement safe browsing in WebKit
[WebKit-https.git] / Source / WebKit / UIProcess / Cocoa / WKSafeBrowsingWarning.mm
1 /*
2  * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WKSafeBrowsingWarning.h"
28
29 #import "PageClient.h"
30 #import "SafeBrowsingResult.h"
31 #import <WebCore/LocalizedStrings.h>
32 #import <WebCore/URL.h>
33 #import <wtf/BlockPtr.h>
34 #import <wtf/Language.h>
35
36 constexpr CGFloat exclamationPointSize = 30;
37 constexpr CGFloat titleSize = 26;
38 constexpr CGFloat boxCornerRadius = 6;
39 #if HAVE(SAFE_BROWSING)
40 constexpr CGFloat marginSize = 20;
41 constexpr CGFloat maxWidth = 675;
42 #endif
43
44 #if PLATFORM(MAC)
45 constexpr CGFloat textSize = 14;
46 using ColorType = NSColor;
47 using FontType = NSFont;
48 using TextViewType = NSTextView;
49 using ButtonType = NSButton;
50 using AlignmentType = NSLayoutAttribute;
51 using ViewType = NSView;
52 using SizeType = NSSize;
53 #else
54 constexpr CGFloat textSize = 20;
55 using ColorType = UIColor;
56 using FontType = UIFont;
57 using TextViewType = UITextView;
58 using ButtonType = UIButton;
59 using AlignmentType = UIStackViewAlignment;
60 using ViewType = UIView;
61 using SizeType = CGSize;
62 #endif
63
64 enum class WarningItem : uint8_t {
65     Background,
66     BoxBackground,
67     ExclamationPoint,
68     TitleText,
69     MessageText,
70     ShowDetailsButton,
71     GoBackButton
72 };
73
74 static NSURL *confirmMalwareSentinel()
75 {
76     return [NSURL URLWithString:@"WKConfirmMalwareSentinel"];
77 }
78
79 static NSURL *visitUnsafeWebsiteSentinel()
80 {
81     return [NSURL URLWithString:@"WKVisitUnsafeWebsiteSentinel"];
82 }
83
84 static ColorType *colorForItem(WarningItem item, ViewType *warning)
85 {
86     ASSERT([warning isKindOfClass:[WKSafeBrowsingWarning class]]);
87 #if PLATFORM(MAC)
88
89     auto colorNamed = [] (NSString *name) -> ColorType* {
90 #if HAVE(SAFE_BROWSING)
91         return [NSColor colorNamed:name bundle:[NSBundle bundleWithIdentifier:@"com.apple.WebKit"]];
92 #else
93         ASSERT_NOT_REACHED();
94         return nil;
95 #endif
96     };
97
98     switch (item) {
99     case WarningItem::Background:
100         return colorNamed(@"WKSafeBrowsingWarningBackground");
101     case WarningItem::BoxBackground:
102         return [NSColor windowBackgroundColor];
103     case WarningItem::TitleText:
104     case WarningItem::ExclamationPoint:
105         return colorNamed(@"WKSafeBrowsingWarningTitle");
106     case WarningItem::MessageText:
107         return colorNamed(@"WKSafeBrowsingWarningText");
108     case WarningItem::ShowDetailsButton:
109     case WarningItem::GoBackButton:
110         ASSERT_NOT_REACHED();
111         return nil;
112     }
113 #else
114     UIColor *red = [UIColor colorWithRed:0.998 green:0.239 blue:0.233 alpha:1.0];
115     UIColor *white = [UIColor whiteColor];
116     bool narrow = warning.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact;
117
118     switch (item) {
119     case WarningItem::Background:
120         return red;
121     case WarningItem::BoxBackground:
122         return narrow ? red : white;
123     case WarningItem::TitleText:
124     case WarningItem::ExclamationPoint:
125         return narrow ? white : red;
126     case WarningItem::MessageText:
127     case WarningItem::ShowDetailsButton:
128         return narrow ? white : [UIColor darkTextColor];
129     case WarningItem::GoBackButton:
130         return narrow ? white : warning.tintColor;
131     }
132 #endif
133     ASSERT_NOT_REACHED();
134     return nil;
135 }
136
137 static void replace(NSMutableAttributedString *string, NSString *toReplace, NSString *replaceWith)
138 {
139     [string replaceCharactersInRange:[string.string rangeOfString:toReplace] withString:replaceWith];
140 }
141
142 static void addLinkAndReplace(NSMutableAttributedString *string, NSString *toReplace, NSString *replaceWith, NSURL *linkTarget)
143 {
144     NSMutableAttributedString *stringWithLink = [[[NSMutableAttributedString alloc] initWithString:replaceWith] autorelease];
145     [stringWithLink addAttributes:@{
146         NSLinkAttributeName: linkTarget,
147         NSUnderlineStyleAttributeName: @1
148     } range:NSMakeRange(0, replaceWith.length)];
149     [string replaceCharactersInRange:[string.string rangeOfString:toReplace] withAttributedString:stringWithLink];
150 }
151
152 @interface WKSafeBrowsingExclamationPoint : ViewType
153 @end
154
155 @implementation WKSafeBrowsingExclamationPoint
156
157 - (void)drawRect:(RectType)rect
158 {
159     constexpr CGFloat centerX = exclamationPointSize / 2;
160     constexpr CGFloat pointCenterY = exclamationPointSize * 7 / 30;
161     constexpr CGFloat pointRadius = 2.25 * exclamationPointSize / 30;
162     constexpr CGFloat lineBottomCenterY = exclamationPointSize * 13 / 30;
163     constexpr CGFloat lineTopCenterY = exclamationPointSize * 23 / 30;
164     constexpr CGFloat lineRadius = 1.75 * exclamationPointSize / 30;
165     ViewType *warning = self.superview.superview;
166 #if PLATFORM(MAC)
167     [colorForItem(WarningItem::ExclamationPoint, warning) set];
168     NSBezierPath *exclamationPoint = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(0, 0, exclamationPointSize, exclamationPointSize)];
169     [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, lineBottomCenterY } radius:lineRadius startAngle:0 endAngle:180 clockwise:YES];
170     [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, lineTopCenterY } radius:lineRadius startAngle:180 endAngle:360 clockwise:YES];
171     [exclamationPoint lineToPoint: { centerX + lineRadius, lineBottomCenterY }];
172     [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, pointCenterY } radius:pointRadius startAngle:0 endAngle:180 clockwise:YES];
173     [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, pointCenterY } radius:pointRadius startAngle:180 endAngle:360 clockwise:YES];
174 #else
175     auto flip = [] (auto y) {
176         return exclamationPointSize - y;
177     };
178     [colorForItem(WarningItem::BoxBackground, warning) set];
179     auto square = CGRectMake(0, 0, exclamationPointSize, exclamationPointSize);
180     [[UIBezierPath bezierPathWithRect:square] fill];
181     
182     [colorForItem(WarningItem::ExclamationPoint, warning) set];
183     UIBezierPath *exclamationPoint = [UIBezierPath bezierPathWithOvalInRect:square];
184     [exclamationPoint addArcWithCenter: { centerX, flip(lineTopCenterY) } radius:lineRadius startAngle:2 * piDouble endAngle:piDouble clockwise:NO];
185     [exclamationPoint addArcWithCenter: { centerX, flip(lineBottomCenterY) } radius:lineRadius startAngle:piDouble endAngle:0 clockwise:NO];
186     [exclamationPoint addArcWithCenter: { centerX, flip(pointCenterY) } radius:pointRadius startAngle:0 endAngle:piDouble clockwise:NO];
187     [exclamationPoint addArcWithCenter: { centerX, flip(pointCenterY) } radius:pointRadius startAngle:piDouble endAngle:piDouble * 2 clockwise:NO];
188     [exclamationPoint addLineToPoint: { centerX + lineRadius, flip(lineBottomCenterY) }];
189     [exclamationPoint addLineToPoint: { centerX + lineRadius, flip(lineTopCenterY) }];
190 #endif
191     [exclamationPoint fill];
192 }
193
194 - (NSSize)intrinsicContentSize
195 {
196     return { exclamationPointSize, exclamationPointSize };
197 }
198
199 @end
200
201 static NSURL *reportAnErrorURL(const WebKit::SafeBrowsingResult& result)
202 {
203     return WebCore::URL({ }, makeString(result.reportAnErrorURLBase(), "&url=", encodeWithURLEscapeSequences(result.url()), "&hl=", defaultLanguage()));
204 }
205
206 static NSURL *malwareDetailsURL(const WebKit::SafeBrowsingResult& result)
207 {
208     return WebCore::URL({ }, makeString(result.malwareDetailsURLBase(), "&site=", result.url().host(), "&hl=", defaultLanguage()));
209 }
210
211 static NSString *titleText(const WebKit::SafeBrowsingResult& result)
212 {
213     if (result.isPhishing())
214         return WEB_UI_NSSTRING(@"Deceptive Website Warning", "Phishing warning title");
215     if (result.isMalware())
216         return WEB_UI_NSSTRING(@"Malware Website Warning", "Malware warning title");
217     ASSERT(result.isUnwantedSoftware());
218     return WEB_UI_NSSTRING(@"Website With Harmful Software Warning", "Unwanted software warning title");
219 }
220
221 static NSString *warningText(const WebKit::SafeBrowsingResult& result)
222 {
223     if (result.isPhishing())
224         return WEB_UI_NSSTRING(@"This website may try to trick you into doing something dangerous, like installing software or disclosing personal or financial information, like passwords, phone numbers, or credit cards.", "Phishing warning");
225     if (result.isMalware())
226         return WEB_UI_NSSTRING(@"This website may attempt to install dangerous software, which could harm your computer or steal your personal or financial information, like passwords, photos, or credit cards.", "Malware warning");
227     ASSERT(result.isUnwantedSoftware());
228     return WEB_UI_NSSTRING(@"This website may try to trick you into installing software that harms your browsing experience, like changing your settings without your permission or showing you unwanted ads. Once installed, it may be difficult to remove.", "Unwanted software warning");
229 }
230
231 static NSMutableAttributedString *detailsText(const WebKit::SafeBrowsingResult& result)
232 {
233     if (result.isPhishing()) {
234         NSString *phishingDescription = WEB_UI_NSSTRING(@"Warnings are shown for websites that have been reported as deceptive. Deceptive websites try to trick you into believing they are legitimate websites you trust.", "Phishing warning description");
235         NSString *learnMore = WEB_UI_NSSTRING(@"Learn more…", "Action from safe browsing warning");
236         NSString *phishingActions = WEB_UI_NSSTRING(@"If you believe this website is safe, you can %report-an-error%. Or, if you understand the risks involved, you can %bypass-link%.", "Phishing warning description");
237         NSString *reportAnError = WEB_UI_NSSTRING(@"report an error", "Action from safe browsing warning");
238         NSString *visitUnsafeWebsite = WEB_UI_NSSTRING(@"visit this unsafe website", "Action from safe browsing warning");
239         
240         NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@ %@\n\n%@", phishingDescription, learnMore, phishingActions]] autorelease];
241         addLinkAndReplace(attributedString, learnMore, learnMore, result.learnMoreURL());
242         addLinkAndReplace(attributedString, @"%report-an-error%", reportAnError, reportAnErrorURL(result));
243         addLinkAndReplace(attributedString, @"%bypass-link%", visitUnsafeWebsite, visitUnsafeWebsiteSentinel());
244         return attributedString;
245     }
246     
247     auto malwareOrUnwantedSoftwareDetails = [] (const WebKit::SafeBrowsingResult& result, NSString *description, NSString *statusStringToReplace, bool confirmMalware) {
248         NSMutableAttributedString *malwareDescription = [[[NSMutableAttributedString alloc] initWithString:description] autorelease];
249         replace(malwareDescription, @"%safeBrowsingProvider%", result.localizedProviderName());
250         NSMutableAttributedString *statusLink = [[[NSMutableAttributedString alloc] initWithString:WEB_UI_NSSTRING(@"the status of “%site%”", "Part of malware description")] autorelease];
251         replace(statusLink, @"%site%", result.url().host().toString());
252         addLinkAndReplace(malwareDescription, statusStringToReplace, statusLink.string, malwareDetailsURL(result));
253         
254         NSMutableAttributedString *ifYouUnderstand = [[[NSMutableAttributedString alloc] initWithString:WEB_UI_NSSTRING(@"If you understand the risks involved, you can %visit-this-unsafe-site-link%.", "Action from safe browsing warning")] autorelease];
255         addLinkAndReplace(ifYouUnderstand, @"%visit-this-unsafe-site-link%", WEB_UI_NSSTRING(@"visit this unsafe website", "Action from safe browsing warning"), confirmMalware ? confirmMalwareSentinel() : visitUnsafeWebsiteSentinel());
256         
257         [malwareDescription appendAttributedString:[[[NSMutableAttributedString alloc] initWithString:@"\n\n"] autorelease]];
258         [malwareDescription appendAttributedString:ifYouUnderstand];
259         return malwareDescription;
260     };
261     
262     if (result.isMalware())
263         return malwareOrUnwantedSoftwareDetails(result, WEB_UI_NSSTRING(@"Warnings are shown for websites where malicious software has been detected. You can check the %status-link% on the %safeBrowsingProvider% diagnostic page.", "Malware warning description"), @"%status-link%", true);
264     ASSERT(result.isUnwantedSoftware());
265     return malwareOrUnwantedSoftwareDetails(result, WEB_UI_NSSTRING(@"Warnings are shown for websites where harmful software has been detected. You can check %the-status-of-site% on the %safeBrowsingProvider% diagnostic page.", "Unwanted software warning description"), @"%the-status-of-site%", false);
266 }
267
268 static ButtonType *makeButton(WarningItem item, WKSafeBrowsingWarning *warning, SEL action)
269 {
270     NSString *title = nil;
271     if (item == WarningItem::ShowDetailsButton)
272         title = WEB_UI_NSSTRING(@"Show details", "Action from safe browsing warning");
273     else
274         title = WEB_UI_NSSTRING(@"Go back", "Action from safe browsing warning");
275     title = [title capitalizedString];
276 #if PLATFORM(MAC)
277     return [NSButton buttonWithTitle:title target:warning action:action];
278 #else
279     UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
280     NSAttributedString *attributedTitle = [[[NSAttributedString alloc] initWithString:title attributes:@{
281         NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle),
282         NSUnderlineColorAttributeName:[UIColor whiteColor],
283         NSForegroundColorAttributeName:colorForItem(item, warning),
284         NSFontAttributeName:[FontType systemFontOfSize:textSize]
285     }] autorelease];
286     [button setAttributedTitle:attributedTitle forState:UIControlStateNormal];
287     [button addTarget:warning action:action forControlEvents:UIControlEventTouchUpInside];
288     return button;
289 #endif
290 }
291
292 static ViewType *makeTitleLabel(NSString *title, ViewType *warning)
293 {
294     auto attributedString = [[[NSAttributedString alloc] initWithString:title attributes:@{
295         NSFontAttributeName:[FontType boldSystemFontOfSize:titleSize],
296         NSForegroundColorAttributeName:colorForItem(WarningItem::TitleText, warning)
297     }] autorelease];
298 #if PLATFORM(MAC)
299     return [NSTextField labelWithAttributedString:attributedString];
300 #else
301     auto label = [[UILabel new] autorelease];
302     label.attributedText = attributedString;
303     label.lineBreakMode = NSLineBreakByWordWrapping;
304     label.numberOfLines = 0;
305     return label;
306 #endif
307 }
308
309 static void setBackground(ViewType *view, ColorType *color)
310 {
311 #if PLATFORM(MAC)
312     view.wantsLayer = YES;
313     view.layer.backgroundColor = color.CGColor;
314 #else
315     view.backgroundColor = color;
316 #endif
317 }
318
319 @interface WKSafeBrowsingTextView : TextViewType {
320 @package
321     WeakObjCPtr<WKSafeBrowsingWarning> _warning;
322 }
323 - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString forWarning:(WKSafeBrowsingWarning *)warning;
324 @end
325
326 @implementation WKSafeBrowsingWarning
327
328 - (instancetype)initWithFrame:(RectType)frame safeBrowsingResult:(const WebKit::SafeBrowsingResult&)result completionHandler:(CompletionHandler<void(Variant<WebKit::ContinueUnsafeLoad, WebCore::URL>&&)>&&)completionHandler
329 {
330     if (!(self = [super initWithFrame:frame])) {
331         completionHandler(WebKit::ContinueUnsafeLoad::Yes);
332         return nil;
333     }
334     _completionHandler = WTFMove(completionHandler);
335     _result = makeRef(result);
336     setBackground(self, colorForItem(WarningItem::Background, self));
337 #if PLATFORM(MAC)
338     [self addContent];
339 #endif
340     return self;
341 }
342
343 - (void)addContent
344 {
345     auto exclamationPoint = [[WKSafeBrowsingExclamationPoint new] autorelease];
346     auto title = makeTitleLabel(titleText(*_result), self);
347     auto warning = [[[WKSafeBrowsingTextView alloc] initWithAttributedString:[[[NSAttributedString alloc] initWithString:warningText(*_result) attributes:@{ NSFontAttributeName:[FontType systemFontOfSize:textSize] }] autorelease] forWarning:self] autorelease];
348     auto showDetails = makeButton(WarningItem::ShowDetailsButton, self, @selector(showDetailsClicked));
349     auto goBack = makeButton(WarningItem::GoBackButton, self, @selector(goBackClicked));
350     auto box = [[ViewType new] autorelease];
351     setBackground(box, colorForItem(WarningItem::BoxBackground, self));
352     box.layer.cornerRadius = boxCornerRadius;
353     _textViews = adoptNS([NSMutableArray new]);
354     [_textViews addObject:warning];
355
356     for (ViewType *view in @[exclamationPoint, title, warning, goBack, showDetails]) {
357         view.translatesAutoresizingMaskIntoConstraints = NO;
358         [box addSubview:view];
359     }
360     box.translatesAutoresizingMaskIntoConstraints = NO;
361     [self addSubview:box];
362
363 #if HAVE(SAFE_BROWSING)
364     [NSLayoutConstraint activateConstraints:@[
365         [[self.topAnchor anchorWithOffsetToAnchor:box.topAnchor] constraintEqualToAnchor:[box.bottomAnchor anchorWithOffsetToAnchor:self.bottomAnchor] multiplier:0.5],
366         [[self.leftAnchor anchorWithOffsetToAnchor:box.leftAnchor] constraintEqualToAnchor:[box.rightAnchor anchorWithOffsetToAnchor:self.rightAnchor]],
367
368         [box.widthAnchor constraintLessThanOrEqualToConstant:maxWidth],
369         [box.widthAnchor constraintLessThanOrEqualToAnchor:self.widthAnchor],
370
371         [[box.leadingAnchor anchorWithOffsetToAnchor:exclamationPoint.leadingAnchor] constraintEqualToConstant:marginSize],
372         [[box.leadingAnchor anchorWithOffsetToAnchor:title.leadingAnchor] constraintEqualToConstant:marginSize * 1.5 + exclamationPointSize],
373         [[box.leadingAnchor anchorWithOffsetToAnchor:warning.leadingAnchor] constraintEqualToConstant:marginSize],
374
375         [[title.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor] constraintGreaterThanOrEqualToConstant:marginSize],
376         [[warning.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor] constraintGreaterThanOrEqualToConstant:marginSize],
377         [[goBack.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor] constraintEqualToConstant:marginSize],
378
379         [[title.topAnchor anchorWithOffsetToAnchor:exclamationPoint.topAnchor] constraintEqualToAnchor:[exclamationPoint.bottomAnchor anchorWithOffsetToAnchor:title.bottomAnchor]],
380
381         [goBack.topAnchor constraintEqualToAnchor:showDetails.topAnchor],
382         [[showDetails.trailingAnchor anchorWithOffsetToAnchor:goBack.leadingAnchor] constraintEqualToConstant:marginSize],
383
384         [[box.topAnchor anchorWithOffsetToAnchor:title.topAnchor] constraintEqualToConstant:marginSize],
385         [[title.bottomAnchor anchorWithOffsetToAnchor:warning.topAnchor] constraintEqualToConstant:marginSize],
386         [[warning.bottomAnchor anchorWithOffsetToAnchor:goBack.topAnchor] constraintEqualToConstant:marginSize],
387         [[goBack.bottomAnchor anchorWithOffsetToAnchor:box.bottomAnchor] constraintEqualToConstant:marginSize]
388     ]];
389 #endif
390 }
391
392 - (void)showDetailsClicked
393 {
394     ViewType *box = self.subviews.lastObject;
395     ButtonType *showDetails = box.subviews.lastObject;
396     [showDetails removeFromSuperview];
397
398     NSMutableAttributedString *text = detailsText(*_result);
399     [text addAttributes:@{ NSFontAttributeName:[FontType systemFontOfSize:textSize] } range:NSMakeRange(0, text.length)];
400     WKSafeBrowsingTextView *details = [[[WKSafeBrowsingTextView alloc] initWithAttributedString:text forWarning:self] autorelease];
401     [_textViews addObject:details];
402     ViewType *bottom = [[ViewType new] autorelease];
403     setBackground(bottom, colorForItem(WarningItem::BoxBackground, self));
404     bottom.layer.cornerRadius = boxCornerRadius;
405
406 #if HAVE(SAFE_BROWSING)
407     constexpr auto maxY = kCALayerMinXMaxYCorner | kCALayerMaxXMaxYCorner;
408     constexpr auto minY = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
409 #if PLATFORM(MAC)
410     box.layer.maskedCorners = maxY;
411     bottom.layer.maskedCorners = minY;
412 #else
413     box.layer.maskedCorners = minY;
414     bottom.layer.maskedCorners = maxY;
415 #endif
416 #endif
417
418     ViewType *line = [[ViewType new] autorelease];
419     setBackground(line, [ColorType lightGrayColor]);
420     for (ViewType *view in @[details, bottom, line])
421         view.translatesAutoresizingMaskIntoConstraints = NO;
422
423     [self addSubview:bottom];
424     [bottom addSubview:line];
425     [bottom addSubview:details];
426 #if HAVE(SAFE_BROWSING)
427     [NSLayoutConstraint activateConstraints:@[
428         [box.widthAnchor constraintEqualToAnchor:bottom.widthAnchor],
429         [box.bottomAnchor constraintEqualToAnchor:bottom.topAnchor],
430         [box.leadingAnchor constraintEqualToAnchor:bottom.leadingAnchor],
431         [line.widthAnchor constraintEqualToAnchor:bottom.widthAnchor],
432         [line.leadingAnchor constraintEqualToAnchor:bottom.leadingAnchor],
433         [line.topAnchor constraintEqualToAnchor:bottom.topAnchor],
434         [line.heightAnchor constraintEqualToConstant:1],
435         [[bottom.topAnchor anchorWithOffsetToAnchor:details.topAnchor] constraintEqualToConstant:marginSize],
436         [[details.bottomAnchor anchorWithOffsetToAnchor:bottom.bottomAnchor] constraintEqualToConstant:marginSize],
437         [[bottom.leadingAnchor anchorWithOffsetToAnchor:details.leadingAnchor] constraintEqualToConstant:marginSize],
438         [[details.trailingAnchor anchorWithOffsetToAnchor:bottom.trailingAnchor] constraintEqualToConstant:marginSize],
439     ]];
440 #endif
441     [self layoutText];
442 #if !PLATFORM(MAC)
443     [self layoutIfNeeded];
444     CGFloat height = 0;
445     for (ViewType *subview in self.subviews)
446         height += subview.frame.size.height;
447     [self setContentSize: { self.frame.size.width, self.frame.size.height / 2 + height }];
448 #endif
449 }
450
451 - (void)layoutText
452 {
453     for (WKSafeBrowsingTextView *view in _textViews.get())
454         [view invalidateIntrinsicContentSize];
455 }
456
457 #if PLATFORM(MAC)
458 - (BOOL)textView:(NSTextView *)textView clickedOnLink:(id)link atIndex:(NSUInteger)charIndex
459 {
460     [self clickedOnLink:link];
461     return YES;
462 }
463
464 - (void)layout
465 {
466     [super layout];
467     [self layoutText];
468 }
469 #else
470 - (void)layoutSubviews
471 {
472     [super layoutSubviews];
473     [self layoutText];
474 }
475
476 - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction
477 {
478     [self clickedOnLink:URL];
479     return NO;
480 }
481
482 - (void)didMoveToWindow
483 {
484     [self addContent];
485 }
486 #endif
487
488 - (void)dealloc
489 {
490     if (_completionHandler)
491         _completionHandler(WebKit::ContinueUnsafeLoad::No);
492     [super dealloc];
493 }
494
495 - (void)goBackClicked
496 {
497     if (_completionHandler)
498         _completionHandler(WebKit::ContinueUnsafeLoad::No);
499 }
500
501 - (void)clickedOnLink:(NSURL *)link
502 {
503     if (!_completionHandler)
504         return;
505
506     if ([link isEqual:visitUnsafeWebsiteSentinel()])
507         return _completionHandler(WebKit::ContinueUnsafeLoad::Yes);
508
509     if ([link isEqual:confirmMalwareSentinel()]) {
510 #if PLATFORM(MAC)
511         auto alert = adoptNS([NSAlert new]);
512         [alert setMessageText:WEB_UI_NSSTRING(@"Are you sure you wish to go to this site?", "Malware confirmation dialog title")];
513         [alert setInformativeText:WEB_UI_NSSTRING(@"Merely visiting a site is sufficient for malware to install itself and harm your computer.", "Malware confirmation dialog")];
514         [alert addButtonWithTitle:@"Cancel"];
515         [alert addButtonWithTitle:@"Continue"];
516         [alert beginSheetModalForWindow:self.window completionHandler:BlockPtr<void(NSModalResponse)>::fromCallable([weakSelf = WeakObjCPtr<WKSafeBrowsingWarning>(self), alert](NSModalResponse returnCode) {
517             if (auto strongSelf = weakSelf.get()) {
518                 if (returnCode == NSAlertSecondButtonReturn && strongSelf->_completionHandler)
519                     strongSelf->_completionHandler(WebKit::ContinueUnsafeLoad::Yes);
520             }
521         }).get()];
522 #else
523         _completionHandler(WebKit::ContinueUnsafeLoad::Yes);
524 #endif
525         return;
526     }
527
528     ASSERT([link isKindOfClass:[NSURL class]]);
529     _completionHandler((NSURL *)link);
530 }
531
532 @end
533
534 @implementation WKSafeBrowsingTextView
535
536 - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString forWarning:(WKSafeBrowsingWarning *)warning
537 {
538     if (!(self = [super init]))
539         return nil;
540     self->_warning = warning;
541     self.delegate = warning;
542
543     ColorType *foregroundColor = colorForItem(WarningItem::MessageText, warning);
544     NSMutableAttributedString *string = [attributedString mutableCopy];
545     [string addAttributes:@{ NSForegroundColorAttributeName : foregroundColor } range:NSMakeRange(0, string.length)];
546     [self setBackgroundColor:colorForItem(WarningItem::BoxBackground, warning)];
547     [self setLinkTextAttributes:@{ NSForegroundColorAttributeName : foregroundColor }];
548     [self.textStorage appendAttributedString:string];
549     self.editable = NO;
550 #if !PLATFORM(MAC)
551     self.scrollEnabled = NO;
552 #endif
553
554     return self;
555 }
556
557 - (SizeType)intrinsicContentSize
558 {
559 #if PLATFORM(MAC)
560     [self.layoutManager ensureLayoutForTextContainer:self.textContainer];
561     return { NSViewNoIntrinsicMetric, [self.layoutManager usedRectForTextContainer:self.textContainer].size.height };
562 #else
563     auto width = std::min<CGFloat>(maxWidth, [_warning frame].size.width) - 2 * marginSize;
564     constexpr auto noHeightConstraint = CGFLOAT_MAX;
565     return { width, [self sizeThatFits: { width, noHeightConstraint }].height };
566 #endif
567 }
568
569 @end