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