Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(261)

Side by Side Diff: mojo/shell/dynamic_application_loader.cc

Issue 694303002: Allow local file to run though content handler. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Small refactor Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « mojo/common/data_pipe_utils_unittest.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "mojo/shell/dynamic_application_loader.h" 5 #include "mojo/shell/dynamic_application_loader.h"
6 6
7 #include "base/bind.h" 7 #include "base/bind.h"
8 #include "base/command_line.h" 8 #include "base/command_line.h"
9 #include "base/files/file_path.h" 9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h" 10 #include "base/files/file_util.h"
11 #include "base/format_macros.h"
12 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h" 13 #include "base/memory/scoped_ptr.h"
12 #include "base/memory/weak_ptr.h" 14 #include "base/memory/weak_ptr.h"
13 #include "base/message_loop/message_loop.h" 15 #include "base/message_loop/message_loop.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h" 18 #include "base/strings/utf_string_conversions.h"
15 #include "mojo/common/common_type_converters.h" 19 #include "mojo/common/common_type_converters.h"
16 #include "mojo/common/data_pipe_utils.h" 20 #include "mojo/common/data_pipe_utils.h"
21 #include "mojo/public/cpp/system/data_pipe.h"
17 #include "mojo/services/public/interfaces/network/url_loader.mojom.h" 22 #include "mojo/services/public/interfaces/network/url_loader.mojom.h"
18 #include "mojo/shell/context.h" 23 #include "mojo/shell/context.h"
19 #include "mojo/shell/data_pipe_peek.h" 24 #include "mojo/shell/data_pipe_peek.h"
20 #include "mojo/shell/filename_util.h" 25 #include "mojo/shell/filename_util.h"
21 #include "mojo/shell/switches.h" 26 #include "mojo/shell/switches.h"
22 #include "url/url_util.h" 27 #include "url/url_util.h"
23 28
24 namespace mojo { 29 namespace mojo {
25 namespace shell { 30 namespace shell {
26 31
32 namespace {
33
34 void IgnoreResult(bool result) {
35 }
36
37 class LoaderResponse {
38 public:
39 virtual ~LoaderResponse() {}
40
41 virtual URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
42 uint32_t skip) = 0;
43
44 virtual void AsPath(
45 base::TaskRunner* task_runner,
46 base::Callback<void(const base::FilePath&, bool)> callback) = 0;
47
48 bool PeekContentHandler(std::string* mojo_shebang,
49 GURL* mojo_content_handler_url) {
50 std::string shebang;
51 if (HasMojoMagic() && PeekFirstLine(&shebang)) {
52 std::string url_string;
53 base::TrimWhitespace(
54 shebang.substr(2, std::string::npos), base::TRIM_ALL, &url_string);
hansmuller 2014/11/04 16:36:57 Is this necessary? The URL parser is required to t
qsr 2014/11/04 17:30:57 Hum. Did that to try to correct another issue I ha
55 GURL url(url_string);
56 if (url.is_valid()) {
57 *mojo_shebang = shebang;
58 *mojo_content_handler_url = url;
59 return true;
60 }
61 }
62 return false;
63 }
64
65 virtual std::string MimeType() = 0;
66
67 protected:
68 const char* kMojoMagic = "#!mojo:";
69 const size_t kMaxShebangLength = 2048;
70
71 virtual bool HasMojoMagic() = 0;
72 virtual bool PeekFirstLine(std::string* line) = 0;
73 };
74
75 class NetworkLoaderResponse : public LoaderResponse {
76 public:
77 explicit NetworkLoaderResponse(URLResponsePtr response)
78 : response_(response.Pass()) {}
79
80 ~NetworkLoaderResponse() override {
81 if (!path_.empty())
82 base::DeleteFile(path_, false);
83 }
84
85 URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
86 uint32_t skip) override {
87 if (skip) {
88 DCHECK(ReadDataRaw(response_->body.get(),
89 nullptr,
90 &skip,
91 MOJO_READ_DATA_FLAG_ALL_OR_NONE |
92 MOJO_READ_DATA_FLAG_DISCARD) == MOJO_RESULT_OK);
93 }
94 return response_.Pass();
95 }
96
97 void AsPath(
98 base::TaskRunner* task_runner,
99 base::Callback<void(const base::FilePath&, bool)> callback) override {
100 if (!path_.empty() || !response_) {
101 callback.Run(path_, base::PathExists(path_));
102 return;
103 }
104 base::CreateTemporaryFile(&path_);
105 common::CopyToFile(response_->body.Pass(),
106 path_,
107 task_runner,
108 base::Bind(callback, path_));
109 }
110
111 std::string MimeType() override {
112 DCHECK(response_);
113 return response_->mime_type;
114 }
115
116 private:
117 // TODO(hansmuller): Revisit this when a real peek operation is available.
118 const MojoDeadline kPeekTimeout = MOJO_DEADLINE_INDEFINITE;
119
120 bool HasMojoMagic() override {
121 std::string magic;
122 return BlockingPeekNBytes(response_->body.get(),
123 &magic,
124 strlen(kMojoMagic),
125 kPeekTimeout) &&
126 magic == kMojoMagic;
127 }
128 bool PeekFirstLine(std::string* line) override {
129 return BlockingPeekLine(
130 response_->body.get(), line, kMaxShebangLength, kPeekTimeout);
131 }
132
133 URLResponsePtr response_;
134 base::FilePath path_;
135 };
136
137 class LocalLoaderResponse : public LoaderResponse {
138 public:
139 LocalLoaderResponse(const GURL& url, base::FilePath path)
140 : url_(url), path_(path) {}
141
142 URLResponsePtr AsURLResponse(base::TaskRunner* task_runner,
143 uint32_t skip) override {
144 URLResponsePtr response(URLResponse::New());
145 response->url = String::From(url_);
146 DataPipe data_pipe;
147 response->body = data_pipe.consumer_handle.Pass();
148 int64 file_size;
149 if (base::GetFileSize(path_, &file_size)) {
150 response->headers = Array<String>(1);
151 response->headers[0] =
152 base::StringPrintf("Content-Length: %" PRId64, file_size);
153 }
154 common::CopyFromFile(path_,
155 data_pipe.producer_handle.Pass(),
156 skip,
157 task_runner,
158 base::Bind(&IgnoreResult));
159 return response.Pass();
160 }
161
162 void AsPath(
163 base::TaskRunner* task_runner,
164 base::Callback<void(const base::FilePath&, bool)> callback) override {
165 callback.Run(path_, base::PathExists(path_));
166 }
167
168 std::string MimeType() override { return ""; }
169
170 private:
171 GURL url_;
172 base::FilePath path_;
173
174 bool HasMojoMagic() override {
175 std::string magic;
176 ReadFileToString(path_, &magic, strlen(kMojoMagic));
177 return magic == kMojoMagic;
178 }
179
180 bool PeekFirstLine(std::string* line) override {
181 std::string start_of_file;
182 ReadFileToString(path_, &start_of_file, kMaxShebangLength);
183 size_t return_position = start_of_file.find('\n');
184 if (return_position == std::string::npos)
185 return false;
186 *line = start_of_file.substr(0, return_position + 1);
187 return true;
188 }
189 };
190
191 } // namespace
192
27 // Encapsulates loading and running one individual application. 193 // Encapsulates loading and running one individual application.
28 // 194 //
29 // Loaders are owned by DynamicApplicationLoader. DynamicApplicationLoader must 195 // Loaders are owned by DynamicApplicationLoader. DynamicApplicationLoader must
30 // ensure that all the parameters passed to Loader subclasses stay valid through 196 // ensure that all the parameters passed to Loader subclasses stay valid through
31 // Loader's lifetime. 197 // Loader's lifetime.
32 // 198 //
33 // Async operations are done with WeakPtr to protect against 199 // Async operations are done with WeakPtr to protect against
34 // DynamicApplicationLoader going away (and taking all the Loaders with it) 200 // DynamicApplicationLoader going away (and taking all the Loaders with it)
35 // while the async operation is outstanding. 201 // while the async operation is outstanding.
36 class DynamicApplicationLoader::Loader { 202 class DynamicApplicationLoader::Loader {
37 public: 203 public:
38 Loader(Context* context, 204 Loader(Context* context,
205 MimeTypeToURLMap* mime_type_to_url,
39 DynamicServiceRunnerFactory* runner_factory, 206 DynamicServiceRunnerFactory* runner_factory,
40 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks, 207 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
41 const LoaderCompleteCallback& loader_complete_callback) 208 const LoaderCompleteCallback& loader_complete_callback)
42 : load_callbacks_(load_callbacks), 209 : load_callbacks_(load_callbacks),
43 loader_complete_callback_(loader_complete_callback), 210 loader_complete_callback_(loader_complete_callback),
44 context_(context), 211 context_(context),
212 mime_type_to_url_(mime_type_to_url),
45 runner_factory_(runner_factory), 213 runner_factory_(runner_factory),
46 weak_ptr_factory_(this) {} 214 weak_ptr_factory_(this) {}
47 215
48 virtual ~Loader() {} 216 virtual ~Loader() {}
49 217
50 protected: 218 protected:
51 void RunLibrary(const base::FilePath& path, bool path_exists) { 219 void RunLibrary(const base::FilePath& path, bool path_exists) {
52 ScopedMessagePipeHandle shell_handle = 220 ScopedMessagePipeHandle shell_handle =
53 load_callbacks_->RegisterApplication(); 221 load_callbacks_->RegisterApplication();
54 if (!shell_handle.is_valid()) { 222 if (!shell_handle.is_valid()) {
(...skipping 10 matching lines...) Expand all
65 233
66 runner_ = runner_factory_->Create(context_); 234 runner_ = runner_factory_->Create(context_);
67 runner_->Start( 235 runner_->Start(
68 path, 236 path,
69 shell_handle.Pass(), 237 shell_handle.Pass(),
70 base::Bind(&Loader::LoaderComplete, weak_ptr_factory_.GetWeakPtr())); 238 base::Bind(&Loader::LoaderComplete, weak_ptr_factory_.GetWeakPtr()));
71 } 239 }
72 240
73 void LoaderComplete() { loader_complete_callback_.Run(this); } 241 void LoaderComplete() { loader_complete_callback_.Run(this); }
74 242
243 void OnResponse(scoped_ptr<LoaderResponse> response) {
244 // If the response begins with a #!mojo:<content-handler-url>, use it.
245 {
246 GURL url;
247 std::string shebang;
248 if (response->PeekContentHandler(&shebang, &url)) {
249 load_callbacks_->LoadWithContentHandler(
250 url,
251 response->AsURLResponse(context_->task_runners()->blocking_pool(),
252 shebang.size()));
253 }
254 }
255
256 MimeTypeToURLMap::iterator iter =
257 mime_type_to_url_->find(response->MimeType());
258 if (iter != mime_type_to_url_->end()) {
259 load_callbacks_->LoadWithContentHandler(
260 iter->second,
261 response->AsURLResponse(context_->task_runners()->blocking_pool(),
262 0));
263 return;
264 }
265
266 // TODO(aa): Santify check that the thing we got looks vaguely like a mojo
hansmuller 2014/11/04 16:36:57 Santify => Sanity
qsr 2014/11/04 17:30:57 Done.
267 // application. That could either mean looking for the platform-specific dll
268 // header, or looking for some specific mojo signature prepended to the
269 // library.
270
271 response->AsPath(
272 context_->task_runners()->blocking_pool(),
273 base::Bind(&Loader::RunLibrary, weak_ptr_factory_.GetWeakPtr()));
274 }
275
75 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks_; 276 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks_;
76 LoaderCompleteCallback loader_complete_callback_; 277 LoaderCompleteCallback loader_complete_callback_;
77 Context* context_; 278 Context* context_;
279 MimeTypeToURLMap* mime_type_to_url_;
78 280
79 private: 281 private:
80 DynamicServiceRunnerFactory* runner_factory_; 282 DynamicServiceRunnerFactory* runner_factory_;
81 scoped_ptr<DynamicServiceRunner> runner_; 283 scoped_ptr<DynamicServiceRunner> runner_;
82 base::WeakPtrFactory<Loader> weak_ptr_factory_; 284 base::WeakPtrFactory<Loader> weak_ptr_factory_;
83 }; 285 };
84 286
85 // A loader for local files. 287 // A loader for local files.
86 class DynamicApplicationLoader::LocalLoader : public Loader { 288 class DynamicApplicationLoader::LocalLoader : public Loader {
87 public: 289 public:
88 LocalLoader(const GURL& url, 290 LocalLoader(const GURL& url,
89 Context* context, 291 Context* context,
292 MimeTypeToURLMap* mime_type_to_url,
90 DynamicServiceRunnerFactory* runner_factory, 293 DynamicServiceRunnerFactory* runner_factory,
91 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks, 294 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
92 const LoaderCompleteCallback& loader_complete_callback) 295 const LoaderCompleteCallback& loader_complete_callback)
93 : Loader(context, 296 : Loader(context,
297 mime_type_to_url,
94 runner_factory, 298 runner_factory,
95 load_callbacks, 299 load_callbacks,
96 loader_complete_callback), 300 loader_complete_callback) {
97 weak_ptr_factory_(this) {
98 DCHECK(url.SchemeIsFile()); 301 DCHECK(url.SchemeIsFile());
99 url::RawCanonOutputW<1024> output; 302 url::RawCanonOutputW<1024> output;
100 url::DecodeURLEscapeSequences( 303 url::DecodeURLEscapeSequences(
101 url.path().data(), static_cast<int>(url.path().length()), &output); 304 url.path().data(), static_cast<int>(url.path().length()), &output);
102 base::string16 decoded_path = 305 base::string16 decoded_path =
103 base::string16(output.data(), output.length()); 306 base::string16(output.data(), output.length());
104 #if defined(OS_WIN) 307 #if defined(OS_WIN)
105 base::FilePath path(decoded_path); 308 base::FilePath path(decoded_path);
106 #else 309 #else
107 base::FilePath path(base::UTF16ToUTF8(decoded_path)); 310 base::FilePath path(base::UTF16ToUTF8(decoded_path));
108 #endif 311 #endif
109 312
110 // Async for consistency with network case. 313 OnResponse(make_scoped_ptr(new LocalLoaderResponse(url, path)));
111 base::MessageLoop::current()->PostTask(
112 FROM_HERE,
113 base::Bind(&LocalLoader::RunLibrary,
114 weak_ptr_factory_.GetWeakPtr(),
115 path,
116 base::PathExists(path)));
117 } 314 }
118 315
119 ~LocalLoader() override {} 316 ~LocalLoader() override {}
120
121 private:
122 base::WeakPtrFactory<LocalLoader> weak_ptr_factory_;
123 }; 317 };
124 318
125 // A loader for network files. 319 // A loader for network files.
126 class DynamicApplicationLoader::NetworkLoader : public Loader { 320 class DynamicApplicationLoader::NetworkLoader : public Loader {
127 public: 321 public:
128 NetworkLoader(const GURL& url, 322 NetworkLoader(const GURL& url,
129 MimeTypeToURLMap* mime_type_to_url, 323 MimeTypeToURLMap* mime_type_to_url,
130 Context* context, 324 Context* context,
131 DynamicServiceRunnerFactory* runner_factory, 325 DynamicServiceRunnerFactory* runner_factory,
132 NetworkService* network_service, 326 NetworkService* network_service,
133 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks, 327 scoped_refptr<ApplicationLoader::LoadCallbacks> load_callbacks,
134 const LoaderCompleteCallback& loader_complete_callback) 328 const LoaderCompleteCallback& loader_complete_callback)
135 : Loader(context, 329 : Loader(context,
330 mime_type_to_url,
136 runner_factory, 331 runner_factory,
137 load_callbacks, 332 load_callbacks,
138 loader_complete_callback), 333 loader_complete_callback),
139 mime_type_to_url_(mime_type_to_url),
140 weak_ptr_factory_(this) { 334 weak_ptr_factory_(this) {
141 URLRequestPtr request(URLRequest::New()); 335 URLRequestPtr request(URLRequest::New());
142 request->url = String::From(url); 336 request->url = String::From(url);
143 request->auto_follow_redirects = true; 337 request->auto_follow_redirects = true;
144 338
145 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 339 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
146 switches::kDisableCache)) { 340 switches::kDisableCache)) {
147 request->bypass_cache = true; 341 request->bypass_cache = true;
148 } 342 }
149 343
150 network_service->CreateURLLoader(GetProxy(&url_loader_)); 344 network_service->CreateURLLoader(GetProxy(&url_loader_));
151 url_loader_->Start(request.Pass(), 345 url_loader_->Start(request.Pass(),
152 base::Bind(&NetworkLoader::OnLoadComplete, 346 base::Bind(&NetworkLoader::OnLoadComplete,
153 weak_ptr_factory_.GetWeakPtr())); 347 weak_ptr_factory_.GetWeakPtr()));
154 } 348 }
155 349
156 ~NetworkLoader() override {
157 if (!file_.empty())
158 base::DeleteFile(file_, false);
159 }
160
161 private: 350 private:
162 bool PeekContentHandler(DataPipeConsumerHandle source,
163 std::string* mojo_shebang,
164 GURL* mojo_content_handler_url)
165 {
166 const char* kMojoMagic = "#!mojo:";
167 // TODO(hansmuller): Revisit this when a real peek operation is available.
168 const MojoDeadline kPeekTimeout = MOJO_DEADLINE_INDEFINITE;
169 const size_t kMaxShebangLength = 2048;
170
171 std::string magic;
172 std::string shebang;
173 if (BlockingPeekNBytes(source, &magic, strlen(kMojoMagic), kPeekTimeout) &&
174 magic == kMojoMagic &&
175 BlockingPeekLine(source, &shebang, kMaxShebangLength, kPeekTimeout)) {
176 GURL url(shebang.substr(2, std::string::npos));
177 if (url.is_valid()) {
178 *mojo_shebang = shebang;
179 *mojo_content_handler_url = url;
180 return true;
181 }
182 }
183 return false;
184 }
185
186 void OnLoadComplete(URLResponsePtr response) { 351 void OnLoadComplete(URLResponsePtr response) {
187 if (response->error) { 352 if (response->error) {
188 LOG(ERROR) << "Error (" << response->error->code << ": " 353 LOG(ERROR) << "Error (" << response->error->code << ": "
189 << response->error->description << ") while fetching " 354 << response->error->description << ") while fetching "
190 << response->url; 355 << response->url;
191 LoaderComplete(); 356 LoaderComplete();
192 return; 357 return;
193 } 358 }
194 359
195 // If the response begins with a #!mojo:<content-handler-url>, use it. 360 OnResponse(make_scoped_ptr(new NetworkLoaderResponse(response.Pass())));
196 {
197 GURL url;
198 std::string shebang;
199 if (PeekContentHandler(response->body.get(), &shebang, &url)) {
200 uint32_t num_skip_bytes = shebang.size();
201 if (ReadDataRaw(response->body.get(),
202 nullptr,
203 &num_skip_bytes,
204 MOJO_READ_DATA_FLAG_ALL_OR_NONE |
205 MOJO_READ_DATA_FLAG_DISCARD) ==
206 MOJO_RESULT_OK) {
207 load_callbacks_->LoadWithContentHandler(url, response.Pass());
208 return;
209 }
210 }
211 }
212
213 MimeTypeToURLMap::iterator iter =
214 mime_type_to_url_->find(response->mime_type);
215 if (iter != mime_type_to_url_->end()) {
216 load_callbacks_->LoadWithContentHandler(iter->second, response.Pass());
217 return;
218 }
219
220 // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo
221 // application. That could either mean looking for the platform-specific dll
222 // header, or looking for some specific mojo signature prepended to the
223 // library.
224
225 base::CreateTemporaryFile(&file_);
226 common::CopyToFile(
227 response->body.Pass(),
228 file_,
229 context_->task_runners()->blocking_pool(),
230 base::Bind(
231 &NetworkLoader::RunLibrary, weak_ptr_factory_.GetWeakPtr(), file_));
232 } 361 }
233 362
234 MimeTypeToURLMap* mime_type_to_url_;
235 URLLoaderPtr url_loader_; 363 URLLoaderPtr url_loader_;
236 base::FilePath file_;
237 base::WeakPtrFactory<NetworkLoader> weak_ptr_factory_; 364 base::WeakPtrFactory<NetworkLoader> weak_ptr_factory_;
238 }; 365 };
239 DynamicApplicationLoader::DynamicApplicationLoader( 366 DynamicApplicationLoader::DynamicApplicationLoader(
240 Context* context, 367 Context* context,
241 scoped_ptr<DynamicServiceRunnerFactory> runner_factory) 368 scoped_ptr<DynamicServiceRunnerFactory> runner_factory)
242 : context_(context), 369 : context_(context),
243 runner_factory_(runner_factory.Pass()), 370 runner_factory_(runner_factory.Pass()),
244 371
245 // Unretained() is correct here because DynamicApplicationLoader owns the 372 // Unretained() is correct here because DynamicApplicationLoader owns the
246 // loaders that we pass this callback to. 373 // loaders that we pass this callback to.
(...skipping 18 matching lines...) Expand all
265 GURL resolved_url; 392 GURL resolved_url;
266 if (url.SchemeIs("mojo")) { 393 if (url.SchemeIs("mojo")) {
267 resolved_url = context_->mojo_url_resolver()->Resolve(url); 394 resolved_url = context_->mojo_url_resolver()->Resolve(url);
268 } else { 395 } else {
269 resolved_url = url; 396 resolved_url = url;
270 } 397 }
271 398
272 if (resolved_url.SchemeIsFile()) { 399 if (resolved_url.SchemeIsFile()) {
273 loaders_.push_back(new LocalLoader(resolved_url, 400 loaders_.push_back(new LocalLoader(resolved_url,
274 context_, 401 context_,
402 &mime_type_to_url_,
275 runner_factory_.get(), 403 runner_factory_.get(),
276 load_callbacks, 404 load_callbacks,
277 loader_complete_callback_)); 405 loader_complete_callback_));
278 return; 406 return;
279 } 407 }
280 408
281 if (!network_service_) { 409 if (!network_service_) {
282 context_->application_manager()->ConnectToService( 410 context_->application_manager()->ConnectToService(
283 GURL("mojo:network_service"), &network_service_); 411 GURL("mojo:network_service"), &network_service_);
284 } 412 }
(...skipping 12 matching lines...) Expand all
297 // TODO(darin): What should we do about service errors? This implies that 425 // TODO(darin): What should we do about service errors? This implies that
298 // the app closed its handle to the service manager. Maybe we don't care? 426 // the app closed its handle to the service manager. Maybe we don't care?
299 } 427 }
300 428
301 void DynamicApplicationLoader::LoaderComplete(Loader* loader) { 429 void DynamicApplicationLoader::LoaderComplete(Loader* loader) {
302 loaders_.erase(std::find(loaders_.begin(), loaders_.end(), loader)); 430 loaders_.erase(std::find(loaders_.begin(), loaders_.end(), loader));
303 } 431 }
304 432
305 } // namespace shell 433 } // namespace shell
306 } // namespace mojo 434 } // namespace mojo
OLDNEW
« no previous file with comments | « mojo/common/data_pipe_utils_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698