| Index: content/renderer/mus/compositor_mus_connection_unittest.cc
|
| diff --git a/content/renderer/mus/compositor_mus_connection_unittest.cc b/content/renderer/mus/compositor_mus_connection_unittest.cc
|
| deleted file mode 100644
|
| index d4d93b313af8c3530c897c88a9d7624a006311a8..0000000000000000000000000000000000000000
|
| --- a/content/renderer/mus/compositor_mus_connection_unittest.cc
|
| +++ /dev/null
|
| @@ -1,502 +0,0 @@
|
| -// Copyright 2016 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 "content/renderer/mus/compositor_mus_connection.h"
|
| -
|
| -#include <memory>
|
| -
|
| -#include "base/macros.h"
|
| -#include "base/memory/ref_counted.h"
|
| -#include "base/test/test_simple_task_runner.h"
|
| -#include "base/time/time.h"
|
| -#include "content/common/input/input_event_ack.h"
|
| -#include "content/common/input/input_event_ack_state.h"
|
| -#include "content/public/test/mock_render_thread.h"
|
| -#include "content/renderer/input/input_handler_manager.h"
|
| -#include "content/renderer/input/input_handler_manager_client.h"
|
| -#include "content/renderer/input/render_widget_input_handler.h"
|
| -#include "content/renderer/mus/render_widget_mus_connection.h"
|
| -#include "content/renderer/render_widget.h"
|
| -#include "content/test/fake_compositor_dependencies.h"
|
| -#include "mojo/public/cpp/bindings/interface_request.h"
|
| -#include "services/ui/public/cpp/tests/test_window.h"
|
| -#include "testing/gtest/include/gtest/gtest.h"
|
| -#include "third_party/WebKit/public/platform/scheduler/test/fake_renderer_scheduler.h"
|
| -#include "ui/events/blink/did_overscroll_params.h"
|
| -#include "ui/events/event_utils.h"
|
| -#include "ui/events/mojo/event.mojom.h"
|
| -#include "ui/events/mojo/event_constants.mojom.h"
|
| -#include "ui/events/mojo/keyboard_codes.mojom.h"
|
| -
|
| -using ui::mojom::EventResult;
|
| -
|
| -namespace content {
|
| -
|
| -namespace {
|
| -
|
| -// Wrapper for the callback provided to
|
| -// CompositorMusConnection:OnWindowInputEvent. This tracks whether the it was
|
| -// called, along with the result.
|
| -class TestCallback : public base::RefCounted<TestCallback> {
|
| - public:
|
| - TestCallback() : called_(false), result_(EventResult::UNHANDLED) {}
|
| -
|
| - bool called() { return called_; }
|
| - EventResult result() { return result_; }
|
| -
|
| - void ResultCallback(EventResult result) {
|
| - called_ = true;
|
| - result_ = result;
|
| - }
|
| -
|
| - private:
|
| - friend class base::RefCounted<TestCallback>;
|
| -
|
| - ~TestCallback() {}
|
| -
|
| - bool called_;
|
| - EventResult result_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(TestCallback);
|
| -};
|
| -
|
| -// Allows for overriding the behaviour of HandleInputEvent, to simulate input
|
| -// handlers which consume events before they are sent to the renderer.
|
| -class TestInputHandlerManager : public InputHandlerManager {
|
| - public:
|
| - TestInputHandlerManager(
|
| - const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
|
| - InputHandlerManagerClient* client,
|
| - blink::scheduler::RendererScheduler* renderer_scheduler)
|
| - : InputHandlerManager(task_runner, client, nullptr, renderer_scheduler),
|
| - override_result_(false),
|
| - result_(InputEventAckState::INPUT_EVENT_ACK_STATE_UNKNOWN) {}
|
| - ~TestInputHandlerManager() override {}
|
| -
|
| - // Stops overriding the behaviour of HandleInputEvent
|
| - void ClearHandleInputEventOverride();
|
| -
|
| - // Overrides the behaviour of HandleInputEvent, returing |result|.
|
| - void SetHandleInputEventResult(InputEventAckState result);
|
| -
|
| - // InputHandlerManager:
|
| - void HandleInputEvent(int routing_id,
|
| - blink::WebScopedInputEvent input_event,
|
| - const ui::LatencyInfo& latency_info,
|
| - const InputEventAckStateCallback& callback) override;
|
| -
|
| - private:
|
| - // If true InputHandlerManager::HandleInputEvent is not called.
|
| - bool override_result_;
|
| -
|
| - // The result to return in HandleInputEvent if |override_result_|.
|
| - InputEventAckState result_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(TestInputHandlerManager);
|
| -};
|
| -
|
| -void TestInputHandlerManager::ClearHandleInputEventOverride() {
|
| - override_result_ = false;
|
| -}
|
| -
|
| -void TestInputHandlerManager::SetHandleInputEventResult(
|
| - InputEventAckState result) {
|
| - override_result_ = true;
|
| - result_ = result;
|
| -}
|
| -
|
| -void TestInputHandlerManager::HandleInputEvent(
|
| - int routing_id,
|
| - blink::WebScopedInputEvent input_event,
|
| - const ui::LatencyInfo& latency_info,
|
| - const InputEventAckStateCallback& callback) {
|
| - if (override_result_) {
|
| - callback.Run(result_, std::move(input_event), latency_info, nullptr);
|
| - return;
|
| - }
|
| - InputHandlerManager::HandleInputEvent(routing_id, std::move(input_event),
|
| - latency_info, callback);
|
| -}
|
| -
|
| -// Empty implementation of InputHandlerManagerClient.
|
| -class TestInputHandlerManagerClient : public InputHandlerManagerClient {
|
| - public:
|
| - TestInputHandlerManagerClient() {}
|
| - ~TestInputHandlerManagerClient() override{};
|
| -
|
| - // InputHandlerManagerClient:
|
| - void SetInputHandlerManager(
|
| - InputHandlerManager* input_handler_manager) override {}
|
| - void RegisterRoutingID(int routing_id) override {}
|
| - void UnregisterRoutingID(int routing_id) override {}
|
| - void DidOverscroll(int routing_id,
|
| - const ui::DidOverscrollParams& params) override {}
|
| - void DidStopFlinging(int routing_id) override {}
|
| - void DispatchNonBlockingEventToMainThread(
|
| - int routing_id,
|
| - blink::WebScopedInputEvent event,
|
| - const ui::LatencyInfo& latency_info) override {}
|
| -
|
| - void NotifyInputEventHandled(int routing_id,
|
| - blink::WebInputEvent::Type type,
|
| - InputEventAckState ack_result) override {}
|
| - void ProcessRafAlignedInput(int routing_id) override {}
|
| -
|
| - private:
|
| - DISALLOW_COPY_AND_ASSIGN(TestInputHandlerManagerClient);
|
| -};
|
| -
|
| -// Implementation of RenderWidget for testing, performs no initialization.
|
| -class TestRenderWidget : public RenderWidget {
|
| - public:
|
| - explicit TestRenderWidget(CompositorDependencies* compositor_deps)
|
| - : RenderWidget(1,
|
| - compositor_deps,
|
| - blink::WebPopupTypeNone,
|
| - ScreenInfo(),
|
| - true,
|
| - false,
|
| - false) {}
|
| -
|
| - protected:
|
| - ~TestRenderWidget() override {}
|
| -
|
| - private:
|
| - DISALLOW_COPY_AND_ASSIGN(TestRenderWidget);
|
| -};
|
| -
|
| -// Test override of RenderWidgetInputHandler to allow the control of
|
| -// HandleInputEvent. This will perform no actions on input until a
|
| -// RenderWidgetInputHandlerDelegate is set. Once set this will always ack
|
| -// received events.
|
| -class TestRenderWidgetInputHandler : public RenderWidgetInputHandler {
|
| - public:
|
| - TestRenderWidgetInputHandler(RenderWidget* render_widget);
|
| - ~TestRenderWidgetInputHandler() override {}
|
| -
|
| - void set_delegate(RenderWidgetInputHandlerDelegate* delegate) {
|
| - delegate_ = delegate;
|
| - }
|
| - void set_state(InputEventAckState state) { state_ = state; }
|
| -
|
| - // RenderWidgetInputHandler:
|
| - void HandleInputEvent(const blink::WebInputEvent& input_event,
|
| - const ui::LatencyInfo& latency_info,
|
| - InputEventDispatchType dispatch_type) override;
|
| -
|
| - private:
|
| - // The input delegate which receives event acks.
|
| - RenderWidgetInputHandlerDelegate* delegate_;
|
| -
|
| - // The result of input handling to send to |delegate_| during the ack.
|
| - InputEventAckState state_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(TestRenderWidgetInputHandler);
|
| -};
|
| -
|
| -TestRenderWidgetInputHandler::TestRenderWidgetInputHandler(
|
| - RenderWidget* render_widget)
|
| - : RenderWidgetInputHandler(render_widget, render_widget),
|
| - delegate_(nullptr),
|
| - state_(InputEventAckState::INPUT_EVENT_ACK_STATE_UNKNOWN) {}
|
| -
|
| -void TestRenderWidgetInputHandler::HandleInputEvent(
|
| - const blink::WebInputEvent& input_event,
|
| - const ui::LatencyInfo& latency_info,
|
| - InputEventDispatchType dispatch_type) {
|
| - if (delegate_) {
|
| - std::unique_ptr<InputEventAck> ack(new InputEventAck(
|
| - InputEventAckSource::COMPOSITOR_THREAD, input_event.type(), state_));
|
| - delegate_->OnInputEventAck(std::move(ack));
|
| - }
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -// Test suite for CompositorMusConnection, this does not setup a full renderer
|
| -// environment. This does not establish a connection to a mus server, nor does
|
| -// it initialize one.
|
| -class CompositorMusConnectionTest : public testing::Test {
|
| - public:
|
| - CompositorMusConnectionTest() {}
|
| - ~CompositorMusConnectionTest() override {}
|
| -
|
| - // Returns a valid key event, so that it can be converted to a web event by
|
| - // CompositorMusConnection.
|
| - std::unique_ptr<ui::Event> GenerateKeyEvent();
|
| -
|
| - // Calls CompositorMusConnection::OnWindowInputEvent.
|
| - void OnWindowInputEvent(
|
| - ui::Window* window,
|
| - const ui::Event& event,
|
| - std::unique_ptr<base::Callback<void(EventResult)>>* ack_callback);
|
| -
|
| - // Confirms the state of pending tasks enqueued on each task runner, and runs
|
| - // until idle.
|
| - void VerifyAndRunQueues(bool main_task_runner_enqueued,
|
| - bool compositor_task_runner_enqueued);
|
| -
|
| - CompositorMusConnection* compositor_connection() {
|
| - return compositor_connection_.get();
|
| - }
|
| - RenderWidgetMusConnection* connection() { return connection_; }
|
| - TestInputHandlerManager* input_handler_manager() {
|
| - return input_handler_manager_.get();
|
| - }
|
| - TestRenderWidgetInputHandler* render_widget_input_handler() {
|
| - return render_widget_input_handler_.get();
|
| - }
|
| -
|
| - // testing::Test:
|
| - void SetUp() override;
|
| - void TearDown() override;
|
| -
|
| - private:
|
| - // Mocks/Fakes of the testing environment.
|
| - TestInputHandlerManagerClient input_handler_manager_client_;
|
| - FakeCompositorDependencies compositor_dependencies_;
|
| - blink::scheduler::FakeRendererScheduler renderer_scheduler_;
|
| - MockRenderThread render_thread_;
|
| - scoped_refptr<TestRenderWidget> render_widget_;
|
| - mojo::InterfaceRequest<ui::mojom::WindowTreeClient> request_;
|
| -
|
| - // Not owned, RenderWidgetMusConnection tracks in static state. Cleared during
|
| - // TearDown.
|
| - RenderWidgetMusConnection* connection_;
|
| -
|
| - // Test versions of task runners, see VerifyAndRunQueues to use in testing.
|
| - scoped_refptr<base::TestSimpleTaskRunner> main_task_runner_;
|
| - scoped_refptr<base::TestSimpleTaskRunner> compositor_task_runner_;
|
| -
|
| - // Actual CompositorMusConnection for testing.
|
| - scoped_refptr<CompositorMusConnection> compositor_connection_;
|
| -
|
| - // Test implementations, to control input given to |compositor_connection_|.
|
| - std::unique_ptr<TestInputHandlerManager> input_handler_manager_;
|
| - std::unique_ptr<TestRenderWidgetInputHandler> render_widget_input_handler_;
|
| -
|
| - DISALLOW_COPY_AND_ASSIGN(CompositorMusConnectionTest);
|
| -};
|
| -
|
| -std::unique_ptr<ui::Event> CompositorMusConnectionTest::GenerateKeyEvent() {
|
| - return std::unique_ptr<ui::Event>(new ui::KeyEvent(
|
| - ui::ET_KEY_PRESSED, ui::KeyboardCode::VKEY_A, ui::EF_NONE));
|
| -}
|
| -
|
| -void CompositorMusConnectionTest::OnWindowInputEvent(
|
| - ui::Window* window,
|
| - const ui::Event& event,
|
| - std::unique_ptr<base::Callback<void(EventResult)>>* ack_callback) {
|
| - compositor_connection_->OnWindowInputEvent(window, event, ack_callback);
|
| -}
|
| -
|
| -void CompositorMusConnectionTest::VerifyAndRunQueues(
|
| - bool main_task_runner_enqueued,
|
| - bool compositor_task_runner_enqueued) {
|
| - // Run through the enqueued actions.
|
| - EXPECT_EQ(main_task_runner_enqueued, main_task_runner_->HasPendingTask());
|
| - main_task_runner_->RunUntilIdle();
|
| -
|
| - EXPECT_EQ(compositor_task_runner_enqueued,
|
| - compositor_task_runner_->HasPendingTask());
|
| - compositor_task_runner_->RunUntilIdle();
|
| -}
|
| -
|
| -void CompositorMusConnectionTest::SetUp() {
|
| - testing::Test::SetUp();
|
| -
|
| - main_task_runner_ = new base::TestSimpleTaskRunner();
|
| - compositor_task_runner_ = new base::TestSimpleTaskRunner();
|
| -
|
| - input_handler_manager_.reset(new TestInputHandlerManager(
|
| - compositor_task_runner_, &input_handler_manager_client_,
|
| - &renderer_scheduler_));
|
| -
|
| - const int routing_id = 42;
|
| - compositor_connection_ = new CompositorMusConnection(
|
| - routing_id, main_task_runner_, compositor_task_runner_,
|
| - std::move(request_), input_handler_manager_.get());
|
| -
|
| - // CompositorMusConnection attempts to create connection to the non-existant
|
| - // server. Clear that.
|
| - compositor_task_runner_->ClearPendingTasks();
|
| -
|
| - render_widget_ = new TestRenderWidget(&compositor_dependencies_);
|
| - render_widget_input_handler_.reset(
|
| - new TestRenderWidgetInputHandler(render_widget_.get()));
|
| - connection_ = RenderWidgetMusConnection::GetOrCreate(routing_id);
|
| - connection_->SetInputHandler(render_widget_input_handler_.get());
|
| -}
|
| -
|
| -void CompositorMusConnectionTest::TearDown() {
|
| - // Clear static state.
|
| - connection_->OnConnectionLost();
|
| - testing::Test::TearDown();
|
| -}
|
| -
|
| -// Tests that for events which the renderer will ack, yet not consume, that
|
| -// CompositorMusConnection consumes the ack during OnWindowInputEvent, and calls
|
| -// it with the correct state once processed.
|
| -TEST_F(CompositorMusConnectionTest, NotConsumed) {
|
| - TestRenderWidgetInputHandler* input_handler = render_widget_input_handler();
|
| - input_handler->set_delegate(connection());
|
| - input_handler->set_state(
|
| - InputEventAckState::INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
|
| -
|
| - ui::TestWindow test_window;
|
| - std::unique_ptr<ui::Event> event(GenerateKeyEvent());
|
| - scoped_refptr<TestCallback> test_callback(new TestCallback);
|
| - std::unique_ptr<base::Callback<void(EventResult)>> ack_callback(
|
| - new base::Callback<void(EventResult)>(
|
| - base::Bind(&TestCallback::ResultCallback, test_callback)));
|
| -
|
| - OnWindowInputEvent(&test_window, *event.get(), &ack_callback);
|
| - // OnWindowInputEvent is expected to clear the callback if it plans on
|
| - // handling the ack.
|
| - EXPECT_FALSE(ack_callback.get());
|
| -
|
| - VerifyAndRunQueues(true, true);
|
| -
|
| - // The ack callback should have been called
|
| - EXPECT_TRUE(test_callback->called());
|
| - EXPECT_EQ(EventResult::UNHANDLED, test_callback->result());
|
| -}
|
| -
|
| -// Tests that for events which the renderer will ack, and consume, that
|
| -// CompositorMusConnection consumes the ack during OnWindowInputEvent, and calls
|
| -// it with the correct state once processed.
|
| -TEST_F(CompositorMusConnectionTest, Consumed) {
|
| - TestRenderWidgetInputHandler* input_handler = render_widget_input_handler();
|
| - input_handler->set_delegate(connection());
|
| - input_handler->set_state(InputEventAckState::INPUT_EVENT_ACK_STATE_CONSUMED);
|
| -
|
| - ui::TestWindow test_window;
|
| - std::unique_ptr<ui::Event> event(GenerateKeyEvent());
|
| - scoped_refptr<TestCallback> test_callback(new TestCallback);
|
| - std::unique_ptr<base::Callback<void(EventResult)>> ack_callback(
|
| - new base::Callback<void(EventResult)>(
|
| - base::Bind(&TestCallback::ResultCallback, test_callback)));
|
| -
|
| - OnWindowInputEvent(&test_window, *event.get(), &ack_callback);
|
| - // OnWindowInputEvent is expected to clear the callback if it plans on
|
| - // handling the ack.
|
| - EXPECT_FALSE(ack_callback.get());
|
| -
|
| - VerifyAndRunQueues(true, true);
|
| -
|
| - // The ack callback should have been called
|
| - EXPECT_TRUE(test_callback->called());
|
| - EXPECT_EQ(EventResult::HANDLED, test_callback->result());
|
| -}
|
| -
|
| -// Tests that when the RenderWidgetInputHandler does not ack before a new event
|
| -// arrives, that only the most recent ack is fired.
|
| -TEST_F(CompositorMusConnectionTest, LostAck) {
|
| - ui::TestWindow test_window;
|
| - std::unique_ptr<ui::Event> event1(GenerateKeyEvent());
|
| - scoped_refptr<TestCallback> test_callback1(new TestCallback);
|
| - std::unique_ptr<base::Callback<void(EventResult)>> ack_callback1(
|
| - new base::Callback<void(EventResult)>(
|
| - base::Bind(&TestCallback::ResultCallback, test_callback1)));
|
| -
|
| - OnWindowInputEvent(&test_window, *event1.get(), &ack_callback1);
|
| - EXPECT_FALSE(ack_callback1.get());
|
| - // When simulating the timeout the ack is never enqueued
|
| - VerifyAndRunQueues(true, false);
|
| -
|
| - // Setting a delegate will lead to the next event being acked. Having a
|
| - // cleared queue simulates the input handler timing out on an event.
|
| - TestRenderWidgetInputHandler* input_handler = render_widget_input_handler();
|
| - input_handler->set_delegate(connection());
|
| - input_handler->set_state(InputEventAckState::INPUT_EVENT_ACK_STATE_CONSUMED);
|
| -
|
| - std::unique_ptr<ui::Event> event2(GenerateKeyEvent());
|
| - scoped_refptr<TestCallback> test_callback2(new TestCallback);
|
| - std::unique_ptr<base::Callback<void(EventResult)>> ack_callback2(
|
| - new base::Callback<void(EventResult)>(
|
| - base::Bind(&TestCallback::ResultCallback, test_callback2)));
|
| - OnWindowInputEvent(&test_window, *event2.get(), &ack_callback2);
|
| - EXPECT_FALSE(ack_callback2.get());
|
| -
|
| - VerifyAndRunQueues(true, true);
|
| -
|
| - // Only the most recent ack was called.
|
| - EXPECT_FALSE(test_callback1->called());
|
| - EXPECT_TRUE(test_callback2->called());
|
| - EXPECT_EQ(EventResult::HANDLED, test_callback2->result());
|
| -}
|
| -
|
| -// Tests that when an input handler consumes the event, that
|
| -// CompositorMusConnection will consume the ack, but call as UNHANDLED.
|
| -TEST_F(CompositorMusConnectionTest, InputHandlerConsumes) {
|
| - input_handler_manager()->SetHandleInputEventResult(
|
| - InputEventAckState::INPUT_EVENT_ACK_STATE_CONSUMED);
|
| - ui::TestWindow test_window;
|
| - std::unique_ptr<ui::Event> event(GenerateKeyEvent());
|
| - scoped_refptr<TestCallback> test_callback(new TestCallback);
|
| - std::unique_ptr<base::Callback<void(EventResult)>> ack_callback(
|
| - new base::Callback<void(EventResult)>(
|
| - base::Bind(&TestCallback::ResultCallback, test_callback)));
|
| -
|
| - OnWindowInputEvent(&test_window, *event.get(), &ack_callback);
|
| -
|
| - EXPECT_FALSE(ack_callback.get());
|
| - VerifyAndRunQueues(false, false);
|
| - EXPECT_TRUE(test_callback->called());
|
| - EXPECT_EQ(EventResult::UNHANDLED, test_callback->result());
|
| -}
|
| -
|
| -// Tests that when the renderer will not ack an event, that
|
| -// CompositorMusConnection will consume the ack, but call as UNHANDLED.
|
| -TEST_F(CompositorMusConnectionTest, RendererWillNotSendAck) {
|
| - ui::TestWindow test_window;
|
| - ui::PointerEvent event(
|
| - ui::ET_POINTER_DOWN, gfx::Point(), gfx::Point(), ui::EF_NONE, 0, 0,
|
| - ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE),
|
| - ui::EventTimeForNow());
|
| -
|
| - scoped_refptr<TestCallback> test_callback(new TestCallback);
|
| - std::unique_ptr<base::Callback<void(EventResult)>> ack_callback(
|
| - new base::Callback<void(EventResult)>(
|
| - base::Bind(&TestCallback::ResultCallback, test_callback)));
|
| -
|
| - OnWindowInputEvent(&test_window, event, &ack_callback);
|
| - EXPECT_FALSE(ack_callback.get());
|
| -
|
| - VerifyAndRunQueues(true, false);
|
| - EXPECT_TRUE(test_callback->called());
|
| - EXPECT_EQ(EventResult::UNHANDLED, test_callback->result());
|
| -}
|
| -
|
| -// Tests that when a touch event id provided, that CompositorMusConnection
|
| -// consumes the ack.
|
| -TEST_F(CompositorMusConnectionTest, TouchEventConsumed) {
|
| - TestRenderWidgetInputHandler* input_handler = render_widget_input_handler();
|
| - input_handler->set_delegate(connection());
|
| - input_handler->set_state(InputEventAckState::INPUT_EVENT_ACK_STATE_CONSUMED);
|
| -
|
| - ui::TestWindow test_window;
|
| - ui::PointerEvent event(
|
| - ui::ET_POINTER_DOWN, gfx::Point(), gfx::Point(), ui::EF_NONE, 0, 0,
|
| - ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH),
|
| - ui::EventTimeForNow());
|
| -
|
| - scoped_refptr<TestCallback> test_callback(new TestCallback);
|
| - std::unique_ptr<base::Callback<void(EventResult)>> ack_callback(
|
| - new base::Callback<void(EventResult)>(
|
| - base::Bind(&TestCallback::ResultCallback, test_callback)));
|
| -
|
| - OnWindowInputEvent(&test_window, event, &ack_callback);
|
| - // OnWindowInputEvent is expected to clear the callback if it plans on
|
| - // handling the ack.
|
| - EXPECT_FALSE(ack_callback.get());
|
| -
|
| - VerifyAndRunQueues(true, true);
|
| -
|
| - // The ack callback should have been called
|
| - EXPECT_TRUE(test_callback->called());
|
| - EXPECT_EQ(EventResult::HANDLED, test_callback->result());
|
| -}
|
| -
|
| -} // namespace content
|
|
|