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