// Copyright 2014 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/test/content_browser_test_utils_internal.h" #include #include #include #include #include "base/strings/stringprintf.h" #include "content/browser/frame_host/frame_tree_node.h" #include "content/browser/frame_host/navigator.h" #include "content/browser/frame_host/render_frame_proxy_host.h" #include "content/test/test_frame_navigation_observer.h" #include "url/gurl.h" namespace content { void NavigateFrameToURL(FrameTreeNode* node, const GURL& url) { TestFrameNavigationObserver observer(node); NavigationController::LoadURLParams params(url); params.transition_type = ui::PAGE_TRANSITION_LINK; params.frame_tree_node_id = node->frame_tree_node_id(); node->navigator()->GetController()->LoadURLWithParams(params); observer.Wait(); } FrameTreeVisualizer::FrameTreeVisualizer() { } FrameTreeVisualizer::~FrameTreeVisualizer() { } std::string FrameTreeVisualizer::DepictFrameTree(FrameTreeNode* root) { // Tracks the sites actually used in this depiction. std::map legend; // Traversal 1: Assign names to current frames. This ensures that the first // call to the pretty-printer will result in a naming of the site instances // that feels natural and stable. std::stack to_explore; for (to_explore.push(root); !to_explore.empty();) { FrameTreeNode* node = to_explore.top(); to_explore.pop(); for (size_t i = node->child_count(); i-- != 0;) { to_explore.push(node->child_at(i)); } RenderFrameHost* current = node->render_manager()->current_frame_host(); legend[GetName(current->GetSiteInstance())] = current->GetSiteInstance(); } // Traversal 2: Assign names to the pending/speculative frames. For stability // of assigned names it's important to do this before trying to name the // proxies, which have a less well defined order. for (to_explore.push(root); !to_explore.empty();) { FrameTreeNode* node = to_explore.top(); to_explore.pop(); for (size_t i = node->child_count(); i-- != 0;) { to_explore.push(node->child_at(i)); } RenderFrameHost* pending = node->render_manager()->pending_frame_host(); RenderFrameHost* spec = node->render_manager()->speculative_render_frame_host_.get(); if (pending) legend[GetName(pending->GetSiteInstance())] = pending->GetSiteInstance(); if (spec) legend[GetName(spec->GetSiteInstance())] = spec->GetSiteInstance(); } // Traversal 3: Assign names to the proxies and add them to |legend| too. // Typically, only openers should have their names assigned this way. for (to_explore.push(root); !to_explore.empty();) { FrameTreeNode* node = to_explore.top(); to_explore.pop(); for (size_t i = node->child_count(); i-- != 0;) { to_explore.push(node->child_at(i)); } // Sort the proxies by SiteInstance ID to avoid hash_map ordering. std::map sorted_proxy_hosts; for (auto& proxy_pair : node->render_manager()->proxy_hosts_) { sorted_proxy_hosts.insert(proxy_pair); } for (auto& proxy_pair : sorted_proxy_hosts) { RenderFrameProxyHost* proxy = proxy_pair.second; legend[GetName(proxy->GetSiteInstance())] = proxy->GetSiteInstance(); } } // Traversal 4: Now that all names are assigned, make a big loop to pretty- // print the tree. Each iteration produces exactly one line of format. std::string result; for (to_explore.push(root); !to_explore.empty();) { FrameTreeNode* node = to_explore.top(); to_explore.pop(); for (size_t i = node->child_count(); i-- != 0;) { to_explore.push(node->child_at(i)); } // Draw the feeler line tree graphics by walking up to the root. A feeler // line is needed for each ancestor that is the last child of its parent. // This creates the ASCII art that looks like: // Foo // |--Foo // |--Foo // | |--Foo // | +--Foo // | +--Foo // +--Foo // +--Foo // // TODO(nick): Make this more elegant. std::string line; if (node != root) { if (node->parent()->child_at(node->parent()->child_count() - 1) != node) line = " |--"; else line = " +--"; for (FrameTreeNode* up = node->parent(); up != root; up = up->parent()) { if (up->parent()->child_at(up->parent()->child_count() - 1) != up) line = " | " + line; else line = " " + line; } } // Prefix one extra space of padding for two reasons. First, this helps the // diagram aligns nicely with the legend. Second, this makes it easier to // read the diffs that gtest spits out on EXPECT_EQ failure. line = " " + line; // Summarize the FrameTreeNode's state. Always show the site of the current // RenderFrameHost, and show any exceptional state of the node, like a // pending or speculative RenderFrameHost. RenderFrameHost* current = node->render_manager()->current_frame_host(); RenderFrameHost* pending = node->render_manager()->pending_frame_host(); RenderFrameHost* spec = node->render_manager()->speculative_render_frame_host_.get(); base::StringAppendF(&line, "Site %s", GetName(current->GetSiteInstance()).c_str()); if (pending) { base::StringAppendF(&line, " (%s pending)", GetName(pending->GetSiteInstance()).c_str()); } if (spec) { base::StringAppendF(&line, " (%s speculative)", GetName(spec->GetSiteInstance()).c_str()); } // Show the SiteInstances of the RenderFrameProxyHosts of this node. if (!node->render_manager()->proxy_hosts_.empty()) { // Show a dashed line of variable length before the proxy list. Always at // least two dashes. line.append(" --"); // To make proxy lists align vertically for the first three tree levels, // pad with dashes up to a first tab stop at column 19 (which works out to // text editor column 28 in the typical diagram fed to EXPECT_EQ as a // string literal). Lining the lists up vertically makes differences in // the proxy sets easier to spot visually. We choose not to use the // *actual* tree height here, because that would make the diagram's // appearance less stable as the tree's shape evolves. while (line.length() < 20) { line.append("-"); } line.append(" proxies for"); // Sort these alphabetically, to avoid hash_map ordering dependency. std::vector sorted_proxy_hosts; for (auto& proxy_pair : node->render_manager()->proxy_hosts_) { sorted_proxy_hosts.push_back( GetName(proxy_pair.second->GetSiteInstance())); } std::sort(sorted_proxy_hosts.begin(), sorted_proxy_hosts.end()); for (std::string& proxy_name : sorted_proxy_hosts) { base::StringAppendF(&line, " %s", proxy_name.c_str()); } } if (node != root) result.append("\n"); result.append(line); } // Finally, show a legend with details of the site instances. const char* prefix = "Where "; for (auto& legend_entry : legend) { SiteInstanceImpl* site_instance = static_cast(legend_entry.second); base::StringAppendF(&result, "\n%s%s = %s", prefix, legend_entry.first.c_str(), site_instance->GetSiteURL().spec().c_str()); // Highlight some exceptionable conditions. if (site_instance->active_frame_count() == 0) result.append(" (active_frame_count == 0)"); if (!site_instance->GetProcess()->HasConnection()) result.append(" (no process)"); prefix = " "; } return result; } std::string FrameTreeVisualizer::GetName(SiteInstance* site_instance) { // Indices into the vector correspond to letters of the alphabet. size_t index = std::find(seen_site_instance_ids_.begin(), seen_site_instance_ids_.end(), site_instance->GetId()) - seen_site_instance_ids_.begin(); if (index == seen_site_instance_ids_.size()) seen_site_instance_ids_.push_back(site_instance->GetId()); // Whosoever writes a test using >=26 site instances shall be a lucky ducky. if (index < 25) return base::StringPrintf("%c", 'A' + static_cast(index)); else return base::StringPrintf("Z%d", static_cast(index - 25)); } } // namespace content