WKSafeBrowsingWarning.mm   [plain text]


/*
 * Copyright (C) 2018 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "WKSafeBrowsingWarning.h"

#import "PageClient.h"
#import "SafeBrowsingWarning.h"
#import <WebCore/LocalizedStrings.h>
#import <wtf/URL.h>
#import <wtf/BlockPtr.h>
#import <wtf/Language.h>

#if PLATFORM(WATCHOS)
#import "UIKitSPI.h"
#import <PepperUICore/UIScrollView+PUICAdditionsPrivate.h>
#endif

constexpr CGFloat exclamationPointSize = 30;
constexpr CGFloat boxCornerRadius = 6;
#if HAVE(SAFE_BROWSING)
#if PLATFORM(WATCHOS)
constexpr CGFloat marginSize = 9;
#else
constexpr CGFloat marginSize = 20;
#endif
constexpr CGFloat maxWidth = 675;
#endif

#if PLATFORM(MAC)
using FontType = NSFont;
using TextViewType = NSTextView;
using ButtonType = NSButton;
using AlignmentType = NSLayoutAttribute;
using SizeType = NSSize;
#else
using FontType = UIFont;
using TextViewType = UITextView;
using ButtonType = UIButton;
using AlignmentType = UIStackViewAlignment;
using SizeType = CGSize;
#endif

enum class WarningItem : uint8_t {
    Background,
    BoxBackground,
    ExclamationPoint,
    TitleText,
    MessageText,
    ShowDetailsButton,
    GoBackButton
};

enum class WarningTextSize : uint8_t {
    Title,
    Body
};

static FontType *fontOfSize(WarningTextSize size)
{
#if PLATFORM(MAC)
    switch (size) {
    case WarningTextSize::Title:
        return [NSFont boldSystemFontOfSize:26];
    case WarningTextSize::Body:
        return [NSFont systemFontOfSize:14];
    }
#elif HAVE(SAFE_BROWSING)
    switch (size) {
    case WarningTextSize::Title:
#if PLATFORM(WATCHOS)
        return [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
#else
        return [UIFont preferredFontForTextStyle:UIFontTextStyleLargeTitle];
#endif
    case WarningTextSize::Body:
        return [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
    }
#else
    return nil;
#endif
}

static ColorType *colorForItem(WarningItem item, ViewType *warning)
{
    ASSERT([warning isKindOfClass:[WKSafeBrowsingWarning class]]);
#if PLATFORM(MAC)

    auto colorNamed = [] (NSString *name) -> ColorType* {
#if HAVE(SAFE_BROWSING)
        return [NSColor colorNamed:name bundle:[NSBundle bundleWithIdentifier:@"com.apple.WebKit"]];
#else
        ASSERT_NOT_REACHED();
        return nil;
#endif
    };

    switch (item) {
    case WarningItem::Background:
        return colorNamed(@"WKSafeBrowsingWarningBackground");
    case WarningItem::BoxBackground:
        return [NSColor windowBackgroundColor];
    case WarningItem::TitleText:
    case WarningItem::ExclamationPoint:
        return colorNamed(@"WKSafeBrowsingWarningTitle");
    case WarningItem::MessageText:
        return colorNamed(@"WKSafeBrowsingWarningText");
    case WarningItem::ShowDetailsButton:
    case WarningItem::GoBackButton:
        ASSERT_NOT_REACHED();
        return nil;
    }
#else
    UIColor *red = [UIColor colorWithRed:0.998 green:0.239 blue:0.233 alpha:1.0];
    UIColor *white = [UIColor whiteColor];
    bool narrow = warning.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact;

    switch (item) {
    case WarningItem::Background:
        return red;
    case WarningItem::BoxBackground:
        return narrow ? red : white;
    case WarningItem::TitleText:
    case WarningItem::ExclamationPoint:
        return narrow ? white : red;
    case WarningItem::MessageText:
    case WarningItem::ShowDetailsButton:
        return narrow ? white : [UIColor darkTextColor];
    case WarningItem::GoBackButton:
        return narrow ? white : warning.tintColor;
    }
#endif
    ASSERT_NOT_REACHED();
    return nil;
}

@interface WKSafeBrowsingExclamationPoint : ViewType
@end

@implementation WKSafeBrowsingExclamationPoint

- (void)drawRect:(RectType)rect
{
    constexpr CGFloat centerX = exclamationPointSize / 2;
    constexpr CGFloat pointCenterY = exclamationPointSize * 7 / 30;
    constexpr CGFloat pointRadius = 2.25 * exclamationPointSize / 30;
    constexpr CGFloat lineBottomCenterY = exclamationPointSize * 13 / 30;
    constexpr CGFloat lineTopCenterY = exclamationPointSize * 23 / 30;
    constexpr CGFloat lineRadius = 1.75 * exclamationPointSize / 30;
    ViewType *warning = self.superview.superview;
#if PLATFORM(MAC)
    [colorForItem(WarningItem::ExclamationPoint, warning) set];
    NSBezierPath *exclamationPoint = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(0, 0, exclamationPointSize, exclamationPointSize)];
    [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, lineBottomCenterY } radius:lineRadius startAngle:0 endAngle:180 clockwise:YES];
    [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, lineTopCenterY } radius:lineRadius startAngle:180 endAngle:360 clockwise:YES];
    [exclamationPoint lineToPoint: { centerX + lineRadius, lineBottomCenterY }];
    [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, pointCenterY } radius:pointRadius startAngle:0 endAngle:180 clockwise:YES];
    [exclamationPoint appendBezierPathWithArcWithCenter: { centerX, pointCenterY } radius:pointRadius startAngle:180 endAngle:360 clockwise:YES];
#else
    auto flip = [] (auto y) {
        return exclamationPointSize - y;
    };
    [colorForItem(WarningItem::BoxBackground, warning) set];
    auto square = CGRectMake(0, 0, exclamationPointSize, exclamationPointSize);
    [[UIBezierPath bezierPathWithRect:square] fill];
    
    [colorForItem(WarningItem::ExclamationPoint, warning) set];
    UIBezierPath *exclamationPoint = [UIBezierPath bezierPathWithOvalInRect:square];
    [exclamationPoint addArcWithCenter: { centerX, flip(lineTopCenterY) } radius:lineRadius startAngle:2 * piDouble endAngle:piDouble clockwise:NO];
    [exclamationPoint addArcWithCenter: { centerX, flip(lineBottomCenterY) } radius:lineRadius startAngle:piDouble endAngle:0 clockwise:NO];
    [exclamationPoint addArcWithCenter: { centerX, flip(pointCenterY) } radius:pointRadius startAngle:0 endAngle:piDouble clockwise:NO];
    [exclamationPoint addArcWithCenter: { centerX, flip(pointCenterY) } radius:pointRadius startAngle:piDouble endAngle:piDouble * 2 clockwise:NO];
    [exclamationPoint addLineToPoint: { centerX + lineRadius, flip(lineBottomCenterY) }];
    [exclamationPoint addLineToPoint: { centerX + lineRadius, flip(lineTopCenterY) }];
#endif
    [exclamationPoint fill];
}

#if PLATFORM(MAC)
- (void)viewDidChangeEffectiveAppearance
{
    [self setNeedsDisplay:YES];
}
#endif

- (NSSize)intrinsicContentSize
{
    return { exclamationPointSize, exclamationPointSize };
}

@end

static ButtonType *makeButton(WarningItem item, WKSafeBrowsingWarning *warning, SEL action)
{
    NSString *title = nil;
    if (item == WarningItem::ShowDetailsButton)
        title = WEB_UI_NSSTRING(@"Show Details", "Action from safe browsing warning");
    else
        title = WEB_UI_NSSTRING(@"Go Back", "Action from safe browsing warning");
#if PLATFORM(MAC)
    return [NSButton buttonWithTitle:title target:warning action:action];
#else
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    NSAttributedString *attributedTitle = [[[NSAttributedString alloc] initWithString:title attributes:@{
        NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle),
        NSUnderlineColorAttributeName:[UIColor whiteColor],
        NSForegroundColorAttributeName:colorForItem(item, warning),
        NSFontAttributeName:fontOfSize(WarningTextSize::Body)
    }] autorelease];
    [button setAttributedTitle:attributedTitle forState:UIControlStateNormal];
    [button addTarget:warning action:action forControlEvents:UIControlEventTouchUpInside];
    return button;
#endif
}

#if HAVE(SAFE_BROWSING)
static CGSize buttonSize(ButtonType *button)
{
#if PLATFORM(MAC)
    return button.frame.size;
#else
    return button.titleLabel.intrinsicContentSize;
#endif
}
#endif

static ViewType *makeLabel(NSAttributedString *attributedString)
{
#if PLATFORM(MAC)
    return [NSTextField labelWithAttributedString:attributedString];
#else
    auto label = [[UILabel new] autorelease];
    label.attributedText = attributedString;
    label.lineBreakMode = NSLineBreakByWordWrapping;
    label.numberOfLines = 0;
    return label;
#endif
}

@implementation WKSafeBrowsingBox

- (void)setSafeBrowsingBackgroundColor:(ColorType *)color
{
#if PLATFORM(MAC)
    _backgroundColor = color;
    self.wantsLayer = YES;
#else
    self.backgroundColor = color;
#endif
}

#if PLATFORM(MAC)
- (void)updateLayer
{
    self.layer.backgroundColor = [_backgroundColor CGColor];
}
#endif

@end

@interface WKSafeBrowsingTextView : TextViewType {
@package
    WeakObjCPtr<WKSafeBrowsingWarning> _warning;
}
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString forWarning:(WKSafeBrowsingWarning *)warning;
@end

@implementation WKSafeBrowsingWarning

- (instancetype)initWithFrame:(RectType)frame safeBrowsingWarning:(const WebKit::SafeBrowsingWarning&)warning completionHandler:(CompletionHandler<void(Variant<WebKit::ContinueUnsafeLoad, URL>&&)>&&)completionHandler
{
    if (!(self = [super initWithFrame:frame])) {
        completionHandler(WebKit::ContinueUnsafeLoad::Yes);
        return nil;
    }
    _completionHandler = [weakSelf = WeakObjCPtr<WKSafeBrowsingWarning>(self), completionHandler = WTFMove(completionHandler)] (Variant<WebKit::ContinueUnsafeLoad, URL>&& result) mutable {
#if PLATFORM(WATCHOS)
        if (auto strongSelf = weakSelf.get())
            [strongSelf.get()->_previousFirstResponder becomeFirstResponder];
#endif
        completionHandler(WTFMove(result));
    };
    _warning = makeRef(warning);
#if PLATFORM(MAC)
    [self setSafeBrowsingBackgroundColor:colorForItem(WarningItem::Background, self)];
    [self addContent];
#else
    [self setBackgroundColor:colorForItem(WarningItem::Background, self)];
#endif

#if PLATFORM(WATCHOS)
    self.crownInputScrollDirection = PUICCrownInputScrollDirectionVertical;
#endif
    return self;
}

- (void)addContent
{
    auto exclamationPoint = [[WKSafeBrowsingExclamationPoint new] autorelease];
    auto title = makeLabel([[[NSAttributedString alloc] initWithString:_warning->title() attributes:@{
        NSFontAttributeName:fontOfSize(WarningTextSize::Title),
        NSForegroundColorAttributeName:colorForItem(WarningItem::TitleText, self)
#if PLATFORM(WATCHOS)
        , NSHyphenationFactorDocumentAttribute:@1
#endif
    }] autorelease]);
    auto warning = makeLabel([[[NSAttributedString alloc] initWithString:_warning->warning() attributes:@{
        NSFontAttributeName:fontOfSize(WarningTextSize::Body),
        NSForegroundColorAttributeName:colorForItem(WarningItem::MessageText, self)
#if PLATFORM(WATCHOS)
        , NSHyphenationFactorDocumentAttribute:@1
#endif
    }] autorelease]);
    auto showDetails = makeButton(WarningItem::ShowDetailsButton, self, @selector(showDetailsClicked));
    auto goBack = makeButton(WarningItem::GoBackButton, self, @selector(goBackClicked));
    auto box = [[WKSafeBrowsingBox new] autorelease];
    _box = box;
    [box setSafeBrowsingBackgroundColor:colorForItem(WarningItem::BoxBackground, self)];
    box.layer.cornerRadius = boxCornerRadius;

    for (ViewType *view in @[exclamationPoint, title, warning, goBack, showDetails]) {
        view.translatesAutoresizingMaskIntoConstraints = NO;
        [box addSubview:view];
    }
    box.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:box];

#if PLATFORM(WATCHOS)
    [NSLayoutConstraint activateConstraints:@[
        [[box.leadingAnchor anchorWithOffsetToAnchor:exclamationPoint.leadingAnchor] constraintEqualToAnchor:[exclamationPoint.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor]],
        [[box.leadingAnchor anchorWithOffsetToAnchor:title.leadingAnchor] constraintEqualToConstant:marginSize],
        [[title.bottomAnchor anchorWithOffsetToAnchor:warning.topAnchor] constraintEqualToConstant:marginSize],
        [[exclamationPoint.bottomAnchor anchorWithOffsetToAnchor:title.topAnchor] constraintEqualToConstant:marginSize],
        [[box.topAnchor anchorWithOffsetToAnchor:exclamationPoint.topAnchor] constraintEqualToConstant:marginSize + self.frame.size.height / 2],
        [[self.topAnchor anchorWithOffsetToAnchor:box.topAnchor] constraintEqualToAnchor:[box.bottomAnchor anchorWithOffsetToAnchor:self.bottomAnchor] multiplier:0.2],
    ]];
#elif HAVE(SAFE_BROWSING)
    [NSLayoutConstraint activateConstraints:@[
        [[box.leadingAnchor anchorWithOffsetToAnchor:exclamationPoint.leadingAnchor] constraintEqualToConstant:marginSize],
        [[box.leadingAnchor anchorWithOffsetToAnchor:title.leadingAnchor] constraintEqualToConstant:marginSize * 1.5 + exclamationPointSize],
        [[title.topAnchor anchorWithOffsetToAnchor:exclamationPoint.topAnchor] constraintEqualToAnchor:[exclamationPoint.bottomAnchor anchorWithOffsetToAnchor:title.bottomAnchor]],
        [[title.bottomAnchor anchorWithOffsetToAnchor:warning.topAnchor] constraintEqualToConstant:marginSize],
        [[box.topAnchor anchorWithOffsetToAnchor:title.topAnchor] constraintEqualToConstant:marginSize],
        [[self.topAnchor anchorWithOffsetToAnchor:box.topAnchor] constraintEqualToAnchor:[box.bottomAnchor anchorWithOffsetToAnchor:self.bottomAnchor] multiplier:0.5],
    ]];
#endif

#if HAVE(SAFE_BROWSING)
    [NSLayoutConstraint activateConstraints:@[
        [[self.leftAnchor anchorWithOffsetToAnchor:box.leftAnchor] constraintEqualToAnchor:[box.rightAnchor anchorWithOffsetToAnchor:self.rightAnchor]],

        [box.widthAnchor constraintLessThanOrEqualToConstant:maxWidth],
        [box.widthAnchor constraintLessThanOrEqualToAnchor:self.widthAnchor],

        [[box.leadingAnchor anchorWithOffsetToAnchor:warning.leadingAnchor] constraintEqualToConstant:marginSize],

        [[title.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor] constraintGreaterThanOrEqualToConstant:marginSize],
        [[warning.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor] constraintGreaterThanOrEqualToConstant:marginSize],
        [[goBack.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor] constraintEqualToConstant:marginSize],

        [[warning.bottomAnchor anchorWithOffsetToAnchor:goBack.topAnchor] constraintEqualToConstant:marginSize],
    ]];
    
    bool needsVerticalButtonLayout = buttonSize(showDetails).width + buttonSize(goBack).width + 3 * marginSize > self.frame.size.width;
    if (needsVerticalButtonLayout) {
        [NSLayoutConstraint activateConstraints:@[
            [[showDetails.trailingAnchor anchorWithOffsetToAnchor:box.trailingAnchor] constraintEqualToConstant:marginSize],
            [[goBack.bottomAnchor anchorWithOffsetToAnchor:showDetails.topAnchor] constraintEqualToConstant:marginSize],
            [[goBack.bottomAnchor anchorWithOffsetToAnchor:box.bottomAnchor] constraintEqualToConstant:marginSize * 2 + buttonSize(showDetails).height],
        ]];
    } else {
        [NSLayoutConstraint activateConstraints:@[
            [[showDetails.trailingAnchor anchorWithOffsetToAnchor:goBack.leadingAnchor] constraintEqualToConstant:marginSize],
            [goBack.topAnchor constraintEqualToAnchor:showDetails.topAnchor],
            [[goBack.bottomAnchor anchorWithOffsetToAnchor:box.bottomAnchor] constraintEqualToConstant:marginSize],
        ]];
    }
#if !PLATFORM(MAC)
    [self updateContentSize];
#endif
#endif
    
#if PLATFORM(WATCHOS)
    self->_previousFirstResponder = [self firstResponder];
    [self becomeFirstResponder];
#endif
}

- (void)showDetailsClicked
{
    WKSafeBrowsingBox *box = _box.get().get();
    ButtonType *showDetails = box.subviews.lastObject;
    [showDetails removeFromSuperview];

    NSMutableAttributedString *text = [[_warning->details() mutableCopy] autorelease];
    [text addAttributes:@{ NSFontAttributeName:fontOfSize(WarningTextSize::Body) } range:NSMakeRange(0, text.length)];
    WKSafeBrowsingTextView *details = [[[WKSafeBrowsingTextView alloc] initWithAttributedString:text forWarning:self] autorelease];
    _details = details;
    WKSafeBrowsingBox *bottom = [[WKSafeBrowsingBox new] autorelease];
    [bottom setSafeBrowsingBackgroundColor:colorForItem(WarningItem::BoxBackground, self)];
    bottom.layer.cornerRadius = boxCornerRadius;

#if HAVE(SAFE_BROWSING)
    constexpr auto maxY = kCALayerMinXMaxYCorner | kCALayerMaxXMaxYCorner;
    constexpr auto minY = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
#if PLATFORM(MAC)
    box.layer.maskedCorners = maxY;
    bottom.layer.maskedCorners = minY;
#else
    box.layer.maskedCorners = minY;
    bottom.layer.maskedCorners = maxY;
#endif
#endif

    WKSafeBrowsingBox *line = [[WKSafeBrowsingBox new] autorelease];
    [line setSafeBrowsingBackgroundColor:[ColorType lightGrayColor]];
    for (ViewType *view in @[details, bottom, line])
        view.translatesAutoresizingMaskIntoConstraints = NO;

    [self addSubview:bottom];
    [bottom addSubview:line];
    [bottom addSubview:details];
#if HAVE(SAFE_BROWSING)
    [NSLayoutConstraint activateConstraints:@[
        [box.widthAnchor constraintEqualToAnchor:bottom.widthAnchor],
        [box.bottomAnchor constraintEqualToAnchor:bottom.topAnchor],
        [box.leadingAnchor constraintEqualToAnchor:bottom.leadingAnchor],
        [line.widthAnchor constraintEqualToAnchor:bottom.widthAnchor],
        [line.leadingAnchor constraintEqualToAnchor:bottom.leadingAnchor],
        [line.topAnchor constraintEqualToAnchor:bottom.topAnchor],
        [line.heightAnchor constraintEqualToConstant:1],
        [[bottom.topAnchor anchorWithOffsetToAnchor:details.topAnchor] constraintEqualToConstant:marginSize],
        [[details.bottomAnchor anchorWithOffsetToAnchor:bottom.bottomAnchor] constraintEqualToConstant:marginSize],
        [[bottom.leadingAnchor anchorWithOffsetToAnchor:details.leadingAnchor] constraintEqualToConstant:marginSize],
        [[details.trailingAnchor anchorWithOffsetToAnchor:bottom.trailingAnchor] constraintEqualToConstant:marginSize],
    ]];
#endif
    [self layoutText];
#if !PLATFORM(MAC)
    [self updateContentSize];
#endif
}

#if !PLATFORM(MAC)
- (void)updateContentSize
{
    [self layoutIfNeeded];
    CGFloat height = 0;
    for (ViewType *subview in self.subviews)
        height += subview.frame.size.height;
    [self setContentSize: { self.frame.size.width, self.frame.size.height / 2 + height }];
}
#endif

- (void)layoutText
{
    [_details invalidateIntrinsicContentSize];
}

#if PLATFORM(MAC)
- (BOOL)textView:(NSTextView *)textView clickedOnLink:(id)link atIndex:(NSUInteger)charIndex
{
    [self clickedOnLink:link];
    return YES;
}

- (void)layout
{
    [super layout];
    [self layoutText];
}
#else
- (void)layoutSubviews
{
    [super layoutSubviews];
    [self layoutText];
}

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction
{
    [self clickedOnLink:URL];
    return NO;
}

- (void)didMoveToWindow
{
    [self addContent];
}
#endif

- (void)dealloc
{
    if (_completionHandler)
        _completionHandler(WebKit::ContinueUnsafeLoad::No);
    [super dealloc];
}

- (void)goBackClicked
{
    if (_completionHandler)
        _completionHandler(WebKit::ContinueUnsafeLoad::No);
}

- (void)clickedOnLink:(NSURL *)link
{
    if (!_completionHandler)
        return;

    if ([link isEqual:WebKit::SafeBrowsingWarning::visitUnsafeWebsiteSentinel()])
        return _completionHandler(WebKit::ContinueUnsafeLoad::Yes);

    if ([link isEqual:WebKit::SafeBrowsingWarning::confirmMalwareSentinel()]) {
#if PLATFORM(MAC)
        auto alert = adoptNS([NSAlert new]);
        [alert setMessageText:WEB_UI_NSSTRING(@"Are you sure you wish to go to this site?", "Malware confirmation dialog title")];
        [alert setInformativeText:WEB_UI_NSSTRING(@"Merely visiting a site is sufficient for malware to install itself and harm your computer.", "Malware confirmation dialog")];
        [alert addButtonWithTitle:WEB_UI_NSSTRING(@"Cancel", "Cancel")];
        [alert addButtonWithTitle:WEB_UI_NSSTRING(@"Continue", "Continue")];
        [alert beginSheetModalForWindow:self.window completionHandler:makeBlockPtr([weakSelf = WeakObjCPtr<WKSafeBrowsingWarning>(self), alert](NSModalResponse returnCode) {
            if (auto strongSelf = weakSelf.get()) {
                if (returnCode == NSAlertSecondButtonReturn && strongSelf->_completionHandler)
                    strongSelf->_completionHandler(WebKit::ContinueUnsafeLoad::Yes);
            }
        }).get()];
#else
        _completionHandler(WebKit::ContinueUnsafeLoad::Yes);
#endif
        return;
    }

    ASSERT([link isKindOfClass:[NSURL class]]);
    _completionHandler((NSURL *)link);
}

- (BOOL)forMainFrameNavigation
{
    return _warning->forMainFrameNavigation();
}

@end

@implementation WKSafeBrowsingTextView

- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString forWarning:(WKSafeBrowsingWarning *)warning
{
    if (!(self = [super init]))
        return nil;
    self->_warning = warning;
    self.delegate = warning;

    ColorType *foregroundColor = colorForItem(WarningItem::MessageText, warning);
    NSMutableAttributedString *string = [[attributedString mutableCopy] autorelease];
    [string addAttributes:@{ NSForegroundColorAttributeName : foregroundColor } range:NSMakeRange(0, string.length)];
    [self setBackgroundColor:colorForItem(WarningItem::BoxBackground, warning)];
    [self setLinkTextAttributes:@{ NSForegroundColorAttributeName : foregroundColor }];
    [self.textStorage appendAttributedString:string];
    self.editable = NO;
#if !PLATFORM(MAC)
    self.scrollEnabled = NO;
#endif

    return self;
}

- (SizeType)intrinsicContentSize
{
#if PLATFORM(MAC)
    [self.layoutManager ensureLayoutForTextContainer:self.textContainer];
    return { NSViewNoIntrinsicMetric, [self.layoutManager usedRectForTextContainer:self.textContainer].size.height };
#elif HAVE(SAFE_BROWSING)
    auto width = std::min<CGFloat>(maxWidth, [_warning frame].size.width) - 2 * marginSize;
    constexpr auto noHeightConstraint = CGFLOAT_MAX;
    return { width, [self sizeThatFits: { width, noHeightConstraint }].height };
#else
    return { 0, 0 };
#endif
}

@end