Fix a crash seen during the Inspector tests on the WebKit1 bots.
[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     bool useTexturedWindow = useWebKitWebInspector();
340
341     NSUInteger styleMask = (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask);
342
343     if (useTexturedWindow)
344         styleMask |= NSTexturedBackgroundWindowMask;
345
346     window = [[NSWindow alloc] initWithContentRect:NSMakeRect(60.0, 200.0, 750.0, 650.0) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
347     [window setDelegate:self];
348     [window setMinSize:NSMakeSize(400.0, 400.0)];
349
350     if (useTexturedWindow) {
351         [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
352         [window setContentBorderThickness:55. forEdge:NSMaxYEdge];
353         WKNSWindowMakeBottomCornersSquare(window);
354     }
355
356     [self setWindow:window];
357     [window release];
358
359     return window;
360 }
361
362 // MARK: -
363
364 - (BOOL)windowShouldClose:(id)sender
365 {
366     [self destroyInspectorView:true];
367
368     return YES;
369 }
370
371 - (void)close
372 {
373     if (!_visible)
374         return;
375
376     _visible = NO;
377
378     if (_attachedToInspectedWebView) {
379         if ([_inspectedWebView.get() _isClosed])
380             return;
381
382         [_webView removeFromSuperview];
383
384         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
385         NSRect frameViewRect = [frameView frame];
386
387         // Setting the height based on the previous height is done to work with
388         // Safari's find banner. This assumes the previous height is the Y origin.
389         frameViewRect.size.height += NSMinY(frameViewRect);
390         frameViewRect.origin.y = 0.0;
391
392         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
393         [frameView setFrame:frameViewRect];
394
395         [_inspectedWebView.get() displayIfNeeded];
396     } else
397         [super close];
398 }
399
400 - (IBAction)showWindow:(id)sender
401 {
402     if (_visible) {
403         if (!_attachedToInspectedWebView)
404             [super showWindow:sender]; // call super so the window will be ordered front if needed
405         return;
406     }
407
408     _visible = YES;
409     
410     _shouldAttach = _inspectorClient->inspectorStartsAttached();
411     
412     if (_shouldAttach && !_frontendClient->canAttachWindow())
413         _shouldAttach = NO;
414
415     if (_shouldAttach) {
416         WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
417
418         [_webView removeFromSuperview];
419         [_inspectedWebView.get() addSubview:_webView positioned:NSWindowBelow relativeTo:(NSView *)frameView];
420         [[_inspectedWebView.get() window] makeFirstResponder:_webView];
421
422         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)];
423         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin)];
424
425         _attachedToInspectedWebView = YES;
426     } else {
427         _attachedToInspectedWebView = NO;
428
429         NSView *contentView = [[self window] contentView];
430         [_webView setFrame:[contentView frame]];
431         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
432         [_webView removeFromSuperview];
433         [contentView addSubview:_webView];
434
435         [super showWindow:nil];
436     }
437 }
438
439 // MARK: -
440
441 - (void)attach
442 {
443     if (_attachedToInspectedWebView)
444         return;
445
446     _inspectorClient->setInspectorStartsAttached(true);
447
448     [self close];
449     [self showWindow:nil];
450 }
451
452 - (void)detach
453 {
454     if (!_attachedToInspectedWebView)
455         return;
456
457     _inspectorClient->setInspectorStartsAttached(false);
458
459     [self close];
460     [self showWindow:nil];
461 }
462
463 - (BOOL)attached
464 {
465     return _attachedToInspectedWebView;
466 }
467
468 - (void)setFrontendClient:(WebInspectorFrontendClient*)frontendClient
469 {
470     _frontendClient = frontendClient;
471 }
472
473 - (void)setInspectorClient:(WebInspectorClient*)inspectorClient
474 {
475     _inspectorClient = inspectorClient;
476 }
477
478 - (WebInspectorClient*)inspectorClient
479 {
480     return _inspectorClient;
481 }
482
483 - (void)setAttachedWindowHeight:(unsigned)height
484 {
485     if (!_attachedToInspectedWebView)
486         return;
487
488     WebFrameView *frameView = [[_inspectedWebView.get() mainFrame] frameView];
489     NSRect frameViewRect = [frameView frame];
490
491     // Setting the height based on the difference is done to work with
492     // Safari's find banner. This assumes the previous height is the Y origin.
493     CGFloat heightDifference = (NSMinY(frameViewRect) - height);
494     frameViewRect.size.height += heightDifference;
495     frameViewRect.origin.y = height;
496
497     [_webView setFrame:NSMakeRect(0.0, 0.0, NSWidth(frameViewRect), height)];
498     [frameView setFrame:frameViewRect];
499 }
500
501 - (void)destroyInspectorView:(bool)notifyInspectorController
502 {
503     [[_inspectedWebView.get() inspector] releaseFrontend];
504     _inspectorClient->releaseFrontend();
505
506     if (_destroyingInspectorView)
507         return;
508     _destroyingInspectorView = YES;
509
510     if (_attachedToInspectedWebView)
511         [self close];
512
513     _visible = NO;
514
515     if (notifyInspectorController) {
516         if (Page* inspectedPage = [_inspectedWebView.get() page])
517             inspectedPage->inspectorController()->disconnectFrontend();
518     }
519
520     RetainPtr<WebInspectorWindowController> protect(self);
521     [_webView close];
522 }
523
524 // MARK: -
525 // MARK: UI delegate
526
527 - (NSUInteger)webView:(WebView *)sender dragDestinationActionMaskForDraggingInfo:(id <NSDraggingInfo>)draggingInfo
528 {
529     return WebDragDestinationActionNone;
530 }
531
532 // MARK: -
533 // MARK: Policy delegate
534
535 - (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
536 {
537     // Allow non-main frames to navigate anywhere.
538     if (frame != [webView mainFrame]) {
539         [listener use];
540         return;
541     }
542
543     // Allow loading of the main inspector file.
544     if ([[request URL] isFileURL] && [[[request URL] path] isEqualToString:[self inspectorPagePath]]) {
545         [listener use];
546         return;
547     }
548
549     // Prevent everything else from loading in the inspector's page.
550     [listener ignore];
551
552     // And instead load it in the inspected page.
553     [[_inspectedWebView.get() mainFrame] loadRequest:request];
554 }
555
556 // MARK: -
557 // These methods can be used by UI elements such as menu items and toolbar buttons when the inspector is the key window.
558
559 // This method is really only implemented to keep any UI elements enabled.
560 - (void)showWebInspector:(id)sender
561 {
562     [[_inspectedWebView.get() inspector] show:sender];
563 }
564
565 - (void)showErrorConsole:(id)sender
566 {
567     [[_inspectedWebView.get() inspector] showConsole:sender];
568 }
569
570 - (void)toggleDebuggingJavaScript:(id)sender
571 {
572     [[_inspectedWebView.get() inspector] toggleDebuggingJavaScript:sender];
573 }
574
575 - (void)toggleProfilingJavaScript:(id)sender
576 {
577     [[_inspectedWebView.get() inspector] toggleProfilingJavaScript:sender];
578 }
579
580 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
581 {
582     BOOL isMenuItem = [(id)item isKindOfClass:[NSMenuItem class]];
583     if ([item action] == @selector(toggleDebuggingJavaScript:) && isMenuItem) {
584         NSMenuItem *menuItem = (NSMenuItem *)item;
585         if ([[_inspectedWebView.get() inspector] isDebuggingJavaScript])
586             [menuItem setTitle:UI_STRING_INTERNAL("Stop Debugging JavaScript", "title for Stop Debugging JavaScript menu item")];
587         else
588             [menuItem setTitle:UI_STRING_INTERNAL("Start Debugging JavaScript", "title for Start Debugging JavaScript menu item")];
589     } else if ([item action] == @selector(toggleProfilingJavaScript:) && isMenuItem) {
590         NSMenuItem *menuItem = (NSMenuItem *)item;
591         if ([[_inspectedWebView.get() inspector] isProfilingJavaScript])
592             [menuItem setTitle:UI_STRING_INTERNAL("Stop Profiling JavaScript", "title for Stop Profiling JavaScript menu item")];
593         else
594             [menuItem setTitle:UI_STRING_INTERNAL("Start Profiling JavaScript", "title for Start Profiling JavaScript menu item")];
595     }
596
597     return YES;
598 }
599
600
601 @end