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