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