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