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 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 { 81 } 82 83 void WebInspectorClient::inspectorDestroyed() 84 { 85 delete this; 86 } 87 88 void 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 100 void WebInspectorClient::highlight(Node* node) 101 { 102 [m_highlighter.get() highlightNode:kit(node)]; 103 } 104 105 void WebInspectorClient::hideHighlight() 106 { 107 [m_highlighter.get() hideHighlight]; 108 } 109 110 WebInspectorFrontendClient::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 118 void 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 137 String 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 145 String WebInspectorFrontendClient::hiddenPanels() 146 { 147 NSString *hiddenPanels = [[NSUserDefaults standardUserDefaults] stringForKey:@"WebKitInspectorHiddenPanels"]; 148 if (hiddenPanels) 149 return hiddenPanels; 150 return String(); 151 } 152 153 void WebInspectorFrontendClient::bringToFront() 154 { 155 updateWindowTitle(); 156 [m_windowController.get() showWindow:nil]; 157 } 158 159 void WebInspectorFrontendClient::closeWindow() 160 { 161 [m_windowController.get() destroyInspectorView:true]; 162 } 163 164 void WebInspectorFrontendClient::disconnectFromBackend() 165 { 166 [m_windowController.get() destroyInspectorView:false]; 167 } 168 169 void WebInspectorFrontendClient::attachWindow() 170 { 171 if ([m_windowController.get() attached]) 172 return; 173 [m_windowController.get() attach]; 174 restoreAttachedWindowHeight(); 175 } 176 177 void WebInspectorFrontendClient::detachWindow() 178 { 179 [m_windowController.get() detach]; 180 } 181 182 void WebInspectorFrontendClient::setAttachedWindowHeight(unsigned height) 183 { 184 [m_windowController.get() setAttachedWindowHeight:height]; 185 } 186 187 void WebInspectorFrontendClient::inspectedURLChanged(const String& newURL) 188 { 189 m_inspectedURL = newURL; 190 updateWindowTitle(); 191 } 192 193 void 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 200 void 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 207 void 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