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 62using namespace Inspector; 63 64namespace WebCore { 65 66static const char* inspectorAttachedHeightSetting = "inspectorAttachedHeight"; 67static const unsigned defaultAttachedHeight = 300; 68static const float minimumAttachedHeight = 250.0f; 69static const float maximumAttachedHeightRatio = 0.75f; 70static const float minimumAttachedWidth = 750.0f; 71static const float minimumAttachedInspectedWidth = 320.0f; 72 73class InspectorBackendDispatchTask { 74 WTF_MAKE_FAST_ALLOCATED; 75public: 76 InspectorBackendDispatchTask(InspectorController* inspectorController) 77 : m_inspectorController(inspectorController) 78 , m_timer(this, &InspectorBackendDispatchTask::timerFired) 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 timerFired(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 104private: 105 InspectorController* m_inspectorController; 106 Timer<InspectorBackendDispatchTask> m_timer; 107 Deque<String> m_messages; 108}; 109 110String InspectorFrontendClientLocal::Settings::getProperty(const String&) 111{ 112 return String(); 113} 114 115void InspectorFrontendClientLocal::Settings::setProperty(const String&, const String&) 116{ 117} 118 119InspectorFrontendClientLocal::InspectorFrontendClientLocal(InspectorController* inspectorController, Page* frontendPage, std::unique_ptr<Settings> settings) 120 : m_inspectorController(inspectorController) 121 , m_frontendPage(frontendPage) 122 , m_settings(WTF::move(settings)) 123 , m_frontendLoaded(false) 124 , m_dockSide(UNDOCKED) 125{ 126 m_frontendPage->settings().setAllowFileAccessFromFileURLs(true); 127 m_dispatchTask = std::make_unique<InspectorBackendDispatchTask>(inspectorController); 128} 129 130InspectorFrontendClientLocal::~InspectorFrontendClientLocal() 131{ 132 if (m_frontendHost) 133 m_frontendHost->disconnectClient(); 134 m_frontendPage = 0; 135 m_inspectorController = 0; 136} 137 138void 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 148void 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 162void 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 173bool 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 191void InspectorFrontendClientLocal::setDockingUnavailable(bool unavailable) 192{ 193 evaluateOnLoad(String::format("[\"setDockingUnavailable\", %s]", unavailable ? "true" : "false")); 194} 195 196void 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 204void 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 211void InspectorFrontendClientLocal::openInNewTab(const String& url) 212{ 213 UserGestureIndicator indicator(DefinitelyProcessingUserGesture); 214 Frame& mainFrame = m_inspectorController->inspectedPage().mainFrame(); 215 FrameLoadRequest request(mainFrame.document()->securityOrigin(), ResourceRequest(), "_blank"); 216 217 bool created; 218 WindowFeatures windowFeatures; 219 RefPtr<Frame> frame = WebCore::createWindow(&mainFrame, &mainFrame, request, windowFeatures, created); 220 if (!frame) 221 return; 222 223 frame->loader().setOpener(&mainFrame); 224 frame->page()->setOpenedByDOM(); 225 226 // FIXME: Why does one use mainFrame and the other frame? 227 frame->loader().changeLocation(mainFrame.document()->securityOrigin(), frame->document()->completeURL(url), "", LockHistory::No, LockBackForwardList::No); 228} 229 230void InspectorFrontendClientLocal::moveWindowBy(float x, float y) 231{ 232 FloatRect frameRect = m_frontendPage->chrome().windowRect(); 233 frameRect.move(x, y); 234 m_frontendPage->chrome().setWindowRect(frameRect); 235} 236 237void InspectorFrontendClientLocal::setAttachedWindow(DockSide dockSide) 238{ 239 const char* side = "undocked"; 240 switch (dockSide) { 241 case UNDOCKED: 242 side = "undocked"; 243 break; 244 case DOCKED_TO_RIGHT: 245 side = "right"; 246 break; 247 case DOCKED_TO_BOTTOM: 248 side = "bottom"; 249 break; 250 } 251 252 m_dockSide = dockSide; 253 254 evaluateOnLoad(String::format("[\"setDockSide\", \"%s\"]", side)); 255} 256 257void InspectorFrontendClientLocal::restoreAttachedWindowHeight() 258{ 259 unsigned inspectedPageHeight = m_inspectorController->inspectedPage().mainFrame().view()->visibleHeight(); 260 String value = m_settings->getProperty(inspectorAttachedHeightSetting); 261 unsigned preferredHeight = value.isEmpty() ? defaultAttachedHeight : value.toUInt(); 262 263 // This call might not go through (if the window starts out detached), but if the window is initially created attached, 264 // InspectorController::attachWindow is never called, so we need to make sure to set the attachedWindowHeight. 265 // FIXME: Clean up code so we only have to call setAttachedWindowHeight in InspectorController::attachWindow 266 setAttachedWindowHeight(constrainedAttachedWindowHeight(preferredHeight, inspectedPageHeight)); 267} 268 269bool InspectorFrontendClientLocal::isDebuggingEnabled() 270{ 271 if (m_frontendLoaded) 272 return evaluateAsBoolean("[\"isDebuggingEnabled\"]"); 273 return false; 274} 275 276void InspectorFrontendClientLocal::setDebuggingEnabled(bool enabled) 277{ 278 evaluateOnLoad(String::format("[\"setDebuggingEnabled\", %s]", enabled ? "true" : "false")); 279} 280 281bool InspectorFrontendClientLocal::isTimelineProfilingEnabled() 282{ 283 if (m_frontendLoaded) 284 return evaluateAsBoolean("[\"isTimelineProfilingEnabled\"]"); 285 return false; 286} 287 288void InspectorFrontendClientLocal::setTimelineProfilingEnabled(bool enabled) 289{ 290 evaluateOnLoad(String::format("[\"setTimelineProfilingEnabled\", %s]", enabled ? "true" : "false")); 291} 292 293bool InspectorFrontendClientLocal::isProfilingJavaScript() 294{ 295 if (m_frontendLoaded) 296 return evaluateAsBoolean("[\"isProfilingJavaScript\"]"); 297 return false; 298} 299 300void InspectorFrontendClientLocal::startProfilingJavaScript() 301{ 302 evaluateOnLoad("[\"startProfilingJavaScript\"]"); 303} 304 305void InspectorFrontendClientLocal::stopProfilingJavaScript() 306{ 307 evaluateOnLoad("[\"stopProfilingJavaScript\"]"); 308} 309 310void InspectorFrontendClientLocal::showConsole() 311{ 312 evaluateOnLoad("[\"showConsole\"]"); 313} 314 315void InspectorFrontendClientLocal::showResources() 316{ 317 evaluateOnLoad("[\"showResources\"]"); 318} 319 320void InspectorFrontendClientLocal::showMainResourceForFrame(Frame* frame) 321{ 322 String frameId = m_inspectorController->pageAgent()->frameId(frame); 323 evaluateOnLoad(String::format("[\"showMainResourceForFrame\", \"%s\"]", frameId.ascii().data())); 324} 325 326unsigned InspectorFrontendClientLocal::constrainedAttachedWindowHeight(unsigned preferredHeight, unsigned totalWindowHeight) 327{ 328 return roundf(std::max(minimumAttachedHeight, std::min<float>(preferredHeight, totalWindowHeight * maximumAttachedHeightRatio))); 329} 330 331unsigned InspectorFrontendClientLocal::constrainedAttachedWindowWidth(unsigned preferredWidth, unsigned totalWindowWidth) 332{ 333 return roundf(std::max(minimumAttachedWidth, std::min<float>(preferredWidth, totalWindowWidth - minimumAttachedInspectedWidth))); 334} 335 336void InspectorFrontendClientLocal::sendMessageToBackend(const String& message) 337{ 338 m_dispatchTask->dispatch(message); 339} 340 341bool InspectorFrontendClientLocal::isUnderTest() 342{ 343 return m_inspectorController->isUnderTest(); 344} 345 346bool InspectorFrontendClientLocal::evaluateAsBoolean(const String& expression) 347{ 348 Deprecated::ScriptValue value = m_frontendPage->mainFrame().script().executeScript(expression); 349 return value.toString(mainWorldExecState(&m_frontendPage->mainFrame())) == "true"; 350} 351 352void InspectorFrontendClientLocal::evaluateOnLoad(const String& expression) 353{ 354 if (m_frontendLoaded) 355 m_frontendPage->mainFrame().script().executeScript("InspectorFrontendAPI.dispatch(" + expression + ")"); 356 else 357 m_evaluateOnLoad.append(expression); 358} 359 360} // namespace WebCore 361 362#endif 363