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