28951a48c7dc472301210aa6625e58e164697c55
[WebKit-https.git] / Source / WebKitLegacy / mac / WebCoreSupport / WebInspectorClient.mm
1 /*
2  * Copyright (C) 2006-2008, 2015 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 "WebUIDelegatePrivate.h"
44 #import "WebViewInternal.h"
45 #import <JavaScriptCore/InspectorAgentBase.h>
46 #import <SecurityInterface/SFCertificatePanel.h>
47 #import <SecurityInterface/SFCertificateView.h>
48 #import <WebCore/CertificateInfo.h>
49 #import <WebCore/Frame.h>
50 #import <WebCore/InspectorController.h>
51 #import <WebCore/InspectorFrontendClient.h>
52 #import <WebCore/Page.h>
53 #import <WebCore/ScriptController.h>
54 #import <WebKitLegacy/DOMExtensions.h>
55 #import <algorithm>
56 #import <wtf/SoftLinking.h>
57 #import <wtf/text/Base64.h>
58
59 SOFT_LINK_STAGED_FRAMEWORK(WebInspectorUI, PrivateFrameworks, A)
60
61 using namespace WebCore;
62 using namespace Inspector;
63
64 static const CGFloat minimumWindowWidth = 500;
65 static const CGFloat minimumWindowHeight = 400;
66 static const CGFloat initialWindowWidth = 1000;
67 static const CGFloat initialWindowHeight = 650;
68
69 @interface WebInspectorWindowController : NSWindowController <NSWindowDelegate, WebPolicyDelegate, WebUIDelegate> {
70 @private
71     RetainPtr<WebView> _inspectedWebView;
72     WebView* _frontendWebView;
73     WebInspectorFrontendClient* _frontendClient;
74     WebInspectorClient* _inspectorClient;
75     BOOL _attachedToInspectedWebView;
76     BOOL _shouldAttach;
77     BOOL _visible;
78     BOOL _destroyingInspectorView;
79 }
80 - (id)initWithInspectedWebView:(WebView *)inspectedWebView isUnderTest:(BOOL)isUnderTest;
81 - (NSString *)inspectorPagePath;
82 - (NSString *)inspectorTestPagePath;
83 - (WebView *)frontendWebView;
84 - (void)attach;
85 - (void)detach;
86 - (BOOL)attached;
87 - (void)setFrontendClient:(WebInspectorFrontendClient*)frontendClient;
88 - (void)setInspectorClient:(WebInspectorClient*)inspectorClient;
89 - (WebInspectorClient*)inspectorClient;
90 - (void)setAttachedWindowHeight:(unsigned)height;
91 - (void)setDockingUnavailable:(BOOL)unavailable;
92 - (void)destroyInspectorView;
93 @end
94
95
96 // MARK: -
97
98 WebInspectorClient::WebInspectorClient(WebView* inspectedWebView)
99     : m_inspectedWebView(inspectedWebView)
100     , m_highlighter(adoptNS([[WebNodeHighlighter alloc] initWithInspectedWebView:inspectedWebView]))
101 {
102 }
103
104 void WebInspectorClient::inspectedPageDestroyed()
105 {
106     delete this;
107 }
108
109 FrontendChannel* WebInspectorClient::openLocalFrontend(InspectorController* inspectedPageController)
110 {
111     RetainPtr<WebInspectorWindowController> windowController = adoptNS([[WebInspectorWindowController alloc] initWithInspectedWebView:m_inspectedWebView isUnderTest:inspectedPageController->isUnderTest()]);
112     [windowController.get() setInspectorClient:this];
113
114     m_frontendPage = core([windowController.get() frontendWebView]);
115     m_frontendClient = std::make_unique<WebInspectorFrontendClient>(m_inspectedWebView, windowController.get(), inspectedPageController, m_frontendPage, createFrontendSettings());
116
117     RetainPtr<WebInspectorFrontend> webInspectorFrontend = adoptNS([[WebInspectorFrontend alloc] initWithFrontendClient:m_frontendClient.get()]);
118     [[m_inspectedWebView inspector] setFrontend:webInspectorFrontend.get()];
119
120     m_frontendPage->inspectorController().setInspectorFrontendClient(m_frontendClient.get());
121
122     return this;
123 }
124
125 void WebInspectorClient::bringFrontendToFront()
126 {
127     ASSERT(m_frontendClient);
128     m_frontendClient->bringToFront();
129 }
130
131 void WebInspectorClient::didResizeMainFrame(Frame*)
132 {
133     if (m_frontendClient)
134         m_frontendClient->attachAvailabilityChanged(canAttach());
135 }
136
137 void WebInspectorClient::windowFullScreenDidChange()
138 {
139     if (m_frontendClient)
140         m_frontendClient->attachAvailabilityChanged(canAttach());
141 }
142
143 bool WebInspectorClient::canAttach()
144 {
145     return m_frontendClient->canAttach() && !inspectorAttachDisabled();
146 }
147
148 void WebInspectorClient::highlight()
149 {
150     [m_highlighter.get() highlight];
151 }
152
153 void WebInspectorClient::hideHighlight()
154 {
155     [m_highlighter.get() hideHighlight];
156 }
157
158 void WebInspectorClient::didSetSearchingForNode(bool enabled)
159 {
160     WebInspector *inspector = [m_inspectedWebView inspector];
161
162     ASSERT(isMainThread());
163
164     if (enabled) {
165         [[m_inspectedWebView window] makeKeyAndOrderFront:nil];
166         [[m_inspectedWebView window] makeFirstResponder:m_inspectedWebView];
167         [[NSNotificationCenter defaultCenter] postNotificationName:WebInspectorDidStartSearchingForNode object:inspector];
168     } else
169         [[NSNotificationCenter defaultCenter] postNotificationName:WebInspectorDidStopSearchingForNode object:inspector];
170 }
171
172 void WebInspectorClient::releaseFrontend()
173 {
174     m_frontendClient = nullptr;
175     m_frontendPage = nullptr;
176 }
177
178
179 WebInspectorFrontendClient::WebInspectorFrontendClient(WebView* inspectedWebView, WebInspectorWindowController* frontendWindowController, InspectorController* inspectedPageController, Page* frontendPage, std::unique_ptr<Settings> settings)
180     : InspectorFrontendClientLocal(inspectedPageController, frontendPage, WTFMove(settings))
181     , m_inspectedWebView(inspectedWebView)
182     , m_frontendWindowController(frontendWindowController)
183 {
184     [frontendWindowController setFrontendClient:this];
185 }
186
187 void WebInspectorFrontendClient::attachAvailabilityChanged(bool available)
188 {
189     setDockingUnavailable(!available);
190     [m_frontendWindowController.get() setDockingUnavailable:!available];
191 }
192
193 bool WebInspectorFrontendClient::canAttach()
194 {
195     if ([[m_frontendWindowController window] styleMask] & NSWindowStyleMaskFullScreen)
196         return false;
197
198     return canAttachWindow();
199 }
200
201 void WebInspectorFrontendClient::frontendLoaded()
202 {
203     [m_frontendWindowController.get() showWindow:nil];
204     if ([m_frontendWindowController.get() attached])
205         restoreAttachedWindowHeight();
206
207     InspectorFrontendClientLocal::frontendLoaded();
208
209     WebFrame *frame = [m_inspectedWebView mainFrame];
210
211     WebFrameLoadDelegateImplementationCache* implementations = WebViewGetFrameLoadDelegateImplementations(m_inspectedWebView);
212     if (implementations->didClearInspectorWindowObjectForFrameFunc)
213         CallFrameLoadDelegate(implementations->didClearInspectorWindowObjectForFrameFunc, m_inspectedWebView,
214                               @selector(webView:didClearInspectorWindowObject:forFrame:), [frame windowObject], frame);
215
216     bool attached = [m_frontendWindowController.get() attached];
217     setAttachedWindow(attached ? DockSide::Bottom : DockSide::Undocked);
218 }
219
220 void WebInspectorFrontendClient::startWindowDrag()
221 {
222     [[m_frontendWindowController window] performWindowDragWithEvent:[NSApp currentEvent]];
223 }
224
225 String WebInspectorFrontendClient::localizedStringsURL()
226 {
227     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
228     WebInspectorUILibrary();
229
230     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"localizedStrings" ofType:@"js"];
231     if ([path length])
232         return [[NSURL fileURLWithPath:path] absoluteString];
233     return String();
234 }
235
236 void WebInspectorFrontendClient::bringToFront()
237 {
238     updateWindowTitle();
239
240     [m_frontendWindowController.get() showWindow:nil];
241
242     // Use the window from the WebView since m_frontendWindowController's window
243     // is not the same when the Inspector is docked.
244     WebView *frontendWebView = [m_frontendWindowController.get() frontendWebView];
245     [[frontendWebView window] makeFirstResponder:frontendWebView];
246 }
247
248 void WebInspectorFrontendClient::closeWindow()
249 {
250     [m_frontendWindowController.get() destroyInspectorView];
251 }
252
253 void WebInspectorFrontendClient::reopen()
254 {
255     WebInspector* inspector = [m_inspectedWebView inspector];
256     [inspector close:nil];
257     [inspector show:nil];
258 }
259
260 void WebInspectorFrontendClient::attachWindow(DockSide)
261 {
262     if ([m_frontendWindowController.get() attached])
263         return;
264     [m_frontendWindowController.get() attach];
265     restoreAttachedWindowHeight();
266 }
267
268 void WebInspectorFrontendClient::detachWindow()
269 {
270     [m_frontendWindowController.get() detach];
271 }
272
273 void WebInspectorFrontendClient::setAttachedWindowHeight(unsigned height)
274 {
275     [m_frontendWindowController.get() setAttachedWindowHeight:height];
276 }
277
278 void WebInspectorFrontendClient::setAttachedWindowWidth(unsigned)
279 {
280     // Dock to right is not implemented in WebKit 1.
281 }
282
283 void WebInspectorFrontendClient::inspectedURLChanged(const String& newURL)
284 {
285     m_inspectedURL = newURL;
286     updateWindowTitle();
287 }
288
289 void WebInspectorFrontendClient::showCertificate(const CertificateInfo& certificateInfo)
290 {
291     ASSERT(!certificateInfo.isEmpty());
292
293     RetainPtr<SFCertificatePanel> certificatePanel = adoptNS([[SFCertificatePanel alloc] init]);
294
295     NSWindow *window = [[m_frontendWindowController frontendWebView] window];
296     if (!window)
297         window = [NSApp keyWindow];
298
299 #if HAVE(SEC_TRUST_SERIALIZATION)
300     [certificatePanel beginSheetForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:nullptr trust:certificateInfo.trust() showGroup:YES];
301 #else
302     [certificatePanel beginSheetForWindow:window modalDelegate:nil didEndSelector:NULL contextInfo:nullptr certificates:(NSArray *)certificateInfo.certificateChain() showGroup:YES];
303 #endif
304
305     // This must be called after the trust panel has been displayed, because the certificateView doesn't exist beforehand.
306     SFCertificateView *certificateView = [certificatePanel certificateView];
307     [certificateView setDisplayTrust:YES];
308     [certificateView setEditableTrust:NO];
309     [certificateView setDisplayDetails:YES];
310     [certificateView setDetailsDisclosed:YES];
311 }
312
313 void WebInspectorFrontendClient::updateWindowTitle() const
314 {
315     NSString *title = [NSString stringWithFormat:UI_STRING_INTERNAL("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_inspectedURL];
316     [[m_frontendWindowController.get() window] setTitle:title];
317 }
318
319 void WebInspectorFrontendClient::save(const String& suggestedURL, const String& content, bool base64Encoded, bool forceSaveDialog)
320 {
321     ASSERT(!suggestedURL.isEmpty());
322
323     NSURL *platformURL = m_suggestedToActualURLMap.get(suggestedURL).get();
324     if (!platformURL) {
325         platformURL = [NSURL URLWithString:suggestedURL];
326         // The user must confirm new filenames before we can save to them.
327         forceSaveDialog = true;
328     }
329
330     ASSERT(platformURL);
331     if (!platformURL)
332         return;
333
334     // Necessary for the block below.
335     String suggestedURLCopy = suggestedURL;
336     String contentCopy = content;
337
338     auto saveToURL = ^(NSURL *actualURL) {
339         ASSERT(actualURL);
340
341         m_suggestedToActualURLMap.set(suggestedURLCopy, actualURL);
342
343         if (base64Encoded) {
344             Vector<char> out;
345             if (!base64Decode(contentCopy, out, Base64ValidatePadding))
346                 return;
347             RetainPtr<NSData> dataContent = adoptNS([[NSData alloc] initWithBytes:out.data() length:out.size()]);
348             [dataContent writeToURL:actualURL atomically:YES];
349         } else
350             [contentCopy writeToURL:actualURL atomically:YES encoding:NSUTF8StringEncoding error:NULL];
351
352         core([m_frontendWindowController frontendWebView])->mainFrame().script().executeScript([NSString stringWithFormat:@"InspectorFrontendAPI.savedURL(\"%@\")", actualURL.absoluteString]);
353     };
354
355     if (!forceSaveDialog) {
356         saveToURL(platformURL);
357         return;
358     }
359
360     NSSavePanel *panel = [NSSavePanel savePanel];
361     panel.nameFieldStringValue = platformURL.lastPathComponent;
362
363     // If we have a file URL we've already saved this file to a path and
364     // can provide a good directory to show. Otherwise, use the system's
365     // default behavior for the initial directory to show in the dialog.
366     if (platformURL.isFileURL)
367         panel.directoryURL = [platformURL URLByDeletingLastPathComponent];
368
369     auto completionHandler = ^(NSInteger result) {
370         if (result == NSModalResponseCancel)
371             return;
372         ASSERT(result == NSModalResponseOK);
373         saveToURL(panel.URL);
374     };
375
376     NSWindow *frontendWindow = [[m_frontendWindowController frontendWebView] window];
377     NSWindow *window = frontendWindow ? frontendWindow : [NSApp keyWindow];
378     if (window)
379         [panel beginSheetModalForWindow:window completionHandler:completionHandler];
380     else
381         completionHandler([panel runModal]);
382 }
383
384 void WebInspectorFrontendClient::append(const String& suggestedURL, const String& content)
385 {
386     ASSERT(!suggestedURL.isEmpty());
387
388     RetainPtr<NSURL> actualURL = m_suggestedToActualURLMap.get(suggestedURL);
389     // do not append unless the user has already confirmed this filename in save().
390     if (!actualURL)
391         return;
392
393     NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:actualURL.get() error:NULL];
394     [handle seekToEndOfFile];
395     [handle writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
396     [handle closeFile];
397
398     core([m_frontendWindowController frontendWebView])->mainFrame().script().executeScript([NSString stringWithFormat:@"InspectorFrontendAPI.appendedToURL(\"%@\")", [actualURL absoluteString]]);
399 }
400
401 // MARK: -
402
403 @implementation WebInspectorWindowController
404 - (id)init
405 {
406     if (!(self = [super initWithWindow:nil]))
407         return nil;
408
409     // Keep preferences separate from the rest of the client, making sure we are using expected preference values.
410
411     WebPreferences *preferences = [[WebPreferences alloc] init];
412     [preferences setAllowsAnimatedImages:YES];
413     [preferences setAuthorAndUserStylesEnabled:YES];
414     [preferences setAutosaves:NO];
415     [preferences setDefaultFixedFontSize:11];
416     [preferences setFixedFontFamily:@"Menlo"];
417     [preferences setJavaEnabled:NO];
418     [preferences setJavaScriptEnabled:YES];
419     [preferences setLoadsImagesAutomatically:YES];
420     [preferences setMinimumFontSize:0];
421     [preferences setMinimumLogicalFontSize:9];
422     [preferences setPlugInsEnabled:NO];
423     [preferences setTabsToLinks:NO];
424     [preferences setUserStyleSheetEnabled:NO];
425     [preferences setAllowFileAccessFromFileURLs:YES];
426     [preferences setAllowUniversalAccessFromFileURLs:YES];
427     [preferences setStorageBlockingPolicy:WebAllowAllStorage];
428
429     _frontendWebView = [[WebView alloc] init];
430     [_frontendWebView setPreferences:preferences];
431     [_frontendWebView setProhibitsMainFrameScrolling:YES];
432     [_frontendWebView setUIDelegate:self];
433     [_frontendWebView setPolicyDelegate:self];
434
435     [preferences release];
436
437     [self setWindowFrameAutosaveName:@"Web Inspector 2"];
438     return self;
439 }
440
441 - (id)initWithInspectedWebView:(WebView *)webView isUnderTest:(BOOL)isUnderTest
442 {
443     if (!(self = [self init]))
444         return nil;
445
446     _inspectedWebView = webView;
447
448     NSString *pagePath = isUnderTest ? [self inspectorTestPagePath] : [self inspectorPagePath];
449     NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath: pagePath]];
450     [[_frontendWebView mainFrame] loadRequest:request];
451     [request release];
452
453     return self;
454 }
455
456 - (void)dealloc
457 {
458     [_frontendWebView release];
459     [super dealloc];
460 }
461
462 // MARK: -
463
464 - (NSString *)inspectorPagePath
465 {
466     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
467     WebInspectorUILibrary();
468
469     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Main" ofType:@"html"];
470     ASSERT([path length]);
471     return path;
472 }
473
474 - (NSString *)inspectorTestPagePath
475 {
476     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
477     WebInspectorUILibrary();
478
479     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspectorUI"] pathForResource:@"Test" ofType:@"html"];
480
481     // We might not have a Test.html in Production builds.
482     if (!path)
483         return nil;
484
485     return path;
486 }
487
488 // MARK: -
489
490 - (WebView *)frontendWebView
491 {
492     return _frontendWebView;
493 }
494
495 - (NSWindow *)window
496 {
497     NSWindow *window = [super window];
498     if (window)
499         return window;
500
501     NSUInteger styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView;
502     window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, initialWindowWidth, initialWindowHeight) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
503     [window setDelegate:self];
504     [window setMinSize:NSMakeSize(minimumWindowWidth, minimumWindowHeight)];
505     [window setCollectionBehavior:([window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary)];
506
507     CGFloat approximatelyHalfScreenSize = (window.screen.frame.size.width / 2) - 4;
508     CGFloat minimumFullScreenWidth = std::max<CGFloat>(636, approximatelyHalfScreenSize);
509     [window setMinFullScreenContentSize:NSMakeSize(minimumFullScreenWidth, minimumWindowHeight)];
510     [window setCollectionBehavior:([window collectionBehavior] | NSWindowCollectionBehaviorFullScreenAllowsTiling)];
511
512     window.titlebarAppearsTransparent = YES;
513
514     [self setWindow:window];
515     [window release];
516
517     return window;
518 }
519
520 // MARK: -
521
522 - (NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect
523 {
524     // AppKit doesn't know about our HTML toolbar, and places the sheet just a little bit too high.
525     // FIXME: It would be better to get the height of the toolbar and use it in this calculation.
526     rect.origin.y -= 1;
527     return rect;
528 }
529
530 - (BOOL)windowShouldClose:(id)sender
531 {
532     [self destroyInspectorView];
533
534     return YES;
535 }
536
537 - (void)windowDidEnterFullScreen:(NSNotification *)notification
538 {
539     _inspectorClient->windowFullScreenDidChange();
540 }
541
542 - (void)windowDidExitFullScreen:(NSNotification *)notification
543 {
544     _inspectorClient->windowFullScreenDidChange();
545 }
546
547 - (void)close
548 {
549     if (!_visible)
550         return;
551
552     _visible = NO;
553
554     if (_attachedToInspectedWebView) {
555         if ([_inspectedWebView.get() _isClosed])
556             return;
557
558         [_frontendWebView removeFromSuperview];
559
560         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
561         NSRect frameViewRect = [frameView frame];
562
563         // Setting the height based on the previous height is done to work with
564         // Safari's find banner. This assumes the previous height is the Y origin.
565         frameViewRect.size.height += NSMinY(frameViewRect);
566         frameViewRect.origin.y = 0.0;
567
568         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
569         [frameView setFrame:frameViewRect];
570
571         [_inspectedWebView.get() displayIfNeeded];
572     } else
573         [super close];
574 }
575
576 - (IBAction)attachWindow:(id)sender
577 {
578     _frontendClient->attachWindow(InspectorFrontendClient::DockSide::Bottom);
579 }
580
581 - (IBAction)showWindow:(id)sender
582 {
583     if (_visible) {
584         if (!_attachedToInspectedWebView)
585             [super showWindow:sender]; // call super so the window will be ordered front if needed
586         return;
587     }
588
589     _visible = YES;
590
591     _shouldAttach = _inspectorClient->inspectorStartsAttached() && _frontendClient->canAttach();
592
593     if (_shouldAttach) {
594         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
595
596         [_frontendWebView removeFromSuperview];
597         [_inspectedWebView.get() addSubview:_frontendWebView positioned:NSWindowBelow relativeTo:(NSView *)frameView];
598         [[_inspectedWebView.get() window] makeFirstResponder:_frontendWebView];
599
600         [_frontendWebView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)];
601         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin)];
602
603         _attachedToInspectedWebView = YES;
604     } else {
605         _attachedToInspectedWebView = NO;
606
607         NSView *contentView = [[self window] contentView];
608         [_frontendWebView setFrame:[contentView frame]];
609         [_frontendWebView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
610         [_frontendWebView removeFromSuperview];
611         [contentView addSubview:_frontendWebView];
612
613         [super showWindow:nil];
614     }
615 }
616
617 // MARK: -
618
619 - (void)attach
620 {
621     if (_attachedToInspectedWebView)
622         return;
623
624     _inspectorClient->setInspectorStartsAttached(true);
625     _frontendClient->setAttachedWindow(InspectorFrontendClient::DockSide::Bottom);
626
627     [self close];
628     [self showWindow:nil];
629 }
630
631 - (void)detach
632 {
633     if (!_attachedToInspectedWebView)
634         return;
635
636     _inspectorClient->setInspectorStartsAttached(false);
637     _frontendClient->setAttachedWindow(InspectorFrontendClient::DockSide::Undocked);
638
639     [self close];
640     [self showWindow:nil];
641 }
642
643 - (BOOL)attached
644 {
645     return _attachedToInspectedWebView;
646 }
647
648 - (void)setFrontendClient:(WebInspectorFrontendClient*)frontendClient
649 {
650     _frontendClient = frontendClient;
651 }
652
653 - (void)setInspectorClient:(WebInspectorClient*)inspectorClient
654 {
655     _inspectorClient = inspectorClient;
656 }
657
658 - (WebInspectorClient*)inspectorClient
659 {
660     return _inspectorClient;
661 }
662
663 - (void)setAttachedWindowHeight:(unsigned)height
664 {
665     if (!_attachedToInspectedWebView)
666         return;
667
668     WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
669     NSRect frameViewRect = [frameView frame];
670
671     // Setting the height based on the difference is done to work with
672     // Safari's find banner. This assumes the previous height is the Y origin.
673     CGFloat heightDifference = (NSMinY(frameViewRect) - height);
674     frameViewRect.size.height += heightDifference;
675     frameViewRect.origin.y = height;
676
677     [_frontendWebView setFrame:NSMakeRect(0.0, 0.0, NSWidth(frameViewRect), height)];
678     [frameView setFrame:frameViewRect];
679 }
680
681 - (void)setDockingUnavailable:(BOOL)unavailable
682 {
683     // Do nothing.
684 }
685
686 - (void)destroyInspectorView
687 {
688     RetainPtr<WebInspectorWindowController> protect(self);
689
690     if (Page* frontendPage = _frontendClient->frontendPage())
691         frontendPage->inspectorController().setInspectorFrontendClient(nullptr);
692     if (Page* inspectedPage = [_inspectedWebView.get() page])
693         inspectedPage->inspectorController().disconnectFrontend(*_inspectorClient);
694
695     [[_inspectedWebView.get() inspector] releaseFrontend];
696     _inspectorClient->releaseFrontend();
697
698     if (_destroyingInspectorView)
699         return;
700     _destroyingInspectorView = YES;
701
702     [self close];
703
704     _visible = NO;
705
706     [_frontendWebView close];
707 }
708
709 // MARK: -
710 // MARK: UI delegate
711
712 - (void)webView:(WebView *)sender runOpenPanelForFileButtonWithResultListener:(id<WebOpenPanelResultListener>)resultListener allowMultipleFiles:(BOOL)allowMultipleFiles
713 {
714     NSOpenPanel *panel = [NSOpenPanel openPanel];
715     panel.canChooseDirectories = NO;
716     panel.canChooseFiles = YES;
717     panel.allowsMultipleSelection = allowMultipleFiles;
718
719     auto completionHandler = ^(NSInteger result) {
720         if (result == NSModalResponseCancel) {
721             [resultListener cancel];
722             return;
723         }
724         ASSERT(result == NSModalResponseOK);
725
726         NSArray *URLs = panel.URLs;
727         NSMutableArray *filenames = [NSMutableArray arrayWithCapacity:URLs.count];
728         for (NSURL *URL in URLs)
729             [filenames addObject:URL.path];
730
731         [resultListener chooseFilenames:filenames];
732     };
733
734     if (_frontendWebView.window)
735         [panel beginSheetModalForWindow:_frontendWebView.window completionHandler:completionHandler];
736     else
737         completionHandler([panel runModal]);
738 }
739
740 - (void)webView:(WebView *)sender frame:(WebFrame *)frame exceededDatabaseQuotaForSecurityOrigin:(WebSecurityOrigin *)origin database:(NSString *)databaseIdentifier
741 {
742     id <WebQuotaManager> databaseQuotaManager = origin.databaseQuotaManager;
743     databaseQuotaManager.quota = std::max<unsigned long long>(5 * 1024 * 1024, databaseQuotaManager.usage * 1.25);
744 }
745
746 - (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
747 {
748     NSMutableArray *menuItems = [[NSMutableArray alloc] init];
749
750     for (NSMenuItem *item in defaultMenuItems) {
751         switch (item.tag) {
752         case WebMenuItemTagOpenLinkInNewWindow:
753         case WebMenuItemTagOpenImageInNewWindow:
754         case WebMenuItemTagOpenFrameInNewWindow:
755         case WebMenuItemTagOpenMediaInNewWindow:
756         case WebMenuItemTagDownloadLinkToDisk:
757         case WebMenuItemTagDownloadImageToDisk:
758             break;
759         default:
760             [menuItems addObject:item];
761             break;
762         }
763     }
764
765     return [menuItems autorelease];
766 }
767
768 // MARK: -
769 // MARK: Policy delegate
770
771 - (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
772 {
773     // Allow non-main frames to navigate anywhere.
774     if (frame != [webView mainFrame]) {
775         [listener use];
776         return;
777     }
778
779     // Allow loading of the main inspector file.
780     if ([[request URL] isFileURL] && [[[request URL] path] isEqualToString:[self inspectorPagePath]]) {
781         [listener use];
782         return;
783     }
784
785     // Allow loading of the test inspector file.
786     NSString *testPagePath = [self inspectorTestPagePath];
787     if (testPagePath && [[request URL] isFileURL] && [[[request URL] path] isEqualToString:testPagePath]) {
788         [listener use];
789         return;
790     }
791
792     // Prevent everything else from loading in the inspector's page.
793     [listener ignore];
794
795     // And instead load it in the inspected page.
796     [[_inspectedWebView.get() mainFrame] loadRequest:request];
797 }
798
799 // MARK: -
800 // These methods can be used by UI elements such as menu items and toolbar buttons when the inspector is the key window.
801
802 // This method is really only implemented to keep any UI elements enabled.
803 - (void)showWebInspector:(id)sender
804 {
805     [[_inspectedWebView.get() inspector] show:sender];
806 }
807
808 - (void)showErrorConsole:(id)sender
809 {
810     [[_inspectedWebView.get() inspector] showConsole:sender];
811 }
812
813 - (void)toggleDebuggingJavaScript:(id)sender
814 {
815     [[_inspectedWebView.get() inspector] toggleDebuggingJavaScript:sender];
816 }
817
818 - (void)toggleProfilingJavaScript:(id)sender
819 {
820     [[_inspectedWebView.get() inspector] toggleProfilingJavaScript:sender];
821 }
822
823 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
824 {
825     BOOL isMenuItem = [(id)item isKindOfClass:[NSMenuItem class]];
826     if ([item action] == @selector(toggleDebuggingJavaScript:) && isMenuItem) {
827         NSMenuItem *menuItem = (NSMenuItem *)item;
828         if ([[_inspectedWebView.get() inspector] isDebuggingJavaScript])
829             [menuItem setTitle:UI_STRING_INTERNAL("Stop Debugging JavaScript", "title for Stop Debugging JavaScript menu item")];
830         else
831             [menuItem setTitle:UI_STRING_INTERNAL("Start Debugging JavaScript", "title for Start Debugging JavaScript menu item")];
832     } else if ([item action] == @selector(toggleProfilingJavaScript:) && isMenuItem) {
833         NSMenuItem *menuItem = (NSMenuItem *)item;
834         if ([[_inspectedWebView.get() inspector] isProfilingJavaScript])
835             [menuItem setTitle:UI_STRING_INTERNAL("Stop Profiling JavaScript", "title for Stop Profiling JavaScript menu item")];
836         else
837             [menuItem setTitle:UI_STRING_INTERNAL("Start Profiling JavaScript", "title for Start Profiling JavaScript menu item")];
838     }
839
840     return YES;
841 }
842
843 @end