1 // Copyright 2014 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 "chrome/browser/devtools/chrome_devtools_manager_delegate.h"
6 
7 #include <utility>
8 
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/stl_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "build/build_config.h"
14 #include "chrome/browser/devtools/chrome_devtools_session.h"
15 #include "chrome/browser/devtools/device/android_device_manager.h"
16 #include "chrome/browser/devtools/device/tcp_device_provider.h"
17 #include "chrome/browser/devtools/devtools_browser_context_manager.h"
18 #include "chrome/browser/devtools/devtools_window.h"
19 #include "chrome/browser/devtools/protocol/target_handler.h"
20 #include "chrome/browser/extensions/extension_tab_util.h"
21 #include "chrome/browser/policy/developer_tools_policy_handler.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/profiles/profile_manager.h"
24 #include "chrome/browser/ui/browser_navigator.h"
25 #include "chrome/browser/ui/browser_navigator_params.h"
26 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/grit/browser_resources.h"
29 #include "components/guest_view/browser/guest_view_base.h"
30 #include "components/keep_alive_registry/keep_alive_types.h"
31 #include "components/keep_alive_registry/scoped_keep_alive.h"
32 #include "content/public/browser/devtools_agent_host.h"
33 #include "content/public/browser/devtools_agent_host_client_channel.h"
34 #include "content/public/browser/render_frame_host.h"
35 #include "content/public/browser/render_process_host.h"
36 #include "content/public/browser/web_contents.h"
37 #include "content/public/common/content_switches.h"
38 #include "extensions/browser/extension_host.h"
39 #include "extensions/browser/extension_registry.h"
40 #include "extensions/browser/process_manager.h"
41 #include "extensions/common/manifest.h"
42 #include "ui/base/resource/resource_bundle.h"
43 
44 #if defined(OS_CHROMEOS)
45 #include "base/command_line.h"
46 #include "chromeos/constants/chromeos_switches.h"
47 #endif
48 
49 using content::DevToolsAgentHost;
50 
51 const char ChromeDevToolsManagerDelegate::kTypeApp[] = "app";
52 const char ChromeDevToolsManagerDelegate::kTypeBackgroundPage[] =
53     "background_page";
54 
55 namespace {
56 
GetExtensionInfo(content::WebContents * wc,std::string * name,std::string * type)57 bool GetExtensionInfo(content::WebContents* wc,
58                       std::string* name,
59                       std::string* type) {
60   Profile* profile = Profile::FromBrowserContext(wc->GetBrowserContext());
61   if (!profile)
62     return false;
63   const extensions::Extension* extension =
64       extensions::ProcessManager::Get(profile)->GetExtensionForWebContents(wc);
65   if (!extension)
66     return false;
67   extensions::ExtensionHost* extension_host =
68       extensions::ProcessManager::Get(profile)->GetBackgroundHostForExtension(
69           extension->id());
70   if (extension_host && extension_host->host_contents() == wc) {
71     *name = extension->name();
72     *type = ChromeDevToolsManagerDelegate::kTypeBackgroundPage;
73     return true;
74   }
75   if (extension->is_hosted_app() || extension->is_legacy_packaged_app() ||
76       extension->is_platform_app()) {
77     *name = extension->name();
78     *type = ChromeDevToolsManagerDelegate::kTypeApp;
79     return true;
80   }
81   return false;
82 }
83 
84 ChromeDevToolsManagerDelegate* g_instance;
85 
86 }  // namespace
87 
88 // static
GetInstance()89 ChromeDevToolsManagerDelegate* ChromeDevToolsManagerDelegate::GetInstance() {
90   return g_instance;
91 }
92 
ChromeDevToolsManagerDelegate()93 ChromeDevToolsManagerDelegate::ChromeDevToolsManagerDelegate() {
94   DCHECK(!g_instance);
95   g_instance = this;
96 
97   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
98   if (command_line->HasSwitch(switches::kNoStartupWindow) &&
99       (command_line->HasSwitch(switches::kRemoteDebuggingPipe) ||
100        command_line->HasSwitch(switches::kRemoteDebuggingPort))) {
101     // If running without a startup window with remote debugging,
102     // we are controlled entirely by the automation process.
103     // Keep the application running until explicit close through DevTools
104     // protocol.
105     keep_alive_.reset(new ScopedKeepAlive(KeepAliveOrigin::REMOTE_DEBUGGING,
106                                           KeepAliveRestartOption::DISABLED));
107   }
108 }
109 
~ChromeDevToolsManagerDelegate()110 ChromeDevToolsManagerDelegate::~ChromeDevToolsManagerDelegate() {
111   DCHECK(g_instance == this);
112   g_instance = nullptr;
113 }
114 
Inspect(content::DevToolsAgentHost * agent_host)115 void ChromeDevToolsManagerDelegate::Inspect(
116     content::DevToolsAgentHost* agent_host) {
117   DevToolsWindow::OpenDevToolsWindow(agent_host, nullptr);
118 }
119 
HandleCommand(content::DevToolsAgentHostClientChannel * channel,base::span<const uint8_t> message,NotHandledCallback callback)120 void ChromeDevToolsManagerDelegate::HandleCommand(
121     content::DevToolsAgentHostClientChannel* channel,
122     base::span<const uint8_t> message,
123     NotHandledCallback callback) {
124   auto it = sessions_.find(channel);
125   if (it == sessions_.end()) {
126     std::move(callback).Run(message);
127     // This should not happen, but happens. NOTREACHED tries to get
128     // a repro in some test.
129     NOTREACHED();
130     return;
131   }
132   it->second->HandleCommand(message, std::move(callback));
133 }
134 
GetTargetType(content::WebContents * web_contents)135 std::string ChromeDevToolsManagerDelegate::GetTargetType(
136     content::WebContents* web_contents) {
137   if (base::Contains(AllTabContentses(), web_contents))
138     return DevToolsAgentHost::kTypePage;
139 
140   std::string extension_name;
141   std::string extension_type;
142   if (!GetExtensionInfo(web_contents, &extension_name, &extension_type))
143     return DevToolsAgentHost::kTypeOther;
144   return extension_type;
145 }
146 
GetTargetTitle(content::WebContents * web_contents)147 std::string ChromeDevToolsManagerDelegate::GetTargetTitle(
148     content::WebContents* web_contents) {
149   std::string extension_name;
150   std::string extension_type;
151   if (!GetExtensionInfo(web_contents, &extension_name, &extension_type))
152     return std::string();
153   return extension_name;
154 }
155 
AllowInspectingRenderFrameHost(content::RenderFrameHost * rfh)156 bool ChromeDevToolsManagerDelegate::AllowInspectingRenderFrameHost(
157     content::RenderFrameHost* rfh) {
158   Profile* profile =
159       Profile::FromBrowserContext(rfh->GetProcess()->GetBrowserContext());
160   return AllowInspection(profile, extensions::ProcessManager::Get(profile)
161                                       ->GetExtensionForRenderFrameHost(rfh));
162 }
163 
164 // static
AllowInspection(Profile * profile,content::WebContents * web_contents)165 bool ChromeDevToolsManagerDelegate::AllowInspection(
166     Profile* profile,
167     content::WebContents* web_contents) {
168   const extensions::Extension* extension = nullptr;
169   if (web_contents) {
170     extension =
171         extensions::ProcessManager::Get(
172             Profile::FromBrowserContext(web_contents->GetBrowserContext()))
173             ->GetExtensionForWebContents(web_contents);
174   }
175   return AllowInspection(profile, extension);
176 }
177 
178 // static
AllowInspection(Profile * profile,const extensions::Extension * extension)179 bool ChromeDevToolsManagerDelegate::AllowInspection(
180     Profile* profile,
181     const extensions::Extension* extension) {
182 #if defined(OS_CHROMEOS)
183   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
184   if (command_line->HasSwitch(chromeos::switches::kForceDevToolsAvailable))
185     return true;
186 #endif
187 
188   using Availability = policy::DeveloperToolsPolicyHandler::Availability;
189   Availability availability =
190       policy::DeveloperToolsPolicyHandler::GetDevToolsAvailability(
191           profile->GetPrefs());
192 #if defined(OS_CHROMEOS)
193   // Do not create DevTools if it's disabled for primary profile.
194   Profile* primary_profile = ProfileManager::GetPrimaryUserProfile();
195   if (primary_profile &&
196       policy::DeveloperToolsPolicyHandler::IsDevToolsAvailabilitySetByPolicy(
197           primary_profile->GetPrefs())) {
198     availability =
199         policy::DeveloperToolsPolicyHandler::GetMostRestrictiveAvailability(
200             availability,
201             policy::DeveloperToolsPolicyHandler::GetDevToolsAvailability(
202                 primary_profile->GetPrefs()));
203   }
204 #endif
205 
206   switch (availability) {
207     case Availability::kDisallowed:
208       return false;
209     case Availability::kAllowed:
210       return true;
211     case Availability::kDisallowedForForceInstalledExtensions:
212       return !extension ||
213              !extensions::Manifest::IsPolicyLocation(extension->location());
214     default:
215       NOTREACHED() << "Unknown developer tools policy";
216       return true;
217   }
218 }
219 
ClientAttached(content::DevToolsAgentHostClientChannel * channel)220 void ChromeDevToolsManagerDelegate::ClientAttached(
221     content::DevToolsAgentHostClientChannel* channel) {
222   DCHECK(sessions_.find(channel) == sessions_.end());
223   sessions_.emplace(channel, std::make_unique<ChromeDevToolsSession>(channel));
224 }
225 
ClientDetached(content::DevToolsAgentHostClientChannel * channel)226 void ChromeDevToolsManagerDelegate::ClientDetached(
227     content::DevToolsAgentHostClientChannel* channel) {
228   sessions_.erase(channel);
229 }
230 
231 scoped_refptr<DevToolsAgentHost>
CreateNewTarget(const GURL & url)232 ChromeDevToolsManagerDelegate::CreateNewTarget(const GURL& url) {
233   NavigateParams params(ProfileManager::GetLastUsedProfile(), url,
234                         ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
235   params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
236   Navigate(&params);
237   if (!params.navigated_or_inserted_contents)
238     return nullptr;
239   return DevToolsAgentHost::GetOrCreateFor(
240       params.navigated_or_inserted_contents);
241 }
242 
GetDiscoveryPageHTML()243 std::string ChromeDevToolsManagerDelegate::GetDiscoveryPageHTML() {
244   return ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
245       IDR_DEVTOOLS_DISCOVERY_PAGE_HTML);
246 }
247 
248 std::vector<content::BrowserContext*>
GetBrowserContexts()249 ChromeDevToolsManagerDelegate::GetBrowserContexts() {
250   return DevToolsBrowserContextManager::GetInstance().GetBrowserContexts();
251 }
252 
253 content::BrowserContext*
GetDefaultBrowserContext()254 ChromeDevToolsManagerDelegate::GetDefaultBrowserContext() {
255   return DevToolsBrowserContextManager::GetInstance()
256       .GetDefaultBrowserContext();
257 }
258 
CreateBrowserContext()259 content::BrowserContext* ChromeDevToolsManagerDelegate::CreateBrowserContext() {
260   return DevToolsBrowserContextManager::GetInstance().CreateBrowserContext();
261 }
262 
DisposeBrowserContext(content::BrowserContext * context,DisposeCallback callback)263 void ChromeDevToolsManagerDelegate::DisposeBrowserContext(
264     content::BrowserContext* context,
265     DisposeCallback callback) {
266   DevToolsBrowserContextManager::GetInstance().DisposeBrowserContext(
267       context, std::move(callback));
268 }
269 
HasBundledFrontendResources()270 bool ChromeDevToolsManagerDelegate::HasBundledFrontendResources() {
271   return true;
272 }
273 
DevicesAvailable(const DevToolsDeviceDiscovery::CompleteDevices & devices)274 void ChromeDevToolsManagerDelegate::DevicesAvailable(
275     const DevToolsDeviceDiscovery::CompleteDevices& devices) {
276   DevToolsAgentHost::List remote_targets;
277   for (const auto& complete : devices) {
278     for (const auto& browser : complete.second->browsers()) {
279       for (const auto& page : browser->pages())
280         remote_targets.push_back(page->CreateTarget());
281     }
282   }
283   remote_agent_hosts_.swap(remote_targets);
284 }
285 
UpdateDeviceDiscovery()286 void ChromeDevToolsManagerDelegate::UpdateDeviceDiscovery() {
287   RemoteLocations remote_locations;
288   for (const auto& it : sessions_) {
289     TargetHandler* target_handler = it.second->target_handler();
290     if (!target_handler)
291       continue;
292     RemoteLocations& locations = target_handler->remote_locations();
293     remote_locations.insert(locations.begin(), locations.end());
294   }
295 
296   bool equals = remote_locations.size() == remote_locations_.size();
297   if (equals) {
298     auto it1 = remote_locations.begin();
299     auto it2 = remote_locations_.begin();
300     while (it1 != remote_locations.end()) {
301       DCHECK(it2 != remote_locations_.end());
302       if (!(*it1).Equals(*it2))
303         equals = false;
304       ++it1;
305       ++it2;
306     }
307     DCHECK(it2 == remote_locations_.end());
308   }
309 
310   if (equals)
311     return;
312 
313   if (remote_locations.empty()) {
314     device_discovery_.reset();
315     remote_agent_hosts_.clear();
316   } else {
317     if (!device_manager_)
318       device_manager_ = AndroidDeviceManager::Create();
319 
320     AndroidDeviceManager::DeviceProviders providers;
321     providers.push_back(new TCPDeviceProvider(remote_locations));
322     device_manager_->SetDeviceProviders(providers);
323 
324     device_discovery_.reset(new DevToolsDeviceDiscovery(device_manager_.get(),
325         base::Bind(&ChromeDevToolsManagerDelegate::DevicesAvailable,
326                    base::Unretained(this))));
327   }
328   remote_locations_.swap(remote_locations);
329 }
330 
ResetAndroidDeviceManagerForTesting()331 void ChromeDevToolsManagerDelegate::ResetAndroidDeviceManagerForTesting() {
332   device_manager_.reset();
333 
334   // We also need |device_discovery_| to go away because there may be a pending
335   // task using a raw pointer to the DeviceManager we just deleted.
336   device_discovery_.reset();
337 }
338 
BrowserCloseRequested()339 void ChromeDevToolsManagerDelegate::BrowserCloseRequested() {
340   // Do not keep the application running anymore, we got an explicit request
341   // to close.
342   keep_alive_.reset();
343 }
344