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