| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "content/browser/frame_host/frame_tree_node_blame_context.h" |
| 6 |
| 7 #include "base/memory/ptr_util.h" |
| 8 #include "base/run_loop.h" |
| 9 #include "base/test/trace_event_analyzer.h" |
| 10 #include "base/trace_event/trace_buffer.h" |
| 11 #include "base/trace_event/trace_event_argument.h" |
| 12 #include "content/browser/frame_host/frame_tree.h" |
| 13 #include "content/browser/frame_host/frame_tree_node.h" |
| 14 #include "content/test/test_render_view_host.h" |
| 15 #include "content/test/test_web_contents.h" |
| 16 #include "testing/gtest/include/gtest/gtest.h" |
| 17 #include "third_party/WebKit/public/web/WebSandboxFlags.h" |
| 18 |
| 19 namespace content { |
| 20 |
| 21 namespace { |
| 22 |
| 23 bool PointerCompare(const trace_analyzer::TraceEvent* lhs, |
| 24 const trace_analyzer::TraceEvent* rhs) { |
| 25 CHECK(lhs); |
| 26 CHECK(rhs); |
| 27 return *lhs < *rhs; |
| 28 } |
| 29 |
| 30 void OnTraceDataCollected(base::Closure quit_closure, |
| 31 base::trace_event::TraceResultBuffer* buffer, |
| 32 const scoped_refptr<base::RefCountedString>& json, |
| 33 bool has_more_events) { |
| 34 buffer->AddFragment(json->data()); |
| 35 if (!has_more_events) |
| 36 quit_closure.Run(); |
| 37 } |
| 38 |
| 39 void ExpectFrameTreeNodeObject(const trace_analyzer::TraceEvent* event) { |
| 40 EXPECT_EQ("navigation", event->category); |
| 41 EXPECT_EQ("Frame", event->name); |
| 42 } |
| 43 |
| 44 void ExpectFrameTreeNodeSnapshot(const trace_analyzer::TraceEvent* event) { |
| 45 ExpectFrameTreeNodeObject(event); |
| 46 EXPECT_TRUE(event->HasArg("snapshot")); |
| 47 EXPECT_TRUE(event->arg_values.at("snapshot") |
| 48 ->IsType(base::Value::Type::TYPE_DICTIONARY)); |
| 49 } |
| 50 |
| 51 std::string GetParentNodeID(const trace_analyzer::TraceEvent* event) { |
| 52 const base::Value* arg_snapshot = event->arg_values.at("snapshot").get(); |
| 53 const base::DictionaryValue* snapshot; |
| 54 EXPECT_TRUE(arg_snapshot->GetAsDictionary(&snapshot)); |
| 55 if (!snapshot->HasKey("parent")) |
| 56 return std::string(); |
| 57 const base::DictionaryValue* parent; |
| 58 EXPECT_TRUE(snapshot->GetDictionary("parent", &parent)); |
| 59 std::string parent_id; |
| 60 EXPECT_TRUE(parent->GetString("id_ref", &parent_id)); |
| 61 return parent_id; |
| 62 } |
| 63 |
| 64 std::string GetSnapshotURL(const trace_analyzer::TraceEvent* event) { |
| 65 const base::Value* arg_snapshot = event->arg_values.at("snapshot").get(); |
| 66 const base::DictionaryValue* snapshot; |
| 67 EXPECT_TRUE(arg_snapshot->GetAsDictionary(&snapshot)); |
| 68 if (!snapshot->HasKey("url")) |
| 69 return std::string(); |
| 70 std::string url; |
| 71 EXPECT_TRUE(snapshot->GetString("url", &url)); |
| 72 return url; |
| 73 } |
| 74 |
| 75 } // namespace |
| 76 |
| 77 class FrameTreeNodeBlameContextTest : public RenderViewHostImplTestHarness { |
| 78 public: |
| 79 FrameTree* tree() { return contents()->GetFrameTree(); } |
| 80 FrameTreeNode* root() { return tree()->root(); } |
| 81 int process_id() { |
| 82 return root()->current_frame_host()->GetProcess()->GetID(); |
| 83 } |
| 84 |
| 85 // Create a frame tree specified by shape, which is a string of paired |
| 86 // parentheses. Each pair of parentheses represents a FrameTreeNode, and the |
| 87 // nesting of parentheses represents the parent-child relation between nodes. |
| 88 // Nodes represented by outer-most parentheses are children of the root node. |
| 89 // See the test cases for sample usage. |
| 90 void CreateFrameTree(const char* shape) { |
| 91 main_test_rfh()->InitializeRenderFrameIfNeeded(); |
| 92 CreateSubframes(root(), 1, shape); |
| 93 } |
| 94 |
| 95 void RemoveAllNonRootFrames() { |
| 96 RemoveAllSubFramesRecursively(root()); |
| 97 } |
| 98 |
| 99 void StartTracing() { |
| 100 base::trace_event::TraceLog::GetInstance()->SetEnabled( |
| 101 base::trace_event::TraceConfig("*"), |
| 102 base::trace_event::TraceLog::RECORDING_MODE); |
| 103 } |
| 104 |
| 105 void StopTracing() { |
| 106 base::trace_event::TraceLog::GetInstance()->SetDisabled(); |
| 107 } |
| 108 |
| 109 std::unique_ptr<trace_analyzer::TraceAnalyzer> CreateTraceAnalyzer() { |
| 110 base::trace_event::TraceResultBuffer buffer; |
| 111 base::trace_event::TraceResultBuffer::SimpleOutput trace_output; |
| 112 buffer.SetOutputCallback(trace_output.GetCallback()); |
| 113 base::RunLoop run_loop; |
| 114 buffer.Start(); |
| 115 base::trace_event::TraceLog::GetInstance()->Flush( |
| 116 base::Bind(&OnTraceDataCollected, run_loop.QuitClosure(), |
| 117 base::Unretained(&buffer))); |
| 118 run_loop.Run(); |
| 119 buffer.Finish(); |
| 120 |
| 121 return base::WrapUnique( |
| 122 trace_analyzer::TraceAnalyzer::Create(trace_output.json_output)); |
| 123 } |
| 124 |
| 125 private: |
| 126 int CreateSubframes(FrameTreeNode* node, int self_id, const char* shape) { |
| 127 int consumption = 0; |
| 128 for (int child_num = 1; ; ++child_num) { |
| 129 char ch = shape[consumption++]; |
| 130 if (!ch || ch == ')') |
| 131 break; |
| 132 int child_id = self_id * 10 + child_num; |
| 133 tree()->AddFrame(node, process_id(), child_id, |
| 134 blink::WebTreeScopeType::Document, std::string(), |
| 135 base::StringPrintf("uniqueName%d", child_id), |
| 136 blink::WebSandboxFlags::None, |
| 137 blink::WebFrameOwnerProperties()); |
| 138 FrameTreeNode* child = node->child_at(child_num - 1); |
| 139 consumption += CreateSubframes(child, child_id, shape + consumption); |
| 140 } |
| 141 return consumption; |
| 142 } |
| 143 |
| 144 void RemoveAllSubFramesRecursively(FrameTreeNode* node) { |
| 145 while (node->child_count()) { |
| 146 FrameTreeNode* child = node->child_at(0); |
| 147 RemoveAllSubFramesRecursively(child); |
| 148 tree()->RemoveFrame(child); |
| 149 } |
| 150 } |
| 151 }; |
| 152 |
| 153 // Create a frame tree with tracing off, and then enable tracing. |
| 154 // Test if the frame tree is dumped with correct topology. |
| 155 TEST_F(FrameTreeNodeBlameContextTest, OnTracingEnabled) { |
| 156 /* Shape of the frame tree to be created: |
| 157 * () |
| 158 * / \ |
| 159 * () () |
| 160 * / \ | |
| 161 * () () () |
| 162 * | |
| 163 * () |
| 164 */ |
| 165 const char* tree_shape = "(()())((()))"; |
| 166 |
| 167 CreateFrameTree(tree_shape); |
| 168 StartTracing(); |
| 169 StopTracing(); |
| 170 |
| 171 std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = |
| 172 CreateTraceAnalyzer(); |
| 173 trace_analyzer::TraceEventVector events; |
| 174 trace_analyzer::Query q = trace_analyzer::Query::EventPhaseIs( |
| 175 TRACE_EVENT_PHASE_SNAPSHOT_OBJECT); |
| 176 analyzer->FindEvents(q, &events); |
| 177 |
| 178 EXPECT_EQ(7u, events.size()); |
| 179 for (int i = 0; i < 7; ++i) { |
| 180 ExpectFrameTreeNodeSnapshot(events[i]); |
| 181 FrameTreeNode* node = tree()->FindByID(strtol(events[i]->id.c_str(), |
| 182 nullptr, 16)); |
| 183 EXPECT_NE(nullptr, node); |
| 184 std::string parent_id = GetParentNodeID(events[i]); |
| 185 if (parent_id.empty()) |
| 186 EXPECT_EQ(nullptr, node->parent()); |
| 187 else { |
| 188 FrameTreeNode* parent = tree()->FindByID(strtol(parent_id.c_str(), |
| 189 nullptr, 16)); |
| 190 EXPECT_EQ(node->parent(), parent); |
| 191 } |
| 192 } |
| 193 } |
| 194 |
| 195 // Create a frame tree with tracing on. |
| 196 // Test if the creation of each frame is correctly traced. |
| 197 TEST_F(FrameTreeNodeBlameContextTest, OnNewFrameCreation) { |
| 198 /* Shape of the frame tree to be created: |
| 199 * () |
| 200 * / \ |
| 201 * () () |
| 202 * / \ | |
| 203 * () () () |
| 204 * | |
| 205 * () |
| 206 */ |
| 207 const char* tree_shape = "(()())((()))"; |
| 208 |
| 209 StartTracing(); |
| 210 CreateFrameTree(tree_shape); |
| 211 StopTracing(); |
| 212 |
| 213 std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = |
| 214 CreateTraceAnalyzer(); |
| 215 trace_analyzer::TraceEventVector events; |
| 216 trace_analyzer::Query q = trace_analyzer::Query::EventPhaseIs( |
| 217 TRACE_EVENT_PHASE_CREATE_OBJECT); |
| 218 analyzer->FindEvents(q, &events); |
| 219 |
| 220 // The creation of all non-root nodes should be traced. |
| 221 EXPECT_EQ(6u, events.size()); |
| 222 for (int i = 0; i < 6; i++) { |
| 223 ExpectFrameTreeNodeObject(events[i]); |
| 224 FrameTreeNode* node = tree()->FindByID(strtol(events[i]->id.c_str(), |
| 225 nullptr, 16)); |
| 226 EXPECT_NE(nullptr, node); |
| 227 } |
| 228 } |
| 229 |
| 230 // Delete frames from a frame tree with tracing on. |
| 231 // Test if the destruction of each blame context is correctly traced. |
| 232 TEST_F(FrameTreeNodeBlameContextTest, OnFrameDeletion) { |
| 233 /* Shape of the frame tree to be created: |
| 234 * () |
| 235 * / \ |
| 236 * () () |
| 237 * / \ | |
| 238 * () () () |
| 239 * | |
| 240 * () |
| 241 */ |
| 242 const char* tree_shape = "(()())((()))"; |
| 243 |
| 244 CreateFrameTree(tree_shape); |
| 245 std::set<int> node_ids; |
| 246 { |
| 247 FrameTree::NodeRange nodes = tree()->Nodes(); |
| 248 for (auto it = nodes.begin(); it != nodes.end(); ++it) |
| 249 node_ids.insert((*it)->frame_tree_node_id()); |
| 250 } |
| 251 |
| 252 StartTracing(); |
| 253 RemoveAllNonRootFrames(); |
| 254 StopTracing(); |
| 255 |
| 256 std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = |
| 257 CreateTraceAnalyzer(); |
| 258 trace_analyzer::TraceEventVector events; |
| 259 trace_analyzer::Query q = trace_analyzer::Query::EventPhaseIs( |
| 260 TRACE_EVENT_PHASE_DELETE_OBJECT); |
| 261 analyzer->FindEvents(q, &events); |
| 262 |
| 263 // The removal of all non-root nodes should be traced. |
| 264 EXPECT_EQ(6u, events.size()); |
| 265 for (int i = 0; i < 6; i++) { |
| 266 ExpectFrameTreeNodeObject(events[i]); |
| 267 int id = strtol(events[i]->id.c_str(), nullptr, 16); |
| 268 EXPECT_NE(node_ids.end(), node_ids.find(id)); |
| 269 } |
| 270 } |
| 271 |
| 272 // Change URL of the root node. Test if URL change is correctly traced. |
| 273 TEST_F(FrameTreeNodeBlameContextTest, OnURLChange) { |
| 274 main_test_rfh()->InitializeRenderFrameIfNeeded(); |
| 275 |
| 276 GURL url1("http://a.com/"); |
| 277 GURL url2("https://b.net/"); |
| 278 StartTracing(); |
| 279 root()->SetCurrentURL(url1); |
| 280 root()->SetCurrentURL(url2); |
| 281 root()->ResetForNewProcess(); |
| 282 StopTracing(); |
| 283 |
| 284 std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer = |
| 285 CreateTraceAnalyzer(); |
| 286 trace_analyzer::TraceEventVector events; |
| 287 trace_analyzer::Query q = trace_analyzer::Query::EventPhaseIs( |
| 288 TRACE_EVENT_PHASE_SNAPSHOT_OBJECT); |
| 289 analyzer->FindEvents(q, &events); |
| 290 std::sort(events.begin(), events.end(), PointerCompare); |
| 291 |
| 292 // Four snapshots are traced: one at startup and one for each URL change. |
| 293 EXPECT_EQ(4u, events.size()); |
| 294 EXPECT_EQ("", GetSnapshotURL(events[0])); |
| 295 EXPECT_EQ(url1.spec(), GetSnapshotURL(events[1])); |
| 296 EXPECT_EQ(url2.spec(), GetSnapshotURL(events[2])); |
| 297 EXPECT_EQ("", GetSnapshotURL(events[3])); |
| 298 } |
| 299 |
| 300 } // namespace content |
| OLD | NEW |