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