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

Unified Diff: chrome/browser/ui/panels/base_panel_browser_test.cc

Issue 2263863002: Remove implementation of Panels on OSes other than ChromeOS. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: CR feedback Created 4 years, 4 months 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/ui/panels/base_panel_browser_test.cc
diff --git a/chrome/browser/ui/panels/base_panel_browser_test.cc b/chrome/browser/ui/panels/base_panel_browser_test.cc
deleted file mode 100644
index 10ffeb0e69ff0bda18ed3068d11141ee92f31b23..0000000000000000000000000000000000000000
--- a/chrome/browser/ui/panels/base_panel_browser_test.cc
+++ /dev/null
@@ -1,590 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/panels/base_panel_browser_test.h"
-
-#include <string.h>
-
-#include "base/bind.h"
-#include "base/command_line.h"
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "base/message_loop/message_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "build/build_config.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/panels/detached_panel_collection.h"
-#include "chrome/browser/ui/panels/native_panel.h"
-#include "chrome/browser/ui/panels/panel_collection.h"
-#include "chrome/browser/ui/panels/panel_mouse_watcher.h"
-#include "chrome/browser/ui/panels/stacked_panel_collection.h"
-#include "chrome/browser/ui/panels/test_panel_active_state_observer.h"
-#include "chrome/browser/ui/panels/test_panel_mouse_watcher.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/test/base/interactive_test_utils.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/sync/api/string_ordinal.h"
-#include "content/public/browser/notification_service.h"
-#include "content/public/common/url_constants.h"
-#include "content/public/test/web_contents_tester.h"
-#include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_system.h"
-#include "extensions/browser/install_flag.h"
-#include "extensions/common/manifest_constants.h"
-
-#if defined(OS_LINUX)
-#include "ui/base/x/x11_util.h"
-#endif
-
-#if defined(OS_MACOSX)
-#include "base/mac/scoped_nsautorelease_pool.h"
-#include "chrome/browser/ui/cocoa/run_loop_testing.h"
-#endif
-
-using content::WebContentsTester;
-using extensions::Extension;
-
-namespace {
-
-const gfx::Rect kTestingPrimaryDisplayArea = gfx::Rect(0, 0, 800, 600);
-const gfx::Rect kTestingPrimaryWorkArea = gfx::Rect(0, 0, 800, 580);
-
-struct MockDesktopBar {
- bool auto_hiding_enabled;
- DisplaySettingsProvider::DesktopBarVisibility visibility;
- int thickness;
-};
-
-class MockDisplaySettingsProviderImpl :
- public BasePanelBrowserTest::MockDisplaySettingsProvider {
- public:
- MockDisplaySettingsProviderImpl();
- ~MockDisplaySettingsProviderImpl() override {}
-
- // Overridden from DisplaySettingsProvider:
- gfx::Rect GetPrimaryDisplayArea() const override;
- gfx::Rect GetPrimaryWorkArea() const override;
- gfx::Rect GetDisplayAreaMatching(const gfx::Rect& bounds) const override;
- gfx::Rect GetWorkAreaMatching(const gfx::Rect& bounds) const override;
- bool IsAutoHidingDesktopBarEnabled(DesktopBarAlignment alignment) override;
- int GetDesktopBarThickness(DesktopBarAlignment alignment) const override;
- DesktopBarVisibility GetDesktopBarVisibility(
- DesktopBarAlignment alignment) const override;
- bool IsFullScreen() override;
-
- // Overridden from MockDisplaySettingsProvider:
- void SetPrimaryDisplay(const gfx::Rect& display_area,
- const gfx::Rect& work_area) override;
- void SetSecondaryDisplay(const gfx::Rect& display_area,
- const gfx::Rect& work_area) override;
- void EnableAutoHidingDesktopBar(DesktopBarAlignment alignment,
- bool enabled,
- int thickness) override;
- void SetDesktopBarVisibility(DesktopBarAlignment alignment,
- DesktopBarVisibility visibility) override;
- void SetDesktopBarThickness(DesktopBarAlignment alignment,
- int thickness) override;
- void EnableFullScreenMode(bool enabled) override;
-
- private:
- gfx::Rect primary_display_area_;
- gfx::Rect primary_work_area_;
- gfx::Rect secondary_display_area_;
- gfx::Rect secondary_work_area_;
- MockDesktopBar mock_desktop_bars[3];
- bool full_screen_enabled_;
-
- DISALLOW_COPY_AND_ASSIGN(MockDisplaySettingsProviderImpl);
-};
-
-
-MockDisplaySettingsProviderImpl::MockDisplaySettingsProviderImpl()
- : full_screen_enabled_(false) {
- memset(mock_desktop_bars, 0, sizeof(mock_desktop_bars));
-}
-
-gfx::Rect MockDisplaySettingsProviderImpl::GetPrimaryDisplayArea() const {
- return primary_display_area_;
-}
-
-gfx::Rect MockDisplaySettingsProviderImpl::GetPrimaryWorkArea() const {
- return primary_work_area_;
-}
-
-gfx::Rect MockDisplaySettingsProviderImpl::GetDisplayAreaMatching(
- const gfx::Rect& bounds) const {
- if (secondary_display_area_.IsEmpty())
- return primary_display_area_;
-
- gfx::Rect primary_intersection =
- gfx::IntersectRects(bounds, primary_display_area_);
- int primary_intersection_size =
- primary_intersection.width() * primary_intersection.height();
-
- gfx::Rect secondary_intersection =
- gfx::IntersectRects(bounds, secondary_display_area_);
- int secondary_intersection_size =
- secondary_intersection.width() * secondary_intersection.height();
-
- return primary_intersection_size >= secondary_intersection_size ?
- primary_display_area_ : secondary_display_area_;
-}
-
-gfx::Rect MockDisplaySettingsProviderImpl::GetWorkAreaMatching(
- const gfx::Rect& bounds) const {
- if (secondary_work_area_.IsEmpty())
- return primary_work_area_;
-
- gfx::Rect primary_intersection =
- gfx::IntersectRects(bounds, primary_work_area_);
- int primary_intersection_size =
- primary_intersection.width() * primary_intersection.height();
-
- gfx::Rect secondary_intersection =
- gfx::IntersectRects(bounds, secondary_work_area_);
- int secondary_intersection_size =
- secondary_intersection.width() * secondary_intersection.height();
-
- return primary_intersection_size >= secondary_intersection_size ?
- primary_work_area_ : secondary_work_area_;
-}
-
-bool MockDisplaySettingsProviderImpl::IsAutoHidingDesktopBarEnabled(
- DesktopBarAlignment alignment) {
- return mock_desktop_bars[static_cast<int>(alignment)].auto_hiding_enabled;
-}
-
-int MockDisplaySettingsProviderImpl::GetDesktopBarThickness(
- DesktopBarAlignment alignment) const {
- return mock_desktop_bars[static_cast<int>(alignment)].thickness;
-}
-
-DisplaySettingsProvider::DesktopBarVisibility
-MockDisplaySettingsProviderImpl::GetDesktopBarVisibility(
- DesktopBarAlignment alignment) const {
- return mock_desktop_bars[static_cast<int>(alignment)].visibility;
-}
-
-bool MockDisplaySettingsProviderImpl::IsFullScreen() {
- return full_screen_enabled_;
-}
-
-void MockDisplaySettingsProviderImpl::EnableAutoHidingDesktopBar(
- DesktopBarAlignment alignment, bool enabled, int thickness) {
- MockDesktopBar* bar = &(mock_desktop_bars[static_cast<int>(alignment)]);
- bar->auto_hiding_enabled = enabled;
- bar->thickness = thickness;
-}
-
-void MockDisplaySettingsProviderImpl::SetPrimaryDisplay(
- const gfx::Rect& display_area, const gfx::Rect& work_area) {
- DCHECK(display_area.Contains(work_area));
- primary_display_area_ = display_area;
- primary_work_area_ = work_area;
- OnDisplaySettingsChanged();
-}
-
-void MockDisplaySettingsProviderImpl::SetSecondaryDisplay(
- const gfx::Rect& display_area, const gfx::Rect& work_area) {
- DCHECK(display_area.Contains(work_area));
- secondary_display_area_ = display_area;
- secondary_work_area_ = work_area;
- OnDisplaySettingsChanged();
-}
-
-void MockDisplaySettingsProviderImpl::SetDesktopBarVisibility(
- DesktopBarAlignment alignment, DesktopBarVisibility visibility) {
- MockDesktopBar* bar = &(mock_desktop_bars[static_cast<int>(alignment)]);
- if (!bar->auto_hiding_enabled)
- return;
- if (visibility == bar->visibility)
- return;
- bar->visibility = visibility;
- FOR_EACH_OBSERVER(
- DesktopBarObserver,
- desktop_bar_observers(),
- OnAutoHidingDesktopBarVisibilityChanged(alignment, visibility));
-}
-
-void MockDisplaySettingsProviderImpl::SetDesktopBarThickness(
- DesktopBarAlignment alignment, int thickness) {
- MockDesktopBar* bar = &(mock_desktop_bars[static_cast<int>(alignment)]);
- if (!bar->auto_hiding_enabled)
- return;
- if (thickness == bar->thickness)
- return;
- bar->thickness = thickness;
- FOR_EACH_OBSERVER(
- DesktopBarObserver,
- desktop_bar_observers(),
- OnAutoHidingDesktopBarThicknessChanged(alignment, thickness));
-}
-
-void MockDisplaySettingsProviderImpl::EnableFullScreenMode(bool enabled) {
- full_screen_enabled_ = enabled;
- CheckFullScreenMode(PERFORM_FULLSCREEN_CHECK);
-}
-
-} // namespace
-
-const base::FilePath::CharType* BasePanelBrowserTest::kTestDir =
- FILE_PATH_LITERAL("panels");
-
-BasePanelBrowserTest::BasePanelBrowserTest()
- : InProcessBrowserTest(),
- mock_display_settings_enabled_(true) {
-}
-
-BasePanelBrowserTest::~BasePanelBrowserTest() {
-}
-
-void BasePanelBrowserTest::SetUpCommandLine(base::CommandLine* command_line) {
- command_line->AppendSwitch(switches::kEnablePanels);
-}
-
-void BasePanelBrowserTest::SetUpOnMainThread() {
- InProcessBrowserTest::SetUpOnMainThread();
-
- // Setup the work area and desktop bar so that we have consistent testing
- // environment for all panel related tests.
- if (mock_display_settings_enabled_) {
- mock_display_settings_provider_ = new MockDisplaySettingsProviderImpl();
- mock_display_settings_provider_->SetPrimaryDisplay(
- kTestingPrimaryDisplayArea, kTestingPrimaryWorkArea);
- PanelManager::SetDisplaySettingsProviderForTesting(
- mock_display_settings_provider_);
- }
-
- PanelManager* panel_manager = PanelManager::GetInstance();
- panel_manager->enable_auto_sizing(false);
-
- PanelManager::shorten_time_intervals_for_testing();
-
- // Simulate the mouse movement so that tests are not affected by actual mouse
- // events.
- PanelMouseWatcher* mouse_watcher = new TestPanelMouseWatcher();
- panel_manager->SetMouseWatcherForTesting(mouse_watcher);
-
- // This is needed so the subsequently created panels can be activated.
- // On a Mac, it transforms background-only test process into foreground one.
- ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
-}
-
-void BasePanelBrowserTest::WaitForPanelActiveState(
- Panel* panel, ActiveState expected_state) {
- DCHECK(expected_state == SHOW_AS_ACTIVE ||
- expected_state == SHOW_AS_INACTIVE);
-
-#if defined(OS_MACOSX)
- std::unique_ptr<NativePanelTesting> panel_testing(
- CreateNativePanelTesting(panel));
- ASSERT_TRUE(panel_testing->EnsureApplicationRunOnForeground()) <<
- "Failed to bring application to foreground. Bail out.";
-#endif
-
- PanelActiveStateObserver signal(panel, expected_state == SHOW_AS_ACTIVE);
- signal.Wait();
-}
-
-void BasePanelBrowserTest::WaitForBoundsAnimationFinished(Panel* panel) {
- std::unique_ptr<NativePanelTesting> panel_testing(
- CreateNativePanelTesting(panel));
- // Sometimes there are several animations in sequence due to content
- // auto resizing. Wait for all animations to finish.
- while (panel_testing->IsAnimatingBounds()) {
- content::WindowedNotificationObserver signal(
- chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED,
- content::Source<Panel>(panel));
- if (!panel_testing->IsAnimatingBounds())
- return;
- signal.Wait();
- }
-}
-
-BasePanelBrowserTest::CreatePanelParams::CreatePanelParams(
- const std::string& name,
- const gfx::Rect& bounds,
- ActiveState show_flag)
- : name(name),
- bounds(bounds),
- show_flag(show_flag),
- wait_for_fully_created(true),
- expected_active_state(show_flag),
- create_mode(PanelManager::CREATE_AS_DOCKED),
- profile(NULL) {
-}
-
-Panel* BasePanelBrowserTest::CreatePanelWithParams(
- const CreatePanelParams& params) {
-#if defined(OS_MACOSX)
- // Opening panels on a Mac causes NSWindowController of the Panel window
- // to be autoreleased. We need a pool drained after it's done so the test
- // can close correctly. The NSWindowController of the Panel window controls
- // lifetime of the Panel object so we want to release it as soon as
- // possible. In real Chrome, this is done by message pump.
- // On non-Mac platform, this is an empty class.
- base::mac::ScopedNSAutoreleasePool autorelease_pool;
-#endif
-
- content::WindowedNotificationObserver observer(
- content::NOTIFICATION_LOAD_STOP,
- content::NotificationService::AllSources());
-
- PanelManager* manager = PanelManager::GetInstance();
- Panel* panel = manager->CreatePanel(
- params.name,
- params.profile ? params.profile : browser()->profile(),
- params.url,
- nullptr,
- params.bounds,
- params.create_mode);
-
- if (!params.url.is_empty())
- observer.Wait();
-
- if (!manager->auto_sizing_enabled() ||
- params.bounds.width() || params.bounds.height()) {
- EXPECT_FALSE(panel->auto_resizable());
- } else {
- EXPECT_TRUE(panel->auto_resizable());
- }
-
- if (params.show_flag == SHOW_AS_ACTIVE) {
- panel->Show();
- } else {
- panel->ShowInactive();
- }
-
- if (params.wait_for_fully_created) {
- base::MessageLoopForUI::current()->RunUntilIdle();
-
-#if defined(OS_LINUX) && defined(USE_X11)
- // On bots, we might have a simple window manager which always activates new
- // windows, and can't always deactivate them. Re-activate the main tabbed
- // browser to "deactivate" the newly created panel.
- if (params.expected_active_state == SHOW_AS_INACTIVE &&
- ui::GuessWindowManager() == ui::WM_ICE_WM) {
- // Wait for new panel to become active before deactivating to ensure
- // the activated notification is consumed before we wait for the panel
- // to become inactive.
- WaitForPanelActiveState(panel, SHOW_AS_ACTIVE);
- browser()->window()->Activate();
- }
-#endif
- // More waiting, because gaining or losing focus may require inter-process
- // asynchronous communication, and it is not enough to just run the local
- // message loop to make sure this activity has completed.
- WaitForPanelActiveState(panel, params.expected_active_state);
-
- // Wait for the bounds animations on creation to finish.
- WaitForBoundsAnimationFinished(panel);
- }
-
- return panel;
-}
-
-Panel* BasePanelBrowserTest::CreatePanelWithBounds(
- const std::string& panel_name, const gfx::Rect& bounds) {
- CreatePanelParams params(panel_name, bounds, SHOW_AS_ACTIVE);
- return CreatePanelWithParams(params);
-}
-
-Panel* BasePanelBrowserTest::CreatePanel(const std::string& panel_name) {
- CreatePanelParams params(panel_name, gfx::Rect(), SHOW_AS_ACTIVE);
- return CreatePanelWithParams(params);
-}
-
-Panel* BasePanelBrowserTest::CreateDockedPanel(const std::string& name,
- const gfx::Rect& bounds) {
- Panel* panel = CreatePanelWithBounds(name, bounds);
- EXPECT_EQ(PanelCollection::DOCKED, panel->collection()->type());
- return panel;
-}
-
-Panel* BasePanelBrowserTest::CreateDetachedPanel(const std::string& name,
- const gfx::Rect& bounds) {
- Panel* panel = CreatePanelWithBounds(name, bounds);
- PanelManager* panel_manager = panel->manager();
- panel_manager->MovePanelToCollection(panel,
- panel_manager->detached_collection(),
- PanelCollection::DEFAULT_POSITION);
- EXPECT_EQ(PanelCollection::DETACHED, panel->collection()->type());
- // The panel is first created as docked panel, which ignores the specified
- // origin in |bounds|. We need to reposition the panel after it becomes
- // detached.
- panel->SetPanelBounds(bounds);
- WaitForBoundsAnimationFinished(panel);
- return panel;
-}
-
-Panel* BasePanelBrowserTest::CreateStackedPanel(const std::string& name,
- const gfx::Rect& bounds,
- StackedPanelCollection* stack) {
- Panel* panel = CreateDetachedPanel(name, bounds);
- panel->manager()->MovePanelToCollection(
- panel,
- stack,
- static_cast<PanelCollection::PositioningMask>(
- PanelCollection::DEFAULT_POSITION |
- PanelCollection::COLLAPSE_TO_FIT));
- EXPECT_EQ(PanelCollection::STACKED, panel->collection()->type());
- WaitForBoundsAnimationFinished(panel);
- return panel;
-}
-
-Panel* BasePanelBrowserTest::CreateInactivePanel(const std::string& name) {
- // Create an active panel first, instead of inactive panel. This is because
- // certain window managers on Linux, like icewm, will always activate the
- // new window.
- Panel* panel = CreatePanel(name);
-
- DeactivatePanel(panel);
- WaitForPanelActiveState(panel, SHOW_AS_INACTIVE);
-
- return panel;
-}
-
-Panel* BasePanelBrowserTest::CreateInactiveDockedPanel(
- const std::string& name, const gfx::Rect& bounds) {
- // Create an active panel first, instead of inactive panel. This is because
- // certain window managers on Linux, like icewm, will always activate the
- // new window.
- Panel* panel = CreateDockedPanel(name, bounds);
-
- DeactivatePanel(panel);
- WaitForPanelActiveState(panel, SHOW_AS_INACTIVE);
-
- return panel;
-}
-
-Panel* BasePanelBrowserTest::CreateInactiveDetachedPanel(
- const std::string& name, const gfx::Rect& bounds) {
- // Create an active panel first, instead of inactive panel. This is because
- // certain window managers on Linux, like icewm, will always activate the
- // new window.
- Panel* panel = CreateDetachedPanel(name, bounds);
-
- DeactivatePanel(panel);
- WaitForPanelActiveState(panel, SHOW_AS_INACTIVE);
-
- return panel;
-}
-
-void BasePanelBrowserTest::ActivatePanel(Panel* panel) {
- // For certain window managers on Linux, the window activation/deactivation
- // signals might not be sent. To work around this, we explicitly deactivate
- // all other panels first.
-#if defined(OS_LINUX)
- std::vector<Panel*> panels = PanelManager::GetInstance()->panels();
- for (std::vector<Panel*>::const_iterator iter = panels.begin();
- iter != panels.end(); ++iter) {
- Panel* current_panel = *iter;
- if (panel != current_panel)
- current_panel->Deactivate();
- }
-#endif
-
- panel->Activate();
-}
-
-void BasePanelBrowserTest::DeactivatePanel(Panel* panel) {
-#if defined(OS_LINUX)
- // For certain window managers on Linux, like icewm, panel activation and
- // deactivation notification might not get tiggered when non-panel window is
- // activated or deactivated. So we deactivate the panel directly.
- panel->Deactivate();
-#else
- // Make the panel lose focus by activating the browser window. This is
- // because:
- // 1) On Windows, deactivating the panel window might cause the application
- // to lose the foreground status. When this occurs, trying to activate
- // the panel window again will not be allowed by the system.
- // 2) On MacOS, deactivating a window is not supported by Cocoa.
- browser()->window()->Activate();
-#endif
-}
-
-// static
-NativePanelTesting* BasePanelBrowserTest::CreateNativePanelTesting(
- Panel* panel) {
- return panel->native_panel()->CreateNativePanelTesting();
-}
-
-scoped_refptr<Extension> BasePanelBrowserTest::CreateExtension(
- const base::FilePath::StringType& path,
- extensions::Manifest::Location location,
- const base::DictionaryValue& extra_value) {
- extensions::ExtensionPrefs* extension_prefs =
- extensions::ExtensionPrefs::Get(browser()->profile());
- base::FilePath full_path = extension_prefs->install_directory().Append(path);
-
- std::unique_ptr<base::DictionaryValue> input_value(extra_value.DeepCopy());
- input_value->SetString(extensions::manifest_keys::kVersion, "1.0.0.0");
- input_value->SetString(extensions::manifest_keys::kName, "Sample Extension");
-
- std::string error;
- scoped_refptr<Extension> extension = Extension::Create(
- full_path, location, *input_value, Extension::NO_FLAGS, &error);
- EXPECT_TRUE(extension.get());
- EXPECT_STREQ("", error.c_str());
- extensions::ExtensionSystem::Get(
- browser()->profile())->extension_service()->OnExtensionInstalled(
- extension.get(),
- syncer::StringOrdinal(),
- extensions::kInstallFlagInstallImmediately);
- return extension;
-}
-
-void BasePanelBrowserTest::CloseWindowAndWait(Panel* panel) {
- // Closing a panel may involve several async tasks. Need to use
- // message pump and wait for the notification.
- PanelManager* manager = PanelManager::GetInstance();
- int panel_count = manager->num_panels();
- content::WindowedNotificationObserver signal(
- chrome::NOTIFICATION_PANEL_CLOSED,
- content::Source<Panel>(panel));
- panel->Close();
- signal.Wait();
- // Now we have one less panel.
- EXPECT_EQ(panel_count - 1, manager->num_panels());
-
-#if defined(OS_MACOSX)
- // Mac window controllers may be autoreleased, and in the non-test
- // environment, may actually depend on the autorelease pool being recycled
- // with the run loop in order to perform important work. Replicate this in
- // the test environment.
- AutoreleasePool()->Recycle();
-
- // Make sure that everything has a chance to run.
- chrome::testing::NSRunLoopRunAllPending();
-#endif // OS_MACOSX
-}
-
-void BasePanelBrowserTest::MoveMouseAndWaitForExpansionStateChange(
- Panel* panel,
- const gfx::Point& position) {
- content::WindowedNotificationObserver signal(
- chrome::NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE,
- content::Source<Panel>(panel));
- MoveMouse(position);
- signal.Wait();
-}
-
-void BasePanelBrowserTest::MoveMouse(const gfx::Point& position) {
- PanelManager::GetInstance()->mouse_watcher()->NotifyMouseMovement(position);
-}
-
-std::string BasePanelBrowserTest::MakePanelName(int index) {
- std::string panel_name("Panel");
- return panel_name + base::IntToString(index);
-}
-
-bool BasePanelBrowserTest::WmSupportWindowActivation() {
- return true;
-}
« no previous file with comments | « chrome/browser/ui/panels/base_panel_browser_test.h ('k') | chrome/browser/ui/panels/detached_panel_browsertest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698