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