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(¶ms);
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