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
53SOFT_LINK_STAGED_FRAMEWORK(WebInspectorUI, PrivateFrameworks, A)
54
55using namespace WebCore;
56using namespace WebKit;
57
58// The height needed to match a typical NSToolbar.
59static const CGFloat windowContentBorderThickness = 55;
60
61// The margin from the top and right of the dock button (same as the full screen button).
62static const CGFloat dockButtonMargin = 3;
63
64// The spacing between the dock buttons.
65static const CGFloat dockButtonSpacing = dockButtonMargin * 2;
66
67static 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
191namespace WebKit {
192
193static 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
209static 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
217static 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
225static 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
230static 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
261void 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
268WKRect 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
277static 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
296void 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
373void 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
382WebPageProxy* 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
473void WebInspectorProxy::platformOpen()
474{
475    if (m_isAttached)
476        platformAttach();
477    else
478        createInspectorWindow();
479
480    platformBringToFront();
481}
482
483void 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
497void 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
511void 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
526bool 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
532void WebInspectorProxy::platformAttachAvailabilityChanged(bool available)
533{
534    m_dockBottomButton.get().hidden = !available;
535    m_dockRightButton.get().hidden = !available;
536}
537
538void WebInspectorProxy::platformInspectedURLChanged(const String& urlString)
539{
540    m_urlString = urlString;
541
542    updateInspectorWindowTitle();
543}
544
545void 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
589void 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
606void 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
619void 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
666unsigned WebInspectorProxy::platformInspectedWindowHeight()
667{
668    WKView *inspectedView = m_page->wkView();
669    NSRect inspectedViewRect = [inspectedView frame];
670    return static_cast<unsigned>(inspectedViewRect.size.height);
671}
672
673unsigned WebInspectorProxy::platformInspectedWindowWidth()
674{
675    WKView *inspectedView = m_page->wkView();
676    NSRect inspectedViewRect = [inspectedView frame];
677    return static_cast<unsigned>(inspectedViewRect.size.width);
678}
679
680void 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
713void 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
735void WebInspectorProxy::platformSetAttachedWindowHeight(unsigned height)
736{
737    if (!m_isAttached)
738        return;
739
740    inspectedViewFrameDidChange(height);
741}
742
743void WebInspectorProxy::platformSetAttachedWindowWidth(unsigned width)
744{
745    if (!m_isAttached)
746        return;
747
748    inspectedViewFrameDidChange(width);
749}
750
751void WebInspectorProxy::platformSetToolbarHeight(unsigned height)
752{
753    [m_inspectorWindow setContentBorderThickness:height forEdge:NSMaxYEdge];
754}
755
756String 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
769String 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)
785