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