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