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