blob: eb7d4ef89816d22d1372f867206102381ad3590c [file] [log] [blame]
/*
* Copyright (C) 2012 Apple Inc. All rights reserved.
* Copyright (C) 2013 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
* OWNER OR 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 "core/inspector/InspectorLayerTreeAgent.h"
#include "core/dom/DOMNodeIds.h"
#include "core/dom/Document.h"
#include "core/frame/FrameHost.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/VisualViewport.h"
#include "core/inspector/IdentifiersFactory.h"
#include "core/inspector/InspectedFrames.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/loader/DocumentLoader.h"
#include "core/page/ChromeClient.h"
#include "platform/geometry/IntRect.h"
#include "platform/graphics/CompositingReasons.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/PictureSnapshot.h"
#include "platform/graphics/paint/SkPictureBuilder.h"
#include "platform/image-encoders/PNGImageEncoder.h"
#include "platform/transforms/TransformationMatrix.h"
#include "public/platform/WebFloatPoint.h"
#include "public/platform/WebLayer.h"
#include "wtf/text/Base64.h"
#include "wtf/text/StringBuilder.h"
#include <memory>
namespace blink {
using protocol::Array;
unsigned InspectorLayerTreeAgent::s_lastSnapshotId;
inline String idForLayer(const GraphicsLayer* graphicsLayer) {
return String::number(graphicsLayer->platformLayer()->id());
}
static std::unique_ptr<protocol::LayerTree::ScrollRect> buildScrollRect(
const WebRect& rect,
const String& type) {
std::unique_ptr<protocol::DOM::Rect> rectObject =
protocol::DOM::Rect::create()
.setX(rect.x)
.setY(rect.y)
.setHeight(rect.height)
.setWidth(rect.width)
.build();
std::unique_ptr<protocol::LayerTree::ScrollRect> scrollRectObject =
protocol::LayerTree::ScrollRect::create()
.setRect(std::move(rectObject))
.setType(type)
.build();
return scrollRectObject;
}
static std::unique_ptr<Array<protocol::LayerTree::ScrollRect>>
buildScrollRectsForLayer(GraphicsLayer* graphicsLayer,
bool reportWheelScrollers) {
std::unique_ptr<Array<protocol::LayerTree::ScrollRect>> scrollRects =
Array<protocol::LayerTree::ScrollRect>::create();
WebLayer* webLayer = graphicsLayer->platformLayer();
WebVector<WebRect> nonFastScrollableRects =
webLayer->nonFastScrollableRegion();
for (size_t i = 0; i < nonFastScrollableRects.size(); ++i) {
scrollRects->addItem(buildScrollRect(
nonFastScrollableRects[i],
protocol::LayerTree::ScrollRect::TypeEnum::RepaintsOnScroll));
}
WebVector<WebRect> touchEventHandlerRects =
webLayer->touchEventHandlerRegion();
for (size_t i = 0; i < touchEventHandlerRects.size(); ++i) {
scrollRects->addItem(buildScrollRect(
touchEventHandlerRects[i],
protocol::LayerTree::ScrollRect::TypeEnum::TouchEventHandler));
}
if (reportWheelScrollers) {
WebRect webRect(webLayer->position().x, webLayer->position().y,
webLayer->bounds().width, webLayer->bounds().height);
scrollRects->addItem(buildScrollRect(
webRect, protocol::LayerTree::ScrollRect::TypeEnum::WheelEventHandler));
}
return scrollRects->length() ? std::move(scrollRects) : nullptr;
}
static std::unique_ptr<protocol::LayerTree::Layer> buildObjectForLayer(
GraphicsLayer* graphicsLayer,
int nodeId,
bool reportWheelEventListeners) {
WebLayer* webLayer = graphicsLayer->platformLayer();
std::unique_ptr<protocol::LayerTree::Layer> layerObject =
protocol::LayerTree::Layer::create()
.setLayerId(idForLayer(graphicsLayer))
.setOffsetX(webLayer->position().x)
.setOffsetY(webLayer->position().y)
.setWidth(webLayer->bounds().width)
.setHeight(webLayer->bounds().height)
.setPaintCount(graphicsLayer->paintCount())
.setDrawsContent(webLayer->drawsContent())
.build();
if (nodeId)
layerObject->setBackendNodeId(nodeId);
GraphicsLayer* parent = graphicsLayer->parent();
if (parent)
layerObject->setParentLayerId(idForLayer(parent));
if (!graphicsLayer->contentsAreVisible())
layerObject->setInvisible(true);
const TransformationMatrix& transform = graphicsLayer->transform();
if (!transform.isIdentity()) {
TransformationMatrix::FloatMatrix4 flattenedMatrix;
transform.toColumnMajorFloatArray(flattenedMatrix);
std::unique_ptr<Array<double>> transformArray = Array<double>::create();
for (size_t i = 0; i < WTF_ARRAY_LENGTH(flattenedMatrix); ++i)
transformArray->addItem(flattenedMatrix[i]);
layerObject->setTransform(std::move(transformArray));
const FloatPoint3D& transformOrigin = graphicsLayer->transformOrigin();
// FIXME: rename these to setTransformOrigin*
if (webLayer->bounds().width > 0)
layerObject->setAnchorX(transformOrigin.x() / webLayer->bounds().width);
else
layerObject->setAnchorX(0.0);
if (webLayer->bounds().height > 0)
layerObject->setAnchorY(transformOrigin.y() / webLayer->bounds().height);
else
layerObject->setAnchorY(0.0);
layerObject->setAnchorZ(transformOrigin.z());
}
std::unique_ptr<Array<protocol::LayerTree::ScrollRect>> scrollRects =
buildScrollRectsForLayer(graphicsLayer, reportWheelEventListeners);
if (scrollRects)
layerObject->setScrollRects(std::move(scrollRects));
return layerObject;
}
InspectorLayerTreeAgent::InspectorLayerTreeAgent(
InspectedFrames* inspectedFrames)
: m_inspectedFrames(inspectedFrames), m_suppressLayerPaintEvents(false) {}
InspectorLayerTreeAgent::~InspectorLayerTreeAgent() {}
DEFINE_TRACE(InspectorLayerTreeAgent) {
visitor->trace(m_inspectedFrames);
InspectorBaseAgent::trace(visitor);
}
void InspectorLayerTreeAgent::restore() {
// We do not re-enable layer agent automatically after navigation. This is
// because it depends on DOMAgent and node ids in particular, so we let
// front-end request document and re-enable the agent manually after this.
}
Response InspectorLayerTreeAgent::enable() {
m_instrumentingAgents->addInspectorLayerTreeAgent(this);
Document* document = m_inspectedFrames->root()->document();
if (document &&
document->lifecycle().state() >= DocumentLifecycle::CompositingClean)
layerTreeDidChange();
return Response::OK();
}
Response InspectorLayerTreeAgent::disable() {
m_instrumentingAgents->removeInspectorLayerTreeAgent(this);
m_snapshotById.clear();
return Response::OK();
}
void InspectorLayerTreeAgent::layerTreeDidChange() {
frontend()->layerTreeDidChange(buildLayerTree());
}
void InspectorLayerTreeAgent::didPaint(const GraphicsLayer* graphicsLayer,
GraphicsContext&,
const LayoutRect& rect) {
if (m_suppressLayerPaintEvents)
return;
// Should only happen for FrameView paints when compositing is off. Consider
// different instrumentation method for that.
if (!graphicsLayer)
return;
std::unique_ptr<protocol::DOM::Rect> domRect = protocol::DOM::Rect::create()
.setX(rect.x())
.setY(rect.y())
.setWidth(rect.width())
.setHeight(rect.height())
.build();
frontend()->layerPainted(idForLayer(graphicsLayer), std::move(domRect));
}
std::unique_ptr<Array<protocol::LayerTree::Layer>>
InspectorLayerTreeAgent::buildLayerTree() {
PaintLayerCompositor* compositor = paintLayerCompositor();
if (!compositor || !compositor->inCompositingMode())
return nullptr;
LayerIdToNodeIdMap layerIdToNodeIdMap;
std::unique_ptr<Array<protocol::LayerTree::Layer>> layers =
Array<protocol::LayerTree::Layer>::create();
buildLayerIdToNodeIdMap(compositor->rootLayer(), layerIdToNodeIdMap);
int scrollingLayerId = m_inspectedFrames->root()
->view()
->layerForScrolling()
->platformLayer()
->id();
bool haveBlockingWheelEventHandlers =
m_inspectedFrames->root()->chromeClient().eventListenerProperties(
WebEventListenerClass::MouseWheel) ==
WebEventListenerProperties::Blocking;
gatherGraphicsLayers(rootGraphicsLayer(), layerIdToNodeIdMap, layers,
haveBlockingWheelEventHandlers, scrollingLayerId);
return layers;
}
void InspectorLayerTreeAgent::buildLayerIdToNodeIdMap(
PaintLayer* root,
LayerIdToNodeIdMap& layerIdToNodeIdMap) {
if (root->hasCompositedLayerMapping()) {
if (Node* node = root->layoutObject()->generatingNode()) {
GraphicsLayer* graphicsLayer =
root->compositedLayerMapping()->childForSuperlayers();
layerIdToNodeIdMap.set(graphicsLayer->platformLayer()->id(),
idForNode(node));
}
}
for (PaintLayer* child = root->firstChild(); child;
child = child->nextSibling())
buildLayerIdToNodeIdMap(child, layerIdToNodeIdMap);
if (!root->layoutObject()->isLayoutIFrame())
return;
FrameView* childFrameView =
toFrameView(toLayoutPart(root->layoutObject())->widget());
LayoutViewItem childLayoutViewItem = childFrameView->layoutViewItem();
if (!childLayoutViewItem.isNull()) {
if (PaintLayerCompositor* childCompositor =
childLayoutViewItem.compositor())
buildLayerIdToNodeIdMap(childCompositor->rootLayer(), layerIdToNodeIdMap);
}
}
void InspectorLayerTreeAgent::gatherGraphicsLayers(
GraphicsLayer* root,
HashMap<int, int>& layerIdToNodeIdMap,
std::unique_ptr<Array<protocol::LayerTree::Layer>>& layers,
bool hasWheelEventHandlers,
int scrollingLayerId) {
int layerId = root->platformLayer()->id();
if (m_pageOverlayLayerIds.find(layerId) != WTF::kNotFound)
return;
layers->addItem(buildObjectForLayer(
root, layerIdToNodeIdMap.get(layerId),
hasWheelEventHandlers && layerId == scrollingLayerId));
for (size_t i = 0, size = root->children().size(); i < size; ++i)
gatherGraphicsLayers(root->children()[i], layerIdToNodeIdMap, layers,
hasWheelEventHandlers, scrollingLayerId);
}
int InspectorLayerTreeAgent::idForNode(Node* node) {
return DOMNodeIds::idForNode(node);
}
PaintLayerCompositor* InspectorLayerTreeAgent::paintLayerCompositor() {
LayoutViewItem layoutView = m_inspectedFrames->root()->contentLayoutItem();
PaintLayerCompositor* compositor =
layoutView.isNull() ? nullptr : layoutView.compositor();
return compositor;
}
GraphicsLayer* InspectorLayerTreeAgent::rootGraphicsLayer() {
return m_inspectedFrames->root()
->host()
->visualViewport()
.rootGraphicsLayer();
}
static GraphicsLayer* findLayerById(GraphicsLayer* root, int layerId) {
if (root->platformLayer()->id() == layerId)
return root;
for (size_t i = 0, size = root->children().size(); i < size; ++i) {
if (GraphicsLayer* layer = findLayerById(root->children()[i], layerId))
return layer;
}
return nullptr;
}
Response InspectorLayerTreeAgent::layerById(const String& layerId,
GraphicsLayer*& result) {
bool ok;
int id = layerId.toInt(&ok);
if (!ok)
return Response::Error("Invalid layer id");
PaintLayerCompositor* compositor = paintLayerCompositor();
if (!compositor)
return Response::Error("Not in compositing mode");
result = findLayerById(rootGraphicsLayer(), id);
if (!result)
return Response::Error("No layer matching given id found");
return Response::OK();
}
Response InspectorLayerTreeAgent::compositingReasons(
const String& layerId,
std::unique_ptr<Array<String>>* reasonStrings) {
GraphicsLayer* graphicsLayer = nullptr;
Response response = layerById(layerId, graphicsLayer);
if (!response.isSuccess())
return response;
CompositingReasons reasonsBitmask = graphicsLayer->getCompositingReasons();
*reasonStrings = Array<String>::create();
for (size_t i = 0; i < kNumberOfCompositingReasons; ++i) {
if (!(reasonsBitmask & kCompositingReasonStringMap[i].reason))
continue;
(*reasonStrings)->addItem(kCompositingReasonStringMap[i].shortName);
#ifndef _NDEBUG
reasonsBitmask &= ~kCompositingReasonStringMap[i].reason;
#endif
}
ASSERT(!reasonsBitmask);
return Response::OK();
}
Response InspectorLayerTreeAgent::makeSnapshot(const String& layerId,
String* snapshotId) {
GraphicsLayer* layer = nullptr;
Response response = layerById(layerId, layer);
if (!response.isSuccess())
return response;
if (!layer->drawsContent())
return Response::Error("Layer does not draw content");
IntSize size = expandedIntSize(layer->size());
IntRect interestRect(IntPoint(0, 0), size);
m_suppressLayerPaintEvents = true;
layer->paint(&interestRect);
m_suppressLayerPaintEvents = false;
GraphicsContext context(layer->getPaintController());
context.beginRecording(interestRect);
layer->getPaintController().paintArtifact().replay(context);
RefPtr<PictureSnapshot> snapshot =
adoptRef(new PictureSnapshot(context.endRecording()));
*snapshotId = String::number(++s_lastSnapshotId);
bool newEntry = m_snapshotById.add(*snapshotId, snapshot).isNewEntry;
DCHECK(newEntry);
return Response::OK();
}
Response InspectorLayerTreeAgent::loadSnapshot(
std::unique_ptr<Array<protocol::LayerTree::PictureTile>> tiles,
String* snapshotId) {
if (!tiles->length())
return Response::Error("Invalid argument, no tiles provided");
Vector<RefPtr<PictureSnapshot::TilePictureStream>> decodedTiles;
decodedTiles.grow(tiles->length());
for (size_t i = 0; i < tiles->length(); ++i) {
protocol::LayerTree::PictureTile* tile = tiles->get(i);
decodedTiles[i] = adoptRef(new PictureSnapshot::TilePictureStream());
decodedTiles[i]->layerOffset.set(tile->getX(), tile->getY());
if (!base64Decode(tile->getPicture(), decodedTiles[i]->data))
return Response::Error("Invalid base64 encoding");
}
RefPtr<PictureSnapshot> snapshot = PictureSnapshot::load(decodedTiles);
if (!snapshot)
return Response::Error("Invalid snapshot format");
if (snapshot->isEmpty())
return Response::Error("Empty snapshot");
*snapshotId = String::number(++s_lastSnapshotId);
bool newEntry = m_snapshotById.add(*snapshotId, snapshot).isNewEntry;
DCHECK(newEntry);
return Response::OK();
}
Response InspectorLayerTreeAgent::releaseSnapshot(const String& snapshotId) {
SnapshotById::iterator it = m_snapshotById.find(snapshotId);
if (it == m_snapshotById.end())
return Response::Error("Snapshot not found");
m_snapshotById.remove(it);
return Response::OK();
}
Response InspectorLayerTreeAgent::snapshotById(const String& snapshotId,
const PictureSnapshot*& result) {
SnapshotById::iterator it = m_snapshotById.find(snapshotId);
if (it == m_snapshotById.end())
return Response::Error("Snapshot not found");
result = it->value.get();
return Response::OK();
}
Response InspectorLayerTreeAgent::replaySnapshot(const String& snapshotId,
Maybe<int> fromStep,
Maybe<int> toStep,
Maybe<double> scale,
String* dataURL) {
const PictureSnapshot* snapshot = nullptr;
Response response = snapshotById(snapshotId, snapshot);
if (!response.isSuccess())
return response;
std::unique_ptr<Vector<char>> base64Data = snapshot->replay(
fromStep.fromMaybe(0), toStep.fromMaybe(0), scale.fromMaybe(1.0));
if (!base64Data)
return Response::Error("Image encoding failed");
StringBuilder url;
url.append("data:image/png;base64,");
url.reserveCapacity(url.length() + base64Data->size());
url.append(base64Data->begin(), base64Data->size());
*dataURL = url.toString();
return Response::OK();
}
static void parseRect(protocol::DOM::Rect* object, FloatRect* rect) {
*rect = FloatRect(object->getX(), object->getY(), object->getWidth(),
object->getHeight());
}
Response InspectorLayerTreeAgent::profileSnapshot(
const String& snapshotId,
Maybe<int> minRepeatCount,
Maybe<double> minDuration,
Maybe<protocol::DOM::Rect> clipRect,
std::unique_ptr<protocol::Array<protocol::Array<double>>>* outTimings) {
const PictureSnapshot* snapshot = nullptr;
Response response = snapshotById(snapshotId, snapshot);
if (!response.isSuccess())
return response;
FloatRect rect;
if (clipRect.isJust())
parseRect(clipRect.fromJust(), &rect);
std::unique_ptr<PictureSnapshot::Timings> timings =
snapshot->profile(minRepeatCount.fromMaybe(1), minDuration.fromMaybe(0),
clipRect.isJust() ? &rect : 0);
*outTimings = Array<Array<double>>::create();
for (size_t i = 0; i < timings->size(); ++i) {
const Vector<double>& row = (*timings)[i];
std::unique_ptr<Array<double>> outRow = Array<double>::create();
for (size_t j = 0; j < row.size(); ++j)
outRow->addItem(row[j]);
(*outTimings)->addItem(std::move(outRow));
}
return Response::OK();
}
Response InspectorLayerTreeAgent::snapshotCommandLog(
const String& snapshotId,
std::unique_ptr<Array<protocol::DictionaryValue>>* commandLog) {
const PictureSnapshot* snapshot = nullptr;
Response response = snapshotById(snapshotId, snapshot);
if (!response.isSuccess())
return response;
protocol::ErrorSupport errors;
std::unique_ptr<protocol::Value> logValue = protocol::StringUtil::parseJSON(
snapshot->snapshotCommandLog()->toJSONString());
*commandLog =
Array<protocol::DictionaryValue>::fromValue(logValue.get(), &errors);
if (errors.hasErrors())
return Response::Error(errors.errors());
return Response::OK();
}
void InspectorLayerTreeAgent::willAddPageOverlay(const GraphicsLayer* layer) {
m_pageOverlayLayerIds.append(layer->platformLayer()->id());
}
void InspectorLayerTreeAgent::didRemovePageOverlay(const GraphicsLayer* layer) {
size_t index = m_pageOverlayLayerIds.find(layer->platformLayer()->id());
if (index == WTF::kNotFound)
return;
m_pageOverlayLayerIds.remove(index);
}
} // namespace blink