OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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/devtools/devtools_url_request_interceptor.h" | |
6 | |
7 #include "base/memory/ptr_util.h" | |
8 #include "base/strings/stringprintf.h" | |
9 #include "base/supports_user_data.h" | |
10 #include "content/browser/devtools/devtools_agent_host_impl.h" | |
11 #include "content/browser/devtools/devtools_url_interceptor_request_job.h" | |
12 #include "content/browser/devtools/protocol/network_handler.h" | |
13 #include "content/public/browser/browser_context.h" | |
14 #include "content/public/browser/browser_thread.h" | |
15 #include "content/public/browser/devtools_agent_host.h" | |
16 #include "content/public/browser/render_frame_host.h" | |
17 #include "content/public/browser/render_process_host.h" | |
18 #include "content/public/browser/resource_request_info.h" | |
19 #include "content/public/browser/web_contents.h" | |
20 #include "content/public/browser/web_contents_observer.h" | |
21 #include "net/http/http_request_headers.h" | |
22 #include "net/url_request/url_request.h" | |
23 | |
24 namespace content { | |
25 | |
26 namespace { | |
27 const char kDevToolsURLRequestInterceptorKeyName[] = | |
28 "DevToolsURLRequestInterceptor"; | |
29 | |
30 class DevToolsURLRequestInterceptorUserData | |
31 : public base::SupportsUserData::Data { | |
32 public: | |
33 explicit DevToolsURLRequestInterceptorUserData( | |
34 DevToolsURLRequestInterceptor* devtools_url_request_interceptor) | |
35 : devtools_url_request_interceptor_(devtools_url_request_interceptor) {} | |
36 | |
37 DevToolsURLRequestInterceptor* devtools_url_request_interceptor() const { | |
38 return devtools_url_request_interceptor_; | |
39 } | |
40 | |
41 private: | |
42 DevToolsURLRequestInterceptor* devtools_url_request_interceptor_; | |
43 | |
44 DISALLOW_COPY_AND_ASSIGN(DevToolsURLRequestInterceptorUserData); | |
45 }; | |
46 | |
47 } // namespace | |
48 | |
49 DevToolsURLRequestInterceptor::DevToolsURLRequestInterceptor( | |
50 BrowserContext* browser_context) | |
51 : browser_context_(browser_context), state_(new State()) { | |
52 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
53 browser_context_->SetUserData( | |
54 kDevToolsURLRequestInterceptorKeyName, | |
55 base::MakeUnique<DevToolsURLRequestInterceptorUserData>(this)); | |
56 } | |
57 | |
58 DevToolsURLRequestInterceptor::~DevToolsURLRequestInterceptor() { | |
59 // The BrowserContext owns us, so we don't need to unregister | |
60 // DevToolsURLRequestInterceptorUserData explicitly. | |
61 } | |
62 | |
63 net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRequest( | |
64 net::URLRequest* request, | |
65 net::NetworkDelegate* network_delegate) const { | |
66 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
67 return state()->MaybeCreateDevToolsURLInterceptorRequestJob(request, | |
68 network_delegate); | |
69 } | |
70 | |
71 net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptRedirect( | |
72 net::URLRequest* request, | |
73 net::NetworkDelegate* network_delegate, | |
74 const GURL& location) const { | |
75 return nullptr; | |
76 } | |
77 | |
78 net::URLRequestJob* DevToolsURLRequestInterceptor::MaybeInterceptResponse( | |
79 net::URLRequest* request, | |
80 net::NetworkDelegate* network_delegate) const { | |
81 return nullptr; | |
82 } | |
83 | |
84 DevToolsURLRequestInterceptor::State::State() : next_id_(0) {} | |
85 | |
86 DevToolsURLRequestInterceptor::State::~State() {} | |
87 | |
88 void DevToolsURLRequestInterceptor::State::ContinueInterceptedRequest( | |
89 std::string interception_id, | |
90 std::unique_ptr<Modifications> modifications, | |
91 std::unique_ptr<ContinueInterceptedRequestCallback> callback) { | |
92 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
93 | |
94 BrowserThread::PostTask( | |
95 BrowserThread::IO, FROM_HERE, | |
96 base::Bind(&DevToolsURLRequestInterceptor::State:: | |
97 ContinueInterceptedRequestOnIoThread, | |
98 this, interception_id, base::Passed(std::move(modifications)), | |
99 base::Passed(std::move(callback)))); | |
100 } | |
101 | |
102 void DevToolsURLRequestInterceptor::State::ContinueInterceptedRequestOnIoThread( | |
103 std::string interception_id, | |
104 std::unique_ptr<Modifications> modifications, | |
105 std::unique_ptr<ContinueInterceptedRequestCallback> callback) { | |
106 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
107 DevToolsURLInterceptorRequestJob* job = GetJob(interception_id); | |
108 if (!job) { | |
109 BrowserThread::PostTask( | |
110 BrowserThread::UI, FROM_HERE, | |
111 base::BindOnce( | |
112 &ContinueInterceptedRequestCallback::sendFailure, | |
113 base::Owned(callback.release()), | |
dgozman
2017/05/31 17:01:10
I think base::Passed(&callback) is the idiomatic w
alex clarke (OOO till 29th)
2017/06/01 12:06:40
IIRC you have to do base::Passed(std::move(callbac
| |
114 protocol::Response::InvalidParams("Invalid InterceptionId."))); | |
115 return; | |
116 } | |
117 | |
118 if (job->ContinueInterceptedRequest(std::move(modifications))) { | |
119 BrowserThread::PostTask( | |
120 BrowserThread::UI, FROM_HERE, | |
121 base::BindOnce(&ContinueInterceptedRequestCallback::sendSuccess, | |
122 base::Owned(callback.release()))); | |
dgozman
2017/05/31 17:01:10
ditto
alex clarke (OOO till 29th)
2017/06/01 12:06:40
Done.
| |
123 } else { | |
124 BrowserThread::PostTask( | |
125 BrowserThread::UI, FROM_HERE, | |
126 base::BindOnce( | |
127 &ContinueInterceptedRequestCallback::sendFailure, | |
128 base::Owned(callback.release()), | |
dgozman
2017/05/31 17:01:10
ditto
alex clarke (OOO till 29th)
2017/06/01 12:06:40
Done.
| |
129 protocol::Response::InvalidParams("Response already processed."))); | |
130 } | |
131 } | |
132 | |
133 DevToolsURLInterceptorRequestJob* DevToolsURLRequestInterceptor::State:: | |
134 MaybeCreateDevToolsURLInterceptorRequestJob( | |
135 net::URLRequest* request, | |
136 net::NetworkDelegate* network_delegate) { | |
137 // Bail out if we're not intercepting anything. | |
138 if (intercepted_render_frames_.empty()) { | |
139 DCHECK(intercepted_frame_tree_nodes_.empty()); | |
140 return nullptr; | |
141 } | |
142 const ResourceRequestInfo* resource_request_info = | |
143 ResourceRequestInfo::ForRequest(request); | |
144 if (!resource_request_info) | |
145 return nullptr; | |
146 int child_id = resource_request_info->GetChildID(); | |
147 int frame_tree_node_id = resource_request_info->GetFrameTreeNodeId(); | |
148 const InterceptedPage* intercepted_page; | |
149 if (frame_tree_node_id == -1) { | |
150 // |frame_tree_node_id| is not set for renderer side requests, fall back to | |
151 // the RenderFrameID. | |
152 int render_frame_id = resource_request_info->GetRenderFrameID(); | |
153 const auto find_it = intercepted_render_frames_.find( | |
154 std::make_pair(render_frame_id, child_id)); | |
155 if (find_it == intercepted_render_frames_.end()) | |
156 return nullptr; | |
157 intercepted_page = &find_it->second; | |
158 } else { | |
159 // |frame_tree_node_id| is set for browser side navigations, so use that | |
160 // because the RenderFrameID isn't known (neither is the ChildID). | |
161 const auto find_it = intercepted_frame_tree_nodes_.find(frame_tree_node_id); | |
162 if (find_it == intercepted_frame_tree_nodes_.end()) | |
163 return nullptr; | |
164 intercepted_page = &find_it->second; | |
165 } | |
166 | |
167 // We don't want to intercept our own sub requests. | |
168 if (sub_requests_.find(request) != sub_requests_.end()) | |
169 return nullptr; | |
170 | |
171 bool is_redirect; | |
172 std::string interception_id = GetIdForRequest(request, &is_redirect); | |
173 DevToolsURLInterceptorRequestJob* job = new DevToolsURLInterceptorRequestJob( | |
174 this, interception_id, request, network_delegate, | |
175 intercepted_page->web_contents, intercepted_page->network_handler, | |
176 is_redirect); | |
177 interception_id_to_job_map_[interception_id] = job; | |
178 return job; | |
179 } | |
180 | |
181 class DevToolsURLRequestInterceptor::State::InterceptedWebContentsObserver | |
182 : public WebContentsObserver { | |
183 public: | |
184 InterceptedWebContentsObserver( | |
185 WebContents* web_contents, | |
186 scoped_refptr<DevToolsURLRequestInterceptor::State> state, | |
187 base::WeakPtr<protocol::NetworkHandler> network_handler) | |
188 : WebContentsObserver(web_contents), | |
189 state_(state), | |
190 network_handler_(network_handler) { | |
191 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
192 } | |
193 | |
194 void RenderFrameCreated(RenderFrameHost* render_frame_host) override { | |
195 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
196 BrowserThread::PostTask( | |
197 BrowserThread::IO, FROM_HERE, | |
198 base::BindOnce(&DevToolsURLRequestInterceptor::State:: | |
199 StartInterceptingRequestsInternal, | |
200 state_, render_frame_host->GetRoutingID(), | |
201 render_frame_host->GetFrameTreeNodeId(), | |
202 render_frame_host->GetProcess()->GetID(), web_contents(), | |
203 network_handler_)); | |
204 } | |
205 | |
206 void RenderFrameDeleted(RenderFrameHost* render_frame_host) override { | |
207 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
208 BrowserThread::PostTask( | |
209 BrowserThread::IO, FROM_HERE, | |
210 base::BindOnce(&DevToolsURLRequestInterceptor::State:: | |
211 StopInterceptingRequestsInternal, | |
212 state_, render_frame_host->GetRoutingID(), | |
213 render_frame_host->GetFrameTreeNodeId(), | |
214 render_frame_host->GetProcess()->GetID())); | |
215 } | |
216 | |
217 private: | |
218 scoped_refptr<DevToolsURLRequestInterceptor::State> state_; | |
219 base::WeakPtr<protocol::NetworkHandler> network_handler_; | |
220 }; | |
221 | |
222 void DevToolsURLRequestInterceptor::State::StartInterceptingRequestsInternal( | |
223 int render_frame_id, | |
224 int frame_tree_node_id, | |
225 int process_id, | |
226 WebContents* web_contents, | |
227 base::WeakPtr<protocol::NetworkHandler> network_handler) { | |
228 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
229 intercepted_render_frames_.emplace( | |
230 std::piecewise_construct, | |
231 std::forward_as_tuple(render_frame_id, process_id), | |
232 std::forward_as_tuple(web_contents, network_handler)); | |
233 intercepted_frame_tree_nodes_.emplace( | |
234 std::piecewise_construct, std::forward_as_tuple(frame_tree_node_id), | |
235 std::forward_as_tuple(web_contents, network_handler)); | |
236 } | |
237 | |
238 void DevToolsURLRequestInterceptor::State::StopInterceptingRequestsInternal( | |
239 int render_frame_id, | |
240 int frame_tree_node_id, | |
241 int process_id) { | |
242 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
243 intercepted_render_frames_.erase(std::make_pair(render_frame_id, process_id)); | |
244 intercepted_frame_tree_nodes_.erase(frame_tree_node_id); | |
245 } | |
246 | |
247 void DevToolsURLRequestInterceptor::State::StartInterceptingRequests( | |
248 WebContents* web_contents, | |
249 base::WeakPtr<protocol::NetworkHandler> network_handler) { | |
250 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
251 // WebContents methods are UI thread only. | |
252 for (RenderFrameHost* render_frame_host : web_contents->GetAllFrames()) { | |
253 BrowserThread::PostTask( | |
254 BrowserThread::IO, FROM_HERE, | |
255 base::BindOnce(&DevToolsURLRequestInterceptor::State:: | |
256 StartInterceptingRequestsInternal, | |
257 this, render_frame_host->GetRoutingID(), | |
258 render_frame_host->GetFrameTreeNodeId(), | |
259 render_frame_host->GetProcess()->GetID(), web_contents, | |
260 network_handler)); | |
261 } | |
262 | |
263 // Listen for future updates. | |
264 observers_.emplace(web_contents, | |
265 base::MakeUnique<InterceptedWebContentsObserver>( | |
266 web_contents, this, network_handler)); | |
267 } | |
268 | |
269 void DevToolsURLRequestInterceptor::State::StopInterceptingRequests( | |
270 WebContents* web_contents) { | |
271 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
272 observers_.erase(web_contents); | |
273 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, | |
274 base::BindOnce(&DevToolsURLRequestInterceptor::State:: | |
275 StopInterceptingRequestsOnIoThread, | |
276 this, web_contents)); | |
277 } | |
278 | |
279 void DevToolsURLRequestInterceptor::State::StopInterceptingRequestsOnIoThread( | |
280 WebContents* web_contents) { | |
281 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
282 // Remove any intercepted render frames associated with |web_contents|. | |
283 base::flat_map<std::pair<int, int>, InterceptedPage> | |
284 remaining_intercepted_render_frames; | |
285 for (const auto pair : intercepted_render_frames_) { | |
286 if (pair.second.web_contents == web_contents) | |
287 continue; | |
288 remaining_intercepted_render_frames.insert(pair); | |
289 } | |
290 std::swap(remaining_intercepted_render_frames, intercepted_render_frames_); | |
291 | |
292 // Remove any intercepted frame tree nodes associated with |web_contents|. | |
293 base::flat_map<int, InterceptedPage> remaining_intercepted_frame_tree_nodes; | |
294 for (const auto pair : intercepted_frame_tree_nodes_) { | |
295 if (pair.second.web_contents == web_contents) | |
296 continue; | |
297 remaining_intercepted_frame_tree_nodes.insert(pair); | |
298 } | |
299 std::swap(remaining_intercepted_frame_tree_nodes, | |
300 intercepted_frame_tree_nodes_); | |
301 | |
302 // Tell any jobs associated with |web_contents| to stop intercepting. | |
303 for (const auto pair : interception_id_to_job_map_) { | |
304 if (pair.second->web_contents() == web_contents) | |
305 pair.second->StopIntercepting(); | |
306 } | |
307 } | |
308 | |
309 void DevToolsURLRequestInterceptor::State::RegisterSubRequest( | |
310 const net::URLRequest* sub_request) { | |
311 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
312 DCHECK(sub_requests_.find(sub_request) == sub_requests_.end()); | |
313 sub_requests_.insert(sub_request); | |
314 } | |
315 | |
316 void DevToolsURLRequestInterceptor::State::UnregisterSubRequest( | |
317 const net::URLRequest* sub_request) { | |
318 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
319 DCHECK(sub_requests_.find(sub_request) != sub_requests_.end()); | |
320 sub_requests_.erase(sub_request); | |
321 } | |
322 | |
323 void DevToolsURLRequestInterceptor::State::ExpectRequestAfterRedirect( | |
324 const net::URLRequest* request, | |
325 std::string id) { | |
326 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
327 expected_redirects_[request] = id; | |
328 } | |
329 | |
330 std::string DevToolsURLRequestInterceptor::State::GetIdForRequest( | |
331 const net::URLRequest* request, | |
332 bool* is_redirect) { | |
333 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
334 auto find_it = expected_redirects_.find(request); | |
335 if (find_it == expected_redirects_.end()) { | |
336 *is_redirect = false; | |
337 return base::StringPrintf("id-%zu", ++next_id_); | |
338 } | |
339 *is_redirect = true; | |
340 std::string id = find_it->second; | |
341 expected_redirects_.erase(find_it); | |
342 return id; | |
343 } | |
344 | |
345 DevToolsURLInterceptorRequestJob* DevToolsURLRequestInterceptor::State::GetJob( | |
346 const std::string& interception_id) const { | |
347 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
348 const auto it = interception_id_to_job_map_.find(interception_id); | |
349 if (it == interception_id_to_job_map_.end()) | |
350 return nullptr; | |
351 return it->second; | |
352 } | |
353 | |
354 void DevToolsURLRequestInterceptor::State::JobFinished( | |
355 const std::string& interception_id) { | |
356 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
357 interception_id_to_job_map_.erase(interception_id); | |
358 } | |
359 | |
360 // static | |
361 DevToolsURLRequestInterceptor* | |
362 DevToolsURLRequestInterceptor::FromBrowserContext(BrowserContext* context) { | |
363 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
364 return static_cast<DevToolsURLRequestInterceptorUserData*>( | |
365 context->GetUserData(kDevToolsURLRequestInterceptorKeyName)) | |
366 ->devtools_url_request_interceptor(); | |
367 } | |
368 | |
369 DevToolsURLRequestInterceptor::Modifications::Modifications( | |
370 base::Optional<net::Error> error_reason, | |
371 base::Optional<std::string> raw_response, | |
372 protocol::Maybe<std::string> modified_url, | |
373 protocol::Maybe<std::string> modified_method, | |
374 protocol::Maybe<std::string> modified_post_data, | |
375 protocol::Maybe<protocol::Network::Headers> modified_headers) | |
376 : error_reason(std::move(error_reason)), | |
377 raw_response(std::move(raw_response)), | |
378 modified_url(std::move(modified_url)), | |
379 modified_method(std::move(modified_method)), | |
380 modified_post_data(std::move(modified_post_data)), | |
381 modified_headers(std::move(modified_headers)) {} | |
382 | |
383 DevToolsURLRequestInterceptor::Modifications::~Modifications() {} | |
384 | |
385 DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage( | |
dgozman
2017/05/31 17:01:10
nit: could use =default for this.
alex clarke (OOO till 29th)
2017/06/01 12:06:40
Done. Too bad it still has to be out of line.
| |
386 const InterceptedPage& other) | |
387 : web_contents(other.web_contents), | |
388 network_handler(other.network_handler) {} | |
389 | |
390 DevToolsURLRequestInterceptor::State::InterceptedPage::InterceptedPage( | |
391 WebContents* web_contents, | |
392 base::WeakPtr<protocol::NetworkHandler> network_handler) | |
393 : web_contents(web_contents), network_handler(network_handler) {} | |
394 | |
395 DevToolsURLRequestInterceptor::State::InterceptedPage::~InterceptedPage() {} | |
dgozman
2017/05/31 17:01:10
ditto
alex clarke (OOO till 29th)
2017/06/01 12:06:40
Done.
| |
396 | |
397 } // namespace content | |
OLD | NEW |