2 * Copyright (C) 2010, 2014 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "WebInspectorProxy.h"
29 #if PLATFORM(MAC) && ENABLE(INSPECTOR)
32 #import "WKInspectorPrivateMac.h"
33 #import "WKMutableArray.h"
34 #import "WKOpenPanelParameters.h"
35 #import "WKOpenPanelResultListener.h"
36 #import "WKRetainPtr.h"
38 #import "WKViewInternal.h"
39 #import "WebContext.h"
40 #import "WebInspectorMessages.h"
41 #import "WebInspectorUIMessages.h"
42 #import "WebPageGroup.h"
43 #import "WebPageProxy.h"
44 #import "WebPreferences.h"
45 #import "WebProcessProxy.h"
46 #import <QuartzCore/CoreAnimation.h>
47 #import <WebCore/InspectorFrontendClientLocal.h>
48 #import <WebCore/LocalizedStrings.h>
49 #import <WebCore/SoftLinking.h>
50 #import <WebKitSystemInterface.h>
52 #import <mach-o/dyld.h>
53 #import <wtf/text/Base64.h>
54 #import <wtf/text/WTFString.h>
56 SOFT_LINK_STAGED_FRAMEWORK(WebInspectorUI, PrivateFrameworks, A)
58 using namespace WebCore;
59 using namespace WebKit;
61 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
62 // The height needed to match a typical NSToolbar.
63 static const CGFloat windowContentBorderThickness = 55;
66 // The margin from the top and right of the dock button (same as the full screen button).
67 static const CGFloat dockButtonMargin = 3;
69 // The spacing between the dock buttons.
70 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
71 static const CGFloat dockButtonSpacing = 1;
72 static const NSUInteger windowStyleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSFullSizeContentViewWindowMask;
74 static const CGFloat dockButtonSpacing = dockButtonMargin * 2;
75 static const NSUInteger windowStyleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSTexturedBackgroundWindowMask;
78 // The time we keep our WebView alive before closing it and its process.
79 // Reusing the WebView improves start up time for people that jump in and out of the Inspector.
80 static const unsigned webViewCloseTimeout = 60;
82 // WKWebInspectorProxyObjCAdapter is a helper ObjC object used as a delegate or notification observer
83 // for the sole purpose of getting back into the C++ code from an ObjC caller.
85 @interface WKWebInspectorProxyObjCAdapter ()
87 - (id)initWithWebInspectorProxy:(WebInspectorProxy*)inspectorProxy;
89 - (void)didRelaunchProcess;
93 @implementation WKWebInspectorProxyObjCAdapter
95 - (WKInspectorRef)inspectorRef
97 return toAPI(static_cast<WebInspectorProxy*>(_inspectorProxy));
100 - (id)initWithWebInspectorProxy:(WebInspectorProxy*)inspectorProxy
102 ASSERT_ARG(inspectorProxy, inspectorProxy);
104 if (!(self = [super init]))
107 _inspectorProxy = static_cast<void*>(inspectorProxy); // Not retained to prevent cycles
112 - (IBAction)attachRight:(id)sender
114 static_cast<WebInspectorProxy*>(_inspectorProxy)->attach(AttachmentSideRight);
117 - (IBAction)attachBottom:(id)sender
119 static_cast<WebInspectorProxy*>(_inspectorProxy)->attach(AttachmentSideBottom);
124 _inspectorProxy = nullptr;
127 - (void)didRelaunchProcess
129 static_cast<WebInspectorProxy*>(_inspectorProxy)->didRelaunchInspectorPageProcess();
132 - (void)windowDidMove:(NSNotification *)notification
134 static_cast<WebInspectorProxy*>(_inspectorProxy)->windowFrameDidChange();
137 - (void)windowDidResize:(NSNotification *)notification
139 static_cast<WebInspectorProxy*>(_inspectorProxy)->windowFrameDidChange();
142 - (void)windowWillClose:(NSNotification *)notification
144 static_cast<WebInspectorProxy*>(_inspectorProxy)->close();
147 - (void)ignoreNextInspectedViewFrameDidChange
149 _ignoreNextInspectedViewFrameDidChange = YES;
152 - (void)inspectedViewFrameDidChange:(NSNotification *)notification
154 if (_ignoreNextInspectedViewFrameDidChange) {
155 _ignoreNextInspectedViewFrameDidChange = NO;
159 // Resizing the views while inside this notification can lead to bad results when entering
160 // or exiting full screen. To avoid that we need to perform the work after a delay. We only
161 // depend on this for enforcing the height constraints, so a small delay isn't terrible. Most
162 // of the time the views will already have the correct frames because of autoresizing masks.
164 dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
165 if (!_inspectorProxy)
167 static_cast<WebInspectorProxy*>(_inspectorProxy)->inspectedViewFrameDidChange();
173 @interface WKWebInspectorWKView : WKView
174 @property (nonatomic, assign) WKWebInspectorProxyObjCAdapter *inspectorProxyObjCAdapter;
177 @implementation WKWebInspectorWKView
179 @synthesize inspectorProxyObjCAdapter;
183 return WKInspectorViewTag;
186 - (void)_didRelaunchProcess
188 [super _didRelaunchProcess];
190 [self.inspectorProxyObjCAdapter didRelaunchProcess];
195 @interface NSView (AppKitDetails)
196 - (void)_addKnownSubview:(NSView *)subview;
199 @interface NSWindow (AppKitDetails)
200 - (NSCursor *)_cursorForResizeDirection:(NSInteger)direction;
201 - (NSRect)_customTitleFrame;
204 @interface WKWebInspectorWindow : NSWindow {
206 RetainPtr<NSButton> _dockBottomButton;
207 RetainPtr<NSButton> _dockRightButton;
211 @implementation WKWebInspectorWindow
213 - (NSCursor *)_cursorForResizeDirection:(NSInteger)direction
215 // Don't show a resize cursor for the northeast (top right) direction if the dock button is visible.
216 // This matches what happens when the full screen button is visible.
217 if (direction == 1 && ![_dockRightButton isHidden])
219 return [super _cursorForResizeDirection:direction];
222 - (NSRect)_customTitleFrame
224 // Adjust the title frame if needed to prevent it from intersecting the dock button.
225 NSRect titleFrame = [super _customTitleFrame];
226 NSRect dockButtonFrame = _dockBottomButton.get().frame;
227 if (NSMaxX(titleFrame) > NSMinX(dockButtonFrame) - dockButtonMargin)
228 titleFrame.size.width -= (NSMaxX(titleFrame) - NSMinX(dockButtonFrame)) + dockButtonMargin;
236 static WKRect getWindowFrame(WKPageRef, const void* clientInfo)
238 WebInspectorProxy* webInspectorProxy = static_cast<WebInspectorProxy*>(const_cast<void*>(clientInfo));
239 ASSERT(webInspectorProxy);
241 return webInspectorProxy->inspectorWindowFrame();
244 static void setWindowFrame(WKPageRef, WKRect frame, const void* clientInfo)
246 WebInspectorProxy* webInspectorProxy = static_cast<WebInspectorProxy*>(const_cast<void*>(clientInfo));
247 ASSERT(webInspectorProxy);
249 webInspectorProxy->setInspectorWindowFrame(frame);
252 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*)
254 return std::max<unsigned long long>(expectedUsage, currentDatabaseUsage * 1.25);
257 static void runOpenPanel(WKPageRef page, WKFrameRef frame, WKOpenPanelParametersRef parameters, WKOpenPanelResultListenerRef listener, const void* clientInfo)
259 WebInspectorProxy* webInspectorProxy = static_cast<WebInspectorProxy*>(const_cast<void*>(clientInfo));
260 ASSERT(webInspectorProxy);
262 NSOpenPanel *openPanel = [NSOpenPanel openPanel];
263 [openPanel setAllowsMultipleSelection:WKOpenPanelParametersGetAllowsMultipleFiles(parameters)];
267 // If the inspector is detached, then openPanel will be window-modal; otherwise, openPanel is opened in a new window.
268 [openPanel beginSheetModalForWindow:webInspectorProxy->inspectorWindow() completionHandler:^(NSInteger result) {
269 if (result == NSFileHandlingPanelOKButton) {
270 WKMutableArrayRef fileURLs = WKMutableArrayCreate();
272 for (NSURL* nsURL in [openPanel URLs]) {
273 WKURLRef wkURL = WKURLCreateWithCFURL(reinterpret_cast<CFURLRef>(nsURL));
274 WKArrayAppendItem(fileURLs, wkURL);
278 WKOpenPanelResultListenerChooseFiles(listener, fileURLs);
282 WKOpenPanelResultListenerCancel(listener);
288 void WebInspectorProxy::setInspectorWindowFrame(WKRect& frame)
292 [m_inspectorWindow setFrame:NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height) display:YES];
295 WKRect WebInspectorProxy::inspectorWindowFrame()
298 return WKRectMake(0, 0, 0, 0);
300 NSRect frame = m_inspectorWindow.get().frame;
301 return WKRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
304 void WebInspectorProxy::closeTimerFired()
306 ASSERT(!m_isAttached || !m_inspectorWindow);
307 if (m_isAttached || m_inspectorWindow)
310 if (m_inspectorView) {
311 WebPageProxy* inspectorPage = toImpl(m_inspectorView.get().pageRef);
312 inspectorPage->close();
313 [m_inspectorView setInspectorProxyObjCAdapter:nil];
314 m_inspectorView = nil;
317 [m_inspectorProxyObjCAdapter close];
318 m_inspectorProxyObjCAdapter = nil;
321 static NSButton *createDockButton(NSString *imageName)
323 // Create a full screen button so we can turn it into a dock button.
324 NSButton *dockButton = [NSWindow standardWindowButton:NSWindowFullScreenButton forStyleMask:windowStyleMask];
326 // Set the autoresizing mask to keep the dock button pinned to the top right corner.
327 dockButton.autoresizingMask = NSViewMinXMargin | NSViewMinYMargin;
329 // Get the dock image and make it a template so the button cell effects will apply.
330 NSImage *dockImage = [[NSBundle bundleForClass:[WKWebInspectorWKView class]] imageForResource:imageName];
331 [dockImage setTemplate:YES];
333 // Set the dock image on the button cell.
334 NSCell *dockButtonCell = dockButton.cell;
335 dockButtonCell.image = dockImage;
337 return [dockButton retain];
340 void WebInspectorProxy::createInspectorWindow()
342 ASSERT(!m_inspectorWindow);
344 NSRect windowFrame = NSMakeRect(0, 0, initialWindowWidth, initialWindowHeight);
346 // Restore the saved window frame, if there was one.
347 NSString *savedWindowFrameString = page()->pageGroup().preferences().inspectorWindowFrame();
348 NSRect savedWindowFrame = NSRectFromString(savedWindowFrameString);
349 if (!NSIsEmptyRect(savedWindowFrame))
350 windowFrame = savedWindowFrame;
352 WKWebInspectorWindow *window = [[WKWebInspectorWindow alloc] initWithContentRect:windowFrame styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];
353 [window setDelegate:m_inspectorProxyObjCAdapter.get()];
354 [window setMinSize:NSMakeSize(minimumWindowWidth, minimumWindowHeight)];
355 [window setReleasedWhenClosed:NO];
357 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
358 window.titlebarAppearsTransparent = YES;
360 [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
361 [window setContentBorderThickness:windowContentBorderThickness forEdge:NSMaxYEdge];
364 m_inspectorWindow = adoptNS(window);
366 NSView *contentView = [window contentView];
368 static const int32_t firstVersionOfSafariWithDockToRightSupport = 0x02181d0d; // 536.29.13
369 static bool supportsDockToRight = NSVersionOfLinkTimeLibrary("Safari") >= firstVersionOfSafariWithDockToRightSupport;
371 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
372 m_dockBottomButton = adoptNS(createDockButton(@"DockBottom"));
373 m_dockRightButton = adoptNS(createDockButton(@"DockRight"));
375 m_dockBottomButton.get().alphaValue = 0.55;
376 m_dockRightButton.get().alphaValue = supportsDockToRight ? 0.55 : 0.25;
378 m_dockBottomButton = adoptNS(createDockButton(@"DockBottomLegacy"));
379 m_dockRightButton = adoptNS(createDockButton(@"DockRightLegacy"));
381 m_dockRightButton.get().alphaValue = supportsDockToRight ? 1 : 0.5;
384 m_dockBottomButton.get().target = m_inspectorProxyObjCAdapter.get();
385 m_dockBottomButton.get().action = @selector(attachBottom:);
387 m_dockRightButton.get().target = m_inspectorProxyObjCAdapter.get();
388 m_dockRightButton.get().action = @selector(attachRight:);
389 m_dockRightButton.get().enabled = supportsDockToRight;
391 // Store the dock buttons on the window too so it can check its visibility.
392 window->_dockBottomButton = m_dockBottomButton;
393 window->_dockRightButton = m_dockRightButton;
395 // Get the frame view, the superview of the content view, and its frame.
396 // This will be the superview of the dock button too.
397 NSView *frameView = contentView.superview;
398 NSRect frameViewBounds = frameView.bounds;
399 NSSize dockButtonSize = m_dockBottomButton.get().frame.size;
401 ASSERT(!frameView.isFlipped);
403 // Position the dock button in the corner to match where the full screen button is normally.
404 NSPoint dockButtonOrigin;
405 dockButtonOrigin.x = NSMaxX(frameViewBounds) - dockButtonSize.width - dockButtonMargin;
406 dockButtonOrigin.y = NSMaxY(frameViewBounds) - dockButtonSize.height - dockButtonMargin;
407 m_dockRightButton.get().frameOrigin = dockButtonOrigin;
409 dockButtonOrigin.x -= dockButtonSize.width + dockButtonSpacing;
410 m_dockBottomButton.get().frameOrigin = dockButtonOrigin;
412 if ([frameView respondsToSelector:@selector(_addKnownSubview:)]) {
413 [frameView _addKnownSubview:m_dockBottomButton.get()];
414 [frameView _addKnownSubview:m_dockRightButton.get()];
416 [frameView addSubview:m_dockBottomButton.get()];
417 [frameView addSubview:m_dockRightButton.get()];
420 // Hide the dock buttons if we can't attach.
421 m_dockBottomButton.get().hidden = !canAttach();
422 m_dockRightButton.get().hidden = !canAttach();
424 [m_inspectorView setFrame:[contentView bounds]];
425 [m_inspectorView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
426 [contentView addSubview:m_inspectorView.get()];
428 // Center the window if the saved frame was empty.
429 if (NSIsEmptyRect(savedWindowFrame))
432 updateInspectorWindowTitle();
435 void WebInspectorProxy::updateInspectorWindowTitle() const
437 if (!m_inspectorWindow)
440 NSString *title = [NSString stringWithFormat:WEB_UI_STRING("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_urlString];
441 [m_inspectorWindow setTitle:title];
444 WebPageProxy* WebInspectorProxy::platformCreateInspectorPage()
450 if (m_inspectorView) {
451 ASSERT(m_inspectorProxyObjCAdapter);
452 return toImpl(m_inspectorView.get().pageRef);
455 ASSERT(!m_inspectorView);
456 ASSERT(!m_inspectorProxyObjCAdapter);
460 NSRect inspectedViewFrame = m_page->wkView().frame;
462 switch (m_attachmentSide) {
463 case AttachmentSideBottom:
464 initialRect = NSMakeRect(0, 0, NSWidth(inspectedViewFrame), inspectorPageGroup()->preferences().inspectorAttachedHeight());
466 case AttachmentSideRight:
467 initialRect = NSMakeRect(0, 0, inspectorPageGroup()->preferences().inspectorAttachedWidth(), NSHeight(inspectedViewFrame));
471 initialRect = NSMakeRect(0, 0, initialWindowWidth, initialWindowHeight);
473 NSString *windowFrameString = page()->pageGroup().preferences().inspectorWindowFrame();
474 NSRect windowFrame = NSRectFromString(windowFrameString);
475 if (!NSIsEmptyRect(windowFrame))
476 initialRect = [NSWindow contentRectForFrameRect:windowFrame styleMask:windowStyleMask];
479 m_inspectorView = adoptNS([[WKWebInspectorWKView alloc] initWithFrame:initialRect contextRef:toAPI(&inspectorContext()) pageGroupRef:toAPI(inspectorPageGroup()) relatedToPage:nullptr]);
480 ASSERT(m_inspectorView);
482 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
483 [m_inspectorView setDrawsBackground:NO];
486 m_inspectorProxyObjCAdapter = adoptNS([[WKWebInspectorProxyObjCAdapter alloc] initWithWebInspectorProxy:this]);
487 ASSERT(m_inspectorProxyObjCAdapter);
489 [m_inspectorView setInspectorProxyObjCAdapter:m_inspectorProxyObjCAdapter.get()];
491 WebPageProxy* inspectorPage = toImpl(m_inspectorView.get().pageRef);
492 ASSERT(inspectorPage);
494 WKPageUIClientV2 uiClient = {
496 0, // createNewPage_deprecatedForUseWithV0
502 0, // runJavaScriptAlert
503 0, // runJavaScriptConfirm
504 0, // runJavaScriptPrompt
506 0, // mouseDidMoveOverElement_deprecatedForUseWithV0
507 0, // missingPluginButtonClicked_deprecatedForUseWithV0
508 0, // didNotHandleKeyEvent
509 0, // didNotHandleWheelEvent
510 0, // areToolbarsVisible
511 0, // setToolbarsVisible
512 0, // isMenuBarVisible
513 0, // setMenuBarVisible
514 0, // isStatusBarVisible
515 0, // setStatusBarVisible
520 0, // runBeforeUnloadConfirmPanel
523 exceededDatabaseQuota,
525 0, // decidePolicyForGeolocationPermissionRequest
533 0, // saveDataToFileInDownloadsFolder
534 0, // shouldInterruptJavaScript
536 0, // mouseDidMoveOverElement
537 0, // decidePolicyForNotificationPermissionRequest
538 0, // unavailablePluginButtonClicked_deprecatedForUseWithV1
539 0, // showColorPicker
540 0, // hideColorPicker
541 0, // unavailablePluginButtonClicked
544 WKPageSetPageUIClient(toAPI(inspectorPage), &uiClient.base);
546 return inspectorPage;
549 void WebInspectorProxy::platformOpen()
554 createInspectorWindow();
556 platformBringToFront();
559 void WebInspectorProxy::platformDidClose()
561 if (m_inspectorWindow) {
562 [m_inspectorWindow setDelegate:nil];
563 [m_inspectorWindow orderOut:nil];
564 m_inspectorWindow = nil;
567 m_closeTimer.startOneShot(webViewCloseTimeout);
570 void WebInspectorProxy::platformInvalidate()
577 void WebInspectorProxy::platformHide()
584 if (m_inspectorWindow) {
585 [m_inspectorWindow setDelegate:nil];
586 [m_inspectorWindow orderOut:nil];
587 m_inspectorWindow = nil;
591 void WebInspectorProxy::platformBringToFront()
593 // If the Web Inspector is no longer in the same window as the inspected view,
594 // then we need to reopen the Inspector to get it attached to the right window.
595 // This can happen when dragging tabs to another window in Safari.
596 if (m_isAttached && m_inspectorView.get().window != m_page->wkView().window) {
601 // FIXME <rdar://problem/10937688>: this will not bring a background tab in Safari to the front, only its window.
602 [m_inspectorView.get().window makeKeyAndOrderFront:nil];
603 [m_inspectorView.get().window makeFirstResponder:m_inspectorView.get()];
606 bool WebInspectorProxy::platformIsFront()
608 // FIXME <rdar://problem/10937688>: this will not return false for a background tab in Safari, only a background window.
609 return m_isVisible && [m_inspectorView.get().window isMainWindow];
612 void WebInspectorProxy::platformAttachAvailabilityChanged(bool available)
614 m_dockBottomButton.get().hidden = !available;
615 m_dockRightButton.get().hidden = !available;
618 void WebInspectorProxy::platformInspectedURLChanged(const String& urlString)
620 m_urlString = urlString;
622 updateInspectorWindowTitle();
625 void WebInspectorProxy::platformSave(const String& suggestedURL, const String& content, bool base64Encoded, bool forceSaveDialog)
627 ASSERT(!suggestedURL.isEmpty());
629 NSURL *platformURL = m_suggestedToActualURLMap.get(suggestedURL).get();
631 platformURL = [NSURL URLWithString:suggestedURL];
632 // The user must confirm new filenames before we can save to them.
633 forceSaveDialog = true;
640 // Necessary for the block below.
641 String suggestedURLCopy = suggestedURL;
642 String contentCopy = content;
644 auto saveToURL = ^(NSURL *actualURL) {
647 m_suggestedToActualURLMap.set(suggestedURLCopy, actualURL);
651 if (!base64Decode(contentCopy, out, Base64FailOnInvalidCharacterOrExcessPadding))
653 RetainPtr<NSData> dataContent = adoptNS([[NSData alloc] initWithBytes:out.data() length:out.size()]);
654 [dataContent writeToURL:actualURL atomically:YES];
656 [contentCopy writeToURL:actualURL atomically:YES encoding:NSUTF8StringEncoding error:NULL];
658 m_inspectorPage->process().send(Messages::WebInspectorUI::DidSave([actualURL absoluteString]), m_inspectorPage->pageID());
661 if (!forceSaveDialog) {
662 saveToURL(platformURL);
666 NSSavePanel *panel = [NSSavePanel savePanel];
667 panel.nameFieldStringValue = platformURL.lastPathComponent;
668 panel.directoryURL = [platformURL URLByDeletingLastPathComponent];
670 [panel beginSheetModalForWindow:m_inspectorWindow.get() completionHandler:^(NSInteger result) {
671 if (result == NSFileHandlingPanelCancelButton)
673 ASSERT(result == NSFileHandlingPanelOKButton);
674 saveToURL(panel.URL);
678 void WebInspectorProxy::platformAppend(const String& suggestedURL, const String& content)
680 ASSERT(!suggestedURL.isEmpty());
682 RetainPtr<NSURL> actualURL = m_suggestedToActualURLMap.get(suggestedURL);
683 // Do not append unless the user has already confirmed this filename in save().
687 NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:actualURL.get() error:NULL];
688 [handle seekToEndOfFile];
689 [handle writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
692 m_inspectorPage->process().send(Messages::WebInspectorUI::DidAppend([actualURL absoluteString]), m_inspectorPage->pageID());
695 void WebInspectorProxy::windowFrameDidChange()
697 ASSERT(!m_isAttached);
699 ASSERT(m_inspectorWindow);
701 if (m_isAttached || !m_isVisible || !m_inspectorWindow)
704 NSString *frameString = NSStringFromRect([m_inspectorWindow frame]);
705 page()->pageGroup().preferences().setInspectorWindowFrame(frameString);
708 void WebInspectorProxy::inspectedViewFrameDidChange(CGFloat currentDimension)
710 if (!m_isAttached || !m_isVisible)
713 WKView *inspectedView = m_page->wkView();
714 NSRect inspectedViewFrame = [inspectedView frame];
715 NSRect inspectorFrame = NSZeroRect;
716 NSRect parentBounds = [[inspectedView superview] bounds];
717 CGFloat inspectedViewTop = NSMaxY(inspectedViewFrame);
719 switch (m_attachmentSide) {
720 case AttachmentSideBottom: {
721 if (!currentDimension)
722 currentDimension = NSHeight([m_inspectorView frame]);
724 CGFloat parentHeight = NSHeight(parentBounds);
725 CGFloat inspectorHeight = InspectorFrontendClientLocal::constrainedAttachedWindowHeight(currentDimension, parentHeight);
727 // Preserve the top position of the inspected view so banners in Safari still work.
728 inspectedViewFrame = NSMakeRect(0, inspectorHeight, NSWidth(parentBounds), inspectedViewTop - inspectorHeight);
729 inspectorFrame = NSMakeRect(0, 0, NSWidth(inspectedViewFrame), inspectorHeight);
733 case AttachmentSideRight: {
734 if (!currentDimension)
735 currentDimension = NSWidth([m_inspectorView frame]);
737 CGFloat parentWidth = NSWidth(parentBounds);
738 CGFloat inspectorWidth = InspectorFrontendClientLocal::constrainedAttachedWindowWidth(currentDimension, parentWidth);
740 // Preserve the top position of the inspected view so banners in Safari still work. But don't use that
741 // top position for the inspector view since the banners only stretch as wide as the the inspected view.
742 inspectedViewFrame = NSMakeRect(0, 0, parentWidth - inspectorWidth, inspectedViewTop);
743 inspectorFrame = NSMakeRect(parentWidth - inspectorWidth, 0, inspectorWidth, NSHeight(parentBounds) - inspectedView._topContentInset);
748 if (NSEqualRects([m_inspectorView frame], inspectorFrame) && NSEqualRects([inspectedView frame], inspectedViewFrame))
751 [m_inspectorProxyObjCAdapter ignoreNextInspectedViewFrameDidChange];
753 // Disable screen updates to make sure the layers for both views resize in sync.
754 [[m_inspectorView window] disableScreenUpdatesUntilFlush];
756 [m_inspectorView setFrame:inspectorFrame];
757 [inspectedView setFrame:inspectedViewFrame];
760 unsigned WebInspectorProxy::platformInspectedWindowHeight()
762 WKView *inspectedView = m_page->wkView();
763 NSRect inspectedViewRect = [inspectedView frame];
764 return static_cast<unsigned>(inspectedViewRect.size.height);
767 unsigned WebInspectorProxy::platformInspectedWindowWidth()
769 WKView *inspectedView = m_page->wkView();
770 NSRect inspectedViewRect = [inspectedView frame];
771 return static_cast<unsigned>(inspectedViewRect.size.width);
774 void WebInspectorProxy::platformAttach()
776 WKView *inspectedView = m_page->wkView();
777 [[NSNotificationCenter defaultCenter] addObserver:m_inspectorProxyObjCAdapter.get() selector:@selector(inspectedViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:inspectedView];
779 if (m_inspectorWindow) {
780 [m_inspectorWindow setDelegate:nil];
781 [m_inspectorWindow orderOut:nil];
782 m_inspectorWindow = nil;
785 [m_inspectorView removeFromSuperview];
787 [m_inspectorView setAutoresizingMask:NSViewWidthSizable | NSViewMaxYMargin];
789 CGFloat currentDimension;
791 switch (m_attachmentSide) {
792 case AttachmentSideBottom:
793 currentDimension = inspectorPageGroup()->preferences().inspectorAttachedHeight();
795 case AttachmentSideRight:
796 currentDimension = inspectorPageGroup()->preferences().inspectorAttachedWidth();
800 inspectedViewFrameDidChange(currentDimension);
802 [[inspectedView superview] addSubview:m_inspectorView.get() positioned:NSWindowBelow relativeTo:inspectedView];
804 [[inspectedView window] makeFirstResponder:m_inspectorView.get()];
807 void WebInspectorProxy::platformDetach()
809 WKView *inspectedView = m_page->wkView();
810 [[NSNotificationCenter defaultCenter] removeObserver:m_inspectorProxyObjCAdapter.get() name:NSViewFrameDidChangeNotification object:inspectedView];
812 [m_inspectorView removeFromSuperview];
814 // Make sure that we size the inspected view's frame after detaching so that it takes up the space that the
815 // attached inspector used to. Preserve the top position of the inspected view so banners in Safari still work.
817 inspectedView.frame = NSMakeRect(0, 0, NSWidth(inspectedView.superview.bounds), NSMaxY(inspectedView.frame));
819 // Return early if we are not visible. This means the inspector was closed while attached
820 // and we should not create and show the inspector window.
824 createInspectorWindow();
826 platformBringToFront();
829 void WebInspectorProxy::platformSetAttachedWindowHeight(unsigned height)
834 inspectedViewFrameDidChange(height);
837 void WebInspectorProxy::platformSetAttachedWindowWidth(unsigned width)
842 inspectedViewFrameDidChange(width);
845 void WebInspectorProxy::platformSetToolbarHeight(unsigned height)
847 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
848 [m_inspectorWindow setContentBorderThickness:height forEdge:NSMaxYEdge];
852 String WebInspectorProxy::inspectorPageURL() const
854 // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
855 WebInspectorUILibrary();
857 NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Main" ofType:@"html"];
858 ASSERT([path length]);
860 return [[NSURL fileURLWithPath:path] absoluteString];
863 String WebInspectorProxy::inspectorTestPageURL() const
865 // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
866 WebInspectorUILibrary();
868 NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Test" ofType:@"html"];
870 // We might not have a Test.html in Production builds.
874 return [[NSURL fileURLWithPath:path] absoluteString];
877 String WebInspectorProxy::inspectorBaseURL() const
879 // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
880 WebInspectorUILibrary();
882 NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] resourcePath];
883 ASSERT([path length]);
885 return [[NSURL fileURLWithPath:path] absoluteString];
888 } // namespace WebKit
890 #endif // PLATFORM(MAC) && ENABLE(INSPECTOR)