Inspector doesn't get focused when opened in dock mode
[WebKit-https.git] / Source / WebKit2 / UIProcess / mac / WebInspectorProxyMac.mm
1 /*
2  * Copyright (C) 2010, 2014 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 "WebInspectorProxy.h"
28
29 #if PLATFORM(MAC) && WK_API_ENABLED
30
31 #import "WKAPICast.h"
32 #import "WKInspectorPrivateMac.h"
33 #import "WKMutableArray.h"
34 #import "WKOpenPanelParameters.h"
35 #import "WKOpenPanelResultListener.h"
36 #import "WKPreferencesInternal.h"
37 #import "WKProcessPoolInternal.h"
38 #import "WKRetainPtr.h"
39 #import "WKURLCF.h"
40 #import "WKViewInternal.h"
41 #import "WKWebViewConfigurationPrivate.h"
42 #import "WKWebViewInternal.h"
43 #import "WebInspectorMessages.h"
44 #import "WebInspectorUIMessages.h"
45 #import "WebPageGroup.h"
46 #import "WebPageProxy.h"
47 #import "WebPreferences.h"
48 #import "WebProcessProxy.h"
49 #import <QuartzCore/CoreAnimation.h>
50 #import <WebCore/InspectorFrontendClientLocal.h>
51 #import <WebCore/LocalizedStrings.h>
52 #import <WebCore/SoftLinking.h>
53 #import <WebKitSystemInterface.h>
54 #import <algorithm>
55 #import <mach-o/dyld.h>
56 #import <wtf/text/Base64.h>
57 #import <wtf/text/WTFString.h>
58
59 SOFT_LINK_STAGED_FRAMEWORK(WebInspectorUI, PrivateFrameworks, A)
60
61 using namespace WebCore;
62 using namespace WebKit;
63
64 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
65 // The height needed to match a typical NSToolbar.
66 static const CGFloat windowContentBorderThickness = 55;
67 #endif
68
69 // The margin from the top and right of the dock button (same as the full screen button).
70 static const CGFloat dockButtonMargin = 3;
71
72 // The spacing between the dock buttons.
73 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
74 static const CGFloat dockButtonSpacing = 1;
75 static const NSUInteger windowStyleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSFullSizeContentViewWindowMask;
76 #else
77 static const CGFloat dockButtonSpacing = dockButtonMargin * 2;
78 static const NSUInteger windowStyleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSTexturedBackgroundWindowMask;
79 #endif
80
81 // The time we keep our WebView alive before closing it and its process.
82 // Reusing the WebView improves start up time for people that jump in and out of the Inspector.
83 static const unsigned webViewCloseTimeout = 60;
84
85 // WKWebInspectorProxyObjCAdapter is a helper ObjC object used as a delegate or notification observer
86 // for the sole purpose of getting back into the C++ code from an ObjC caller.
87
88 @interface WKWebInspectorProxyObjCAdapter ()
89
90 - (id)initWithWebInspectorProxy:(WebInspectorProxy*)inspectorProxy;
91 - (void)close;
92 - (void)didRelaunchProcess;
93
94 @end
95
96 @implementation WKWebInspectorProxyObjCAdapter
97
98 - (WKInspectorRef)inspectorRef
99 {
100     return toAPI(static_cast<WebInspectorProxy*>(_inspectorProxy));
101 }
102
103 - (id)initWithWebInspectorProxy:(WebInspectorProxy*)inspectorProxy
104 {
105     ASSERT_ARG(inspectorProxy, inspectorProxy);
106
107     if (!(self = [super init]))
108         return nil;
109
110     _inspectorProxy = static_cast<void*>(inspectorProxy); // Not retained to prevent cycles
111
112     return self;
113 }
114
115 - (IBAction)attachRight:(id)sender
116 {
117     static_cast<WebInspectorProxy*>(_inspectorProxy)->attach(AttachmentSide::Right);
118 }
119
120 - (IBAction)attachBottom:(id)sender
121 {
122     static_cast<WebInspectorProxy*>(_inspectorProxy)->attach(AttachmentSide::Bottom);
123 }
124
125 - (void)close
126 {
127     _inspectorProxy = nullptr;
128 }
129
130 - (void)didRelaunchProcess
131 {
132     static_cast<WebInspectorProxy*>(_inspectorProxy)->didRelaunchInspectorPageProcess();
133 }
134
135 - (void)windowDidMove:(NSNotification *)notification
136 {
137     static_cast<WebInspectorProxy*>(_inspectorProxy)->windowFrameDidChange();
138 }
139
140 - (void)windowDidResize:(NSNotification *)notification
141 {
142     static_cast<WebInspectorProxy*>(_inspectorProxy)->windowFrameDidChange();
143 }
144
145 - (void)windowWillClose:(NSNotification *)notification
146 {
147     static_cast<WebInspectorProxy*>(_inspectorProxy)->close();
148 }
149
150 - (void)ignoreNextInspectedViewFrameDidChange
151 {
152     _ignoreNextInspectedViewFrameDidChange = YES;
153 }
154
155 - (void)inspectedViewFrameDidChange:(NSNotification *)notification
156 {
157     if (_ignoreNextInspectedViewFrameDidChange) {
158         _ignoreNextInspectedViewFrameDidChange = NO;
159         return;
160     }
161
162     // Resizing the views while inside this notification can lead to bad results when entering
163     // or exiting full screen. To avoid that we need to perform the work after a delay. We only
164     // depend on this for enforcing the height constraints, so a small delay isn't terrible. Most
165     // of the time the views will already have the correct frames because of autoresizing masks.
166
167     dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
168         if (!_inspectorProxy)
169             return;
170         static_cast<WebInspectorProxy*>(_inspectorProxy)->inspectedViewFrameDidChange();
171     });
172 }
173
174 @end
175
176 @interface WKWebInspectorWKWebView : WKWebView {
177 @private
178     WKWebInspectorProxyObjCAdapter *_inspectorProxyObjCAdapter;
179 }
180
181 @property (nonatomic, assign) WKWebInspectorProxyObjCAdapter *inspectorProxyObjCAdapter;
182 @end
183
184 @implementation WKWebInspectorWKWebView
185
186 @synthesize inspectorProxyObjCAdapter = _inspectorProxyObjCAdapter;
187
188 - (NSInteger)tag
189 {
190     return WKInspectorViewTag;
191 }
192
193 - (void)_didRelaunchProcess
194 {
195     [self.inspectorProxyObjCAdapter didRelaunchProcess];
196 }
197
198 @end
199
200 @interface NSView (AppKitDetails)
201 - (void)_addKnownSubview:(NSView *)subview;
202 @end
203
204 @interface NSWindow (AppKitDetails)
205 - (NSCursor *)_cursorForResizeDirection:(NSInteger)direction;
206 - (NSRect)_customTitleFrame;
207 @end
208
209 @interface WKWebInspectorWindow : NSWindow {
210 @public
211     RetainPtr<NSButton> _dockBottomButton;
212     RetainPtr<NSButton> _dockRightButton;
213 }
214 @end
215
216 @implementation WKWebInspectorWindow
217
218 - (NSCursor *)_cursorForResizeDirection:(NSInteger)direction
219 {
220     // Don't show a resize cursor for the northeast (top right) direction if the dock button is visible.
221     // This matches what happens when the full screen button is visible.
222     if (direction == 1 && ![_dockRightButton isHidden])
223         return nil;
224     return [super _cursorForResizeDirection:direction];
225 }
226
227 - (NSRect)_customTitleFrame
228 {
229     // Adjust the title frame if needed to prevent it from intersecting the dock button.
230     NSRect titleFrame = [super _customTitleFrame];
231     NSRect dockButtonFrame = _dockBottomButton.get().frame;
232     if (NSMaxX(titleFrame) > NSMinX(dockButtonFrame) - dockButtonMargin)
233         titleFrame.size.width -= (NSMaxX(titleFrame) - NSMinX(dockButtonFrame)) + dockButtonMargin;
234     return titleFrame;
235 }
236
237 @end
238
239 namespace WebKit {
240
241 static WKRect getWindowFrame(WKPageRef, const void* clientInfo)
242 {
243     WebInspectorProxy* webInspectorProxy = static_cast<WebInspectorProxy*>(const_cast<void*>(clientInfo));
244     ASSERT(webInspectorProxy);
245
246     return webInspectorProxy->inspectorWindowFrame();
247 }
248
249 static void setWindowFrame(WKPageRef, WKRect frame, const void* clientInfo)
250 {
251     WebInspectorProxy* webInspectorProxy = static_cast<WebInspectorProxy*>(const_cast<void*>(clientInfo));
252     ASSERT(webInspectorProxy);
253
254     webInspectorProxy->setInspectorWindowFrame(frame);
255 }
256
257 static unsigned long long exceededDatabaseQuota(WKPageRef, WKFrameRef, WKSecurityOriginRef, WKStringRef, WKStringRef, unsigned long long, unsigned long long, unsigned long long currentDatabaseUsage, unsigned long long expectedUsage, const void*)
258 {
259     return std::max<unsigned long long>(expectedUsage, currentDatabaseUsage * 1.25);
260 }
261
262 static void runOpenPanel(WKPageRef page, WKFrameRef frame, WKOpenPanelParametersRef parameters, WKOpenPanelResultListenerRef listener, const void* clientInfo)
263 {
264     WebInspectorProxy* webInspectorProxy = static_cast<WebInspectorProxy*>(const_cast<void*>(clientInfo));
265     ASSERT(webInspectorProxy);
266
267     NSOpenPanel *openPanel = [NSOpenPanel openPanel];
268     [openPanel setAllowsMultipleSelection:WKOpenPanelParametersGetAllowsMultipleFiles(parameters)];
269
270     WKRetain(listener);
271
272     // If the inspector is detached, then openPanel will be window-modal; otherwise, openPanel is opened in a new window.
273     [openPanel beginSheetModalForWindow:webInspectorProxy->inspectorWindow() completionHandler:^(NSInteger result) {
274         if (result == NSFileHandlingPanelOKButton) {
275             WKMutableArrayRef fileURLs = WKMutableArrayCreate();
276
277             for (NSURL* nsURL in [openPanel URLs]) {
278                 WKURLRef wkURL = WKURLCreateWithCFURL(reinterpret_cast<CFURLRef>(nsURL));
279                 WKArrayAppendItem(fileURLs, wkURL);
280                 WKRelease(wkURL);
281             }
282
283             WKOpenPanelResultListenerChooseFiles(listener, fileURLs);
284
285             WKRelease(fileURLs);
286         } else
287             WKOpenPanelResultListenerCancel(listener);
288
289         WKRelease(listener);
290     }];
291 }
292
293 void WebInspectorProxy::attachmentViewDidChange(NSView *oldView, NSView *newView)
294 {
295     [[NSNotificationCenter defaultCenter] removeObserver:m_inspectorProxyObjCAdapter.get() name:NSViewFrameDidChangeNotification object:oldView];
296     [[NSNotificationCenter defaultCenter] addObserver:m_inspectorProxyObjCAdapter.get() selector:@selector(inspectedViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:newView];
297
298     if (m_isAttached)
299         attach(m_attachmentSide);
300 }
301
302 void WebInspectorProxy::setInspectorWindowFrame(WKRect& frame)
303 {
304     if (m_isAttached)
305         return;
306     [m_inspectorWindow setFrame:NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height) display:YES];
307 }
308
309 WKRect WebInspectorProxy::inspectorWindowFrame()
310 {
311     if (m_isAttached)
312         return WKRectMake(0, 0, 0, 0);
313
314     NSRect frame = m_inspectorWindow.get().frame;
315     return WKRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
316 }
317
318 void WebInspectorProxy::closeTimerFired()
319 {
320     ASSERT(!m_isAttached || !m_inspectorWindow);
321     if (m_isAttached || m_inspectorWindow)
322         return;
323
324     if (m_inspectorView) {
325         m_inspectorView->_page->close();
326         [m_inspectorView setInspectorProxyObjCAdapter:nil];
327         m_inspectorView = nil;
328     }
329
330     [[NSNotificationCenter defaultCenter] removeObserver:m_inspectorProxyObjCAdapter.get()];
331
332     [m_inspectorProxyObjCAdapter close];
333     m_inspectorProxyObjCAdapter = nil;
334 }
335
336 static NSButton *createDockButton(NSString *imageName)
337 {
338     // Create a full screen button so we can turn it into a dock button.
339     NSButton *dockButton = [NSWindow standardWindowButton:NSWindowFullScreenButton forStyleMask:windowStyleMask];
340
341     // Set the autoresizing mask to keep the dock button pinned to the top right corner.
342     dockButton.autoresizingMask = NSViewMinXMargin | NSViewMinYMargin;
343
344     // Get the dock image and make it a template so the button cell effects will apply.
345     NSImage *dockImage = [[NSBundle bundleForClass:[WKWebInspectorWKWebView class]] imageForResource:imageName];
346     [dockImage setTemplate:YES];
347
348     // Set the dock image on the button cell.
349     NSCell *dockButtonCell = dockButton.cell;
350     dockButtonCell.image = dockImage;
351
352     return [dockButton retain];
353 }
354
355 void WebInspectorProxy::createInspectorWindow()
356 {
357     ASSERT(!m_inspectorWindow);
358
359     NSRect windowFrame = NSMakeRect(0, 0, initialWindowWidth, initialWindowHeight);
360
361     // Restore the saved window frame, if there was one.
362     NSString *savedWindowFrameString = inspectedPage()->pageGroup().preferences().inspectorWindowFrame();
363     NSRect savedWindowFrame = NSRectFromString(savedWindowFrameString);
364     if (!NSIsEmptyRect(savedWindowFrame))
365         windowFrame = savedWindowFrame;
366
367     WKWebInspectorWindow *window = [[WKWebInspectorWindow alloc] initWithContentRect:windowFrame styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];
368     [window setDelegate:m_inspectorProxyObjCAdapter.get()];
369     [window setMinSize:NSMakeSize(minimumWindowWidth, minimumWindowHeight)];
370     [window setReleasedWhenClosed:NO];
371
372 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
373     window.titlebarAppearsTransparent = YES;
374 #else
375     [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
376     [window setContentBorderThickness:windowContentBorderThickness forEdge:NSMaxYEdge];
377 #endif
378
379     m_inspectorWindow = adoptNS(window);
380
381     NSView *contentView = [window contentView];
382
383     static const int32_t firstVersionOfSafariWithDockToRightSupport = 0x02181d0d; // 536.29.13
384     static bool supportsDockToRight = NSVersionOfLinkTimeLibrary("Safari") >= firstVersionOfSafariWithDockToRightSupport;
385
386 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
387     m_dockBottomButton = adoptNS(createDockButton(@"DockBottom"));
388     m_dockRightButton = adoptNS(createDockButton(@"DockRight"));
389
390     m_dockBottomButton.get().alphaValue = 0.55;
391     m_dockRightButton.get().alphaValue = supportsDockToRight ? 0.55 : 0.25;
392 #else
393     m_dockBottomButton = adoptNS(createDockButton(@"DockBottomLegacy"));
394     m_dockRightButton = adoptNS(createDockButton(@"DockRightLegacy"));
395
396     m_dockRightButton.get().alphaValue = supportsDockToRight ? 1 : 0.5;
397 #endif
398
399     m_dockBottomButton.get().target = m_inspectorProxyObjCAdapter.get();
400     m_dockBottomButton.get().action = @selector(attachBottom:);
401
402     m_dockRightButton.get().target = m_inspectorProxyObjCAdapter.get();
403     m_dockRightButton.get().action = @selector(attachRight:);
404     m_dockRightButton.get().enabled = supportsDockToRight;
405
406     // Store the dock buttons on the window too so it can check its visibility.
407     window->_dockBottomButton = m_dockBottomButton;
408     window->_dockRightButton = m_dockRightButton;
409
410     // Get the frame view, the superview of the content view, and its frame.
411     // This will be the superview of the dock button too.
412     NSView *frameView = contentView.superview;
413     NSRect frameViewBounds = frameView.bounds;
414     NSSize dockButtonSize = m_dockBottomButton.get().frame.size;
415
416     ASSERT(!frameView.isFlipped);
417
418     // Position the dock button in the corner to match where the full screen button is normally.
419     NSPoint dockButtonOrigin;
420     dockButtonOrigin.x = NSMaxX(frameViewBounds) - dockButtonSize.width - dockButtonMargin;
421     dockButtonOrigin.y = NSMaxY(frameViewBounds) - dockButtonSize.height - dockButtonMargin;
422     m_dockRightButton.get().frameOrigin = dockButtonOrigin;
423
424     dockButtonOrigin.x -= dockButtonSize.width + dockButtonSpacing;
425     m_dockBottomButton.get().frameOrigin = dockButtonOrigin;
426
427     if ([frameView respondsToSelector:@selector(_addKnownSubview:)]) {
428         [frameView _addKnownSubview:m_dockBottomButton.get()];
429         [frameView _addKnownSubview:m_dockRightButton.get()];
430     } else {
431         [frameView addSubview:m_dockBottomButton.get()];
432         [frameView addSubview:m_dockRightButton.get()];
433     }
434
435     // Hide the dock buttons if we can't attach.
436     m_dockBottomButton.get().hidden = !canAttach();
437     m_dockRightButton.get().hidden = !canAttach();
438
439     [m_inspectorView setFrame:[contentView bounds]];
440     [m_inspectorView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
441     [contentView addSubview:m_inspectorView.get()];
442
443     // Center the window if the saved frame was empty.
444     if (NSIsEmptyRect(savedWindowFrame))
445         [window center];
446
447     updateInspectorWindowTitle();
448 }
449
450 void WebInspectorProxy::updateInspectorWindowTitle() const
451 {
452     if (!m_inspectorWindow)
453         return;
454
455     NSString *title = [NSString stringWithFormat:WEB_UI_STRING("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_urlString];
456     [m_inspectorWindow setTitle:title];
457 }
458
459 WebPageProxy* WebInspectorProxy::platformCreateInspectorPage()
460 {
461     ASSERT(inspectedPage());
462
463     m_closeTimer.stop();
464
465     if (m_inspectorView) {
466         ASSERT(m_inspectorProxyObjCAdapter);
467         return m_inspectorView->_page.get();
468     }
469
470     ASSERT(!m_inspectorView);
471     ASSERT(!m_inspectorProxyObjCAdapter);
472
473     NSView *inspectedView = inspectedPage()->wkView()._inspectorAttachmentView;
474
475     NSRect initialRect;
476     if (m_isAttached) {
477         NSRect inspectedViewFrame = inspectedView.frame;
478
479         switch (m_attachmentSide) {
480         case AttachmentSide::Bottom:
481             initialRect = NSMakeRect(0, 0, NSWidth(inspectedViewFrame), inspectorPagePreferences().inspectorAttachedHeight());
482             break;
483         case AttachmentSide::Right:
484             initialRect = NSMakeRect(0, 0, inspectorPagePreferences().inspectorAttachedWidth(), NSHeight(inspectedViewFrame));
485             break;
486         }
487     } else {
488         initialRect = NSMakeRect(0, 0, initialWindowWidth, initialWindowHeight);
489
490         NSString *windowFrameString = inspectedPage()->pageGroup().preferences().inspectorWindowFrame();
491         NSRect windowFrame = NSRectFromString(windowFrameString);
492         if (!NSIsEmptyRect(windowFrame))
493             initialRect = [NSWindow contentRectForFrameRect:windowFrame styleMask:windowStyleMask];
494     }
495
496     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
497     WKPreferences *preferences = [configuration preferences];
498 #ifndef NDEBUG
499     // Allow developers to inspect the Web Inspector in debug builds without changing settings.
500     preferences._developerExtrasEnabled = YES;
501     preferences._logsPageMessagesToSystemConsoleEnabled = YES;
502 #endif
503     preferences._allowFileAccessFromFileURLs = YES;
504     preferences._javaScriptRuntimeFlags = _WKJavaScriptRuntimeFlagsSymbolEnabled;
505     [configuration setProcessPool: ::WebKit::wrapper(inspectorProcessPool())];
506     [configuration _setGroupIdentifier:inspectorPageGroupIdentifier()];
507
508     m_inspectorView = adoptNS([[WKWebInspectorWKWebView alloc] initWithFrame:initialRect configuration:configuration.get()]);
509     ASSERT(m_inspectorView);
510
511 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 10900
512     m_inspectorView->_page->setDrawsBackground(false);
513 #endif
514
515 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
516     [m_inspectorView _setAutomaticallyAdjustsContentInsets:NO];
517 #endif
518
519     m_inspectorProxyObjCAdapter = adoptNS([[WKWebInspectorProxyObjCAdapter alloc] initWithWebInspectorProxy:this]);
520     ASSERT(m_inspectorProxyObjCAdapter);
521
522     [m_inspectorView setInspectorProxyObjCAdapter:m_inspectorProxyObjCAdapter.get()];
523
524     [[NSNotificationCenter defaultCenter] addObserver:m_inspectorProxyObjCAdapter.get() selector:@selector(inspectedViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:inspectedView];
525
526     WebPageProxy* inspectorPage = m_inspectorView->_page.get();
527     ASSERT(inspectorPage);
528
529     WKPageUIClientV2 uiClient = {
530         { 2, this },
531         0, // createNewPage_deprecatedForUseWithV0
532         0, // showPage
533         0, // closePage
534         0, // takeFocus
535         0, // focus
536         0, // unfocus
537         0, // runJavaScriptAlert
538         0, // runJavaScriptConfirm
539         0, // runJavaScriptPrompt
540         0, // setStatusText
541         0, // mouseDidMoveOverElement_deprecatedForUseWithV0
542         0, // missingPluginButtonClicked_deprecatedForUseWithV0
543         0, // didNotHandleKeyEvent
544         0, // didNotHandleWheelEvent
545         0, // areToolbarsVisible
546         0, // setToolbarsVisible
547         0, // isMenuBarVisible
548         0, // setMenuBarVisible
549         0, // isStatusBarVisible
550         0, // setStatusBarVisible
551         0, // isResizable
552         0, // setResizable
553         getWindowFrame,
554         setWindowFrame,
555         0, // runBeforeUnloadConfirmPanel
556         0, // didDraw
557         0, // pageDidScroll
558         exceededDatabaseQuota,
559         runOpenPanel,
560         0, // decidePolicyForGeolocationPermissionRequest
561         0, // headerHeight
562         0, // footerHeight
563         0, // drawHeader
564         0, // drawFooter
565         0, // printFrame
566         0, // runModal
567         0, // unused
568         0, // saveDataToFileInDownloadsFolder
569         0, // shouldInterruptJavaScript
570         0, // createPage
571         0, // mouseDidMoveOverElement
572         0, // decidePolicyForNotificationPermissionRequest
573         0, // unavailablePluginButtonClicked_deprecatedForUseWithV1
574         0, // showColorPicker
575         0, // hideColorPicker
576         0, // unavailablePluginButtonClicked
577     };
578
579     WKPageSetPageUIClient(toAPI(inspectorPage), &uiClient.base);
580
581     return inspectorPage;
582 }
583
584 bool WebInspectorProxy::platformCanAttach(bool webProcessCanAttach)
585 {
586     NSView *inspectedView = inspectedPage()->wkView()._inspectorAttachmentView;
587     if ([inspectedView isKindOfClass:[WKView class]])
588         return webProcessCanAttach;
589
590     static const float minimumAttachedHeight = 250;
591     static const float maximumAttachedHeightRatio = 0.75;
592     static const float minimumAttachedWidth = 750;
593
594     NSRect inspectedViewFrame = inspectedView.frame;
595
596     float maximumAttachedHeight = NSHeight(inspectedViewFrame) * maximumAttachedHeightRatio;
597     return minimumAttachedHeight <= maximumAttachedHeight && minimumAttachedWidth <= NSWidth(inspectedViewFrame);
598 }
599
600 void WebInspectorProxy::platformOpen()
601 {
602     if (m_isAttached)
603         platformAttach();
604     else
605         createInspectorWindow();
606
607     platformBringToFront();
608 }
609
610 void WebInspectorProxy::platformDidClose()
611 {
612     if (m_inspectorWindow) {
613         [m_inspectorWindow setDelegate:nil];
614         [m_inspectorWindow orderOut:nil];
615         m_inspectorWindow = nil;
616     }
617
618     m_closeTimer.startOneShot(webViewCloseTimeout);
619 }
620
621 void WebInspectorProxy::platformInvalidate()
622 {
623     m_closeTimer.stop();
624
625     closeTimerFired();
626 }
627
628 void WebInspectorProxy::platformHide()
629 {
630     if (m_isAttached) {
631         platformDetach();
632         return;
633     }
634
635     if (m_inspectorWindow) {
636         [m_inspectorWindow setDelegate:nil];
637         [m_inspectorWindow orderOut:nil];
638         m_inspectorWindow = nil;
639     }
640 }
641
642 void WebInspectorProxy::platformBringToFront()
643 {
644     // If the Web Inspector is no longer in the same window as the inspected view,
645     // then we need to reopen the Inspector to get it attached to the right window.
646     // This can happen when dragging tabs to another window in Safari.
647     if (m_isAttached && m_inspectorView.get().window != inspectedPage()->wkView().window) {
648         platformOpen();
649         return;
650     }
651
652     // FIXME <rdar://problem/10937688>: this will not bring a background tab in Safari to the front, only its window.
653     [m_inspectorView.get().window makeKeyAndOrderFront:nil];
654     [m_inspectorView.get().window makeFirstResponder:m_inspectorView->_page->wkView()];
655 }
656
657 bool WebInspectorProxy::platformIsFront()
658 {
659     // FIXME <rdar://problem/10937688>: this will not return false for a background tab in Safari, only a background window.
660     return m_isVisible && [m_inspectorView.get().window isMainWindow];
661 }
662
663 void WebInspectorProxy::platformAttachAvailabilityChanged(bool available)
664 {
665     m_dockBottomButton.get().hidden = !available;
666     m_dockRightButton.get().hidden = !available;
667 }
668
669 void WebInspectorProxy::platformInspectedURLChanged(const String& urlString)
670 {
671     m_urlString = urlString;
672
673     updateInspectorWindowTitle();
674 }
675
676 void WebInspectorProxy::platformSave(const String& suggestedURL, const String& content, bool base64Encoded, bool forceSaveDialog)
677 {
678     ASSERT(!suggestedURL.isEmpty());
679     
680     NSURL *platformURL = m_suggestedToActualURLMap.get(suggestedURL).get();
681     if (!platformURL) {
682         platformURL = [NSURL URLWithString:suggestedURL];
683         // The user must confirm new filenames before we can save to them.
684         forceSaveDialog = true;
685     }
686     
687     ASSERT(platformURL);
688     if (!platformURL)
689         return;
690
691     // Necessary for the block below.
692     String suggestedURLCopy = suggestedURL;
693     String contentCopy = content;
694
695     auto saveToURL = ^(NSURL *actualURL) {
696         ASSERT(actualURL);
697
698         m_suggestedToActualURLMap.set(suggestedURLCopy, actualURL);
699
700         if (base64Encoded) {
701             Vector<char> out;
702             if (!base64Decode(contentCopy, out, Base64FailOnInvalidCharacterOrExcessPadding))
703                 return;
704             RetainPtr<NSData> dataContent = adoptNS([[NSData alloc] initWithBytes:out.data() length:out.size()]);
705             [dataContent writeToURL:actualURL atomically:YES];
706         } else
707             [contentCopy writeToURL:actualURL atomically:YES encoding:NSUTF8StringEncoding error:NULL];
708
709         m_inspectorPage->process().send(Messages::WebInspectorUI::DidSave([actualURL absoluteString]), m_inspectorPage->pageID());
710     };
711
712     if (!forceSaveDialog) {
713         saveToURL(platformURL);
714         return;
715     }
716
717     NSSavePanel *panel = [NSSavePanel savePanel];
718     panel.nameFieldStringValue = platformURL.lastPathComponent;
719     panel.directoryURL = [platformURL URLByDeletingLastPathComponent];
720
721     [panel beginSheetModalForWindow:m_inspectorWindow.get() completionHandler:^(NSInteger result) {
722         if (result == NSFileHandlingPanelCancelButton)
723             return;
724         ASSERT(result == NSFileHandlingPanelOKButton);
725         saveToURL(panel.URL);
726     }];
727 }
728
729 void WebInspectorProxy::platformAppend(const String& suggestedURL, const String& content)
730 {
731     ASSERT(!suggestedURL.isEmpty());
732     
733     RetainPtr<NSURL> actualURL = m_suggestedToActualURLMap.get(suggestedURL);
734     // Do not append unless the user has already confirmed this filename in save().
735     if (!actualURL)
736         return;
737
738     NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:actualURL.get() error:NULL];
739     [handle seekToEndOfFile];
740     [handle writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
741     [handle closeFile];
742
743     m_inspectorPage->process().send(Messages::WebInspectorUI::DidAppend([actualURL absoluteString]), m_inspectorPage->pageID());
744 }
745
746 void WebInspectorProxy::windowFrameDidChange()
747 {
748     ASSERT(!m_isAttached);
749     ASSERT(m_isVisible);
750     ASSERT(m_inspectorWindow);
751
752     if (m_isAttached || !m_isVisible || !m_inspectorWindow)
753         return;
754
755     NSString *frameString = NSStringFromRect([m_inspectorWindow frame]);
756     inspectedPage()->pageGroup().preferences().setInspectorWindowFrame(frameString);
757 }
758
759 void WebInspectorProxy::inspectedViewFrameDidChange(CGFloat currentDimension)
760 {
761     if (!m_isVisible)
762         return;
763
764     if (!m_isAttached) {
765         // Check if the attach avaibility changed. We need to do this here in case
766         // the attachment view is not the WKView.
767         attachAvailabilityChanged(platformCanAttach(canAttach()));
768         return;
769     }
770
771     NSView *inspectedView = inspectedPage()->wkView()._inspectorAttachmentView;
772     NSRect inspectedViewFrame = [inspectedView frame];
773     NSRect inspectorFrame = NSZeroRect;
774     NSRect parentBounds = [[inspectedView superview] bounds];
775     CGFloat inspectedViewTop = NSMaxY(inspectedViewFrame);
776
777     switch (m_attachmentSide) {
778     case AttachmentSide::Bottom: {
779         if (!currentDimension)
780             currentDimension = NSHeight([m_inspectorView frame]);
781
782         CGFloat parentHeight = NSHeight(parentBounds);
783         CGFloat inspectorHeight = InspectorFrontendClientLocal::constrainedAttachedWindowHeight(currentDimension, parentHeight);
784
785         // Preserve the top position of the inspected view so banners in Safari still work.
786         inspectedViewFrame = NSMakeRect(0, inspectorHeight, NSWidth(parentBounds), inspectedViewTop - inspectorHeight);
787         inspectorFrame = NSMakeRect(0, 0, NSWidth(inspectedViewFrame), inspectorHeight);
788         break;
789     }
790
791     case AttachmentSide::Right: {
792         if (!currentDimension)
793             currentDimension = NSWidth([m_inspectorView frame]);
794
795         CGFloat parentWidth = NSWidth(parentBounds);
796         CGFloat inspectorWidth = InspectorFrontendClientLocal::constrainedAttachedWindowWidth(currentDimension, parentWidth);
797
798         // Preserve the top position of the inspected view so banners in Safari still work. But don't use that
799         // top position for the inspector view since the banners only stretch as wide as the the inspected view.
800         inspectedViewFrame = NSMakeRect(0, 0, parentWidth - inspectorWidth, inspectedViewTop);
801         CGFloat insetExcludingBanners = 0;
802         if ([inspectedView isKindOfClass:[WKView class]])
803             insetExcludingBanners = ((WKView *)inspectedView)._topContentInset - ((WKView *)inspectedView)._totalHeightOfBanners;
804         inspectorFrame = NSMakeRect(parentWidth - inspectorWidth, 0, inspectorWidth, NSHeight(parentBounds) - insetExcludingBanners);
805         break;
806     }
807     }
808
809     if (NSEqualRects([m_inspectorView frame], inspectorFrame) && NSEqualRects([inspectedView frame], inspectedViewFrame))
810         return;
811
812     [m_inspectorProxyObjCAdapter ignoreNextInspectedViewFrameDidChange];
813
814     // Disable screen updates to make sure the layers for both views resize in sync.
815     [[m_inspectorView window] disableScreenUpdatesUntilFlush];
816
817     [m_inspectorView setFrame:inspectorFrame];
818     [inspectedView setFrame:inspectedViewFrame];
819 }
820
821 unsigned WebInspectorProxy::platformInspectedWindowHeight()
822 {
823     NSView *inspectedView = inspectedPage()->wkView()._inspectorAttachmentView;
824     NSRect inspectedViewRect = [inspectedView frame];
825     return static_cast<unsigned>(inspectedViewRect.size.height);
826 }
827
828 unsigned WebInspectorProxy::platformInspectedWindowWidth()
829 {
830     NSView *inspectedView = inspectedPage()->wkView()._inspectorAttachmentView;
831     NSRect inspectedViewRect = [inspectedView frame];
832     return static_cast<unsigned>(inspectedViewRect.size.width);
833 }
834
835 void WebInspectorProxy::platformAttach()
836 {
837     NSView *inspectedView = inspectedPage()->wkView()._inspectorAttachmentView;
838
839     if (m_inspectorWindow) {
840         [m_inspectorWindow setDelegate:nil];
841         [m_inspectorWindow orderOut:nil];
842         m_inspectorWindow = nil;
843     }
844
845     [m_inspectorView removeFromSuperview];
846
847     CGFloat currentDimension;
848
849     switch (m_attachmentSide) {
850     case AttachmentSide::Bottom:
851         [m_inspectorView setAutoresizingMask:NSViewWidthSizable | NSViewMaxYMargin];
852         currentDimension = inspectorPagePreferences().inspectorAttachedHeight();
853         break;
854     case AttachmentSide::Right:
855         [m_inspectorView setAutoresizingMask:NSViewHeightSizable | NSViewMinXMargin];
856         currentDimension = inspectorPagePreferences().inspectorAttachedWidth();
857         break;
858     }
859
860     inspectedViewFrameDidChange(currentDimension);
861
862     [[inspectedView superview] addSubview:m_inspectorView.get() positioned:NSWindowBelow relativeTo:inspectedView];
863     [m_inspectorView.get().window makeFirstResponder:m_inspectorView->_page->wkView()];
864 }
865
866 void WebInspectorProxy::platformDetach()
867 {
868     NSView *inspectedView = inspectedPage()->wkView()._inspectorAttachmentView;
869
870     [m_inspectorView removeFromSuperview];
871
872     // Make sure that we size the inspected view's frame after detaching so that it takes up the space that the
873     // attached inspector used to. Preserve the top position of the inspected view so banners in Safari still work.
874
875     inspectedView.frame = NSMakeRect(0, 0, NSWidth(inspectedView.superview.bounds), NSMaxY(inspectedView.frame));
876
877     // Return early if we are not visible. This means the inspector was closed while attached
878     // and we should not create and show the inspector window.
879     if (!m_isVisible)
880         return;
881
882     createInspectorWindow();
883
884     platformBringToFront();
885 }
886
887 void WebInspectorProxy::platformSetAttachedWindowHeight(unsigned height)
888 {
889     if (!m_isAttached)
890         return;
891
892     inspectedViewFrameDidChange(height);
893 }
894
895 void WebInspectorProxy::platformSetAttachedWindowWidth(unsigned width)
896 {
897     if (!m_isAttached)
898         return;
899
900     inspectedViewFrameDidChange(width);
901 }
902
903 void WebInspectorProxy::platformSetToolbarHeight(unsigned height)
904 {
905 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
906     [m_inspectorWindow setContentBorderThickness:height forEdge:NSMaxYEdge];
907 #endif
908 }
909
910 String WebInspectorProxy::inspectorPageURL() const
911 {
912     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
913     WebInspectorUILibrary();
914
915     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Main" ofType:@"html"];
916     ASSERT([path length]);
917
918     return [[NSURL fileURLWithPath:path] absoluteString];
919 }
920
921 String WebInspectorProxy::inspectorTestPageURL() const
922 {
923     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
924     WebInspectorUILibrary();
925
926     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Test" ofType:@"html"];
927
928     // We might not have a Test.html in Production builds.
929     if (!path)
930         return String();
931
932     return [[NSURL fileURLWithPath:path] absoluteString];
933 }
934
935 String WebInspectorProxy::inspectorBaseURL() const
936 {
937     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
938     WebInspectorUILibrary();
939
940     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] resourcePath];
941     ASSERT([path length]);
942
943     return [[NSURL fileURLWithPath:path] absoluteString];
944 }
945
946 } // namespace WebKit
947
948 #endif // PLATFORM(MAC) && WK_API_ENABLED