Web Inspector: InspectorFrontendAPIDispatcher should not ignore all exceptions
[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 "ExceptionDetails.h"
39 #include "FloatRect.h"
40 #include "Frame.h"
41 #include "FrameLoadRequest.h"
42 #include "FrameLoader.h"
43 #include "FrameView.h"
44 #include "InspectorController.h"
45 #include "InspectorFrontendHost.h"
46 #include "InspectorPageAgent.h"
47 #include "Logging.h"
48 #include "Page.h"
49 #include "ScriptController.h"
50 #include "ScriptSourceCode.h"
51 #include "ScriptState.h"
52 #include "Settings.h"
53 #include "Timer.h"
54 #include "UserGestureIndicator.h"
55 #include "WindowFeatures.h"
56 #include <JavaScriptCore/FrameTracers.h>
57 #include <JavaScriptCore/InspectorBackendDispatchers.h>
58 #include <wtf/Deque.h>
59 #include <wtf/text/CString.h>
60
61
62 namespace WebCore {
63
64 using namespace Inspector;
65
66 static const char* inspectorAttachedHeightSetting = "inspectorAttachedHeight";
67 static const unsigned defaultAttachedHeight = 300;
68 static const float minimumAttachedHeight = 250.0f;
69 static const float maximumAttachedHeightRatio = 0.75f;
70 static const float minimumAttachedWidth = 500.0f;
71 static const float minimumAttachedInspectedWidth = 320.0f;
72
73 class InspectorBackendDispatchTask : public RefCounted<InspectorBackendDispatchTask> {
74     WTF_MAKE_FAST_ALLOCATED;
75 public:
76     static Ref<InspectorBackendDispatchTask> create(InspectorController* inspectedPageController)
77     {
78         return adoptRef(*new InspectorBackendDispatchTask(inspectedPageController));
79     }
80
81     void dispatch(const String& message)
82     {
83         ASSERT_ARG(message, !message.isEmpty());
84
85         m_messages.append(message);
86         scheduleOneShot();
87     }
88
89     void reset()
90     {
91         m_messages.clear();
92         m_inspectedPageController = nullptr;
93     }
94
95 private:
96     InspectorBackendDispatchTask(InspectorController* inspectedPageController)
97         : m_inspectedPageController(inspectedPageController)
98     {
99         ASSERT_ARG(inspectedPageController, inspectedPageController);
100     }
101
102     void scheduleOneShot()
103     {
104         if (m_hasScheduledTask)
105             return;
106         m_hasScheduledTask = true;
107
108         // The frontend can be closed and destroy the owning frontend client before or in the
109         // process of dispatching the task, so keep a protector reference here.
110         RunLoop::current().dispatch([this, protectedThis = makeRef(*this)] {
111             m_hasScheduledTask = false;
112             dispatchOneMessage();
113         });
114     }
115
116     void dispatchOneMessage()
117     {
118         // Owning frontend client may have been destroyed after the task was scheduled.
119         if (!m_inspectedPageController) {
120             ASSERT(m_messages.isEmpty());
121             return;
122         }
123
124         if (!m_messages.isEmpty())
125             m_inspectedPageController->dispatchMessageFromFrontend(m_messages.takeFirst());
126
127         if (!m_messages.isEmpty() && m_inspectedPageController)
128             scheduleOneShot();
129     }
130
131     InspectorController* m_inspectedPageController { nullptr };
132     Deque<String> m_messages;
133     bool m_hasScheduledTask { false };
134 };
135
136 String InspectorFrontendClientLocal::Settings::getProperty(const String&)
137 {
138     return String();
139 }
140
141 void InspectorFrontendClientLocal::Settings::setProperty(const String&, const String&)
142 {
143 }
144
145 void InspectorFrontendClientLocal::Settings::deleteProperty(const String&)
146 {
147 }
148
149 InspectorFrontendClientLocal::InspectorFrontendClientLocal(InspectorController* inspectedPageController, Page* frontendPage, std::unique_ptr<Settings> settings)
150     : m_inspectedPageController(inspectedPageController)
151     , m_frontendPage(frontendPage)
152     , m_settings(WTFMove(settings))
153     , m_dockSide(DockSide::Undocked)
154     , m_dispatchTask(InspectorBackendDispatchTask::create(inspectedPageController))
155     , m_frontendAPIDispatcher(InspectorFrontendAPIDispatcher::create(*frontendPage))
156 {
157     m_frontendPage->settings().setAllowFileAccessFromFileURLs(true);
158 }
159
160 InspectorFrontendClientLocal::~InspectorFrontendClientLocal()
161 {
162     if (m_frontendHost)
163         m_frontendHost->disconnectClient();
164     m_frontendPage = nullptr;
165     m_inspectedPageController = nullptr;
166     m_dispatchTask->reset();
167 }
168
169 void InspectorFrontendClientLocal::resetState()
170 {
171     m_settings->deleteProperty(inspectorAttachedHeightSetting);
172 }
173
174 void InspectorFrontendClientLocal::windowObjectCleared()
175 {
176     if (m_frontendHost)
177         m_frontendHost->disconnectClient();
178     
179     m_frontendHost = InspectorFrontendHost::create(this, m_frontendPage);
180     m_frontendHost->addSelfToGlobalObjectInWorld(debuggerWorld());
181 }
182
183 void InspectorFrontendClientLocal::frontendLoaded()
184 {
185     // Call setDockingUnavailable before bringToFront. If we display the inspector window via bringToFront first it causes
186     // the call to canAttachWindow to return the wrong result on Windows.
187     // Calling bringToFront first causes the visibleHeight of the inspected page to always return 0 immediately after.
188     // Thus if we call canAttachWindow first we can avoid this problem. This change does not cause any regressions on Mac.
189     setDockingUnavailable(!canAttachWindow());
190     bringToFront();
191
192     m_frontendAPIDispatcher->frontendLoaded();
193 }
194
195 void InspectorFrontendClientLocal::pagePaused()
196 {
197     // NOTE: pagePaused() and pageUnpaused() do not suspend/unsuspend the frontend API dispatcher
198     // for this subclass of InspectorFrontendClient. The inspected page and the frontend page
199     // exist in the same web process, so messages need to be sent even while the debugger is paused.
200     // Suspending here would stall out later commands that resume the debugger, causing the test to time out.
201 }
202
203 void InspectorFrontendClientLocal::pageUnpaused()
204 {
205 }
206
207 UserInterfaceLayoutDirection InspectorFrontendClientLocal::userInterfaceLayoutDirection() const
208 {
209     return m_frontendPage->userInterfaceLayoutDirection();
210 }
211
212 void InspectorFrontendClientLocal::requestSetDockSide(DockSide dockSide)
213 {
214     if (dockSide == DockSide::Undocked) {
215         detachWindow();
216         setAttachedWindow(dockSide);
217     } else if (canAttachWindow()) {
218         attachWindow(dockSide);
219         setAttachedWindow(dockSide);
220     }
221 }
222
223 bool InspectorFrontendClientLocal::canAttachWindow()
224 {
225     // Don't allow attaching to another inspector -- two inspectors in one window is too much!
226     bool isInspectorPage = m_inspectedPageController->inspectionLevel() > 0;
227     if (isInspectorPage)
228         return false;
229
230     // If we are already attached, allow attaching again to allow switching sides.
231     if (m_dockSide != DockSide::Undocked)
232         return true;
233
234     // Don't allow the attach if the window would be too small to accommodate the minimum inspector size.
235     unsigned inspectedPageHeight = m_inspectedPageController->inspectedPage().mainFrame().view()->visibleHeight();
236     unsigned inspectedPageWidth = m_inspectedPageController->inspectedPage().mainFrame().view()->visibleWidth();
237     unsigned maximumAttachedHeight = inspectedPageHeight * maximumAttachedHeightRatio;
238     return minimumAttachedHeight <= maximumAttachedHeight && minimumAttachedWidth <= inspectedPageWidth;
239 }
240
241 void InspectorFrontendClientLocal::setDockingUnavailable(bool unavailable)
242 {
243     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setDockingUnavailable"_s, { JSON::Value::create(unavailable) });
244 }
245
246 void InspectorFrontendClientLocal::changeAttachedWindowHeight(unsigned height)
247 {
248     unsigned totalHeight = m_frontendPage->mainFrame().view()->visibleHeight() + m_inspectedPageController->inspectedPage().mainFrame().view()->visibleHeight();
249     unsigned attachedHeight = constrainedAttachedWindowHeight(height, totalHeight);
250     m_settings->setProperty(inspectorAttachedHeightSetting, String::number(attachedHeight));
251     setAttachedWindowHeight(attachedHeight);
252 }
253
254 void InspectorFrontendClientLocal::changeAttachedWindowWidth(unsigned width)
255 {
256     unsigned totalWidth = m_frontendPage->mainFrame().view()->visibleWidth() + m_inspectedPageController->inspectedPage().mainFrame().view()->visibleWidth();
257     unsigned attachedWidth = constrainedAttachedWindowWidth(width, totalWidth);
258     setAttachedWindowWidth(attachedWidth);
259 }
260
261 void InspectorFrontendClientLocal::changeSheetRect(const FloatRect& rect)
262 {
263     setSheetRect(rect);
264 }
265
266 void InspectorFrontendClientLocal::openURLExternally(const String& url)
267 {
268     UserGestureIndicator indicator { ProcessingUserGesture };
269     Frame& mainFrame = m_inspectedPageController->inspectedPage().mainFrame();
270     FrameLoadRequest frameLoadRequest { *mainFrame.document(), mainFrame.document()->securityOrigin(), { }, "_blank"_s, InitiatedByMainFrame::Unknown };
271
272     bool created;
273     auto frame = WebCore::createWindow(mainFrame, mainFrame, WTFMove(frameLoadRequest), { }, created);
274     if (!frame)
275         return;
276
277     frame->loader().setOpener(&mainFrame);
278     frame->page()->setOpenedByDOM();
279
280     // FIXME: Why do we compute the absolute URL with respect to |frame| instead of |mainFrame|?
281     ResourceRequest resourceRequest { frame->document()->completeURL(url) };
282     FrameLoadRequest frameLoadRequest2 { *mainFrame.document(), mainFrame.document()->securityOrigin(), WTFMove(resourceRequest), "_self"_s, InitiatedByMainFrame::Unknown };
283     frame->loader().changeLocation(WTFMove(frameLoadRequest2));
284 }
285
286 void InspectorFrontendClientLocal::moveWindowBy(float x, float y)
287 {
288     FloatRect frameRect = m_frontendPage->chrome().windowRect();
289     frameRect.move(x, y);
290     m_frontendPage->chrome().setWindowRect(frameRect);
291 }
292
293 void InspectorFrontendClientLocal::setAttachedWindow(DockSide dockSide)
294 {
295     const char* side = "undocked";
296     switch (dockSide) {
297     case DockSide::Undocked:
298         side = "undocked";
299         break;
300     case DockSide::Right:
301         side = "right";
302         break;
303     case DockSide::Left:
304         side = "left";
305         break;
306     case DockSide::Bottom:
307         side = "bottom";
308         break;
309     }
310
311     m_dockSide = dockSide;
312
313     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setDockSide"_s, { JSON::Value::create(makeString(side)) });
314 }
315
316 void InspectorFrontendClientLocal::restoreAttachedWindowHeight()
317 {
318     unsigned inspectedPageHeight = m_inspectedPageController->inspectedPage().mainFrame().view()->visibleHeight();
319     String value = m_settings->getProperty(inspectorAttachedHeightSetting);
320     unsigned preferredHeight = value.isEmpty() ? defaultAttachedHeight : value.toUInt();
321     
322     // This call might not go through (if the window starts out detached), but if the window is initially created attached,
323     // InspectorController::attachWindow is never called, so we need to make sure to set the attachedWindowHeight.
324     // FIXME: Clean up code so we only have to call setAttachedWindowHeight in InspectorController::attachWindow
325     setAttachedWindowHeight(constrainedAttachedWindowHeight(preferredHeight, inspectedPageHeight));
326 }
327
328 Optional<bool> InspectorFrontendClientLocal::evaluationResultToBoolean(InspectorFrontendAPIDispatcher::EvaluationResult result)
329 {
330     if (!result)
331         return WTF::nullopt;
332
333     auto valueOrException = result.value();
334     if (!valueOrException) {
335         LOG(Inspector, "Encountered exception while evaluating upon the frontend: %s", valueOrException.error().message.utf8().data());
336         return WTF::nullopt;
337     }
338
339     return valueOrException.value().toBoolean(m_frontendAPIDispatcher->frontendGlobalObject());
340 }
341
342 bool InspectorFrontendClientLocal::isDebuggingEnabled()
343 {
344     return evaluationResultToBoolean(m_frontendAPIDispatcher->dispatchCommandWithResultSync("isDebuggingEnabled"_s)).valueOr(false);
345 }
346
347 void InspectorFrontendClientLocal::setDebuggingEnabled(bool enabled)
348 {
349     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setDebuggingEnabled"_s, { JSON::Value::create(enabled) });
350 }
351
352 bool InspectorFrontendClientLocal::isTimelineProfilingEnabled()
353 {
354     return evaluationResultToBoolean(m_frontendAPIDispatcher->dispatchCommandWithResultSync("isTimelineProfilingEnabled"_s)).valueOr(false);
355 }
356
357 void InspectorFrontendClientLocal::setTimelineProfilingEnabled(bool enabled)
358 {
359     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("setTimelineProfilingEnabled"_s, { JSON::Value::create(enabled) });
360 }
361
362 bool InspectorFrontendClientLocal::isProfilingJavaScript()
363 {
364     return evaluationResultToBoolean(m_frontendAPIDispatcher->dispatchCommandWithResultSync("isProfilingJavaScript"_s)).valueOr(false);
365 }
366
367 void InspectorFrontendClientLocal::startProfilingJavaScript()
368 {
369     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("startProfilingJavaScript"_s);
370 }
371
372 void InspectorFrontendClientLocal::stopProfilingJavaScript()
373 {
374     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("stopProfilingJavaScript"_s);
375 }
376
377 void InspectorFrontendClientLocal::showConsole()
378 {
379     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("showConsole"_s);
380 }
381
382 void InspectorFrontendClientLocal::showResources()
383 {
384     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("showResources"_s);
385 }
386
387 void InspectorFrontendClientLocal::showMainResourceForFrame(Frame* frame)
388 {
389     String frameId = m_inspectedPageController->ensurePageAgent().frameId(frame);
390     m_frontendAPIDispatcher->dispatchCommandWithResultAsync("showMainResourceForFrame"_s, { JSON::Value::create(frameId) });
391 }
392
393 unsigned InspectorFrontendClientLocal::constrainedAttachedWindowHeight(unsigned preferredHeight, unsigned totalWindowHeight)
394 {
395     return roundf(std::max(minimumAttachedHeight, std::min<float>(preferredHeight, totalWindowHeight * maximumAttachedHeightRatio)));
396 }
397
398 unsigned InspectorFrontendClientLocal::constrainedAttachedWindowWidth(unsigned preferredWidth, unsigned totalWindowWidth)
399 {
400     return roundf(std::max(minimumAttachedWidth, std::min<float>(preferredWidth, totalWindowWidth - minimumAttachedInspectedWidth)));
401 }
402
403 void InspectorFrontendClientLocal::sendMessageToBackend(const String& message)
404 {
405     m_dispatchTask->dispatch(message);
406 }
407
408 bool InspectorFrontendClientLocal::isUnderTest()
409 {
410     return m_inspectedPageController->isUnderTest();
411 }
412
413 unsigned InspectorFrontendClientLocal::inspectionLevel() const
414 {
415     return m_inspectedPageController->inspectionLevel() + 1;
416 }
417
418 Page* InspectorFrontendClientLocal::inspectedPage() const
419 {
420     if (!m_inspectedPageController)
421         return nullptr;
422
423     return &m_inspectedPageController->inspectedPage();
424 }
425
426 } // namespace WebCore