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

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