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