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