[Mac] WebInspectorUI.framework does not need to be soft-linked anymore
[WebKit-https.git] / Source / WebKit / UIProcess / mac / WebInspectorProxyMac.mm
1 /*
2  * Copyright (C) 2010-2019 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) && WK_API_ENABLED
30
31 #import "WKInspectorPrivateMac.h"
32 #import "WKInspectorViewController.h"
33 #import "WKViewInternal.h"
34 #import "WKWebViewInternal.h"
35 #import "WebInspectorUIMessages.h"
36 #import "WebPageGroup.h"
37 #import "WebPageProxy.h"
38 #import "_WKInspectorInternal.h"
39 #import "_WKInspectorWindow.h"
40 #import <SecurityInterface/SFCertificatePanel.h>
41 #import <SecurityInterface/SFCertificateView.h>
42 #import <WebCore/CertificateInfo.h>
43 #import <WebCore/InspectorFrontendClientLocal.h>
44 #import <WebCore/LocalizedStrings.h>
45 #import <wtf/text/Base64.h>
46
47 static const NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;
48
49 // The time we keep our WebView alive before closing it and its process.
50 // Reusing the WebView improves start up time for people that jump in and out of the Inspector.
51 static const Seconds webViewCloseTimeout { 1_min };
52
53 static void* kWindowContentLayoutObserverContext = &kWindowContentLayoutObserverContext;
54
55 @interface WKWebInspectorProxyObjCAdapter () <WKInspectorViewControllerDelegate>
56
57 - (instancetype)initWithWebInspectorProxy:(WebKit::WebInspectorProxy*)inspectorProxy;
58 - (void)invalidate;
59
60 @end
61
62 @implementation WKWebInspectorProxyObjCAdapter {
63     WebKit::WebInspectorProxy* _inspectorProxy;
64 }
65
66 - (WKInspectorRef)inspectorRef
67 {
68     return toAPI(_inspectorProxy);
69 }
70
71 - (_WKInspector *)inspector
72 {
73     if (_inspectorProxy)
74         return wrapper(*_inspectorProxy);
75     return nil;
76 }
77
78 - (instancetype)initWithWebInspectorProxy:(WebKit::WebInspectorProxy*)inspectorProxy
79 {
80     ASSERT_ARG(inspectorProxy, inspectorProxy);
81
82     if (!(self = [super init]))
83         return nil;
84
85     // Unretained to avoid a reference cycle.
86     _inspectorProxy = inspectorProxy;
87
88     return self;
89 }
90
91 - (void)invalidate
92 {
93     _inspectorProxy = nullptr;
94 }
95
96 - (void)windowDidMove:(NSNotification *)notification
97 {
98     if (_inspectorProxy)
99         _inspectorProxy->windowFrameDidChange();
100 }
101
102 - (void)windowDidResize:(NSNotification *)notification
103 {
104     if (_inspectorProxy)
105         _inspectorProxy->windowFrameDidChange();
106 }
107
108 - (void)windowWillClose:(NSNotification *)notification
109 {
110     if (_inspectorProxy)
111         _inspectorProxy->close();
112 }
113
114 - (void)windowDidEnterFullScreen:(NSNotification *)notification
115 {
116     if (_inspectorProxy)
117         _inspectorProxy->windowFullScreenDidChange();
118 }
119
120 - (void)windowDidExitFullScreen:(NSNotification *)notification
121 {
122     if (_inspectorProxy)
123         _inspectorProxy->windowFullScreenDidChange();
124 }
125
126 - (void)inspectedViewFrameDidChange:(NSNotification *)notification
127 {
128     // Resizing the views while inside this notification can lead to bad results when entering
129     // or exiting full screen. To avoid that we need to perform the work after a delay. We only
130     // depend on this for enforcing the height constraints, so a small delay isn't terrible. Most
131     // of the time the views will already have the correct frames because of autoresizing masks.
132
133     dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
134         if (_inspectorProxy)
135             _inspectorProxy->inspectedViewFrameDidChange();
136     });
137 }
138
139 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
140 {
141     if (context != kWindowContentLayoutObserverContext) {
142         [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
143         return;
144     }
145
146     NSWindow *window = object;
147     ASSERT([window isKindOfClass:[NSWindow class]]);
148     if (window.inLiveResize)
149         return;
150
151     dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
152         if (_inspectorProxy)
153             _inspectorProxy->inspectedViewFrameDidChange();
154     });
155 }
156
157 // MARK: WKInspectorViewControllerDelegate methods
158
159 - (void)inspectorViewControllerInspectorDidCrash:(WKInspectorViewController *)inspectorViewController
160 {
161     if (_inspectorProxy)
162         _inspectorProxy->closeForCrash();
163 }
164
165 - (BOOL)inspectorViewControllerInspectorIsUnderTest:(WKInspectorViewController *)inspectorViewController
166 {
167     return _inspectorProxy ? _inspectorProxy->isUnderTest() : false;
168 }
169
170 - (void)inspectorViewController:(WKInspectorViewController *)inspectorViewController willMoveToWindow:(NSWindow *)newWindow
171 {
172     if (_inspectorProxy)
173         _inspectorProxy->attachmentWillMoveFromWindow(inspectorViewController.webView.window);
174 }
175
176 - (void)inspectorViewControllerDidMoveToWindow:(WKInspectorViewController *)inspectorViewController
177 {
178     if (_inspectorProxy)
179         _inspectorProxy->attachmentDidMoveToWindow(inspectorViewController.webView.window);
180 }
181
182 @end
183
184 namespace WebKit {
185 using namespace WebCore;
186
187 void WebInspectorProxy::attachmentViewDidChange(NSView *oldView, NSView *newView)
188 {
189     [[NSNotificationCenter defaultCenter] removeObserver:m_objCAdapter.get() name:NSViewFrameDidChangeNotification object:oldView];
190     [[NSNotificationCenter defaultCenter] addObserver:m_objCAdapter.get() selector:@selector(inspectedViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:newView];
191
192     if (m_isAttached)
193         attach(m_attachmentSide);
194 }
195
196 void WebInspectorProxy::attachmentWillMoveFromWindow(NSWindow *oldWindow)
197 {
198     if (m_isObservingContentLayoutRect) {
199         m_isObservingContentLayoutRect = false;
200         [oldWindow removeObserver:m_objCAdapter.get() forKeyPath:@"contentLayoutRect" context:kWindowContentLayoutObserverContext];
201     }
202 }
203
204 void WebInspectorProxy::attachmentDidMoveToWindow(NSWindow *newWindow)
205 {
206     if (m_isAttached && !!newWindow) {
207         m_isObservingContentLayoutRect = true;
208         [newWindow addObserver:m_objCAdapter.get() forKeyPath:@"contentLayoutRect" options:0 context:kWindowContentLayoutObserverContext];
209         inspectedViewFrameDidChange();
210     }
211 }
212
213 void WebInspectorProxy::updateInspectorWindowTitle() const
214 {
215     if (!m_inspectorWindow)
216         return;
217
218     unsigned level = inspectionLevel();
219     if (level > 1) {
220         NSString *debugTitle = [NSString stringWithFormat:WEB_UI_STRING("Web Inspector [%d] — %@", "Web Inspector window title when inspecting Web Inspector"), level, (NSString *)m_urlString];
221         [m_inspectorWindow setTitle:debugTitle];
222     } else {
223         NSString *title = [NSString stringWithFormat:WEB_UI_STRING("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_urlString];
224         [m_inspectorWindow setTitle:title];
225     }
226 }
227
228 RetainPtr<NSWindow> WebInspectorProxy::createFrontendWindow(NSRect savedWindowFrame, InspectionTargetType targetType)
229 {
230     NSRect windowFrame = !NSIsEmptyRect(savedWindowFrame) ? savedWindowFrame : NSMakeRect(0, 0, initialWindowWidth, initialWindowHeight);
231     auto window = adoptNS([[_WKInspectorWindow alloc] initWithContentRect:windowFrame styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO]);
232     [window setMinSize:NSMakeSize(minimumWindowWidth, minimumWindowHeight)];
233     [window setReleasedWhenClosed:NO];
234     [window setCollectionBehavior:([window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary)];
235
236     bool forRemoteTarget = targetType == InspectionTargetType::Remote;
237     [window setForRemoteTarget:forRemoteTarget];
238
239     CGFloat approximatelyHalfScreenSize = ([window screen].frame.size.width / 2) - 4;
240     CGFloat minimumFullScreenWidth = std::max<CGFloat>(636, approximatelyHalfScreenSize);
241     [window setMinFullScreenContentSize:NSMakeSize(minimumFullScreenWidth, minimumWindowHeight)];
242     [window setCollectionBehavior:([window collectionBehavior] | NSWindowCollectionBehaviorFullScreenAllowsTiling)];
243
244     [window setTitlebarAppearsTransparent:YES];
245
246     // Center the window if the saved frame was empty.
247     if (NSIsEmptyRect(savedWindowFrame))
248         [window center];
249
250     return window;
251 }
252
253 WebPageProxy* WebInspectorProxy::platformCreateFrontendPage()
254 {
255     ASSERT(inspectedPage());
256     ASSERT(!m_inspectorPage);
257
258     m_closeFrontendAfterInactivityTimer.stop();
259
260     if (m_inspectorViewController) {
261         ASSERT(m_objCAdapter);
262         return [m_inspectorViewController webView]->_page.get();
263     }
264
265     m_objCAdapter = adoptNS([[WKWebInspectorProxyObjCAdapter alloc] initWithWebInspectorProxy:this]);
266     NSView *inspectedView = inspectedPage()->inspectorAttachmentView();
267     [[NSNotificationCenter defaultCenter] addObserver:m_objCAdapter.get() selector:@selector(inspectedViewFrameDidChange:) name:NSViewFrameDidChangeNotification object:inspectedView];
268
269     m_inspectorViewController = adoptNS([[WKInspectorViewController alloc] initWithInspectedPage:inspectedPage()]);
270     [m_inspectorViewController.get() setDelegate:m_objCAdapter.get()];
271
272     WebPageProxy *inspectorPage = [m_inspectorViewController webView]->_page.get();
273     ASSERT(inspectorPage);
274
275     return inspectorPage;
276 }
277
278 void WebInspectorProxy::platformCreateFrontendWindow()
279 {
280     ASSERT(!m_inspectorWindow);
281
282     NSString *savedWindowFrameString = inspectedPage()->pageGroup().preferences().inspectorWindowFrame();
283     NSRect savedWindowFrame = NSRectFromString(savedWindowFrameString);
284
285     m_inspectorWindow = WebInspectorProxy::createFrontendWindow(savedWindowFrame, InspectionTargetType::Local);
286     [m_inspectorWindow setDelegate:m_objCAdapter.get()];
287
288     WKWebView *inspectorView = [m_inspectorViewController webView];
289     NSView *contentView = [m_inspectorWindow contentView];
290     inspectorView.frame = [contentView bounds];
291     [contentView addSubview:inspectorView];
292
293     updateInspectorWindowTitle();
294 }
295
296 void WebInspectorProxy::closeFrontendPage()
297 {
298     ASSERT(!m_isAttached || !m_inspectorWindow);
299
300     if (m_inspectorViewController) {
301         [m_inspectorViewController.get() setDelegate:nil];
302         m_inspectorViewController = nil;
303     }
304
305     if (m_objCAdapter) {
306         [[NSNotificationCenter defaultCenter] removeObserver:m_objCAdapter.get()];
307
308         [m_objCAdapter invalidate];
309         m_objCAdapter = nil;
310     }
311 }
312
313 void WebInspectorProxy::closeFrontendAfterInactivityTimerFired()
314 {
315     closeFrontendPage();
316 }
317
318 void WebInspectorProxy::platformCloseFrontendPageAndWindow()
319 {
320     if (m_inspectorWindow) {
321         [m_inspectorWindow setDelegate:nil];
322         [m_inspectorWindow close];
323         m_inspectorWindow = nil;
324     }
325
326     m_closeFrontendAfterInactivityTimer.startOneShot(webViewCloseTimeout);
327 }
328
329 void WebInspectorProxy::platformDidCloseForCrash()
330 {
331     m_closeFrontendAfterInactivityTimer.stop();
332
333     closeFrontendPage();
334 }
335
336 void WebInspectorProxy::platformInvalidate()
337 {
338     m_closeFrontendAfterInactivityTimer.stop();
339
340     closeFrontendPage();
341 }
342
343 void WebInspectorProxy::platformHide()
344 {
345     if (m_isAttached) {
346         platformDetach();
347         return;
348     }
349
350     if (m_inspectorWindow) {
351         [m_inspectorWindow setDelegate:nil];
352         [m_inspectorWindow close];
353         m_inspectorWindow = nil;
354     }
355 }
356
357 void WebInspectorProxy::platformBringToFront()
358 {
359     // If the Web Inspector is no longer in the same window as the inspected view,
360     // then we need to reopen the Inspector to get it attached to the right window.
361     // This can happen when dragging tabs to another window in Safari.
362     if (m_isAttached && [m_inspectorViewController webView].window != inspectedPage()->platformWindow()) {
363         open();
364         return;
365     }
366
367     // FIXME <rdar://problem/10937688>: this will not bring a background tab in Safari to the front, only its window.
368     [[m_inspectorViewController webView].window makeKeyAndOrderFront:nil];
369     [[m_inspectorViewController webView].window makeFirstResponder:[m_inspectorViewController webView]];
370 }
371
372 void WebInspectorProxy::platformBringInspectedPageToFront()
373 {
374     [inspectedPage()->platformWindow() makeKeyAndOrderFront:nil];
375 }
376
377 bool WebInspectorProxy::platformIsFront()
378 {
379     // FIXME <rdar://problem/10937688>: this will not return false for a background tab in Safari, only a background window.
380     return m_isVisible && [m_inspectorViewController webView].window.isMainWindow;
381 }
382
383 bool WebInspectorProxy::platformCanAttach(bool webProcessCanAttach)
384 {
385     NSView *inspectedView = inspectedPage()->inspectorAttachmentView();
386     if ([WKInspectorViewController viewIsInspectorWebView:inspectedView])
387         return webProcessCanAttach;
388
389     static const float minimumAttachedHeight = 250;
390     static const float maximumAttachedHeightRatio = 0.75;
391     static const float minimumAttachedWidth = 500;
392
393     NSRect inspectedViewFrame = inspectedView.frame;
394
395     float maximumAttachedHeight = NSHeight(inspectedViewFrame) * maximumAttachedHeightRatio;
396     return minimumAttachedHeight <= maximumAttachedHeight && minimumAttachedWidth <= NSWidth(inspectedViewFrame);
397 }
398
399 void WebInspectorProxy::platformAttachAvailabilityChanged(bool available)
400 {
401     // Do nothing.
402 }
403
404 void WebInspectorProxy::platformInspectedURLChanged(const String& urlString)
405 {
406     m_urlString = urlString;
407
408     updateInspectorWindowTitle();
409 }
410
411 void WebInspectorProxy::platformShowCertificate(const CertificateInfo& certificateInfo)
412 {
413     ASSERT(!certificateInfo.isEmpty());
414
415     RetainPtr<SFCertificatePanel> certificatePanel = adoptNS([[SFCertificatePanel alloc] init]);
416
417     NSWindow *window;
418     if (m_inspectorWindow)
419         window = m_inspectorWindow.get();
420     else
421         window = [[m_inspectorViewController webView] window];
422
423     if (!window)
424         window = [NSApp keyWindow];
425
426 #if HAVE(SEC_TRUST_SERIALIZATION)
427     [certificatePanel beginSheetForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:nullptr trust:certificateInfo.trust() showGroup:YES];
428 #else
429     [certificatePanel beginSheetForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:nullptr certificates:(NSArray *)certificateInfo.certificateChain() showGroup:YES];
430 #endif
431
432     // This must be called after the trust panel has been displayed, because the certificateView doesn't exist beforehand.
433     SFCertificateView *certificateView = [certificatePanel certificateView];
434     [certificateView setDisplayTrust:YES];
435     [certificateView setEditableTrust:NO];
436     [certificateView setDisplayDetails:YES];
437     [certificateView setDetailsDisclosed:YES];
438 }
439
440 void WebInspectorProxy::platformSave(const String& suggestedURL, const String& content, bool base64Encoded, bool forceSaveDialog)
441 {
442     ASSERT(!suggestedURL.isEmpty());
443     
444     NSURL *platformURL = m_suggestedToActualURLMap.get(suggestedURL).get();
445     if (!platformURL) {
446         platformURL = [NSURL URLWithString:suggestedURL];
447         // The user must confirm new filenames before we can save to them.
448         forceSaveDialog = true;
449     }
450     
451     ASSERT(platformURL);
452     if (!platformURL)
453         return;
454
455     // Necessary for the block below.
456     String suggestedURLCopy = suggestedURL;
457     String contentCopy = content;
458
459     auto saveToURL = ^(NSURL *actualURL) {
460         ASSERT(actualURL);
461
462         m_suggestedToActualURLMap.set(suggestedURLCopy, actualURL);
463
464         if (base64Encoded) {
465             Vector<char> out;
466             if (!base64Decode(contentCopy, out, Base64ValidatePadding))
467                 return;
468             RetainPtr<NSData> dataContent = adoptNS([[NSData alloc] initWithBytes:out.data() length:out.size()]);
469             [dataContent writeToURL:actualURL atomically:YES];
470         } else
471             [contentCopy writeToURL:actualURL atomically:YES encoding:NSUTF8StringEncoding error:NULL];
472
473         m_inspectorPage->process().send(Messages::WebInspectorUI::DidSave([actualURL absoluteString]), m_inspectorPage->pageID());
474     };
475
476     if (!forceSaveDialog) {
477         saveToURL(platformURL);
478         return;
479     }
480
481     NSSavePanel *panel = [NSSavePanel savePanel];
482     panel.nameFieldStringValue = platformURL.lastPathComponent;
483
484     // If we have a file URL we've already saved this file to a path and
485     // can provide a good directory to show. Otherwise, use the system's
486     // default behavior for the initial directory to show in the dialog.
487     if (platformURL.isFileURL)
488         panel.directoryURL = [platformURL URLByDeletingLastPathComponent];
489
490     auto completionHandler = ^(NSInteger result) {
491         if (result == NSModalResponseCancel)
492             return;
493         ASSERT(result == NSModalResponseOK);
494         saveToURL(panel.URL);
495     };
496
497     NSWindow *window = m_inspectorWindow ? m_inspectorWindow.get() : [NSApp keyWindow];
498     if (window)
499         [panel beginSheetModalForWindow:window completionHandler:completionHandler];
500     else
501         completionHandler([panel runModal]);
502 }
503
504 void WebInspectorProxy::platformAppend(const String& suggestedURL, const String& content)
505 {
506     ASSERT(!suggestedURL.isEmpty());
507     
508     RetainPtr<NSURL> actualURL = m_suggestedToActualURLMap.get(suggestedURL);
509     // Do not append unless the user has already confirmed this filename in save().
510     if (!actualURL)
511         return;
512
513     NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:actualURL.get() error:NULL];
514     [handle seekToEndOfFile];
515     [handle writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
516     [handle closeFile];
517
518     m_inspectorPage->process().send(Messages::WebInspectorUI::DidAppend([actualURL absoluteString]), m_inspectorPage->pageID());
519 }
520
521 void WebInspectorProxy::windowFrameDidChange()
522 {
523     ASSERT(!m_isAttached);
524     ASSERT(m_isVisible);
525     ASSERT(m_inspectorWindow);
526
527     if (m_isAttached || !m_isVisible || !m_inspectorWindow)
528         return;
529
530     NSString *frameString = NSStringFromRect([m_inspectorWindow frame]);
531     inspectedPage()->pageGroup().preferences().setInspectorWindowFrame(frameString);
532 }
533
534 void WebInspectorProxy::windowFullScreenDidChange()
535 {
536     ASSERT(!m_isAttached);
537     ASSERT(m_isVisible);
538     ASSERT(m_inspectorWindow);
539
540     if (m_isAttached || !m_isVisible || !m_inspectorWindow)
541         return;
542
543     attachAvailabilityChanged(platformCanAttach(canAttach()));    
544 }
545
546 void WebInspectorProxy::inspectedViewFrameDidChange(CGFloat currentDimension)
547 {
548     if (!m_isVisible)
549         return;
550
551     if (!m_isAttached) {
552         // Check if the attach availability changed. We need to do this here in case
553         // the attachment view is not the WKView.
554         attachAvailabilityChanged(platformCanAttach(canAttach()));
555         return;
556     }
557
558     NSView *inspectedView = inspectedPage()->inspectorAttachmentView();
559     WKWebView *inspectorView = [m_inspectorViewController webView];
560
561     NSRect inspectedViewFrame = inspectedView.frame;
562     NSRect oldInspectorViewFrame = inspectorView.frame;
563     NSRect newInspectorViewFrame = NSZeroRect;
564     NSRect parentBounds = inspectedView.superview.bounds;
565     CGFloat inspectedViewTop = NSMaxY(inspectedViewFrame);
566
567     switch (m_attachmentSide) {
568     case AttachmentSide::Bottom: {
569         if (!currentDimension)
570             currentDimension = NSHeight(oldInspectorViewFrame);
571
572         CGFloat parentHeight = NSHeight(parentBounds);
573         CGFloat inspectorHeight = InspectorFrontendClientLocal::constrainedAttachedWindowHeight(currentDimension, parentHeight);
574
575         // Preserve the top position of the inspected view so banners in Safari still work.
576         inspectedViewFrame = NSMakeRect(0, inspectorHeight, NSWidth(parentBounds), inspectedViewTop - inspectorHeight);
577         newInspectorViewFrame = NSMakeRect(0, 0, NSWidth(inspectedViewFrame), inspectorHeight);
578         break;
579     }
580
581     case AttachmentSide::Right: {
582         if (!currentDimension)
583             currentDimension = NSWidth(oldInspectorViewFrame);
584
585         CGFloat parentWidth = NSWidth(parentBounds);
586         CGFloat inspectorWidth = InspectorFrontendClientLocal::constrainedAttachedWindowWidth(currentDimension, parentWidth);
587
588         // Preserve the top position of the inspected view so banners in Safari still work. But don't use that
589         // top position for the inspector view since the banners only stretch as wide as the inspected view.
590         inspectedViewFrame = NSMakeRect(0, 0, parentWidth - inspectorWidth, inspectedViewTop);
591         newInspectorViewFrame = NSMakeRect(parentWidth - inspectorWidth, 0, inspectorWidth, NSHeight(parentBounds));
592
593         if (NSWindow *inspectorWindow = inspectorView.window) {
594             NSRect contentLayoutRect = [inspectedView.superview convertRect:inspectorWindow.contentLayoutRect fromView:nil];
595             newInspectorViewFrame = NSIntersectionRect(newInspectorViewFrame, contentLayoutRect);
596         }
597         break;
598     }
599
600     case AttachmentSide::Left: {
601         if (!currentDimension)
602             currentDimension = NSWidth(oldInspectorViewFrame);
603
604         CGFloat parentWidth = NSWidth(parentBounds);
605         CGFloat inspectorWidth = InspectorFrontendClientLocal::constrainedAttachedWindowWidth(currentDimension, parentWidth);
606
607         // Preserve the top position of the inspected view so banners in Safari still work. But don't use that
608         // top position for the inspector view since the banners only stretch as wide as the inspected view.
609         inspectedViewFrame = NSMakeRect(inspectorWidth, 0, parentWidth - inspectorWidth, inspectedViewTop);
610         newInspectorViewFrame = NSMakeRect(0, 0, inspectorWidth, NSHeight(parentBounds));
611
612         if (NSWindow *inspectorWindow = inspectorView.window) {
613             NSRect contentLayoutRect = [inspectedView.superview convertRect:inspectorWindow.contentLayoutRect fromView:nil];
614             newInspectorViewFrame = NSIntersectionRect(newInspectorViewFrame, contentLayoutRect);
615         }
616         break;
617     }
618     }
619
620     if (NSEqualRects(oldInspectorViewFrame, newInspectorViewFrame) && NSEqualRects([inspectedView frame], inspectedViewFrame))
621         return;
622
623     // Disable screen updates to make sure the layers for both views resize in sync.
624     [inspectorView.window disableScreenUpdatesUntilFlush];
625
626     [inspectorView setFrame:newInspectorViewFrame];
627     [inspectedView setFrame:inspectedViewFrame];
628 }
629
630 unsigned WebInspectorProxy::platformInspectedWindowHeight()
631 {
632     NSView *inspectedView = inspectedPage()->inspectorAttachmentView();
633     NSRect inspectedViewRect = [inspectedView frame];
634     return static_cast<unsigned>(inspectedViewRect.size.height);
635 }
636
637 unsigned WebInspectorProxy::platformInspectedWindowWidth()
638 {
639     NSView *inspectedView = inspectedPage()->inspectorAttachmentView();
640     NSRect inspectedViewRect = [inspectedView frame];
641     return static_cast<unsigned>(inspectedViewRect.size.width);
642 }
643
644 void WebInspectorProxy::platformAttach()
645 {
646     NSView *inspectedView = inspectedPage()->inspectorAttachmentView();
647     WKWebView *inspectorView = [m_inspectorViewController webView];
648
649     if (m_inspectorWindow) {
650         [m_inspectorWindow setDelegate:nil];
651         [m_inspectorWindow close];
652         m_inspectorWindow = nil;
653     }
654
655     [inspectorView removeFromSuperview];
656
657     CGFloat currentDimension;
658     switch (m_attachmentSide) {
659     case AttachmentSide::Bottom:
660         [inspectorView setAutoresizingMask:NSViewWidthSizable | NSViewMaxYMargin];
661         currentDimension = inspectorPagePreferences().inspectorAttachedHeight();
662         break;
663     case AttachmentSide::Right:
664         [inspectorView setAutoresizingMask:NSViewHeightSizable | NSViewMinXMargin];
665         currentDimension = inspectorPagePreferences().inspectorAttachedWidth();
666         break;
667     case AttachmentSide::Left:
668         [inspectorView setAutoresizingMask:NSViewHeightSizable | NSViewMaxXMargin];
669         currentDimension = inspectorPagePreferences().inspectorAttachedWidth();
670         break;
671     }
672
673     inspectedViewFrameDidChange(currentDimension);
674
675     [inspectedView.superview addSubview:inspectorView positioned:NSWindowBelow relativeTo:inspectedView];
676     [inspectorView.window makeFirstResponder:inspectorView];
677 }
678
679 void WebInspectorProxy::platformDetach()
680 {
681     NSView *inspectedView = inspectedPage()->inspectorAttachmentView();
682     WKWebView *inspectorView = [m_inspectorViewController webView];
683
684     [inspectorView removeFromSuperview];
685
686     [inspectorView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
687
688     // Make sure that we size the inspected view's frame after detaching so that it takes up the space that the
689     // attached inspector used to. Preserve the top position of the inspected view so banners in Safari still work.
690
691     inspectedView.frame = NSMakeRect(0, 0, NSWidth(inspectedView.superview.bounds), NSMaxY(inspectedView.frame));
692
693     // Return early if we are not visible. This means the inspector was closed while attached
694     // and we should not create and show the inspector window.
695     if (!m_isVisible)
696         return;
697
698     open();
699 }
700
701 void WebInspectorProxy::platformSetAttachedWindowHeight(unsigned height)
702 {
703     if (!m_isAttached)
704         return;
705
706     inspectedViewFrameDidChange(height);
707 }
708
709 void WebInspectorProxy::platformSetAttachedWindowWidth(unsigned width)
710 {
711     if (!m_isAttached)
712         return;
713
714     inspectedViewFrameDidChange(width);
715 }
716
717 void WebInspectorProxy::platformStartWindowDrag()
718 {
719     [m_inspectorViewController webView]->_page->startWindowDrag();
720 }
721
722 String WebInspectorProxy::inspectorPageURL()
723 {
724     NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"];
725     if (!bundle)
726         return String();
727
728     NSString *path = [bundle pathForResource:@"Main" ofType:@"html"];
729     ASSERT(path && path.length);
730     if (!path)
731         return String();
732
733     return [NSURL fileURLWithPath:path isDirectory:NO].absoluteString;
734 }
735
736 String WebInspectorProxy::inspectorTestPageURL()
737 {
738     NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"];
739     if (!bundle)
740         return String();
741
742     // We might not have a Test.html in Production builds.
743     NSString *path = [bundle pathForResource:@"Main" ofType:@"html"];
744     if (!path)
745         return String();
746
747     return [NSURL fileURLWithPath:path isDirectory:NO].absoluteString;
748 }
749
750 String WebInspectorProxy::inspectorBaseURL()
751 {
752     NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"];
753     if (!bundle)
754         return String();
755
756     NSString *path = bundle.resourcePath;
757     ASSERT(path && path.length);
758
759     return [NSURL fileURLWithPath:path isDirectory:YES].absoluteString;
760 }
761
762 } // namespace WebKit
763
764 #endif // PLATFORM(MAC) && WK_API_ENABLED