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