Always use a textured window for the Web Inspector.
[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 "WebInspectorPrivate.h"
37 #import "WebInspectorFrontend.h"
38 #import "WebLocalizableStringsInternal.h"
39 #import "WebNodeHighlighter.h"
40 #import "WebUIDelegate.h"
41 #import "WebPolicyDelegate.h"
42 #import "WebViewInternal.h"
43 #import <WebCore/InspectorController.h>
44 #import <WebCore/Page.h>
45 #import <WebCore/SoftLinking.h>
46 #import <WebKit/DOMExtensions.h>
47 #import <WebKitSystemInterface.h>
48 #import <wtf/PassOwnPtr.h>
49
50 SOFT_LINK_STAGED_FRAMEWORK_OPTIONAL(WebInspector, PrivateFrameworks, A)
51
52 using namespace WebCore;
53
54 @interface WebInspectorWindowController : NSWindowController <NSWindowDelegate> {
55 @private
56     RetainPtr<WebView> _inspectedWebView;
57     WebView *_webView;
58     WebInspectorFrontendClient* _frontendClient;
59     WebInspectorClient* _inspectorClient;
60     BOOL _attachedToInspectedWebView;
61     BOOL _shouldAttach;
62     BOOL _visible;
63     BOOL _destroyingInspectorView;
64 }
65 - (id)initWithInspectedWebView:(WebView *)webView;
66 - (NSString *)inspectorPagePath;
67 - (WebView *)webView;
68 - (void)attach;
69 - (void)detach;
70 - (BOOL)attached;
71 - (void)setFrontendClient:(WebInspectorFrontendClient*)frontendClient;
72 - (void)setInspectorClient:(WebInspectorClient*)inspectorClient;
73 - (WebInspectorClient*)inspectorClient;
74 - (void)setAttachedWindowHeight:(unsigned)height;
75 - (void)destroyInspectorView:(bool)notifyInspectorController;
76 @end
77
78
79 // MARK: -
80
81 WebInspectorClient::WebInspectorClient(WebView *webView)
82     : m_webView(webView)
83     , m_highlighter(AdoptNS, [[WebNodeHighlighter alloc] initWithInspectedWebView:webView])
84     , m_frontendPage(0)
85     , m_frontendClient(0)
86 {
87 }
88
89 void WebInspectorClient::inspectorDestroyed()
90 {
91     closeInspectorFrontend();
92     delete this;
93 }
94
95 InspectorFrontendChannel* WebInspectorClient::openInspectorFrontend(InspectorController* inspectorController)
96 {
97     RetainPtr<WebInspectorWindowController> windowController(AdoptNS, [[WebInspectorWindowController alloc] initWithInspectedWebView:m_webView]);
98     [windowController.get() setInspectorClient:this];
99
100     m_frontendPage = core([windowController.get() webView]);
101     OwnPtr<WebInspectorFrontendClient> frontendClient = adoptPtr(new WebInspectorFrontendClient(m_webView, windowController.get(), inspectorController, m_frontendPage, createFrontendSettings()));
102     m_frontendClient = frontendClient.get();
103     RetainPtr<WebInspectorFrontend> webInspectorFrontend(AdoptNS, [[WebInspectorFrontend alloc] initWithFrontendClient:frontendClient.get()]);
104     [[m_webView inspector] setFrontend:webInspectorFrontend.get()];
105     m_frontendPage->inspectorController()->setInspectorFrontendClient(frontendClient.release());
106     return this;
107 }
108
109 void WebInspectorClient::closeInspectorFrontend()
110 {
111     if (m_frontendClient)
112         m_frontendClient->disconnectFromBackend();
113 }
114
115 void WebInspectorClient::bringFrontendToFront()
116 {
117     m_frontendClient->bringToFront();
118 }
119
120 void WebInspectorClient::didResizeMainFrame(Frame*)
121 {
122     if (m_frontendClient)
123         m_frontendClient->setDockingUnavailable(!m_frontendClient->canAttachWindow());
124 }
125
126 void WebInspectorClient::highlight()
127 {
128     [m_highlighter.get() highlight];
129 }
130
131 void WebInspectorClient::hideHighlight()
132 {
133     [m_highlighter.get() hideHighlight];
134 }
135
136 void WebInspectorClient::releaseFrontend()
137 {
138     m_frontendClient = 0;
139     m_frontendPage = 0;
140 }
141
142 WebInspectorFrontendClient::WebInspectorFrontendClient(WebView* inspectedWebView, WebInspectorWindowController* windowController, InspectorController* inspectorController, Page* frontendPage, WTF::PassOwnPtr<Settings> settings)
143     : InspectorFrontendClientLocal(inspectorController,  frontendPage, settings)
144     , m_inspectedWebView(inspectedWebView)
145     , m_windowController(windowController)
146 {
147     [windowController setFrontendClient:this];
148 }
149
150 void WebInspectorFrontendClient::frontendLoaded()
151 {
152     [m_windowController.get() showWindow:nil];
153     if ([m_windowController.get() attached])
154         restoreAttachedWindowHeight();
155
156     InspectorFrontendClientLocal::frontendLoaded();
157
158     WebFrame *frame = [m_inspectedWebView mainFrame];
159     
160     WebFrameLoadDelegateImplementationCache* implementations = WebViewGetFrameLoadDelegateImplementations(m_inspectedWebView);
161     if (implementations->didClearInspectorWindowObjectForFrameFunc)
162         CallFrameLoadDelegate(implementations->didClearInspectorWindowObjectForFrameFunc, m_inspectedWebView,
163                               @selector(webView:didClearInspectorWindowObject:forFrame:), [frame windowObject], frame);
164
165     bool attached = [m_windowController.get() attached];
166     setAttachedWindow(attached);
167 }
168
169 static bool useWebKitWebInspector()
170 {
171     // Call the soft link framework function to dlopen it, then [NSBundle bundleWithIdentifier:] will work.
172     WebInspectorLibrary();
173
174     if (![[NSBundle bundleWithIdentifier:@"com.apple.WebInspector"] pathForResource:@"Main" ofType:@"html"])
175         return true;
176
177     if (![[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"inspector" ofType:@"html" inDirectory:@"inspector"])
178         return false;
179
180     return [[NSUserDefaults standardUserDefaults] boolForKey:@"UseWebKitWebInspector"];
181 }
182
183 String WebInspectorFrontendClient::localizedStringsURL()
184 {
185     NSBundle *bundle = useWebKitWebInspector() ? [NSBundle bundleWithIdentifier:@"com.apple.WebCore"] : [NSBundle bundleWithIdentifier:@"com.apple.WebInspector"]; 
186     NSString *path = [bundle pathForResource:@"localizedStrings" ofType:@"js"];
187     if ([path length])
188         return [[NSURL fileURLWithPath:path] absoluteString];
189     return String();
190 }
191
192 String WebInspectorFrontendClient::hiddenPanels()
193 {
194     NSString *hiddenPanels = [[NSUserDefaults standardUserDefaults] stringForKey:@"WebKitInspectorHiddenPanels"];
195     if (hiddenPanels)
196         return hiddenPanels;
197     return String();
198 }
199
200 void WebInspectorFrontendClient::bringToFront()
201 {
202     updateWindowTitle();
203
204     [m_windowController.get() showWindow:nil];
205
206     // Use the window from the WebView since m_windowController's window
207     // is not the same when the Inspector is docked.
208     WebView *webView = [m_windowController.get() webView];
209     [[webView window] makeFirstResponder:webView];
210 }
211
212 void WebInspectorFrontendClient::closeWindow()
213 {
214     [m_windowController.get() destroyInspectorView:true];
215 }
216
217 void WebInspectorFrontendClient::disconnectFromBackend()
218 {
219     [m_windowController.get() destroyInspectorView:false];
220 }
221
222 void WebInspectorFrontendClient::attachWindow()
223 {
224     if ([m_windowController.get() attached])
225         return;
226     [m_windowController.get() attach];
227     restoreAttachedWindowHeight();
228 }
229
230 void WebInspectorFrontendClient::detachWindow()
231 {
232     [m_windowController.get() detach];
233 }
234
235 void WebInspectorFrontendClient::setAttachedWindowHeight(unsigned height)
236 {
237     [m_windowController.get() setAttachedWindowHeight:height];
238 }
239
240 void WebInspectorFrontendClient::inspectedURLChanged(const String& newURL)
241 {
242     m_inspectedURL = newURL;
243     updateWindowTitle();
244 }
245
246 void WebInspectorFrontendClient::updateWindowTitle() const
247 {
248     NSString *title = [NSString stringWithFormat:UI_STRING_INTERNAL("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_inspectedURL];
249     [[m_windowController.get() window] setTitle:title];
250 }
251
252
253 // MARK: -
254
255 @implementation WebInspectorWindowController
256 - (id)init
257 {
258     if (!(self = [super initWithWindow:nil]))
259         return nil;
260
261     // Keep preferences separate from the rest of the client, making sure we are using expected preference values.
262
263     WebPreferences *preferences = [[WebPreferences alloc] init];
264     [preferences setAllowsAnimatedImages:YES];
265     [preferences setApplicationChromeModeEnabled:YES];
266     [preferences setAuthorAndUserStylesEnabled:YES];
267     [preferences setAutosaves:NO];
268     [preferences setDefaultFixedFontSize:11];
269     [preferences setFixedFontFamily:@"Menlo"];
270     [preferences setJavaEnabled:NO];
271     [preferences setJavaScriptEnabled:YES];
272     [preferences setLoadsImagesAutomatically:YES];
273     [preferences setMinimumFontSize:0];
274     [preferences setMinimumLogicalFontSize:9];
275     [preferences setPlugInsEnabled:NO];
276     [preferences setSuppressesIncrementalRendering:YES];
277     [preferences setTabsToLinks:NO];
278     [preferences setUserStyleSheetEnabled:NO];
279
280     _webView = [[WebView alloc] init];
281     [_webView setPreferences:preferences];
282     [_webView setDrawsBackground:NO];
283     [_webView setProhibitsMainFrameScrolling:YES];
284     [_webView setUIDelegate:self];
285     [_webView setPolicyDelegate:self];
286
287     [preferences release];
288
289     NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:[self inspectorPagePath]]];
290     [[_webView mainFrame] loadRequest:request];
291     [request release];
292
293     [self setWindowFrameAutosaveName:@"Web Inspector 2"];
294     return self;
295 }
296
297 - (id)initWithInspectedWebView:(WebView *)webView
298 {
299     if (!(self = [self init]))
300         return nil;
301
302     _inspectedWebView = webView;
303     return self;
304 }
305
306 - (void)dealloc
307 {
308     [_webView release];
309     [super dealloc];
310 }
311
312 // MARK: -
313
314 - (NSString *)inspectorPagePath
315 {
316     NSString *path;
317     if (useWebKitWebInspector())
318         path = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"inspector" ofType:@"html" inDirectory:@"inspector"];
319     else
320         path = [[NSBundle bundleWithIdentifier:@"com.apple.WebInspector"] pathForResource:@"Main" ofType:@"html"];
321
322     ASSERT([path length]);
323     return path;
324 }
325
326 // MARK: -
327
328 - (WebView *)webView
329 {
330     return _webView;
331 }
332
333 - (NSWindow *)window
334 {
335     NSWindow *window = [super window];
336     if (window)
337         return window;
338
339     NSUInteger styleMask = (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSTexturedBackgroundWindowMask);
340     window = [[NSWindow alloc] initWithContentRect:NSMakeRect(60.0, 200.0, 750.0, 650.0) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
341     [window setDelegate:self];
342     [window setMinSize:NSMakeSize(400.0, 400.0)];
343     [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
344     [window setContentBorderThickness:55. forEdge:NSMaxYEdge];
345     WKNSWindowMakeBottomCornersSquare(window);
346
347     [self setWindow:window];
348     [window release];
349
350     return window;
351 }
352
353 // MARK: -
354
355 - (BOOL)windowShouldClose:(id)sender
356 {
357     [self destroyInspectorView:true];
358
359     return YES;
360 }
361
362 - (void)close
363 {
364     if (!_visible)
365         return;
366
367     _visible = NO;
368
369     if (_attachedToInspectedWebView) {
370         if ([_inspectedWebView.get() _isClosed])
371             return;
372
373         [_webView removeFromSuperview];
374
375         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
376         NSRect frameViewRect = [frameView frame];
377
378         // Setting the height based on the previous height is done to work with
379         // Safari's find banner. This assumes the previous height is the Y origin.
380         frameViewRect.size.height += NSMinY(frameViewRect);
381         frameViewRect.origin.y = 0.0;
382
383         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
384         [frameView setFrame:frameViewRect];
385
386         [_inspectedWebView.get() displayIfNeeded];
387     } else
388         [super close];
389 }
390
391 - (IBAction)showWindow:(id)sender
392 {
393     if (_visible) {
394         if (!_attachedToInspectedWebView)
395             [super showWindow:sender]; // call super so the window will be ordered front if needed
396         return;
397     }
398
399     _visible = YES;
400     
401     _shouldAttach = _inspectorClient->inspectorStartsAttached();
402     
403     if (_shouldAttach && !_frontendClient->canAttachWindow())
404         _shouldAttach = NO;
405
406     if (_shouldAttach) {
407         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
408
409         [_webView removeFromSuperview];
410         [_inspectedWebView.get() addSubview:_webView positioned:NSWindowBelow relativeTo:(NSView *)frameView];
411         [[_inspectedWebView.get() window] makeFirstResponder:_webView];
412
413         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)];
414         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin)];
415
416         _attachedToInspectedWebView = YES;
417     } else {
418         _attachedToInspectedWebView = NO;
419
420         NSView *contentView = [[self window] contentView];
421         [_webView setFrame:[contentView frame]];
422         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
423         [_webView removeFromSuperview];
424         [contentView addSubview:_webView];
425
426         [super showWindow:nil];
427     }
428 }
429
430 // MARK: -
431
432 - (void)attach
433 {
434     if (_attachedToInspectedWebView)
435         return;
436
437     _inspectorClient->setInspectorStartsAttached(true);
438
439     [self close];
440     [self showWindow:nil];
441 }
442
443 - (void)detach
444 {
445     if (!_attachedToInspectedWebView)
446         return;
447
448     _inspectorClient->setInspectorStartsAttached(false);
449
450     [self close];
451     [self showWindow:nil];
452 }
453
454 - (BOOL)attached
455 {
456     return _attachedToInspectedWebView;
457 }
458
459 - (void)setFrontendClient:(WebInspectorFrontendClient*)frontendClient
460 {
461     _frontendClient = frontendClient;
462 }
463
464 - (void)setInspectorClient:(WebInspectorClient*)inspectorClient
465 {
466     _inspectorClient = inspectorClient;
467 }
468
469 - (WebInspectorClient*)inspectorClient
470 {
471     return _inspectorClient;
472 }
473
474 - (void)setAttachedWindowHeight:(unsigned)height
475 {
476     if (!_attachedToInspectedWebView)
477         return;
478
479     WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
480     NSRect frameViewRect = [frameView frame];
481
482     // Setting the height based on the difference is done to work with
483     // Safari's find banner. This assumes the previous height is the Y origin.
484     CGFloat heightDifference = (NSMinY(frameViewRect) - height);
485     frameViewRect.size.height += heightDifference;
486     frameViewRect.origin.y = height;
487
488     [_webView setFrame:NSMakeRect(0.0, 0.0, NSWidth(frameViewRect), height)];
489     [frameView setFrame:frameViewRect];
490 }
491
492 - (void)destroyInspectorView:(bool)notifyInspectorController
493 {
494     [[_inspectedWebView.get() inspector] releaseFrontend];
495     _inspectorClient->releaseFrontend();
496
497     if (_destroyingInspectorView)
498         return;
499     _destroyingInspectorView = YES;
500
501     if (_attachedToInspectedWebView)
502         [self close];
503
504     _visible = NO;
505
506     if (notifyInspectorController) {
507         if (Page* inspectedPage = [_inspectedWebView.get() page])
508             inspectedPage->inspectorController()->disconnectFrontend();
509     }
510
511     RetainPtr<WebInspectorWindowController> protect(self);
512     [_webView close];
513 }
514
515 // MARK: -
516 // MARK: UI delegate
517
518 - (NSUInteger)webView:(WebView *)sender dragDestinationActionMaskForDraggingInfo:(id <NSDraggingInfo>)draggingInfo
519 {
520     return WebDragDestinationActionNone;
521 }
522
523 // MARK: -
524 // MARK: Policy delegate
525
526 - (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
527 {
528     // Allow non-main frames to navigate anywhere.
529     if (frame != [webView mainFrame]) {
530         [listener use];
531         return;
532     }
533
534     // Allow loading of the main inspector file.
535     if ([[request URL] isFileURL] && [[[request URL] path] isEqualToString:[self inspectorPagePath]]) {
536         [listener use];
537         return;
538     }
539
540     // Prevent everything else from loading in the inspector's page.
541     [listener ignore];
542
543     // And instead load it in the inspected page.
544     [[_inspectedWebView.get() mainFrame] loadRequest:request];
545 }
546
547 // MARK: -
548 // These methods can be used by UI elements such as menu items and toolbar buttons when the inspector is the key window.
549
550 // This method is really only implemented to keep any UI elements enabled.
551 - (void)showWebInspector:(id)sender
552 {
553     [[_inspectedWebView.get() inspector] show:sender];
554 }
555
556 - (void)showErrorConsole:(id)sender
557 {
558     [[_inspectedWebView.get() inspector] showConsole:sender];
559 }
560
561 - (void)toggleDebuggingJavaScript:(id)sender
562 {
563     [[_inspectedWebView.get() inspector] toggleDebuggingJavaScript:sender];
564 }
565
566 - (void)toggleProfilingJavaScript:(id)sender
567 {
568     [[_inspectedWebView.get() inspector] toggleProfilingJavaScript:sender];
569 }
570
571 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
572 {
573     BOOL isMenuItem = [(id)item isKindOfClass:[NSMenuItem class]];
574     if ([item action] == @selector(toggleDebuggingJavaScript:) && isMenuItem) {
575         NSMenuItem *menuItem = (NSMenuItem *)item;
576         if ([[_inspectedWebView.get() inspector] isDebuggingJavaScript])
577             [menuItem setTitle:UI_STRING_INTERNAL("Stop Debugging JavaScript", "title for Stop Debugging JavaScript menu item")];
578         else
579             [menuItem setTitle:UI_STRING_INTERNAL("Start Debugging JavaScript", "title for Start Debugging JavaScript menu item")];
580     } else if ([item action] == @selector(toggleProfilingJavaScript:) && isMenuItem) {
581         NSMenuItem *menuItem = (NSMenuItem *)item;
582         if ([[_inspectedWebView.get() inspector] isProfilingJavaScript])
583             [menuItem setTitle:UI_STRING_INTERNAL("Stop Profiling JavaScript", "title for Stop Profiling JavaScript menu item")];
584         else
585             [menuItem setTitle:UI_STRING_INTERNAL("Start Profiling JavaScript", "title for Start Profiling JavaScript menu item")];
586     }
587
588     return YES;
589 }
590
591
592 @end