blob: 856dc26a7cfca1288a6509b9f0823969e939516c [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "web/InspectorOverlayImpl.h"
#include "bindings/core/v8/ScriptController.h"
#include "bindings/core/v8/V8InspectorOverlayHost.h"
#include "core/dom/Node.h"
#include "core/frame/FrameHost.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/input/EventHandler.h"
#include "core/inspector/InspectorOverlayHost.h"
#include "core/inspector/LayoutEditor.h"
#include "core/loader/EmptyClients.h"
#include "core/loader/FrameLoadRequest.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "platform/JSONValues.h"
#include "platform/ScriptForbiddenScope.h"
#include "platform/graphics/GraphicsContext.h"
#include "public/platform/Platform.h"
#include "public/platform/WebData.h"
#include "web/PageOverlay.h"
#include "web/WebGraphicsContextImpl.h"
#include "web/WebInputEventConversion.h"
#include "web/WebLocalFrameImpl.h"
#include "web/WebViewImpl.h"
#include <v8.h>
namespace blink {
namespace {
class InspectorOverlayStub : public NoBaseWillBeGarbageCollectedFinalized<InspectorOverlayStub>, public InspectorOverlay {
WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED(InspectorOverlayStub);
WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(InspectorOverlayStub);
public:
InspectorOverlayStub() { }
DECLARE_TRACE();
// InspectorOverlay implementation.
void update() override { }
void setPausedInDebuggerMessage(const String*) override { }
void setInspectModeEnabled(bool) override { }
void hideHighlight() override { }
void highlightNode(Node*, Node* eventTarget, const InspectorHighlightConfig&, bool omitTooltip) override { }
void highlightQuad(PassOwnPtr<FloatQuad>, const InspectorHighlightConfig&) override { }
void showAndHideViewSize(bool showGrid) override { }
void setListener(InspectorOverlay::Listener* listener) override { }
void suspendUpdates() override { }
void resumeUpdates() override { }
void clear() override { }
void setLayoutEditor(PassOwnPtrWillBeRawPtr<LayoutEditor>) override { }
};
DEFINE_TRACE(InspectorOverlayStub)
{
InspectorOverlay::trace(visitor);
}
} // anonymous namespace
class InspectorOverlayImpl::InspectorPageOverlayDelegate final : public PageOverlay::Delegate {
public:
explicit InspectorPageOverlayDelegate(InspectorOverlayImpl& overlay)
: m_overlay(&overlay)
{ }
DEFINE_INLINE_VIRTUAL_TRACE()
{
visitor->trace(m_overlay);
PageOverlay::Delegate::trace(visitor);
}
void paintPageOverlay(WebGraphicsContext* context, const WebSize& webViewSize)
{
if (m_overlay->isEmpty())
return;
GraphicsContext& graphicsContext = toWebGraphicsContextImpl(context)->graphicsContext();
FrameView* view = m_overlay->overlayMainFrame()->view();
ASSERT(!view->needsLayout());
view->paint(&graphicsContext, IntRect(0, 0, view->width(), view->height()));
}
private:
RawPtrWillBeMember<InspectorOverlayImpl> m_overlay;
};
class InspectorOverlayImpl::InspectorOverlayChromeClient final : public EmptyChromeClient {
public:
static PassOwnPtrWillBeRawPtr<InspectorOverlayChromeClient> create(ChromeClient& client, InspectorOverlayImpl& overlay)
{
return adoptPtrWillBeNoop(new InspectorOverlayChromeClient(client, overlay));
}
DEFINE_INLINE_VIRTUAL_TRACE()
{
visitor->trace(m_client);
visitor->trace(m_overlay);
EmptyChromeClient::trace(visitor);
}
void setCursor(const Cursor& cursor) override
{
m_client->setCursor(cursor);
}
void setToolTip(const String& tooltip, TextDirection direction) override
{
m_client->setToolTip(tooltip, direction);
}
void invalidateRect(const IntRect&) override
{
m_overlay->invalidate();
}
void scheduleAnimation() override
{
if (m_overlay->m_inLayout)
return;
m_client->scheduleAnimation();
}
private:
InspectorOverlayChromeClient(ChromeClient& client, InspectorOverlayImpl& overlay)
: m_client(&client)
, m_overlay(&overlay)
{ }
RawPtrWillBeMember<ChromeClient> m_client;
RawPtrWillBeMember<InspectorOverlayImpl> m_overlay;
};
// static
PassOwnPtrWillBeRawPtr<InspectorOverlay> InspectorOverlayImpl::createEmpty()
{
return adoptPtrWillBeNoop(new InspectorOverlayStub());
}
InspectorOverlayImpl::InspectorOverlayImpl(WebViewImpl* webViewImpl)
: m_webViewImpl(webViewImpl)
, m_inspectModeEnabled(false)
, m_overlayHost(InspectorOverlayHost::create())
, m_drawViewSize(false)
, m_drawViewSizeWithGrid(false)
, m_omitTooltip(false)
, m_timer(this, &InspectorOverlayImpl::onTimer)
, m_suspendCount(0)
, m_inLayout(false)
, m_needsUpdate(false)
{
m_overlayHost->setDebuggerListener(this);
}
InspectorOverlayImpl::~InspectorOverlayImpl()
{
ASSERT(!m_overlayPage);
}
DEFINE_TRACE(InspectorOverlayImpl)
{
visitor->trace(m_highlightNode);
visitor->trace(m_eventTargetNode);
visitor->trace(m_overlayPage);
visitor->trace(m_overlayChromeClient);
visitor->trace(m_overlayHost);
visitor->trace(m_listener);
visitor->trace(m_layoutEditor);
InspectorOverlay::trace(visitor);
}
void InspectorOverlayImpl::invalidate()
{
if (!m_pageOverlay)
m_pageOverlay = PageOverlay::create(m_webViewImpl, new InspectorPageOverlayDelegate(*this));
m_pageOverlay->update();
}
void InspectorOverlayImpl::layout()
{
if (isEmpty())
return;
TemporaryChange<bool> scoped(m_inLayout, true);
if (m_needsUpdate) {
m_needsUpdate = false;
rebuildOverlayPage();
}
overlayMainFrame()->view()->updateAllLifecyclePhases();
}
bool InspectorOverlayImpl::handleInputEvent(const WebInputEvent& inputEvent)
{
bool handled = false;
if (isEmpty())
return handled;
if (WebInputEvent::isGestureEventType(inputEvent.type) && inputEvent.type == WebInputEvent::GestureTap) {
// Only let GestureTab in (we only need it and we know PlatformGestureEventBuilder supports it).
PlatformGestureEvent gestureEvent = PlatformGestureEventBuilder(m_webViewImpl->mainFrameImpl()->frameView(), static_cast<const WebGestureEvent&>(inputEvent));
overlayMainFrame()->eventHandler().handleGestureEvent(gestureEvent);
}
if (WebInputEvent::isMouseEventType(inputEvent.type) && inputEvent.type != WebInputEvent::MouseEnter) {
// PlatformMouseEventBuilder does not work with MouseEnter type, so we filter it out manually.
PlatformMouseEvent mouseEvent = PlatformMouseEventBuilder(m_webViewImpl->mainFrameImpl()->frameView(), static_cast<const WebMouseEvent&>(inputEvent));
if (mouseEvent.type() == PlatformEvent::MouseMoved)
handled = overlayMainFrame()->eventHandler().handleMouseMoveEvent(mouseEvent);
if (mouseEvent.type() == PlatformEvent::MousePressed)
handled = overlayMainFrame()->eventHandler().handleMousePressEvent(mouseEvent);
if (mouseEvent.type() == PlatformEvent::MouseReleased)
handled = overlayMainFrame()->eventHandler().handleMouseReleaseEvent(mouseEvent);
}
if (WebInputEvent::isTouchEventType(inputEvent.type)) {
PlatformTouchEvent touchEvent = PlatformTouchEventBuilder(m_webViewImpl->mainFrameImpl()->frameView(), static_cast<const WebTouchEvent&>(inputEvent));
overlayMainFrame()->eventHandler().handleTouchEvent(touchEvent);
}
if (WebInputEvent::isKeyboardEventType(inputEvent.type)) {
PlatformKeyboardEvent keyboardEvent = PlatformKeyboardEventBuilder(static_cast<const WebKeyboardEvent&>(inputEvent));
overlayMainFrame()->eventHandler().keyEvent(keyboardEvent);
}
return handled;
}
void InspectorOverlayImpl::setPausedInDebuggerMessage(const String* message)
{
m_pausedInDebuggerMessage = message ? *message : String();
update();
}
void InspectorOverlayImpl::setInspectModeEnabled(bool enabled)
{
m_inspectModeEnabled = enabled;
update();
}
void InspectorOverlayImpl::hideHighlight()
{
if (m_layoutEditor)
m_layoutEditor->setNode(nullptr);
m_highlightNode.clear();
m_eventTargetNode.clear();
m_highlightQuad.clear();
update();
}
void InspectorOverlayImpl::highlightNode(Node* node, Node* eventTarget, const InspectorHighlightConfig& highlightConfig, bool omitTooltip)
{
m_nodeHighlightConfig = highlightConfig;
m_highlightNode = node;
if (m_layoutEditor && highlightConfig.showLayoutEditor)
m_layoutEditor->setNode(node);
m_eventTargetNode = eventTarget;
m_omitTooltip = omitTooltip;
update();
}
void InspectorOverlayImpl::highlightQuad(PassOwnPtr<FloatQuad> quad, const InspectorHighlightConfig& highlightConfig)
{
m_quadHighlightConfig = highlightConfig;
m_highlightQuad = quad;
m_omitTooltip = false;
update();
}
void InspectorOverlayImpl::showAndHideViewSize(bool showGrid)
{
m_drawViewSize = true;
m_drawViewSizeWithGrid = showGrid;
update();
m_timer.startOneShot(1, FROM_HERE);
}
bool InspectorOverlayImpl::isEmpty()
{
if (m_suspendCount)
return true;
bool hasAlwaysVisibleElements = m_highlightNode || m_eventTargetNode || m_highlightQuad || m_drawViewSize;
bool hasInvisibleInInspectModeElements = !m_pausedInDebuggerMessage.isNull();
return !(hasAlwaysVisibleElements || (hasInvisibleInInspectModeElements && !m_inspectModeEnabled));
}
void InspectorOverlayImpl::update()
{
if (isEmpty()) {
if (m_pageOverlay)
m_pageOverlay.clear();
return;
}
m_needsUpdate = true;
m_webViewImpl->page()->chromeClient().scheduleAnimation();
}
void InspectorOverlayImpl::rebuildOverlayPage()
{
FrameView* view = m_webViewImpl->mainFrameImpl()->frameView();
if (!view)
return;
IntRect visibleRectInDocument = view->scrollableArea()->visibleContentRect();
IntSize viewportSize = m_webViewImpl->page()->frameHost().visualViewport().size();
toLocalFrame(overlayPage()->mainFrame())->view()->resize(viewportSize);
reset(viewportSize, visibleRectInDocument.location());
drawNodeHighlight();
drawQuadHighlight();
if (!m_inspectModeEnabled)
drawPausedInDebuggerMessage();
drawViewSize();
}
static PassRefPtr<JSONObject> buildObjectForSize(const IntSize& size)
{
RefPtr<JSONObject> result = JSONObject::create();
result->setNumber("width", size.width());
result->setNumber("height", size.height());
return result.release();
}
void InspectorOverlayImpl::drawNodeHighlight()
{
if (!m_highlightNode)
return;
bool appendElementInfo = m_highlightNode->isElementNode() && !m_omitTooltip && m_nodeHighlightConfig.showInfo && m_highlightNode->layoutObject() && m_highlightNode->document().frame();
InspectorHighlight highlight(m_highlightNode.get(), m_nodeHighlightConfig, appendElementInfo);
if (m_eventTargetNode)
highlight.appendEventTargetQuads(m_eventTargetNode.get(), m_nodeHighlightConfig);
RefPtr<JSONObject> highlightJSON = highlight.asJSONObject();
evaluateInOverlay("drawHighlight", highlightJSON.release());
if (m_layoutEditor && m_nodeHighlightConfig.showLayoutEditor) {
RefPtr<JSONObject> layoutEditorInfo = m_layoutEditor->buildJSONInfo();
if (layoutEditorInfo)
evaluateInOverlay("showLayoutEditor", layoutEditorInfo.release());
}
}
void InspectorOverlayImpl::drawQuadHighlight()
{
if (!m_highlightQuad)
return;
InspectorHighlight highlight;
highlight.appendQuad(*m_highlightQuad, m_quadHighlightConfig.content, m_quadHighlightConfig.contentOutline);
evaluateInOverlay("drawHighlight", highlight.asJSONObject());
}
void InspectorOverlayImpl::drawPausedInDebuggerMessage()
{
if (!m_pausedInDebuggerMessage.isNull())
evaluateInOverlay("drawPausedInDebuggerMessage", m_pausedInDebuggerMessage);
}
void InspectorOverlayImpl::drawViewSize()
{
if (m_drawViewSize)
evaluateInOverlay("drawViewSize", m_drawViewSizeWithGrid ? "true" : "false");
}
Page* InspectorOverlayImpl::overlayPage()
{
if (m_overlayPage)
return m_overlayPage.get();
ScriptForbiddenScope::AllowUserAgentScript allowScript;
static FrameLoaderClient* dummyFrameLoaderClient = new EmptyFrameLoaderClient;
Page::PageClients pageClients;
fillWithEmptyClients(pageClients);
ASSERT(!m_overlayChromeClient);
m_overlayChromeClient = InspectorOverlayChromeClient::create(m_webViewImpl->page()->chromeClient(), *this);
pageClients.chromeClient = m_overlayChromeClient.get();
m_overlayPage = adoptPtrWillBeNoop(new Page(pageClients));
Settings& settings = m_webViewImpl->page()->settings();
Settings& overlaySettings = m_overlayPage->settings();
overlaySettings.genericFontFamilySettings().updateStandard(settings.genericFontFamilySettings().standard());
overlaySettings.genericFontFamilySettings().updateSerif(settings.genericFontFamilySettings().serif());
overlaySettings.genericFontFamilySettings().updateSansSerif(settings.genericFontFamilySettings().sansSerif());
overlaySettings.genericFontFamilySettings().updateCursive(settings.genericFontFamilySettings().cursive());
overlaySettings.genericFontFamilySettings().updateFantasy(settings.genericFontFamilySettings().fantasy());
overlaySettings.genericFontFamilySettings().updatePictograph(settings.genericFontFamilySettings().pictograph());
overlaySettings.setMinimumFontSize(settings.minimumFontSize());
overlaySettings.setMinimumLogicalFontSize(settings.minimumLogicalFontSize());
overlaySettings.setScriptEnabled(true);
overlaySettings.setPluginsEnabled(false);
overlaySettings.setLoadsImagesAutomatically(true);
// FIXME: http://crbug.com/363843. Inspector should probably create its
// own graphics layers and attach them to the tree rather than going
// through some non-composited paint function.
overlaySettings.setAcceleratedCompositingEnabled(false);
RefPtrWillBeRawPtr<LocalFrame> frame = LocalFrame::create(dummyFrameLoaderClient, &m_overlayPage->frameHost(), 0);
frame->setView(FrameView::create(frame.get()));
frame->init();
FrameLoader& loader = frame->loader();
frame->view()->setCanHaveScrollbars(false);
frame->view()->setTransparent(true);
const WebData& overlayPageHTMLResource = Platform::current()->loadResource("InspectorOverlayPage.html");
RefPtr<SharedBuffer> data = SharedBuffer::create(overlayPageHTMLResource.data(), overlayPageHTMLResource.size());
loader.load(FrameLoadRequest(0, blankURL(), SubstituteData(data, "text/html", "UTF-8", KURL(), ForceSynchronousLoad)));
v8::Isolate* isolate = toIsolate(frame.get());
ScriptState* scriptState = ScriptState::forMainWorld(frame.get());
ASSERT(scriptState->contextIsValid());
ScriptState::Scope scope(scriptState);
v8::Local<v8::Object> global = scriptState->context()->Global();
v8::Local<v8::Value> overlayHostObj = toV8(m_overlayHost.get(), global, isolate);
ASSERT(!overlayHostObj.IsEmpty());
v8CallOrCrash(global->Set(scriptState->context(), v8AtomicString(isolate, "InspectorOverlayHost"), overlayHostObj));
#if OS(WIN)
evaluateInOverlay("setPlatform", "windows");
#elif OS(MACOSX)
evaluateInOverlay("setPlatform", "mac");
#elif OS(POSIX)
evaluateInOverlay("setPlatform", "linux");
#endif
return m_overlayPage.get();
}
LocalFrame* InspectorOverlayImpl::overlayMainFrame()
{
return toLocalFrame(overlayPage()->mainFrame());
}
void InspectorOverlayImpl::reset(const IntSize& viewportSize, const IntPoint& documentScrollOffset)
{
RefPtr<JSONObject> resetData = JSONObject::create();
resetData->setNumber("deviceScaleFactor", m_webViewImpl->page()->deviceScaleFactor());
resetData->setObject("viewportSize", buildObjectForSize(viewportSize));
resetData->setNumber("pageZoomFactor", m_webViewImpl->mainFrameImpl()->frame()->pageZoomFactor());
resetData->setNumber("scrollX", documentScrollOffset.x());
resetData->setNumber("scrollY", documentScrollOffset.y());
evaluateInOverlay("reset", resetData.release());
}
void InspectorOverlayImpl::evaluateInOverlay(const String& method, const String& argument)
{
ScriptForbiddenScope::AllowUserAgentScript allowScript;
RefPtr<JSONArray> command = JSONArray::create();
command->pushString(method);
command->pushString(argument);
toLocalFrame(overlayPage()->mainFrame())->script().executeScriptInMainWorld("dispatch(" + command->toJSONString() + ")", ScriptController::ExecuteScriptWhenScriptsDisabled);
}
void InspectorOverlayImpl::evaluateInOverlay(const String& method, PassRefPtr<JSONValue> argument)
{
ScriptForbiddenScope::AllowUserAgentScript allowScript;
RefPtr<JSONArray> command = JSONArray::create();
command->pushString(method);
command->pushValue(argument);
toLocalFrame(overlayPage()->mainFrame())->script().executeScriptInMainWorld("dispatch(" + command->toJSONString() + ")", ScriptController::ExecuteScriptWhenScriptsDisabled);
}
void InspectorOverlayImpl::onTimer(Timer<InspectorOverlayImpl>*)
{
m_drawViewSize = false;
update();
}
void InspectorOverlayImpl::clear()
{
if (m_overlayPage) {
m_overlayPage->willBeDestroyed();
m_overlayPage.clear();
m_overlayChromeClient.clear();
}
m_drawViewSize = false;
m_pausedInDebuggerMessage = String();
m_inspectModeEnabled = false;
m_timer.stop();
hideHighlight();
}
void InspectorOverlayImpl::overlayResumed()
{
if (m_listener)
m_listener->overlayResumed();
}
void InspectorOverlayImpl::overlaySteppedOver()
{
if (m_listener)
m_listener->overlaySteppedOver();
}
void InspectorOverlayImpl::suspendUpdates()
{
if (!m_suspendCount++)
clear();
}
void InspectorOverlayImpl::resumeUpdates()
{
--m_suspendCount;
}
void InspectorOverlayImpl::setLayoutEditor(PassOwnPtrWillBeRawPtr<LayoutEditor> layoutEditor)
{
m_layoutEditor = layoutEditor;
m_overlayHost->setLayoutEditorListener(m_layoutEditor.get());
}
} // namespace blink