| OLD | NEW |
| 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/application_manager.h" | 5 #include "mojo/shell/application_manager.h" |
| 6 | 6 |
| 7 #include <utility> |
| 8 |
| 7 #include "base/bind.h" | 9 #include "base/bind.h" |
| 8 #include "base/command_line.h" | 10 #include "base/command_line.h" |
| 9 #include "base/logging.h" | 11 #include "base/logging.h" |
| 10 #include "base/macros.h" | 12 #include "base/macros.h" |
| 11 #include "base/stl_util.h" | 13 #include "base/stl_util.h" |
| 12 #include "base/strings/string_util.h" | 14 #include "base/strings/string_util.h" |
| 13 #include "base/trace_event/trace_event.h" | 15 #include "base/trace_event/trace_event.h" |
| 14 #include "mojo/public/cpp/bindings/binding.h" | 16 #include "mojo/public/cpp/bindings/binding.h" |
| 15 #include "mojo/shell/application_instance.h" | 17 #include "mojo/shell/application_instance.h" |
| 16 #include "mojo/shell/fetcher.h" | 18 #include "mojo/shell/fetcher.h" |
| (...skipping 27 matching lines...) Expand all Loading... |
| 44 } | 46 } |
| 45 | 47 |
| 46 bool ApplicationManager::TestAPI::HasRunningInstanceForURL( | 48 bool ApplicationManager::TestAPI::HasRunningInstanceForURL( |
| 47 const GURL& url) const { | 49 const GURL& url) const { |
| 48 return manager_->identity_to_instance_.find(Identity(url)) != | 50 return manager_->identity_to_instance_.find(Identity(url)) != |
| 49 manager_->identity_to_instance_.end(); | 51 manager_->identity_to_instance_.end(); |
| 50 } | 52 } |
| 51 | 53 |
| 52 ApplicationManager::ApplicationManager( | 54 ApplicationManager::ApplicationManager( |
| 53 scoped_ptr<PackageManager> package_manager) | 55 scoped_ptr<PackageManager> package_manager) |
| 54 : ApplicationManager(package_manager.Pass(), nullptr, nullptr) {} | 56 : ApplicationManager(std::move(package_manager), nullptr, nullptr) {} |
| 55 | 57 |
| 56 ApplicationManager::ApplicationManager( | 58 ApplicationManager::ApplicationManager( |
| 57 scoped_ptr<PackageManager> package_manager, | 59 scoped_ptr<PackageManager> package_manager, |
| 58 scoped_ptr<NativeRunnerFactory> native_runner_factory, | 60 scoped_ptr<NativeRunnerFactory> native_runner_factory, |
| 59 base::TaskRunner* task_runner) | 61 base::TaskRunner* task_runner) |
| 60 : package_manager_(package_manager.Pass()), | 62 : package_manager_(std::move(package_manager)), |
| 61 task_runner_(task_runner), | 63 task_runner_(task_runner), |
| 62 native_runner_factory_(native_runner_factory.Pass()), | 64 native_runner_factory_(std::move(native_runner_factory)), |
| 63 weak_ptr_factory_(this) { | 65 weak_ptr_factory_(this) { |
| 64 package_manager_->SetApplicationManager(this); | 66 package_manager_->SetApplicationManager(this); |
| 65 SetLoaderForURL(make_scoped_ptr(new ShellApplicationLoader(this)), | 67 SetLoaderForURL(make_scoped_ptr(new ShellApplicationLoader(this)), |
| 66 GURL("mojo:shell")); | 68 GURL("mojo:shell")); |
| 67 } | 69 } |
| 68 | 70 |
| 69 ApplicationManager::~ApplicationManager() { | 71 ApplicationManager::~ApplicationManager() { |
| 70 TerminateShellConnections(); | 72 TerminateShellConnections(); |
| 71 STLDeleteValues(&url_to_loader_); | 73 STLDeleteValues(&url_to_loader_); |
| 72 } | 74 } |
| 73 | 75 |
| 74 void ApplicationManager::TerminateShellConnections() { | 76 void ApplicationManager::TerminateShellConnections() { |
| 75 STLDeleteValues(&identity_to_instance_); | 77 STLDeleteValues(&identity_to_instance_); |
| 76 } | 78 } |
| 77 | 79 |
| 78 void ApplicationManager::ConnectToApplication( | 80 void ApplicationManager::ConnectToApplication( |
| 79 scoped_ptr<ConnectToApplicationParams> params) { | 81 scoped_ptr<ConnectToApplicationParams> params) { |
| 80 TRACE_EVENT_INSTANT1("mojo_shell", "ApplicationManager::ConnectToApplication", | 82 TRACE_EVENT_INSTANT1("mojo_shell", "ApplicationManager::ConnectToApplication", |
| 81 TRACE_EVENT_SCOPE_THREAD, "original_url", | 83 TRACE_EVENT_SCOPE_THREAD, "original_url", |
| 82 params->target().url().spec()); | 84 params->target().url().spec()); |
| 83 DCHECK(params->target().url().is_valid()); | 85 DCHECK(params->target().url().is_valid()); |
| 84 | 86 |
| 85 // Connect to an existing matching instance, if possible. | 87 // Connect to an existing matching instance, if possible. |
| 86 if (ConnectToRunningApplication(¶ms)) | 88 if (ConnectToRunningApplication(¶ms)) |
| 87 return; | 89 return; |
| 88 | 90 |
| 89 ApplicationLoader* loader = GetLoaderForURL(params->target().url()); | 91 ApplicationLoader* loader = GetLoaderForURL(params->target().url()); |
| 90 if (loader) { | 92 if (loader) { |
| 91 GURL url = params->target().url(); | 93 GURL url = params->target().url(); |
| 92 loader->Load(url, CreateAndConnectToInstance(params.Pass(), nullptr)); | 94 loader->Load(url, CreateAndConnectToInstance(std::move(params), nullptr)); |
| 93 return; | 95 return; |
| 94 } | 96 } |
| 95 | 97 |
| 96 URLRequestPtr original_url_request = params->TakeTargetURLRequest(); | 98 URLRequestPtr original_url_request = params->TakeTargetURLRequest(); |
| 97 auto callback = | 99 auto callback = |
| 98 base::Bind(&ApplicationManager::HandleFetchCallback, | 100 base::Bind(&ApplicationManager::HandleFetchCallback, |
| 99 weak_ptr_factory_.GetWeakPtr(), base::Passed(¶ms)); | 101 weak_ptr_factory_.GetWeakPtr(), base::Passed(¶ms)); |
| 100 package_manager_->FetchRequest(original_url_request.Pass(), callback); | 102 package_manager_->FetchRequest(std::move(original_url_request), callback); |
| 101 } | 103 } |
| 102 | 104 |
| 103 bool ApplicationManager::ConnectToRunningApplication( | 105 bool ApplicationManager::ConnectToRunningApplication( |
| 104 scoped_ptr<ConnectToApplicationParams>* params) { | 106 scoped_ptr<ConnectToApplicationParams>* params) { |
| 105 ApplicationInstance* instance = GetApplicationInstance((*params)->target()); | 107 ApplicationInstance* instance = GetApplicationInstance((*params)->target()); |
| 106 if (!instance) | 108 if (!instance) |
| 107 return false; | 109 return false; |
| 108 | 110 |
| 109 instance->ConnectToClient(params->Pass()); | 111 instance->ConnectToClient(std::move(*params)); |
| 110 return true; | 112 return true; |
| 111 } | 113 } |
| 112 | 114 |
| 113 ApplicationInstance* ApplicationManager::GetApplicationInstance( | 115 ApplicationInstance* ApplicationManager::GetApplicationInstance( |
| 114 const Identity& identity) const { | 116 const Identity& identity) const { |
| 115 const auto& it = identity_to_instance_.find(identity); | 117 const auto& it = identity_to_instance_.find(identity); |
| 116 return it != identity_to_instance_.end() ? it->second : nullptr; | 118 return it != identity_to_instance_.end() ? it->second : nullptr; |
| 117 } | 119 } |
| 118 | 120 |
| 119 void ApplicationManager::CreateInstanceForHandle(ScopedHandle channel, | 121 void ApplicationManager::CreateInstanceForHandle(ScopedHandle channel, |
| 120 const GURL& url, | 122 const GURL& url, |
| 121 CapabilityFilterPtr filter) { | 123 CapabilityFilterPtr filter) { |
| 122 // Instances created by others are considered unique, and thus have no | 124 // Instances created by others are considered unique, and thus have no |
| 123 // identity. As such they cannot be connected to by anyone else, and so we | 125 // identity. As such they cannot be connected to by anyone else, and so we |
| 124 // never call ConnectToClient(). | 126 // never call ConnectToClient(). |
| 125 // TODO(beng): GetPermissiveCapabilityFilter() here obviously cannot make it | 127 // TODO(beng): GetPermissiveCapabilityFilter() here obviously cannot make it |
| 126 // to production. See note in application_manager.mojom. | 128 // to production. See note in application_manager.mojom. |
| 127 // http://crbug.com/555392 | 129 // http://crbug.com/555392 |
| 128 CapabilityFilter local_filter = filter->filter.To<CapabilityFilter>(); | 130 CapabilityFilter local_filter = filter->filter.To<CapabilityFilter>(); |
| 129 Identity target_id(url, std::string(), local_filter); | 131 Identity target_id(url, std::string(), local_filter); |
| 130 ApplicationInstance* instance = nullptr; | 132 ApplicationInstance* instance = nullptr; |
| 131 InterfaceRequest<Application> application_request = | 133 InterfaceRequest<Application> application_request = |
| 132 CreateInstance(target_id, base::Closure(), &instance); | 134 CreateInstance(target_id, base::Closure(), &instance); |
| 133 scoped_ptr<NativeRunner> runner = | 135 scoped_ptr<NativeRunner> runner = |
| 134 native_runner_factory_->Create(base::FilePath()); | 136 native_runner_factory_->Create(base::FilePath()); |
| 135 runner->InitHost(channel.Pass(), application_request.Pass()); | 137 runner->InitHost(std::move(channel), std::move(application_request)); |
| 136 instance->SetNativeRunner(runner.get()); | 138 instance->SetNativeRunner(runner.get()); |
| 137 native_runners_.push_back(std::move(runner)); | 139 native_runners_.push_back(std::move(runner)); |
| 138 } | 140 } |
| 139 | 141 |
| 140 void ApplicationManager::AddListener( | 142 void ApplicationManager::AddListener( |
| 141 mojom::ApplicationManagerListenerPtr listener) { | 143 mojom::ApplicationManagerListenerPtr listener) { |
| 142 Array<mojom::ApplicationInfoPtr> applications; | 144 Array<mojom::ApplicationInfoPtr> applications; |
| 143 for (auto& entry : identity_to_instance_) | 145 for (auto& entry : identity_to_instance_) |
| 144 applications.push_back(CreateApplicationInfoForInstance(entry.second)); | 146 applications.push_back(CreateApplicationInfoForInstance(entry.second)); |
| 145 listener->SetRunningApplications(std::move(applications)); | 147 listener->SetRunningApplications(std::move(applications)); |
| 146 | 148 |
| 147 listeners_.AddInterfacePtr(std::move(listener)); | 149 listeners_.AddInterfacePtr(std::move(listener)); |
| 148 } | 150 } |
| 149 | 151 |
| 150 InterfaceRequest<Application> ApplicationManager::CreateAndConnectToInstance( | 152 InterfaceRequest<Application> ApplicationManager::CreateAndConnectToInstance( |
| 151 scoped_ptr<ConnectToApplicationParams> params, | 153 scoped_ptr<ConnectToApplicationParams> params, |
| 152 ApplicationInstance** resulting_instance) { | 154 ApplicationInstance** resulting_instance) { |
| 153 ApplicationInstance* instance = nullptr; | 155 ApplicationInstance* instance = nullptr; |
| 154 InterfaceRequest<Application> application_request = | 156 InterfaceRequest<Application> application_request = |
| 155 CreateInstance(params->target(), params->on_application_end(), &instance); | 157 CreateInstance(params->target(), params->on_application_end(), &instance); |
| 156 instance->ConnectToClient(params.Pass()); | 158 instance->ConnectToClient(std::move(params)); |
| 157 if (resulting_instance) | 159 if (resulting_instance) |
| 158 *resulting_instance = instance; | 160 *resulting_instance = instance; |
| 159 return application_request.Pass(); | 161 return application_request; |
| 160 } | 162 } |
| 161 | 163 |
| 162 InterfaceRequest<Application> ApplicationManager::CreateInstance( | 164 InterfaceRequest<Application> ApplicationManager::CreateInstance( |
| 163 const Identity& target_id, | 165 const Identity& target_id, |
| 164 const base::Closure& on_application_end, | 166 const base::Closure& on_application_end, |
| 165 ApplicationInstance** resulting_instance) { | 167 ApplicationInstance** resulting_instance) { |
| 166 ApplicationPtr application; | 168 ApplicationPtr application; |
| 167 InterfaceRequest<Application> application_request = GetProxy(&application); | 169 InterfaceRequest<Application> application_request = GetProxy(&application); |
| 168 ApplicationInstance* instance = new ApplicationInstance( | 170 ApplicationInstance* instance = new ApplicationInstance( |
| 169 application.Pass(), this, target_id, Shell::kInvalidContentHandlerID, | 171 std::move(application), this, target_id, Shell::kInvalidContentHandlerID, |
| 170 on_application_end); | 172 on_application_end); |
| 171 DCHECK(identity_to_instance_.find(target_id) == | 173 DCHECK(identity_to_instance_.find(target_id) == |
| 172 identity_to_instance_.end()); | 174 identity_to_instance_.end()); |
| 173 identity_to_instance_[target_id] = instance; | 175 identity_to_instance_[target_id] = instance; |
| 174 mojom::ApplicationInfoPtr application_info = | 176 mojom::ApplicationInfoPtr application_info = |
| 175 CreateApplicationInfoForInstance(instance); | 177 CreateApplicationInfoForInstance(instance); |
| 176 listeners_.ForAllPtrs( | 178 listeners_.ForAllPtrs( |
| 177 [this, &application_info](mojom::ApplicationManagerListener* listener) { | 179 [this, &application_info](mojom::ApplicationManagerListener* listener) { |
| 178 listener->ApplicationStarted(application_info.Clone()); | 180 listener->ApplicationStarted(application_info.Clone()); |
| 179 }); | 181 }); |
| 180 instance->InitializeApplication(); | 182 instance->InitializeApplication(); |
| 181 if (resulting_instance) | 183 if (resulting_instance) |
| 182 *resulting_instance = instance; | 184 *resulting_instance = instance; |
| 183 return application_request.Pass(); | 185 return application_request; |
| 184 } | 186 } |
| 185 | 187 |
| 186 void ApplicationManager::HandleFetchCallback( | 188 void ApplicationManager::HandleFetchCallback( |
| 187 scoped_ptr<ConnectToApplicationParams> params, | 189 scoped_ptr<ConnectToApplicationParams> params, |
| 188 scoped_ptr<Fetcher> fetcher) { | 190 scoped_ptr<Fetcher> fetcher) { |
| 189 if (!fetcher) { | 191 if (!fetcher) { |
| 190 // Network error. Drop |params| to tell the requestor. | 192 // Network error. Drop |params| to tell the requestor. |
| 191 params->connect_callback().Run(Shell::kInvalidContentHandlerID); | 193 params->connect_callback().Run(Shell::kInvalidContentHandlerID); |
| 192 return; | 194 return; |
| 193 } | 195 } |
| 194 | 196 |
| 195 GURL redirect_url = fetcher->GetRedirectURL(); | 197 GURL redirect_url = fetcher->GetRedirectURL(); |
| 196 if (!redirect_url.is_empty()) { | 198 if (!redirect_url.is_empty()) { |
| 197 // And around we go again... Whee! | 199 // And around we go again... Whee! |
| 198 // TODO(sky): this loses the original URL info. | 200 // TODO(sky): this loses the original URL info. |
| 199 URLRequestPtr new_request = URLRequest::New(); | 201 URLRequestPtr new_request = URLRequest::New(); |
| 200 new_request->url = redirect_url.spec(); | 202 new_request->url = redirect_url.spec(); |
| 201 HttpHeaderPtr header = HttpHeader::New(); | 203 HttpHeaderPtr header = HttpHeader::New(); |
| 202 header->name = "Referer"; | 204 header->name = "Referer"; |
| 203 header->value = fetcher->GetRedirectReferer().spec(); | 205 header->value = fetcher->GetRedirectReferer().spec(); |
| 204 new_request->headers.push_back(header.Pass()); | 206 new_request->headers.push_back(std::move(header)); |
| 205 params->SetTargetURLRequest(new_request.Pass()); | 207 params->SetTargetURLRequest(std::move(new_request)); |
| 206 ConnectToApplication(params.Pass()); | 208 ConnectToApplication(std::move(params)); |
| 207 return; | 209 return; |
| 208 } | 210 } |
| 209 | 211 |
| 210 // We already checked if the application was running before we fetched it, but | 212 // We already checked if the application was running before we fetched it, but |
| 211 // it might have started while the fetch was outstanding. We don't want to | 213 // it might have started while the fetch was outstanding. We don't want to |
| 212 // have two copies of the app running, so check again. | 214 // have two copies of the app running, so check again. |
| 213 if (ConnectToRunningApplication(¶ms)) | 215 if (ConnectToRunningApplication(¶ms)) |
| 214 return; | 216 return; |
| 215 | 217 |
| 216 Identity source = params->source(); | 218 Identity source = params->source(); |
| 217 Identity target = params->target(); | 219 Identity target = params->target(); |
| 218 Shell::ConnectToApplicationCallback connect_callback = | 220 Shell::ConnectToApplicationCallback connect_callback = |
| 219 params->connect_callback(); | 221 params->connect_callback(); |
| 220 params->set_connect_callback(EmptyConnectCallback()); | 222 params->set_connect_callback(EmptyConnectCallback()); |
| 221 ApplicationInstance* app = nullptr; | 223 ApplicationInstance* app = nullptr; |
| 222 InterfaceRequest<Application> request( | 224 InterfaceRequest<Application> request( |
| 223 CreateAndConnectToInstance(params.Pass(), &app)); | 225 CreateAndConnectToInstance(std::move(params), &app)); |
| 224 | 226 |
| 225 uint32_t content_handler_id = package_manager_->HandleWithContentHandler( | 227 uint32_t content_handler_id = package_manager_->HandleWithContentHandler( |
| 226 fetcher.get(), source, target.url(), target.filter(), &request); | 228 fetcher.get(), source, target.url(), target.filter(), &request); |
| 227 if (content_handler_id != Shell::kInvalidContentHandlerID) { | 229 if (content_handler_id != Shell::kInvalidContentHandlerID) { |
| 228 app->set_requesting_content_handler_id(content_handler_id); | 230 app->set_requesting_content_handler_id(content_handler_id); |
| 229 connect_callback.Run(content_handler_id); | 231 connect_callback.Run(content_handler_id); |
| 230 return; | 232 return; |
| 231 } | 233 } |
| 232 | 234 |
| 233 // TODO(erg): Have a better way of switching the sandbox on. For now, switch | 235 // TODO(erg): Have a better way of switching the sandbox on. For now, switch |
| 234 // it on hard coded when we're using some of the sandboxable core services. | 236 // it on hard coded when we're using some of the sandboxable core services. |
| 235 bool start_sandboxed = false; | 237 bool start_sandboxed = false; |
| 236 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( | 238 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 237 switches::kMojoNoSandbox)) { | 239 switches::kMojoNoSandbox)) { |
| 238 start_sandboxed = (target.url() == GURL("mojo://core_services/") && | 240 start_sandboxed = (target.url() == GURL("mojo://core_services/") && |
| 239 target.qualifier() == "Core") || | 241 target.qualifier() == "Core") || |
| 240 target.url() == GURL("mojo://html_viewer/"); | 242 target.url() == GURL("mojo://html_viewer/"); |
| 241 } | 243 } |
| 242 | 244 |
| 243 connect_callback.Run(Shell::kInvalidContentHandlerID); | 245 connect_callback.Run(Shell::kInvalidContentHandlerID); |
| 244 | 246 |
| 245 fetcher->AsPath(task_runner_, | 247 fetcher->AsPath( |
| 246 base::Bind(&ApplicationManager::RunNativeApplication, | 248 task_runner_, |
| 247 weak_ptr_factory_.GetWeakPtr(), | 249 base::Bind(&ApplicationManager::RunNativeApplication, |
| 248 base::Passed(request.Pass()), start_sandboxed, | 250 weak_ptr_factory_.GetWeakPtr(), |
| 249 base::Passed(fetcher.Pass()), | 251 base::Passed(std::move(request)), start_sandboxed, |
| 250 base::Unretained(app))); | 252 base::Passed(std::move(fetcher)), base::Unretained(app))); |
| 251 } | 253 } |
| 252 | 254 |
| 253 void ApplicationManager::RunNativeApplication( | 255 void ApplicationManager::RunNativeApplication( |
| 254 InterfaceRequest<Application> application_request, | 256 InterfaceRequest<Application> application_request, |
| 255 bool start_sandboxed, | 257 bool start_sandboxed, |
| 256 scoped_ptr<Fetcher> fetcher, | 258 scoped_ptr<Fetcher> fetcher, |
| 257 ApplicationInstance* instance, | 259 ApplicationInstance* instance, |
| 258 const base::FilePath& path, | 260 const base::FilePath& path, |
| 259 bool path_exists) { | 261 bool path_exists) { |
| 260 // We only passed fetcher to keep it alive. Done with it now. | 262 // We only passed fetcher to keep it alive. Done with it now. |
| 261 fetcher.reset(); | 263 fetcher.reset(); |
| 262 | 264 |
| 263 DCHECK(application_request.is_pending()); | 265 DCHECK(application_request.is_pending()); |
| 264 | 266 |
| 265 if (!path_exists) { | 267 if (!path_exists) { |
| 266 LOG(ERROR) << "Library not started because library path '" << path.value() | 268 LOG(ERROR) << "Library not started because library path '" << path.value() |
| 267 << "' does not exist."; | 269 << "' does not exist."; |
| 268 return; | 270 return; |
| 269 } | 271 } |
| 270 | 272 |
| 271 TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path", | 273 TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path", |
| 272 path.AsUTF8Unsafe()); | 274 path.AsUTF8Unsafe()); |
| 273 scoped_ptr<NativeRunner> runner = native_runner_factory_->Create(path); | 275 scoped_ptr<NativeRunner> runner = native_runner_factory_->Create(path); |
| 274 runner->Start(path, start_sandboxed, application_request.Pass(), | 276 runner->Start(path, start_sandboxed, std::move(application_request), |
| 275 base::Bind(&ApplicationManager::CleanupRunner, | 277 base::Bind(&ApplicationManager::CleanupRunner, |
| 276 weak_ptr_factory_.GetWeakPtr(), runner.get())); | 278 weak_ptr_factory_.GetWeakPtr(), runner.get())); |
| 277 instance->SetNativeRunner(runner.get()); | 279 instance->SetNativeRunner(runner.get()); |
| 278 native_runners_.push_back(std::move(runner)); | 280 native_runners_.push_back(std::move(runner)); |
| 279 } | 281 } |
| 280 | 282 |
| 281 void ApplicationManager::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, | 283 void ApplicationManager::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, |
| 282 const GURL& url) { | 284 const GURL& url) { |
| 283 URLToLoaderMap::iterator it = url_to_loader_.find(url); | 285 URLToLoaderMap::iterator it = url_to_loader_.find(url); |
| 284 if (it != url_to_loader_.end()) | 286 if (it != url_to_loader_.end()) |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 329 } | 331 } |
| 330 } | 332 } |
| 331 } | 333 } |
| 332 | 334 |
| 333 Shell::ConnectToApplicationCallback EmptyConnectCallback() { | 335 Shell::ConnectToApplicationCallback EmptyConnectCallback() { |
| 334 return base::Bind(&OnEmptyOnConnectCallback); | 336 return base::Bind(&OnEmptyOnConnectCallback); |
| 335 } | 337 } |
| 336 | 338 |
| 337 } // namespace shell | 339 } // namespace shell |
| 338 } // namespace mojo | 340 } // namespace mojo |
| OLD | NEW |