Web Inspector: InspectorController should support multiple frontend channels
[WebKit-https.git] / Source / WebKit / mac / WebCoreSupport / WebInspectorClient.mm
1 /*
2  * Copyright (C) 2006, 2007, 2008 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "WebInspectorClient.h"
30
31 #import "DOMNodeInternal.h"
32 #import "WebDelegateImplementationCaching.h"
33 #import "WebFrameInternal.h"
34 #import "WebFrameView.h"
35 #import "WebInspector.h"
36 #import "WebInspectorFrontend.h"
37 #import "WebInspectorPrivate.h"
38 #import "WebLocalizableStringsInternal.h"
39 #import "WebNodeHighlighter.h"
40 #import "WebPolicyDelegate.h"
41 #import "WebQuotaManager.h"
42 #import "WebSecurityOriginPrivate.h"
43 #import "WebUIDelegatePrivate.h"
44 #import "WebViewInternal.h"
45 #import <algorithm>
46 #import <bindings/ScriptValue.h>
47 #import <inspector/InspectorAgentBase.h>
48 #import <WebCore/InspectorController.h>
49 #import <WebCore/InspectorFrontendClient.h>
50 #import <WebCore/MainFrame.h>
51 #import <WebCore/Page.h>
52 #import <WebCore/ScriptController.h>
53 #import <WebCore/SoftLinking.h>
54 #import <WebKitLegacy/DOMExtensions.h>
55 #import <WebKitSystemInterface.h>
56 #import <wtf/text/Base64.h>
57
58 SOFT_LINK_STAGED_FRAMEWORK(WebInspectorUI, PrivateFrameworks, A)
59
60 using namespace WebCore;
61
62 static const CGFloat minimumWindowWidth = 500;
63 static const CGFloat minimumWindowHeight = 400;
64 static const CGFloat initialWindowWidth = 1000;
65 static const CGFloat initialWindowHeight = 650;
66
67 @interface WebInspectorWindowController : NSWindowController <NSWindowDelegate, WebPolicyDelegate, WebUIDelegate> {
68 @private
69     RetainPtr<WebView> _inspectedWebView;
70     WebView *_webView;
71     WebInspectorFrontendClient* _frontendClient;
72     WebInspectorClient* _inspectorClient;
73     BOOL _attachedToInspectedWebView;
74     BOOL _shouldAttach;
75     BOOL _visible;
76     BOOL _destroyingInspectorView;
77 }
78 - (id)initWithInspectedWebView:(WebView *)webView isUnderTest:(BOOL)isUnderTest;
79 - (NSString *)inspectorPagePath;
80 - (NSString *)inspectorTestPagePath;
81 - (WebView *)webView;
82 - (void)attach;
83 - (void)detach;
84 - (BOOL)attached;
85 - (void)setFrontendClient:(WebInspectorFrontendClient*)frontendClient;
86 - (void)setInspectorClient:(WebInspectorClient*)inspectorClient;
87 - (WebInspectorClient*)inspectorClient;
88 - (void)setAttachedWindowHeight:(unsigned)height;
89 - (void)setDockingUnavailable:(BOOL)unavailable;
90 - (void)destroyInspectorView;
91 @end
92
93
94 // MARK: -
95
96 WebInspectorClient::WebInspectorClient(WebView *webView)
97     : m_webView(webView)
98     , m_highlighter(adoptNS([[WebNodeHighlighter alloc] initWithInspectedWebView:webView]))
99     , m_frontendPage(nullptr)
100 {
101 }
102
103 void WebInspectorClient::inspectorDestroyed()
104 {
105     closeInspectorFrontend();
106     delete this;
107 }
108
109 InspectorFrontendChannel* WebInspectorClient::openInspectorFrontend(InspectorController* inspectorController)
110 {
111     RetainPtr<WebInspectorWindowController> windowController = adoptNS([[WebInspectorWindowController alloc] initWithInspectedWebView:m_webView isUnderTest:inspectorController->isUnderTest()]);
112     [windowController.get() setInspectorClient:this];
113
114     m_frontendPage = core([windowController.get() webView]);
115     m_frontendClient = std::make_unique<WebInspectorFrontendClient>(m_webView, windowController.get(), inspectorController, m_frontendPage, createFrontendSettings());
116
117     RetainPtr<WebInspectorFrontend> webInspectorFrontend = adoptNS([[WebInspectorFrontend alloc] initWithFrontendClient:m_frontendClient.get()]);
118     [[m_webView inspector] setFrontend:webInspectorFrontend.get()];
119
120     m_frontendPage->inspectorController().setInspectorFrontendClient(m_frontendClient.get());
121
122     return this;
123 }
124
125 void WebInspectorClient::closeInspectorFrontend()
126 {
127     if (m_frontendClient)
128         m_frontendClient->disconnectFromBackend();
129 }
130
131 void WebInspectorClient::bringFrontendToFront()
132 {
133     ASSERT(m_frontendClient);
134     m_frontendClient->bringToFront();
135 }
136
137 void WebInspectorClient::didResizeMainFrame(Frame*)
138 {
139     if (m_frontendClient)
140         m_frontendClient->attachAvailabilityChanged(canAttach());
141 }
142
143 void WebInspectorClient::windowFullScreenDidChange()
144 {
145     if (m_frontendClient)
146         m_frontendClient->attachAvailabilityChanged(canAttach());
147 }
148
149 bool WebInspectorClient::canAttach()
150 {
151     return m_frontendClient->canAttach() && !inspectorAttachDisabled();
152 }
153
154 void WebInspectorClient::highlight()
155 {
156     [m_highlighter.get() highlight];
157 }
158
159 void WebInspectorClient::hideHighlight()
160 {
161     [m_highlighter.get() hideHighlight];
162 }
163
164 void WebInspectorClient::didSetSearchingForNode(bool enabled)
165 {
166     WebInspector *inspector = [m_webView inspector];
167
168     ASSERT(isMainThread());
169
170     if (enabled)
171         [[NSNotificationCenter defaultCenter] postNotificationName:WebInspectorDidStartSearchingForNode object:inspector];
172     else
173         [[NSNotificationCenter defaultCenter] postNotificationName:WebInspectorDidStopSearchingForNode object:inspector];
174 }
175
176 void WebInspectorClient::releaseFrontend()
177 {
178     m_frontendClient = nullptr;
179     m_frontendPage = nullptr;
180 }
181
182
183 WebInspectorFrontendClient::WebInspectorFrontendClient(WebView* inspectedWebView, WebInspectorWindowController* windowController, InspectorController* inspectorController, Page* frontendPage, std::unique_ptr<Settings> settings)
184     : InspectorFrontendClientLocal(inspectorController,  frontendPage, WTF::move(settings))
185     , m_inspectedWebView(inspectedWebView)
186     , m_windowController(windowController)
187 {
188     [windowController setFrontendClient:this];
189 }
190
191 void WebInspectorFrontendClient::attachAvailabilityChanged(bool available)
192 {
193     setDockingUnavailable(!available);
194     [m_windowController.get() setDockingUnavailable:!available];
195 }
196
197 bool WebInspectorFrontendClient::canAttach()
198 {
199     if ([[m_windowController window] styleMask] & NSFullScreenWindowMask)
200         return false;
201
202     return canAttachWindow();
203 }
204
205 void WebInspectorFrontendClient::frontendLoaded()
206 {
207     [m_windowController.get() showWindow:nil];
208     if ([m_windowController.get() attached])
209         restoreAttachedWindowHeight();
210
211     InspectorFrontendClientLocal::frontendLoaded();
212
213     WebFrame *frame = [m_inspectedWebView mainFrame];
214
215     WebFrameLoadDelegateImplementationCache* implementations = WebViewGetFrameLoadDelegateImplementations(m_inspectedWebView);
216     if (implementations->didClearInspectorWindowObjectForFrameFunc)
217         CallFrameLoadDelegate(implementations->didClearInspectorWindowObjectForFrameFunc, m_inspectedWebView,
218                               @selector(webView:didClearInspectorWindowObject:forFrame:), [frame windowObject], frame);
219
220     bool attached = [m_windowController.get() attached];
221     setAttachedWindow(attached ? DockSide::Bottom : DockSide::Undocked);
222 }
223
224 void WebInspectorFrontendClient::startWindowDrag()
225 {
226 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
227     [[m_windowController window] performWindowDragWithEvent:[NSApp currentEvent]];
228 #endif
229 }
230
231 String WebInspectorFrontendClient::localizedStringsURL()
232 {
233     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
234     WebInspectorUILibrary();
235
236     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"localizedStrings" ofType:@"js"];
237     if ([path length])
238         return [[NSURL fileURLWithPath:path] absoluteString];
239     return String();
240 }
241
242 void WebInspectorFrontendClient::bringToFront()
243 {
244     updateWindowTitle();
245
246     [m_windowController.get() showWindow:nil];
247
248     // Use the window from the WebView since m_windowController's window
249     // is not the same when the Inspector is docked.
250     WebView *webView = [m_windowController.get() webView];
251     [[webView window] makeFirstResponder:webView];
252 }
253
254 void WebInspectorFrontendClient::closeWindow()
255 {
256     [m_windowController.get() destroyInspectorView];
257 }
258
259 void WebInspectorFrontendClient::disconnectFromBackend()
260 {
261     [m_windowController.get() destroyInspectorView];
262 }
263
264 void WebInspectorFrontendClient::attachWindow(DockSide)
265 {
266     if ([m_windowController.get() attached])
267         return;
268     [m_windowController.get() attach];
269     restoreAttachedWindowHeight();
270 }
271
272 void WebInspectorFrontendClient::detachWindow()
273 {
274     [m_windowController.get() detach];
275 }
276
277 void WebInspectorFrontendClient::setAttachedWindowHeight(unsigned height)
278 {
279     [m_windowController.get() setAttachedWindowHeight:height];
280 }
281
282 void WebInspectorFrontendClient::setAttachedWindowWidth(unsigned)
283 {
284     // Dock to right is not implemented in WebKit 1.
285 }
286
287 void WebInspectorFrontendClient::setToolbarHeight(unsigned height)
288 {
289 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
290     [[m_windowController window] setContentBorderThickness:height forEdge:NSMaxYEdge];
291 #endif
292 }
293
294 void WebInspectorFrontendClient::inspectedURLChanged(const String& newURL)
295 {
296     m_inspectedURL = newURL;
297     updateWindowTitle();
298 }
299
300 void WebInspectorFrontendClient::updateWindowTitle() const
301 {
302     NSString *title = [NSString stringWithFormat:UI_STRING_INTERNAL("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_inspectedURL];
303     [[m_windowController.get() window] setTitle:title];
304 }
305
306 void WebInspectorFrontendClient::save(const String& suggestedURL, const String& content, bool base64Encoded, bool forceSaveDialog)
307 {
308     ASSERT(!suggestedURL.isEmpty());
309
310     NSURL *platformURL = m_suggestedToActualURLMap.get(suggestedURL).get();
311     if (!platformURL) {
312         platformURL = [NSURL URLWithString:suggestedURL];
313         // The user must confirm new filenames before we can save to them.
314         forceSaveDialog = true;
315     }
316
317     ASSERT(platformURL);
318     if (!platformURL)
319         return;
320
321     // Necessary for the block below.
322     String suggestedURLCopy = suggestedURL;
323     String contentCopy = content;
324
325     auto saveToURL = ^(NSURL *actualURL) {
326         ASSERT(actualURL);
327
328         m_suggestedToActualURLMap.set(suggestedURLCopy, actualURL);
329
330         if (base64Encoded) {
331             Vector<char> out;
332             if (!base64Decode(contentCopy, out, Base64FailOnInvalidCharacterOrExcessPadding))
333                 return;
334             RetainPtr<NSData> dataContent = adoptNS([[NSData alloc] initWithBytes:out.data() length:out.size()]);
335             [dataContent writeToURL:actualURL atomically:YES];
336         } else
337             [contentCopy writeToURL:actualURL atomically:YES encoding:NSUTF8StringEncoding error:NULL];
338
339         core([m_windowController webView])->mainFrame().script().executeScript([NSString stringWithFormat:@"InspectorFrontendAPI.savedURL(\"%@\")", actualURL.absoluteString]);
340     };
341
342     if (!forceSaveDialog) {
343         saveToURL(platformURL);
344         return;
345     }
346
347     NSSavePanel *panel = [NSSavePanel savePanel];
348     panel.nameFieldStringValue = platformURL.lastPathComponent;
349     panel.directoryURL = [platformURL URLByDeletingLastPathComponent];
350
351     auto completionHandler = ^(NSInteger result) {
352         if (result == NSFileHandlingPanelCancelButton)
353             return;
354         ASSERT(result == NSFileHandlingPanelOKButton);
355         saveToURL(panel.URL);
356     };
357
358     NSWindow *window = [[m_windowController webView] window];
359     if (window)
360         [panel beginSheetModalForWindow:window completionHandler:completionHandler];
361     else
362         completionHandler([panel runModal]);
363 }
364
365 void WebInspectorFrontendClient::append(const String& suggestedURL, const String& content)
366 {
367     ASSERT(!suggestedURL.isEmpty());
368
369     RetainPtr<NSURL> actualURL = m_suggestedToActualURLMap.get(suggestedURL);
370     // do not append unless the user has already confirmed this filename in save().
371     if (!actualURL)
372         return;
373
374     NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:actualURL.get() error:NULL];
375     [handle seekToEndOfFile];
376     [handle writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
377     [handle closeFile];
378
379     core([m_windowController webView])->mainFrame().script().executeScript([NSString stringWithFormat:@"InspectorFrontendAPI.appendedToURL(\"%@\")", [actualURL absoluteString]]);
380 }
381
382 // MARK: -
383
384 @implementation WebInspectorWindowController
385 - (id)init
386 {
387     if (!(self = [super initWithWindow:nil]))
388         return nil;
389
390     // Keep preferences separate from the rest of the client, making sure we are using expected preference values.
391
392     WebPreferences *preferences = [[WebPreferences alloc] init];
393     [preferences setAllowsAnimatedImages:YES];
394     [preferences setAuthorAndUserStylesEnabled:YES];
395     [preferences setAutosaves:NO];
396     [preferences setDefaultFixedFontSize:11];
397     [preferences setFixedFontFamily:@"Menlo"];
398     [preferences setJavaEnabled:NO];
399     [preferences setJavaScriptEnabled:YES];
400     [preferences setLoadsImagesAutomatically:YES];
401     [preferences setMinimumFontSize:0];
402     [preferences setMinimumLogicalFontSize:9];
403     [preferences setPlugInsEnabled:NO];
404     [preferences setTabsToLinks:NO];
405     [preferences setUserStyleSheetEnabled:NO];
406
407     _webView = [[WebView alloc] init];
408     [_webView setPreferences:preferences];
409     [_webView setProhibitsMainFrameScrolling:YES];
410     [_webView setUIDelegate:self];
411     [_webView setPolicyDelegate:self];
412
413 #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
414     [_webView setDrawsBackground:NO];
415 #endif
416
417     [preferences release];
418
419     [self setWindowFrameAutosaveName:@"Web Inspector 2"];
420     return self;
421 }
422
423 - (id)initWithInspectedWebView:(WebView *)webView isUnderTest:(BOOL)isUnderTest
424 {
425     if (!(self = [self init]))
426         return nil;
427
428     _inspectedWebView = webView;
429
430     NSString *pagePath = isUnderTest ? [self inspectorTestPagePath] : [self inspectorPagePath];
431     NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath: pagePath]];
432     [[_webView mainFrame] loadRequest:request];
433     [request release];
434
435     return self;
436 }
437
438 - (void)dealloc
439 {
440     [_webView release];
441     [super dealloc];
442 }
443
444 // MARK: -
445
446 - (NSString *)inspectorPagePath
447 {
448     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
449     WebInspectorUILibrary();
450
451     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Main" ofType:@"html"];
452     ASSERT([path length]);
453     return path;
454 }
455
456 - (NSString *)inspectorTestPagePath
457 {
458     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
459     WebInspectorUILibrary();
460
461     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Test" ofType:@"html"];
462
463     // We might not have a Test.html in Production builds.
464     if (!path)
465         return nil;
466
467     return path;
468 }
469
470 // MARK: -
471
472 - (WebView *)webView
473 {
474     return _webView;
475 }
476
477 - (NSWindow *)window
478 {
479     NSWindow *window = [super window];
480     if (window)
481         return window;
482
483 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
484     NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSFullSizeContentViewWindowMask;
485 #else
486     NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSTexturedBackgroundWindowMask;
487 #endif
488
489     window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, initialWindowWidth, initialWindowHeight) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
490     [window setDelegate:self];
491     [window setMinSize:NSMakeSize(minimumWindowWidth, minimumWindowHeight)];
492     [window setCollectionBehavior:([window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary)];
493
494 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100
495     CGFloat approximatelyHalfScreenSize = (window.screen.frame.size.width / 2) - 4;
496     CGFloat minimumFullScreenWidth = std::max<CGFloat>(636, approximatelyHalfScreenSize);
497     [window setMinFullScreenContentSize:NSMakeSize(minimumFullScreenWidth, minimumWindowHeight)];
498     [window setCollectionBehavior:([window collectionBehavior] | NSWindowCollectionBehaviorFullScreenAllowsTiling)];
499 #endif
500
501 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
502     window.titlebarAppearsTransparent = YES;
503 #else
504     [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
505     [window setContentBorderThickness:55. forEdge:NSMaxYEdge];
506 #endif
507
508     [self setWindow:window];
509     [window release];
510
511     return window;
512 }
513
514 // MARK: -
515
516 - (NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect
517 {
518     // AppKit doesn't know about our HTML toolbar, and places the sheet just a little bit too high.
519     // FIXME: It would be better to get the height of the toolbar and use it in this calculation.
520     rect.origin.y -= 1;
521     return rect;
522 }
523
524 - (BOOL)windowShouldClose:(id)sender
525 {
526     [self destroyInspectorView];
527
528     return YES;
529 }
530
531 - (void)windowDidEnterFullScreen:(NSNotification *)notification
532 {
533     _inspectorClient->windowFullScreenDidChange();
534 }
535
536 - (void)windowDidExitFullScreen:(NSNotification *)notification
537 {
538     _inspectorClient->windowFullScreenDidChange();
539 }
540
541 - (void)close
542 {
543     if (!_visible)
544         return;
545
546     _visible = NO;
547
548     if (_attachedToInspectedWebView) {
549         if ([_inspectedWebView.get() _isClosed])
550             return;
551
552         [_webView removeFromSuperview];
553
554         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
555         NSRect frameViewRect = [frameView frame];
556
557         // Setting the height based on the previous height is done to work with
558         // Safari's find banner. This assumes the previous height is the Y origin.
559         frameViewRect.size.height += NSMinY(frameViewRect);
560         frameViewRect.origin.y = 0.0;
561
562         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
563         [frameView setFrame:frameViewRect];
564
565         [_inspectedWebView.get() displayIfNeeded];
566     } else
567         [super close];
568 }
569
570 - (IBAction)attachWindow:(id)sender
571 {
572     _frontendClient->attachWindow(InspectorFrontendClient::DockSide::Bottom);
573 }
574
575 - (IBAction)showWindow:(id)sender
576 {
577     if (_visible) {
578         if (!_attachedToInspectedWebView)
579             [super showWindow:sender]; // call super so the window will be ordered front if needed
580         return;
581     }
582
583     _visible = YES;
584
585     _shouldAttach = _inspectorClient->inspectorStartsAttached() && _frontendClient->canAttach();
586
587     if (_shouldAttach) {
588         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
589
590         [_webView removeFromSuperview];
591         [_inspectedWebView.get() addSubview:_webView positioned:NSWindowBelow relativeTo:(NSView *)frameView];
592         [[_inspectedWebView.get() window] makeFirstResponder:_webView];
593
594         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)];
595         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin)];
596
597         _attachedToInspectedWebView = YES;
598     } else {
599         _attachedToInspectedWebView = NO;
600
601         NSView *contentView = [[self window] contentView];
602         [_webView setFrame:[contentView frame]];
603         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
604         [_webView removeFromSuperview];
605         [contentView addSubview:_webView];
606
607         [super showWindow:nil];
608     }
609 }
610
611 // MARK: -
612
613 - (void)attach
614 {
615     if (_attachedToInspectedWebView)
616         return;
617
618     _inspectorClient->setInspectorStartsAttached(true);
619     _frontendClient->setAttachedWindow(InspectorFrontendClient::DockSide::Bottom);
620
621     [self close];
622     [self showWindow:nil];
623 }
624
625 - (void)detach
626 {
627     if (!_attachedToInspectedWebView)
628         return;
629
630     _inspectorClient->setInspectorStartsAttached(false);
631     _frontendClient->setAttachedWindow(InspectorFrontendClient::DockSide::Undocked);
632
633     [self close];
634     [self showWindow:nil];
635 }
636
637 - (BOOL)attached
638 {
639     return _attachedToInspectedWebView;
640 }
641
642 - (void)setFrontendClient:(WebInspectorFrontendClient*)frontendClient
643 {
644     _frontendClient = frontendClient;
645 }
646
647 - (void)setInspectorClient:(WebInspectorClient*)inspectorClient
648 {
649     _inspectorClient = inspectorClient;
650 }
651
652 - (WebInspectorClient*)inspectorClient
653 {
654     return _inspectorClient;
655 }
656
657 - (void)setAttachedWindowHeight:(unsigned)height
658 {
659     if (!_attachedToInspectedWebView)
660         return;
661
662     WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
663     NSRect frameViewRect = [frameView frame];
664
665     // Setting the height based on the difference is done to work with
666     // Safari's find banner. This assumes the previous height is the Y origin.
667     CGFloat heightDifference = (NSMinY(frameViewRect) - height);
668     frameViewRect.size.height += heightDifference;
669     frameViewRect.origin.y = height;
670
671     [_webView setFrame:NSMakeRect(0.0, 0.0, NSWidth(frameViewRect), height)];
672     [frameView setFrame:frameViewRect];
673 }
674
675 - (void)setDockingUnavailable:(BOOL)unavailable
676 {
677     // Do nothing.
678 }
679
680 - (void)destroyInspectorView
681 {
682     RetainPtr<WebInspectorWindowController> protect(self);
683
684     [[_inspectedWebView.get() inspector] releaseFrontend];
685     _inspectorClient->releaseFrontend();
686
687     if (_destroyingInspectorView)
688         return;
689     _destroyingInspectorView = YES;
690
691     if (_attachedToInspectedWebView)
692         [self close];
693
694     _visible = NO;
695
696     if (Page* inspectedPage = [_inspectedWebView.get() page]) {
697         inspectedPage->inspectorController().setInspectorFrontendClient(nullptr);
698         inspectedPage->inspectorController().disconnectFrontend(_inspectorClient);
699     }
700
701     [_webView close];
702 }
703
704 // MARK: -
705 // MARK: UI delegate
706
707 - (void)webView:(WebView *)sender runOpenPanelForFileButtonWithResultListener:(id<WebOpenPanelResultListener>)resultListener allowMultipleFiles:(BOOL)allowMultipleFiles
708 {
709     NSOpenPanel *panel = [NSOpenPanel openPanel];
710     panel.canChooseDirectories = NO;
711     panel.canChooseFiles = YES;
712     panel.allowsMultipleSelection = allowMultipleFiles;
713
714     auto completionHandler = ^(NSInteger result) {
715         if (result == NSFileHandlingPanelCancelButton) {
716             [resultListener cancel];
717             return;
718         }
719         ASSERT(result == NSFileHandlingPanelOKButton);
720
721         NSArray *URLs = panel.URLs;
722         NSMutableArray *filenames = [NSMutableArray arrayWithCapacity:URLs.count];
723         for (NSURL *URL in URLs)
724             [filenames addObject:URL.path];
725
726         [resultListener chooseFilenames:filenames];
727     };
728
729     if (_webView.window)
730         [panel beginSheetModalForWindow:_webView.window completionHandler:completionHandler];
731     else
732         completionHandler([panel runModal]);
733 }
734
735 - (void)webView:(WebView *)sender frame:(WebFrame *)frame exceededDatabaseQuotaForSecurityOrigin:(WebSecurityOrigin *)origin database:(NSString *)databaseIdentifier
736 {
737     id <WebQuotaManager> databaseQuotaManager = origin.databaseQuotaManager;
738     databaseQuotaManager.quota = std::max<unsigned long long>(5 * 1024 * 1024, databaseQuotaManager.usage * 1.25);
739 }
740
741 - (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
742 {
743     NSMutableArray *menuItems = [[NSMutableArray alloc] init];
744
745     for (NSMenuItem *item in defaultMenuItems) {
746         switch (item.tag) {
747         case WebMenuItemTagOpenLinkInNewWindow:
748         case WebMenuItemTagOpenImageInNewWindow:
749         case WebMenuItemTagOpenFrameInNewWindow:
750         case WebMenuItemTagOpenMediaInNewWindow:
751         case WebMenuItemTagDownloadLinkToDisk:
752         case WebMenuItemTagDownloadImageToDisk:
753             break;
754         default:
755             [menuItems addObject:item];
756             break;
757         }
758     }
759
760     return [menuItems autorelease];
761 }
762
763 // MARK: -
764 // MARK: Policy delegate
765
766 - (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
767 {
768     // Allow non-main frames to navigate anywhere.
769     if (frame != [webView mainFrame]) {
770         [listener use];
771         return;
772     }
773
774     // Allow loading of the main inspector file.
775     if ([[request URL] isFileURL] && [[[request URL] path] isEqualToString:[self inspectorPagePath]]) {
776         [listener use];
777         return;
778     }
779
780     // Allow loading of the test inspector file.
781     NSString *testPagePath = [self inspectorTestPagePath];
782     if (testPagePath && [[request URL] isFileURL] && [[[request URL] path] isEqualToString:testPagePath]) {
783         [listener use];
784         return;
785     }
786
787     // Prevent everything else from loading in the inspector's page.
788     [listener ignore];
789
790     // And instead load it in the inspected page.
791     [[_inspectedWebView.get() mainFrame] loadRequest:request];
792 }
793
794 // MARK: -
795 // These methods can be used by UI elements such as menu items and toolbar buttons when the inspector is the key window.
796
797 // This method is really only implemented to keep any UI elements enabled.
798 - (void)showWebInspector:(id)sender
799 {
800     [[_inspectedWebView.get() inspector] show:sender];
801 }
802
803 - (void)showErrorConsole:(id)sender
804 {
805     [[_inspectedWebView.get() inspector] showConsole:sender];
806 }
807
808 - (void)toggleDebuggingJavaScript:(id)sender
809 {
810     [[_inspectedWebView.get() inspector] toggleDebuggingJavaScript:sender];
811 }
812
813 - (void)toggleProfilingJavaScript:(id)sender
814 {
815     [[_inspectedWebView.get() inspector] toggleProfilingJavaScript:sender];
816 }
817
818 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
819 {
820     BOOL isMenuItem = [(id)item isKindOfClass:[NSMenuItem class]];
821     if ([item action] == @selector(toggleDebuggingJavaScript:) && isMenuItem) {
822         NSMenuItem *menuItem = (NSMenuItem *)item;
823         if ([[_inspectedWebView.get() inspector] isDebuggingJavaScript])
824             [menuItem setTitle:UI_STRING_INTERNAL("Stop Debugging JavaScript", "title for Stop Debugging JavaScript menu item")];
825         else
826             [menuItem setTitle:UI_STRING_INTERNAL("Start Debugging JavaScript", "title for Start Debugging JavaScript menu item")];
827     } else if ([item action] == @selector(toggleProfilingJavaScript:) && isMenuItem) {
828         NSMenuItem *menuItem = (NSMenuItem *)item;
829         if ([[_inspectedWebView.get() inspector] isProfilingJavaScript])
830             [menuItem setTitle:UI_STRING_INTERNAL("Stop Profiling JavaScript", "title for Stop Profiling JavaScript menu item")];
831         else
832             [menuItem setTitle:UI_STRING_INTERNAL("Start Profiling JavaScript", "title for Start Profiling JavaScript menu item")];
833     }
834
835     return YES;
836 }
837
838 @end