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