Fixes a bug where Safari's find banner would be permanently hidden
[WebKit-https.git] / WebKit / mac / WebCoreSupport / WebInspectorClient.mm
1 /*
2  * Copyright (C) 2006, 2007 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 "WebFrameInternal.h"
32 #import "WebFrameView.h"
33 #import "WebInspector.h"
34 #import "WebLocalizableStrings.h"
35 #import "WebNodeHighlight.h"
36 #import "WebUIDelegate.h"
37 #import "WebViewInternal.h"
38
39 #import <WebCore/InspectorController.h>
40 #import <WebCore/Page.h>
41
42 #import <WebKit/DOMExtensions.h>
43
44 using namespace WebCore;
45
46 @interface WebInspectorWindowController : NSWindowController <NSWindowDelegate> {
47 @private
48     WebView *_inspectedWebView;
49     WebView *_webView;
50     WebNodeHighlight *_currentHighlight;
51     BOOL _attachedToInspectedWebView;
52     BOOL _shouldAttach;
53     BOOL _visible;
54     BOOL _movingWindows;
55 }
56 - (id)initWithInspectedWebView:(WebView *)webView;
57 - (BOOL)inspectorVisible;
58 - (WebView *)webView;
59 - (void)attach;
60 - (void)detach;
61 - (void)setAttachedWindowHeight:(unsigned)height;
62 - (void)highlightAndScrollToNode:(DOMNode *)node;
63 - (void)highlightNode:(DOMNode *)node;
64 - (void)hideHighlight;
65 @end
66
67 #pragma mark -
68
69 WebInspectorClient::WebInspectorClient(WebView *webView)
70 : m_webView(webView)
71 {
72 }
73
74 void WebInspectorClient::inspectorDestroyed()
75 {
76     [[m_windowController.get() webView] close];
77     delete this;
78 }
79
80 Page* WebInspectorClient::createPage()
81 {
82     if (!m_windowController)
83         m_windowController.adoptNS([[WebInspectorWindowController alloc] initWithInspectedWebView:m_webView]);
84
85     return core([m_windowController.get() webView]);
86 }
87
88 String WebInspectorClient::localizedStringsURL()
89 {
90     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"localizedStrings" ofType:@"js"];
91     if (path)
92         return [[NSURL fileURLWithPath:path] absoluteString];
93     return String();
94 }
95
96 void WebInspectorClient::showWindow()
97 {
98     updateWindowTitle();
99     [m_windowController.get() showWindow:nil];
100 }
101
102 void WebInspectorClient::closeWindow()
103 {
104     [m_windowController.get() close];
105 }
106
107 void WebInspectorClient::attachWindow()
108 {
109     [m_windowController.get() attach];
110 }
111
112 void WebInspectorClient::detachWindow()
113 {
114     [m_windowController.get() detach];
115 }
116
117 void WebInspectorClient::setAttachedWindowHeight(unsigned height)
118 {
119     [m_windowController.get() setAttachedWindowHeight:height];
120 }
121
122 void WebInspectorClient::highlight(Node* node)
123 {
124     [m_windowController.get() highlightAndScrollToNode:kit(node)];
125 }
126
127 void WebInspectorClient::hideHighlight()
128 {
129     [m_windowController.get() hideHighlight];
130 }
131
132 void WebInspectorClient::inspectedURLChanged(const String& newURL)
133 {
134     m_inspectedURL = newURL;
135     updateWindowTitle();
136 }
137
138 void WebInspectorClient::updateWindowTitle() const
139 {
140     NSString *title = [NSString stringWithFormat:UI_STRING("Web Inspector — %@", "Web Inspector window title"), (NSString *)m_inspectedURL];
141     [[m_windowController.get() window] setTitle:title];
142 }
143
144 #pragma mark -
145
146 #define WebKitInspectorAttachedKey @"WebKitInspectorAttached"
147 #define WebKitInspectorAttachedViewHeightKey @"WebKitInspectorAttachedViewHeight"
148
149 @implementation WebInspectorWindowController
150 - (id)init
151 {
152     if (![super initWithWindow:nil])
153         return nil;
154
155     // Keep preferences separate from the rest of the client, making sure we are using expected preference values.
156     // One reason this is good is that it keeps the inspector out of history via "private browsing".
157
158     WebPreferences *preferences = [[WebPreferences alloc] init];
159     [preferences setAutosaves:NO];
160     [preferences setPrivateBrowsingEnabled:YES];
161     [preferences setLoadsImagesAutomatically:YES];
162     [preferences setAuthorAndUserStylesEnabled:YES];
163     [preferences setJavaScriptEnabled:YES];
164     [preferences setAllowsAnimatedImages:YES];
165     [preferences setPlugInsEnabled:NO];
166     [preferences setJavaEnabled:NO];
167     [preferences setUserStyleSheetEnabled:NO];
168     [preferences setTabsToLinks:NO];
169     [preferences setMinimumFontSize:0];
170     [preferences setMinimumLogicalFontSize:9];
171
172     _webView = [[WebView alloc] init];
173     [_webView setPreferences:preferences];
174     [_webView setDrawsBackground:NO];
175     [_webView setProhibitsMainFrameScrolling:YES];
176     [_webView setUIDelegate:self];
177
178     [preferences release];
179
180     NSNumber *attached = [[NSUserDefaults standardUserDefaults] objectForKey:WebKitInspectorAttachedKey];
181     ASSERT(!attached || [attached isKindOfClass:[NSNumber class]]);
182     _shouldAttach = attached ? [attached boolValue] : YES;
183
184     NSString *path = [[NSBundle bundleWithIdentifier:@"com.apple.WebCore"] pathForResource:@"inspector" ofType:@"html" inDirectory:@"inspector"];
185     NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL fileURLWithPath:path]];
186     [[_webView mainFrame] loadRequest:request];
187     [request release];
188
189     [self setWindowFrameAutosaveName:@"Web Inspector 2"];
190     return self;
191 }
192
193 - (id)initWithInspectedWebView:(WebView *)webView
194 {
195     if (![self init])
196         return nil;
197
198     // Don't retain to avoid a circular reference
199     _inspectedWebView = webView;
200     return self;
201 }
202
203 - (void)dealloc
204 {
205     ASSERT(!_currentHighlight);
206     [_webView release];
207     [super dealloc];
208 }
209
210 #pragma mark -
211
212 - (BOOL)inspectorVisible
213 {
214     return _visible;
215 }
216
217 - (WebView *)webView
218 {
219     return _webView;
220 }
221
222 - (NSWindow *)window
223 {
224     NSWindow *window = [super window];
225     if (window)
226         return window;
227
228     NSUInteger styleMask = (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask);
229
230 #ifndef BUILDING_ON_TIGER
231     styleMask |= NSTexturedBackgroundWindowMask;
232 #endif
233
234     window = [[NSWindow alloc] initWithContentRect:NSMakeRect(60.0, 200.0, 750.0, 650.0) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO];
235     [window setDelegate:self];
236     [window setMinSize:NSMakeSize(400.0, 400.0)];
237
238 #ifndef BUILDING_ON_TIGER
239     [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
240     [window setContentBorderThickness:55. forEdge:NSMaxYEdge];
241 #endif
242
243     [self setWindow:window];
244     [window release];
245
246     return window;
247 }
248
249 #pragma mark -
250
251 - (BOOL)windowShouldClose:(id)sender
252 {
253     _visible = NO;
254
255     [_inspectedWebView page]->inspectorController()->setWindowVisible(false);
256
257     [self hideHighlight];
258
259     return YES;
260 }
261
262 - (void)close
263 {
264     if (!_visible)
265         return;
266
267     _visible = NO;
268
269     if (!_movingWindows)
270         [_inspectedWebView page]->inspectorController()->setWindowVisible(false);
271
272     [self hideHighlight];
273
274     if (_attachedToInspectedWebView) {
275         if ([_inspectedWebView _isClosed])
276             return;
277
278         [_webView removeFromSuperview];
279
280         WebFrameView *frameView = [[_inspectedWebView mainFrame] frameView];
281         NSRect frameViewRect = [frameView frame];
282
283         // Setting the height based on the previous height is done to work with
284         // Safari's find banner. This assumes the previous height is the Y origin.
285         frameViewRect.size.height += NSMinY(frameViewRect);
286         frameViewRect.origin.y = 0.0;
287
288         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
289         [frameView setFrame:frameViewRect];
290
291         [_inspectedWebView displayIfNeeded];
292     } else
293         [super close];
294 }
295
296 - (IBAction)showWindow:(id)sender
297 {
298     if (_visible) {
299         if (!_attachedToInspectedWebView)
300             [super showWindow:sender]; // call super so the window will be ordered front if needed
301         return;
302     }
303
304     _visible = YES;
305
306     if (_shouldAttach) {
307         WebFrameView *frameView = [[_inspectedWebView mainFrame] frameView];
308
309         [_webView removeFromSuperview];
310         [_inspectedWebView addSubview:_webView positioned:NSWindowBelow relativeTo:(NSView *)frameView];
311
312         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMaxYMargin)];
313         [frameView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable | NSViewMinYMargin)];
314
315         _attachedToInspectedWebView = YES;
316
317         [self setAttachedWindowHeight:[[NSUserDefaults standardUserDefaults] integerForKey:WebKitInspectorAttachedViewHeightKey]];
318     } else {
319         _attachedToInspectedWebView = NO;
320
321         NSView *contentView = [[self window] contentView];
322         [_webView setFrame:[contentView frame]];
323         [_webView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
324         [_webView removeFromSuperview];
325         [contentView addSubview:_webView];
326
327         [super showWindow:nil];
328     }
329
330     [_inspectedWebView page]->inspectorController()->setWindowVisible(true, _shouldAttach);
331 }
332
333 #pragma mark -
334
335 - (void)attach
336 {
337     if (_attachedToInspectedWebView)
338         return;
339
340     _shouldAttach = YES;
341     _movingWindows = YES;
342
343     [self close];
344     [self showWindow:nil];
345
346     _movingWindows = NO;
347 }
348
349 - (void)detach
350 {
351     if (!_attachedToInspectedWebView)
352         return;
353
354     _shouldAttach = NO;
355     _movingWindows = YES;
356
357     [self close];
358     [self showWindow:nil];
359
360     _movingWindows = NO;
361 }
362
363 - (void)setAttachedWindowHeight:(unsigned)height
364 {
365     [[NSUserDefaults standardUserDefaults] setInteger:height forKey:WebKitInspectorAttachedViewHeightKey];
366
367     if (!_attachedToInspectedWebView)
368         return;
369
370     WebFrameView *frameView = [[_inspectedWebView mainFrame] frameView];
371     NSRect frameViewRect = [frameView frame];
372
373     CGFloat attachedHeight = round(MAX(250.0, MIN(height, (NSHeight([_inspectedWebView frame]) * 0.75))));
374
375     // Setting the height based on the difference is done to work with
376     // Safari's find banner. This assumes the previous height is the Y origin.
377     CGFloat heightDifference = (NSMinY(frameViewRect) - attachedHeight);
378     frameViewRect.size.height += heightDifference;
379     frameViewRect.origin.y = attachedHeight;
380
381     [_webView setFrame:NSMakeRect(0.0, 0.0, NSWidth(frameViewRect), attachedHeight)];
382     [frameView setFrame:frameViewRect];
383 }
384
385 #pragma mark -
386
387 - (void)highlightAndScrollToNode:(DOMNode *)node
388 {
389     NSRect bounds = [node boundingBox];
390     if (!NSIsEmptyRect(bounds)) {
391         // FIXME: this needs to use the frame the node coordinates are in
392         NSRect visible = [[[[_inspectedWebView mainFrame] frameView] documentView] visibleRect];
393         BOOL needsScroll = !NSContainsRect(visible, bounds) && !NSContainsRect(bounds, visible);
394
395         // only scroll if the bounds isn't in the visible rect and dosen't contain the visible rect
396         if (needsScroll) {
397             // scroll to the parent element if we aren't focused on an element
398             DOMElement *element;
399             if ([node isKindOfClass:[DOMElement class]])
400                 element = (DOMElement *)node;
401             else
402                 element = (DOMElement *)[node parentNode];
403             [element scrollIntoViewIfNeeded:YES];
404
405             // give time for the scroll to happen
406             [self performSelector:@selector(highlightNode:) withObject:node afterDelay:0.25];
407         } else
408             [self highlightNode:node];
409     }
410 }
411
412 - (void)highlightNode:(DOMNode *)node
413 {
414     // The scrollview's content view stays around between page navigations, so target it
415     NSView *view = [[[[[_inspectedWebView mainFrame] frameView] documentView] enclosingScrollView] contentView];
416     if (![view window])
417         return; // skip the highlight if we have no window (e.g. hidden tab)
418
419     if (!_currentHighlight) {
420         _currentHighlight = [[WebNodeHighlight alloc] initWithTargetView:view inspectorController:[_inspectedWebView page]->inspectorController()];
421         [_currentHighlight setDelegate:self];
422         [_currentHighlight attach];
423     }
424
425     // FIXME: this is a hack until we hook up a didDraw and didScroll call in WebHTMLView
426     [[_currentHighlight highlightView] setNeedsDisplay:YES];
427 }
428
429 - (void)hideHighlight
430 {
431     [_currentHighlight detach];
432     [_currentHighlight setDelegate:nil];
433     [_currentHighlight release];
434     _currentHighlight = nil;
435 }
436
437 #pragma mark -
438 #pragma mark UI delegate
439
440 - (NSUInteger)webView:(WebView *)sender dragDestinationActionMaskForDraggingInfo:(id <NSDraggingInfo>)draggingInfo
441 {
442     return WebDragDestinationActionNone;
443 }
444
445 #pragma mark -
446
447 // These methods can be used by UI elements such as menu items and toolbar buttons when the inspector is the key window.
448
449 // This method is really only implemented to keep any UI elements enabled.
450 - (void)showWebInspector:(id)sender
451 {
452     [[_inspectedWebView inspector] show:sender];
453 }
454
455 - (void)showErrorConsole:(id)sender
456 {
457     [[_inspectedWebView inspector] showConsole:sender];
458 }
459
460 - (void)toggleDebuggingJavaScript:(id)sender
461 {
462     [[_inspectedWebView inspector] toggleDebuggingJavaScript:sender];
463 }
464
465 - (void)toggleProfilingJavaScript:(id)sender
466 {
467     [[_inspectedWebView inspector] toggleProfilingJavaScript:sender];
468 }
469
470 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
471 {
472     BOOL isMenuItem = [(id)item isKindOfClass:[NSMenuItem class]];
473     if ([item action] == @selector(toggleDebuggingJavaScript:) && isMenuItem) {
474         NSMenuItem *menuItem = (NSMenuItem *)item;
475         if ([[_inspectedWebView inspector] isDebuggingJavaScript])
476             [menuItem setTitle:UI_STRING("Stop Debugging JavaScript", "title for Stop Debugging JavaScript menu item")];
477         else
478             [menuItem setTitle:UI_STRING("Start Debugging JavaScript", "title for Start Debugging JavaScript menu item")];
479     } else if ([item action] == @selector(toggleProfilingJavaScript:) && isMenuItem) {
480         NSMenuItem *menuItem = (NSMenuItem *)item;
481         if ([[_inspectedWebView inspector] isProfilingJavaScript])
482             [menuItem setTitle:UI_STRING("Stop Profiling JavaScript", "title for Stop Profiling JavaScript menu item")];
483         else
484             [menuItem setTitle:UI_STRING("Start Profiling JavaScript", "title for Start Profiling JavaScript menu item")];
485     }
486
487     return YES;
488 }
489
490 @end