| /* |
| * 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 "third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.h" |
| |
| #include <memory> |
| |
| #include "cc/base/region.h" |
| #include "cc/layers/picture_layer.h" |
| #include "third_party/blink/public/platform/web_float_point.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/inspector/identifiers_factory.h" |
| #include "third_party/blink/renderer/core/inspector/inspected_frames.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h" |
| #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/platform/geometry/int_rect.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing_reasons.h" |
| #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/picture_snapshot.h" |
| #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" |
| #include "third_party/blink/renderer/platform/wtf/text/base64.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace blink { |
| |
| using protocol::Array; |
| using protocol::Maybe; |
| using protocol::Response; |
| unsigned InspectorLayerTreeAgent::last_snapshot_id_; |
| |
| inline String IdForLayer(const GraphicsLayer* graphics_layer) { |
| return String::Number(graphics_layer->CcLayer()->id()); |
| } |
| |
| static std::unique_ptr<protocol::DOM::Rect> BuildObjectForRect( |
| const gfx::Rect& rect) { |
| return protocol::DOM::Rect::create() |
| .setX(rect.x()) |
| .setY(rect.y()) |
| .setHeight(rect.height()) |
| .setWidth(rect.width()) |
| .build(); |
| } |
| |
| static std::unique_ptr<protocol::LayerTree::ScrollRect> BuildScrollRect( |
| const gfx::Rect& rect, |
| const String& type) { |
| std::unique_ptr<protocol::DOM::Rect> rect_object = BuildObjectForRect(rect); |
| std::unique_ptr<protocol::LayerTree::ScrollRect> scroll_rect_object = |
| protocol::LayerTree::ScrollRect::create() |
| .setRect(std::move(rect_object)) |
| .setType(type) |
| .build(); |
| return scroll_rect_object; |
| } |
| |
| static std::unique_ptr<Array<protocol::LayerTree::ScrollRect>> |
| BuildScrollRectsForLayer(GraphicsLayer* graphics_layer, |
| bool report_wheel_scrollers) { |
| std::unique_ptr<Array<protocol::LayerTree::ScrollRect>> scroll_rects = |
| Array<protocol::LayerTree::ScrollRect>::create(); |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| const cc::Region& non_fast_scrollable_rects = |
| cc_layer->non_fast_scrollable_region(); |
| for (const gfx::Rect& rect : non_fast_scrollable_rects) { |
| scroll_rects->addItem(BuildScrollRect( |
| IntRect(rect), |
| protocol::LayerTree::ScrollRect::TypeEnum::RepaintsOnScroll)); |
| } |
| const cc::Region& touch_event_handler_region = |
| cc_layer->touch_action_region().region(); |
| |
| for (const gfx::Rect& rect : touch_event_handler_region) { |
| scroll_rects->addItem(BuildScrollRect( |
| IntRect(rect), |
| protocol::LayerTree::ScrollRect::TypeEnum::TouchEventHandler)); |
| } |
| if (report_wheel_scrollers) { |
| scroll_rects->addItem(BuildScrollRect( |
| // TODO(yutak): This truncates the floating point position to integers. |
| gfx::Rect(cc_layer->position().x(), cc_layer->position().y(), |
| cc_layer->bounds().width(), cc_layer->bounds().height()), |
| protocol::LayerTree::ScrollRect::TypeEnum::WheelEventHandler)); |
| } |
| return scroll_rects->length() ? std::move(scroll_rects) : nullptr; |
| } |
| |
| // TODO(flackr): We should be getting the sticky position constraints from the |
| // property tree once blink is able to access them. https://crbug.com/754339 |
| static GraphicsLayer* FindLayerByElementId(GraphicsLayer* root, |
| CompositorElementId element_id) { |
| if (root->CcLayer()->element_id() == element_id) |
| return root; |
| for (size_t i = 0, size = root->Children().size(); i < size; ++i) { |
| if (GraphicsLayer* layer = |
| FindLayerByElementId(root->Children()[i], element_id)) |
| return layer; |
| } |
| return nullptr; |
| } |
| |
| static std::unique_ptr<protocol::LayerTree::StickyPositionConstraint> |
| BuildStickyInfoForLayer(GraphicsLayer* root, cc::Layer* layer) { |
| cc::LayerStickyPositionConstraint constraints = |
| layer->sticky_position_constraint(); |
| if (!constraints.is_sticky) |
| return nullptr; |
| |
| std::unique_ptr<protocol::DOM::Rect> sticky_box_rect = |
| BuildObjectForRect(constraints.scroll_container_relative_sticky_box_rect); |
| |
| std::unique_ptr<protocol::DOM::Rect> containing_block_rect = |
| BuildObjectForRect( |
| constraints.scroll_container_relative_containing_block_rect); |
| |
| std::unique_ptr<protocol::LayerTree::StickyPositionConstraint> |
| constraints_obj = |
| protocol::LayerTree::StickyPositionConstraint::create() |
| .setStickyBoxRect(std::move(sticky_box_rect)) |
| .setContainingBlockRect(std::move(containing_block_rect)) |
| .build(); |
| if (constraints.nearest_element_shifting_sticky_box) { |
| constraints_obj->setNearestLayerShiftingStickyBox(String::Number( |
| FindLayerByElementId(root, |
| constraints.nearest_element_shifting_sticky_box) |
| ->CcLayer() |
| ->id())); |
| } |
| if (constraints.nearest_element_shifting_containing_block) { |
| constraints_obj->setNearestLayerShiftingContainingBlock(String::Number( |
| FindLayerByElementId( |
| root, constraints.nearest_element_shifting_containing_block) |
| ->CcLayer() |
| ->id())); |
| } |
| |
| return constraints_obj; |
| } |
| |
| static std::unique_ptr<protocol::LayerTree::Layer> BuildObjectForLayer( |
| GraphicsLayer* root, |
| GraphicsLayer* graphics_layer, |
| int node_id, |
| bool report_wheel_event_listeners) { |
| cc::Layer* cc_layer = graphics_layer->CcLayer(); |
| std::unique_ptr<protocol::LayerTree::Layer> layer_object = |
| protocol::LayerTree::Layer::create() |
| .setLayerId(IdForLayer(graphics_layer)) |
| .setOffsetX(cc_layer->position().x()) |
| .setOffsetY(cc_layer->position().y()) |
| .setWidth(cc_layer->bounds().width()) |
| .setHeight(cc_layer->bounds().height()) |
| .setPaintCount(graphics_layer->PaintCount()) |
| .setDrawsContent(cc_layer->DrawsContent()) |
| .build(); |
| |
| if (node_id) |
| layer_object->setBackendNodeId(node_id); |
| |
| GraphicsLayer* parent = graphics_layer->Parent(); |
| if (parent) |
| layer_object->setParentLayerId(IdForLayer(parent)); |
| if (!graphics_layer->ContentsAreVisible()) |
| layer_object->setInvisible(true); |
| const TransformationMatrix& transform = graphics_layer->Transform(); |
| if (!transform.IsIdentity()) { |
| TransformationMatrix::FloatMatrix4 flattened_matrix; |
| transform.ToColumnMajorFloatArray(flattened_matrix); |
| std::unique_ptr<Array<double>> transform_array = Array<double>::create(); |
| for (size_t i = 0; i < arraysize(flattened_matrix); ++i) |
| transform_array->addItem(flattened_matrix[i]); |
| layer_object->setTransform(std::move(transform_array)); |
| const FloatPoint3D& transform_origin = graphics_layer->TransformOrigin(); |
| // FIXME: rename these to setTransformOrigin* |
| if (cc_layer->bounds().width() > 0) { |
| layer_object->setAnchorX(transform_origin.X() / |
| cc_layer->bounds().width()); |
| } else { |
| layer_object->setAnchorX(0.f); |
| } |
| if (cc_layer->bounds().height() > 0) { |
| layer_object->setAnchorY(transform_origin.Y() / |
| cc_layer->bounds().height()); |
| } else { |
| layer_object->setAnchorY(0.f); |
| } |
| layer_object->setAnchorZ(transform_origin.Z()); |
| } |
| std::unique_ptr<Array<protocol::LayerTree::ScrollRect>> scroll_rects = |
| BuildScrollRectsForLayer(graphics_layer, report_wheel_event_listeners); |
| if (scroll_rects) |
| layer_object->setScrollRects(std::move(scroll_rects)); |
| std::unique_ptr<protocol::LayerTree::StickyPositionConstraint> sticky_info = |
| BuildStickyInfoForLayer(root, cc_layer); |
| if (sticky_info) |
| layer_object->setStickyPositionConstraint(std::move(sticky_info)); |
| return layer_object; |
| } |
| |
| InspectorLayerTreeAgent::InspectorLayerTreeAgent( |
| InspectedFrames* inspected_frames, |
| Client* client) |
| : inspected_frames_(inspected_frames), |
| client_(client), |
| suppress_layer_paint_events_(false) {} |
| |
| InspectorLayerTreeAgent::~InspectorLayerTreeAgent() = default; |
| |
| void InspectorLayerTreeAgent::Trace(blink::Visitor* visitor) { |
| visitor->Trace(inspected_frames_); |
| 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() { |
| instrumenting_agents_->addInspectorLayerTreeAgent(this); |
| Document* document = inspected_frames_->Root()->GetDocument(); |
| if (document && |
| document->Lifecycle().GetState() >= DocumentLifecycle::kCompositingClean) |
| LayerTreeDidChange(); |
| return Response::OK(); |
| } |
| |
| Response InspectorLayerTreeAgent::disable() { |
| instrumenting_agents_->removeInspectorLayerTreeAgent(this); |
| snapshot_by_id_.clear(); |
| return Response::OK(); |
| } |
| |
| void InspectorLayerTreeAgent::LayerTreeDidChange() { |
| GetFrontend()->layerTreeDidChange(BuildLayerTree()); |
| } |
| |
| void InspectorLayerTreeAgent::DidPaint(const GraphicsLayer* graphics_layer, |
| GraphicsContext&, |
| const LayoutRect& rect) { |
| if (suppress_layer_paint_events_) |
| return; |
| // Should only happen for LocalFrameView paints when compositing is off. |
| // Consider different instrumentation method for that. |
| if (!graphics_layer) |
| return; |
| |
| std::unique_ptr<protocol::DOM::Rect> dom_rect = protocol::DOM::Rect::create() |
| .setX(rect.X()) |
| .setY(rect.Y()) |
| .setWidth(rect.Width()) |
| .setHeight(rect.Height()) |
| .build(); |
| GetFrontend()->layerPainted(IdForLayer(graphics_layer), std::move(dom_rect)); |
| } |
| |
| std::unique_ptr<Array<protocol::LayerTree::Layer>> |
| InspectorLayerTreeAgent::BuildLayerTree() { |
| PaintLayerCompositor* compositor = GetPaintLayerCompositor(); |
| if (!compositor || !compositor->InCompositingMode()) |
| return nullptr; |
| |
| LayerIdToNodeIdMap layer_id_to_node_id_map; |
| std::unique_ptr<Array<protocol::LayerTree::Layer>> layers = |
| Array<protocol::LayerTree::Layer>::create(); |
| BuildLayerIdToNodeIdMap(compositor->RootLayer(), layer_id_to_node_id_map); |
| auto* layer_for_scrolling = |
| inspected_frames_->Root()->View()->LayoutViewport()->LayerForScrolling(); |
| int scrolling_layer_id = |
| layer_for_scrolling ? layer_for_scrolling->CcLayer()->id() : 0; |
| bool have_blocking_wheel_event_handlers = |
| inspected_frames_->Root()->GetChromeClient().EventListenerProperties( |
| inspected_frames_->Root(), WebEventListenerClass::kMouseWheel) == |
| WebEventListenerProperties::kBlocking; |
| |
| GatherGraphicsLayers(RootGraphicsLayer(), layer_id_to_node_id_map, layers, |
| have_blocking_wheel_event_handlers, scrolling_layer_id); |
| return layers; |
| } |
| |
| void InspectorLayerTreeAgent::BuildLayerIdToNodeIdMap( |
| PaintLayer* root, |
| LayerIdToNodeIdMap& layer_id_to_node_id_map) { |
| if (root->HasCompositedLayerMapping()) { |
| if (Node* node = root->GetLayoutObject().GeneratingNode()) { |
| GraphicsLayer* graphics_layer = |
| root->GetCompositedLayerMapping()->ChildForSuperlayers(); |
| layer_id_to_node_id_map.Set(graphics_layer->CcLayer()->id(), |
| IdForNode(node)); |
| } |
| } |
| for (PaintLayer* child = root->FirstChild(); child; |
| child = child->NextSibling()) |
| BuildLayerIdToNodeIdMap(child, layer_id_to_node_id_map); |
| if (!root->GetLayoutObject().IsLayoutIFrame()) |
| return; |
| FrameView* child_frame_view = |
| ToLayoutEmbeddedContent(root->GetLayoutObject()).ChildFrameView(); |
| if (!child_frame_view || !child_frame_view->IsLocalFrameView()) |
| return; |
| LayoutView* child_layout_view = |
| ToLocalFrameView(child_frame_view)->GetLayoutView(); |
| if (!child_layout_view) |
| return; |
| PaintLayerCompositor* child_compositor = child_layout_view->Compositor(); |
| if (!child_compositor) |
| return; |
| BuildLayerIdToNodeIdMap(child_compositor->RootLayer(), |
| layer_id_to_node_id_map); |
| } |
| |
| void InspectorLayerTreeAgent::GatherGraphicsLayers( |
| GraphicsLayer* layer, |
| HashMap<int, int>& layer_id_to_node_id_map, |
| std::unique_ptr<Array<protocol::LayerTree::Layer>>& layers, |
| bool has_wheel_event_handlers, |
| int scrolling_layer_id) { |
| if (client_->IsInspectorLayer(layer)) |
| return; |
| int layer_id = layer->CcLayer()->id(); |
| layers->addItem(BuildObjectForLayer( |
| RootGraphicsLayer(), layer, layer_id_to_node_id_map.at(layer_id), |
| has_wheel_event_handlers && layer_id == scrolling_layer_id)); |
| for (size_t i = 0, size = layer->Children().size(); i < size; ++i) |
| GatherGraphicsLayers(layer->Children()[i], layer_id_to_node_id_map, layers, |
| has_wheel_event_handlers, scrolling_layer_id); |
| } |
| |
| int InspectorLayerTreeAgent::IdForNode(Node* node) { |
| return DOMNodeIds::IdForNode(node); |
| } |
| |
| PaintLayerCompositor* InspectorLayerTreeAgent::GetPaintLayerCompositor() { |
| auto* layout_view = inspected_frames_->Root()->ContentLayoutObject(); |
| PaintLayerCompositor* compositor = |
| layout_view ? layout_view->Compositor() : nullptr; |
| return compositor; |
| } |
| |
| GraphicsLayer* InspectorLayerTreeAgent::RootGraphicsLayer() { |
| return inspected_frames_->Root() |
| ->GetPage() |
| ->GetVisualViewport() |
| .RootGraphicsLayer(); |
| } |
| |
| static GraphicsLayer* FindLayerById(GraphicsLayer* root, int layer_id) { |
| if (root->CcLayer()->id() == layer_id) |
| return root; |
| for (size_t i = 0, size = root->Children().size(); i < size; ++i) { |
| if (GraphicsLayer* layer = FindLayerById(root->Children()[i], layer_id)) |
| return layer; |
| } |
| return nullptr; |
| } |
| |
| Response InspectorLayerTreeAgent::LayerById(const String& layer_id, |
| GraphicsLayer*& result) { |
| bool ok; |
| int id = layer_id.ToInt(&ok); |
| if (!ok) |
| return Response::Error("Invalid layer id"); |
| PaintLayerCompositor* compositor = GetPaintLayerCompositor(); |
| 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& layer_id, |
| std::unique_ptr<Array<String>>* reason_strings) { |
| GraphicsLayer* graphics_layer = nullptr; |
| Response response = LayerById(layer_id, graphics_layer); |
| if (!response.isSuccess()) |
| return response; |
| CompositingReasons reasons_bitmask = graphics_layer->GetCompositingReasons(); |
| *reason_strings = Array<String>::create(); |
| for (const char* name : CompositingReason::ShortNames(reasons_bitmask)) |
| (*reason_strings)->addItem(name); |
| return Response::OK(); |
| } |
| |
| Response InspectorLayerTreeAgent::makeSnapshot(const String& layer_id, |
| String* snapshot_id) { |
| GraphicsLayer* layer = nullptr; |
| Response response = LayerById(layer_id, layer); |
| if (!response.isSuccess()) |
| return response; |
| if (!layer->DrawsContent()) |
| return Response::Error("Layer does not draw content"); |
| |
| IntRect interest_rect(IntPoint(), layer->Size()); |
| suppress_layer_paint_events_ = true; |
| |
| // If we hit a devtool break point in the middle of document lifecycle, for |
| // example, https://crbug.com/788219, this will prevent crash when clicking |
| // the "layer" panel. |
| if (inspected_frames_->Root()->GetDocument() && inspected_frames_->Root() |
| ->GetDocument() |
| ->Lifecycle() |
| .LifecyclePostponed()) |
| return Response::Error("Layer does not draw content"); |
| |
| inspected_frames_->Root()->View()->UpdateAllLifecyclePhasesExceptPaint(); |
| for (auto frame = inspected_frames_->begin(); |
| frame != inspected_frames_->end(); ++frame) { |
| frame->GetDocument()->Lifecycle().AdvanceTo(DocumentLifecycle::kInPaint); |
| } |
| layer->Paint(&interest_rect); |
| for (auto frame = inspected_frames_->begin(); |
| frame != inspected_frames_->end(); ++frame) { |
| frame->GetDocument()->Lifecycle().AdvanceTo(DocumentLifecycle::kPaintClean); |
| } |
| |
| suppress_layer_paint_events_ = false; |
| |
| auto snapshot = base::AdoptRef(new PictureSnapshot( |
| ToSkPicture(layer->CapturePaintRecord(), interest_rect))); |
| |
| *snapshot_id = String::Number(++last_snapshot_id_); |
| bool new_entry = snapshot_by_id_.insert(*snapshot_id, snapshot).is_new_entry; |
| DCHECK(new_entry); |
| return Response::OK(); |
| } |
| |
| Response InspectorLayerTreeAgent::loadSnapshot( |
| std::unique_ptr<Array<protocol::LayerTree::PictureTile>> tiles, |
| String* snapshot_id) { |
| if (!tiles->length()) |
| return Response::Error("Invalid argument, no tiles provided"); |
| Vector<scoped_refptr<PictureSnapshot::TilePictureStream>> decoded_tiles; |
| decoded_tiles.Grow(tiles->length()); |
| for (size_t i = 0; i < tiles->length(); ++i) { |
| protocol::LayerTree::PictureTile* tile = tiles->get(i); |
| decoded_tiles[i] = base::AdoptRef(new PictureSnapshot::TilePictureStream()); |
| decoded_tiles[i]->layer_offset.Set(tile->getX(), tile->getY()); |
| if (!Base64Decode(tile->getPicture(), decoded_tiles[i]->data)) |
| return Response::Error("Invalid base64 encoding"); |
| } |
| scoped_refptr<PictureSnapshot> snapshot = |
| PictureSnapshot::Load(decoded_tiles); |
| if (!snapshot) |
| return Response::Error("Invalid snapshot format"); |
| if (snapshot->IsEmpty()) |
| return Response::Error("Empty snapshot"); |
| |
| *snapshot_id = String::Number(++last_snapshot_id_); |
| bool new_entry = snapshot_by_id_.insert(*snapshot_id, snapshot).is_new_entry; |
| DCHECK(new_entry); |
| return Response::OK(); |
| } |
| |
| Response InspectorLayerTreeAgent::releaseSnapshot(const String& snapshot_id) { |
| SnapshotById::iterator it = snapshot_by_id_.find(snapshot_id); |
| if (it == snapshot_by_id_.end()) |
| return Response::Error("Snapshot not found"); |
| snapshot_by_id_.erase(it); |
| return Response::OK(); |
| } |
| |
| Response InspectorLayerTreeAgent::GetSnapshotById( |
| const String& snapshot_id, |
| const PictureSnapshot*& result) { |
| SnapshotById::iterator it = snapshot_by_id_.find(snapshot_id); |
| if (it == snapshot_by_id_.end()) |
| return Response::Error("Snapshot not found"); |
| result = it->value.get(); |
| return Response::OK(); |
| } |
| |
| Response InspectorLayerTreeAgent::replaySnapshot(const String& snapshot_id, |
| Maybe<int> from_step, |
| Maybe<int> to_step, |
| Maybe<double> scale, |
| String* data_url) { |
| const PictureSnapshot* snapshot = nullptr; |
| Response response = GetSnapshotById(snapshot_id, snapshot); |
| if (!response.isSuccess()) |
| return response; |
| Vector<char> base64_data = snapshot->Replay( |
| from_step.fromMaybe(0), to_step.fromMaybe(0), scale.fromMaybe(1.0)); |
| if (base64_data.IsEmpty()) |
| return Response::Error("Image encoding failed"); |
| static constexpr char kUrlPrefix[] = "data:image/png;base64,"; |
| StringBuilder url; |
| url.ReserveCapacity(sizeof(kUrlPrefix) + base64_data.size()); |
| url.Append(kUrlPrefix); |
| url.Append(base64_data.begin(), base64_data.size()); |
| *data_url = 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& snapshot_id, |
| Maybe<int> min_repeat_count, |
| Maybe<double> min_duration, |
| Maybe<protocol::DOM::Rect> clip_rect, |
| std::unique_ptr<protocol::Array<protocol::Array<double>>>* out_timings) { |
| const PictureSnapshot* snapshot = nullptr; |
| Response response = GetSnapshotById(snapshot_id, snapshot); |
| if (!response.isSuccess()) |
| return response; |
| FloatRect rect; |
| if (clip_rect.isJust()) |
| ParseRect(clip_rect.fromJust(), &rect); |
| auto timings = |
| snapshot->Profile(min_repeat_count.fromMaybe(1), |
| TimeDelta::FromSecondsD(min_duration.fromMaybe(0)), |
| clip_rect.isJust() ? &rect : nullptr); |
| *out_timings = Array<Array<double>>::create(); |
| for (const auto& row : timings) { |
| std::unique_ptr<Array<double>> out_row = Array<double>::create(); |
| for (TimeDelta delta : row) |
| out_row->addItem(delta.InSecondsF()); |
| (*out_timings)->addItem(std::move(out_row)); |
| } |
| return Response::OK(); |
| } |
| |
| Response InspectorLayerTreeAgent::snapshotCommandLog( |
| const String& snapshot_id, |
| std::unique_ptr<Array<protocol::DictionaryValue>>* command_log) { |
| const PictureSnapshot* snapshot = nullptr; |
| Response response = GetSnapshotById(snapshot_id, snapshot); |
| if (!response.isSuccess()) |
| return response; |
| protocol::ErrorSupport errors; |
| std::unique_ptr<protocol::Value> log_value = protocol::StringUtil::parseJSON( |
| snapshot->SnapshotCommandLog()->ToJSONString()); |
| *command_log = |
| Array<protocol::DictionaryValue>::fromValue(log_value.get(), &errors); |
| if (errors.hasErrors()) |
| return Response::Error(errors.errors()); |
| return Response::OK(); |
| } |
| |
| } // namespace blink |