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