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