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