WKInspectorViewController.mm   [plain text]


/*
 * Copyright (C) 2017 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 "WKInspectorViewController.h"

#if PLATFORM(MAC)

#import "APINavigation.h"
#import "VersionChecks.h"
#import "WKFrameInfo.h"
#import "WKInspectorWKWebView.h"
#import "WKNavigationAction.h"
#import "WKNavigationDelegate.h"
#import "WKOpenPanelParameters.h"
#import "WKPreferencesPrivate.h"
#import "WKProcessPoolInternal.h"
#import "WKUIDelegatePrivate.h"
#import "WKWebViewConfigurationPrivate.h"
#import "WKWebViewPrivate.h"
#import "WebInspectorProxy.h"
#import "WebInspectorUtilities.h"
#import "WebPageProxy.h"
#import <wtf/WeakObjCPtr.h>

@interface WKInspectorViewController () <WKUIDelegate, WKNavigationDelegate, WKInspectorWKWebViewDelegate>
@end

@implementation WKInspectorViewController {
    WebKit::WebPageProxy* _inspectedPage;
    RetainPtr<WKInspectorWKWebView> _webView;
    WeakObjCPtr<id <WKInspectorViewControllerDelegate>> _delegate;
}

- (instancetype)initWithInspectedPage:(WebKit::WebPageProxy*)inspectedPage
{
    if (!(self = [super init]))
        return nil;

    // The (local) inspected page is nil if the controller is hosting a Remote Web Inspector view.
    _inspectedPage = inspectedPage;

    return self;
}

- (void)dealloc
{
    if (_webView) {
        [_webView setUIDelegate:nil];
        [_webView setNavigationDelegate:nil];
        [_webView setInspectorWKWebViewDelegate:nil];
        _webView = nil;
    }

    [super dealloc];
}

- (id <WKInspectorViewControllerDelegate>)delegate
{
    return _delegate.getAutoreleased();
}

- (WKWebView *)webView
{
    // Construct lazily so the client can set the delegate before the WebView is created.
    if (!_webView) {
        NSRect initialFrame = NSMakeRect(0, 0, WebKit::WebInspectorProxy::initialWindowWidth, WebKit::WebInspectorProxy::initialWindowHeight);
        _webView = adoptNS([[WKInspectorWKWebView alloc] initWithFrame:initialFrame configuration:[self configuration]]);
        [_webView setUIDelegate:self];
        [_webView setNavigationDelegate:self];
        [_webView setInspectorWKWebViewDelegate:self];
        [_webView _setAutomaticallyAdjustsContentInsets:NO];
        [_webView _setUseSystemAppearance:YES];
        [_webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    }

    return _webView.get();
}

- (void)setDelegate:(id <WKInspectorViewControllerDelegate>)delegate
{
    _delegate = delegate;
}

- (WKWebViewConfiguration *)configuration
{
    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);

    WKPreferences *preferences = configuration.get().preferences;
    preferences._allowFileAccessFromFileURLs = YES;
    [configuration _setAllowUniversalAccessFromFileURLs:YES];
    preferences._storageBlockingPolicy = _WKStorageBlockingPolicyAllowAll;
    preferences._javaScriptRuntimeFlags = 0;

#ifndef NDEBUG
    // Allow developers to inspect the Web Inspector in debug builds without changing settings.
    preferences._developerExtrasEnabled = YES;
    preferences._logsPageMessagesToSystemConsoleEnabled = YES;
#endif

    if (!!_delegate && [_delegate respondsToSelector:@selector(inspectorViewControllerInspectorIsUnderTest:)]) {
        if ([_delegate inspectorViewControllerInspectorIsUnderTest:self]) {
            preferences._hiddenPageDOMTimerThrottlingEnabled = NO;
            preferences._pageVisibilityBasedProcessSuppressionEnabled = NO;
        }
    }

    [configuration setProcessPool:wrapper(WebKit::inspectorProcessPool(WebKit::inspectorLevelForPage(_inspectedPage)))];
    [configuration _setGroupIdentifier:WebKit::inspectorPageGroupIdentifierForPage(_inspectedPage)];

    return configuration.autorelease();
}

+ (BOOL)viewIsInspectorWebView:(NSView *)view
{
    return [view isKindOfClass:[WKInspectorWKWebView class]];
}

// MARK: WKUIDelegate methods

- (void)_webView:(WKWebView *)webView getWindowFrameWithCompletionHandler:(void (^)(CGRect))completionHandler
{
    if (!_webView.get().window)
        completionHandler(CGRectZero);
    else
        completionHandler(NSRectToCGRect([webView frame]));
}

- (void)_webView:(WKWebView *)webView setWindowFrame:(CGRect)frame
{
    if (!_webView.get().window)
        return;

    [_webView.get().window setFrame:NSRectFromCGRect(frame) display:YES];
}

- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> *URLs))completionHandler
{
    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
    openPanel.allowsMultipleSelection = parameters.allowsMultipleSelection;

    auto reportSelectedFiles = ^(NSInteger result) {
        if (result == NSModalResponseOK)
            completionHandler(openPanel.URLs);
        else
            completionHandler(nil);
    };

    if (_webView.get().window)
        [openPanel beginSheetModalForWindow:_webView.get().window completionHandler:reportSelectedFiles];
    else
        reportSelectedFiles([openPanel runModal]);
}

- (void)_webView:(WKWebView *)webView decideDatabaseQuotaForSecurityOrigin:(WKSecurityOrigin *)securityOrigin currentQuota:(unsigned long long)currentQuota currentOriginUsage:(unsigned long long)currentOriginUsage currentDatabaseUsage:(unsigned long long)currentUsage expectedUsage:(unsigned long long)expectedUsage decisionHandler:(void (^)(unsigned long long newQuota))decisionHandler
{
    decisionHandler(std::max<unsigned long long>(expectedUsage, currentUsage * 1.25));
}

- (NSMenu *)_webView:(WKWebView *)webView contextMenu:(NSMenu *)menu forElement:(_WKContextMenuElementInfo *)element
{
    for (NSInteger i = menu.numberOfItems - 1; i >= 0; --i) {
        NSMenuItem *item = [menu itemAtIndex:i];
        switch (item.tag) {
        case kWKContextMenuItemTagOpenLinkInNewWindow:
        case kWKContextMenuItemTagOpenImageInNewWindow:
        case kWKContextMenuItemTagOpenFrameInNewWindow:
        case kWKContextMenuItemTagOpenMediaInNewWindow:
        case kWKContextMenuItemTagCopyImageUrlToClipboard:
        case kWKContextMenuItemTagCopyImageToClipboard:
        case kWKContextMenuItemTagDownloadLinkToDisk:
        case kWKContextMenuItemTagDownloadImageToDisk:
            [menu removeItemAtIndex:i];
            break;
        }
    }

    return menu;
}

// MARK: WKNavigationDelegate methods

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
{
    if (!!_delegate && [_delegate respondsToSelector:@selector(inspectorViewControllerInspectorDidCrash:)])
        [_delegate inspectorViewControllerInspectorDidCrash:self];
}

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    // Allow non-main frames to navigate anywhere.
    if (!navigationAction.targetFrame.isMainFrame) {
        decisionHandler(WKNavigationActionPolicyAllow);
        return;
    }

    // Allow loading of the main inspector file.
    if (WebKit::WebInspectorProxy::isMainOrTestInspectorPage(navigationAction.request.URL)) {
        decisionHandler(WKNavigationActionPolicyAllow);
        return;
    }

    // Prevent everything else.
    decisionHandler(WKNavigationActionPolicyCancel);
    
    // And instead load it in the inspected page.
    if (_inspectedPage)
        _inspectedPage->loadRequest(navigationAction.request);
}

// MARK: WKInspectorWKWebViewDelegate methods

- (void)inspectorWKWebViewReload:(WKInspectorWKWebView *)webView
{
    if (!_inspectedPage)
        return;

    OptionSet<WebCore::ReloadOption> reloadOptions;
    if (WebKit::linkedOnOrAfter(WebKit::SDKVersion::FirstWithExpiredOnlyReloadBehavior))
        reloadOptions.add(WebCore::ReloadOption::ExpiredOnly);

    _inspectedPage->reload(reloadOptions);
}

- (void)inspectorWKWebViewReloadFromOrigin:(WKInspectorWKWebView *)webView
{
    if (!_inspectedPage)
        return;

    _inspectedPage->reload(WebCore::ReloadOption::FromOrigin);
}

- (void)inspectorWKWebView:(WKInspectorWKWebView *)webView willMoveToWindow:(NSWindow *)newWindow
{
    if (!!_delegate && [_delegate respondsToSelector:@selector(inspectorViewController:willMoveToWindow:)])
        [_delegate inspectorViewController:self willMoveToWindow:newWindow];
}

- (void)inspectorWKWebViewDidMoveToWindow:(WKInspectorWKWebView *)webView
{
    if (!!_delegate && [_delegate respondsToSelector:@selector(inspectorViewControllerDidMoveToWindow:)])
        [_delegate inspectorViewControllerDidMoveToWindow:self];
}

@end

#endif // PLATFORM(MAC)