OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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 "apps/launcher.h" | 5 #include "apps/launcher.h" |
6 | 6 |
7 #include "apps/browser/api/app_runtime/app_runtime_api.h" | 7 #include "apps/browser/api/app_runtime/app_runtime_api.h" |
8 #include "apps/browser/file_handler_util.h" | 8 #include "apps/browser/file_handler_util.h" |
9 #include "apps/common/api/app_runtime.h" | 9 #include "apps/common/api/app_runtime.h" |
10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
82 } | 82 } |
83 | 83 |
84 // Helper method to launch the platform app |extension| with no data. This | 84 // Helper method to launch the platform app |extension| with no data. This |
85 // should be called in the fallback case, where it has been impossible to | 85 // should be called in the fallback case, where it has been impossible to |
86 // load or obtain file launch data. | 86 // load or obtain file launch data. |
87 void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) { | 87 void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) { |
88 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 88 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
89 AppEventRouter::DispatchOnLaunchedEvent(profile, extension); | 89 AppEventRouter::DispatchOnLaunchedEvent(profile, extension); |
90 } | 90 } |
91 | 91 |
92 // Class to handle launching of platform apps to open a specific path. | 92 // Class to handle launching of platform apps to open specific paths. |
93 // An instance of this class is created for each launch. The lifetime of these | 93 // An instance of this class is created for each launch. The lifetime of these |
94 // instances is managed by reference counted pointers. As long as an instance | 94 // instances is managed by reference counted pointers. As long as an instance |
95 // has outstanding tasks on a message queue it will be retained; once all | 95 // has outstanding tasks on a message queue it will be retained; once all |
96 // outstanding tasks are completed it will be deleted. | 96 // outstanding tasks are completed it will be deleted. |
97 class PlatformAppPathLauncher | 97 class PlatformAppPathLauncher |
98 : public base::RefCountedThreadSafe<PlatformAppPathLauncher> { | 98 : public base::RefCountedThreadSafe<PlatformAppPathLauncher> { |
99 public: | 99 public: |
100 PlatformAppPathLauncher(Profile* profile, | 100 PlatformAppPathLauncher(Profile* profile, |
101 const Extension* extension, | 101 const Extension* extension, |
| 102 const std::vector<base::FilePath>& file_paths) |
| 103 : profile_(profile), extension_(extension), file_paths_(file_paths) {} |
| 104 |
| 105 PlatformAppPathLauncher(Profile* profile, |
| 106 const Extension* extension, |
102 const base::FilePath& file_path) | 107 const base::FilePath& file_path) |
103 : profile_(profile), extension_(extension), file_path_(file_path) {} | 108 : profile_(profile), extension_(extension) { |
| 109 if (!file_path.empty()) |
| 110 file_paths_.push_back(file_path); |
| 111 } |
104 | 112 |
105 void Launch() { | 113 void Launch() { |
106 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 114 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
107 if (file_path_.empty()) { | 115 if (file_paths_.empty()) { |
108 LaunchPlatformAppWithNoData(profile_, extension_); | 116 LaunchPlatformAppWithNoData(profile_, extension_); |
109 return; | 117 return; |
110 } | 118 } |
111 | 119 |
112 DCHECK(file_path_.IsAbsolute()); | 120 for (size_t i = 0; i < file_paths_.size(); ++i) { |
| 121 DCHECK(file_paths_[i].IsAbsolute()); |
| 122 } |
113 | 123 |
114 if (HasFileSystemWritePermission(extension_)) { | 124 if (HasFileSystemWritePermission(extension_)) { |
115 std::vector<base::FilePath> paths; | |
116 paths.push_back(file_path_); | |
117 PrepareFilesForWritableApp( | 125 PrepareFilesForWritableApp( |
118 paths, | 126 file_paths_, |
119 profile_, | 127 profile_, |
120 false, | 128 false, |
121 base::Bind(&PlatformAppPathLauncher::OnFileValid, this), | 129 base::Bind(&PlatformAppPathLauncher::OnFileValid, this), |
122 base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this)); | 130 base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this)); |
123 return; | 131 return; |
124 } | 132 } |
125 | 133 |
126 OnFileValid(); | 134 OnFileValid(); |
127 } | 135 } |
128 | 136 |
(...skipping 12 matching lines...) Expand all Loading... |
141 } | 149 } |
142 | 150 |
143 private: | 151 private: |
144 friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>; | 152 friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>; |
145 | 153 |
146 virtual ~PlatformAppPathLauncher() {} | 154 virtual ~PlatformAppPathLauncher() {} |
147 | 155 |
148 void MakePathAbsolute(const base::FilePath& current_directory) { | 156 void MakePathAbsolute(const base::FilePath& current_directory) { |
149 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 157 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
150 | 158 |
151 if (!DoMakePathAbsolute(current_directory, &file_path_)) { | 159 for (std::vector<base::FilePath>::iterator it = file_paths_.begin(); |
152 LOG(WARNING) << "Cannot make absolute path from " << file_path_.value(); | 160 it != file_paths_.end(); |
153 file_path_ = base::FilePath(); | 161 ++it) { |
| 162 if (!DoMakePathAbsolute(current_directory, &*it)) { |
| 163 LOG(WARNING) << "Cannot make absolute path from " << it->value(); |
| 164 BrowserThread::PostTask( |
| 165 BrowserThread::UI, |
| 166 FROM_HERE, |
| 167 base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this)); |
| 168 return; |
| 169 } |
154 } | 170 } |
155 | 171 |
156 BrowserThread::PostTask(BrowserThread::UI, | 172 BrowserThread::PostTask(BrowserThread::UI, |
157 FROM_HERE, | 173 FROM_HERE, |
158 base::Bind(&PlatformAppPathLauncher::Launch, this)); | 174 base::Bind(&PlatformAppPathLauncher::Launch, this)); |
159 } | 175 } |
160 | 176 |
161 void OnFileValid() { | 177 void OnFileValid() { |
| 178 mime_types_.resize(file_paths_.size()); |
162 #if defined(OS_CHROMEOS) | 179 #if defined(OS_CHROMEOS) |
163 if (file_manager::util::IsUnderNonNativeLocalPath(profile_, file_path_)) { | 180 GetNextNonNativeMimeType(); |
164 file_manager::util::GetNonNativeLocalPathMimeType( | 181 #else |
165 profile_, | |
166 file_path_, | |
167 base::Bind(&PlatformAppPathLauncher::OnGotMimeType, this)); | |
168 return; | |
169 } | |
170 #endif | |
171 | |
172 BrowserThread::PostTask( | 182 BrowserThread::PostTask( |
173 BrowserThread::FILE, | 183 BrowserThread::FILE, |
174 FROM_HERE, | 184 FROM_HERE, |
175 base::Bind(&PlatformAppPathLauncher::GetMimeTypeAndLaunch, this)); | 185 base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this)); |
| 186 #endif |
176 } | 187 } |
177 | 188 |
178 void OnFileInvalid(const base::FilePath& /* error_path */) { | 189 void OnFileInvalid(const base::FilePath& /* error_path */) { |
179 LaunchWithNoLaunchData(); | 190 LaunchWithNoLaunchData(); |
180 } | 191 } |
181 | 192 |
182 void GetMimeTypeAndLaunch() { | 193 #if defined(OS_CHROMEOS) |
183 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | 194 void GetNextNonNativeMimeType() { |
| 195 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
184 | 196 |
185 // If the file doesn't exist, or is a directory, launch with no launch data. | 197 bool any_native_files = false; |
186 if (!base::PathExists(file_path_) || | 198 for (size_t i = 0; i < mime_types_.size(); ++i) { |
187 base::DirectoryExists(file_path_)) { | 199 if (!mime_types_[i].empty()) |
188 LOG(WARNING) << "No file exists with path " << file_path_.value(); | 200 continue; |
189 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( | 201 const base::FilePath& file_path = file_paths_[i]; |
190 &PlatformAppPathLauncher::LaunchWithNoLaunchData, this)); | 202 if (file_manager::util::IsUnderNonNativeLocalPath(profile_, file_path)) { |
| 203 file_manager::util::GetNonNativeLocalPathMimeType( |
| 204 profile_, |
| 205 file_path, |
| 206 base::Bind(&PlatformAppPathLauncher::OnGotMimeType, this, i)); |
| 207 return; |
| 208 } |
| 209 any_native_files = true; |
| 210 } |
| 211 |
| 212 // If there are any native files, we need to call GetMimeTypesAndLaunch to |
| 213 // obtain mime types for the files. |
| 214 if (any_native_files) { |
| 215 BrowserThread::PostTask( |
| 216 BrowserThread::FILE, |
| 217 FROM_HERE, |
| 218 base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this)); |
191 return; | 219 return; |
192 } | 220 } |
193 | 221 |
194 std::string mime_type; | 222 // Otherwise, we can call LaunchWithMimeTypes directly. |
195 if (!net::GetMimeTypeFromFile(file_path_, &mime_type)) { | 223 LaunchWithMimeTypes(); |
196 // If MIME type of the file can't be determined by its path, | |
197 // try to sniff it by its content. | |
198 std::vector<char> content(net::kMaxBytesToSniff); | |
199 int bytes_read = base::ReadFile(file_path_, &content[0], content.size()); | |
200 if (bytes_read >= 0) { | |
201 net::SniffMimeType(&content[0], | |
202 bytes_read, | |
203 net::FilePathToFileURL(file_path_), | |
204 std::string(), // type_hint (passes no hint) | |
205 &mime_type); | |
206 } | |
207 if (mime_type.empty()) | |
208 mime_type = kFallbackMimeType; | |
209 } | |
210 | |
211 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( | |
212 &PlatformAppPathLauncher::LaunchWithMimeType, this, mime_type)); | |
213 } | 224 } |
214 | 225 |
215 #if defined(OS_CHROMEOS) | 226 void OnGotMimeType(size_t index, bool success, const std::string& mime_type) { |
216 void OnGotMimeType(bool success, const std::string& mime_type) { | |
217 if (!success) { | 227 if (!success) { |
218 LaunchWithNoLaunchData(); | 228 LaunchWithNoLaunchData(); |
219 return; | 229 return; |
220 } | 230 } |
221 LaunchWithMimeType(mime_type.empty() ? kFallbackMimeType : mime_type); | 231 mime_types_[index] = mime_type.empty() ? kFallbackMimeType : mime_type; |
| 232 GetNextNonNativeMimeType(); |
222 } | 233 } |
223 #endif | 234 #endif |
224 | 235 |
| 236 void GetMimeTypesAndLaunch() { |
| 237 DCHECK_CURRENTLY_ON(BrowserThread::FILE); |
| 238 |
| 239 for (size_t i = 0; i < mime_types_.size(); ++i) { |
| 240 if (!this->mime_types_[i].empty()) |
| 241 continue; |
| 242 const base::FilePath& file_path = file_paths_[i]; |
| 243 |
| 244 // If the file doesn't exist, or is a directory, launch with no launch |
| 245 // data. |
| 246 if (!base::PathExists(file_path) || base::DirectoryExists(file_path)) { |
| 247 LOG(WARNING) << "No file exists with path " << file_path.value(); |
| 248 BrowserThread::PostTask( |
| 249 BrowserThread::UI, |
| 250 FROM_HERE, |
| 251 base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this)); |
| 252 return; |
| 253 } |
| 254 |
| 255 std::string mime_type; |
| 256 if (!net::GetMimeTypeFromFile(file_path, &mime_type)) { |
| 257 // If MIME type of the file can't be determined by its path, |
| 258 // try to sniff it by its content. |
| 259 std::vector<char> content(net::kMaxBytesToSniff); |
| 260 int bytes_read = base::ReadFile(file_path, &content[0], content.size()); |
| 261 if (bytes_read >= 0) { |
| 262 net::SniffMimeType(&content[0], |
| 263 bytes_read, |
| 264 net::FilePathToFileURL(file_path), |
| 265 std::string(), // type_hint (passes no hint) |
| 266 &mime_type); |
| 267 } |
| 268 if (mime_type.empty()) |
| 269 mime_type = kFallbackMimeType; |
| 270 } |
| 271 mime_types_[i] = mime_type; |
| 272 } |
| 273 |
| 274 BrowserThread::PostTask( |
| 275 BrowserThread::UI, |
| 276 FROM_HERE, |
| 277 base::Bind(&PlatformAppPathLauncher::LaunchWithMimeTypes, this)); |
| 278 } |
| 279 |
225 void LaunchWithNoLaunchData() { | 280 void LaunchWithNoLaunchData() { |
226 // This method is required as an entry point on the UI thread. | 281 // This method is required as an entry point on the UI thread. |
227 LaunchPlatformAppWithNoData(profile_, extension_); | 282 LaunchPlatformAppWithNoData(profile_, extension_); |
228 } | 283 } |
229 | 284 |
230 void LaunchWithMimeType(const std::string& mime_type) { | 285 void LaunchWithMimeTypes() { |
| 286 DCHECK(file_paths_.size() == mime_types_.size()); |
| 287 |
231 // Find file handler from the platform app for the file being opened. | 288 // Find file handler from the platform app for the file being opened. |
232 const extensions::FileHandlerInfo* handler = NULL; | 289 const extensions::FileHandlerInfo* handler = NULL; |
233 if (!handler_id_.empty()) | 290 if (!handler_id_.empty()) { |
234 handler = FileHandlerForId(*extension_, handler_id_); | 291 handler = FileHandlerForId(*extension_, handler_id_); |
235 else | 292 if (handler) { |
236 handler = FirstFileHandlerForFile(*extension_, mime_type, file_path_); | 293 for (size_t i = 0; i < file_paths_.size(); ++i) { |
237 if (handler && !FileHandlerCanHandleFile(*handler, mime_type, file_path_)) { | 294 if (!FileHandlerCanHandleFile( |
238 LOG(WARNING) << "Extension does not provide a valid file handler for " | 295 *handler, mime_types_[i], file_paths_[i])) { |
239 << file_path_.value(); | 296 LOG(WARNING) |
240 LaunchWithNoLaunchData(); | 297 << "Extension does not provide a valid file handler for " |
241 return; | 298 << file_paths_[i].value(); |
| 299 handler = NULL; |
| 300 break; |
| 301 } |
| 302 } |
| 303 } |
| 304 } else { |
| 305 std::set<std::pair<base::FilePath, std::string> > path_and_file_type_set; |
| 306 for (size_t i = 0; i < file_paths_.size(); ++i) { |
| 307 path_and_file_type_set.insert( |
| 308 std::make_pair(file_paths_[i], mime_types_[i])); |
| 309 } |
| 310 const std::vector<const extensions::FileHandlerInfo*>& handlers = |
| 311 extensions::app_file_handler_util::FindFileHandlersForFiles( |
| 312 *extension_, path_and_file_type_set); |
| 313 if (!handlers.empty()) |
| 314 handler = handlers[0]; |
242 } | 315 } |
243 | 316 |
244 // If this app doesn't have a file handler that supports the file, launch | 317 // If this app doesn't have a file handler that supports the file, launch |
245 // with no launch data. | 318 // with no launch data. |
246 if (!handler) { | 319 if (!handler) { |
247 LOG(WARNING) << "Extension does not provide a valid file handler for " | 320 LOG(WARNING) << "Extension does not provide a valid file handler."; |
248 << file_path_.value(); | |
249 LaunchWithNoLaunchData(); | 321 LaunchWithNoLaunchData(); |
250 return; | 322 return; |
251 } | 323 } |
252 | 324 |
253 if (handler_id_.empty()) | 325 if (handler_id_.empty()) |
254 handler_id_ = handler->id; | 326 handler_id_ = handler->id; |
255 | 327 |
256 // Access needs to be granted to the file for the process associated with | 328 // Access needs to be granted to the file for the process associated with |
257 // the extension. To do this the ExtensionHost is needed. This might not be | 329 // the extension. To do this the ExtensionHost is needed. This might not be |
258 // available, or it might be in the process of being unloaded, in which case | 330 // available, or it might be in the process of being unloaded, in which case |
259 // the lazy background task queue is used to load the extension and then | 331 // the lazy background task queue is used to load the extension and then |
260 // call back to us. | 332 // call back to us. |
261 extensions::LazyBackgroundTaskQueue* queue = | 333 extensions::LazyBackgroundTaskQueue* const queue = |
262 ExtensionSystem::Get(profile_)->lazy_background_task_queue(); | 334 ExtensionSystem::Get(profile_)->lazy_background_task_queue(); |
263 if (queue->ShouldEnqueueTask(profile_, extension_)) { | 335 if (queue->ShouldEnqueueTask(profile_, extension_)) { |
264 queue->AddPendingTask(profile_, extension_->id(), base::Bind( | 336 queue->AddPendingTask( |
265 &PlatformAppPathLauncher::GrantAccessToFileAndLaunch, | 337 profile_, |
266 this, mime_type)); | 338 extension_->id(), |
| 339 base::Bind(&PlatformAppPathLauncher::GrantAccessToFilesAndLaunch, |
| 340 this)); |
267 return; | 341 return; |
268 } | 342 } |
269 | 343 |
270 extensions::ProcessManager* process_manager = | 344 extensions::ProcessManager* const process_manager = |
271 ExtensionSystem::Get(profile_)->process_manager(); | 345 ExtensionSystem::Get(profile_)->process_manager(); |
272 ExtensionHost* host = | 346 ExtensionHost* const host = |
273 process_manager->GetBackgroundHostForExtension(extension_->id()); | 347 process_manager->GetBackgroundHostForExtension(extension_->id()); |
274 DCHECK(host); | 348 DCHECK(host); |
275 GrantAccessToFileAndLaunch(mime_type, host); | 349 GrantAccessToFilesAndLaunch(host); |
276 } | 350 } |
277 | 351 |
278 void GrantAccessToFileAndLaunch(const std::string& mime_type, | 352 void GrantAccessToFilesAndLaunch(ExtensionHost* host) { |
279 ExtensionHost* host) { | |
280 // If there was an error loading the app page, |host| will be NULL. | 353 // If there was an error loading the app page, |host| will be NULL. |
281 if (!host) { | 354 if (!host) { |
282 LOG(ERROR) << "Could not load app page for " << extension_->id(); | 355 LOG(ERROR) << "Could not load app page for " << extension_->id(); |
283 return; | 356 return; |
284 } | 357 } |
285 | 358 |
286 GrantedFileEntry file_entry = | 359 std::vector<GrantedFileEntry> file_entries; |
287 CreateFileEntry(profile_, | 360 for (size_t i = 0; i < file_paths_.size(); ++i) { |
288 extension_, | 361 file_entries.push_back( |
289 host->render_process_host()->GetID(), | 362 CreateFileEntry(profile_, |
290 file_path_, | 363 extension_, |
291 false); | 364 host->render_process_host()->GetID(), |
292 AppEventRouter::DispatchOnLaunchedEventWithFileEntry( | 365 file_paths_[i], |
293 profile_, extension_, handler_id_, mime_type, file_entry); | 366 false)); |
| 367 } |
| 368 |
| 369 AppEventRouter::DispatchOnLaunchedEventWithFileEntries( |
| 370 profile_, extension_, handler_id_, mime_types_, file_entries); |
294 } | 371 } |
295 | 372 |
296 // The profile the app should be run in. | 373 // The profile the app should be run in. |
297 Profile* profile_; | 374 Profile* profile_; |
298 // The extension providing the app. | 375 // The extension providing the app. |
299 // TODO(benwells): Hold onto the extension ID instead of a pointer as it | 376 // TODO(benwells): Hold onto the extension ID instead of a pointer as it |
300 // is possible the extension will be unloaded while we're doing our thing. | 377 // is possible the extension will be unloaded while we're doing our thing. |
301 // See http://crbug.com/372270 for details. | 378 // See http://crbug.com/372270 for details. |
302 const Extension* extension_; | 379 const Extension* extension_; |
303 // The path to be passed through to the app. | 380 // The path to be passed through to the app. |
304 base::FilePath file_path_; | 381 std::vector<base::FilePath> file_paths_; |
| 382 std::vector<std::string> mime_types_; |
305 // The ID of the file handler used to launch the app. | 383 // The ID of the file handler used to launch the app. |
306 std::string handler_id_; | 384 std::string handler_id_; |
307 | 385 |
308 DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); | 386 DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); |
309 }; | 387 }; |
310 | 388 |
311 } // namespace | 389 } // namespace |
312 | 390 |
313 void LaunchPlatformAppWithCommandLine(Profile* profile, | 391 void LaunchPlatformAppWithCommandLine(Profile* profile, |
314 const Extension* extension, | 392 const Extension* extension, |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
362 launcher->Launch(); | 440 launcher->Launch(); |
363 } | 441 } |
364 | 442 |
365 void LaunchPlatformApp(Profile* profile, const Extension* extension) { | 443 void LaunchPlatformApp(Profile* profile, const Extension* extension) { |
366 LaunchPlatformAppWithCommandLine(profile, | 444 LaunchPlatformAppWithCommandLine(profile, |
367 extension, | 445 extension, |
368 CommandLine(CommandLine::NO_PROGRAM), | 446 CommandLine(CommandLine::NO_PROGRAM), |
369 base::FilePath()); | 447 base::FilePath()); |
370 } | 448 } |
371 | 449 |
372 void LaunchPlatformAppWithFileHandler(Profile* profile, | 450 void LaunchPlatformAppWithFileHandler( |
373 const Extension* extension, | 451 Profile* profile, |
374 const std::string& handler_id, | 452 const Extension* extension, |
375 const base::FilePath& file_path) { | 453 const std::string& handler_id, |
| 454 const std::vector<base::FilePath>& file_paths) { |
376 scoped_refptr<PlatformAppPathLauncher> launcher = | 455 scoped_refptr<PlatformAppPathLauncher> launcher = |
377 new PlatformAppPathLauncher(profile, extension, file_path); | 456 new PlatformAppPathLauncher(profile, extension, file_paths); |
378 launcher->LaunchWithHandler(handler_id); | 457 launcher->LaunchWithHandler(handler_id); |
379 } | 458 } |
380 | 459 |
381 void RestartPlatformApp(Profile* profile, const Extension* extension) { | 460 void RestartPlatformApp(Profile* profile, const Extension* extension) { |
382 EventRouter* event_router = EventRouter::Get(profile); | 461 EventRouter* event_router = EventRouter::Get(profile); |
383 bool listening_to_restart = event_router-> | 462 bool listening_to_restart = event_router-> |
384 ExtensionHasEventListener(extension->id(), | 463 ExtensionHasEventListener(extension->id(), |
385 app_runtime::OnRestarted::kEventName); | 464 app_runtime::OnRestarted::kEventName); |
386 | 465 |
387 if (listening_to_restart) { | 466 if (listening_to_restart) { |
(...skipping 16 matching lines...) Expand all Loading... |
404 void LaunchPlatformAppWithUrl(Profile* profile, | 483 void LaunchPlatformAppWithUrl(Profile* profile, |
405 const Extension* extension, | 484 const Extension* extension, |
406 const std::string& handler_id, | 485 const std::string& handler_id, |
407 const GURL& url, | 486 const GURL& url, |
408 const GURL& referrer_url) { | 487 const GURL& referrer_url) { |
409 AppEventRouter::DispatchOnLaunchedEventWithUrl( | 488 AppEventRouter::DispatchOnLaunchedEventWithUrl( |
410 profile, extension, handler_id, url, referrer_url); | 489 profile, extension, handler_id, url, referrer_url); |
411 } | 490 } |
412 | 491 |
413 } // namespace apps | 492 } // namespace apps |
OLD | NEW |