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