| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/frame_host/frame_tree_node_blame_context.h" |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/test/trace_event_analyzer.h" |
| #include "base/trace_event/trace_buffer.h" |
| #include "base/trace_event/trace_event_argument.h" |
| #include "content/browser/frame_host/frame_tree.h" |
| #include "content/browser/frame_host/frame_tree_node.h" |
| #include "content/common/frame_owner_properties.h" |
| #include "content/test/test_render_view_host.h" |
| #include "content/test/test_web_contents.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/web/WebSandboxFlags.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| bool EventPointerCompare(const trace_analyzer::TraceEvent* lhs, |
| const trace_analyzer::TraceEvent* rhs) { |
| CHECK(lhs); |
| CHECK(rhs); |
| return *lhs < *rhs; |
| } |
| |
| void OnTraceDataCollected(base::Closure quit_closure, |
| base::trace_event::TraceResultBuffer* buffer, |
| const scoped_refptr<base::RefCountedString>& json, |
| bool has_more_events) { |
| buffer->AddFragment(json->data()); |
| if (!has_more_events) |
| quit_closure.Run(); |
| } |
| |
| void ExpectFrameTreeNodeObject(const trace_analyzer::TraceEvent* event) { |
| EXPECT_EQ("navigation", event->category); |
| EXPECT_EQ("FrameTreeNode", event->name); |
| } |
| |
| void ExpectFrameTreeNodeSnapshot(const trace_analyzer::TraceEvent* event) { |
| ExpectFrameTreeNodeObject(event); |
| EXPECT_TRUE(event->HasArg("snapshot")); |
| EXPECT_TRUE(event->arg_values.at("snapshot") |
| ->IsType(base::Value::Type::DICTIONARY)); |
| } |
| |
| std::string GetParentNodeID(const trace_analyzer::TraceEvent* event) { |
| const base::Value* arg_snapshot = event->arg_values.at("snapshot").get(); |
| const base::DictionaryValue* snapshot; |
| EXPECT_TRUE(arg_snapshot->GetAsDictionary(&snapshot)); |
| if (!snapshot->HasKey("parent")) |
| return std::string(); |
| const base::DictionaryValue* parent; |
| EXPECT_TRUE(snapshot->GetDictionary("parent", &parent)); |
| std::string parent_id; |
| EXPECT_TRUE(parent->GetString("id_ref", &parent_id)); |
| return parent_id; |
| } |
| |
| std::string GetSnapshotURL(const trace_analyzer::TraceEvent* event) { |
| const base::Value* arg_snapshot = event->arg_values.at("snapshot").get(); |
| const base::DictionaryValue* snapshot; |
| EXPECT_TRUE(arg_snapshot->GetAsDictionary(&snapshot)); |
| if (!snapshot->HasKey("url")) |
| return std::string(); |
| std::string url; |
| EXPECT_TRUE(snapshot->GetString("url", &url)); |
| return url; |
| } |
| |
| } // namespace |
| |
| class FrameTreeNodeBlameContextTest : public RenderViewHostImplTestHarness { |
| public: |
| FrameTree* tree() { return contents()->GetFrameTree(); } |
| FrameTreeNode* root() { return tree()->root(); } |
| int process_id() { |
| return root()->current_frame_host()->GetProcess()->GetID(); |
| } |
| |
| // Creates a frame tree specified by |shape|, which is a string of paired |
| // parentheses. Each pair of parentheses represents a FrameTreeNode, and the |
| // nesting of parentheses represents the parent-child relation between nodes. |
| // Nodes represented by outer-most parentheses are children of the root node. |
| // NOTE: Each node can have at most 9 child nodes, and the tree height (i.e., |
| // max # of edges in any root-to-leaf path) must be at most 9. |
| // See the test cases for sample usage. |
| void CreateFrameTree(const char* shape) { |
| main_test_rfh()->InitializeRenderFrameIfNeeded(); |
| CreateSubframes(root(), 1, shape); |
| } |
| |
| void RemoveAllNonRootFrames() { |
| while (root()->child_count()) |
| tree()->RemoveFrame(root()->child_at(0)); |
| } |
| |
| void StartTracing() { |
| base::trace_event::TraceLog::GetInstance()->SetEnabled( |
| base::trace_event::TraceConfig("*"), |
| base::trace_event::TraceLog::RECORDING_MODE); |
| } |
| |
| void StopTracing() { |
| base::trace_event::TraceLog::GetInstance()->SetDisabled(); |
| } |
| |
| std::unique_ptr<trace_analyzer::TraceAnalyzer> CreateTraceAnalyzer() { |
| base::trace_event::TraceResultBuffer buffer; |
| base::trace_event::TraceResultBuffer::SimpleOutput trace_output; |
| buffer.SetOutputCallback(trace_output.GetCallback()); |
| base::RunLoop run_loop; |
| buffer.Start(); |
| base::trace_event::TraceLog::GetInstance()->Flush( |
| base::Bind(&OnTraceDataCollected, run_loop.QuitClosure(), |
| base::Unretained(&buffer))); |
| run_loop.Run(); |
| buffer.Finish(); |
| |
| return base::WrapUnique( |
| trace_analyzer::TraceAnalyzer::Create(trace_output.json_output)); |
| } |
| |
| private: |
| int CreateSubframes(FrameTreeNode* node, int self_id, const char* shape) { |
| int consumption = 0; |
| for (int child_num = 1; shape[consumption++] == '('; ++child_num) { |
| int child_id = self_id * 10 + child_num; |
| tree()->AddFrame(node, process_id(), child_id, |
| blink::WebTreeScopeType::Document, std::string(), |
| base::StringPrintf("uniqueName%d", child_id), |
| blink::WebSandboxFlags::None, FrameOwnerProperties()); |
| FrameTreeNode* child = node->child_at(child_num - 1); |
| consumption += CreateSubframes(child, child_id, shape + consumption); |
| } |
| return consumption; |
| } |
| }; |
| |
| // Creates a frame tree, tests if (i) the creation of each new frame is |
| // correctly traced, and (ii) the topology given by the snapshots is correct. |
| TEST_F(FrameTreeNodeBlameContextTest, FrameCreation) { |
| /* Shape of the frame tree to be created: |
| * () |
| * / \ |
| * () () |
| * / \ | |
| * () () () |
| * | |
| * () |
| */ |
| const char* tree_shape = "(()())((()))"; |
| |
| StartTracing(); |
| CreateFrameTree(tree_shape); |
| StopTracing(); |
| |
| std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = |
| CreateTraceAnalyzer(); |
| trace_analyzer::TraceEventVector events; |
| trace_analyzer::Query q = |
| trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_CREATE_OBJECT) || |
| trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_SNAPSHOT_OBJECT); |
| analyzer->FindEvents(q, &events); |
| |
| // Two events for each new node: creation and snapshot. |
| EXPECT_EQ(12u, events.size()); |
| |
| std::set<FrameTreeNode*> creation_traced; |
| std::set<FrameTreeNode*> snapshot_traced; |
| for (auto* event : events) { |
| ExpectFrameTreeNodeObject(event); |
| FrameTreeNode* node = |
| tree()->FindByID(strtol(event->id.c_str(), nullptr, 16)); |
| EXPECT_NE(nullptr, node); |
| if (event->HasArg("snapshot")) { |
| ExpectFrameTreeNodeSnapshot(event); |
| EXPECT_FALSE(base::ContainsValue(snapshot_traced, node)); |
| snapshot_traced.insert(node); |
| std::string parent_id = GetParentNodeID(event); |
| EXPECT_FALSE(parent_id.empty()); |
| EXPECT_EQ(node->parent(), |
| tree()->FindByID(strtol(parent_id.c_str(), nullptr, 16))); |
| } else { |
| EXPECT_EQ(TRACE_EVENT_PHASE_CREATE_OBJECT, event->phase); |
| EXPECT_FALSE(base::ContainsValue(creation_traced, node)); |
| creation_traced.insert(node); |
| } |
| } |
| } |
| |
| // Deletes frames from a frame tree, tests if the destruction of each frame is |
| // correctly traced. |
| TEST_F(FrameTreeNodeBlameContextTest, FrameDeletion) { |
| /* Shape of the frame tree to be created: |
| * () |
| * / \ |
| * () () |
| * / \ | |
| * () () () |
| * | |
| * () |
| */ |
| const char* tree_shape = "(()())((()))"; |
| |
| CreateFrameTree(tree_shape); |
| std::set<int> node_ids; |
| for (FrameTreeNode* node : tree()->Nodes()) |
| node_ids.insert(node->frame_tree_node_id()); |
| |
| StartTracing(); |
| RemoveAllNonRootFrames(); |
| StopTracing(); |
| |
| std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = |
| CreateTraceAnalyzer(); |
| trace_analyzer::TraceEventVector events; |
| trace_analyzer::Query q = |
| trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_DELETE_OBJECT); |
| analyzer->FindEvents(q, &events); |
| |
| // The removal of all non-root nodes should be traced. |
| EXPECT_EQ(6u, events.size()); |
| for (auto* event : events) { |
| ExpectFrameTreeNodeObject(event); |
| int id = strtol(event->id.c_str(), nullptr, 16); |
| EXPECT_TRUE(base::ContainsValue(node_ids, id)); |
| node_ids.erase(id); |
| } |
| } |
| |
| // Changes URL of the root node. Tests if URL change is correctly traced. |
| TEST_F(FrameTreeNodeBlameContextTest, URLChange) { |
| main_test_rfh()->InitializeRenderFrameIfNeeded(); |
| GURL url1("http://a.com/"); |
| GURL url2("https://b.net/"); |
| |
| StartTracing(); |
| root()->SetCurrentURL(url1); |
| root()->SetCurrentURL(url2); |
| root()->ResetForNewProcess(); |
| StopTracing(); |
| |
| std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = |
| CreateTraceAnalyzer(); |
| trace_analyzer::TraceEventVector events; |
| trace_analyzer::Query q = |
| trace_analyzer::Query::EventPhaseIs(TRACE_EVENT_PHASE_SNAPSHOT_OBJECT); |
| analyzer->FindEvents(q, &events); |
| std::sort(events.begin(), events.end(), EventPointerCompare); |
| |
| // Three snapshots are traced, one for each URL change. |
| EXPECT_EQ(3u, events.size()); |
| EXPECT_EQ(url1.spec(), GetSnapshotURL(events[0])); |
| EXPECT_EQ(url2.spec(), GetSnapshotURL(events[1])); |
| EXPECT_EQ("", GetSnapshotURL(events[2])); |
| } |
| |
| } // namespace content |