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