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
52using namespace WebCore;
53
54static LPCTSTR kWebInspectorWindowClassName = TEXT("WebInspectorWindowClass");
55static ATOM registerWindowClass();
56static LPCTSTR kWebInspectorPointerProp = TEXT("WebInspectorPointer");
57
58static const IntRect& defaultWindowRect()
59{
60    static IntRect rect(60, 200, 750, 650);
61    return rect;
62}
63
64static CFBundleRef getWebKitBundle()
65{
66    return CFBundleGetBundleWithIdentifier(CFSTR("com.apple.WebKit"));
67}
68
69WebInspectorClient::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
77WebInspectorClient::~WebInspectorClient()
78{
79    m_frontendPage = 0;
80}
81
82void WebInspectorClient::inspectorDestroyed()
83{
84    delete this;
85}
86
87void 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
179void 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
195void WebInspectorClient::hideHighlight()
196{
197    if (m_highlight)
198        m_highlight->setShowsWhileWebViewIsVisible(false);
199}
200
201void WebInspectorClient::updateHighlight()
202{
203    if (m_highlight && m_highlight->isShowing())
204        m_highlight->update();
205}
206
207WebInspectorFrontendClient::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
225WebInspectorFrontendClient::~WebInspectorFrontendClient()
226{
227    destroyInspectorView(true);
228}
229
230void WebInspectorFrontendClient::frontendLoaded()
231{
232    InspectorFrontendClientLocal::frontendLoaded();
233
234    setAttachedWindow(m_attached);
235}
236
237String 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
246String WebInspectorFrontendClient::hiddenPanels()
247{
248    // FIXME: implement this
249    return String();
250}
251
252void WebInspectorFrontendClient::bringToFront()
253{
254    showWindowWithoutNotifications();
255}
256
257void WebInspectorFrontendClient::closeWindow()
258{
259    destroyInspectorView(true);
260}
261
262void WebInspectorFrontendClient::disconnectFromBackend()
263{
264    destroyInspectorView(false);
265}
266
267void WebInspectorFrontendClient::attachWindow()
268{
269    if (m_attached)
270        return;
271
272    m_inspectorClient->setInspectorStartsAttached(true);
273
274    closeWindowWithoutNotifications();
275    showWindowWithoutNotifications();
276}
277
278void WebInspectorFrontendClient::detachWindow()
279{
280    if (!m_attached)
281        return;
282
283    m_inspectorClient->setInspectorStartsAttached(false);
284
285    closeWindowWithoutNotifications();
286    showWindowWithoutNotifications();
287}
288
289void 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
317void WebInspectorFrontendClient::inspectedURLChanged(const String& newURL)
318{
319    m_inspectedURL = newURL;
320    updateWindowTitle();
321}
322
323void WebInspectorFrontendClient::saveSessionSetting(const String& key, const String& value)
324{
325    m_inspectorClient->saveSessionSetting(key, value);
326}
327
328void WebInspectorFrontendClient::loadSessionSetting(const String& key, String* value)
329{
330    m_inspectorClient->loadSessionSetting(key, value);
331}
332
333void 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
360void 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
409void 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
426void WebInspectorFrontendClient::updateWindowTitle()
427{
428    String title = makeString("Web Inspector ", static_cast<UChar>(0x2014), ' ', m_inspectedURL);
429    ::SetWindowText(m_frontendHwnd, title.charactersWithNullTermination());
430}
431
432LRESULT 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
441LRESULT 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
451LRESULT WebInspectorFrontendClient::onClose(WPARAM, LPARAM)
452{
453    ::ShowWindow(m_frontendHwnd, SW_HIDE);
454    m_inspectedWebView->page()->inspectorController()->close();
455
456    return 0;
457}
458
459LRESULT WebInspectorFrontendClient::onSetFocus()
460{
461    SetFocus(m_frontendWebViewHwnd);
462    return 0;
463}
464
465void 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
484static 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
506void 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
517static 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