1/* 2 * Copyright (C) 2006, 2007, 2008, 2009, 2010 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#include "config.h" 30#include "WebInspectorClient.h" 31 32#if ENABLE(INSPECTOR) 33 34#include "WebCoreBundleWin.h" 35#include "WebInspectorDelegate.h" 36#include "WebKit.h" 37#include "WebMutableURLRequest.h" 38#include "WebNodeHighlight.h" 39#include "WebView.h" 40 41#include <WebCore/BString.h> 42#include <WebCore/Element.h> 43#include <WebCore/FloatRect.h> 44#include <WebCore/FrameView.h> 45#include <WebCore/InspectorController.h> 46#include <WebCore/NotImplemented.h> 47#include <WebCore/Page.h> 48#include <WebCore/RenderObject.h> 49#include <WebCore/WindowMessageBroadcaster.h> 50 51#include <wchar.h> 52#include <wtf/RetainPtr.h> 53#include <wtf/text/StringConcatenate.h> 54 55using namespace WebCore; 56 57static LPCTSTR kWebInspectorWindowClassName = TEXT("WebInspectorWindowClass"); 58static ATOM registerWindowClass(); 59static LPCTSTR kWebInspectorPointerProp = TEXT("WebInspectorPointer"); 60 61static const IntRect& defaultWindowRect() 62{ 63 static IntRect rect(60, 200, 750, 650); 64 return rect; 65} 66 67WebInspectorClient::WebInspectorClient(WebView* webView) 68 : m_inspectedWebView(webView) 69 , m_frontendPage(0) 70 , m_frontendClient(0) 71{ 72 ASSERT(m_inspectedWebView); 73 m_inspectedWebView->viewWindow((OLE_HANDLE*)&m_inspectedWebViewHwnd); 74} 75 76WebInspectorClient::~WebInspectorClient() 77{ 78} 79 80void WebInspectorClient::inspectorDestroyed() 81{ 82 closeInspectorFrontend(); 83 delete this; 84} 85 86WebCore::InspectorFrontendChannel* WebInspectorClient::openInspectorFrontend(InspectorController* inspectorController) 87{ 88 registerWindowClass(); 89 90 HWND frontendHwnd = ::CreateWindowEx(0, kWebInspectorWindowClassName, 0, WS_OVERLAPPEDWINDOW, 91 defaultWindowRect().x(), defaultWindowRect().y(), defaultWindowRect().width(), defaultWindowRect().height(), 92 0, 0, 0, 0); 93 94 if (!frontendHwnd) 95 return 0; 96 97 COMPtr<WebView> frontendWebView(AdoptCOM, WebView::createInstance()); 98 99 if (FAILED(frontendWebView->setHostWindow((OLE_HANDLE)(ULONG64)frontendHwnd))) 100 return 0; 101 102 RECT rect; 103 GetClientRect(frontendHwnd, &rect); 104 if (FAILED(frontendWebView->initWithFrame(rect, 0, 0))) 105 return 0; 106 107 COMPtr<WebInspectorDelegate> delegate(AdoptCOM, WebInspectorDelegate::createInstance()); 108 if (FAILED(frontendWebView->setUIDelegate(delegate.get()))) 109 return 0; 110 111 // Keep preferences separate from the rest of the client, making sure we are using expected preference values. 112 // FIXME: It's crazy that we have to do this song and dance to end up with 113 // a private WebPreferences object, even within WebKit. We should make this 114 // process simpler, and consider whether we can make it simpler for WebKit 115 // clients as well. 116 COMPtr<WebPreferences> tempPreferences(AdoptCOM, WebPreferences::createInstance()); 117 COMPtr<IWebPreferences> iPreferences; 118 if (FAILED(tempPreferences->initWithIdentifier(BString(L"WebInspectorPreferences"), &iPreferences))) 119 return 0; 120 COMPtr<WebPreferences> preferences(Query, iPreferences); 121 if (!preferences) 122 return 0; 123 if (FAILED(preferences->setAutosaves(FALSE))) 124 return 0; 125 if (FAILED(preferences->setLoadsImagesAutomatically(TRUE))) 126 return 0; 127 if (FAILED(preferences->setAuthorAndUserStylesEnabled(TRUE))) 128 return 0; 129 if (FAILED(preferences->setAllowsAnimatedImages(TRUE))) 130 return 0; 131 if (FAILED(preferences->setLoadsImagesAutomatically(TRUE))) 132 return 0; 133 if (FAILED(preferences->setPlugInsEnabled(FALSE))) 134 return 0; 135 if (FAILED(preferences->setJavaEnabled(FALSE))) 136 return 0; 137 if (FAILED(preferences->setUserStyleSheetEnabled(FALSE))) 138 return 0; 139 if (FAILED(preferences->setTabsToLinks(FALSE))) 140 return 0; 141 if (FAILED(preferences->setMinimumFontSize(0))) 142 return 0; 143 if (FAILED(preferences->setMinimumLogicalFontSize(9))) 144 return 0; 145 if (FAILED(preferences->setFixedFontFamily(BString(L"Courier New")))) 146 return 0; 147 if (FAILED(preferences->setDefaultFixedFontSize(13))) 148 return 0; 149 150 if (FAILED(frontendWebView->setPreferences(preferences.get()))) 151 return 0; 152 153 frontendWebView->setProhibitsMainFrameScrolling(TRUE); 154 155 HWND frontendWebViewHwnd; 156 if (FAILED(frontendWebView->viewWindow(reinterpret_cast<OLE_HANDLE*>(&frontendWebViewHwnd)))) 157 return 0; 158 159 COMPtr<WebMutableURLRequest> request(AdoptCOM, WebMutableURLRequest::createInstance()); 160 161 RetainPtr<CFURLRef> htmlURLRef = adoptCF(CFBundleCopyResourceURL(webKitBundle(), CFSTR("Main"), CFSTR("html"), CFSTR("WebInspectorUI"))); 162 if (!htmlURLRef) 163 return 0; 164 165 CFStringRef urlStringRef = ::CFURLGetString(htmlURLRef.get()); 166 if (FAILED(request->initWithURL(BString(urlStringRef), WebURLRequestUseProtocolCachePolicy, 60))) 167 return 0; 168 169 if (FAILED(frontendWebView->topLevelFrame()->loadRequest(request.get()))) 170 return 0; 171 172 m_frontendPage = core(frontendWebView.get()); 173 OwnPtr<WebInspectorFrontendClient> frontendClient = adoptPtr(new WebInspectorFrontendClient(m_inspectedWebView, m_inspectedWebViewHwnd, frontendHwnd, frontendWebView, frontendWebViewHwnd, this, createFrontendSettings())); 174 m_frontendClient = frontendClient.get(); 175 m_frontendPage->inspectorController()->setInspectorFrontendClient(frontendClient.release()); 176 m_frontendHwnd = frontendHwnd; 177 return this; 178} 179 180void WebInspectorClient::closeInspectorFrontend() 181{ 182 if (m_frontendClient) 183 m_frontendClient->destroyInspectorView(false); 184} 185 186void WebInspectorClient::bringFrontendToFront() 187{ 188 m_frontendClient->bringToFront(); 189} 190 191void WebInspectorClient::highlight() 192{ 193 bool creatingHighlight = !m_highlight; 194 195 if (creatingHighlight) 196 m_highlight = adoptPtr(new WebNodeHighlight(m_inspectedWebView)); 197 198 if (m_highlight->isShowing()) 199 m_highlight->update(); 200 else 201 m_highlight->setShowsWhileWebViewIsVisible(true); 202 203 if (creatingHighlight && IsWindowVisible(m_frontendHwnd)) 204 m_highlight->placeBehindWindow(m_frontendHwnd); 205} 206 207void WebInspectorClient::hideHighlight() 208{ 209 if (m_highlight) 210 m_highlight->setShowsWhileWebViewIsVisible(false); 211} 212 213void WebInspectorClient::updateHighlight() 214{ 215 if (m_highlight && m_highlight->isShowing()) 216 m_highlight->update(); 217} 218 219void WebInspectorClient::releaseFrontend() 220{ 221 m_frontendClient = 0; 222 m_frontendPage = 0; 223 m_frontendHwnd = 0; 224} 225 226WebInspectorFrontendClient::WebInspectorFrontendClient(WebView* inspectedWebView, HWND inspectedWebViewHwnd, HWND frontendHwnd, const COMPtr<WebView>& frontendWebView, HWND frontendWebViewHwnd, WebInspectorClient* inspectorClient, PassOwnPtr<Settings> settings) 227 : InspectorFrontendClientLocal(inspectedWebView->page()->inspectorController(), core(frontendWebView.get()), settings) 228 , m_inspectedWebView(inspectedWebView) 229 , m_inspectedWebViewHwnd(inspectedWebViewHwnd) 230 , m_inspectorClient(inspectorClient) 231 , m_frontendHwnd(frontendHwnd) 232 , m_frontendWebView(frontendWebView) 233 , m_frontendWebViewHwnd(frontendWebViewHwnd) 234 , m_attached(false) 235 , m_destroyingInspectorView(false) 236{ 237 ::SetProp(frontendHwnd, kWebInspectorPointerProp, reinterpret_cast<HANDLE>(this)); 238 // FIXME: Implement window size/position save/restore 239#if 0 240 [self setWindowFrameAutosaveName:@"Web Inspector"]; 241#endif 242} 243 244WebInspectorFrontendClient::~WebInspectorFrontendClient() 245{ 246 destroyInspectorView(true); 247} 248 249void WebInspectorFrontendClient::frontendLoaded() 250{ 251 InspectorFrontendClientLocal::frontendLoaded(); 252 253 if (m_attached) 254 restoreAttachedWindowHeight(); 255 256 setAttachedWindow(m_attached ? DOCKED_TO_BOTTOM : UNDOCKED); 257} 258 259String WebInspectorFrontendClient::localizedStringsURL() 260{ 261 RetainPtr<CFURLRef> url = adoptCF(CFBundleCopyResourceURL(webKitBundle(), CFSTR("localizedStrings"), CFSTR("js"), CFSTR("WebInspectorUI"))); 262 if (!url) 263 url = adoptCF(CFBundleCopyResourceURL(webKitBundle(), CFSTR("localizedStrings"), CFSTR("js"), 0)); 264 265 if (!url) 266 return String(); 267 268 return CFURLGetString(url.get()); 269} 270 271void WebInspectorFrontendClient::bringToFront() 272{ 273 showWindowWithoutNotifications(); 274} 275 276void WebInspectorFrontendClient::closeWindow() 277{ 278 destroyInspectorView(true); 279} 280 281void WebInspectorFrontendClient::attachWindow(DockSide) 282{ 283 if (m_attached) 284 return; 285 286 m_inspectorClient->setInspectorStartsAttached(true); 287 288 closeWindowWithoutNotifications(); 289 // We need to set the attached window's height before we actually attach the window. 290 // Make sure that m_attached is true so that calling setAttachedWindowHeight from restoreAttachedWindowHeight doesn't return early. 291 m_attached = true; 292 // Immediately after calling showWindowWithoutNotifications(), the parent frameview's visibleHeight incorrectly returns 0 always (Windows only). 293 // We are expecting this value to be just the height of the parent window when we call restoreAttachedWindowHeight, which it is before 294 // calling showWindowWithoutNotifications(). 295 restoreAttachedWindowHeight(); 296 showWindowWithoutNotifications(); 297} 298 299void WebInspectorFrontendClient::detachWindow() 300{ 301 if (!m_attached) 302 return; 303 304 m_inspectorClient->setInspectorStartsAttached(false); 305 306 closeWindowWithoutNotifications(); 307 showWindowWithoutNotifications(); 308} 309 310void WebInspectorFrontendClient::setAttachedWindowHeight(unsigned height) 311{ 312 if (!m_attached) 313 return; 314 315 HWND hostWindow; 316 if (!SUCCEEDED(m_inspectedWebView->hostWindow((OLE_HANDLE*)&hostWindow))) 317 return; 318 319 RECT hostWindowRect; 320 GetClientRect(hostWindow, &hostWindowRect); 321 322 RECT inspectedRect; 323 GetClientRect(m_inspectedWebViewHwnd, &inspectedRect); 324 325 int totalHeight = hostWindowRect.bottom - hostWindowRect.top; 326 int webViewWidth = inspectedRect.right - inspectedRect.left; 327 328 SetWindowPos(m_frontendWebViewHwnd, 0, 0, totalHeight - height, webViewWidth, height, SWP_NOZORDER); 329 330 // We want to set the inspected web view height to the totalHeight, because the height adjustment 331 // of the inspected web view happens in onWebViewWindowPosChanging, not here. 332 SetWindowPos(m_inspectedWebViewHwnd, 0, 0, 0, webViewWidth, totalHeight, SWP_NOZORDER); 333 334 RedrawWindow(m_frontendWebViewHwnd, 0, 0, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW); 335 RedrawWindow(m_inspectedWebViewHwnd, 0, 0, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW); 336} 337 338void WebInspectorFrontendClient::setAttachedWindowWidth(unsigned) 339{ 340 notImplemented(); 341} 342 343void WebInspectorFrontendClient::setToolbarHeight(unsigned) 344{ 345 notImplemented(); 346} 347 348void WebInspectorFrontendClient::inspectedURLChanged(const String& newURL) 349{ 350 m_inspectedURL = newURL; 351 updateWindowTitle(); 352} 353 354void WebInspectorFrontendClient::closeWindowWithoutNotifications() 355{ 356 if (!m_frontendHwnd) 357 return; 358 359 if (!m_attached) { 360 ShowWindow(m_frontendHwnd, SW_HIDE); 361 return; 362 } 363 364 ASSERT(m_frontendWebView); 365 ASSERT(m_inspectedWebViewHwnd); 366 ASSERT(!IsWindowVisible(m_frontendHwnd)); 367 368 // Remove the Inspector's WebView from the inspected WebView's parent window. 369 WindowMessageBroadcaster::removeListener(m_inspectedWebViewHwnd, this); 370 371 m_attached = false; 372 373 m_frontendWebView->setHostWindow(reinterpret_cast<OLE_HANDLE>(m_frontendHwnd)); 374 375 // Make sure everything has the right size/position. 376 HWND hostWindow; 377 if (SUCCEEDED(m_inspectedWebView->hostWindow((OLE_HANDLE*)&hostWindow))) 378 SendMessage(hostWindow, WM_SIZE, 0, 0); 379} 380 381void WebInspectorFrontendClient::showWindowWithoutNotifications() 382{ 383 if (!m_frontendHwnd) 384 return; 385 386 ASSERT(m_frontendWebView); 387 ASSERT(m_inspectedWebViewHwnd); 388 389 bool shouldAttach = false; 390 if (m_attached) 391 shouldAttach = true; 392 else { 393 // If no preference is set - default to an attached window. This is important for inspector LayoutTests. 394 // FIXME: This flag can be fetched directly from the flags storage. 395 shouldAttach = m_inspectorClient->inspectorStartsAttached(); 396 397 if (shouldAttach && !canAttachWindow()) 398 shouldAttach = false; 399 } 400 401 if (!shouldAttach) { 402 // Put the Inspector's WebView inside our window and show it. 403 m_frontendWebView->setHostWindow(reinterpret_cast<OLE_HANDLE>(m_frontendHwnd)); 404 SendMessage(m_frontendHwnd, WM_SIZE, 0, 0); 405 updateWindowTitle(); 406 407 SetWindowPos(m_frontendHwnd, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); 408 return; 409 } 410 411 // Put the Inspector's WebView inside the inspected WebView's parent window. 412 WindowMessageBroadcaster::addListener(m_inspectedWebViewHwnd, this); 413 414 HWND hostWindow; 415 if (FAILED(m_inspectedWebView->hostWindow(reinterpret_cast<OLE_HANDLE*>(&hostWindow)))) 416 return; 417 418 m_frontendWebView->setHostWindow(reinterpret_cast<OLE_HANDLE>(hostWindow)); 419 420 // Then hide our own window. 421 ShowWindow(m_frontendHwnd, SW_HIDE); 422 423 m_attached = true; 424 425 // Make sure everything has the right size/position. 426 SendMessage(hostWindow, WM_SIZE, 0, 0); 427 m_inspectorClient->updateHighlight(); 428} 429 430void WebInspectorFrontendClient::destroyInspectorView(bool notifyInspectorController) 431{ 432 m_inspectorClient->releaseFrontend(); 433 434 if (m_destroyingInspectorView) 435 return; 436 m_destroyingInspectorView = true; 437 438 closeWindowWithoutNotifications(); 439 440 if (notifyInspectorController) { 441 m_inspectedWebView->page()->inspectorController()->disconnectFrontend(); 442 m_inspectorClient->updateHighlight(); 443 } 444 ::DestroyWindow(m_frontendHwnd); 445} 446 447void WebInspectorFrontendClient::updateWindowTitle() 448{ 449 String title = makeString("Web Inspector ", static_cast<UChar>(0x2014), ' ', m_inspectedURL); 450 ::SetWindowText(m_frontendHwnd, title.charactersWithNullTermination()); 451} 452 453LRESULT WebInspectorFrontendClient::onGetMinMaxInfo(WPARAM, LPARAM lParam) 454{ 455 MINMAXINFO* info = reinterpret_cast<MINMAXINFO*>(lParam); 456 POINT size = {400, 400}; 457 info->ptMinTrackSize = size; 458 459 return 0; 460} 461 462LRESULT WebInspectorFrontendClient::onSize(WPARAM, LPARAM) 463{ 464 RECT rect; 465 ::GetClientRect(m_frontendHwnd, &rect); 466 467 ::SetWindowPos(m_frontendWebViewHwnd, 0, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); 468 469 return 0; 470} 471 472LRESULT WebInspectorFrontendClient::onClose(WPARAM, LPARAM) 473{ 474 ::ShowWindow(m_frontendHwnd, SW_HIDE); 475 m_inspectedWebView->page()->inspectorController()->close(); 476 477 return 0; 478} 479 480LRESULT WebInspectorFrontendClient::onSetFocus() 481{ 482 SetFocus(m_frontendWebViewHwnd); 483 return 0; 484} 485 486void WebInspectorFrontendClient::onWebViewWindowPosChanging(WPARAM, LPARAM lParam) 487{ 488 ASSERT(m_attached); 489 490 WINDOWPOS* windowPos = reinterpret_cast<WINDOWPOS*>(lParam); 491 ASSERT_ARG(lParam, windowPos); 492 493 if (windowPos->flags & SWP_NOSIZE) 494 return; 495 496 RECT inspectorRect; 497 GetClientRect(m_frontendWebViewHwnd, &inspectorRect); 498 unsigned inspectorHeight = inspectorRect.bottom - inspectorRect.top; 499 500 windowPos->cy -= inspectorHeight; 501 502 SetWindowPos(m_frontendWebViewHwnd, 0, windowPos->x, windowPos->y + windowPos->cy, windowPos->cx, inspectorHeight, SWP_NOZORDER); 503} 504 505static LRESULT CALLBACK WebInspectorWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 506{ 507 WebInspectorFrontendClient* client = reinterpret_cast<WebInspectorFrontendClient*>(::GetProp(hwnd, kWebInspectorPointerProp)); 508 if (!client) 509 return ::DefWindowProc(hwnd, msg, wParam, lParam); 510 511 switch (msg) { 512 case WM_GETMINMAXINFO: 513 return client->onGetMinMaxInfo(wParam, lParam); 514 case WM_SIZE: 515 return client->onSize(wParam, lParam); 516 case WM_CLOSE: 517 return client->onClose(wParam, lParam); 518 case WM_SETFOCUS: 519 return client->onSetFocus(); 520 default: 521 break; 522 } 523 524 return ::DefWindowProc(hwnd, msg, wParam, lParam); 525} 526 527void WebInspectorFrontendClient::windowReceivedMessage(HWND, UINT msg, WPARAM wParam, LPARAM lParam) 528{ 529 switch (msg) { 530 case WM_WINDOWPOSCHANGING: 531 onWebViewWindowPosChanging(wParam, lParam); 532 break; 533 default: 534 break; 535 } 536} 537 538static ATOM registerWindowClass() 539{ 540 static bool haveRegisteredWindowClass = false; 541 542 if (haveRegisteredWindowClass) 543 return true; 544 545 WNDCLASSEX wcex; 546 547 wcex.cbSize = sizeof(WNDCLASSEX); 548 549 wcex.style = 0; 550 wcex.lpfnWndProc = WebInspectorWndProc; 551 wcex.cbClsExtra = 0; 552 wcex.cbWndExtra = 0; 553 wcex.hInstance = 0; 554 wcex.hIcon = 0; 555 wcex.hCursor = LoadCursor(0, IDC_ARROW); 556 wcex.hbrBackground = 0; 557 wcex.lpszMenuName = 0; 558 wcex.lpszClassName = kWebInspectorWindowClassName; 559 wcex.hIconSm = 0; 560 561 haveRegisteredWindowClass = true; 562 563 return ::RegisterClassEx(&wcex); 564} 565 566#endif // ENABLE(INSPECTOR) 567