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