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 | |
58 | namespace WebCore { |
59 | |
60 | using namespace Inspector; |
61 | |
62 | static const char* inspectorAttachedHeightSetting = "inspectorAttachedHeight" ; |
63 | static const unsigned defaultAttachedHeight = 300; |
64 | static const float minimumAttachedHeight = 250.0f; |
65 | static const float maximumAttachedHeightRatio = 0.75f; |
66 | static const float minimumAttachedWidth = 500.0f; |
67 | static const float minimumAttachedInspectedWidth = 320.0f; |
68 | |
69 | class InspectorBackendDispatchTask : public RefCounted<InspectorBackendDispatchTask> { |
70 | WTF_MAKE_FAST_ALLOCATED; |
71 | public: |
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 | |
108 | private: |
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 | |
121 | String InspectorFrontendClientLocal::Settings::getProperty(const String&) |
122 | { |
123 | return String(); |
124 | } |
125 | |
126 | void InspectorFrontendClientLocal::Settings::setProperty(const String&, const String&) |
127 | { |
128 | } |
129 | |
130 | InspectorFrontendClientLocal::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 | |
140 | InspectorFrontendClientLocal::~InspectorFrontendClientLocal() |
141 | { |
142 | if (m_frontendHost) |
143 | m_frontendHost->disconnectClient(); |
144 | m_frontendPage = nullptr; |
145 | m_inspectedPageController = nullptr; |
146 | m_dispatchTask->reset(); |
147 | } |
148 | |
149 | void 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 | |
158 | void 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 | |
172 | UserInterfaceLayoutDirection InspectorFrontendClientLocal::userInterfaceLayoutDirection() const |
173 | { |
174 | return m_frontendPage->userInterfaceLayoutDirection(); |
175 | } |
176 | |
177 | void 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 | |
188 | bool 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 | |
206 | void InspectorFrontendClientLocal::setDockingUnavailable(bool unavailable) |
207 | { |
208 | evaluateOnLoad(makeString("[\"setDockingUnavailable\", " , unavailable ? "true" : "false" , ']')); |
209 | } |
210 | |
211 | void 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 | |
219 | void 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 | |
226 | void InspectorFrontendClientLocal::changeSheetRect(const FloatRect& rect) |
227 | { |
228 | setSheetRect(rect); |
229 | } |
230 | |
231 | void 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 | |
251 | void 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 | |
258 | void 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 | |
281 | void 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 | |
293 | bool InspectorFrontendClientLocal::isDebuggingEnabled() |
294 | { |
295 | if (m_frontendLoaded) |
296 | return evaluateAsBoolean("[\"isDebuggingEnabled\"]" ); |
297 | return false; |
298 | } |
299 | |
300 | void InspectorFrontendClientLocal::setDebuggingEnabled(bool enabled) |
301 | { |
302 | evaluateOnLoad(makeString("[\"setDebuggingEnabled\", " , enabled ? "true" : "false" , ']')); |
303 | } |
304 | |
305 | bool InspectorFrontendClientLocal::isTimelineProfilingEnabled() |
306 | { |
307 | if (m_frontendLoaded) |
308 | return evaluateAsBoolean("[\"isTimelineProfilingEnabled\"]" ); |
309 | return false; |
310 | } |
311 | |
312 | void InspectorFrontendClientLocal::setTimelineProfilingEnabled(bool enabled) |
313 | { |
314 | evaluateOnLoad(makeString("[\"setTimelineProfilingEnabled\", " , enabled ? "true" : "false" , ']')); |
315 | } |
316 | |
317 | bool InspectorFrontendClientLocal::isProfilingJavaScript() |
318 | { |
319 | if (m_frontendLoaded) |
320 | return evaluateAsBoolean("[\"isProfilingJavaScript\"]" ); |
321 | return false; |
322 | } |
323 | |
324 | void InspectorFrontendClientLocal::startProfilingJavaScript() |
325 | { |
326 | evaluateOnLoad("[\"startProfilingJavaScript\"]" ); |
327 | } |
328 | |
329 | void InspectorFrontendClientLocal::stopProfilingJavaScript() |
330 | { |
331 | evaluateOnLoad("[\"stopProfilingJavaScript\"]" ); |
332 | } |
333 | |
334 | void InspectorFrontendClientLocal::showConsole() |
335 | { |
336 | evaluateOnLoad("[\"showConsole\"]" ); |
337 | } |
338 | |
339 | void InspectorFrontendClientLocal::showResources() |
340 | { |
341 | evaluateOnLoad("[\"showResources\"]" ); |
342 | } |
343 | |
344 | void InspectorFrontendClientLocal::showMainResourceForFrame(Frame* frame) |
345 | { |
346 | String frameId = m_inspectedPageController->ensurePageAgent().frameId(frame); |
347 | evaluateOnLoad(makeString("[\"showMainResourceForFrame\", \"" , frameId, "\"]" )); |
348 | } |
349 | |
350 | unsigned InspectorFrontendClientLocal::constrainedAttachedWindowHeight(unsigned preferredHeight, unsigned totalWindowHeight) |
351 | { |
352 | return roundf(std::max(minimumAttachedHeight, std::min<float>(preferredHeight, totalWindowHeight * maximumAttachedHeightRatio))); |
353 | } |
354 | |
355 | unsigned InspectorFrontendClientLocal::constrainedAttachedWindowWidth(unsigned preferredWidth, unsigned totalWindowWidth) |
356 | { |
357 | return roundf(std::max(minimumAttachedWidth, std::min<float>(preferredWidth, totalWindowWidth - minimumAttachedInspectedWidth))); |
358 | } |
359 | |
360 | void InspectorFrontendClientLocal::sendMessageToBackend(const String& message) |
361 | { |
362 | m_dispatchTask->dispatch(message); |
363 | } |
364 | |
365 | bool InspectorFrontendClientLocal::isUnderTest() |
366 | { |
367 | return m_inspectedPageController->isUnderTest(); |
368 | } |
369 | |
370 | unsigned InspectorFrontendClientLocal::inspectionLevel() const |
371 | { |
372 | return m_inspectedPageController->inspectionLevel() + 1; |
373 | } |
374 | |
375 | bool 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 | |
381 | void 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 | |
389 | Page* InspectorFrontendClientLocal::inspectedPage() const |
390 | { |
391 | if (!m_inspectedPageController) |
392 | return nullptr; |
393 | |
394 | return &m_inspectedPageController->inspectedPage(); |
395 | } |
396 | |
397 | } // namespace WebCore |
398 | |