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