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