[LFC][Floats] FloatAvoider does not need to keep a pointer to Layout::Box around.
[WebKit-https.git] / Source / WebCore / inspector / InspectorFrontendClientLocal.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2015-2020 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "InspectorFrontendClientLocal.h"
34
35 #include "Chrome.h"
36 #include "DOMWrapperWorld.h"
37 #include "Document.h"
38 #include "FloatRect.h"
39 #include "Frame.h"
40 #include "FrameLoadRequest.h"
41 #include "FrameLoader.h"
42 #include "FrameView.h"
43 #include "InspectorController.h"
44 #include "InspectorFrontendHost.h"
45 #include "InspectorPageAgent.h"
46 #include "Page.h"
47 #include "ScriptController.h"
48 #include "ScriptSourceCode.h"
49 #include "ScriptState.h"
50 #include "Settings.h"
51 #include "Timer.h"
52 #include "UserGestureIndicator.h"
53 #include "WindowFeatures.h"
54 #include <JavaScriptCore/FrameTracers.h>
55 #include <JavaScriptCore/InspectorBackendDispatchers.h>
56 #include <wtf/Deque.h>
57 #include <wtf/text/CString.h>
58
59
60 namespace WebCore {
61
62 using namespace Inspector;
63
64 static const char* inspectorAttachedHeightSetting = "inspectorAttachedHeight";
65 static const unsigned defaultAttachedHeight = 300;
66 static const float minimumAttachedHeight = 250.0f;
67 static const float maximumAttachedHeightRatio = 0.75f;
68 static const float minimumAttachedWidth = 500.0f;
69 static const float minimumAttachedInspectedWidth = 320.0f;
70
71 class InspectorBackendDispatchTask : public RefCounted<InspectorBackendDispatchTask> {
72     WTF_MAKE_FAST_ALLOCATED;
73 public:
74     static Ref<InspectorBackendDispatchTask> create(InspectorController* inspectedPageController)
75     {
76         return adoptRef(*new InspectorBackendDispatchTask(inspectedPageController));
77     }
78
79     void dispatch(const String& message)
80     {
81         ASSERT_ARG(message, !message.isEmpty());
82
83         m_messages.append(message);
84         scheduleOneShot();
85     }
86
87     void reset()
88     {
89         m_messages.clear();
90         m_inspectedPageController = nullptr;
91     }
92
93 private:
94     InspectorBackendDispatchTask(InspectorController* inspectedPageController)
95         : m_inspectedPageController(inspectedPageController)
96     {
97         ASSERT_ARG(inspectedPageController, inspectedPageController);
98     }
99
100     void scheduleOneShot()
101     {
102         if (m_hasScheduledTask)
103             return;
104         m_hasScheduledTask = true;
105
106         // The frontend can be closed and destroy the owning frontend client before or in the
107         // process of dispatching the task, so keep a protector reference here.
108         RunLoop::current().dispatch([this, protectedThis = makeRef(*this)] {
109             m_hasScheduledTask = false;
110             dispatchOneMessage();
111         });
112     }
113
114     void dispatchOneMessage()
115     {
116         // Owning frontend client may have been destroyed after the task was scheduled.
117         if (!m_inspectedPageController) {
118             ASSERT(m_messages.isEmpty());
119             return;
120         }
121
122         if (!m_messages.isEmpty())
123             m_inspectedPageController->dispatchMessageFromFrontend(m_messages.takeFirst());
124
125         if (!m_messages.isEmpty() && m_inspectedPageController)
126             scheduleOneShot();
127     }
128
129     InspectorController* m_inspectedPageController { nullptr };
130     Deque<String> m_messages;
131     bool m_hasScheduledTask { false };
132 };
133
134 String InspectorFrontendClientLocal::Settings::getProperty(const String&)
135 {
136     return String();
137 }
138
139 void InspectorFrontendClientLocal::Settings::setProperty(const String&, const String&)
140 {
141 }
142
143 void InspectorFrontendClientLocal::Settings::deleteProperty(const String&)
144 {
145 }
146
147 InspectorFrontendClientLocal::InspectorFrontendClientLocal(InspectorController* inspectedPageController, Page* frontendPage, std::unique_ptr<Settings> settings)
148     : m_inspectedPageController(inspectedPageController)
149     , m_frontendPage(frontendPage)
150     , m_settings(WTFMove(settings))
151     , m_dockSide(DockSide::Undocked)
152     , m_dispatchTask(InspectorBackendDispatchTask::create(inspectedPageController))
153     , m_frontendAPIDispatcher(InspectorFrontendAPIDispatcher::create(*frontendPage))
154 {
155     m_frontendPage->settings().setAllowFileAccessFromFileURLs(true);
156 }
157
158 InspectorFrontendClientLocal::~InspectorFrontendClientLocal()
159 {
160     if (m_frontendHost)
161         m_frontendHost->disconnectClient();
162     m_frontendPage = nullptr;
163     m_inspectedPageController = nullptr;
164     m_dispatchTask->reset();
165 }
166
167 void InspectorFrontendClientLocal::resetState()
168 {
169     m_settings->deleteProperty(inspectorAttachedHeightSetting);
170 }
171
172 void InspectorFrontendClientLocal::windowObjectCleared()
173 {
174     if (m_frontendHost)
175         m_frontendHost->disconnectClient();
176     
177     m_frontendHost = InspectorFrontendHost::create(this, m_frontendPage);
178     m_frontendHost->addSelfToGlobalObjectInWorld(debuggerWorld());
179 }
180
181 void InspectorFrontendClientLocal::frontendLoaded()
182 {
183     // Call setDockingUnavailable before bringToFront. If we display the inspector window via bringToFront first it causes
184     // the call to canAttachWindow to return the wrong result on Windows.
185     // Calling bringToFront first causes the visibleHeight of the inspected page to always return 0 immediately after.
186     // Thus if we call canAttachWindow first we can avoid this problem. This change does not cause any regressions on Mac.
187     setDockingUnavailable(!canAttachWindow());
188     bringToFront();
189
190     m_frontendAPIDispatcher->frontendLoaded();
191 }
192
193 void InspectorFrontendClientLocal::pagePaused()
194 {
195     // NOTE: pagePaused() and pageUnpaused() do not suspend/unsuspend the frontend API dispatcher
196     // for this subclass of InspectorFrontendClient. The inspected page and the frontend page
197     // exist in the same web process, so messages need to be sent even while the debugger is paused.
198     // Suspending here would stall out later commands that resume the debugger, causing the test to time out.
199 }
200
201 void InspectorFrontendClientLocal::pageUnpaused()
202 {
203 }
204
205 UserInterfaceLayoutDirection InspectorFrontendClientLocal::userInterfaceLayoutDirection() const
206 {
207     return m_frontendPage->userInterfaceLayoutDirection();
208 }
209
210 void InspectorFrontendClientLocal::requestSetDockSide(DockSide dockSide)
211 {
212     if (dockSide == DockSide::Undocked) {
213         detachWindow();
214         setAttachedWindow(dockSide);
215     } else if (canAttachWindow()) {
216         attachWindow(dockSide);
217         setAttachedWindow(dockSide);
218     }
219 }
220
221 bool InspectorFrontendClientLocal::canAttachWindow()
222 {
223     // Don't allow attaching to another inspector -- two inspectors in one window is too much!
224     bool isInspectorPage = m_inspectedPageController->inspectionLevel() > 0;
225     if (isInspectorPage)
226         return false;
227
228     // If we are already attached, allow attaching again to allow switching sides.
229     if (m_dockSide != DockSide::Undocked)
230         return true;
231
232     // Don't allow the attach if the window would be too small to accommodate the minimum inspector size.
233     unsigned inspectedPageHeight = m_inspectedPageController->inspectedPage().mainFrame().view()->visibleHeight();
234     unsigned inspectedPageWidth = m_inspectedPageController->inspectedPage().mainFrame().view()->visibleWidth();
235     unsigned maximumAttachedHeight = inspectedPageHeight * maximumAttachedHeightRatio;
236     return minimumAttachedHeight <= maximumAttachedHeight && minimumAttachedWidth <= inspectedPageWidth;
237 }
238
239 void InspectorFrontendClientLocal::setDockingUnavailable(bool unavailable)
240 {
241     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setDockingUnavailable"_s, { JSON::Value::create(unavailable) });
242 }
243
244 void InspectorFrontendClientLocal::changeAttachedWindowHeight(unsigned height)
245 {
246     unsigned totalHeight = m_frontendPage->mainFrame().view()->visibleHeight() + m_inspectedPageController->inspectedPage().mainFrame().view()->visibleHeight();
247     unsigned attachedHeight = constrainedAttachedWindowHeight(height, totalHeight);
248     m_settings->setProperty(inspectorAttachedHeightSetting, String::number(attachedHeight));
249     setAttachedWindowHeight(attachedHeight);
250 }
251
252 void InspectorFrontendClientLocal::changeAttachedWindowWidth(unsigned width)
253 {
254     unsigned totalWidth = m_frontendPage->mainFrame().view()->visibleWidth() + m_inspectedPageController->inspectedPage().mainFrame().view()->visibleWidth();
255     unsigned attachedWidth = constrainedAttachedWindowWidth(width, totalWidth);
256     setAttachedWindowWidth(attachedWidth);
257 }
258
259 void InspectorFrontendClientLocal::changeSheetRect(const FloatRect& rect)
260 {
261     setSheetRect(rect);
262 }
263
264 void InspectorFrontendClientLocal::openURLExternally(const String& url)
265 {
266     UserGestureIndicator indicator { ProcessingUserGesture };
267     Frame& mainFrame = m_inspectedPageController->inspectedPage().mainFrame();
268     FrameLoadRequest frameLoadRequest { *mainFrame.document(), mainFrame.document()->securityOrigin(), { }, "_blank"_s, InitiatedByMainFrame::Unknown };
269
270     bool created;
271     auto frame = WebCore::createWindow(mainFrame, mainFrame, WTFMove(frameLoadRequest), { }, created);
272     if (!frame)
273         return;
274
275     frame->loader().setOpener(&mainFrame);
276     frame->page()->setOpenedByDOM();
277
278     // FIXME: Why do we compute the absolute URL with respect to |frame| instead of |mainFrame|?
279     ResourceRequest resourceRequest { frame->document()->completeURL(url) };
280     FrameLoadRequest frameLoadRequest2 { *mainFrame.document(), mainFrame.document()->securityOrigin(), WTFMove(resourceRequest), "_self"_s, InitiatedByMainFrame::Unknown };
281     frame->loader().changeLocation(WTFMove(frameLoadRequest2));
282 }
283
284 void InspectorFrontendClientLocal::moveWindowBy(float x, float y)
285 {
286     FloatRect frameRect = m_frontendPage->chrome().windowRect();
287     frameRect.move(x, y);
288     m_frontendPage->chrome().setWindowRect(frameRect);
289 }
290
291 void InspectorFrontendClientLocal::setAttachedWindow(DockSide dockSide)
292 {
293     const char* side = "undocked";
294     switch (dockSide) {
295     case DockSide::Undocked:
296         side = "undocked";
297         break;
298     case DockSide::Right:
299         side = "right";
300         break;
301     case DockSide::Left:
302         side = "left";
303         break;
304     case DockSide::Bottom:
305         side = "bottom";
306         break;
307     }
308
309     m_dockSide = dockSide;
310
311     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setDockSide"_s, { JSON::Value::create(makeString(side)) });
312 }
313
314 void InspectorFrontendClientLocal::restoreAttachedWindowHeight()
315 {
316     unsigned inspectedPageHeight = m_inspectedPageController->inspectedPage().mainFrame().view()->visibleHeight();
317     String value = m_settings->getProperty(inspectorAttachedHeightSetting);
318     unsigned preferredHeight = value.isEmpty() ? defaultAttachedHeight : value.toUInt();
319     
320     // This call might not go through (if the window starts out detached), but if the window is initially created attached,
321     // InspectorController::attachWindow is never called, so we need to make sure to set the attachedWindowHeight.
322     // FIXME: Clean up code so we only have to call setAttachedWindowHeight in InspectorController::attachWindow
323     setAttachedWindowHeight(constrainedAttachedWindowHeight(preferredHeight, inspectedPageHeight));
324 }
325
326 bool InspectorFrontendClientLocal::isDebuggingEnabled()
327 {
328     auto result = m_frontendAPIDispatcher->dispatchCommandWithResultSync("isDebuggingEnabled"_s);
329     return result && result.value().toBoolean(m_frontendAPIDispatcher->frontendGlobalObject());
330
331 }
332
333 void InspectorFrontendClientLocal::setDebuggingEnabled(bool enabled)
334 {
335     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setDebuggingEnabled"_s, { JSON::Value::create(enabled) });
336 }
337
338 bool InspectorFrontendClientLocal::isTimelineProfilingEnabled()
339 {
340     auto result = m_frontendAPIDispatcher->dispatchCommandWithResultSync("isTimelineProfilingEnabled"_s);
341     return result && result.value().toBoolean(m_frontendAPIDispatcher->frontendGlobalObject());
342 }
343
344 void InspectorFrontendClientLocal::setTimelineProfilingEnabled(bool enabled)
345 {
346     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setTimelineProfilingEnabled"_s, { JSON::Value::create(enabled) });
347 }
348
349 bool InspectorFrontendClientLocal::isProfilingJavaScript()
350 {
351     auto result = m_frontendAPIDispatcher->dispatchCommandWithResultSync("isProfilingJavaScript"_s);
352     return result && result.value().toBoolean(m_frontendAPIDispatcher->frontendGlobalObject());
353 }
354
355 void InspectorFrontendClientLocal::startProfilingJavaScript()
356 {
357     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("startProfilingJavaScript"_s);
358 }
359
360 void InspectorFrontendClientLocal::stopProfilingJavaScript()
361 {
362     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("stopProfilingJavaScript"_s);
363 }
364
365 void InspectorFrontendClientLocal::showConsole()
366 {
367     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("showConsole"_s);
368 }
369
370 void InspectorFrontendClientLocal::showResources()
371 {
372     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("showResources"_s);
373 }
374
375 void InspectorFrontendClientLocal::showMainResourceForFrame(Frame* frame)
376 {
377     String frameId = m_inspectedPageController->ensurePageAgent().frameId(frame);
378     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("showMainResourceForFrame"_s, { JSON::Value::create(frameId) });
379 }
380
381 unsigned InspectorFrontendClientLocal::constrainedAttachedWindowHeight(unsigned preferredHeight, unsigned totalWindowHeight)
382 {
383     return roundf(std::max(minimumAttachedHeight, std::min<float>(preferredHeight, totalWindowHeight * maximumAttachedHeightRatio)));
384 }
385
386 unsigned InspectorFrontendClientLocal::constrainedAttachedWindowWidth(unsigned preferredWidth, unsigned totalWindowWidth)
387 {
388     return roundf(std::max(minimumAttachedWidth, std::min<float>(preferredWidth, totalWindowWidth - minimumAttachedInspectedWidth)));
389 }
390
391 void InspectorFrontendClientLocal::sendMessageToBackend(const String& message)
392 {
393     m_dispatchTask->dispatch(message);
394 }
395
396 bool InspectorFrontendClientLocal::isUnderTest()
397 {
398     return m_inspectedPageController->isUnderTest();
399 }
400
401 unsigned InspectorFrontendClientLocal::inspectionLevel() const
402 {
403     return m_inspectedPageController->inspectionLevel() + 1;
404 }
405
406 Page* InspectorFrontendClientLocal::inspectedPage() const
407 {
408     if (!m_inspectedPageController)
409         return nullptr;
410
411     return &m_inspectedPageController->inspectedPage();
412 }
413
414 } // namespace WebCore