OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "content/public/app/content_main_runner.h" | 5 #include "content/public/app/content_main_runner.h" |
6 | 6 |
7 #include <stdlib.h> | 7 #include <stdlib.h> |
8 | 8 |
9 #include "base/allocator/allocator_extension.h" | 9 #include "base/allocator/allocator_extension.h" |
10 #include "base/at_exit.h" | 10 #include "base/at_exit.h" |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
50 #include "third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h" | 50 #include "third_party/tcmalloc/chromium/src/gperftools/malloc_extension.h" |
51 #endif | 51 #endif |
52 | 52 |
53 #if defined(OS_WIN) | 53 #if defined(OS_WIN) |
54 #include <cstring> | 54 #include <cstring> |
55 #include <atlbase.h> | 55 #include <atlbase.h> |
56 #include <atlapp.h> | 56 #include <atlapp.h> |
57 #include <malloc.h> | 57 #include <malloc.h> |
58 #elif defined(OS_MACOSX) | 58 #elif defined(OS_MACOSX) |
59 #include "base/mac/scoped_nsautorelease_pool.h" | 59 #include "base/mac/scoped_nsautorelease_pool.h" |
60 #if !defined(OS_IOS) | |
61 #include "base/mach_ipc_mac.h" | 60 #include "base/mach_ipc_mac.h" |
62 #include "base/system_monitor/system_monitor.h" | 61 #include "base/system_monitor/system_monitor.h" |
63 #include "content/browser/mach_broker_mac.h" | 62 #include "content/browser/mach_broker_mac.h" |
64 #include "content/common/sandbox_init_mac.h" | 63 #include "content/common/sandbox_init_mac.h" |
65 #endif // !OS_IOS | |
66 #endif // OS_WIN | 64 #endif // OS_WIN |
67 | 65 |
68 #if defined(OS_POSIX) | 66 #if defined(OS_POSIX) |
69 #include <signal.h> | 67 #include <signal.h> |
70 | 68 |
71 #include "base/global_descriptors_posix.h" | 69 #include "base/global_descriptors_posix.h" |
72 #include "content/public/common/content_descriptors.h" | 70 #include "content/public/common/content_descriptors.h" |
73 | 71 |
74 #if !defined(OS_MACOSX) | 72 #if !defined(OS_MACOSX) |
75 #include "content/public/common/zygote_fork_delegate_linux.h" | 73 #include "content/public/common/zygote_fork_delegate_linux.h" |
(...skipping 29 matching lines...) Expand all Loading... |
105 g_empty_content_plugin_client = LAZY_INSTANCE_INITIALIZER; | 103 g_empty_content_plugin_client = LAZY_INSTANCE_INITIALIZER; |
106 base::LazyInstance<ContentRendererClient> | 104 base::LazyInstance<ContentRendererClient> |
107 g_empty_content_renderer_client = LAZY_INSTANCE_INITIALIZER; | 105 g_empty_content_renderer_client = LAZY_INSTANCE_INITIALIZER; |
108 base::LazyInstance<ContentUtilityClient> | 106 base::LazyInstance<ContentUtilityClient> |
109 g_empty_content_utility_client = LAZY_INSTANCE_INITIALIZER; | 107 g_empty_content_utility_client = LAZY_INSTANCE_INITIALIZER; |
110 | 108 |
111 #if defined(OS_WIN) | 109 #if defined(OS_WIN) |
112 | 110 |
113 static CAppModule _Module; | 111 static CAppModule _Module; |
114 | 112 |
115 #elif defined(OS_MACOSX) && !defined(OS_IOS) | 113 #elif defined(OS_MACOSX) |
116 | 114 |
117 // Completes the Mach IPC handshake by sending this process' task port to the | 115 // Completes the Mach IPC handshake by sending this process' task port to the |
118 // parent process. The parent is listening on the Mach port given by | 116 // parent process. The parent is listening on the Mach port given by |
119 // |GetMachPortName()|. The task port is used by the parent to get CPU/memory | 117 // |GetMachPortName()|. The task port is used by the parent to get CPU/memory |
120 // stats to display in the task manager. | 118 // stats to display in the task manager. |
121 void SendTaskPortToParentProcess() { | 119 void SendTaskPortToParentProcess() { |
122 const mach_msg_timeout_t kTimeoutMs = 100; | 120 const mach_msg_timeout_t kTimeoutMs = 100; |
123 const int32_t kMessageId = 0; | 121 const int32_t kMessageId = 0; |
124 std::string mach_port_name = MachBroker::GetMachPortName(); | 122 std::string mach_port_name = MachBroker::GetMachPortName(); |
125 | 123 |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
182 // when presenting them to the user), reset the locale for numeric | 180 // when presenting them to the user), reset the locale for numeric |
183 // formatting. | 181 // formatting. |
184 // Note that this is not correct for plugin processes -- they can | 182 // Note that this is not correct for plugin processes -- they can |
185 // surface UI -- but it's likely they get this wrong too so why not. | 183 // surface UI -- but it's likely they get this wrong too so why not. |
186 setlocale(LC_NUMERIC, "C"); | 184 setlocale(LC_NUMERIC, "C"); |
187 #endif | 185 #endif |
188 } | 186 } |
189 | 187 |
190 static base::ProcessId GetBrowserPid(const CommandLine& command_line) { | 188 static base::ProcessId GetBrowserPid(const CommandLine& command_line) { |
191 base::ProcessId browser_pid = base::GetCurrentProcId(); | 189 base::ProcessId browser_pid = base::GetCurrentProcId(); |
192 #if !defined(OS_IOS) | |
193 if (command_line.HasSwitch(switches::kProcessChannelID)) { | 190 if (command_line.HasSwitch(switches::kProcessChannelID)) { |
194 #if defined(OS_WIN) || defined(OS_MACOSX) | 191 #if defined(OS_WIN) || defined(OS_MACOSX) |
195 std::string channel_name = | 192 std::string channel_name = |
196 command_line.GetSwitchValueASCII(switches::kProcessChannelID); | 193 command_line.GetSwitchValueASCII(switches::kProcessChannelID); |
197 | 194 |
198 int browser_pid_int; | 195 int browser_pid_int; |
199 base::StringToInt(channel_name, &browser_pid_int); | 196 base::StringToInt(channel_name, &browser_pid_int); |
200 browser_pid = static_cast<base::ProcessId>(browser_pid_int); | 197 browser_pid = static_cast<base::ProcessId>(browser_pid_int); |
201 DCHECK_NE(browser_pid_int, 0); | 198 DCHECK_NE(browser_pid_int, 0); |
202 #elif defined(OS_ANDROID) | 199 #elif defined(OS_ANDROID) |
203 // On Android, the browser process isn't the parent. A bunch | 200 // On Android, the browser process isn't the parent. A bunch |
204 // of work will be required before callers of this routine will | 201 // of work will be required before callers of this routine will |
205 // get what they want. | 202 // get what they want. |
206 // | 203 // |
207 // Note: On Linux, base::GetParentProcessId() is defined in | 204 // Note: On Linux, base::GetParentProcessId() is defined in |
208 // process_util_linux.cc. Note that *_linux.cc is excluded from | 205 // process_util_linux.cc. Note that *_linux.cc is excluded from |
209 // Android builds but a special exception is made in base.gypi | 206 // Android builds but a special exception is made in base.gypi |
210 // for a few files including process_util_linux.cc. | 207 // for a few files including process_util_linux.cc. |
211 LOG(ERROR) << "GetBrowserPid() not implemented for Android()."; | 208 LOG(ERROR) << "GetBrowserPid() not implemented for Android()."; |
212 #elif defined(OS_POSIX) | 209 #elif defined(OS_POSIX) |
213 // On linux, we're in a process forked from the zygote here; so we need the | 210 // On linux, we're in a process forked from the zygote here; so we need the |
214 // parent's parent process' id. | 211 // parent's parent process' id. |
215 browser_pid = | 212 browser_pid = |
216 base::GetParentProcessId( | 213 base::GetParentProcessId( |
217 base::GetParentProcessId(base::GetCurrentProcId())); | 214 base::GetParentProcessId(base::GetCurrentProcId())); |
218 #endif | 215 #endif |
219 } | 216 } |
220 #endif // !OS_IOS | |
221 return browser_pid; | 217 return browser_pid; |
222 } | 218 } |
223 | 219 |
224 static void InitializeStatsTable(const CommandLine& command_line) { | 220 static void InitializeStatsTable(const CommandLine& command_line) { |
225 // Initialize the Stats Counters table. With this initialized, | 221 // Initialize the Stats Counters table. With this initialized, |
226 // the StatsViewer can be utilized to read counters outside of | 222 // the StatsViewer can be utilized to read counters outside of |
227 // Chrome. These lines can be commented out to effectively turn | 223 // Chrome. These lines can be commented out to effectively turn |
228 // counters 'off'. The table is created and exists for the life | 224 // counters 'off'. The table is created and exists for the life |
229 // of the process. It is not cleaned up. | 225 // of the process. It is not cleaned up. |
230 if (command_line.HasSwitch(switches::kEnableStatsTable)) { | 226 if (command_line.HasSwitch(switches::kEnableStatsTable)) { |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 } | 335 } |
340 | 336 |
341 if (delegate) | 337 if (delegate) |
342 return delegate->RunProcess(process_type, main_params); | 338 return delegate->RunProcess(process_type, main_params); |
343 | 339 |
344 NOTREACHED() << "Unknown zygote process type: " << process_type; | 340 NOTREACHED() << "Unknown zygote process type: " << process_type; |
345 return 1; | 341 return 1; |
346 } | 342 } |
347 #endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) | 343 #endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) |
348 | 344 |
349 #if !defined(OS_IOS) | |
350 // Run the FooMain() for a given process type. | 345 // Run the FooMain() for a given process type. |
351 // If |process_type| is empty, runs BrowserMain(). | 346 // If |process_type| is empty, runs BrowserMain(). |
352 // Returns the exit code for this process. | 347 // Returns the exit code for this process. |
353 int RunNamedProcessTypeMain( | 348 int RunNamedProcessTypeMain( |
354 const std::string& process_type, | 349 const std::string& process_type, |
355 const MainFunctionParams& main_function_params, | 350 const MainFunctionParams& main_function_params, |
356 ContentMainDelegate* delegate) { | 351 ContentMainDelegate* delegate) { |
357 static const MainFunction kMainFunctions[] = { | 352 static const MainFunction kMainFunctions[] = { |
358 { "", BrowserMain }, | 353 { "", BrowserMain }, |
359 { switches::kRendererProcess, RendererMain }, | 354 { switches::kRendererProcess, RendererMain }, |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
392 return RunZygote(main_function_params, delegate); | 387 return RunZygote(main_function_params, delegate); |
393 #endif | 388 #endif |
394 | 389 |
395 // If it's a process we don't know about, the embedder should know. | 390 // If it's a process we don't know about, the embedder should know. |
396 if (delegate) | 391 if (delegate) |
397 return delegate->RunProcess(process_type, main_function_params); | 392 return delegate->RunProcess(process_type, main_function_params); |
398 | 393 |
399 NOTREACHED() << "Unknown process type: " << process_type; | 394 NOTREACHED() << "Unknown process type: " << process_type; |
400 return 1; | 395 return 1; |
401 } | 396 } |
402 #endif // !OS_IOS | |
403 | 397 |
404 class ContentMainRunnerImpl : public ContentMainRunner { | 398 class ContentMainRunnerImpl : public ContentMainRunner { |
405 public: | 399 public: |
406 ContentMainRunnerImpl() | 400 ContentMainRunnerImpl() |
407 : is_initialized_(false), | 401 : is_initialized_(false), |
408 is_shutdown_(false), | 402 is_shutdown_(false), |
409 completed_basic_startup_(false), | 403 completed_basic_startup_(false), |
410 delegate_(NULL) { | 404 delegate_(NULL) { |
411 #if defined(OS_WIN) | 405 #if defined(OS_WIN) |
412 memset(&sandbox_info_, 0, sizeof(sandbox_info_)); | 406 memset(&sandbox_info_, 0, sizeof(sandbox_info_)); |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
471 tracked_objects::TIME_SOURCE_TYPE_TCMALLOC); | 465 tracked_objects::TIME_SOURCE_TYPE_TCMALLOC); |
472 } | 466 } |
473 #endif | 467 #endif |
474 | 468 |
475 // On Android, | 469 // On Android, |
476 // - setlocale() is not supported. | 470 // - setlocale() is not supported. |
477 // - We do not override the signal handlers so that we can get | 471 // - We do not override the signal handlers so that we can get |
478 // stack trace when crashing. | 472 // stack trace when crashing. |
479 // - The ipc_fd is passed through the Java service. | 473 // - The ipc_fd is passed through the Java service. |
480 // Thus, these are all disabled. | 474 // Thus, these are all disabled. |
481 #if !defined(OS_ANDROID) && !defined(OS_IOS) | 475 #if !defined(OS_ANDROID) |
482 // Set C library locale to make sure CommandLine can parse argument values | 476 // Set C library locale to make sure CommandLine can parse argument values |
483 // in correct encoding. | 477 // in correct encoding. |
484 setlocale(LC_ALL, ""); | 478 setlocale(LC_ALL, ""); |
485 | 479 |
486 SetupSignalHandlers(); | 480 SetupSignalHandlers(); |
487 | 481 |
488 base::GlobalDescriptors* g_fds = base::GlobalDescriptors::GetInstance(); | 482 base::GlobalDescriptors* g_fds = base::GlobalDescriptors::GetInstance(); |
489 g_fds->Set(kPrimaryIPCChannel, | 483 g_fds->Set(kPrimaryIPCChannel, |
490 kPrimaryIPCChannel + base::GlobalDescriptors::kBaseDescriptor); | 484 kPrimaryIPCChannel + base::GlobalDescriptors::kBaseDescriptor); |
491 | 485 #endif |
492 // The exit manager is in charge of calling the dtors of singleton objects. | |
493 // On Android, AtExitManager is set up when library is loaded. | |
494 // On iOS, it's set up in main(), which can't call directly through to here. | |
495 exit_manager_.reset(new base::AtExitManager); | |
496 | |
497 #endif // !OS_ANDROID && !OS_IOS | |
498 | 486 |
499 #if defined(OS_LINUX) || defined(OS_OPENBSD) | 487 #if defined(OS_LINUX) || defined(OS_OPENBSD) |
500 g_fds->Set(kCrashDumpSignal, | 488 g_fds->Set(kCrashDumpSignal, |
501 kCrashDumpSignal + base::GlobalDescriptors::kBaseDescriptor); | 489 kCrashDumpSignal + base::GlobalDescriptors::kBaseDescriptor); |
502 #endif | 490 #endif |
503 | 491 |
504 #endif // !OS_WIN | 492 #endif // !OS_WIN |
505 | 493 |
506 is_initialized_ = true; | 494 is_initialized_ = true; |
507 delegate_ = delegate; | 495 delegate_ = delegate; |
508 | 496 |
509 base::EnableTerminationOnHeapCorruption(); | 497 base::EnableTerminationOnHeapCorruption(); |
510 base::EnableTerminationOnOutOfMemory(); | 498 base::EnableTerminationOnOutOfMemory(); |
511 | 499 |
| 500 // On Android, AtExitManager is set up when library is loaded. |
| 501 #if !defined(OS_ANDROID) |
| 502 // The exit manager is in charge of calling the dtors of singleton objects. |
| 503 exit_manager_.reset(new base::AtExitManager); |
| 504 #endif |
| 505 |
512 #if defined(OS_MACOSX) | 506 #if defined(OS_MACOSX) |
513 // We need this pool for all the objects created before we get to the | 507 // We need this pool for all the objects created before we get to the |
514 // event loop, but we don't want to leave them hanging around until the | 508 // event loop, but we don't want to leave them hanging around until the |
515 // app quits. Each "main" needs to flush this pool right before it goes into | 509 // app quits. Each "main" needs to flush this pool right before it goes into |
516 // its main event loop to get rid of the cruft. | 510 // its main event loop to get rid of the cruft. |
517 autorelease_pool_.reset(new base::mac::ScopedNSAutoreleasePool()); | 511 autorelease_pool_.reset(new base::mac::ScopedNSAutoreleasePool()); |
518 #endif | 512 #endif |
519 | 513 |
520 // On Android, the command line is initialized when library is loaded. | 514 // On Android, the command line is initialized when library is loaded. |
521 #if !defined(OS_ANDROID) | 515 #if !defined(OS_ANDROID) |
(...skipping 20 matching lines...) Expand all Loading... |
542 base::RouteStdioToConsole(); | 536 base::RouteStdioToConsole(); |
543 #endif | 537 #endif |
544 | 538 |
545 // Enable startup tracing asap to avoid early TRACE_EVENT calls being | 539 // Enable startup tracing asap to avoid early TRACE_EVENT calls being |
546 // ignored. | 540 // ignored. |
547 if (command_line.HasSwitch(switches::kTraceStartup)) { | 541 if (command_line.HasSwitch(switches::kTraceStartup)) { |
548 base::debug::TraceLog::GetInstance()->SetEnabled( | 542 base::debug::TraceLog::GetInstance()->SetEnabled( |
549 command_line.GetSwitchValueASCII(switches::kTraceStartup)); | 543 command_line.GetSwitchValueASCII(switches::kTraceStartup)); |
550 } | 544 } |
551 | 545 |
552 #if defined(OS_MACOSX) && !defined(OS_IOS) | 546 #if defined(OS_MACOSX) |
553 // We need to allocate the IO Ports before the Sandbox is initialized or | 547 // We need to allocate the IO Ports before the Sandbox is initialized or |
554 // the first instance of SystemMonitor is created. | 548 // the first instance of SystemMonitor is created. |
555 // It's important not to allocate the ports for processes which don't | 549 // It's important not to allocate the ports for processes which don't |
556 // register with the system monitor - see crbug.com/88867. | 550 // register with the system monitor - see crbug.com/88867. |
557 if (process_type.empty() || | 551 if (process_type.empty() || |
558 process_type == switches::kPluginProcess || | 552 process_type == switches::kPluginProcess || |
559 process_type == switches::kRendererProcess || | 553 process_type == switches::kRendererProcess || |
560 process_type == switches::kUtilityProcess || | 554 process_type == switches::kUtilityProcess || |
561 process_type == switches::kWorkerProcess || | 555 process_type == switches::kWorkerProcess || |
562 (delegate && | 556 (delegate && |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
614 if (command_line.HasSwitch(switches::kUserAgent)) { | 608 if (command_line.HasSwitch(switches::kUserAgent)) { |
615 webkit_glue::SetUserAgent( | 609 webkit_glue::SetUserAgent( |
616 command_line.GetSwitchValueASCII(switches::kUserAgent), true); | 610 command_line.GetSwitchValueASCII(switches::kUserAgent), true); |
617 } | 611 } |
618 | 612 |
619 if (!process_type.empty()) | 613 if (!process_type.empty()) |
620 CommonSubprocessInit(process_type); | 614 CommonSubprocessInit(process_type); |
621 | 615 |
622 #if defined(OS_WIN) | 616 #if defined(OS_WIN) |
623 CHECK(InitializeSandbox(sandbox_info)); | 617 CHECK(InitializeSandbox(sandbox_info)); |
624 #elif defined(OS_MACOSX) && !defined(OS_IOS) | 618 #elif defined(OS_MACOSX) |
625 if (process_type == switches::kRendererProcess || | 619 if (process_type == switches::kRendererProcess || |
626 process_type == switches::kPpapiPluginProcess || | 620 process_type == switches::kPpapiPluginProcess || |
627 (delegate && delegate->DelaySandboxInitialization(process_type))) { | 621 (delegate && delegate->DelaySandboxInitialization(process_type))) { |
628 // On OS X the renderer sandbox needs to be initialized later in the | 622 // On OS X the renderer sandbox needs to be initialized later in the |
629 // startup sequence in RendererMainPlatformDelegate::EnableSandbox(). | 623 // startup sequence in RendererMainPlatformDelegate::EnableSandbox(). |
630 } else { | 624 } else { |
631 CHECK(InitializeSandbox()); | 625 CHECK(InitializeSandbox()); |
632 } | 626 } |
633 #endif | 627 #endif |
634 | 628 |
635 if (delegate) | 629 if (delegate) |
636 delegate->SandboxInitialized(process_type); | 630 delegate->SandboxInitialized(process_type); |
637 | 631 |
638 #if defined(OS_POSIX) && !defined(OS_IOS) | 632 #if defined(OS_POSIX) |
639 SetProcessTitleFromCommandLine(argv); | 633 SetProcessTitleFromCommandLine(argv); |
640 #endif | 634 #endif |
641 | 635 |
642 // Return -1 to indicate no early termination. | 636 // Return -1 to indicate no early termination. |
643 return -1; | 637 return -1; |
644 } | 638 } |
645 | 639 |
646 virtual int Run() OVERRIDE { | 640 virtual int Run() OVERRIDE { |
647 DCHECK(is_initialized_); | 641 DCHECK(is_initialized_); |
648 DCHECK(!is_shutdown_); | 642 DCHECK(!is_shutdown_); |
649 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); | 643 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); |
650 std::string process_type = | 644 std::string process_type = |
651 command_line.GetSwitchValueASCII(switches::kProcessType); | 645 command_line.GetSwitchValueASCII(switches::kProcessType); |
652 | 646 |
653 MainFunctionParams main_params(command_line); | 647 MainFunctionParams main_params(command_line); |
654 #if defined(OS_WIN) | 648 #if defined(OS_WIN) |
655 main_params.sandbox_info = &sandbox_info_; | 649 main_params.sandbox_info = &sandbox_info_; |
656 #elif defined(OS_MACOSX) | 650 #elif defined(OS_MACOSX) |
657 main_params.autorelease_pool = autorelease_pool_.get(); | 651 main_params.autorelease_pool = autorelease_pool_.get(); |
658 #endif | 652 #endif |
659 | 653 |
660 #if !defined(OS_IOS) | |
661 return RunNamedProcessTypeMain(process_type, main_params, delegate_); | 654 return RunNamedProcessTypeMain(process_type, main_params, delegate_); |
662 #else | |
663 return 1; | |
664 #endif | |
665 } | 655 } |
666 | 656 |
667 virtual void Shutdown() OVERRIDE { | 657 virtual void Shutdown() OVERRIDE { |
668 DCHECK(is_initialized_); | 658 DCHECK(is_initialized_); |
669 DCHECK(!is_shutdown_); | 659 DCHECK(!is_shutdown_); |
670 | 660 |
671 if (completed_basic_startup_ && delegate_) { | 661 if (completed_basic_startup_ && delegate_) { |
672 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); | 662 const CommandLine& command_line = *CommandLine::ForCurrentProcess(); |
673 std::string process_type = | 663 std::string process_type = |
674 command_line.GetSwitchValueASCII(switches::kProcessType); | 664 command_line.GetSwitchValueASCII(switches::kProcessType); |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
719 | 709 |
720 DISALLOW_COPY_AND_ASSIGN(ContentMainRunnerImpl); | 710 DISALLOW_COPY_AND_ASSIGN(ContentMainRunnerImpl); |
721 }; | 711 }; |
722 | 712 |
723 // static | 713 // static |
724 ContentMainRunner* ContentMainRunner::Create() { | 714 ContentMainRunner* ContentMainRunner::Create() { |
725 return new ContentMainRunnerImpl(); | 715 return new ContentMainRunnerImpl(); |
726 } | 716 } |
727 | 717 |
728 } // namespace content | 718 } // namespace content |
OLD | NEW |