2373e82119879d2109664797bdc72ea193736b08
[WebKit-https.git] / Source / WebCore / inspector / InspectorFrontendClientLocal.cpp
1 /*
2  * Copyright (C) 2010 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32
33 #if ENABLE(INSPECTOR)
34
35 #include "InspectorFrontendClientLocal.h"
36
37 #include "Chrome.h"
38 #include "DOMWrapperWorld.h"
39 #include "Document.h"
40 #include "FloatRect.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 "InspectorWebBackendDispatchers.h"
48 #include "MainFrame.h"
49 #include "Page.h"
50 #include "ScriptController.h"
51 #include "ScriptGlobalObject.h"
52 #include "ScriptState.h"
53 #include "Settings.h"
54 #include "Timer.h"
55 #include "UserGestureIndicator.h"
56 #include "WindowFeatures.h"
57 #include <bindings/ScriptValue.h>
58 #include <wtf/Deque.h>
59 #include <wtf/text/CString.h>
60 #include <wtf/text/WTFString.h>
61
62 using namespace Inspector;
63
64 namespace WebCore {
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 = 750.0f;
71 static const float minimumAttachedInspectedWidth = 320.0f;
72
73 class InspectorBackendDispatchTask {
74     WTF_MAKE_FAST_ALLOCATED;
75 public:
76     InspectorBackendDispatchTask(InspectorController* inspectorController)
77         : m_inspectorController(inspectorController)
78         , m_timer(this, &InspectorBackendDispatchTask::onTimer)
79     {
80     }
81
82     void dispatch(const String& message)
83     {
84         m_messages.append(message);
85         if (!m_timer.isActive())
86             m_timer.startOneShot(0);
87     }
88
89     void reset()
90     {
91         m_messages.clear();
92         m_timer.stop();
93     }
94
95     void onTimer(Timer<InspectorBackendDispatchTask>*)
96     {
97         if (!m_messages.isEmpty()) {
98             // Dispatch can lead to the timer destruction -> schedule the next shot first.
99             m_timer.startOneShot(0);
100             m_inspectorController->dispatchMessageFromFrontend(m_messages.takeFirst());
101         }
102     }
103
104 private:
105     InspectorController* m_inspectorController;
106     Timer<InspectorBackendDispatchTask> m_timer;
107     Deque<String> m_messages;
108 };
109
110 String InspectorFrontendClientLocal::Settings::getProperty(const String&)
111 {
112     return String();
113 }
114
115 void InspectorFrontendClientLocal::Settings::setProperty(const String&, const String&)
116 {
117 }
118
119 InspectorFrontendClientLocal::InspectorFrontendClientLocal(InspectorController* inspectorController, Page* frontendPage, PassOwnPtr<Settings> settings)
120     : m_inspectorController(inspectorController)
121     , m_frontendPage(frontendPage)
122     , m_settings(settings)
123     , m_frontendLoaded(false)
124     , m_dockSide(UNDOCKED)
125 {
126     m_frontendPage->settings().setAllowFileAccessFromFileURLs(true);
127     m_dispatchTask = adoptPtr(new InspectorBackendDispatchTask(inspectorController));
128 }
129
130 InspectorFrontendClientLocal::~InspectorFrontendClientLocal()
131 {
132     if (m_frontendHost)
133         m_frontendHost->disconnectClient();
134     m_frontendPage = 0;
135     m_inspectorController = 0;
136 }
137
138 void InspectorFrontendClientLocal::windowObjectCleared()
139 {
140     if (m_frontendHost)
141         m_frontendHost->disconnectClient();
142     
143     JSC::ExecState* frontendExecState = execStateFromPage(debuggerWorld(), m_frontendPage);
144     m_frontendHost = InspectorFrontendHost::create(this, m_frontendPage);
145     ScriptGlobalObject::set(frontendExecState, "InspectorFrontendHost", m_frontendHost.get());
146 }
147
148 void InspectorFrontendClientLocal::frontendLoaded()
149 {
150     // Call setDockingUnavailable before bringToFront. If we display the inspector window via bringToFront first it causes
151     // the call to canAttachWindow to return the wrong result on Windows.
152     // Calling bringToFront first causes the visibleHeight of the inspected page to always return 0 immediately after. 
153     // Thus if we call canAttachWindow first we can avoid this problem. This change does not cause any regressions on Mac.
154     setDockingUnavailable(!canAttachWindow());
155     bringToFront();
156     m_frontendLoaded = true;
157     for (Vector<String>::iterator it = m_evaluateOnLoad.begin(); it != m_evaluateOnLoad.end(); ++it)
158         evaluateOnLoad(*it);
159     m_evaluateOnLoad.clear();
160 }
161
162 void InspectorFrontendClientLocal::requestSetDockSide(DockSide dockSide)
163 {
164     if (dockSide == UNDOCKED) {
165         detachWindow();
166         setAttachedWindow(dockSide);
167     } else if (canAttachWindow()) {
168         attachWindow(dockSide);
169         setAttachedWindow(dockSide);
170     }
171 }
172
173 bool InspectorFrontendClientLocal::canAttachWindow()
174 {
175     // Don't allow attaching to another inspector -- two inspectors in one window is too much!
176     bool isInspectorPage = m_inspectorController->hasInspectorFrontendClient();
177     if (isInspectorPage)
178         return false;
179
180     // If we are already attached, allow attaching again to allow switching sides.
181     if (m_dockSide != UNDOCKED)
182         return true;
183
184     // Don't allow the attach if the window would be too small to accommodate the minimum inspector size.
185     unsigned inspectedPageHeight = m_inspectorController->inspectedPage()->mainFrame().view()->visibleHeight();
186     unsigned inspectedPageWidth = m_inspectorController->inspectedPage()->mainFrame().view()->visibleWidth();
187     unsigned maximumAttachedHeight = inspectedPageHeight * maximumAttachedHeightRatio;
188     return minimumAttachedHeight <= maximumAttachedHeight && minimumAttachedWidth <= inspectedPageWidth;
189 }
190
191 void InspectorFrontendClientLocal::setDockingUnavailable(bool unavailable)
192 {
193     evaluateOnLoad(String::format("[\"setDockingUnavailable\", %s]", unavailable ? "true" : "false"));
194 }
195
196 void InspectorFrontendClientLocal::changeAttachedWindowHeight(unsigned height)
197 {
198     unsigned totalHeight = m_frontendPage->mainFrame().view()->visibleHeight() + m_inspectorController->inspectedPage()->mainFrame().view()->visibleHeight();
199     unsigned attachedHeight = constrainedAttachedWindowHeight(height, totalHeight);
200     m_settings->setProperty(inspectorAttachedHeightSetting, String::number(attachedHeight));
201     setAttachedWindowHeight(attachedHeight);
202 }
203
204 void InspectorFrontendClientLocal::changeAttachedWindowWidth(unsigned width)
205 {
206     unsigned totalWidth = m_frontendPage->mainFrame().view()->visibleWidth() + m_inspectorController->inspectedPage()->mainFrame().view()->visibleWidth();
207     unsigned attachedWidth = constrainedAttachedWindowWidth(width, totalWidth);
208     setAttachedWindowWidth(attachedWidth);
209 }
210
211 void InspectorFrontendClientLocal::openInNewTab(const String& url)
212 {
213     UserGestureIndicator indicator(DefinitelyProcessingUserGesture);
214     Page* page = m_inspectorController->inspectedPage();
215     Frame& mainFrame = page->mainFrame();
216     FrameLoadRequest request(mainFrame.document()->securityOrigin(), ResourceRequest(), "_blank");
217
218     bool created;
219     WindowFeatures windowFeatures;
220     RefPtr<Frame> frame = WebCore::createWindow(&mainFrame, &mainFrame, request, windowFeatures, created);
221     if (!frame)
222         return;
223
224     frame->loader().setOpener(&mainFrame);
225     frame->page()->setOpenedByDOM();
226
227     // FIXME: Why does one use mainFrame and the other frame?
228     frame->loader().changeLocation(mainFrame.document()->securityOrigin(), frame->document()->completeURL(url), "", false, false);
229 }
230
231 void InspectorFrontendClientLocal::moveWindowBy(float x, float y)
232 {
233     FloatRect frameRect = m_frontendPage->chrome().windowRect();
234     frameRect.move(x, y);
235     m_frontendPage->chrome().setWindowRect(frameRect);
236 }
237
238 void InspectorFrontendClientLocal::setAttachedWindow(DockSide dockSide)
239 {
240     const char* side = "undocked";
241     switch (dockSide) {
242     case UNDOCKED:
243         side = "undocked";
244         break;
245     case DOCKED_TO_RIGHT:
246         side = "right";
247         break;
248     case DOCKED_TO_BOTTOM:
249         side = "bottom";
250         break;
251     }
252
253     m_dockSide = dockSide;
254
255     evaluateOnLoad(String::format("[\"setDockSide\", \"%s\"]", side));
256 }
257
258 void InspectorFrontendClientLocal::restoreAttachedWindowHeight()
259 {
260     unsigned inspectedPageHeight = m_inspectorController->inspectedPage()->mainFrame().view()->visibleHeight();
261     String value = m_settings->getProperty(inspectorAttachedHeightSetting);
262     unsigned preferredHeight = value.isEmpty() ? defaultAttachedHeight : value.toUInt();
263     
264     // This call might not go through (if the window starts out detached), but if the window is initially created attached,
265     // InspectorController::attachWindow is never called, so we need to make sure to set the attachedWindowHeight.
266     // FIXME: Clean up code so we only have to call setAttachedWindowHeight in InspectorController::attachWindow
267     setAttachedWindowHeight(constrainedAttachedWindowHeight(preferredHeight, inspectedPageHeight));
268 }
269
270 bool InspectorFrontendClientLocal::isDebuggingEnabled()
271 {
272     if (m_frontendLoaded)
273         return evaluateAsBoolean("[\"isDebuggingEnabled\"]");
274     return false;
275 }
276
277 void InspectorFrontendClientLocal::setDebuggingEnabled(bool enabled)
278 {
279     evaluateOnLoad(String::format("[\"setDebuggingEnabled\", %s]", enabled ? "true" : "false"));
280 }
281
282 bool InspectorFrontendClientLocal::isTimelineProfilingEnabled()
283 {
284     if (m_frontendLoaded)
285         return evaluateAsBoolean("[\"isTimelineProfilingEnabled\"]");
286     return false;
287 }
288
289 void InspectorFrontendClientLocal::setTimelineProfilingEnabled(bool enabled)
290 {
291     evaluateOnLoad(String::format("[\"setTimelineProfilingEnabled\", %s]", enabled ? "true" : "false"));
292 }
293
294 bool InspectorFrontendClientLocal::isProfilingJavaScript()
295 {
296     if (m_frontendLoaded)
297         return evaluateAsBoolean("[\"isProfilingJavaScript\"]");
298     return false;
299 }
300
301 void InspectorFrontendClientLocal::startProfilingJavaScript()
302 {
303     evaluateOnLoad("[\"startProfilingJavaScript\"]");
304 }
305
306 void InspectorFrontendClientLocal::stopProfilingJavaScript()
307 {
308     evaluateOnLoad("[\"stopProfilingJavaScript\"]");
309 }
310
311 void InspectorFrontendClientLocal::showConsole()
312 {
313     evaluateOnLoad("[\"showConsole\"]");
314 }
315
316 void InspectorFrontendClientLocal::showResources()
317 {
318     evaluateOnLoad("[\"showResources\"]");
319 }
320
321 void InspectorFrontendClientLocal::showMainResourceForFrame(Frame* frame)
322 {
323     String frameId = m_inspectorController->pageAgent()->frameId(frame);
324     evaluateOnLoad(String::format("[\"showMainResourceForFrame\", \"%s\"]", frameId.ascii().data()));
325 }
326
327 unsigned InspectorFrontendClientLocal::constrainedAttachedWindowHeight(unsigned preferredHeight, unsigned totalWindowHeight)
328 {
329     return roundf(std::max(minimumAttachedHeight, std::min<float>(preferredHeight, totalWindowHeight * maximumAttachedHeightRatio)));
330 }
331
332 unsigned InspectorFrontendClientLocal::constrainedAttachedWindowWidth(unsigned preferredWidth, unsigned totalWindowWidth)
333 {
334     return roundf(std::max(minimumAttachedWidth, std::min<float>(preferredWidth, totalWindowWidth - minimumAttachedInspectedWidth)));
335 }
336
337 void InspectorFrontendClientLocal::sendMessageToBackend(const String& message)
338 {
339     m_dispatchTask->dispatch(message);
340 }
341
342 bool InspectorFrontendClientLocal::isUnderTest()
343 {
344     return m_inspectorController->isUnderTest();
345 }
346
347 bool InspectorFrontendClientLocal::evaluateAsBoolean(const String& expression)
348 {
349     Deprecated::ScriptValue value = m_frontendPage->mainFrame().script().executeScript(expression);
350     return value.toString(mainWorldExecState(&m_frontendPage->mainFrame())) == "true";
351 }
352
353 void InspectorFrontendClientLocal::evaluateOnLoad(const String& expression)
354 {
355     if (m_frontendLoaded)
356         m_frontendPage->mainFrame().script().executeScript("InspectorFrontendAPI.dispatch(" + expression + ")");
357     else
358         m_evaluateOnLoad.append(expression);
359 }
360
361 } // namespace WebCore
362
363 #endif