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