diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/ui/aura | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/ui/aura')
92 files changed, 17616 insertions, 0 deletions
diff --git a/chromium/ui/aura/DEPS b/chromium/ui/aura/DEPS new file mode 100644 index 00000000000..381faed57f5 --- /dev/null +++ b/chromium/ui/aura/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+grit/ui_resources.h", + "+grit/ui_strings.h", + "+skia/ext", + "+ui/base", + "+ui/gfx", +] diff --git a/chromium/ui/aura/OWNERS b/chromium/ui/aura/OWNERS new file mode 100644 index 00000000000..447142eb555 --- /dev/null +++ b/chromium/ui/aura/OWNERS @@ -0,0 +1,6 @@ +ben@chromium.org +sky@chromium.org +sadrul@chromium.org + +per-file *x11.cc=erg@chromium.org +per-file *x11.h=erg@chromium.org diff --git a/chromium/ui/aura/aura.gyp b/chromium/ui/aura/aura.gyp new file mode 100644 index 00000000000..a4fe8612de6 --- /dev/null +++ b/chromium/ui/aura/aura.gyp @@ -0,0 +1,300 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + 'target_name': 'aura', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../base/base.gyp:base_i18n', + '../../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../../cc/cc.gyp:cc', + '../../gpu/gpu.gyp:gpu', + '../../skia/skia.gyp:skia', + '../compositor/compositor.gyp:compositor', + '../ui.gyp:ui', + '../ui.gyp:ui_resources', + ], + 'defines': [ + 'AURA_IMPLEMENTATION', + ], + 'sources': [ + 'client/activation_change_observer.h', + 'client/activation_change_observer.cc', + 'client/activation_client.cc', + 'client/activation_client.h', + 'client/activation_delegate.cc', + 'client/activation_delegate.h', + 'client/animation_host.cc', + 'client/animation_host.h', + 'client/aura_constants.cc', + 'client/aura_constants.h', + 'client/capture_client.cc', + 'client/capture_client.h', + 'client/capture_delegate.h', + 'client/cursor_client.cc', + 'client/cursor_client.h', + 'client/cursor_client_observer.h', + 'client/cursor_client_observer.cc', + 'client/default_capture_client.cc', + 'client/default_capture_client.h', + 'client/dispatcher_client.cc', + 'client/dispatcher_client.h', + 'client/drag_drop_client.cc', + 'client/drag_drop_client.h', + 'client/drag_drop_delegate.cc', + 'client/drag_drop_delegate.h', + 'client/event_client.cc', + 'client/event_client.h', + 'client/focus_change_observer.cc', + 'client/focus_change_observer.h', + 'client/focus_client.cc', + 'client/focus_client.h', + 'client/screen_position_client.cc', + 'client/screen_position_client.h', + 'client/stacking_client.cc', + 'client/stacking_client.h', + 'client/tooltip_client.cc', + 'client/tooltip_client.h', + 'client/user_action_client.cc', + 'client/user_action_client.h', + 'client/visibility_client.cc', + 'client/visibility_client.h', + 'client/window_move_client.cc', + 'client/window_move_client.h', + 'client/window_types.h', + 'device_list_updater_aurax11.cc', + 'device_list_updater_aurax11.h', + 'dispatcher_win.cc', + 'env.cc', + 'env.h', + 'env_observer.h', + 'focus_manager.cc', + 'focus_manager.h', + 'layout_manager.cc', + 'layout_manager.h', + 'remote_root_window_host_win.cc', + 'remote_root_window_host_win.h', + 'root_window_host.h', + 'root_window_host_delegate.h', + 'root_window_host_mac.h', + 'root_window_host_mac.mm', + 'root_window_host_ozone.cc', + 'root_window_host_ozone.h', + 'root_window_host_win.cc', + 'root_window_host_win.h', + 'root_window_host_x11.cc', + 'root_window_host_x11.h', + 'root_window_mac.h', + 'root_window_mac.mm', + 'root_window_transformer.h', + 'root_window_view_mac.h', + 'root_window_view_mac.mm', + 'root_window.cc', + 'root_window.h', + 'window.cc', + 'window.h', + 'window_delegate.h', + 'window_observer.h', + 'window_tracker.cc', + 'window_tracker.h', + ], + 'conditions': [ + ['OS=="mac"', { + 'sources/': [ + ['exclude', 'client/dispatcher_client.cc'], + ['exclude', 'client/dispatcher_client.h'], + ], + }], + ['use_x11==1', { + 'link_settings': { + 'libraries': [ + '-lX11', + '-lXi', + '-lXfixes', + '-lXrandr', + ], + }, + }], + ['OS=="win"', { + 'dependencies': [ + '../metro_viewer/metro_viewer.gyp:metro_viewer_messages', + '../../ipc/ipc.gyp:ipc', + ], + }], + ], + }, + { + 'target_name': 'aura_test_support', + 'type': 'static_library', + 'dependencies': [ + '../../skia/skia.gyp:skia', + '../../testing/gtest.gyp:gtest', + '../ui.gyp:ui', + '../ui.gyp:ui_test_support', + 'aura', + 'aura_test_support_pak', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'test/aura_test_base.cc', + 'test/aura_test_base.h', + 'test/aura_test_helper.cc', + 'test/aura_test_helper.h', + 'test/event_generator.cc', + 'test/event_generator.h', + 'test/test_activation_client.cc', + 'test/test_activation_client.h', + 'test/test_aura_initializer.cc', + 'test/test_aura_initializer.h', + 'test/test_cursor_client.cc', + 'test/test_cursor_client.h', + 'test/test_event_handler.cc', + 'test/test_event_handler.h', + 'test/test_screen.cc', + 'test/test_screen.h', + 'test/test_stacking_client.cc', + 'test/test_stacking_client.h', + 'test/test_windows.cc', + 'test/test_windows.h', + 'test/test_window_delegate.cc', + 'test/test_window_delegate.h', + 'test/ui_controls_factory_aura.h', + 'test/ui_controls_factory_aurawin.cc', + 'test/ui_controls_factory_aurax11.cc', + 'test/window_test_api.cc', + 'test/window_test_api.h', + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ 4267, ], + }, + { + # We build a minimal set of resources required for aura_test_support. + 'target_name': 'aura_test_support_pak', + 'type': 'none', + 'dependencies': [ + '<(DEPTH)/ui/ui.gyp:ui_resources', + ], + 'variables': { + 'repack_path': '<(DEPTH)/tools/grit/grit/format/repack.py', + }, + 'actions': [ + { + 'action_name': 'repack_aura_test_support_pack', + 'variables': { + 'pak_inputs': [ + '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources/ui_resources_100_percent.pak', + ], + }, + 'inputs': [ + '<(repack_path)', + '<@(pak_inputs)', + ], + 'outputs': [ + '<(PRODUCT_DIR)/aura_test_support_resources.pak', + ], + 'action': ['python', '<(repack_path)', '<@(_outputs)', + '<@(pak_inputs)'], + }, + ], + }, + { + 'target_name': 'aura_demo', + 'type': 'executable', + 'dependencies': [ + '../../base/base.gyp:base', + '../../base/base.gyp:base_i18n', + '../../skia/skia.gyp:skia', + '../../third_party/icu/icu.gyp:icui18n', + '../../third_party/icu/icu.gyp:icuuc', + '../compositor/compositor.gyp:compositor', + '../compositor/compositor.gyp:compositor_test_support', + '../ui.gyp:ui', + '../ui.gyp:ui_resources', + '../../ipc/ipc.gyp:ipc', + 'aura', + 'aura_test_support', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'demo/demo_main.cc', + ], + }, + { + 'target_name': 'aura_bench', + 'type': 'executable', + 'dependencies': [ + '../../base/base.gyp:base', + '../../base/base.gyp:base_i18n', + '../../skia/skia.gyp:skia', + '../../third_party/icu/icu.gyp:icui18n', + '../../third_party/icu/icu.gyp:icuuc', + '../compositor/compositor.gyp:compositor', + '../compositor/compositor.gyp:compositor_test_support', + '../ui.gyp:ui', + '../ui.gyp:ui_resources', + 'aura', + 'aura_test_support', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'bench/bench_main.cc', + ], + }, + { + 'target_name': 'aura_unittests', + 'type': 'executable', + 'dependencies': [ + '../../base/base.gyp:test_support_base', + '../../chrome/chrome_resources.gyp:packed_resources', + '../../skia/skia.gyp:skia', + '../../testing/gtest.gyp:gtest', + '../compositor/compositor.gyp:compositor_test_support', + '../compositor/compositor.gyp:compositor', + '../gl/gl.gyp:gl', + '../ui.gyp:ui', + '../ui.gyp:ui_resources', + '../ui.gyp:ui_test_support', + 'aura_test_support', + 'aura', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'gestures/gesture_recognizer_unittest.cc', + 'test/run_all_unittests.cc', + 'test/test_suite.cc', + 'test/test_suite.h', + 'root_window_unittest.cc', + 'window_unittest.cc', + ], + 'conditions': [ + # osmesa GL implementation is used on linux. + ['OS=="linux"', { + 'dependencies': [ + '<(DEPTH)/third_party/mesa/mesa.gyp:osmesa', + ], + }], + ['OS=="linux" and linux_use_tcmalloc==1', { + 'dependencies': [ + # See http://crbug.com/162998#c4 for why this is needed. + '../../base/allocator/allocator.gyp:allocator', + ], + }], + ], + }, + ], +} diff --git a/chromium/ui/aura/aura_export.h b/chromium/ui/aura/aura_export.h new file mode 100644 index 00000000000..9db662bbf7e --- /dev/null +++ b/chromium/ui/aura/aura_export.h @@ -0,0 +1,32 @@ +// Copyright (c) 2011 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. + +#ifndef UI_AURA_AURA_EXPORT_H +#define UI_AURA_AURA_EXPORT_H + +// Defines AURA_EXPORT so that functionality implemented by the aura module +// can be exported to consumers. + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(AURA_IMPLEMENTATION) +#define AURA_EXPORT __declspec(dllexport) +#else +#define AURA_EXPORT __declspec(dllimport) +#endif // defined(AURA_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(AURA_IMPLEMENTATION) +#define AURA_EXPORT __attribute__((visibility("default"))) +#else +#define AURA_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define AURA_EXPORT +#endif + +#endif // UI_AURA_AURA_EXPORT_H diff --git a/chromium/ui/aura/bench/DEPS b/chromium/ui/aura/bench/DEPS new file mode 100644 index 00000000000..1fda55436bc --- /dev/null +++ b/chromium/ui/aura/bench/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+cc", + "+third_party/khronos", + "+third_party/WebKit/public/platform", +] diff --git a/chromium/ui/aura/bench/bench_main.cc b/chromium/ui/aura/bench/bench_main.cc new file mode 100644 index 00000000000..17ed068e60b --- /dev/null +++ b/chromium/ui/aura/bench/bench_main.cc @@ -0,0 +1,367 @@ +// 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 "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/i18n/icu_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_split.h" +#include "base/time/time.h" +#include "cc/output/context_provider.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/skia/include/core/SkXfermode.h" +#include "ui/aura/client/default_capture_client.h" +#include "ui/aura/env.h" +#include "ui/aura/focus_manager.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/test_screen.h" +#include "ui/aura/window.h" +#include "ui/base/hit_test.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/ui_base_paths.h" +#include "ui/compositor/compositor.h" +#include "ui/compositor/compositor_observer.h" +#include "ui/compositor/debug_utils.h" +#include "ui/compositor/layer.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/skia_util.h" +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES 1 +#endif +#include "third_party/WebKit/public/platform/WebGraphicsContext3D.h" +#include "third_party/khronos/GLES2/gl2ext.h" + +#if defined(USE_X11) +#include "base/message_loop/message_pump_aurax11.h" +#endif + +using base::TimeTicks; +using ui::Compositor; +using ui::Layer; +using ui::LayerDelegate; +using WebKit::WebGraphicsContext3D; + +namespace { + +class ColoredLayer : public Layer, public LayerDelegate { + public: + explicit ColoredLayer(SkColor color) + : Layer(ui::LAYER_TEXTURED), + color_(color), + draw_(true) { + set_delegate(this); + } + + virtual ~ColoredLayer() {} + + // Overridden from LayerDelegate: + virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE { + if (draw_) { + canvas->DrawColor(color_); + } + } + + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE { + } + + virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE { + return base::Closure(); + } + + void set_color(SkColor color) { color_ = color; } + void set_draw(bool draw) { draw_ = draw; } + + private: + SkColor color_; + bool draw_; + + DISALLOW_COPY_AND_ASSIGN(ColoredLayer); +}; + +const int kFrames = 100; + +// Benchmark base class, hooks up drawing callback and displaying FPS. +class BenchCompositorObserver : public ui::CompositorObserver { + public: + explicit BenchCompositorObserver(int max_frames) + : start_time_(), + frames_(0), + max_frames_(max_frames) { + } + + virtual void OnCompositingDidCommit(ui::Compositor* compositor) OVERRIDE {} + + virtual void OnCompositingStarted(Compositor* compositor, + base::TimeTicks start_time) OVERRIDE {} + + virtual void OnCompositingEnded(Compositor* compositor) OVERRIDE { + if (start_time_.is_null()) { + start_time_ = TimeTicks::Now(); + } else { + ++frames_; + if (frames_ % kFrames == 0) { + TimeTicks now = TimeTicks::Now(); + double ms = (now - start_time_).InMillisecondsF() / kFrames; + LOG(INFO) << "FPS: " << 1000.f / ms << " (" << ms << " ms)"; + start_time_ = now; + } + } + if (max_frames_ && frames_ == max_frames_) { + base::MessageLoop::current()->Quit(); + } else { + Draw(); + } + } + + virtual void OnCompositingAborted(Compositor* compositor) OVERRIDE {} + + virtual void OnCompositingLockStateChanged( + Compositor* compositor) OVERRIDE {} + + virtual void OnUpdateVSyncParameters(ui::Compositor* compositor, + base::TimeTicks timebase, + base::TimeDelta interval) OVERRIDE { + } + + virtual void Draw() {} + + int frames() const { return frames_; } + + private: + TimeTicks start_time_; + int frames_; + int max_frames_; + + DISALLOW_COPY_AND_ASSIGN(BenchCompositorObserver); +}; + +class WebGLTexture : public ui::Texture { + public: + WebGLTexture(WebGraphicsContext3D* context, const gfx::Size& size) + : ui::Texture(false, size, 1.0f), + context_(context), + texture_id_(context_->createTexture()) { + context_->bindTexture(GL_TEXTURE_2D, texture_id_); + context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + context_->texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + size.width(), size.height(), 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + } + + virtual unsigned int PrepareTexture() OVERRIDE { + return texture_id_; + } + + virtual WebGraphicsContext3D* HostContext3D() OVERRIDE { + return context_; + } + + private: + virtual ~WebGLTexture() { + context_->deleteTexture(texture_id_); + } + + WebGraphicsContext3D* context_; + unsigned texture_id_; + + DISALLOW_COPY_AND_ASSIGN(WebGLTexture); +}; + +// A benchmark that adds a texture layer that is updated every frame. +class WebGLBench : public BenchCompositorObserver { + public: + WebGLBench(Layer* parent, Compositor* compositor, int max_frames) + : BenchCompositorObserver(max_frames), + parent_(parent), + webgl_(ui::LAYER_TEXTURED), + compositor_(compositor), + context_provider_(), + texture_(), + fbo_(0), + do_draw_(true) { + CommandLine* command_line = CommandLine::ForCurrentProcess(); + do_draw_ = !command_line->HasSwitch("disable-draw"); + + std::string webgl_size = command_line->GetSwitchValueASCII("webgl-size"); + int width = 0; + int height = 0; + if (!webgl_size.empty()) { + std::vector<std::string> split_size; + base::SplitString(webgl_size, 'x', &split_size); + if (split_size.size() == 2) { + width = atoi(split_size[0].c_str()); + height = atoi(split_size[1].c_str()); + } + } + if (!width || !height) { + width = 800; + height = 600; + } + gfx::Rect bounds(width, height); + webgl_.SetBounds(bounds); + parent_->Add(&webgl_); + + context_provider_ = ui::ContextFactory::GetInstance() + ->OffscreenContextProviderForMainThread(); + WebKit::WebGraphicsContext3D* context = context_provider_->Context3d(); + context->makeContextCurrent(); + texture_ = new WebGLTexture(context, bounds.size()); + fbo_ = context->createFramebuffer(); + compositor->AddObserver(this); + webgl_.SetExternalTexture(texture_.get()); + context->bindFramebuffer(GL_FRAMEBUFFER, fbo_); + context->framebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, texture_->PrepareTexture(), 0); + context->clearColor(0.f, 1.f, 0.f, 1.f); + context->clear(GL_COLOR_BUFFER_BIT); + context->flush(); + } + + virtual ~WebGLBench() { + context_provider_->Context3d()->makeContextCurrent(); + context_provider_->Context3d()->deleteFramebuffer(fbo_); + webgl_.SetExternalTexture(NULL); + texture_ = NULL; + compositor_->RemoveObserver(this); + } + + virtual void Draw() OVERRIDE { + if (do_draw_) { + WebKit::WebGraphicsContext3D* context = context_provider_->Context3d(); + context->makeContextCurrent(); + context->clearColor((frames() % kFrames)*1.0/kFrames, 1.f, 0.f, 1.f); + context->clear(GL_COLOR_BUFFER_BIT); + context->flush(); + } + webgl_.SetExternalTexture(texture_.get()); + webgl_.SchedulePaint(gfx::Rect(webgl_.bounds().size())); + compositor_->ScheduleDraw(); + } + + private: + Layer* parent_; + Layer webgl_; + Compositor* compositor_; + scoped_refptr<cc::ContextProvider> context_provider_; + scoped_refptr<WebGLTexture> texture_; + + // The FBO that is used to render to the texture. + unsigned int fbo_; + + // Whether or not to draw to the texture every frame. + bool do_draw_; + + DISALLOW_COPY_AND_ASSIGN(WebGLBench); +}; + +// A benchmark that paints (in software) all tiles every frame. +class SoftwareScrollBench : public BenchCompositorObserver { + public: + SoftwareScrollBench(ColoredLayer* layer, + Compositor* compositor, + int max_frames) + : BenchCompositorObserver(max_frames), + layer_(layer), + compositor_(compositor) { + compositor->AddObserver(this); + layer_->set_draw( + !CommandLine::ForCurrentProcess()->HasSwitch("disable-draw")); + } + + virtual ~SoftwareScrollBench() { + compositor_->RemoveObserver(this); + } + + virtual void Draw() OVERRIDE { + layer_->set_color( + SkColorSetARGBInline(255*(frames() % kFrames)/kFrames, 255, 0, 255)); + layer_->SchedulePaint(gfx::Rect(layer_->bounds().size())); + } + + private: + ColoredLayer* layer_; + Compositor* compositor_; + + DISALLOW_COPY_AND_ASSIGN(SoftwareScrollBench); +}; + +} // namespace + +int main(int argc, char** argv) { + CommandLine::Init(argc, argv); + + base::AtExitManager exit_manager; + + ui::RegisterPathProvider(); + icu_util::Initialize(); + ResourceBundle::InitSharedInstanceWithLocale("en-US", NULL); + + base::MessageLoop message_loop(base::MessageLoop::TYPE_UI); + aura::Env::GetInstance(); + scoped_ptr<aura::TestScreen> test_screen( + aura::TestScreen::CreateFullscreen()); + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, test_screen.get()); + scoped_ptr<aura::RootWindow> root_window( + test_screen->CreateRootWindowForPrimaryDisplay()); + aura::client::SetCaptureClient( + root_window.get(), + new aura::client::DefaultCaptureClient(root_window.get())); + + scoped_ptr<aura::client::FocusClient> focus_client(new aura::FocusManager); + aura::client::SetFocusClient(root_window.get(), focus_client.get()); + + // add layers + ColoredLayer background(SK_ColorRED); + background.SetBounds(root_window->bounds()); + root_window->layer()->Add(&background); + + ColoredLayer window(SK_ColorBLUE); + window.SetBounds(gfx::Rect(background.bounds().size())); + background.Add(&window); + + Layer content_layer(ui::LAYER_NOT_DRAWN); + + CommandLine* command_line = CommandLine::ForCurrentProcess(); + bool force = command_line->HasSwitch("force-render-surface"); + content_layer.SetForceRenderSurface(force); + gfx::Rect bounds(window.bounds().size()); + bounds.Inset(0, 30, 0, 0); + content_layer.SetBounds(bounds); + window.Add(&content_layer); + + ColoredLayer page_background(SK_ColorWHITE); + page_background.SetBounds(gfx::Rect(content_layer.bounds().size())); + content_layer.Add(&page_background); + + int frames = atoi(command_line->GetSwitchValueASCII("frames").c_str()); + scoped_ptr<BenchCompositorObserver> bench; + + if (command_line->HasSwitch("bench-software-scroll")) { + bench.reset(new SoftwareScrollBench(&page_background, + root_window->compositor(), + frames)); + } else { + bench.reset(new WebGLBench(&page_background, + root_window->compositor(), + frames)); + } + +#ifndef NDEBUG + ui::PrintLayerHierarchy(root_window->layer(), gfx::Point(100, 100)); +#endif + + root_window->ShowRootWindow(); + base::MessageLoopForUI::current()->Run(); + focus_client.reset(); + root_window.reset(); + + return 0; +} diff --git a/chromium/ui/aura/client/activation_change_observer.cc b/chromium/ui/aura/client/activation_change_observer.cc new file mode 100644 index 00000000000..db9f332cfae --- /dev/null +++ b/chromium/ui/aura/client/activation_change_observer.cc @@ -0,0 +1,29 @@ +// 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 "ui/aura/client/activation_change_observer.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::ActivationChangeObserver*) + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + ActivationChangeObserver*, kActivationChangeObserverKey, NULL); + +void SetActivationChangeObserver( + Window* window, + ActivationChangeObserver* observer) { + window->SetProperty(kActivationChangeObserverKey, observer); +} + +ActivationChangeObserver* GetActivationChangeObserver(Window* window) { + return window ? window->GetProperty(kActivationChangeObserverKey) : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/activation_change_observer.h b/chromium/ui/aura/client/activation_change_observer.h new file mode 100644 index 00000000000..2f247db04b3 --- /dev/null +++ b/chromium/ui/aura/client/activation_change_observer.h @@ -0,0 +1,45 @@ +// 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. + +#ifndef UI_AURA_CLIENT_ACTIVATION_CHANGE_OBSERVER_H_ +#define UI_AURA_CLIENT_ACTIVATION_CHANGE_OBSERVER_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { +class Window; + +namespace client { + +class AURA_EXPORT ActivationChangeObserver { + public: + // Called when |active| gains focus, or there is no active window + // (|active| is NULL in this case.) |old_active| refers to the + // previous active window or NULL if there was no previously active + // window. + virtual void OnWindowActivated(Window* gained_active, + Window* lost_active) = 0; + + // Called when during window activation the currently active window is + // selected for activation. This can happen when a window requested for + // activation cannot be activated because a system modal window is active. + virtual void OnAttemptToReactivateWindow(aura::Window* request_active, + aura::Window* actual_active) {} + + protected: + virtual ~ActivationChangeObserver() {} +}; + +// Gets/Sets the ActivationChangeObserver for a specific window. This observer +// is notified after the ActivationClient notifies all registered observers. +AURA_EXPORT void SetActivationChangeObserver( + Window* window, + ActivationChangeObserver* observer); +AURA_EXPORT ActivationChangeObserver* GetActivationChangeObserver( + Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_ACTIVATION_CHANGE_OBSERVER_H_ diff --git a/chromium/ui/aura/client/activation_client.cc b/chromium/ui/aura/client/activation_client.cc new file mode 100644 index 00000000000..b55702381ea --- /dev/null +++ b/chromium/ui/aura/client/activation_client.cc @@ -0,0 +1,38 @@ +// 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 "ui/aura/client/activation_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(AURA_EXPORT, aura::Window*) +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::ActivationClient*) + +namespace aura { +namespace client { + +DEFINE_WINDOW_PROPERTY_KEY( + ActivationClient*, kRootWindowActivationClientKey, NULL); +DEFINE_WINDOW_PROPERTY_KEY(bool, kHideOnDeactivate, false); + +void SetActivationClient(RootWindow* root_window, ActivationClient* client) { + root_window->SetProperty(kRootWindowActivationClientKey, client); +} + +ActivationClient* GetActivationClient(RootWindow* root_window) { + return root_window ? + root_window->GetProperty(kRootWindowActivationClientKey) : NULL; +} + +void SetHideOnDeactivate(Window* window, bool hide_on_deactivate) { + window->SetProperty(kHideOnDeactivate, hide_on_deactivate); +} + +bool GetHideOnDeactivate(Window* window) { + return window->GetProperty(kHideOnDeactivate); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/activation_client.h b/chromium/ui/aura/client/activation_client.h new file mode 100644 index 00000000000..db4cd1b15b6 --- /dev/null +++ b/chromium/ui/aura/client/activation_client.h @@ -0,0 +1,78 @@ +// 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. + +#ifndef UI_AURA_CLIENT_ACTIVATION_CLIENT_H_ +#define UI_AURA_CLIENT_ACTIVATION_CLIENT_H_ + +#include "ui/aura/aura_export.h" +#include "ui/aura/window.h" + +namespace ui { +class Event; +} + +namespace aura { +class RootWindow; + +namespace client { +class ActivationChangeObserver; + +// An interface implemented by an object that manages window activation. +class AURA_EXPORT ActivationClient { + public: + // Adds/Removes ActivationChangeObservers. + virtual void AddObserver(ActivationChangeObserver* observer) = 0; + virtual void RemoveObserver(ActivationChangeObserver* observer) = 0; + + // Activates |window|. If |window| is NULL, nothing happens. + virtual void ActivateWindow(Window* window) = 0; + + // Deactivates |window|. What (if anything) is activated next is up to the + // client. If |window| is NULL, nothing happens. + virtual void DeactivateWindow(Window* window) = 0; + + // Retrieves the active window, or NULL if there is none. + virtual Window* GetActiveWindow() = 0; + + // Retrieves the activatable window for |window|, or NULL if there is none. + // Note that this is often but not always the toplevel window (see + // GetToplevelWindow() below), as the toplevel window may not be activatable + // (for example it may be blocked by a modal transient, or some other + // condition). + virtual Window* GetActivatableWindow(Window* window) = 0; + + // Retrieves the toplevel window for |window|, or NULL if there is none. + virtual Window* GetToplevelWindow(Window* window) = 0; + + // Invoked prior to |window| getting focus as a result of the |event|. |event| + // may be NULL. Returning false blocks |window| from getting focus. + virtual bool OnWillFocusWindow(Window* window, const ui::Event* event) = 0; + + // Returns true if |window| can be activated, false otherwise. If |window| has + // a modal child it can not be activated. + virtual bool CanActivateWindow(Window* window) const = 0; + + protected: + virtual ~ActivationClient() {} +}; + +// Sets/Gets the activation client on the RootWindow. +AURA_EXPORT void SetActivationClient(RootWindow* root_window, + ActivationClient* client); +AURA_EXPORT ActivationClient* GetActivationClient(RootWindow* root_window); + +// Some types of transient window are only visible when active. +// The transient parents of these windows may have visual appearance properties +// that differ from transient parents that can be deactivated. +// The presence of this property implies these traits. +// TODO(beng): currently the UI framework (views) implements the actual +// close-on-deactivate component of this feature but it should be +// possible to implement in the aura client. +AURA_EXPORT void SetHideOnDeactivate(Window* window, bool hide_on_deactivate); +AURA_EXPORT bool GetHideOnDeactivate(Window* window); + +} // namespace clients +} // namespace aura + +#endif // UI_AURA_CLIENT_ACTIVATION_CLIENT_H_ diff --git a/chromium/ui/aura/client/activation_delegate.cc b/chromium/ui/aura/client/activation_delegate.cc new file mode 100644 index 00000000000..51b22b00a61 --- /dev/null +++ b/chromium/ui/aura/client/activation_delegate.cc @@ -0,0 +1,27 @@ +// 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 "ui/aura/client/activation_delegate.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::ActivationDelegate*) + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + ActivationDelegate*, kActivationDelegateKey, NULL); + +void SetActivationDelegate(Window* window, ActivationDelegate* delegate) { + window->SetProperty(kActivationDelegateKey, delegate); +} + +ActivationDelegate* GetActivationDelegate(Window* window) { + return window->GetProperty(kActivationDelegateKey); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/activation_delegate.h b/chromium/ui/aura/client/activation_delegate.h new file mode 100644 index 00000000000..0ce86388f52 --- /dev/null +++ b/chromium/ui/aura/client/activation_delegate.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef UI_AURA_CLIENT_ACTIVATION_DELEGATE_H_ +#define UI_AURA_CLIENT_ACTIVATION_DELEGATE_H_ + +#include "ui/aura/aura_export.h" + +namespace ui { +class Event; +} + +namespace aura { +class Window; +namespace client { + +// An interface implemented by an object that configures and responds to changes +// to a window's activation state. +class AURA_EXPORT ActivationDelegate { + public: + // Returns true if the window should be activated. + virtual bool ShouldActivate() const = 0; + + protected: + virtual ~ActivationDelegate() {} +}; + +// Sets/Gets the ActivationDelegate on the Window. No ownership changes. +AURA_EXPORT void SetActivationDelegate(Window* window, + ActivationDelegate* delegate); +AURA_EXPORT ActivationDelegate* GetActivationDelegate(Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_ACTIVATION_DELEGATE_H_ diff --git a/chromium/ui/aura/client/animation_host.cc b/chromium/ui/aura/client/animation_host.cc new file mode 100644 index 00000000000..5df22babc62 --- /dev/null +++ b/chromium/ui/aura/client/animation_host.cc @@ -0,0 +1,32 @@ +// Copyright 2013 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 "ui/aura/client/animation_host.h" + +#include "base/compiler_specific.h" +#include "ui/aura/aura_export.h" + +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::AnimationHost*) + +namespace aura { +namespace client { + +DEFINE_WINDOW_PROPERTY_KEY(AnimationHost*, kRootWindowAnimationHostKey, NULL); + +void SetAnimationHost(Window* window, AnimationHost* animation_host) { + DCHECK(window); + window->SetProperty(kRootWindowAnimationHostKey, animation_host); +} + +AnimationHost* GetAnimationHost(Window* window) { + DCHECK(window); + return window->GetProperty(kRootWindowAnimationHostKey); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/animation_host.h b/chromium/ui/aura/client/animation_host.h new file mode 100644 index 00000000000..549c3469a8b --- /dev/null +++ b/chromium/ui/aura/client/animation_host.h @@ -0,0 +1,42 @@ +// Copyright 2013 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. + +#ifndef UI_AURA_CLIENT_ANIMATION_HOST_H_ +#define UI_AURA_CLIENT_ANIMATION_HOST_H_ + +#include "base/compiler_specific.h" +#include "ui/aura/aura_export.h" + +namespace gfx { +class Rect; +} + +namespace aura { +class Window; +namespace client { + +// Interface for top level window host of animation. Communicates additional +// bounds required for animation as well as animation completion for deferring +// window closes on hide. +class AURA_EXPORT AnimationHost { + public: + // Ensure the host window is at least this large so that transitions have + // sufficient space. + virtual void SetHostTransitionBounds(const gfx::Rect& bounds) = 0; + + // Called after the window has faded out on a hide. + virtual void OnWindowHidingAnimationCompleted() = 0; + + protected: + virtual ~AnimationHost() {} +}; + +AURA_EXPORT void SetAnimationHost(Window* window, + AnimationHost* animation_host); +AURA_EXPORT AnimationHost* GetAnimationHost(Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_ANIMATION_HOST_H_ diff --git a/chromium/ui/aura/client/aura_constants.cc b/chromium/ui/aura/client/aura_constants.cc new file mode 100644 index 00000000000..7ba9f874d25 --- /dev/null +++ b/chromium/ui/aura/client/aura_constants.cc @@ -0,0 +1,38 @@ +// 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 "ui/aura/client/aura_constants.h" + +#include "ui/aura/window_property.h" +#include "ui/gfx/rect.h" + +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(AURA_EXPORT, bool) +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(AURA_EXPORT, ui::ModalType) +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(AURA_EXPORT, gfx::Rect*) +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(AURA_EXPORT, ui::InputMethod*) +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(AURA_EXPORT, ui::WindowShowState) + +namespace aura { +namespace client { + +// Alphabetical sort. + +DEFINE_WINDOW_PROPERTY_KEY(bool, kAlwaysOnTopKey, false); +DEFINE_WINDOW_PROPERTY_KEY(bool, kAnimationsDisabledKey, false); +DEFINE_WINDOW_PROPERTY_KEY(bool, kCanMaximizeKey, false); +DEFINE_WINDOW_PROPERTY_KEY(bool, kCanResizeKey, true); +DEFINE_WINDOW_PROPERTY_KEY(bool, kConstrainedWindowKey, false); +DEFINE_WINDOW_PROPERTY_KEY(bool, kDrawAttentionKey, false); +DEFINE_WINDOW_PROPERTY_KEY(ui::ModalType, kModalKey, ui::MODAL_TYPE_NONE); +// gfx::Rect object for RestoreBoundsKey property is owned by the window +// and will be freed automatically. +DEFINE_OWNED_WINDOW_PROPERTY_KEY(gfx::Rect, kRestoreBoundsKey, NULL); +DEFINE_WINDOW_PROPERTY_KEY( + ui::WindowShowState, kRestoreShowStateKey, ui::SHOW_STATE_DEFAULT); +DEFINE_WINDOW_PROPERTY_KEY(ui::InputMethod*, kRootWindowInputMethodKey, NULL); +DEFINE_WINDOW_PROPERTY_KEY( + ui::WindowShowState, kShowStateKey, ui::SHOW_STATE_DEFAULT); + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/aura_constants.h b/chromium/ui/aura/client/aura_constants.h new file mode 100644 index 00000000000..32c524fde95 --- /dev/null +++ b/chromium/ui/aura/client/aura_constants.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef UI_AURA_CLIENT_AURA_CONSTANTS_H_ +#define UI_AURA_CLIENT_AURA_CONSTANTS_H_ + +#include "ui/aura/aura_export.h" +#include "ui/aura/window.h" +#include "ui/base/ui_base_types.h" + +namespace ui { +class InputMethod; +} + +namespace aura { +namespace client { + +// Alphabetical sort. + +// A property key to store always-on-top flag. +AURA_EXPORT extern const WindowProperty<bool>* const kAlwaysOnTopKey; + +// A property key to store whether animations are disabled for the window. Type +// of value is an int. +AURA_EXPORT extern const WindowProperty<bool>* const kAnimationsDisabledKey; + +// A property key to store the can-maximize flag. +AURA_EXPORT extern const WindowProperty<bool>* const kCanMaximizeKey; + +// A property key to store the can-resize flag. +AURA_EXPORT extern const WindowProperty<bool>* const kCanResizeKey; + +// A property key to store if a window is a constrained window or not. +AURA_EXPORT extern const WindowProperty<bool>* const kConstrainedWindowKey; + +// A property key to indicate that a window should show that it deserves +// attention. +AURA_EXPORT extern const aura::WindowProperty<bool>* const kDrawAttentionKey; + +// A property key to store the window modality. +AURA_EXPORT extern const WindowProperty<ui::ModalType>* const kModalKey; + +// A property key to store the restore bounds for a window. +AURA_EXPORT extern const WindowProperty<gfx::Rect*>* const kRestoreBoundsKey; + +// A property key to store ui::WindowShowState for restoring a window. +// Used in Ash to remember the show state before the window was minimized. +AURA_EXPORT extern const WindowProperty<ui::WindowShowState>* const + kRestoreShowStateKey; + +// A property key to store an input method object that handles a key event. +AURA_EXPORT extern const WindowProperty<ui::InputMethod*>* const + kRootWindowInputMethodKey; + +// A property key to store ui::WindowShowState for a window. +// See ui/base/ui_base_types.h for its definition. +AURA_EXPORT extern const WindowProperty<ui::WindowShowState>* const + kShowStateKey; + +// Alphabetical sort. + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_AURA_CONSTANTS_H_ diff --git a/chromium/ui/aura/client/capture_client.cc b/chromium/ui/aura/client/capture_client.cc new file mode 100644 index 00000000000..d74940a67e5 --- /dev/null +++ b/chromium/ui/aura/client/capture_client.cc @@ -0,0 +1,34 @@ +// 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 "ui/aura/client/capture_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::CaptureClient*) + +namespace aura { +namespace client { + +DEFINE_WINDOW_PROPERTY_KEY( + CaptureClient*, kRootWindowCaptureClientKey, NULL); + +void SetCaptureClient(RootWindow* root_window, CaptureClient* client) { + root_window->SetProperty(kRootWindowCaptureClientKey, client); +} + +CaptureClient* GetCaptureClient(RootWindow* root_window) { + return root_window ? + root_window->GetProperty(kRootWindowCaptureClientKey) : NULL; +} + +Window* GetCaptureWindow(Window* window) { + RootWindow* root_window = window->GetRootWindow(); + return root_window ? + GetCaptureClient(root_window)->GetCaptureWindow() : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/capture_client.h b/chromium/ui/aura/client/capture_client.h new file mode 100644 index 00000000000..b244c0caef1 --- /dev/null +++ b/chromium/ui/aura/client/capture_client.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef UI_AURA_CLIENT_CAPTURE_CLIENT_H_ +#define UI_AURA_CLIENT_CAPTURE_CLIENT_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { +class RootWindow; +class Window; + +namespace client { + +// An interface implemented by an object that manages input capture. +class AURA_EXPORT CaptureClient { + public: + // Does a capture on the |window|. + virtual void SetCapture(Window* window) = 0; + + // Releases a capture from the |window|. + virtual void ReleaseCapture(Window* window) = 0; + + // Returns the current capture window. + virtual Window* GetCaptureWindow() = 0; + + protected: + virtual ~CaptureClient() {} +}; + +// Sets/Gets the capture client on the RootWindow. +AURA_EXPORT void SetCaptureClient(RootWindow* root_window, + CaptureClient* client); +AURA_EXPORT CaptureClient* GetCaptureClient(RootWindow* root_window); + +// A utility function to get the current capture window. Returns NULL +// if the window doesn't have a root window, or there is no capture window. +AURA_EXPORT Window* GetCaptureWindow(Window* window); + +} // namespace clients +} // namespace aura + +#endif // UI_AURA_CLIENT_CAPTURE_CLIENT_H_ diff --git a/chromium/ui/aura/client/capture_delegate.h b/chromium/ui/aura/client/capture_delegate.h new file mode 100644 index 00000000000..c727b0a0697 --- /dev/null +++ b/chromium/ui/aura/client/capture_delegate.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef UI_AURA_CLIENT_CAPTURE_DELEGATE_H_ +#define UI_AURA_CLIENT_CAPTURE_DELEGATE_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { +class Window; +namespace client { + +// This interface provides API to change the RootWindow's capture state +// without exposing them as RootWindow API. +class AURA_EXPORT CaptureDelegate { + public: + // Called when a capture is set on the |new_capture| which is owned by + // this root window, and/or a capture is released on the |old_capture| + // which is owned by this root window. + virtual void UpdateCapture(aura::Window* old_capture, + aura::Window* new_capture) = 0; + // Sets/Release a native capture on host windows. + virtual void SetNativeCapture() = 0; + virtual void ReleaseNativeCapture() = 0; + + protected: + virtual ~CaptureDelegate() {} +}; + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_CAPTURE_DELEGATE_H_ diff --git a/chromium/ui/aura/client/cursor_client.cc b/chromium/ui/aura/client/cursor_client.cc new file mode 100644 index 00000000000..ca105ed7556 --- /dev/null +++ b/chromium/ui/aura/client/cursor_client.cc @@ -0,0 +1,28 @@ +// 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 "ui/aura/client/cursor_client.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::CursorClient*) + +namespace aura { +namespace client { + +// A property key to store a client that handles window moves. +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + CursorClient*, kCursorClientKey, NULL); + +void SetCursorClient(Window* window, CursorClient* client) { + window->SetProperty(kCursorClientKey, client); +} + +CursorClient* GetCursorClient(Window* window) { + return window->GetProperty(kCursorClientKey); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/cursor_client.h b/chromium/ui/aura/client/cursor_client.h new file mode 100644 index 00000000000..acdf255b850 --- /dev/null +++ b/chromium/ui/aura/client/cursor_client.h @@ -0,0 +1,79 @@ +// 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. + +#ifndef UI_AURA_CLIENT_CURSOR_CLIENT_H_ +#define UI_AURA_CLIENT_CURSOR_CLIENT_H_ + +#include "base/strings/string16.h" +#include "ui/aura/aura_export.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Display; +} + +namespace aura { +class Window; +namespace client { +class CursorClientObserver; + +// An interface that receives cursor change events. +class AURA_EXPORT CursorClient { + public: + // Notes that |window| has requested the change to |cursor|. + virtual void SetCursor(gfx::NativeCursor cursor) = 0; + + // Shows the cursor. This does not take effect When mouse events are disabled. + virtual void ShowCursor() = 0; + + // Hides the cursor. Mouse events keep being sent even when the cursor is + // invisible. + virtual void HideCursor() = 0; + + // Sets the scale of the mouse cursor icon. + virtual void SetScale(float scale) = 0; + + // Gets whether the cursor is visible. + virtual bool IsCursorVisible() const = 0; + + // Makes mouse events start being sent and shows the cursor if it was hidden + // with DisableMouseEvents. + virtual void EnableMouseEvents() = 0; + + // Makes mouse events stop being sent and hides the cursor if it is visible. + virtual void DisableMouseEvents() = 0; + + // Returns true if mouse events are enabled. + virtual bool IsMouseEventsEnabled() const = 0; + + // Sets the display for the cursor. + virtual void SetDisplay(const gfx::Display& display) = 0; + + // Locks the cursor change. The cursor type, cursor visibility, and mouse + // events enable state never change as long as lock is held by anyone. + virtual void LockCursor() = 0; + + // Unlocks the cursor change. If all the locks are released, the cursor type, + // cursor visibility, and mouse events enable state are restored to the ones + // set by the lastest call of SetCursor, ShowCursor/HideCursor, and + // EnableMouseEvents/DisableMouseEvents. + virtual void UnlockCursor() = 0; + + // Used to add or remove a CursorClientObserver. + virtual void AddObserver(CursorClientObserver* observer) = 0; + virtual void RemoveObserver(CursorClientObserver* observer) = 0; + + protected: + virtual ~CursorClient() {} +}; + +// Sets/Gets the activation client for the specified window. +AURA_EXPORT void SetCursorClient(Window* window, + CursorClient* client); +AURA_EXPORT CursorClient* GetCursorClient(Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_CURSOR_CLIENT_H_ diff --git a/chromium/ui/aura/client/cursor_client_observer.cc b/chromium/ui/aura/client/cursor_client_observer.cc new file mode 100644 index 00000000000..6e40e60c9d0 --- /dev/null +++ b/chromium/ui/aura/client/cursor_client_observer.cc @@ -0,0 +1,7 @@ +// Copyright (c) 2013 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. + +// Added to make sure that something is built in aura with this +// include so that the symbol CursorClientObserver is exported. +#include "ui/aura/client/cursor_client_observer.h" diff --git a/chromium/ui/aura/client/cursor_client_observer.h b/chromium/ui/aura/client/cursor_client_observer.h new file mode 100644 index 00000000000..1a6d1a83048 --- /dev/null +++ b/chromium/ui/aura/client/cursor_client_observer.h @@ -0,0 +1,24 @@ +// Copyright (c) 2013 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. + +#ifndef UI_AURA_CLIENT_CURSOR_CLIENT_OBSERVER_H_ +#define UI_AURA_CLIENT_CURSOR_CLIENT_OBSERVER_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { +namespace client { + +class AURA_EXPORT CursorClientObserver { + public: + virtual void OnCursorVisibilityChanged(bool is_visible) = 0; + + protected: + virtual ~CursorClientObserver() {} +}; + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_CURSOR_CLIENT_OBSERVER_H_ diff --git a/chromium/ui/aura/client/default_capture_client.cc b/chromium/ui/aura/client/default_capture_client.cc new file mode 100644 index 00000000000..94f5f810532 --- /dev/null +++ b/chromium/ui/aura/client/default_capture_client.cc @@ -0,0 +1,51 @@ +// 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 "ui/aura/client/default_capture_client.h" + +#include "ui/aura/root_window.h" + +namespace aura { +namespace client { + +DefaultCaptureClient::DefaultCaptureClient(RootWindow* root_window) + : root_window_(root_window), + capture_window_(NULL) { + client::SetCaptureClient(root_window_, this); +} + +DefaultCaptureClient::~DefaultCaptureClient() { + client::SetCaptureClient(root_window_, NULL); +} + +void DefaultCaptureClient::SetCapture(Window* window) { + if (capture_window_ == window) + return; + if (window) + root_window_->gesture_recognizer()-> + TransferEventsTo(capture_window_, window); + + Window* old_capture_window = capture_window_; + capture_window_ = window; + + if (capture_window_) + root_window_->SetNativeCapture(); + else + root_window_->ReleaseNativeCapture(); + + root_window_->UpdateCapture(old_capture_window, capture_window_); +} + +void DefaultCaptureClient::ReleaseCapture(Window* window) { + if (capture_window_ != window) + return; + SetCapture(NULL); +} + +Window* DefaultCaptureClient::GetCaptureWindow() { + return capture_window_; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/default_capture_client.h b/chromium/ui/aura/client/default_capture_client.h new file mode 100644 index 00000000000..a628b2d96ef --- /dev/null +++ b/chromium/ui/aura/client/default_capture_client.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef UI_AURA_CLIENT_DEFAULT_CAPTURE_CLIENT_H_ +#define UI_AURA_CLIENT_DEFAULT_CAPTURE_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/client/capture_client.h" + +namespace aura { +namespace client { + +class AURA_EXPORT DefaultCaptureClient : public client::CaptureClient { + public: + explicit DefaultCaptureClient(RootWindow* root_window); + virtual ~DefaultCaptureClient(); + + private: + // Overridden from client::CaptureClient: + virtual void SetCapture(Window* window) OVERRIDE; + virtual void ReleaseCapture(Window* window) OVERRIDE; + virtual Window* GetCaptureWindow() OVERRIDE; + + RootWindow* root_window_; + Window* capture_window_; + + DISALLOW_COPY_AND_ASSIGN(DefaultCaptureClient); +}; + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_DEFAULT_CAPTURE_CLIENT_H_ diff --git a/chromium/ui/aura/client/dispatcher_client.cc b/chromium/ui/aura/client/dispatcher_client.cc new file mode 100644 index 00000000000..8a2d1c36431 --- /dev/null +++ b/chromium/ui/aura/client/dispatcher_client.cc @@ -0,0 +1,26 @@ +// 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 "ui/aura/client/dispatcher_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::DispatcherClient*); + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY(DispatcherClient*, kDispatcherClientKey, NULL); + +void SetDispatcherClient(RootWindow* root_window, DispatcherClient* client) { + root_window->SetProperty(kDispatcherClientKey, client); +} + +DispatcherClient* GetDispatcherClient(RootWindow* root_window) { + return root_window ? root_window->GetProperty(kDispatcherClientKey) : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/dispatcher_client.h b/chromium/ui/aura/client/dispatcher_client.h new file mode 100644 index 00000000000..c775611db71 --- /dev/null +++ b/chromium/ui/aura/client/dispatcher_client.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef UI_AURA_CLIENT_DISPATCHER_CLIENT_H_ +#define UI_AURA_CLIENT_DISPATCHER_CLIENT_H_ + +#include "base/message_loop/message_loop.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/window.h" + +namespace aura { +class RootWindow; +namespace client { + +// An interface implemented by an object which handles nested dispatchers. +class AURA_EXPORT DispatcherClient { + public: + virtual void RunWithDispatcher(base::MessageLoop::Dispatcher* dispatcher, + aura::Window* associated_window, + bool nestable_tasks_allowed) = 0; +}; + +AURA_EXPORT void SetDispatcherClient(RootWindow* root_window, + DispatcherClient* client); +AURA_EXPORT DispatcherClient* GetDispatcherClient(RootWindow* root_window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_DISPATCHER_CLIENT_H_ diff --git a/chromium/ui/aura/client/drag_drop_client.cc b/chromium/ui/aura/client/drag_drop_client.cc new file mode 100644 index 00000000000..1122c1bc2d6 --- /dev/null +++ b/chromium/ui/aura/client/drag_drop_client.cc @@ -0,0 +1,28 @@ +// 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 "ui/aura/client/drag_drop_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::DragDropClient*) + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + DragDropClient*, kRootWindowDragDropClientKey, NULL); + +void SetDragDropClient(RootWindow* root_window, DragDropClient* client) { + root_window->SetProperty(kRootWindowDragDropClientKey, client); +} + +DragDropClient* GetDragDropClient(RootWindow* root_window) { + return root_window ? + root_window->GetProperty(kRootWindowDragDropClientKey) : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/drag_drop_client.h b/chromium/ui/aura/client/drag_drop_client.h new file mode 100644 index 00000000000..c7b145b41aa --- /dev/null +++ b/chromium/ui/aura/client/drag_drop_client.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef UI_AURA_CLIENT_DRAG_DROP_CLIENT_H_ +#define UI_AURA_CLIENT_DRAG_DROP_CLIENT_H_ + +#include "ui/aura/aura_export.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Point; +} + +namespace ui { +class LocatedEvent; +class OSExchangeData; +} + +namespace aura { +class RootWindow; +class Window; +namespace client { + +// An interface implemented by an object that controls a drag and drop session. +class AURA_EXPORT DragDropClient { + public: + virtual ~DragDropClient() {} + + // Initiates a drag and drop session. Returns the drag operation that was + // applied at the end of the drag drop session. |root_location| is in the + // RootWindow's coordinate system. + virtual int StartDragAndDrop(const ui::OSExchangeData& data, + aura::RootWindow* root_window, + aura::Window* source_window, + const gfx::Point& root_location, + int operation, + ui::DragDropTypes::DragEventSource source) = 0; + + // Called when mouse is dragged during a drag and drop. + virtual void DragUpdate(aura::Window* target, + const ui::LocatedEvent& event) = 0; + + // Called when mouse is released during a drag and drop. + virtual void Drop(aura::Window* target, + const ui::LocatedEvent& event) = 0; + + // Called when a drag and drop session is cancelled. + virtual void DragCancel() = 0; + + // Returns true if a drag and drop session is in progress. + virtual bool IsDragDropInProgress() = 0; +}; + +AURA_EXPORT void SetDragDropClient(RootWindow* root_window, + DragDropClient* client); +AURA_EXPORT DragDropClient* GetDragDropClient(RootWindow* root_window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_DRAG_DROP_CLIENT_H_ diff --git a/chromium/ui/aura/client/drag_drop_delegate.cc b/chromium/ui/aura/client/drag_drop_delegate.cc new file mode 100644 index 00000000000..92d986be8da --- /dev/null +++ b/chromium/ui/aura/client/drag_drop_delegate.cc @@ -0,0 +1,27 @@ +// 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 "ui/aura/client/drag_drop_delegate.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::DragDropDelegate*) + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + DragDropDelegate*, kDragDropDelegateKey, NULL); + +void SetDragDropDelegate(Window* window, DragDropDelegate* delegate) { + window->SetProperty(kDragDropDelegateKey, delegate); +} + +DragDropDelegate* GetDragDropDelegate(Window* window) { + return window->GetProperty(kDragDropDelegateKey); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/drag_drop_delegate.h b/chromium/ui/aura/client/drag_drop_delegate.h new file mode 100644 index 00000000000..dde7f32ec73 --- /dev/null +++ b/chromium/ui/aura/client/drag_drop_delegate.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef UI_AURA_CLIENT_DRAG_DROP_DELEGATE_H_ +#define UI_AURA_CLIENT_DRAG_DROP_DELEGATE_H_ + +#include "ui/aura/aura_export.h" + +namespace ui { +class DropTargetEvent; +} + +namespace aura { +class Window; +namespace client { + +// Delegate interface for drag and drop actions on aura::Window. +class AURA_EXPORT DragDropDelegate { + public: + // OnDragEntered is invoked when the mouse enters this window during a drag & + // drop session. This is immediately followed by an invocation of + // OnDragUpdated, and eventually one of OnDragExited or OnPerformDrop. + virtual void OnDragEntered(const ui::DropTargetEvent& event) = 0; + + // Invoked during a drag and drop session while the mouse is over the window. + // This should return a bitmask of the DragDropTypes::DragOperation supported + // based on the location of the event. Return 0 to indicate the drop should + // not be accepted. + virtual int OnDragUpdated(const ui::DropTargetEvent& event) = 0; + + // Invoked during a drag and drop session when the mouse exits the window, or + // when the drag session was canceled and the mouse was over the window. + virtual void OnDragExited() = 0; + + // Invoked during a drag and drop session when OnDragUpdated returns a valid + // operation and the user release the mouse. + virtual int OnPerformDrop(const ui::DropTargetEvent& event) = 0; + + protected: + virtual ~DragDropDelegate() {} +}; + +AURA_EXPORT void SetDragDropDelegate(Window* window, + DragDropDelegate* delegate); +AURA_EXPORT DragDropDelegate* GetDragDropDelegate(Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_DRAG_DROP_DELEGATE_H_ diff --git a/chromium/ui/aura/client/event_client.cc b/chromium/ui/aura/client/event_client.cc new file mode 100644 index 00000000000..dfb3410da47 --- /dev/null +++ b/chromium/ui/aura/client/event_client.cc @@ -0,0 +1,27 @@ +// 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 "ui/aura/client/event_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::EventClient*) + +namespace aura { +namespace client { + +DEFINE_WINDOW_PROPERTY_KEY(EventClient*, kRootWindowEventClientKey, NULL); + +void SetEventClient(RootWindow* root_window, EventClient* client) { + root_window->SetProperty(kRootWindowEventClientKey, client); +} + +EventClient* GetEventClient(const RootWindow* root_window) { + return root_window ? + root_window->GetProperty(kRootWindowEventClientKey) : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/event_client.h b/chromium/ui/aura/client/event_client.h new file mode 100644 index 00000000000..31565e2053b --- /dev/null +++ b/chromium/ui/aura/client/event_client.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef UI_AURA_CLIENT_EVENT_CLIENT_H_ +#define UI_AURA_CLIENT_EVENT_CLIENT_H_ + +#include "ui/aura/aura_export.h" +#include "ui/aura/window.h" + +namespace aura { + +class Event; +class RootWindow; + +namespace client { + +// An interface implemented by an object that alters event processing. +class AURA_EXPORT EventClient { + public: + // Returns true if events can be processed by |window| or any of its children. + virtual bool CanProcessEventsWithinSubtree(const Window* window) const = 0; + + // Returns the top level EventTarget for the current environment. + virtual ui::EventTarget* GetToplevelEventTarget() = 0; + + protected: + virtual ~EventClient() {} +}; + +// Sets/Gets the event client on the RootWindow. +AURA_EXPORT void SetEventClient(RootWindow* root_window, EventClient* client); +AURA_EXPORT EventClient* GetEventClient(const RootWindow* root_window); + +} // namespace clients +} // namespace aura + +#endif // UI_AURA_CLIENT_EVENT_CLIENT_H_ diff --git a/chromium/ui/aura/client/focus_change_observer.cc b/chromium/ui/aura/client/focus_change_observer.cc new file mode 100644 index 00000000000..8db471dfd80 --- /dev/null +++ b/chromium/ui/aura/client/focus_change_observer.cc @@ -0,0 +1,28 @@ +// 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 "ui/aura/client/focus_change_observer.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::FocusChangeObserver*) + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + FocusChangeObserver*, kFocusChangeObserverKey, NULL); + +FocusChangeObserver* GetFocusChangeObserver(Window* window) { + return window ? window->GetProperty(kFocusChangeObserverKey) : NULL; +} + +void SetFocusChangeObserver(Window* window, + FocusChangeObserver* focus_change_observer) { + window->SetProperty(kFocusChangeObserverKey, focus_change_observer); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/focus_change_observer.h b/chromium/ui/aura/client/focus_change_observer.h new file mode 100644 index 00000000000..46db114b20c --- /dev/null +++ b/chromium/ui/aura/client/focus_change_observer.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef UI_AURA_CLIENT_FOCUS_CHANGE_OBSERVER_H_ +#define UI_AURA_CLIENT_FOCUS_CHANGE_OBSERVER_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { +class Window; +namespace client { + +// TODO(beng): this interface will be OBSOLETE by FocusChangeEvent. +class AURA_EXPORT FocusChangeObserver { + public: + // Called when focus moves from |lost_focus| to |gained_focus|. + virtual void OnWindowFocused(Window* gained_focus, Window* lost_focus) = 0; + + protected: + virtual ~FocusChangeObserver() {} +}; + +AURA_EXPORT FocusChangeObserver* GetFocusChangeObserver(Window* window); +AURA_EXPORT void SetFocusChangeObserver( + Window* window, + FocusChangeObserver* focus_change_observer); + + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_FOCUS_CHANGE_OBSERVER_H_ diff --git a/chromium/ui/aura/client/focus_client.cc b/chromium/ui/aura/client/focus_client.cc new file mode 100644 index 00000000000..83ad1a9e28a --- /dev/null +++ b/chromium/ui/aura/client/focus_client.cc @@ -0,0 +1,33 @@ +// 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 "ui/aura/client/focus_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(AURA_EXPORT, aura::Window*) +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::FocusClient*) + +namespace aura { +namespace client { + +DEFINE_WINDOW_PROPERTY_KEY(FocusClient*, kRootWindowFocusClientKey, NULL); + +void SetFocusClient(RootWindow* root_window, FocusClient* client) { + root_window->SetProperty(kRootWindowFocusClientKey, client); +} + +FocusClient* GetFocusClient(Window* window) { + return GetFocusClient(static_cast<const Window*>(window)); +} + +FocusClient* GetFocusClient(const Window* window) { + const RootWindow* root_window = window->GetRootWindow(); + return root_window ? + root_window->GetProperty(kRootWindowFocusClientKey) : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/focus_client.h b/chromium/ui/aura/client/focus_client.h new file mode 100644 index 00000000000..ff5f7f6b542 --- /dev/null +++ b/chromium/ui/aura/client/focus_client.h @@ -0,0 +1,57 @@ +// 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. + +#ifndef UI_AURA_CLIENT_FOCUS_CLIENT_H_ +#define UI_AURA_CLIENT_FOCUS_CLIENT_H_ + +#include "ui/aura/aura_export.h" + +namespace ui { +class Event; +} + +namespace aura { +class RootWindow; +class Window; + +namespace client { +class FocusChangeObserver; + +// An interface implemented by an object that manages window focus. +class AURA_EXPORT FocusClient { + public: + virtual ~FocusClient() {} + + // TODO(beng): these methods will be OBSOLETE by FocusChangeEvent. + virtual void AddObserver(FocusChangeObserver* observer) = 0; + virtual void RemoveObserver(FocusChangeObserver* observer) = 0; + + // Focuses |window|. Passing NULL clears focus. + virtual void FocusWindow(Window* window) = 0; + + // Sets focus to |window| if it's within the active window. Not intended as a + // general purpose API, use FocusWindow() instead. + virtual void ResetFocusWithinActiveWindow(Window* window) = 0; + + // Retrieves the focused window, or NULL if there is none. + virtual Window* GetFocusedWindow() = 0; + + // TODO(beng): temporary compat until FocusController is on. + // Called when |window|'s disposition in |root_window| changes such that + // focus must be shifted away from it. |destroyed| is true if the disposition + // change is that |window| is being destroyed. + virtual void OnWindowHiddenInRootWindow(aura::Window* window, + aura::RootWindow* root_window, + bool destroyed) = 0; +}; + +// Sets/Gets the focus client on the RootWindow. +AURA_EXPORT void SetFocusClient(RootWindow* root_window, FocusClient* client); +AURA_EXPORT FocusClient* GetFocusClient(Window* window); +AURA_EXPORT FocusClient* GetFocusClient(const Window* window); + +} // namespace clients +} // namespace aura + +#endif // UI_AURA_CLIENT_FOCUS_CLIENT_H_ diff --git a/chromium/ui/aura/client/screen_position_client.cc b/chromium/ui/aura/client/screen_position_client.cc new file mode 100644 index 00000000000..ac131561a79 --- /dev/null +++ b/chromium/ui/aura/client/screen_position_client.cc @@ -0,0 +1,29 @@ +// 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 "ui/aura/client/screen_position_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::ScreenPositionClient*) + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY(ScreenPositionClient*, + kScreenPositionClientKey, + NULL); + +void SetScreenPositionClient(RootWindow* window, + ScreenPositionClient* client) { + window->SetProperty(kScreenPositionClientKey, client); +} + +ScreenPositionClient* GetScreenPositionClient(const RootWindow* window) { + return window ? window->GetProperty(kScreenPositionClientKey) : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/screen_position_client.h b/chromium/ui/aura/client/screen_position_client.h new file mode 100644 index 00000000000..e5505eb75ef --- /dev/null +++ b/chromium/ui/aura/client/screen_position_client.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef UI_AURA_SCREEN_POSITION_CLIENT_H_ +#define UI_AURA_SCREEN_POSITION_CLIENT_H_ + +#include "ui/aura/aura_export.h" +#include "ui/aura/window.h" + +namespace gfx { +class Display; +class Rect; +} + +namespace aura { + +class Event; +class RootWindow; +class Window; + +namespace client { + +// An interface implemented by an object that changes coordinates on a +// RootWindow into system coordinates. +class AURA_EXPORT ScreenPositionClient { + public: + // Converts the |screen_point| from a given |window|'s coordinate space + // into screen coordinate space. + virtual void ConvertPointToScreen(const Window* window, + gfx::Point* point) = 0; + virtual void ConvertPointFromScreen(const Window* window, + gfx::Point* point) = 0; + // Converts the |screen_point| from root window host's coordinate of + // into screen coordinate space. + // A typical example of using this function instead of ConvertPointToScreen is + // when X's native input is captured by a drag operation. + // See the comments for ash::GetRootWindowRelativeToWindow for details. + virtual void ConvertHostPointToScreen(aura::RootWindow* root_window, + gfx::Point* point) = 0; + // Sets the bounds of the window. The implementation is responsible + // for finding out and translating the right coordinates for the |window|. + virtual void SetBounds(Window* window, + const gfx::Rect& bounds, + const gfx::Display& display) = 0; + virtual ~ScreenPositionClient() {} +}; + +// Sets/Gets the activation client on the Window. +AURA_EXPORT void SetScreenPositionClient(RootWindow* window, + ScreenPositionClient* client); +AURA_EXPORT ScreenPositionClient* GetScreenPositionClient( + const RootWindow* window); + +} // namespace clients +} // namespace aura + +#endif // UI_AURA_SCREEN_POSITION_CLIENT_H_ diff --git a/chromium/ui/aura/client/stacking_client.cc b/chromium/ui/aura/client/stacking_client.cc new file mode 100644 index 00000000000..5d93d984840 --- /dev/null +++ b/chromium/ui/aura/client/stacking_client.cc @@ -0,0 +1,38 @@ +// 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 "ui/aura/client/stacking_client.h" + +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::StackingClient*) + +namespace aura { +namespace client { + +DEFINE_WINDOW_PROPERTY_KEY( + StackingClient*, kRootWindowStackingClientKey, NULL); + +void SetStackingClient(Window* window, StackingClient* stacking_client) { + DCHECK(window); + + RootWindow* root_window = window->GetRootWindow(); + DCHECK(root_window); + root_window->SetProperty(kRootWindowStackingClientKey, stacking_client); +} + +StackingClient* GetStackingClient(Window* window) { + DCHECK(window); + RootWindow* root_window = window->GetRootWindow(); + DCHECK(root_window); + StackingClient* root_window_stacking_client = + root_window->GetProperty(kRootWindowStackingClientKey); + DCHECK(root_window_stacking_client); + return root_window_stacking_client; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/stacking_client.h b/chromium/ui/aura/client/stacking_client.h new file mode 100644 index 00000000000..89f83eb4601 --- /dev/null +++ b/chromium/ui/aura/client/stacking_client.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef UI_AURA_CLIENT_STACKING_CLIENT_H_ +#define UI_AURA_CLIENT_STACKING_CLIENT_H_ + +#include "ui/aura/aura_export.h" + +namespace gfx { +class Rect; +} + +namespace aura { +class Window; +namespace client { + +// An interface implemented by an object that stacks windows. +class AURA_EXPORT StackingClient { + public: + virtual ~StackingClient() {} + + // Called by the Window when it looks for a default parent. Returns the + // window that |window| should be added to instead. |context| provides a + // Window (generally a RootWindow) that can be used to determine which + // desktop type the default parent should be chosen from. NOTE: this may + // have side effects. It should only be used when |window| is going to be + // immediately added. + // + // TODO(erg): Remove |context|, and maybe after oshima's patch lands, + // |bounds|. + virtual Window* GetDefaultParent( + Window* context, + Window* window, + const gfx::Rect& bounds) = 0; +}; + +// Set/Get a stacking client for a specific window. Setting the stacking client +// sets the stacking client on the window's RootWindow, not the window itself. +// Likewise getting obtains it from the window's RootWindow. If |window| is +// non-NULL it must be in a RootWindow. If |window| is NULL, the default +// stacking client is used. +AURA_EXPORT void SetStackingClient(Window* window, + StackingClient* stacking_client); +StackingClient* GetStackingClient(Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_STACKING_CLIENT_H_ diff --git a/chromium/ui/aura/client/tooltip_client.cc b/chromium/ui/aura/client/tooltip_client.cc new file mode 100644 index 00000000000..a966c28392c --- /dev/null +++ b/chromium/ui/aura/client/tooltip_client.cc @@ -0,0 +1,39 @@ +// 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 "ui/aura/client/tooltip_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::TooltipClient*) +DECLARE_WINDOW_PROPERTY_TYPE(base::string16*) + +namespace aura { +namespace client { + +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + TooltipClient*, kRootWindowTooltipClientKey, NULL); +DEFINE_LOCAL_WINDOW_PROPERTY_KEY(base::string16*, kTooltipTextKey, NULL); + +void SetTooltipClient(RootWindow* root_window, TooltipClient* client) { + root_window->SetProperty(kRootWindowTooltipClientKey, client); +} + +TooltipClient* GetTooltipClient(RootWindow* root_window) { + return root_window ? + root_window->GetProperty(kRootWindowTooltipClientKey) : NULL; +} + +void SetTooltipText(Window* window, base::string16* tooltip_text) { + window->SetProperty(kTooltipTextKey, tooltip_text); +} + +const base::string16 GetTooltipText(Window* window) { + base::string16* string_ptr = window->GetProperty(kTooltipTextKey); + return string_ptr ? *string_ptr : base::string16(); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/tooltip_client.h b/chromium/ui/aura/client/tooltip_client.h new file mode 100644 index 00000000000..d6d33ceb2fb --- /dev/null +++ b/chromium/ui/aura/client/tooltip_client.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef UI_AURA_CLIENT_TOOLTIP_CLIENT_H_ +#define UI_AURA_CLIENT_TOOLTIP_CLIENT_H_ + +#include "ui/aura/aura_export.h" +#include "ui/gfx/font.h" + +namespace aura { +class RootWindow; +class Window; + +namespace client { + +class AURA_EXPORT TooltipClient { + public: + // Informs the shell tooltip manager of change in tooltip for window |target|. + virtual void UpdateTooltip(Window* target) = 0; + + // Sets the time after which the tooltip is hidden for Window |target|. If + // |timeout_in_ms| is <= 0, the tooltip is shown indefinitely. + virtual void SetTooltipShownTimeout(Window* target, int timeout_in_ms) = 0; + + // Enables/Disables tooltips. + virtual void SetTooltipsEnabled(bool enable) = 0; +}; + +AURA_EXPORT void SetTooltipClient(RootWindow* root_window, + TooltipClient* client); +AURA_EXPORT TooltipClient* GetTooltipClient(RootWindow* root_window); + +AURA_EXPORT void SetTooltipText(Window* window, base::string16* tooltip_text); +AURA_EXPORT const base::string16 GetTooltipText(Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_TOOLTIP_CLIENT_H_ diff --git a/chromium/ui/aura/client/user_action_client.cc b/chromium/ui/aura/client/user_action_client.cc new file mode 100644 index 00000000000..cfb5fe1b42f --- /dev/null +++ b/chromium/ui/aura/client/user_action_client.cc @@ -0,0 +1,27 @@ +// 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 "ui/aura/client/user_action_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +namespace aura { +namespace client { + +DEFINE_WINDOW_PROPERTY_KEY(UserActionClient*, + kRootWindowUserActionClientKey, + NULL); + +void SetUserActionClient(RootWindow* root_window, UserActionClient* client) { + root_window->SetProperty(kRootWindowUserActionClientKey, client); +} + +UserActionClient* GetUserActionClient(RootWindow* root_window) { + return root_window ? + root_window->GetProperty(kRootWindowUserActionClientKey) : NULL; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/user_action_client.h b/chromium/ui/aura/client/user_action_client.h new file mode 100644 index 00000000000..16f0b9ff09d --- /dev/null +++ b/chromium/ui/aura/client/user_action_client.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef UI_AURA_CLIENT_USER_ACTION_CLIENT_H_ +#define UI_AURA_CLIENT_USER_ACTION_CLIENT_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { +class RootWindow; +namespace client { + +// An interface for handling a user action that isn't handled by the standard +// event path. +class AURA_EXPORT UserActionClient { + public: + enum Command { + BACK = 0, + FORWARD, + }; + + // Returns true if the command was handled and false otherwise. + virtual bool OnUserAction(Command command) = 0; + + virtual ~UserActionClient() {} +}; + +// Sets/gets the client for handling user action on the specified root window. +AURA_EXPORT void SetUserActionClient(RootWindow* root_window, + UserActionClient* client); +AURA_EXPORT UserActionClient* GetUserActionClient(RootWindow* root_window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_USER_ACTION_CLIENT_H_ diff --git a/chromium/ui/aura/client/visibility_client.cc b/chromium/ui/aura/client/visibility_client.cc new file mode 100644 index 00000000000..5fc7beed7c1 --- /dev/null +++ b/chromium/ui/aura/client/visibility_client.cc @@ -0,0 +1,35 @@ +// 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 "ui/aura/client/visibility_client.h" + +#include "ui/aura/root_window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::VisibilityClient*) + +namespace aura { +namespace client { + +// A property key to store a client that handles window visibility changes. +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + VisibilityClient*, kWindowVisibilityClientKey, NULL); + + +void SetVisibilityClient(Window* window, VisibilityClient* client) { + window->SetProperty(kWindowVisibilityClientKey, client); +} + +VisibilityClient* GetVisibilityClient(Window* window) { + VisibilityClient* visibility_client = NULL; + aura::Window* current = window; + do { + visibility_client = current->GetProperty(kWindowVisibilityClientKey); + current = current->parent(); + } while (current && !visibility_client); + return visibility_client; +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/visibility_client.h b/chromium/ui/aura/client/visibility_client.h new file mode 100644 index 00000000000..b36cddc41b8 --- /dev/null +++ b/chromium/ui/aura/client/visibility_client.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef UI_AURA_CLIENT_VISIBILITY_CLIENT_H_ +#define UI_AURA_CLIENT_VISIBILITY_CLIENT_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { +class Window; +namespace client { + +// An interface implemented by an object that manages the visibility of Windows' +// layers as Window visibility changes. +class AURA_EXPORT VisibilityClient { + public: + // Called when |window|'s visibility is changing to |visible|. The implementor + // can decide whether or not to pass on the visibility to the underlying + // layer. + virtual void UpdateLayerVisibility(Window* window, bool visible) = 0; + + protected: + virtual ~VisibilityClient() {} +}; + +// Sets the VisibilityClient on the Window. +AURA_EXPORT void SetVisibilityClient(Window* window, VisibilityClient* client); + +// Gets the VisibilityClient for the window. This will crawl up |window|'s +// hierarchy until it finds one. +AURA_EXPORT VisibilityClient* GetVisibilityClient(Window* window); + +} // namespace clients +} // namespace aura + +#endif // UI_AURA_CLIENT_VISIBILITY_CLIENT_H_ diff --git a/chromium/ui/aura/client/window_move_client.cc b/chromium/ui/aura/client/window_move_client.cc new file mode 100644 index 00000000000..c65452db607 --- /dev/null +++ b/chromium/ui/aura/client/window_move_client.cc @@ -0,0 +1,28 @@ +// 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 "ui/aura/client/window_move_client.h" + +#include "ui/aura/window.h" +#include "ui/aura/window_property.h" + +DECLARE_WINDOW_PROPERTY_TYPE(aura::client::WindowMoveClient*) + +namespace aura { +namespace client { + +// A property key to store a client that handles window moves. +DEFINE_LOCAL_WINDOW_PROPERTY_KEY( + WindowMoveClient*, kWindowMoveClientKey, NULL); + +void SetWindowMoveClient(Window* window, WindowMoveClient* client) { + window->SetProperty(kWindowMoveClientKey, client); +} + +WindowMoveClient* GetWindowMoveClient(Window* window) { + return window->GetProperty(kWindowMoveClientKey); +} + +} // namespace client +} // namespace aura diff --git a/chromium/ui/aura/client/window_move_client.h b/chromium/ui/aura/client/window_move_client.h new file mode 100644 index 00000000000..15e4245757b --- /dev/null +++ b/chromium/ui/aura/client/window_move_client.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef UI_AURA_CLIENT_WINDOW_MOVE_CLIENT_H_ +#define UI_AURA_CLIENT_WINDOW_MOVE_CLIENT_H_ + +#include "ui/aura/aura_export.h" +#include "ui/gfx/vector2d.h" + +namespace gfx { +class Point; +} + +namespace aura { +class Window; +namespace client { + +enum WindowMoveResult { + MOVE_SUCCESSFUL, // Moving window was successful. + MOVE_CANCELED // Moving window was canceled. +}; + +enum WindowMoveSource { + WINDOW_MOVE_SOURCE_MOUSE, + WINDOW_MOVE_SOURCE_TOUCH, +}; + +// An interface implemented by an object that manages programatically keyed +// window moving. +class AURA_EXPORT WindowMoveClient { + public: + // Starts a nested message loop for moving the window. |drag_offset| is the + // offset from the window origin to the cursor when the drag was started. + // Returns MOVE_SUCCESSFUL if the move has completed successfully, or + // MOVE_CANCELED otherwise. + virtual WindowMoveResult RunMoveLoop(Window* window, + const gfx::Vector2d& drag_offset, + WindowMoveSource source) = 0; + + // Ends a previously started move loop. + virtual void EndMoveLoop() = 0; + + protected: + virtual ~WindowMoveClient() {} +}; + +// Sets/Gets the activation client for the specified window. +AURA_EXPORT void SetWindowMoveClient(Window* window, + WindowMoveClient* client); +AURA_EXPORT WindowMoveClient* GetWindowMoveClient(Window* window); + +} // namespace client +} // namespace aura + +#endif // UI_AURA_CLIENT_WINDOW_MOVE_CLIENT_H_ diff --git a/chromium/ui/aura/client/window_types.h b/chromium/ui/aura/client/window_types.h new file mode 100644 index 00000000000..f0a7b777a8f --- /dev/null +++ b/chromium/ui/aura/client/window_types.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef UI_AURA_WINDOW_TYPES_H_ +#define UI_AURA_WINDOW_TYPES_H_ + +namespace aura { +namespace client { + +// This isn't a property because it can't change after the window has been +// initialized. It's in client because the Aura Client application derives +// meaning from these values, not Aura itself. +enum WindowType { + WINDOW_TYPE_UNKNOWN = 0, + + // Regular windows that should be laid out by the client. + WINDOW_TYPE_NORMAL, + + // Miscellaneous windows that should not be laid out by the shell. + WINDOW_TYPE_POPUP, + + // A window intended as a control. Not laid out by the shell. + WINDOW_TYPE_CONTROL, + + // Always on top windows aligned to bottom right of screen. + WINDOW_TYPE_PANEL, + + WINDOW_TYPE_MENU, + WINDOW_TYPE_TOOLTIP, +}; + +} // namespace client +} // namespace aura + +#endif // UI_AURA_WINDOW_TYPES_H_ diff --git a/chromium/ui/aura/demo/demo_main.cc b/chromium/ui/aura/demo/demo_main.cc new file mode 100644 index 00000000000..06938d87419 --- /dev/null +++ b/chromium/ui/aura/demo/demo_main.cc @@ -0,0 +1,167 @@ +// 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 "base/at_exit.h" +#include "base/command_line.h" +#include "base/i18n/icu_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "third_party/skia/include/core/SkXfermode.h" +#include "ui/aura/client/default_capture_client.h" +#include "ui/aura/client/stacking_client.h" +#include "ui/aura/env.h" +#include "ui/aura/focus_manager.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/test_screen.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/base/events/event.h" +#include "ui/base/hit_test.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/ui_base_paths.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/rect.h" + +#if defined(USE_X11) +#include "base/message_loop/message_pump_aurax11.h" +#endif + +namespace { + +// Trivial WindowDelegate implementation that draws a colored background. +class DemoWindowDelegate : public aura::WindowDelegate { + public: + explicit DemoWindowDelegate(SkColor color) : color_(color) {} + + // Overridden from WindowDelegate: + virtual gfx::Size GetMinimumSize() const OVERRIDE { + return gfx::Size(); + } + + virtual gfx::Size GetMaximumSize() const OVERRIDE { + return gfx::Size(); + } + + virtual void OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) OVERRIDE {} + virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE { + return gfx::kNullCursor; + } + virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE { + return HTCAPTION; + } + virtual bool ShouldDescendIntoChildForEventHandling( + aura::Window* child, + const gfx::Point& location) OVERRIDE { + return true; + } + virtual bool CanFocus() OVERRIDE { return true; } + virtual void OnCaptureLost() OVERRIDE {} + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { + canvas->DrawColor(color_, SkXfermode::kSrc_Mode); + } + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {} + virtual void OnWindowDestroying() OVERRIDE {} + virtual void OnWindowDestroyed() OVERRIDE {} + virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {} + virtual bool HasHitTestMask() const OVERRIDE { return false; } + virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {} + virtual scoped_refptr<ui::Texture> CopyTexture() OVERRIDE { + return scoped_refptr<ui::Texture>(); + } + + private: + SkColor color_; + + DISALLOW_COPY_AND_ASSIGN(DemoWindowDelegate); +}; + +class DemoStackingClient : public aura::client::StackingClient { + public: + explicit DemoStackingClient(aura::RootWindow* root_window) + : root_window_(root_window) { + aura::client::SetStackingClient(root_window_, this); + } + + virtual ~DemoStackingClient() { + aura::client::SetStackingClient(root_window_, NULL); + } + + // Overridden from aura::client::StackingClient: + virtual aura::Window* GetDefaultParent(aura::Window* context, + aura::Window* window, + const gfx::Rect& bounds) OVERRIDE { + if (!capture_client_) { + capture_client_.reset( + new aura::client::DefaultCaptureClient(root_window_)); + } + return root_window_; + } + + private: + aura::RootWindow* root_window_; + + scoped_ptr<aura::client::DefaultCaptureClient> capture_client_; + + DISALLOW_COPY_AND_ASSIGN(DemoStackingClient); +}; + +int DemoMain() { + // Create the message-loop here before creating the root window. + base::MessageLoop message_loop(base::MessageLoop::TYPE_UI); + aura::Env::GetInstance(); + scoped_ptr<aura::TestScreen> test_screen(aura::TestScreen::Create()); + gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, test_screen.get()); + scoped_ptr<aura::RootWindow> root_window( + test_screen->CreateRootWindowForPrimaryDisplay()); + scoped_ptr<DemoStackingClient> stacking_client(new DemoStackingClient( + root_window.get())); + aura::FocusManager focus_manager; + aura::client::SetFocusClient(root_window.get(), &focus_manager); + + // Create a hierarchy of test windows. + DemoWindowDelegate window_delegate1(SK_ColorBLUE); + aura::Window window1(&window_delegate1); + window1.set_id(1); + window1.Init(ui::LAYER_TEXTURED); + window1.SetBounds(gfx::Rect(100, 100, 400, 400)); + window1.Show(); + window1.SetDefaultParentByRootWindow(root_window.get(), gfx::Rect()); + + DemoWindowDelegate window_delegate2(SK_ColorRED); + aura::Window window2(&window_delegate2); + window2.set_id(2); + window2.Init(ui::LAYER_TEXTURED); + window2.SetBounds(gfx::Rect(200, 200, 350, 350)); + window2.Show(); + window2.SetDefaultParentByRootWindow(root_window.get(), gfx::Rect()); + + DemoWindowDelegate window_delegate3(SK_ColorGREEN); + aura::Window window3(&window_delegate3); + window3.set_id(3); + window3.Init(ui::LAYER_TEXTURED); + window3.SetBounds(gfx::Rect(10, 10, 50, 50)); + window3.Show(); + window2.AddChild(&window3); + + root_window->ShowRootWindow(); + base::MessageLoopForUI::current()->Run(); + + return 0; +} + +} // namespace + +int main(int argc, char** argv) { + CommandLine::Init(argc, argv); + + // The exit manager is in charge of calling the dtors of singleton objects. + base::AtExitManager exit_manager; + + ui::RegisterPathProvider(); + icu_util::Initialize(); + ResourceBundle::InitSharedInstanceWithLocale("en-US", NULL); + + return DemoMain(); +} diff --git a/chromium/ui/aura/device_list_updater_aurax11.cc b/chromium/ui/aura/device_list_updater_aurax11.cc new file mode 100644 index 00000000000..0671dfecbfd --- /dev/null +++ b/chromium/ui/aura/device_list_updater_aurax11.cc @@ -0,0 +1,32 @@ +// 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 "ui/aura/device_list_updater_aurax11.h" + +#include <X11/extensions/XInput2.h> + +#include "ui/base/events/event_utils.h" + +namespace aura { + +DeviceListUpdaterAuraX11::DeviceListUpdaterAuraX11() {} + +DeviceListUpdaterAuraX11::~DeviceListUpdaterAuraX11() {} + +base::EventStatus DeviceListUpdaterAuraX11::WillProcessEvent( + const base::NativeEvent& event) { + // XI_HierarchyChanged events are special. There is no window associated with + // these events. So process them directly from here. + if (event->type == GenericEvent && + event->xgeneric.evtype == XI_HierarchyChanged) { + ui::UpdateDeviceList(); + } + + return base::EVENT_CONTINUE; +} + +void DeviceListUpdaterAuraX11::DidProcessEvent(const base::NativeEvent& event) { +} + +} // namespace aura diff --git a/chromium/ui/aura/device_list_updater_aurax11.h b/chromium/ui/aura/device_list_updater_aurax11.h new file mode 100644 index 00000000000..cb8c884fe85 --- /dev/null +++ b/chromium/ui/aura/device_list_updater_aurax11.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef UI_AURA_DEVICE_LIST_UPDATER_AURAX11_H_ +#define UI_AURA_DEVICE_LIST_UPDATER_AURAX11_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_pump_observer.h" + +namespace aura { + +// Filters out global XInput notifications and updates the DeviceList. +class DeviceListUpdaterAuraX11 : public base::MessagePumpObserver { + public: + DeviceListUpdaterAuraX11(); + virtual ~DeviceListUpdaterAuraX11(); + + // Overridden from base::MessagePumpObserver: + virtual base::EventStatus WillProcessEvent( + const base::NativeEvent& event) OVERRIDE; + virtual void DidProcessEvent( + const base::NativeEvent& event) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(DeviceListUpdaterAuraX11); +}; + +} // namespace aura + +#endif // UI_AURA_DEVICE_LIST_UPDATER_AURAX11_H_ diff --git a/chromium/ui/aura/dispatcher_win.cc b/chromium/ui/aura/dispatcher_win.cc new file mode 100644 index 00000000000..7160dc7771d --- /dev/null +++ b/chromium/ui/aura/dispatcher_win.cc @@ -0,0 +1,33 @@ +// 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 "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" + +namespace aura { + +class DispatcherWin : public base::MessageLoop::Dispatcher { + public: + DispatcherWin() {} + virtual ~DispatcherWin() {} + + // Overridden from MessageLoop::Dispatcher: + virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(DispatcherWin); +}; + +bool DispatcherWin::Dispatch(const base::NativeEvent& msg) { + TranslateMessage(&msg); + DispatchMessage(&msg); + return true; +} + +base::MessageLoop::Dispatcher* CreateDispatcher() { + return new DispatcherWin; +} + +} // namespace aura diff --git a/chromium/ui/aura/env.cc b/chromium/ui/aura/env.cc new file mode 100644 index 00000000000..6b2b3e023a3 --- /dev/null +++ b/chromium/ui/aura/env.cc @@ -0,0 +1,117 @@ +// 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 "ui/aura/env.h" + +#include "base/command_line.h" +#include "ui/aura/env_observer.h" +#include "ui/aura/root_window_host.h" +#include "ui/aura/window.h" +#include "ui/compositor/compositor.h" +#include "ui/compositor/compositor_switches.h" + +#if defined(USE_X11) +#include "base/message_loop/message_pump_aurax11.h" +#endif + +namespace aura { + +// static +Env* Env::instance_ = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// Env, public: + +Env::Env() + : mouse_button_flags_(0), + is_touch_down_(false) { +} + +Env::~Env() { +#if defined(USE_X11) + base::MessagePumpAuraX11::Current()->RemoveObserver( + &device_list_updater_aurax11_); +#endif + + FOR_EACH_OBSERVER(EnvObserver, observers_, OnWillDestroyEnv()); + + ui::Compositor::Terminate(); +} + +// static +Env* Env::GetInstance() { + if (!instance_) { + instance_ = new Env; + instance_->Init(); + } + return instance_; +} + +// static +void Env::DeleteInstance() { + delete instance_; + instance_ = NULL; +} + +void Env::AddObserver(EnvObserver* observer) { + observers_.AddObserver(observer); +} + +void Env::RemoveObserver(EnvObserver* observer) { + observers_.RemoveObserver(observer); +} + +#if !defined(OS_MACOSX) +base::MessageLoop::Dispatcher* Env::GetDispatcher() { +#if defined(USE_X11) + return base::MessagePumpAuraX11::Current(); +#else + return dispatcher_.get(); +#endif +} +#endif + +void Env::RootWindowActivated(RootWindow* root_window) { + FOR_EACH_OBSERVER(EnvObserver, observers_, + OnRootWindowActivated(root_window)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Env, private: + +void Env::Init() { +#if !defined(USE_X11) && !defined(USE_OZONE) + dispatcher_.reset(CreateDispatcher()); +#endif +#if defined(USE_X11) + // We can't do this with a root window listener because XI_HierarchyChanged + // messages don't have a target window. + base::MessagePumpAuraX11::Current()->AddObserver( + &device_list_updater_aurax11_); +#endif + ui::Compositor::Initialize(); +} + +void Env::NotifyWindowInitialized(Window* window) { + FOR_EACH_OBSERVER(EnvObserver, observers_, OnWindowInitialized(window)); +} + +void Env::NotifyRootWindowInitialized(RootWindow* root_window) { + FOR_EACH_OBSERVER(EnvObserver, + observers_, + OnRootWindowInitialized(root_window)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Env, ui::EventTarget implementation: + +bool Env::CanAcceptEvent(const ui::Event& event) { + return true; +} + +ui::EventTarget* Env::GetParentTarget() { + return NULL; +} + +} // namespace aura diff --git a/chromium/ui/aura/env.h b/chromium/ui/aura/env.h new file mode 100644 index 00000000000..ad31cd57ac5 --- /dev/null +++ b/chromium/ui/aura/env.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef UI_AURA_ENV_H_ +#define UI_AURA_ENV_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/observer_list.h" +#include "ui/aura/aura_export.h" +#include "ui/base/events/event_handler.h" +#include "ui/base/events/event_target.h" +#include "ui/gfx/point.h" + +#if defined(USE_X11) +#include "ui/aura/device_list_updater_aurax11.h" +#endif + +namespace aura { +class EnvObserver; +class RootWindow; +class Window; + +#if !defined(USE_X11) +// Creates a platform-specific native event dispatcher. +base::MessageLoop::Dispatcher* CreateDispatcher(); +#endif + +// A singleton object that tracks general state within Aura. +// TODO(beng): manage RootWindows. +class AURA_EXPORT Env : public ui::EventTarget { + public: + Env(); + virtual ~Env(); + + static Env* GetInstance(); + static void DeleteInstance(); + + void AddObserver(EnvObserver* observer); + void RemoveObserver(EnvObserver* observer); + + bool is_mouse_button_down() const { return mouse_button_flags_ != 0; } + void set_mouse_button_flags(int mouse_button_flags) { + mouse_button_flags_ = mouse_button_flags; + } + + // Gets/sets the last mouse location seen in a mouse event in the screen + // coordinates. + const gfx::Point& last_mouse_location() const { return last_mouse_location_; } + void set_last_mouse_location(const gfx::Point& last_mouse_location) { + last_mouse_location_ = last_mouse_location; + } + + // Whether any touch device is currently down. + bool is_touch_down() const { return is_touch_down_; } + void set_touch_down(bool value) { is_touch_down_ = value; } + + + // Returns the native event dispatcher. The result should only be passed to + // base::RunLoop(dispatcher), or used to dispatch an event by + // |Dispatch(const NativeEvent&)| on it. It must never be stored. +#if !defined(OS_MACOSX) + base::MessageLoop::Dispatcher* GetDispatcher(); +#endif + + // Invoked by RootWindow when its host is activated. + void RootWindowActivated(RootWindow* root_window); + + private: + friend class Window; + friend class RootWindow; + + void Init(); + + // Called by the Window when it is initialized. Notifies observers. + void NotifyWindowInitialized(Window* window); + + // Called by the RootWindow when it is initialized. Notifies observers. + void NotifyRootWindowInitialized(RootWindow* root_window); + + // Overridden from ui::EventTarget: + virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE; + virtual ui::EventTarget* GetParentTarget() OVERRIDE; + + ObserverList<EnvObserver> observers_; +#if !defined(USE_X11) + scoped_ptr<base::MessageLoop::Dispatcher> dispatcher_; +#endif + + static Env* instance_; + int mouse_button_flags_; + // Location of last mouse event, in screen coordinates. + gfx::Point last_mouse_location_; + bool is_touch_down_; + +#if defined(USE_X11) + DeviceListUpdaterAuraX11 device_list_updater_aurax11_; +#endif + + DISALLOW_COPY_AND_ASSIGN(Env); +}; + +} // namespace aura + +#endif // UI_AURA_ENV_H_ diff --git a/chromium/ui/aura/env_observer.h b/chromium/ui/aura/env_observer.h new file mode 100644 index 00000000000..b624f91b6ec --- /dev/null +++ b/chromium/ui/aura/env_observer.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef UI_AURA_ENV_OBSERVER_H_ +#define UI_AURA_ENV_OBSERVER_H_ + +#include "ui/aura/aura_export.h" + +namespace aura { + +class RootWindow; +class Window; + +class AURA_EXPORT EnvObserver { + public: + // Called when |window| has been initialized. + virtual void OnWindowInitialized(Window* window) = 0; + + // Called when |root_window| has been initialized. + virtual void OnRootWindowInitialized(RootWindow* root_window) {}; + + // Called when a RootWindow's host is activated. + virtual void OnRootWindowActivated(RootWindow* root_window) {} + + // Called right before Env is destroyed. + virtual void OnWillDestroyEnv() {} + + protected: + virtual ~EnvObserver() {} +}; + +} // namespace aura + +#endif // UI_AURA_ENV_OBSERVER_H_ diff --git a/chromium/ui/aura/focus_manager.cc b/chromium/ui/aura/focus_manager.cc new file mode 100644 index 00000000000..921406b4c76 --- /dev/null +++ b/chromium/ui/aura/focus_manager.cc @@ -0,0 +1,105 @@ +// 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 "ui/aura/focus_manager.h" + +#include "ui/aura/client/activation_client.h" +#include "ui/aura/client/focus_change_observer.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window_delegate.h" + +namespace aura { + +//////////////////////////////////////////////////////////////////////////////// +// FocusManager, public: + +FocusManager::FocusManager() : focused_window_(NULL) { +} + +FocusManager::~FocusManager() { +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusManager, client::FocusClient implementation: + +void FocusManager::AddObserver(client::FocusChangeObserver* observer) { + observers_.AddObserver(observer); +} + +void FocusManager::RemoveObserver(client::FocusChangeObserver* observer) { + observers_.RemoveObserver(observer); +} + +void FocusManager::FocusWindow(Window* focused_window) { + if (focused_window == focused_window_) + return; + if (focused_window && !focused_window->CanFocus()) + return; + // The NULL-check of |focused_window| is essential here before asking the + // activation client, since it is valid to clear the focus by calling + // SetFocusedWindow() to NULL. + + if (focused_window) { + RootWindow* root = focused_window->GetRootWindow(); + DCHECK(root); + if (client::GetActivationClient(root) && + !client::GetActivationClient(root)->OnWillFocusWindow( + focused_window, NULL)) { + return; + } + } + + Window* old_focused_window = focused_window_; + focused_window_ = focused_window; + + FOR_EACH_OBSERVER(client::FocusChangeObserver, observers_, + OnWindowFocused(focused_window, old_focused_window)); + client::FocusChangeObserver* observer = + client::GetFocusChangeObserver(old_focused_window); + if (observer) + observer->OnWindowFocused(focused_window_, old_focused_window); + observer = client::GetFocusChangeObserver(focused_window_); + if (observer) + observer->OnWindowFocused(focused_window_, old_focused_window); +} + +void FocusManager::ResetFocusWithinActiveWindow(Window* window) { + FocusWindow(window); +} + +Window* FocusManager::GetFocusedWindow() { + return focused_window_; +} + +void FocusManager::OnWindowHiddenInRootWindow( + aura::Window* window, + aura::RootWindow* root_window, + bool destroyed) { + Window* focused_window = + client::GetFocusClient(root_window)->GetFocusedWindow(); + if (window->Contains(focused_window)) { + Window* focus_to = window->transient_parent(); + if (focus_to) { + // Has to be removed from the transient parent before focusing, + // otherwise |window| will be focused again. + if (destroyed) + focus_to->RemoveTransientChild(window); + } else { + // If the invisible view has no visible transient window, focus to the + // topmost visible parent window. + focus_to = window->parent(); + } + if (focus_to && + (!focus_to->IsVisible() || + !focus_to->CanFocus() || + (client::GetActivationClient(root_window) && + !client::GetActivationClient(root_window)->OnWillFocusWindow( + focus_to, NULL)))) { + focus_to = NULL; + } + client::GetFocusClient(root_window)->FocusWindow(focus_to); + } +} + +} // namespace aura diff --git a/chromium/ui/aura/focus_manager.h b/chromium/ui/aura/focus_manager.h new file mode 100644 index 00000000000..b3a13313edc --- /dev/null +++ b/chromium/ui/aura/focus_manager.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef UI_AURA_FOCUS_MANAGER_H_ +#define UI_AURA_FOCUS_MANAGER_H_ + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/client/focus_client.h" + +namespace ui { +class Event; +} + +namespace aura { + +class Window; + +// An interface implemented by the Desktop to expose the focused window and +// allow for it to be changed. +class AURA_EXPORT FocusManager : public client::FocusClient { + public: + FocusManager(); + virtual ~FocusManager(); + + private: + // Overridden from client::FocusClient: + virtual void AddObserver(client::FocusChangeObserver* observer) OVERRIDE; + virtual void RemoveObserver(client::FocusChangeObserver* observer) OVERRIDE; + virtual void FocusWindow(Window* window) OVERRIDE; + virtual void ResetFocusWithinActiveWindow(Window* window) OVERRIDE; + virtual Window* GetFocusedWindow() OVERRIDE; + virtual void OnWindowHiddenInRootWindow(aura::Window* window, + aura::RootWindow* root_window, + bool destroyed) OVERRIDE; + + protected: + aura::Window* focused_window_; + + ObserverList<client::FocusChangeObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(FocusManager); +}; + +} // namespace aura + +#endif // UI_AURA_FOCUS_MANAGER_H_ diff --git a/chromium/ui/aura/gestures/OWNERS b/chromium/ui/aura/gestures/OWNERS new file mode 100644 index 00000000000..6a1dd965022 --- /dev/null +++ b/chromium/ui/aura/gestures/OWNERS @@ -0,0 +1,2 @@ +rjkroege@chromium.org +sadrul@chromium.org diff --git a/chromium/ui/aura/gestures/gesture_recognizer_unittest.cc b/chromium/ui/aura/gestures/gesture_recognizer_unittest.cc new file mode 100644 index 00000000000..d377ef458a3 --- /dev/null +++ b/chromium/ui/aura/gestures/gesture_recognizer_unittest.cc @@ -0,0 +1,3422 @@ +// 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 "base/command_line.h" +#include "base/memory/scoped_vector.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/timer/timer.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/test_windows.h" +#include "ui/base/events/event.h" +#include "ui/base/events/event_utils.h" +#include "ui/base/gestures/gesture_configuration.h" +#include "ui/base/gestures/gesture_recognizer_impl.h" +#include "ui/base/gestures/gesture_sequence.h" +#include "ui/base/gestures/gesture_types.h" +#include "ui/base/hit_test.h" +#include "ui/base/ui_base_switches.h" +#include "ui/gfx/point.h" +#include "ui/gfx/rect.h" + +#include <queue> + +namespace aura { +namespace test { + +namespace { + +std::string WindowIDAsString(ui::GestureConsumer* consumer) { + return consumer && !consumer->ignores_events() ? + base::IntToString(static_cast<Window*>(consumer)->id()) : "?"; +} + +#define EXPECT_0_EVENTS(events) \ + EXPECT_EQ(0u, events.size()) + +#define EXPECT_1_EVENT(events, e0) \ + EXPECT_EQ(1u, events.size()); \ + EXPECT_EQ(e0, events[0]) + +#define EXPECT_2_EVENTS(events, e0, e1) \ + EXPECT_EQ(2u, events.size()); \ + EXPECT_EQ(e0, events[0]); \ + EXPECT_EQ(e1, events[1]) + +#define EXPECT_3_EVENTS(events, e0, e1, e2) \ + EXPECT_EQ(3u, events.size()); \ + EXPECT_EQ(e0, events[0]); \ + EXPECT_EQ(e1, events[1]); \ + EXPECT_EQ(e2, events[2]) + +#define EXPECT_4_EVENTS(events, e0, e1, e2, e3) \ + EXPECT_EQ(4u, events.size()); \ + EXPECT_EQ(e0, events[0]); \ + EXPECT_EQ(e1, events[1]); \ + EXPECT_EQ(e2, events[2]); \ + EXPECT_EQ(e3, events[3]) + +// A delegate that keeps track of gesture events. +class GestureEventConsumeDelegate : public TestWindowDelegate { + public: + GestureEventConsumeDelegate() + : tap_(false), + tap_down_(false), + tap_cancel_(false), + begin_(false), + end_(false), + scroll_begin_(false), + scroll_update_(false), + scroll_end_(false), + pinch_begin_(false), + pinch_update_(false), + pinch_end_(false), + long_press_(false), + fling_(false), + two_finger_tap_(false), + swipe_left_(false), + swipe_right_(false), + swipe_up_(false), + swipe_down_(false), + scroll_x_(0), + scroll_y_(0), + scroll_velocity_x_(0), + scroll_velocity_y_(0), + velocity_x_(0), + velocity_y_(0), + scroll_x_ordinal_(0), + scroll_y_ordinal_(0), + scroll_velocity_x_ordinal_(0), + scroll_velocity_y_ordinal_(0), + velocity_x_ordinal_(0), + velocity_y_ordinal_(0), + tap_count_(0), + wait_until_event_(ui::ET_UNKNOWN) { + } + + virtual ~GestureEventConsumeDelegate() {} + + void Reset() { + events_.clear(); + tap_ = false; + tap_down_ = false; + tap_cancel_ = false; + begin_ = false; + end_ = false; + scroll_begin_ = false; + scroll_update_ = false; + scroll_end_ = false; + pinch_begin_ = false; + pinch_update_ = false; + pinch_end_ = false; + long_press_ = false; + fling_ = false; + two_finger_tap_ = false; + swipe_left_ = false; + swipe_right_ = false; + swipe_up_ = false; + swipe_down_ = false; + + scroll_begin_position_.SetPoint(0, 0); + tap_location_.SetPoint(0, 0); + + scroll_x_ = 0; + scroll_y_ = 0; + scroll_velocity_x_ = 0; + scroll_velocity_y_ = 0; + velocity_x_ = 0; + velocity_y_ = 0; + scroll_x_ordinal_ = 0; + scroll_y_ordinal_ = 0; + scroll_velocity_x_ordinal_ = 0; + scroll_velocity_y_ordinal_ = 0; + velocity_x_ordinal_ = 0; + velocity_y_ordinal_ = 0; + tap_count_ = 0; + } + + const std::vector<ui::EventType>& events() const { return events_; }; + + bool tap() const { return tap_; } + bool tap_down() const { return tap_down_; } + bool tap_cancel() const { return tap_cancel_; } + bool begin() const { return begin_; } + bool end() const { return end_; } + bool scroll_begin() const { return scroll_begin_; } + bool scroll_update() const { return scroll_update_; } + bool scroll_end() const { return scroll_end_; } + bool pinch_begin() const { return pinch_begin_; } + bool pinch_update() const { return pinch_update_; } + bool pinch_end() const { return pinch_end_; } + bool long_press() const { return long_press_; } + bool long_tap() const { return long_tap_; } + bool fling() const { return fling_; } + bool two_finger_tap() const { return two_finger_tap_; } + bool swipe_left() const { return swipe_left_; } + bool swipe_right() const { return swipe_right_; } + bool swipe_up() const { return swipe_up_; } + bool swipe_down() const { return swipe_down_; } + + const gfx::Point scroll_begin_position() const { + return scroll_begin_position_; + } + + const gfx::Point tap_location() const { + return tap_location_; + } + + float scroll_x() const { return scroll_x_; } + float scroll_y() const { return scroll_y_; } + float scroll_velocity_x() const { return scroll_velocity_x_; } + float scroll_velocity_y() const { return scroll_velocity_y_; } + float velocity_x() const { return velocity_x_; } + float velocity_y() const { return velocity_y_; } + float scroll_x_ordinal() const { return scroll_x_ordinal_; } + float scroll_y_ordinal() const { return scroll_y_ordinal_; } + float scroll_velocity_x_ordinal() const { return scroll_velocity_x_ordinal_; } + float scroll_velocity_y_ordinal() const { return scroll_velocity_y_ordinal_; } + float velocity_x_ordinal() const { return velocity_x_ordinal_; } + float velocity_y_ordinal() const { return velocity_y_ordinal_; } + int touch_id() const { return touch_id_; } + const gfx::Rect& bounding_box() const { return bounding_box_; } + int tap_count() const { return tap_count_; } + + void WaitUntilReceivedGesture(ui::EventType type) { + wait_until_event_ = type; + run_loop_.reset(new base::RunLoop( + Env::GetInstance()->GetDispatcher())); + run_loop_->Run(); + } + + virtual void OnGestureEvent(ui::GestureEvent* gesture) OVERRIDE { + events_.push_back(gesture->type()); + bounding_box_ = gesture->details().bounding_box(); + switch (gesture->type()) { + case ui::ET_GESTURE_TAP: + tap_location_ = gesture->location(); + tap_count_ = gesture->details().tap_count(); + tap_ = true; + break; + case ui::ET_GESTURE_TAP_DOWN: + tap_down_ = true; + break; + case ui::ET_GESTURE_TAP_CANCEL: + tap_cancel_ = true; + break; + case ui::ET_GESTURE_BEGIN: + begin_ = true; + break; + case ui::ET_GESTURE_END: + end_ = true; + break; + case ui::ET_GESTURE_SCROLL_BEGIN: + scroll_begin_ = true; + scroll_begin_position_ = gesture->location(); + break; + case ui::ET_GESTURE_SCROLL_UPDATE: + scroll_update_ = true; + scroll_x_ += gesture->details().scroll_x(); + scroll_y_ += gesture->details().scroll_y(); + scroll_velocity_x_ = gesture->details().velocity_x(); + scroll_velocity_y_ = gesture->details().velocity_y(); + scroll_x_ordinal_ += gesture->details().scroll_x_ordinal(); + scroll_y_ordinal_ += gesture->details().scroll_y_ordinal(); + scroll_velocity_x_ordinal_ = gesture->details().velocity_x_ordinal(); + scroll_velocity_y_ordinal_ = gesture->details().velocity_y_ordinal(); + break; + case ui::ET_GESTURE_SCROLL_END: + EXPECT_TRUE(velocity_x_ == 0 && velocity_y_ == 0); + scroll_end_ = true; + break; + case ui::ET_GESTURE_PINCH_BEGIN: + pinch_begin_ = true; + break; + case ui::ET_GESTURE_PINCH_UPDATE: + pinch_update_ = true; + break; + case ui::ET_GESTURE_PINCH_END: + pinch_end_ = true; + break; + case ui::ET_GESTURE_LONG_PRESS: + long_press_ = true; + touch_id_ = gesture->details().touch_id(); + break; + case ui::ET_GESTURE_LONG_TAP: + long_tap_ = true; + break; + case ui::ET_SCROLL_FLING_START: + EXPECT_TRUE(gesture->details().velocity_x() != 0 || + gesture->details().velocity_y() != 0); + EXPECT_FALSE(scroll_end_); + fling_ = true; + velocity_x_ = gesture->details().velocity_x(); + velocity_y_ = gesture->details().velocity_y(); + velocity_x_ordinal_ = gesture->details().velocity_x_ordinal(); + velocity_y_ordinal_ = gesture->details().velocity_y_ordinal(); + break; + case ui::ET_GESTURE_TWO_FINGER_TAP: + two_finger_tap_ = true; + break; + case ui::ET_GESTURE_MULTIFINGER_SWIPE: + swipe_left_ = gesture->details().swipe_left(); + swipe_right_ = gesture->details().swipe_right(); + swipe_up_ = gesture->details().swipe_up(); + swipe_down_ = gesture->details().swipe_down(); + break; + default: + NOTREACHED(); + } + if (wait_until_event_ == gesture->type() && run_loop_) { + run_loop_->Quit(); + wait_until_event_ = ui::ET_UNKNOWN; + } + gesture->StopPropagation(); + } + + private: + scoped_ptr<base::RunLoop> run_loop_; + std::vector<ui::EventType> events_; + + bool tap_; + bool tap_down_; + bool tap_cancel_; + bool begin_; + bool end_; + bool scroll_begin_; + bool scroll_update_; + bool scroll_end_; + bool pinch_begin_; + bool pinch_update_; + bool pinch_end_; + bool long_press_; + bool long_tap_; + bool fling_; + bool two_finger_tap_; + bool swipe_left_; + bool swipe_right_; + bool swipe_up_; + bool swipe_down_; + + gfx::Point scroll_begin_position_; + gfx::Point tap_location_; + + float scroll_x_; + float scroll_y_; + float scroll_velocity_x_; + float scroll_velocity_y_; + float velocity_x_; + float velocity_y_; + float scroll_x_ordinal_; + float scroll_y_ordinal_; + float scroll_velocity_x_ordinal_; + float scroll_velocity_y_ordinal_; + float velocity_x_ordinal_; + float velocity_y_ordinal_; + int touch_id_; + gfx::Rect bounding_box_; + int tap_count_; + + ui::EventType wait_until_event_; + + DISALLOW_COPY_AND_ASSIGN(GestureEventConsumeDelegate); +}; + +class QueueTouchEventDelegate : public GestureEventConsumeDelegate { + public: + explicit QueueTouchEventDelegate(RootWindow* root_window) + : window_(NULL), + root_window_(root_window), + queue_events_(true) { + } + virtual ~QueueTouchEventDelegate() {} + + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE { + if (queue_events_) { + queue_.push(new ui::TouchEvent(*event, window_, window_)); + event->StopPropagation(); + } + } + + void ReceivedAck() { + ReceivedAckImpl(false); + } + + void ReceivedAckPreventDefaulted() { + ReceivedAckImpl(true); + } + + void set_window(Window* w) { window_ = w; } + void set_queue_events(bool queue) { queue_events_ = queue; } + + private: + void ReceivedAckImpl(bool prevent_defaulted) { + scoped_ptr<ui::TouchEvent> event(queue_.front()); + root_window_->ProcessedTouchEvent(event.get(), window_, + prevent_defaulted ? ui::ER_HANDLED : ui::ER_UNHANDLED); + queue_.pop(); + } + + std::queue<ui::TouchEvent*> queue_; + Window* window_; + RootWindow* root_window_; + bool queue_events_; + + DISALLOW_COPY_AND_ASSIGN(QueueTouchEventDelegate); +}; + +// A delegate that ignores gesture events but keeps track of [synthetic] mouse +// events. +class GestureEventSynthDelegate : public TestWindowDelegate { + public: + GestureEventSynthDelegate() + : mouse_enter_(false), + mouse_exit_(false), + mouse_press_(false), + mouse_release_(false), + mouse_move_(false), + double_click_(false) { + } + + void Reset() { + mouse_enter_ = false; + mouse_exit_ = false; + mouse_press_ = false; + mouse_release_ = false; + mouse_move_ = false; + double_click_ = false; + } + + bool mouse_enter() const { return mouse_enter_; } + bool mouse_exit() const { return mouse_exit_; } + bool mouse_press() const { return mouse_press_; } + bool mouse_move() const { return mouse_move_; } + bool mouse_release() const { return mouse_release_; } + bool double_click() const { return double_click_; } + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + switch (event->type()) { + case ui::ET_MOUSE_PRESSED: + double_click_ = event->flags() & ui::EF_IS_DOUBLE_CLICK; + mouse_press_ = true; + break; + case ui::ET_MOUSE_RELEASED: + mouse_release_ = true; + break; + case ui::ET_MOUSE_MOVED: + mouse_move_ = true; + break; + case ui::ET_MOUSE_ENTERED: + mouse_enter_ = true; + break; + case ui::ET_MOUSE_EXITED: + mouse_exit_ = true; + break; + default: + NOTREACHED(); + } + event->SetHandled(); + } + + private: + bool mouse_enter_; + bool mouse_exit_; + bool mouse_press_; + bool mouse_release_; + bool mouse_move_; + bool double_click_; + + DISALLOW_COPY_AND_ASSIGN(GestureEventSynthDelegate); +}; + +class TestOneShotGestureSequenceTimer + : public base::OneShotTimer<ui::GestureSequence> { + public: + TestOneShotGestureSequenceTimer() {} + + void ForceTimeout() { + if (IsRunning()) { + user_task().Run(); + Stop(); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestOneShotGestureSequenceTimer); +}; + +class TimerTestGestureSequence : public ui::GestureSequence { + public: + explicit TimerTestGestureSequence(ui::GestureEventHelper* helper) + : ui::GestureSequence(helper) { + } + + void ForceTimeout() { + static_cast<TestOneShotGestureSequenceTimer*>( + GetLongPressTimer())->ForceTimeout(); + } + + bool IsTimerRunning() { + return GetLongPressTimer()->IsRunning(); + } + + virtual base::OneShotTimer<ui::GestureSequence>* CreateTimer() OVERRIDE { + return new TestOneShotGestureSequenceTimer(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TimerTestGestureSequence); +}; + +class TestGestureRecognizer : public ui::GestureRecognizerImpl { + public: + explicit TestGestureRecognizer(RootWindow* root_window) + : GestureRecognizerImpl(root_window) { + } + + ui::GestureSequence* GetGestureSequenceForTesting(Window* window) { + return GetGestureSequenceForConsumer(window); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestGestureRecognizer); +}; + +class TimerTestGestureRecognizer : public TestGestureRecognizer { + public: + explicit TimerTestGestureRecognizer(RootWindow* root_window) + : TestGestureRecognizer(root_window) { + } + + virtual ui::GestureSequence* CreateSequence( + ui::GestureEventHelper* helper) OVERRIDE { + return new TimerTestGestureSequence(helper); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TimerTestGestureRecognizer); +}; + +base::TimeDelta GetTime() { + return ui::EventTimeForNow(); +} + +class TimedEvents { + private: + int simulated_now_; + + public: + TimedEvents() : simulated_now_(0) { + } + + base::TimeDelta Now() { + base::TimeDelta t = base::TimeDelta::FromMilliseconds(simulated_now_); + simulated_now_++; + return t; + } + + base::TimeDelta LeapForward(int time_in_millis) { + simulated_now_ += time_in_millis; + return base::TimeDelta::FromMilliseconds(simulated_now_); + } + + base::TimeDelta InFuture(int time_in_millis) { + return base::TimeDelta::FromMilliseconds(simulated_now_ + time_in_millis); + } + + void SendScrollEvents(RootWindow* root_window, + int x_start, + int y_start, + int dx, + int dy, + int touch_id, + int time_step, + int num_steps, + GestureEventConsumeDelegate* delegate) { + int x = x_start; + int y = y_start; + + for (int i = 0; i < num_steps; i++) { + x += dx; + y += dy; + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(x, y), + touch_id, + base::TimeDelta::FromMilliseconds(simulated_now_)); + root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + simulated_now_ += time_step; + } + } + + void SendScrollEvent(RootWindow* root_window, + int x, + int y, + int touch_id, + GestureEventConsumeDelegate* delegate) { + delegate->Reset(); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(x, y), + touch_id, + base::TimeDelta::FromMilliseconds(simulated_now_)); + root_window->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + simulated_now_++; + } +}; + +// An event handler to keep track of events. +class TestEventHandler : public ui::EventHandler { + public: + TestEventHandler() : touch_released_count_(0), touch_pressed_count_(0), + touch_moved_count_(0), touch_stationary_count_(0), + touch_cancelled_count_(0) { + } + + virtual ~TestEventHandler() {} + + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE { + switch (event->type()) { + case ui::ET_TOUCH_RELEASED: + touch_released_count_++; + break; + case ui::ET_TOUCH_PRESSED: + touch_pressed_count_++; + break; + case ui::ET_TOUCH_MOVED: + touch_moved_count_++; + break; + case ui::ET_TOUCH_STATIONARY: + touch_stationary_count_++; + break; + case ui::ET_TOUCH_CANCELLED: + touch_cancelled_count_++; + break; + default: + break; + } + } + + void Reset() { + touch_released_count_ = 0; + touch_pressed_count_ = 0; + touch_moved_count_ = 0; + touch_stationary_count_ = 0; + touch_cancelled_count_ = 0; + } + + int touch_released_count() const { return touch_released_count_; } + int touch_pressed_count() const { return touch_pressed_count_; } + int touch_moved_count() const { return touch_moved_count_; } + int touch_stationary_count() const { return touch_stationary_count_; } + int touch_cancelled_count() const { return touch_cancelled_count_; } + + private: + int touch_released_count_; + int touch_pressed_count_; + int touch_moved_count_; + int touch_stationary_count_; + int touch_cancelled_count_; + + DISALLOW_COPY_AND_ASSIGN(TestEventHandler); +}; + +} // namespace + +class GestureRecognizerTest : public AuraTestBase { + public: + GestureRecognizerTest() {} + + virtual void SetUp() OVERRIDE { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableScrollPrediction); + AuraTestBase::SetUp(); + } + + DISALLOW_COPY_AND_ASSIGN(GestureRecognizerTest); +}; + +// Check that appropriate touch events generate tap gesture events. +TEST_F(GestureRecognizerTest, GestureEventTap) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + // Make sure there is enough delay before the touch is released so that it is + // recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_TRUE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(1, delegate->tap_count()); +} + +// Check that appropriate touch events generate tap gesture events +// when information about the touch radii are provided. +TEST_F(GestureRecognizerTest, GestureEventTapRegion) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 800; + const int kWindowHeight = 600; + const int kTouchId = 2; + gfx::Rect bounds(0, 0, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + // Test with no ET_TOUCH_MOVED events. + { + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + press.set_radius_x(5); + press.set_radius_y(12); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + // Make sure there is enough delay before the touch is released so that it + // is recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + release.set_radius_x(5); + release.set_radius_y(12); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_TRUE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(1, delegate->tap_count()); + gfx::Point actual_point(delegate->tap_location()); + EXPECT_EQ(24, delegate->bounding_box().width()); + EXPECT_EQ(24, delegate->bounding_box().height()); + EXPECT_EQ(101, actual_point.x()); + EXPECT_EQ(201, actual_point.y()); + } + + // Test with no ET_TOUCH_MOVED events but different touch points and radii. + { + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(365, 290), + kTouchId, tes.Now()); + press.set_radius_x(8); + press.set_radius_y(14); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(367, 291), + kTouchId, tes.LeapForward(50)); + release.set_radius_x(20); + release.set_radius_y(13); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_TRUE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(1, delegate->tap_count()); + gfx::Point actual_point(delegate->tap_location()); + EXPECT_EQ(40, delegate->bounding_box().width()); + EXPECT_EQ(40, delegate->bounding_box().height()); + EXPECT_EQ(367, actual_point.x()); + EXPECT_EQ(291, actual_point.y()); + } + + // Test with a single ET_TOUCH_MOVED event. + { + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(46, 205), + kTouchId, tes.Now()); + press.set_radius_x(6); + press.set_radius_y(10); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(49, 204), + kTouchId, tes.LeapForward(50)); + move.set_radius_x(8); + move.set_radius_y(12); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(49, 204), + kTouchId, tes.LeapForward(50)); + release.set_radius_x(4); + release.set_radius_y(8); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_TRUE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(1, delegate->tap_count()); + gfx::Point actual_point(delegate->tap_location()); + EXPECT_EQ(25, delegate->bounding_box().width()); + EXPECT_EQ(24, delegate->bounding_box().height()); + EXPECT_EQ(48, actual_point.x()); + EXPECT_EQ(204, actual_point.y()); + } + + // Test with a few ET_TOUCH_MOVED events. + { + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(400, 150), + kTouchId, tes.Now()); + press.set_radius_x(7); + press.set_radius_y(10); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(397, 151), + kTouchId, tes.LeapForward(50)); + move.set_radius_x(13); + move.set_radius_y(12); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent move1(ui::ET_TOUCH_MOVED, gfx::Point(397, 149), + kTouchId, tes.LeapForward(50)); + move1.set_radius_x(16); + move1.set_radius_y(16); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(400, 150), + kTouchId, tes.LeapForward(50)); + move2.set_radius_x(14); + move2.set_radius_y(10); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(401, 149), + kTouchId, tes.LeapForward(50)); + release.set_radius_x(8); + release.set_radius_y(9); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_TRUE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(1, delegate->tap_count()); + gfx::Point actual_point(delegate->tap_location()); + EXPECT_EQ(33, delegate->bounding_box().width()); + EXPECT_EQ(32, delegate->bounding_box().height()); + EXPECT_EQ(397, actual_point.x()); + EXPECT_EQ(149, actual_point.y()); + } +} + +// Check that appropriate touch events generate scroll gesture events. +TEST_F(GestureRecognizerTest, GestureEventScroll) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 5; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_BEGIN, + ui::ET_GESTURE_TAP_DOWN); + + // Move the touch-point enough so that it is considered as a scroll. This + // should generate both SCROLL_BEGIN and SCROLL_UPDATE gestures. + // The first movement is diagonal, to ensure that we have a free scroll, + // and not a rail scroll. + tes.SendScrollEvent(root_window(), 130, 230, kTouchId, delegate.get()); + EXPECT_3_EVENTS(delegate->events(), + ui::ET_GESTURE_TAP_CANCEL, + ui::ET_GESTURE_SCROLL_BEGIN, + ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_EQ(29, delegate->scroll_x()); + EXPECT_EQ(29, delegate->scroll_y()); + EXPECT_EQ(gfx::Point(1, 1).ToString(), + delegate->scroll_begin_position().ToString()); + + // When scrolling with a single finger, the bounding box of the gesture should + // be empty, since it's a single point and the radius for testing is zero. + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); + + // Move some more to generate a few more scroll updates. + tes.SendScrollEvent(root_window(), 110, 211, kTouchId, delegate.get()); + EXPECT_1_EVENT(delegate->events(), ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_EQ(-20, delegate->scroll_x()); + EXPECT_EQ(-19, delegate->scroll_y()); + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); + + tes.SendScrollEvent(root_window(), 140, 215, kTouchId, delegate.get()); + EXPECT_1_EVENT(delegate->events(), ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_EQ(30, delegate->scroll_x()); + EXPECT_EQ(4, delegate->scroll_y()); + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); + + // Release the touch. This should end the scroll. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, + tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_SCROLL_FLING_START, + ui::ET_GESTURE_END); + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); +} + +// Check that predicted scroll update positions are correct. +TEST_F(GestureRecognizerTest, GestureEventScrollPrediction) { + const double prediction_interval = 0.03; + ui::GestureConfiguration::set_scroll_prediction_seconds(prediction_interval); + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 5; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + // Tracks the total scroll since we want to verify that the correct position + // will be scrolled to throughout the prediction. + gfx::Vector2dF total_scroll; + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_BEGIN, + ui::ET_GESTURE_TAP_DOWN); + + // Move the touch-point enough so that it is considered as a scroll. This + // should generate both SCROLL_BEGIN and SCROLL_UPDATE gestures. + // The first movement is diagonal, to ensure that we have a free scroll, + // and not a rail scroll. + tes.LeapForward(30); + tes.SendScrollEvent(root_window(), 130, 230, kTouchId, delegate.get()); + EXPECT_3_EVENTS(delegate->events(), + ui::ET_GESTURE_TAP_CANCEL, + ui::ET_GESTURE_SCROLL_BEGIN, + ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_GT(delegate->scroll_velocity_x(), 0); + EXPECT_GT(delegate->scroll_velocity_y(), 0); + total_scroll.set_x(total_scroll.x() + delegate->scroll_x()); + total_scroll.set_y(total_scroll.y() + delegate->scroll_y()); + EXPECT_EQ((int)(29 + delegate->scroll_velocity_x() * prediction_interval), + (int)(total_scroll.x())); + EXPECT_EQ((int)(29 + delegate->scroll_velocity_y() * prediction_interval), + (int)(total_scroll.y())); + + // Move some more to generate a few more scroll updates. + tes.LeapForward(30); + tes.SendScrollEvent(root_window(), 110, 211, kTouchId, delegate.get()); + EXPECT_1_EVENT(delegate->events(), ui::ET_GESTURE_SCROLL_UPDATE); + total_scroll.set_x(total_scroll.x() + delegate->scroll_x()); + total_scroll.set_y(total_scroll.y() + delegate->scroll_y()); + EXPECT_EQ((int)(9 + delegate->scroll_velocity_x() * prediction_interval), + (int)(total_scroll.x())); + EXPECT_EQ((int)(10 + delegate->scroll_velocity_y() * prediction_interval), + (int)(total_scroll.y())); + + tes.LeapForward(30); + tes.SendScrollEvent(root_window(), 140, 215, kTouchId, delegate.get()); + EXPECT_1_EVENT(delegate->events(), ui::ET_GESTURE_SCROLL_UPDATE); + total_scroll.set_x(total_scroll.x() + delegate->scroll_x()); + total_scroll.set_y(total_scroll.y() + delegate->scroll_y()); + EXPECT_EQ((int)(39 + delegate->scroll_velocity_x() * prediction_interval), + (int)(total_scroll.x())); + EXPECT_EQ((int)(14 + delegate->scroll_velocity_y() * prediction_interval), + (int)(total_scroll.y())); + + // Release the touch. This should end the scroll. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, + tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); +} + +// Check that the bounding box during a scroll event is correct. +TEST_F(GestureRecognizerTest, GestureEventScrollBoundingBox) { + TimedEvents tes; + for (int radius = 1; radius <= 10; ++radius) { + ui::GestureConfiguration::set_default_radius(radius); + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 5; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + const int kPositionX = 101; + const int kPositionY = 201; + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, + gfx::Point(kPositionX, kPositionY), + kTouchId, + tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_EQ(gfx::Rect(kPositionX - radius, + kPositionY - radius, + radius * 2, + radius * 2).ToString(), + delegate->bounding_box().ToString()); + + const int kScrollAmount = 50; + tes.SendScrollEvents(root_window(), kPositionX, kPositionY, + 1, 1, kTouchId, 1, kScrollAmount, delegate.get()); + EXPECT_EQ(gfx::Point(1, 1).ToString(), + delegate->scroll_begin_position().ToString()); + EXPECT_EQ(gfx::Rect(kPositionX + kScrollAmount - radius, + kPositionY + kScrollAmount - radius, + radius * 2, + radius * 2).ToString(), + delegate->bounding_box().ToString()); + + // Release the touch. This should end the scroll. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, + gfx::Point(kPositionX + kScrollAmount, + kPositionY + kScrollAmount), + kTouchId, press.time_stamp() + + base::TimeDelta::FromMilliseconds(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_EQ(gfx::Rect(kPositionX + kScrollAmount - radius, + kPositionY + kScrollAmount - radius, + radius * 2, + radius * 2).ToString(), + delegate->bounding_box().ToString()); + } + ui::GestureConfiguration::set_default_radius(0); +} + +// Check Scroll End Events report correct velocities +// if the user was on a horizontal rail +TEST_F(GestureRecognizerTest, GestureEventHorizontalRailFling) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kTouchId = 7; + gfx::Rect bounds(0, 0, 1000, 1000); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(0, 0), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + + // Move the touch-point horizontally enough that it is considered a + // horizontal scroll. + tes.SendScrollEvent(root_window(), 20, 1, kTouchId, delegate.get()); + EXPECT_EQ(0, delegate->scroll_y()); + EXPECT_EQ(1, delegate->scroll_y_ordinal()); + EXPECT_EQ(20, delegate->scroll_x()); + EXPECT_EQ(20, delegate->scroll_x_ordinal()); + + // Get a high x velocity, while still staying on the rail + tes.SendScrollEvents(root_window(), 1, 1, + 100, 10, kTouchId, 1, + ui::GestureConfiguration::points_buffered_for_velocity(), + delegate.get()); + // The y-velocity during the scroll should be 0 since this is in a horizontal + // rail scroll. + EXPECT_GT(delegate->scroll_velocity_x(), 0); + EXPECT_EQ(0, delegate->scroll_velocity_y()); + + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + + EXPECT_TRUE(delegate->fling()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_GT(delegate->velocity_x(), 0); + EXPECT_EQ(0, delegate->velocity_y()); +} + +// Check Scroll End Events report correct velocities +// if the user was on a vertical rail +TEST_F(GestureRecognizerTest, GestureEventVerticalRailFling) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kTouchId = 7; + gfx::Rect bounds(0, 0, 1000, 1000); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(0, 0), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + + // Move the touch-point vertically enough that it is considered a + // vertical scroll. + tes.SendScrollEvent(root_window(), 1, 20, kTouchId, delegate.get()); + EXPECT_EQ(20, delegate->scroll_y()); + EXPECT_EQ(20, delegate->scroll_y_ordinal()); + EXPECT_EQ(0, delegate->scroll_x()); + EXPECT_EQ(1, delegate->scroll_x_ordinal()); + EXPECT_EQ(0, delegate->scroll_velocity_x()); + EXPECT_GT(delegate->scroll_velocity_x_ordinal(), 0); + + // Get a high y velocity, while still staying on the rail + tes.SendScrollEvents(root_window(), 1, 1, + 10, 100, kTouchId, 1, + ui::GestureConfiguration::points_buffered_for_velocity(), + delegate.get()); + EXPECT_EQ(0, delegate->scroll_velocity_x()); + EXPECT_GT(delegate->scroll_velocity_y(), 0); + + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + + EXPECT_TRUE(delegate->fling()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_EQ(0, delegate->velocity_x()); + EXPECT_GT(delegate->velocity_y(), 0); +} + +// Check Scroll End Events reports zero velocities +// if the user is not on a rail +TEST_F(GestureRecognizerTest, GestureEventNonRailFling) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kTouchId = 7; + gfx::Rect bounds(0, 0, 1000, 1000); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(0, 0), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + + // Move the touch-point such that a non-rail scroll begins + tes.SendScrollEvent(root_window(), 20, 20, kTouchId, delegate.get()); + EXPECT_EQ(20, delegate->scroll_y()); + EXPECT_EQ(20, delegate->scroll_x()); + + tes.SendScrollEvents(root_window(), 1, 1, + 10, 100, kTouchId, 1, + ui::GestureConfiguration::points_buffered_for_velocity(), + delegate.get()); + + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + + EXPECT_TRUE(delegate->fling()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_GT(delegate->velocity_x(), 0); + EXPECT_GT(delegate->velocity_y(), 0); +} + +// Check that appropriate touch events generate long press events +TEST_F(GestureRecognizerTest, GestureEventLongPress) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + + TimerTestGestureRecognizer* gesture_recognizer = + new TimerTestGestureRecognizer(root_window()); + + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->tap_cancel()); + + // We haven't pressed long enough for a long press to occur + EXPECT_FALSE(delegate->long_press()); + + // Wait until the timer runs out + delegate->WaitUntilReceivedGesture(ui::ET_GESTURE_LONG_PRESS); + EXPECT_TRUE(delegate->long_press()); + EXPECT_EQ(0, delegate->touch_id()); + EXPECT_FALSE(delegate->tap_cancel()); + + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->long_press()); + + // Note the tap down isn't cancelled until the release + EXPECT_TRUE(delegate->tap_cancel()); +} + +// Check that scrolling cancels a long press +TEST_F(GestureRecognizerTest, GestureEventLongPressCancelledByScroll) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + ui::GestureConfiguration::set_long_press_time_in_seconds(.01); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 6; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + + TimerTestGestureRecognizer* gesture_recognizer = + new TimerTestGestureRecognizer(root_window()); + TimerTestGestureSequence* gesture_sequence = + static_cast<TimerTestGestureSequence*>( + gesture_recognizer->GetGestureSequenceForTesting(window.get())); + + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_TRUE(delegate->tap_down()); + + // We haven't pressed long enough for a long press to occur + EXPECT_FALSE(delegate->long_press()); + EXPECT_FALSE(delegate->tap_cancel()); + + // Scroll around, to cancel the long press + tes.SendScrollEvent(root_window(), 130, 230, kTouchId, delegate.get()); + // Wait until the timer runs out + gesture_sequence->ForceTimeout(); + EXPECT_FALSE(delegate->long_press()); + EXPECT_TRUE(delegate->tap_cancel()); + + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(10)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->long_press()); + EXPECT_FALSE(delegate->tap_cancel()); +} + +// Check that appropriate touch events generate long tap events +TEST_F(GestureRecognizerTest, GestureEventLongTap) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + + TimerTestGestureRecognizer* gesture_recognizer = + new TimerTestGestureRecognizer(root_window()); + + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->tap_cancel()); + + // We haven't pressed long enough for a long press to occur + EXPECT_FALSE(delegate->long_press()); + + // Wait until the timer runs out + delegate->WaitUntilReceivedGesture(ui::ET_GESTURE_LONG_PRESS); + EXPECT_TRUE(delegate->long_press()); + EXPECT_EQ(0, delegate->touch_id()); + EXPECT_FALSE(delegate->tap_cancel()); + + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->long_press()); + EXPECT_TRUE(delegate->long_tap()); + + // Note the tap down isn't cancelled until the release + EXPECT_TRUE(delegate->tap_cancel()); +} + +// Check that second tap cancels a long press +TEST_F(GestureRecognizerTest, GestureEventLongPressCancelledBySecondTap) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + ui::GestureConfiguration::set_long_press_time_in_seconds(.01); + const int kWindowWidth = 300; + const int kWindowHeight = 400; + const int kTouchId1 = 8; + const int kTouchId2 = 2; + gfx::Rect bounds(5, 5, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + TimerTestGestureRecognizer* gesture_recognizer = + new TimerTestGestureRecognizer(root_window()); + TimerTestGestureSequence* gesture_sequence = + static_cast<TimerTestGestureSequence*>( + gesture_recognizer->GetGestureSequenceForTesting(window.get())); + + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_TRUE(delegate->begin()); + + // We haven't pressed long enough for a long press to occur + EXPECT_FALSE(delegate->long_press()); + + // Second tap, to cancel the long press + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_FALSE(delegate->tap_down()); // no touch down for second tap. + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + + // Wait until the timer runs out + gesture_sequence->ForceTimeout(); + + // No long press occurred + EXPECT_FALSE(delegate->long_press()); + + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->long_press()); + EXPECT_TRUE(delegate->two_finger_tap()); + EXPECT_FALSE(delegate->tap_cancel()); +} + +// Check that horizontal scroll gestures cause scrolls on horizontal rails. +// Also tests that horizontal rails can be broken. +TEST_F(GestureRecognizerTest, GestureEventHorizontalRailScroll) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kTouchId = 7; + gfx::Rect bounds(0, 0, 1000, 1000); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(0, 0), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + + // Move the touch-point horizontally enough that it is considered a + // horizontal scroll. + tes.SendScrollEvent(root_window(), 20, 1, kTouchId, delegate.get()); + EXPECT_EQ(0, delegate->scroll_y()); + EXPECT_EQ(20, delegate->scroll_x()); + + tes.SendScrollEvent(root_window(), 25, 6, kTouchId, delegate.get()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_EQ(5, delegate->scroll_x()); + // y shouldn't change, as we're on a horizontal rail. + EXPECT_EQ(0, delegate->scroll_y()); + + // Send enough information that a velocity can be calculated for the gesture, + // and we can break the rail + tes.SendScrollEvents(root_window(), 1, 1, + 1, 100, kTouchId, 1, + ui::GestureConfiguration::points_buffered_for_velocity(), + delegate.get()); + // Since the scroll is not longer railing, the velocity should be set for both + // axis. + EXPECT_GT(delegate->scroll_velocity_x(), 0); + EXPECT_GT(delegate->scroll_velocity_y(), 0); + + tes.SendScrollEvent(root_window(), 0, 0, kTouchId, delegate.get()); + tes.SendScrollEvent(root_window(), 5, 5, kTouchId, delegate.get()); + + // The rail should be broken + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_EQ(5, delegate->scroll_x()); + EXPECT_EQ(5, delegate->scroll_y()); +} + +// Check that vertical scroll gestures cause scrolls on vertical rails. +// Also tests that vertical rails can be broken. +TEST_F(GestureRecognizerTest, GestureEventVerticalRailScroll) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kTouchId = 7; + gfx::Rect bounds(0, 0, 1000, 1000); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(0, 0), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + + // Move the touch-point vertically enough that it is considered a + // vertical scroll. + tes.SendScrollEvent(root_window(), 1, 20, kTouchId, delegate.get()); + EXPECT_EQ(0, delegate->scroll_x()); + EXPECT_EQ(20, delegate->scroll_y()); + + tes.SendScrollEvent(root_window(), 6, 25, kTouchId, delegate.get()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_EQ(5, delegate->scroll_y()); + // x shouldn't change, as we're on a vertical rail. + EXPECT_EQ(0, delegate->scroll_x()); + EXPECT_EQ(0, delegate->scroll_velocity_x()); + + // Send enough information that a velocity can be calculated for the gesture, + // and we can break the rail + tes.SendScrollEvents(root_window(), 1, 1, + 100, 1, kTouchId, 1, + ui::GestureConfiguration::points_buffered_for_velocity(), + delegate.get()); + EXPECT_GT(delegate->scroll_velocity_x(), 0); + EXPECT_GT(delegate->scroll_velocity_y(), 0); + + tes.SendScrollEvent(root_window(), 0, 0, kTouchId, delegate.get()); + tes.SendScrollEvent(root_window(), 5, 5, kTouchId, delegate.get()); + + // The rail should be broken + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_EQ(5, delegate->scroll_x()); + EXPECT_EQ(5, delegate->scroll_y()); +} + +TEST_F(GestureRecognizerTest, GestureTapFollowedByScroll) { + // First, tap. Then, do a scroll using the same touch-id. + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 3; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Make sure there is enough delay before the touch is released so that it is + // recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_TRUE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Now, do a scroll gesture. Delay it sufficiently so that it doesn't trigger + // a double-tap. + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(1000)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Move the touch-point enough so that it is considered as a scroll. This + // should generate both SCROLL_BEGIN and SCROLL_UPDATE gestures. + // The first movement is diagonal, to ensure that we have a free scroll, + // and not a rail scroll. + delegate->Reset(); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(130, 230), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_EQ(29, delegate->scroll_x()); + EXPECT_EQ(29, delegate->scroll_y()); + + // Move some more to generate a few more scroll updates. + delegate->Reset(); + ui::TouchEvent move1(ui::ET_TOUCH_MOVED, gfx::Point(110, 211), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_EQ(-20, delegate->scroll_x()); + EXPECT_EQ(-19, delegate->scroll_y()); + + delegate->Reset(); + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(140, 215), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_EQ(30, delegate->scroll_x()); + EXPECT_EQ(4, delegate->scroll_y()); + + // Release the touch. This should end the scroll. + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_TRUE(delegate->fling()); +} + +TEST_F(GestureRecognizerTest, AsynchronousGestureRecognition) { + scoped_ptr<QueueTouchEventDelegate> queued_delegate( + new QueueTouchEventDelegate(root_window())); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId1 = 6; + const int kTouchId2 = 4; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> queue(CreateTestWindowWithDelegate( + queued_delegate.get(), -1234, bounds, root_window())); + + queued_delegate->set_window(queue.get()); + + // Touch down on the window. This should not generate any gesture event. + queued_delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, GetTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + // Introduce some delay before the touch is released so that it is recognized + // as a tap. However, this still should not create any gesture events. + queued_delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, press.time_stamp() + + base::TimeDelta::FromMilliseconds(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + // Create another window, and place a touch-down on it. This should create a + // tap-down gesture. + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -2345, gfx::Rect(0, 0, 50, 50), root_window())); + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(10, 20), + kTouchId2, GetTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(10, 20), + kTouchId2, GetTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + + // Process the first queued event. + queued_delegate->Reset(); + queued_delegate->ReceivedAck(); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_TRUE(queued_delegate->tap_down()); + EXPECT_TRUE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + // Now, process the second queued event. + queued_delegate->Reset(); + queued_delegate->ReceivedAck(); + EXPECT_TRUE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_TRUE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + // Start all over. Press on the first window, then press again on the second + // window. The second press should still go to the first window. + queued_delegate->Reset(); + ui::TouchEvent press3(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, GetTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press3); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + queued_delegate->Reset(); + delegate->Reset(); + ui::TouchEvent press4(ui::ET_TOUCH_PRESSED, gfx::Point(103, 203), + kTouchId2, GetTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press4); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + // Move the second touch-point enough so that it is considered a pinch. This + // should generate both SCROLL_BEGIN and PINCH_BEGIN gestures. + queued_delegate->Reset(); + delegate->Reset(); + int x_move = ui::GestureConfiguration::max_touch_move_in_pixels_for_click(); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(103 + x_move, 203), + kTouchId2, GetTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + queued_delegate->Reset(); + queued_delegate->ReceivedAck(); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_TRUE(queued_delegate->tap_down()); + EXPECT_TRUE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + + queued_delegate->Reset(); + queued_delegate->ReceivedAck(); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); // no touch down for second tap. + EXPECT_TRUE(queued_delegate->tap_cancel()); + EXPECT_TRUE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_FALSE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + EXPECT_FALSE(queued_delegate->pinch_begin()); + EXPECT_FALSE(queued_delegate->pinch_update()); + EXPECT_FALSE(queued_delegate->pinch_end()); + + queued_delegate->Reset(); + queued_delegate->ReceivedAck(); + EXPECT_FALSE(queued_delegate->tap()); + EXPECT_FALSE(queued_delegate->tap_down()); + EXPECT_FALSE(queued_delegate->tap_cancel()); + EXPECT_FALSE(queued_delegate->begin()); + EXPECT_FALSE(queued_delegate->end()); + EXPECT_TRUE(queued_delegate->scroll_begin()); + EXPECT_FALSE(queued_delegate->scroll_update()); + EXPECT_FALSE(queued_delegate->scroll_end()); + EXPECT_TRUE(queued_delegate->pinch_begin()); + EXPECT_FALSE(queued_delegate->pinch_update()); + EXPECT_FALSE(queued_delegate->pinch_end()); +} + +// Check that appropriate touch events generate pinch gesture events. +TEST_F(GestureRecognizerTest, GestureEventPinchFromScroll) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 300; + const int kWindowHeight = 400; + const int kTouchId1 = 5; + const int kTouchId2 = 3; + gfx::Rect bounds(5, 5, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + aura::RootWindow* root = root_window(); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_BEGIN, + ui::ET_GESTURE_TAP_DOWN); + + // Move the touch-point enough so that it is considered as a scroll. This + // should generate both SCROLL_BEGIN and SCROLL_UPDATE gestures. + delegate->Reset(); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(130, 301), + kTouchId1, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + EXPECT_3_EVENTS(delegate->events(), + ui::ET_GESTURE_TAP_CANCEL, + ui::ET_GESTURE_SCROLL_BEGIN, + ui::ET_GESTURE_SCROLL_UPDATE); + + // Press the second finger. It should cause pinch-begin. Note that we will not + // transition to two finger tap here because the touch points are far enough. + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), + kTouchId2, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_BEGIN, + ui::ET_GESTURE_PINCH_BEGIN); + EXPECT_EQ(gfx::Rect(10, 10, 120, 291).ToString(), + delegate->bounding_box().ToString()); + + // Move the first finger. + delegate->Reset(); + ui::TouchEvent move3(ui::ET_TOUCH_MOVED, gfx::Point(95, 201), + kTouchId1, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&move3); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_PINCH_UPDATE, + ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_EQ(gfx::Rect(10, 10, 85, 191).ToString(), + delegate->bounding_box().ToString()); + + // Now move the second finger. + delegate->Reset(); + ui::TouchEvent move4(ui::ET_TOUCH_MOVED, gfx::Point(55, 15), + kTouchId2, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&move4); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_PINCH_UPDATE, + ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_EQ(gfx::Rect(55, 15, 40, 186).ToString(), + delegate->bounding_box().ToString()); + + // Release the first finger. This should end pinch. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_PINCH_END, + ui::ET_GESTURE_END); + EXPECT_EQ(gfx::Rect(55, 15, 46, 186).ToString(), + delegate->bounding_box().ToString()); + + // Move the second finger. This should still generate a scroll. + delegate->Reset(); + ui::TouchEvent move5(ui::ET_TOUCH_MOVED, gfx::Point(25, 10), + kTouchId2, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&move5); + EXPECT_1_EVENT(delegate->events(), ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); +} + +TEST_F(GestureRecognizerTest, GestureEventPinchFromScrollFromPinch) { +scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 300; + const int kWindowHeight = 400; + const int kTouchId1 = 5; + const int kTouchId2 = 3; + gfx::Rect bounds(5, 5, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 301), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + // Since the touch points are far enough we will go to pinch rather than two + // finger tap. + EXPECT_TRUE(delegate->pinch_begin()); + + tes.SendScrollEvent(root_window(), 130, 230, kTouchId1, delegate.get()); + EXPECT_TRUE(delegate->pinch_update()); + + // Pinch has started, now release the second finger + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_TRUE(delegate->pinch_end()); + + tes.SendScrollEvent(root_window(), 130, 230, kTouchId2, delegate.get()); + EXPECT_TRUE(delegate->scroll_update()); + + // Pinch again + delegate->Reset(); + ui::TouchEvent press3(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press3); + // Now the touch points are close. So we will go into two finger tap. + // Move the touch-point enough to break two-finger-tap and enter pinch. + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(101, 202), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + EXPECT_TRUE(delegate->pinch_begin()); + + tes.SendScrollEvent(root_window(), 130, 230, kTouchId1, delegate.get()); + EXPECT_TRUE(delegate->pinch_update()); +} + +TEST_F(GestureRecognizerTest, GestureEventPinchFromTap) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + const int kWindowWidth = 300; + const int kWindowHeight = 400; + const int kTouchId1 = 3; + const int kTouchId2 = 5; + gfx::Rect bounds(5, 5, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + aura::RootWindow* root = root_window(); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 301), + kTouchId1, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_BEGIN, + ui::ET_GESTURE_TAP_DOWN); + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); + + // Press the second finger far enough to break two finger tap. It should + // instead cause a scroll-begin and pinch-begin. + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), + kTouchId2, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_4_EVENTS(delegate->events(), + ui::ET_GESTURE_TAP_CANCEL, + ui::ET_GESTURE_BEGIN, + ui::ET_GESTURE_PINCH_BEGIN, + ui::ET_GESTURE_SCROLL_BEGIN); + EXPECT_EQ(gfx::Rect(10, 10, 91, 291).ToString(), + delegate->bounding_box().ToString()); + + // Move the first finger. + delegate->Reset(); + ui::TouchEvent move3(ui::ET_TOUCH_MOVED, gfx::Point(65, 201), + kTouchId1, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&move3); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_PINCH_UPDATE, + ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_EQ(gfx::Rect(10, 10, 55, 191).ToString(), + delegate->bounding_box().ToString()); + + // Now move the second finger. + delegate->Reset(); + ui::TouchEvent move4(ui::ET_TOUCH_MOVED, gfx::Point(55, 15), + kTouchId2, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&move4); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_PINCH_UPDATE, + ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_EQ(gfx::Rect(55, 15, 10, 186).ToString(), + delegate->bounding_box().ToString()); + + // Release the first finger. This should end pinch. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.LeapForward(10)); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_2_EVENTS(delegate->events(), + ui::ET_GESTURE_PINCH_END, + ui::ET_GESTURE_END); + EXPECT_EQ(gfx::Rect(55, 15, 46, 186).ToString(), + delegate->bounding_box().ToString()); + + // Move the second finger. This should still generate a scroll. + delegate->Reset(); + ui::TouchEvent move5(ui::ET_TOUCH_MOVED, gfx::Point(25, 10), + kTouchId2, tes.Now()); + root->AsRootWindowHostDelegate()->OnHostTouchEvent(&move5); + EXPECT_1_EVENT(delegate->events(), ui::ET_GESTURE_SCROLL_UPDATE); + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); +} + +TEST_F(GestureRecognizerTest, GestureEventIgnoresDisconnectedEvents) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TimedEvents tes; + + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + 6, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); +} + +// Check that a touch is locked to the window of the closest current touch +// within max_separation_for_gesture_touches_in_pixels +TEST_F(GestureRecognizerTest, GestureEventTouchLockSelectsCorrectWindow) { + ui::GestureRecognizer* gesture_recognizer = + new ui::GestureRecognizerImpl(root_window()); + TimedEvents tes; + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + ui::GestureConsumer* target; + const int kNumWindows = 4; + + scoped_ptr<GestureEventConsumeDelegate*[]> delegates( + new GestureEventConsumeDelegate*[kNumWindows]); + + ui::GestureConfiguration:: + set_max_separation_for_gesture_touches_in_pixels(499); + + scoped_ptr<gfx::Rect[]> window_bounds(new gfx::Rect[kNumWindows]); + window_bounds[0] = gfx::Rect(0, 0, 1, 1); + window_bounds[1] = gfx::Rect(500, 0, 1, 1); + window_bounds[2] = gfx::Rect(0, 500, 1, 1); + window_bounds[3] = gfx::Rect(500, 500, 1, 1); + + scoped_ptr<aura::Window*[]> windows(new aura::Window*[kNumWindows]); + + // Instantiate windows with |window_bounds| and touch each window at + // its origin. + for (int i = 0; i < kNumWindows; ++i) { + delegates[i] = new GestureEventConsumeDelegate(); + windows[i] = CreateTestWindowWithDelegate( + delegates[i], i, window_bounds[i], root_window()); + windows[i]->set_id(i); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, window_bounds[i].origin(), + i, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + } + + // Touches should now be associated with the closest touch within + // ui::GestureConfiguration::max_separation_for_gesture_touches_in_pixels + target = gesture_recognizer->GetTargetForLocation(gfx::Point(11, 11)); + EXPECT_EQ("0", WindowIDAsString(target)); + target = gesture_recognizer->GetTargetForLocation(gfx::Point(511, 11)); + EXPECT_EQ("1", WindowIDAsString(target)); + target = gesture_recognizer->GetTargetForLocation(gfx::Point(11, 511)); + EXPECT_EQ("2", WindowIDAsString(target)); + target = gesture_recognizer->GetTargetForLocation(gfx::Point(511, 511)); + EXPECT_EQ("3", WindowIDAsString(target)); + + // Add a touch in the middle associated with windows[2] + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(0, 500), + kNumWindows, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(250, 250), + kNumWindows, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + + target = gesture_recognizer->GetTargetForLocation(gfx::Point(250, 250)); + EXPECT_EQ("2", WindowIDAsString(target)); + + // Make sure that ties are broken by distance to a current touch + // Closer to the point in the bottom right. + target = gesture_recognizer->GetTargetForLocation(gfx::Point(380, 380)); + EXPECT_EQ("3", WindowIDAsString(target)); + + // This touch is closer to the point in the middle + target = gesture_recognizer->GetTargetForLocation(gfx::Point(300, 300)); + EXPECT_EQ("2", WindowIDAsString(target)); + + // A touch too far from other touches won't be locked to anything + target = gesture_recognizer->GetTargetForLocation(gfx::Point(1000, 1000)); + EXPECT_TRUE(target == NULL); + + // Move a touch associated with windows[2] to 1000, 1000 + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(1000, 1000), + kNumWindows, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + + target = gesture_recognizer->GetTargetForLocation(gfx::Point(1000, 1000)); + EXPECT_EQ("2", WindowIDAsString(target)); + + for (int i = 0; i < kNumWindows; ++i) { + // Delete windows before deleting delegates. + delete windows[i]; + delete delegates[i]; + } +} + +// Check that touch events outside the root window are still handled +// by the root window's gesture sequence. +TEST_F(GestureRecognizerTest, GestureEventOutsideRootWindowTap) { + TestGestureRecognizer* gesture_recognizer = + new TestGestureRecognizer(root_window()); + TimedEvents tes; + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + scoped_ptr<aura::Window> window(CreateTestWindowWithBounds( + gfx::Rect(-100, -100, 2000, 2000), root_window())); + + ui::GestureSequence* window_gesture_sequence = + gesture_recognizer->GetGestureSequenceForTesting(window.get()); + + ui::GestureSequence* root_window_gesture_sequence = + gesture_recognizer->GetGestureSequenceForTesting(root_window()); + + gfx::Point pos1(-10, -10); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, pos1, 0, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + + gfx::Point pos2(1000, 1000); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, pos2, 1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + // As these presses were outside the root window, they should be + // associated with the root window. + EXPECT_EQ(0, window_gesture_sequence->point_count()); + EXPECT_EQ(2, root_window_gesture_sequence->point_count()); +} + +TEST_F(GestureRecognizerTest, NoTapWithPreventDefaultedRelease) { + scoped_ptr<QueueTouchEventDelegate> delegate( + new QueueTouchEventDelegate(root_window())); + TimedEvents tes; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, 100, 100); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + delegate->set_window(window.get()); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + + delegate->Reset(); + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->tap_down()); + delegate->Reset(); + delegate->ReceivedAckPreventDefaulted(); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_cancel()); +} + +TEST_F(GestureRecognizerTest, PinchScrollWithPreventDefaultedRelease) { + scoped_ptr<QueueTouchEventDelegate> delegate( + new QueueTouchEventDelegate(root_window())); + TimedEvents tes; + const int kTouchId1 = 7; + const int kTouchId2 = 5; + gfx::Rect bounds(10, 20, 100, 100); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + delegate->set_window(window.get()); + + { + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(15, 25), kTouchId1, + tes.Now()); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(20, 95), kTouchId1, + tes.LeapForward(200)); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(15, 25), kTouchId1, + tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + delegate->Reset(); + + // Ack the press event. + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->tap_down()); + delegate->Reset(); + + // Ack the move event. + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->scroll_begin()); + delegate->Reset(); + + // Ack the release event. Although the release event has been processed, it + // should still generate a scroll-end event. + delegate->ReceivedAckPreventDefaulted(); + EXPECT_TRUE(delegate->scroll_end()); + } + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(15, 25), kTouchId1, + tes.Now()); + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(20, 95), kTouchId1, + tes.LeapForward(200)); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(15, 25), kTouchId1, + tes.LeapForward(50)); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(55, 25), kTouchId2, + tes.Now()); + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(45, 85), kTouchId2, + tes.LeapForward(1000)); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(45, 85), kTouchId2, + tes.LeapForward(14)); + + // Do a pinch. + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + + // Ack the press and move events. + delegate->Reset(); + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->begin()); + EXPECT_TRUE(delegate->tap_down()); + + delegate->Reset(); + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->scroll_begin()); + + delegate->Reset(); + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->begin()); + EXPECT_TRUE(delegate->pinch_begin()); + + delegate->Reset(); + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->pinch_update()); + + // Ack the first release. Although the release is processed, it should still + // generate a pinch-end event. + delegate->Reset(); + delegate->ReceivedAckPreventDefaulted(); + EXPECT_TRUE(delegate->pinch_end()); + EXPECT_TRUE(delegate->end()); + + delegate->Reset(); + delegate->ReceivedAckPreventDefaulted(); + EXPECT_TRUE(delegate->scroll_end()); + EXPECT_TRUE(delegate->end()); +} + +TEST_F(GestureRecognizerTest, CaptureSendsGestureEnd) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TestGestureRecognizer* gesture_recognizer = + new TestGestureRecognizer(root_window()); + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, gfx::Rect(10, 10, 300, 300), root_window())); + EventGenerator generator(root_window()); + + generator.MoveMouseRelativeTo(window.get(), gfx::Point(10, 10)); + generator.PressTouch(); + RunAllPendingInMessageLoop(); + + EXPECT_TRUE(delegate->tap_down()); + + scoped_ptr<aura::Window> capture(CreateTestWindowWithBounds( + gfx::Rect(10, 10, 200, 200), root_window())); + capture->SetCapture(); + RunAllPendingInMessageLoop(); + + EXPECT_TRUE(delegate->end()); + EXPECT_TRUE(delegate->tap_cancel()); +} + +// Check that previous touch actions that are completely finished (either +// released or cancelled), do not receive extra synthetic cancels upon change of +// capture. +TEST_F(GestureRecognizerTest, CaptureDoesNotCancelFinishedTouches) { + scoped_ptr<TestEventHandler> handler(new TestEventHandler); + root_window()->AddPreTargetHandler(handler.get()); + + // Create a window and set it as the capture window. + scoped_ptr<aura::Window> window1(CreateTestWindowWithBounds( + gfx::Rect(10, 10, 300, 300), root_window())); + window1->SetCapture(); + + EventGenerator generator(root_window()); + TimedEvents tes; + + // Generate two touch-press events on the window. + scoped_ptr<ui::TouchEvent> touch0(new ui::TouchEvent(ui::ET_TOUCH_PRESSED, + gfx::Point(20, 20), 0, + tes.Now())); + scoped_ptr<ui::TouchEvent> touch1(new ui::TouchEvent(ui::ET_TOUCH_PRESSED, + gfx::Point(30, 30), 1, + tes.Now())); + generator.Dispatch(touch0.get()); + generator.Dispatch(touch1.get()); + RunAllPendingInMessageLoop(); + EXPECT_EQ(2, handler->touch_pressed_count()); + + // Advance time. + tes.LeapForward(1000); + + // End the two touches, one by a touch-release and one by a touch-cancel; to + // cover both cases. + touch0.reset(new ui::TouchEvent(ui::ET_TOUCH_RELEASED, gfx::Point(20, 20), 0, + tes.Now())); + touch1.reset(new ui::TouchEvent(ui::ET_TOUCH_CANCELLED, gfx::Point(30, 30), 1, + tes.Now())); + generator.Dispatch(touch0.get()); + generator.Dispatch(touch1.get()); + RunAllPendingInMessageLoop(); + EXPECT_EQ(1, handler->touch_released_count()); + EXPECT_EQ(1, handler->touch_cancelled_count()); + + // Create a new window and set it as the new capture window. + scoped_ptr<aura::Window> window2(CreateTestWindowWithBounds( + gfx::Rect(100, 100, 300, 300), root_window())); + window2->SetCapture(); + RunAllPendingInMessageLoop(); + // Check that setting capture does not generate any synthetic touch-cancels + // for the two previously finished touch actions. + EXPECT_EQ(1, handler->touch_cancelled_count()); + + root_window()->RemovePreTargetHandler(handler.get()); +} + +TEST_F(GestureRecognizerTest, PressDoesNotCrash) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + TestGestureRecognizer* gesture_recognizer = + new TestGestureRecognizer(root_window()); + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + TimedEvents tes; + + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, gfx::Rect(10, 10, 300, 300), root_window())); + + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(45, 45), 7, tes.Now()); + press.set_radius_x(40); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_EQ(gfx::Rect(5, 5, 80, 80).ToString(), + delegate->bounding_box().ToString()); + delegate->Reset(); + + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(55, 45), 7, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + // This new press should not generate a tap-down. + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); +} + +TEST_F(GestureRecognizerTest, TwoFingerTap) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId1 = 2; + const int kTouchId2 = 3; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + EXPECT_FALSE(delegate->two_finger_tap()); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); // no touch down for second tap. + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + EXPECT_FALSE(delegate->two_finger_tap()); + + // Little bit of touch move should not affect our state. + delegate->Reset(); + ui::TouchEvent move1(ui::ET_TOUCH_MOVED, gfx::Point(102, 202), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1); + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(131, 202), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + EXPECT_FALSE(delegate->two_finger_tap()); + + // Make sure there is enough delay before the touch is released so that it is + // recognized as a tap. + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_TRUE(delegate->two_finger_tap()); + + // Lift second finger. + // Make sure there is enough delay before the touch is released so that it is + // recognized as a tap. + delegate->Reset(); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(130, 201), + kTouchId2, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_TRUE(delegate->fling()); + EXPECT_FALSE(delegate->two_finger_tap()); +} + +TEST_F(GestureRecognizerTest, TwoFingerTapExpired) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId1 = 2; + const int kTouchId2 = 3; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + // Send release event after sufficient delay so that two finger time expires. + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.LeapForward(1000)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + EXPECT_FALSE(delegate->two_finger_tap()); + + // Lift second finger. + // Make sure there is enough delay before the touch is released so that it is + // recognized as a tap. + delegate->Reset(); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(130, 201), + kTouchId2, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + EXPECT_FALSE(delegate->two_finger_tap()); +} + +TEST_F(GestureRecognizerTest, TwoFingerTapChangesToPinch) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId1 = 2; + const int kTouchId2 = 3; + TimedEvents tes; + + // Test moving first finger + { + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + tes.SendScrollEvent(root_window(), 130, 230, kTouchId1, delegate.get()); + EXPECT_FALSE(delegate->two_finger_tap()); + EXPECT_TRUE(delegate->pinch_begin()); + + // Make sure there is enough delay before the touch is released so that it + // is recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId2, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(delegate->two_finger_tap()); + EXPECT_TRUE(delegate->pinch_end()); + } + + // Test moving second finger + { + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + tes.SendScrollEvent(root_window(), 101, 230, kTouchId2, delegate.get()); + EXPECT_FALSE(delegate->two_finger_tap()); + EXPECT_TRUE(delegate->pinch_begin()); + + // Make sure there is enough delay before the touch is released so that it + // is recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(delegate->two_finger_tap()); + EXPECT_TRUE(delegate->pinch_end()); + } +} + +TEST_F(GestureRecognizerTest, NoTwoFingerTapWhenFirstFingerHasScrolled) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId1 = 2; + const int kTouchId2 = 3; + TimedEvents tes; + + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + tes.SendScrollEvent(root_window(), 130, 230, kTouchId1, delegate.get()); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + EXPECT_TRUE(delegate->pinch_begin()); + + // Make sure there is enough delay before the touch is released so that it + // is recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId2, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(delegate->two_finger_tap()); + EXPECT_TRUE(delegate->pinch_end()); +} + +TEST_F(GestureRecognizerTest, MultiFingerSwipe) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + + gfx::Rect bounds(5, 10, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + const int kSteps = 15; + const int kTouchPoints = 4; + gfx::Point points[kTouchPoints] = { + gfx::Point(10, 30), + gfx::Point(30, 20), + gfx::Point(50, 30), + gfx::Point(80, 50) + }; + + aura::test::EventGenerator generator(root_window(), window.get()); + + for (int count = 2; count <= kTouchPoints; ++count) { + generator.GestureMultiFingerScroll(count, points, 15, kSteps, 0, -150); + EXPECT_TRUE(delegate->swipe_up()); + delegate->Reset(); + + generator.GestureMultiFingerScroll(count, points, 15, kSteps, 0, 150); + EXPECT_TRUE(delegate->swipe_down()); + delegate->Reset(); + + generator.GestureMultiFingerScroll(count, points, 15, kSteps, -150, 0); + EXPECT_TRUE(delegate->swipe_left()); + delegate->Reset(); + + generator.GestureMultiFingerScroll(count, points, 15, kSteps, 150, 0); + EXPECT_TRUE(delegate->swipe_right()); + delegate->Reset(); + } +} + +TEST_F(GestureRecognizerTest, TwoFingerTapCancelled) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId1 = 2; + const int kTouchId2 = 3; + TimedEvents tes; + + // Test canceling first finger. + { + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + delegate->Reset(); + ui::TouchEvent cancel(ui::ET_TOUCH_CANCELLED, gfx::Point(130, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&cancel); + EXPECT_FALSE(delegate->two_finger_tap()); + + // Make sure there is enough delay before the touch is released so that it + // is recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId2, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(delegate->two_finger_tap()); + } + + // Test canceling second finger + { + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + + delegate->Reset(); + ui::TouchEvent cancel(ui::ET_TOUCH_CANCELLED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&cancel); + EXPECT_FALSE(delegate->two_finger_tap()); + + // Make sure there is enough delay before the touch is released so that it + // is recognized as a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.LeapForward(50)); + + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(delegate->two_finger_tap()); + } +} + +TEST_F(GestureRecognizerTest, VeryWideTwoFingerTouchDownShouldBeAPinch) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 523; + const int kWindowHeight = 45; + const int kTouchId1 = 2; + const int kTouchId2 = 3; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + EXPECT_FALSE(delegate->two_finger_tap()); + + delegate->Reset(); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(430, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); // no touch down for second tap. + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_FALSE(delegate->long_press()); + EXPECT_FALSE(delegate->two_finger_tap()); + EXPECT_TRUE(delegate->pinch_begin()); +} + +// Verifies if a window is the target of multiple touch-ids and we hide the +// window everything is cleaned up correctly. +TEST_F(GestureRecognizerTest, FlushAllOnHide) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + gfx::Rect bounds(0, 0, 200, 200); + scoped_ptr<aura::Window> window( + CreateTestWindowWithDelegate(delegate.get(), 0, bounds, root_window())); + const int kTouchId1 = 8; + const int kTouchId2 = 2; + TimedEvents tes; + + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(20, 20), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + window->Hide(); + EXPECT_EQ(NULL, + root_window()->gesture_recognizer()->GetTouchLockedTarget(&press1)); + EXPECT_EQ(NULL, + root_window()->gesture_recognizer()->GetTouchLockedTarget(&press2)); +} + +TEST_F(GestureRecognizerTest, LongPressTimerStopsOnPreventDefaultedTouchMoves) { + scoped_ptr<QueueTouchEventDelegate> delegate( + new QueueTouchEventDelegate(root_window())); + const int kTouchId = 2; + gfx::Rect bounds(100, 200, 100, 100); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + delegate->set_window(window.get()); + TimedEvents tes; + + TimerTestGestureRecognizer* gesture_recognizer = + new TimerTestGestureRecognizer(root_window()); + TimerTestGestureSequence* gesture_sequence = + static_cast<TimerTestGestureSequence*>( + gesture_recognizer->GetGestureSequenceForTesting(window.get())); + + root_window()->SetGestureRecognizerForTesting(gesture_recognizer); + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + // Scroll around, to cancel the long press + tes.SendScrollEvent(root_window(), 130, 230, kTouchId, delegate.get()); + + delegate->Reset(); + delegate->ReceivedAck(); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_TRUE(gesture_sequence->IsTimerRunning()); + + delegate->Reset(); + delegate->ReceivedAckPreventDefaulted(); + EXPECT_FALSE(gesture_sequence->IsTimerRunning()); + gesture_sequence->ForceTimeout(); + EXPECT_FALSE(delegate->long_press()); +} + +// Same as GestureEventConsumeDelegate, but consumes all the touch-move events. +class ConsumesTouchMovesDelegate : public GestureEventConsumeDelegate { + public: + ConsumesTouchMovesDelegate() : consume_touch_move_(true) {} + virtual ~ConsumesTouchMovesDelegate() {} + + void set_consume_touch_move(bool consume) { consume_touch_move_ = consume; } + + private: + virtual void OnTouchEvent(ui::TouchEvent* touch) OVERRIDE { + if (consume_touch_move_ && touch->type() == ui::ET_TOUCH_MOVED) + touch->SetHandled(); + else + GestureEventConsumeDelegate::OnTouchEvent(touch); + } + + bool consume_touch_move_; + + DISALLOW_COPY_AND_ASSIGN(ConsumesTouchMovesDelegate); +}; + +// Same as GestureEventScroll, but tests that the behavior is the same +// even if all the touch-move events are consumed. +TEST_F(GestureRecognizerTest, GestureEventScrollTouchMoveConsumed) { + scoped_ptr<ConsumesTouchMovesDelegate> delegate( + new ConsumesTouchMovesDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 5; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Move the touch-point enough so that it would normally be considered a + // scroll. But since the touch-moves will be consumed, the scroll should not + // start. + tes.SendScrollEvent(root_window(), 130, 230, kTouchId, delegate.get()); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + // TODO(rbyers): Really we should get the TapCancel here instead of below, + // but this is a symptom of a larger issue: crbug.com/146397. + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Release the touch back at the start point. This should end without causing + // a tap. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(130, 230), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); +} + +// Tests the behavior of 2F scroll when all the touch-move events are consumed. +TEST_F(GestureRecognizerTest, GestureEventScrollTwoFingerTouchMoveConsumed) { + scoped_ptr<ConsumesTouchMovesDelegate> delegate( + new ConsumesTouchMovesDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 100; + const int kTouchId1 = 2; + const int kTouchId2 = 3; + TimedEvents tes; + + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + delegate->Reset(); + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + tes.SendScrollEvent(root_window(), 131, 231, kTouchId1, delegate.get()); + + // First finger touches down and moves. + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + delegate->Reset(); + // Second finger touches down and moves. + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(130, 201), + kTouchId2, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + tes.SendScrollEvent(root_window(), 161, 231, kTouchId2, delegate.get()); + + // PinchBegin & ScrollBegin were sent even though the touch-move events + // were consumed. This seems reasonable, as long as we don't send PinchUpdate + // ScrollUpdate when touch-move are consumed. + EXPECT_TRUE(delegate->pinch_begin()); + EXPECT_TRUE(delegate->scroll_begin()); + + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->two_finger_tap()); + + // Should be no PinchUpdate & ScrollUpdate. + EXPECT_FALSE(delegate->pinch_update()); + EXPECT_FALSE(delegate->pinch_end()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + delegate->Reset(); + // Moves First finger again, no PinchUpdate & ScrollUpdate. + tes.SendScrollEvent(root_window(), 161, 261, kTouchId1, delegate.get()); + + EXPECT_FALSE(delegate->pinch_update()); + EXPECT_FALSE(delegate->pinch_end()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Stops consuming touch-move. + delegate->set_consume_touch_move(false); + + delegate->Reset(); + // Making a pinch gesture. + tes.SendScrollEvent(root_window(), 161, 251, kTouchId1, delegate.get()); + tes.SendScrollEvent(root_window(), 161, 241, kTouchId2, delegate.get()); + + // Now we see PinchUpdate & ScrollUpdate. + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_FALSE(delegate->pinch_begin()); + EXPECT_TRUE(delegate->pinch_update()); + EXPECT_FALSE(delegate->pinch_end()); + + delegate->Reset(); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId1, tes.Now()); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(130, 201), + kTouchId2, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->two_finger_tap()); + + // Should see PinchEnd & ScrollEnd. + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_TRUE(delegate->scroll_end()); + + EXPECT_FALSE(delegate->pinch_begin()); + EXPECT_FALSE(delegate->pinch_update()); + EXPECT_TRUE(delegate->pinch_end()); +} + +// Like as GestureEventTouchMoveConsumed but tests the different behavior +// depending on whether the events were consumed before or after the scroll +// started. +TEST_F(GestureRecognizerTest, GestureEventScrollTouchMovePartialConsumed) { + scoped_ptr<ConsumesTouchMovesDelegate> delegate( + new ConsumesTouchMovesDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 5; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + delegate->Reset(); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_FALSE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Move the touch-point enough so that it would normally be considered a + // scroll. But since the touch-moves will be consumed, the scroll should not + // start. + tes.SendScrollEvent(root_window(), 130, 230, kTouchId, delegate.get()); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + // TODO(rbyers): Really we should get the TapCancel here instead of below, + // but this is a symptom of a larger issue: crbug.com/146397. + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + // Now, stop consuming touch-move events, and move the touch-point again. + delegate->set_consume_touch_move(false); + tes.SendScrollEvent(root_window(), 159, 259, kTouchId, delegate.get()); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + // Consuming move events doesn't effect what the ultimate scroll position + // will be if scrolling is later allowed to happen. + EXPECT_EQ(58, delegate->scroll_x()); + EXPECT_EQ(58, delegate->scroll_y()); + EXPECT_EQ(gfx::Point(1, 1).ToString(), + delegate->scroll_begin_position().ToString()); + + // Start consuming touch-move events again. However, since gesture-scroll has + // already started, the touch-move events should still result in scroll-update + // gestures. + delegate->set_consume_touch_move(true); + + // Move some more to generate a few more scroll updates. + tes.SendScrollEvent(root_window(), 110, 211, kTouchId, delegate.get()); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_EQ(-49, delegate->scroll_x()); + EXPECT_EQ(-48, delegate->scroll_y()); + + tes.SendScrollEvent(root_window(), 140, 215, kTouchId, delegate.get()); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + EXPECT_EQ(30, delegate->scroll_x()); + EXPECT_EQ(4, delegate->scroll_y()); + + // Release the touch. This should end the scroll. + delegate->Reset(); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + // Moves arrive without delays and hence have high velocity. + EXPECT_TRUE(delegate->fling()); +} + +// Check that appropriate touch events generate double tap gesture events. +TEST_F(GestureRecognizerTest, GestureEventDoubleTap) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + // First tap (tested in GestureEventTap) + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(104, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(104, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + delegate->Reset(); + + // Second tap + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(101, 203), + kTouchId, tes.LeapForward(200)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(102, 206), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + + EXPECT_TRUE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(2, delegate->tap_count()); +} + +// Check that appropriate touch events generate triple tap gesture events. +TEST_F(GestureRecognizerTest, GestureEventTripleTap) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + // First tap (tested in GestureEventTap) + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(104, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(104, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + + EXPECT_EQ(1, delegate->tap_count()); + delegate->Reset(); + + // Second tap (tested in GestureEventDoubleTap) + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(101, 203), + kTouchId, tes.LeapForward(200)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(102, 206), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + + EXPECT_EQ(2, delegate->tap_count()); + delegate->Reset(); + + // Third tap + ui::TouchEvent press3(ui::ET_TOUCH_PRESSED, gfx::Point(102, 206), + kTouchId, tes.LeapForward(200)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press3); + ui::TouchEvent release3(ui::ET_TOUCH_RELEASED, gfx::Point(102, 206), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release3); + + + EXPECT_TRUE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(3, delegate->tap_count()); +} + +// Check that we don't get a double tap when the two taps are far apart. +TEST_F(GestureRecognizerTest, TwoTapsFarApart) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + // First tap (tested in GestureEventTap) + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + delegate->Reset(); + + // Second tap, close in time but far in distance + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(201, 201), + kTouchId, tes.LeapForward(200)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(201, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + + EXPECT_TRUE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(1, delegate->tap_count()); +} + +// Check that we don't get a double tap when the two taps have a long enough +// delay in between. +TEST_F(GestureRecognizerTest, TwoTapsWithDelayBetween) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + const int kTouchId = 2; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + // First tap (tested in GestureEventTap) + ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + ui::TouchEvent release1(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release1); + delegate->Reset(); + + // Second tap, close in distance but after some delay + ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(2000)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + ui::TouchEvent release2(ui::ET_TOUCH_RELEASED, gfx::Point(101, 201), + kTouchId, tes.LeapForward(50)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release2); + + EXPECT_TRUE(delegate->tap()); + EXPECT_TRUE(delegate->tap_down()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->begin()); + EXPECT_TRUE(delegate->end()); + EXPECT_FALSE(delegate->scroll_begin()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->scroll_end()); + + EXPECT_EQ(1, delegate->tap_count()); +} + +// Checks that if the bounding-box of a gesture changes because of change in +// radius of a touch-point, and not because of change in position, then there +// are not gesture events from that. +TEST_F(GestureRecognizerTest, BoundingBoxRadiusChange) { + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 234; + const int kWindowHeight = 345; + const int kTouchId = 5, kTouchId2 = 7; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + TimedEvents tes; + + ui::TouchEvent press1( + ui::ET_TOUCH_PRESSED, gfx::Point(101, 201), kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_TRUE(delegate->bounding_box().IsEmpty()); + + delegate->Reset(); + + ui::TouchEvent press2( + ui::ET_TOUCH_PRESSED, gfx::Point(201, 201), kTouchId2, + tes.LeapForward(400)); + press2.set_radius_x(5); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_FALSE(delegate->pinch_begin()); + EXPECT_EQ(gfx::Rect(101, 201, 100, 0).ToString(), + delegate->bounding_box().ToString()); + + delegate->Reset(); + + ui::TouchEvent move1(ui::ET_TOUCH_MOVED, gfx::Point(141, 201), kTouchId, + tes.LeapForward(40)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1); + EXPECT_TRUE(delegate->pinch_begin()); + EXPECT_EQ(gfx::Rect(141, 201, 60, 0).ToString(), + delegate->bounding_box().ToString()); + + delegate->Reset(); + + // The position doesn't move, but the radius changes. + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(101, 201), kTouchId, + tes.LeapForward(40)); + move2.set_radius_x(50); + move2.set_radius_y(60); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + EXPECT_FALSE(delegate->tap()); + EXPECT_FALSE(delegate->tap_cancel()); + EXPECT_FALSE(delegate->scroll_update()); + EXPECT_FALSE(delegate->pinch_update()); + + delegate->Reset(); +} + +// Checks that slow scrolls deliver the correct deltas. +// In particular, fix for http;//crbug.com/150573. +TEST_F(GestureRecognizerTest, NoDriftInScroll) { + ui::GestureConfiguration::set_max_touch_move_in_pixels_for_click(3); + ui::GestureConfiguration::set_min_scroll_delta_squared(9); + scoped_ptr<GestureEventConsumeDelegate> delegate( + new GestureEventConsumeDelegate()); + const int kWindowWidth = 234; + const int kWindowHeight = 345; + const int kTouchId = 5; + TimedEvents tes; + gfx::Rect bounds(100, 200, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + delegate.get(), -1234, bounds, root_window())); + + ui::TouchEvent press1( + ui::ET_TOUCH_PRESSED, gfx::Point(101, 208), kTouchId, tes.Now()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press1); + EXPECT_TRUE(delegate->begin()); + + delegate->Reset(); + + ui::TouchEvent move1(ui::ET_TOUCH_MOVED, gfx::Point(101, 206), kTouchId, + tes.LeapForward(40)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move1); + EXPECT_FALSE(delegate->scroll_begin()); + + delegate->Reset(); + + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(101, 204), kTouchId, + tes.LeapForward(40)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + EXPECT_TRUE(delegate->tap_cancel()); + EXPECT_TRUE(delegate->scroll_begin()); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_EQ(-4, delegate->scroll_y()); + + delegate->Reset(); + + ui::TouchEvent move3(ui::ET_TOUCH_MOVED, gfx::Point(101, 204), kTouchId, + tes.LeapForward(40)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move3); + EXPECT_FALSE(delegate->scroll_update()); + + delegate->Reset(); + + ui::TouchEvent move4(ui::ET_TOUCH_MOVED, gfx::Point(101, 203), kTouchId, + tes.LeapForward(40)); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move4); + EXPECT_TRUE(delegate->scroll_update()); + EXPECT_EQ(-1, delegate->scroll_y()); + + delegate->Reset(); +} + +} // namespace test +} // namespace aura diff --git a/chromium/ui/aura/layout_manager.cc b/chromium/ui/aura/layout_manager.cc new file mode 100644 index 00000000000..1621995d5a7 --- /dev/null +++ b/chromium/ui/aura/layout_manager.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2011 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 "ui/aura/layout_manager.h" + +#include "ui/aura/window.h" + +namespace aura { + +LayoutManager::LayoutManager() { +} + +LayoutManager::~LayoutManager() { +} + +void LayoutManager::SetChildBoundsDirect(aura::Window* child, + const gfx::Rect& bounds) { + child->SetBoundsInternal(bounds); +} + +} // namespace aura diff --git a/chromium/ui/aura/layout_manager.h b/chromium/ui/aura/layout_manager.h new file mode 100644 index 00000000000..2ae460676bc --- /dev/null +++ b/chromium/ui/aura/layout_manager.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef UI_AURA_LAYOUT_MANAGER_H_ +#define UI_AURA_LAYOUT_MANAGER_H_ + +#include "base/basictypes.h" +#include "ui/aura/aura_export.h" + +namespace gfx { +class Rect; +} + +namespace aura { +class Window; + +// An interface implemented by an object that places child windows. +class AURA_EXPORT LayoutManager { + public: + LayoutManager(); + virtual ~LayoutManager(); + + // Invoked when the window is resized. + virtual void OnWindowResized() = 0; + + // Invoked when the window |child| has been added. + virtual void OnWindowAddedToLayout(Window* child) = 0; + + // Invoked prior to removing |window|. + virtual void OnWillRemoveWindowFromLayout(Window* child) = 0; + + // Invoked after removing |window|. + virtual void OnWindowRemovedFromLayout(Window* child) = 0; + + // Invoked when the |SetVisible()| is invoked on the window |child|. + // |visible| is the value supplied to |SetVisible()|. If |visible| is true, + // window->IsVisible() may still return false. See description in + // Window::IsVisible() for details. + virtual void OnChildWindowVisibilityChanged(Window* child, bool visible) = 0; + + // Invoked when |Window::SetBounds| is called on |child|. + // Implementation must call |SetChildBoundsDirect| to change the + // |child|'s bounds. LayoutManager may modify |requested_bounds| + // before applying, or ignore the request. + virtual void SetChildBounds(Window* child, + const gfx::Rect& requested_bounds) = 0; + + protected: + // Sets the child's bounds forcibly. LayoutManager is responsible + // for checking the state and make sure the bounds are correctly + // adjusted. + void SetChildBoundsDirect(aura::Window* child, const gfx::Rect& bounds); +}; + +} // namespace aura + +#endif // UI_AURA_LAYOUT_MANAGER_H_ diff --git a/chromium/ui/aura/remote_root_window_host_win.cc b/chromium/ui/aura/remote_root_window_host_win.cc new file mode 100644 index 00000000000..d4285389578 --- /dev/null +++ b/chromium/ui/aura/remote_root_window_host_win.cc @@ -0,0 +1,571 @@ +// 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 "ui/aura/remote_root_window_host_win.h" + +#include <windows.h> + +#include <algorithm> + +#include "base/message_loop/message_loop.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_sender.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/client/cursor_client.h" +#include "ui/aura/root_window.h" +#include "ui/base/cursor/cursor_loader_win.h" +#include "ui/base/events/event_utils.h" +#include "ui/base/keycodes/keyboard_code_conversion_win.h" +#include "ui/base/view_prop.h" +#include "ui/gfx/insets.h" +#include "ui/metro_viewer/metro_viewer_messages.h" + +namespace aura { + +namespace { + +const char* kRootWindowHostWinKey = "__AURA_REMOTE_ROOT_WINDOW_HOST_WIN__"; + +// Sets the keystate for the virtual key passed in to down or up. +void SetKeyState(uint8* key_states, bool key_down, uint32 virtual_key_code) { + DCHECK(key_states); + + if (key_down) + key_states[virtual_key_code] |= 0x80; + else + key_states[virtual_key_code] &= 0x7F; +} + +// Sets the keyboard states for the Shift/Control/Alt/Caps lock keys. +void SetVirtualKeyStates(uint32 flags) { + uint8 keyboard_state[256] = {0}; + ::GetKeyboardState(keyboard_state); + + SetKeyState(keyboard_state, !!(flags & ui::EF_SHIFT_DOWN), VK_SHIFT); + SetKeyState(keyboard_state, !!(flags & ui::EF_CONTROL_DOWN), VK_CONTROL); + SetKeyState(keyboard_state, !!(flags & ui::EF_ALT_DOWN), VK_MENU); + SetKeyState(keyboard_state, !!(flags & ui::EF_CAPS_LOCK_DOWN), VK_CAPITAL); + + ::SetKeyboardState(keyboard_state); +} + +} // namespace + +void HandleOpenFile(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenFileCompletion& on_success, + const FileSelectionCanceled& on_failure) { + DCHECK(aura::RemoteRootWindowHostWin::Instance()); + aura::RemoteRootWindowHostWin::Instance()->HandleOpenFile(title, + default_path, + filter, + on_success, + on_failure); +} + +void HandleOpenMultipleFiles(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenMultipleFilesCompletion& on_success, + const FileSelectionCanceled& on_failure) { + DCHECK(aura::RemoteRootWindowHostWin::Instance()); + aura::RemoteRootWindowHostWin::Instance()->HandleOpenMultipleFiles( + title, + default_path, + filter, + on_success, + on_failure); +} + +void HandleSaveFile(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + int filter_index, + const base::string16& default_extension, + const SaveFileCompletion& on_success, + const FileSelectionCanceled& on_failure) { + DCHECK(aura::RemoteRootWindowHostWin::Instance()); + aura::RemoteRootWindowHostWin::Instance()->HandleSaveFile(title, + default_path, + filter, + filter_index, + default_extension, + on_success, + on_failure); +} + +void HandleSelectFolder(const base::string16& title, + const SelectFolderCompletion& on_success, + const FileSelectionCanceled& on_failure) { + DCHECK(aura::RemoteRootWindowHostWin::Instance()); + aura::RemoteRootWindowHostWin::Instance()->HandleSelectFolder(title, + on_success, + on_failure); +} + +RemoteRootWindowHostWin* g_instance = NULL; + +RemoteRootWindowHostWin* RemoteRootWindowHostWin::Instance() { + return g_instance; +} + +RemoteRootWindowHostWin* RemoteRootWindowHostWin::Create( + const gfx::Rect& bounds) { + g_instance = new RemoteRootWindowHostWin(bounds); + return g_instance; +} + +RemoteRootWindowHostWin::RemoteRootWindowHostWin(const gfx::Rect& bounds) + : delegate_(NULL), + host_(NULL), + ignore_mouse_moves_until_set_cursor_ack_(false) { + prop_.reset(new ui::ViewProp(NULL, kRootWindowHostWinKey, this)); +} + +RemoteRootWindowHostWin::~RemoteRootWindowHostWin() { +} + +void RemoteRootWindowHostWin::Connected(IPC::Sender* host) { + CHECK(host_ == NULL); + host_ = host; +} + +void RemoteRootWindowHostWin::Disconnected() { + CHECK(host_ != NULL); + host_ = NULL; +} + +bool RemoteRootWindowHostWin::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(RemoteRootWindowHostWin, message) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_MouseMoved, OnMouseMoved) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_MouseButton, OnMouseButton) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_KeyDown, OnKeyDown) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_KeyUp, OnKeyUp) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_Character, OnChar) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_VisibilityChanged, + OnVisibilityChanged) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_TouchDown, + OnTouchDown) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_TouchUp, + OnTouchUp) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_TouchMoved, + OnTouchMoved) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_FileSaveAsDone, + OnFileSaveAsDone) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_FileOpenDone, + OnFileOpenDone) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_MultiFileOpenDone, + OnMultiFileOpenDone) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_SelectFolderDone, + OnSelectFolderDone) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_WindowActivated, + OnWindowActivated) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_SetCursorPosAck, + OnSetCursorPosAck) + IPC_MESSAGE_HANDLER(MetroViewerHostMsg_WindowSizeChanged, + OnWindowSizeChanged) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void RemoteRootWindowHostWin::HandleOpenFile( + const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenFileCompletion& on_success, + const FileSelectionCanceled& on_failure) { + if (!host_) + return; + + // Can only have one of these operations in flight. + DCHECK(file_open_completion_callback_.is_null()); + DCHECK(failure_callback_.is_null()); + + file_open_completion_callback_ = on_success; + failure_callback_ = on_failure; + + host_->Send(new MetroViewerHostMsg_DisplayFileOpen(title, + filter, + default_path, + false)); +} + +void RemoteRootWindowHostWin::HandleOpenMultipleFiles( + const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenMultipleFilesCompletion& on_success, + const FileSelectionCanceled& on_failure) { + if (!host_) + return; + + // Can only have one of these operations in flight. + DCHECK(multi_file_open_completion_callback_.is_null()); + DCHECK(failure_callback_.is_null()); + multi_file_open_completion_callback_ = on_success; + failure_callback_ = on_failure; + + host_->Send(new MetroViewerHostMsg_DisplayFileOpen(title, + filter, + default_path, + true)); +} + +void RemoteRootWindowHostWin::HandleSaveFile( + const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + int filter_index, + const base::string16& default_extension, + const SaveFileCompletion& on_success, + const FileSelectionCanceled& on_failure) { + if (!host_) + return; + + MetroViewerHostMsg_SaveAsDialogParams params; + params.title = title; + params.default_extension = default_extension; + params.filter = filter; + params.filter_index = filter_index; + params.suggested_name = default_path; + + // Can only have one of these operations in flight. + DCHECK(file_saveas_completion_callback_.is_null()); + DCHECK(failure_callback_.is_null()); + file_saveas_completion_callback_ = on_success; + failure_callback_ = on_failure; + + host_->Send(new MetroViewerHostMsg_DisplayFileSaveAs(params)); +} + +void RemoteRootWindowHostWin::HandleSelectFolder( + const base::string16& title, + const SelectFolderCompletion& on_success, + const FileSelectionCanceled& on_failure) { + if (!host_) + return; + + // Can only have one of these operations in flight. + DCHECK(select_folder_completion_callback_.is_null()); + DCHECK(failure_callback_.is_null()); + select_folder_completion_callback_ = on_success; + failure_callback_ = on_failure; + + host_->Send(new MetroViewerHostMsg_DisplaySelectFolder(title)); +} + +Window* RemoteRootWindowHostWin::GetAshWindow() { + return GetRootWindow(); +} + +void RemoteRootWindowHostWin::SetDelegate(RootWindowHostDelegate* delegate) { + delegate_ = delegate; +} + +RootWindow* RemoteRootWindowHostWin::GetRootWindow() { + return delegate_->AsRootWindow(); +} + +gfx::AcceleratedWidget RemoteRootWindowHostWin::GetAcceleratedWidget() { + // TODO(cpu): This is bad. Chrome's compositor needs a valid window + // initially and then later on we swap it. Since the compositor never + // uses this initial window we tell ourselves this hack is ok to get + // thing off the ground. + return ::GetDesktopWindow(); +} + +void RemoteRootWindowHostWin::Show() { +} + +void RemoteRootWindowHostWin::Hide() { + NOTIMPLEMENTED(); +} + +void RemoteRootWindowHostWin::ToggleFullScreen() { +} + +gfx::Rect RemoteRootWindowHostWin::GetBounds() const { + gfx::Rect r(gfx::Point(0, 0), aura::RootWindowHost::GetNativeScreenSize()); + return r; +} + +void RemoteRootWindowHostWin::SetBounds(const gfx::Rect& bounds) { + delegate_->OnHostResized(bounds.size()); +} + +gfx::Insets RemoteRootWindowHostWin::GetInsets() const { + return gfx::Insets(); +} + +void RemoteRootWindowHostWin::SetInsets(const gfx::Insets& insets) { +} + +gfx::Point RemoteRootWindowHostWin::GetLocationOnNativeScreen() const { + return gfx::Point(0, 0); +} + +void RemoteRootWindowHostWin::SetCursor(gfx::NativeCursor native_cursor) { + if (!host_) + return; + host_->Send( + new MetroViewerHostMsg_SetCursor(uint64(native_cursor.platform()))); +} + +void RemoteRootWindowHostWin::SetCapture() { +} + +void RemoteRootWindowHostWin::ReleaseCapture() { +} + +bool RemoteRootWindowHostWin::QueryMouseLocation(gfx::Point* location_return) { + aura::client::CursorClient* cursor_client = + aura::client::GetCursorClient(GetRootWindow()); + if (cursor_client && !cursor_client->IsMouseEventsEnabled()) { + *location_return = gfx::Point(0, 0); + return false; + } + POINT pt; + GetCursorPos(&pt); + *location_return = + gfx::Point(static_cast<int>(pt.x), static_cast<int>(pt.y)); + return true; +} + +bool RemoteRootWindowHostWin::ConfineCursorToRootWindow() { + return true; +} + +void RemoteRootWindowHostWin::UnConfineCursor() { +} + +void RemoteRootWindowHostWin::OnCursorVisibilityChanged(bool show) { + NOTIMPLEMENTED(); +} + +void RemoteRootWindowHostWin::MoveCursorTo(const gfx::Point& location) { + VLOG(1) << "In MoveCursorTo: " << location.x() << ", " << location.y(); + if (!host_) + return; + + // This function can be called in cases like when the mouse cursor is + // restricted within a viewport (For e.g. LockCursor) which assumes that + // subsequent mouse moves would be received starting with the new cursor + // coordinates. This is a challenge for Windows ASH for the reasons + // outlined below. + // Other cases which don't expect this behavior should continue to work + // without issues. + + // The mouse events are received by the viewer process and sent to the + // browser. If we invoke the SetCursor API here we continue to receive + // mouse messages from the viewer which were posted before the SetCursor + // API executes which messes up the state in the browser. To workaround + // this we invoke the SetCursor API in the viewer process and ignore + // mouse messages until we received an ACK from the viewer indicating that + // the SetCursor operation completed. + ignore_mouse_moves_until_set_cursor_ack_ = true; + VLOG(1) << "In MoveCursorTo. Sending IPC"; + host_->Send(new MetroViewerHostMsg_SetCursorPos(location.x(), location.y())); +} + +void RemoteRootWindowHostWin::SetFocusWhenShown(bool focus_when_shown) { + NOTIMPLEMENTED(); +} + +void RemoteRootWindowHostWin::PostNativeEvent( + const base::NativeEvent& native_event) { +} + +void RemoteRootWindowHostWin::OnDeviceScaleFactorChanged( + float device_scale_factor) { + NOTIMPLEMENTED(); +} + +void RemoteRootWindowHostWin::PrepareForShutdown() { +} + +void RemoteRootWindowHostWin::OnMouseMoved(int32 x, int32 y, int32 flags) { + if (ignore_mouse_moves_until_set_cursor_ack_) + return; + + gfx::Point location(x, y); + ui::MouseEvent event(ui::ET_MOUSE_MOVED, location, location, flags); + delegate_->OnHostMouseEvent(&event); +} + +void RemoteRootWindowHostWin::OnMouseButton( + int32 x, int32 y, int32 extra, ui::EventType type, ui::EventFlags flags) { + gfx::Point location(x, y); + ui::MouseEvent mouse_event(type, location, location, flags); + + if (type == ui::ET_MOUSEWHEEL) { + ui::MouseWheelEvent wheel_event(mouse_event, 0, extra); + delegate_->OnHostMouseEvent(&wheel_event); + } else if (type == ui::ET_MOUSE_PRESSED) { + // TODO(shrikant): Ideally modify code in event.cc by adding automatic + // tracking of double clicks in synthetic MouseEvent constructor code. + // Non-synthetic MouseEvent constructor code does automatically track + // this. Need to use some caution while modifying synthetic constructor + // as many tests and other code paths depend on it and apparently + // specifically depend on non implicit tracking of previous mouse event. + if (last_mouse_click_event_ && + ui::MouseEvent::IsRepeatedClickEvent(mouse_event, + *last_mouse_click_event_)) { + mouse_event.SetClickCount(2); + } else { + mouse_event.SetClickCount(1); + } + last_mouse_click_event_ .reset(new ui::MouseEvent(mouse_event)); + delegate_->OnHostMouseEvent(&mouse_event); + } else { + delegate_->OnHostMouseEvent(&mouse_event); + } +} + +void RemoteRootWindowHostWin::OnKeyDown(uint32 vkey, + uint32 repeat_count, + uint32 scan_code, + uint32 flags) { + DispatchKeyboardMessage(ui::ET_KEY_PRESSED, vkey, repeat_count, scan_code, + flags, false); +} + +void RemoteRootWindowHostWin::OnKeyUp(uint32 vkey, + uint32 repeat_count, + uint32 scan_code, + uint32 flags) { + DispatchKeyboardMessage(ui::ET_KEY_RELEASED, vkey, repeat_count, scan_code, + flags, false); +} + +void RemoteRootWindowHostWin::OnChar(uint32 key_code, + uint32 repeat_count, + uint32 scan_code, + uint32 flags) { + DispatchKeyboardMessage(ui::ET_KEY_PRESSED, key_code, repeat_count, + scan_code, flags, true); +} + +void RemoteRootWindowHostWin::OnVisibilityChanged(bool visible) { + if (visible) + delegate_->OnHostActivated(); +} + +void RemoteRootWindowHostWin::OnTouchDown(int32 x, + int32 y, + uint64 timestamp, + uint32 pointer_id) { + ui::TouchEvent event(ui::ET_TOUCH_PRESSED, + gfx::Point(x, y), + pointer_id, + base::TimeDelta::FromMicroseconds(timestamp)); + delegate_->OnHostTouchEvent(&event); +} + +void RemoteRootWindowHostWin::OnTouchUp(int32 x, + int32 y, + uint64 timestamp, + uint32 pointer_id) { + ui::TouchEvent event(ui::ET_TOUCH_RELEASED, + gfx::Point(x, y), + pointer_id, + base::TimeDelta::FromMicroseconds(timestamp)); + delegate_->OnHostTouchEvent(&event); +} + +void RemoteRootWindowHostWin::OnTouchMoved(int32 x, + int32 y, + uint64 timestamp, + uint32 pointer_id) { + ui::TouchEvent event(ui::ET_TOUCH_MOVED, + gfx::Point(x, y), + pointer_id, + base::TimeDelta::FromMicroseconds(timestamp)); + delegate_->OnHostTouchEvent(&event); +} + +void RemoteRootWindowHostWin::OnFileSaveAsDone(bool success, + const base::FilePath& filename, + int filter_index) { + if (success) + file_saveas_completion_callback_.Run(filename, filter_index, NULL); + else + failure_callback_.Run(NULL); + file_saveas_completion_callback_.Reset(); + failure_callback_.Reset(); +} + + +void RemoteRootWindowHostWin::OnFileOpenDone(bool success, + const base::FilePath& filename) { + if (success) + file_open_completion_callback_.Run(base::FilePath(filename), 0, NULL); + else + failure_callback_.Run(NULL); + file_open_completion_callback_.Reset(); + failure_callback_.Reset(); +} + +void RemoteRootWindowHostWin::OnMultiFileOpenDone( + bool success, + const std::vector<base::FilePath>& files) { + if (success) + multi_file_open_completion_callback_.Run(files, NULL); + else + failure_callback_.Run(NULL); + multi_file_open_completion_callback_.Reset(); + failure_callback_.Reset(); +} + +void RemoteRootWindowHostWin::OnSelectFolderDone( + bool success, + const base::FilePath& folder) { + if (success) + select_folder_completion_callback_.Run(base::FilePath(folder), 0, NULL); + else + failure_callback_.Run(NULL); + select_folder_completion_callback_.Reset(); + failure_callback_.Reset(); +} + +void RemoteRootWindowHostWin::OnWindowActivated(bool active) { + active ? GetRootWindow()->Focus() : GetRootWindow()->Blur(); +} + +void RemoteRootWindowHostWin::OnSetCursorPosAck() { + DCHECK(ignore_mouse_moves_until_set_cursor_ack_); + ignore_mouse_moves_until_set_cursor_ack_ = false; +} + +void RemoteRootWindowHostWin::OnWindowSizeChanged(uint32 width, uint32 height) { + SetBounds(gfx::Rect(0, 0, width, height)); +} + +void RemoteRootWindowHostWin::DispatchKeyboardMessage(ui::EventType type, + uint32 vkey, + uint32 repeat_count, + uint32 scan_code, + uint32 flags, + bool is_character) { + if (base::MessageLoop::current()->IsNested()) { + SetVirtualKeyStates(flags); + + uint32 message = is_character ? WM_CHAR : + (type == ui::ET_KEY_PRESSED ? WM_KEYDOWN : WM_KEYUP); + ::PostThreadMessage(::GetCurrentThreadId(), + message, + vkey, + repeat_count | scan_code >> 15); + } else { + ui::KeyEvent event(type, + ui::KeyboardCodeForWindowsKeyCode(vkey), + flags, + is_character); + delegate_->OnHostKeyEvent(&event); + } +} + +} // namespace aura diff --git a/chromium/ui/aura/remote_root_window_host_win.h b/chromium/ui/aura/remote_root_window_host_win.h new file mode 100644 index 00000000000..e2e6d1ca286 --- /dev/null +++ b/chromium/ui/aura/remote_root_window_host_win.h @@ -0,0 +1,227 @@ +// 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. + +#ifndef UI_AURA_REMOTE_ROOT_WINDOW_HOST_WIN_H_ +#define UI_AURA_REMOTE_ROOT_WINDOW_HOST_WIN_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "ui/aura/root_window_host.h" +#include "ui/base/events/event.h" +#include "ui/base/events/event_constants.h" +#include "ui/gfx/native_widget_types.h" + +namespace base { +class FilePath; +} + +namespace ui { +class ViewProp; +} + +namespace IPC { +class Message; +class Sender; +} + +namespace aura { + +typedef base::Callback<void(const base::FilePath&, int, void*)> + OpenFileCompletion; + +typedef base::Callback<void(const std::vector<base::FilePath>&, void*)> + OpenMultipleFilesCompletion; + +typedef base::Callback<void(const base::FilePath&, int, void*)> + SaveFileCompletion; + +typedef base::Callback<void(const base::FilePath&, int, void*)> + SelectFolderCompletion; + +typedef base::Callback<void(void*)> FileSelectionCanceled; + +// Handles the open file operation for Metro Chrome Ash. The on_success +// callback passed in is invoked when we receive the opened file name from +// the metro viewer. The on failure callback is invoked on failure. +AURA_EXPORT void HandleOpenFile(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenFileCompletion& on_success, + const FileSelectionCanceled& on_failure); + +// Handles the open multiple file operation for Metro Chrome Ash. The +// on_success callback passed in is invoked when we receive the opened file +// names from the metro viewer. The on failure callback is invoked on failure. +AURA_EXPORT void HandleOpenMultipleFiles( + const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenMultipleFilesCompletion& on_success, + const FileSelectionCanceled& on_failure); + +// Handles the save file operation for Metro Chrome Ash. The on_success +// callback passed in is invoked when we receive the saved file name from +// the metro viewer. The on failure callback is invoked on failure. +AURA_EXPORT void HandleSaveFile(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + int filter_index, + const base::string16& default_extension, + const SaveFileCompletion& on_success, + const FileSelectionCanceled& on_failure); + +// Handles the select folder for Metro Chrome Ash. The on_success +// callback passed in is invoked when we receive the folder name from the +// metro viewer. The on failure callback is invoked on failure. +AURA_EXPORT void HandleSelectFolder(const base::string16& title, + const SelectFolderCompletion& on_success, + const FileSelectionCanceled& on_failure); + +// RootWindowHost implementaton that receives events from a different +// process. In the case of Windows this is the Windows 8 (aka Metro) +// frontend process, which forwards input events to this class. +class AURA_EXPORT RemoteRootWindowHostWin : public RootWindowHost { + public: + static RemoteRootWindowHostWin* Instance(); + static RemoteRootWindowHostWin* Create(const gfx::Rect& bounds); + + // Called when the remote process has established its IPC connection. + // The |host| can be used when we need to send a message to it. + void Connected(IPC::Sender* host); + // Called when the remote process has closed its IPC connection. + void Disconnected(); + + // Called when we have a message from the remote process. + bool OnMessageReceived(const IPC::Message& message); + + void HandleOpenFile(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenFileCompletion& on_success, + const FileSelectionCanceled& on_failure); + + void HandleOpenMultipleFiles(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + const OpenMultipleFilesCompletion& on_success, + const FileSelectionCanceled& on_failure); + + void HandleSaveFile(const base::string16& title, + const base::FilePath& default_path, + const base::string16& filter, + int filter_index, + const base::string16& default_extension, + const SaveFileCompletion& on_success, + const FileSelectionCanceled& on_failure); + + void HandleSelectFolder(const base::string16& title, + const SelectFolderCompletion& on_success, + const FileSelectionCanceled& on_failure); + + // Returns the active ASH root window. + Window* GetAshWindow(); + + private: + explicit RemoteRootWindowHostWin(const gfx::Rect& bounds); + virtual ~RemoteRootWindowHostWin(); + + // IPC message handing methods: + void OnMouseMoved(int32 x, int32 y, int32 flags); + void OnMouseButton(int32 x, + int32 y, + int32 extra, + ui::EventType type, + ui::EventFlags flags); + void OnKeyDown(uint32 vkey, + uint32 repeat_count, + uint32 scan_code, + uint32 flags); + void OnKeyUp(uint32 vkey, + uint32 repeat_count, + uint32 scan_code, + uint32 flags); + void OnChar(uint32 key_code, + uint32 repeat_count, + uint32 scan_code, + uint32 flags); + void OnVisibilityChanged(bool visible); + void OnTouchDown(int32 x, int32 y, uint64 timestamp, uint32 pointer_id); + void OnTouchUp(int32 x, int32 y, uint64 timestamp, uint32 pointer_id); + void OnTouchMoved(int32 x, int32 y, uint64 timestamp, uint32 pointer_id); + void OnFileSaveAsDone(bool success, + const base::FilePath& filename, + int filter_index); + void OnFileOpenDone(bool success, const base::FilePath& filename); + void OnMultiFileOpenDone(bool success, + const std::vector<base::FilePath>& files); + void OnSelectFolderDone(bool success, const base::FilePath& folder); + void OnWindowActivated(bool active); + void OnSetCursorPosAck(); + void OnWindowSizeChanged(uint32 width, uint32 height); + + // RootWindowHost overrides: + virtual void SetDelegate(RootWindowHostDelegate* delegate) OVERRIDE; + virtual RootWindow* GetRootWindow() OVERRIDE; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void ToggleFullScreen() OVERRIDE; + virtual gfx::Rect GetBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual gfx::Insets GetInsets() const OVERRIDE; + virtual void SetInsets(const gfx::Insets& insets) OVERRIDE; + virtual gfx::Point GetLocationOnNativeScreen() const OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor) OVERRIDE; + virtual bool QueryMouseLocation(gfx::Point* location_return) OVERRIDE; + virtual bool ConfineCursorToRootWindow() OVERRIDE; + virtual void UnConfineCursor() OVERRIDE; + virtual void OnCursorVisibilityChanged(bool show) OVERRIDE; + virtual void MoveCursorTo(const gfx::Point& location) OVERRIDE; + virtual void SetFocusWhenShown(bool focus_when_shown) OVERRIDE; + virtual void PostNativeEvent(const base::NativeEvent& native_event) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual void PrepareForShutdown() OVERRIDE; + + // Helper function to dispatch a keyboard message to the desired target. + // The default target is the RootWindowHostDelegate. For nested message loop + // invocations we post a synthetic keyboard message directly into the message + // loop. The dispatcher for the nested loop would then decide how this + // message is routed. + void DispatchKeyboardMessage(ui::EventType type, + uint32 vkey, + uint32 repeat_count, + uint32 scan_code, + uint32 flags, + bool is_character); + + RootWindowHostDelegate* delegate_; + IPC::Sender* host_; + scoped_ptr<ui::ViewProp> prop_; + + // Saved callbacks which inform the caller about the result of the open file/ + // save file/select operations. + OpenFileCompletion file_open_completion_callback_; + OpenMultipleFilesCompletion multi_file_open_completion_callback_; + SaveFileCompletion file_saveas_completion_callback_; + SelectFolderCompletion select_folder_completion_callback_; + FileSelectionCanceled failure_callback_; + + // Set to true if we need to ignore mouse messages until the SetCursorPos + // operation is acked by the viewer. + bool ignore_mouse_moves_until_set_cursor_ack_; + + // Tracking last click event for synthetically generated mouse events. + scoped_ptr<ui::MouseEvent> last_mouse_click_event_; + + DISALLOW_COPY_AND_ASSIGN(RemoteRootWindowHostWin); +}; + +} // namespace aura + +#endif // UI_AURA_REMOTE_ROOT_WINDOW_HOST_WIN_H_ diff --git a/chromium/ui/aura/root_window.cc b/chromium/ui/aura/root_window.cc new file mode 100644 index 00000000000..a638dc1bbe6 --- /dev/null +++ b/chromium/ui/aura/root_window.cc @@ -0,0 +1,1203 @@ +// 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 "ui/aura/root_window.h" + +#include <vector> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "ui/aura/client/activation_client.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/client/cursor_client.h" +#include "ui/aura/client/event_client.h" +#include "ui/aura/client/focus_client.h" +#include "ui/aura/client/screen_position_client.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window_host.h" +#include "ui/aura/root_window_observer.h" +#include "ui/aura/root_window_transformer.h" +#include "ui/aura/window.h" +#include "ui/aura/window_delegate.h" +#include "ui/aura/window_tracker.h" +#include "ui/base/events/event.h" +#include "ui/base/gestures/gesture_recognizer.h" +#include "ui/base/gestures/gesture_types.h" +#include "ui/base/hit_test.h" +#include "ui/base/view_prop.h" +#include "ui/compositor/compositor.h" +#include "ui/compositor/dip_util.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/layer_animator.h" +#include "ui/gfx/display.h" +#include "ui/gfx/point3_f.h" +#include "ui/gfx/point_conversions.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/size_conversions.h" + +using std::vector; + +namespace aura { + +namespace { + +const char kRootWindowForAcceleratedWidget[] = + "__AURA_ROOT_WINDOW_ACCELERATED_WIDGET__"; + +// Returns true if |target| has a non-client (frame) component at |location|, +// in window coordinates. +bool IsNonClientLocation(Window* target, const gfx::Point& location) { + if (!target->delegate()) + return false; + int hit_test_code = target->delegate()->GetNonClientComponent(location); + return hit_test_code != HTCLIENT && hit_test_code != HTNOWHERE; +} + +float GetDeviceScaleFactorFromDisplay(Window* window) { + return gfx::Screen::GetScreenFor(window)-> + GetDisplayNearestWindow(window).device_scale_factor(); +} + +Window* ConsumerToWindow(ui::GestureConsumer* consumer) { + return consumer && !consumer->ignores_events() ? + static_cast<Window*>(consumer) : NULL; +} + +void SetLastMouseLocation(const RootWindow* root_window, + const gfx::Point& location_in_root) { + client::ScreenPositionClient* client = + client::GetScreenPositionClient(root_window); + if (client) { + gfx::Point location_in_screen = location_in_root; + client->ConvertPointToScreen(root_window, &location_in_screen); + Env::GetInstance()->set_last_mouse_location(location_in_screen); + } else { + Env::GetInstance()->set_last_mouse_location(location_in_root); + } +} + +RootWindowHost* CreateHost(RootWindow* root_window, + const RootWindow::CreateParams& params) { + RootWindowHost* host = params.host ? + params.host : RootWindowHost::Create(params.initial_bounds); + host->SetDelegate(root_window); + return host; +} + +class SimpleRootWindowTransformer : public RootWindowTransformer { + public: + SimpleRootWindowTransformer(const RootWindow* root_window, + const gfx::Transform& transform) + : root_window_(root_window), + transform_(transform) { + } + + // RootWindowTransformer overrides: + virtual gfx::Transform GetTransform() const OVERRIDE { + return transform_; + } + + virtual gfx::Transform GetInverseTransform() const OVERRIDE { + gfx::Transform invert; + if (!transform_.GetInverse(&invert)) + return transform_; + return invert; + } + + virtual gfx::Rect GetRootWindowBounds( + const gfx::Size& host_size) const OVERRIDE { + gfx::Rect bounds(host_size); + gfx::RectF new_bounds(ui::ConvertRectToDIP(root_window_->layer(), bounds)); + transform_.TransformRect(&new_bounds); + return gfx::Rect(gfx::ToFlooredSize(new_bounds.size())); + } + + virtual gfx::Insets GetHostInsets() const OVERRIDE { + return gfx::Insets(); + } + + private: + virtual ~SimpleRootWindowTransformer() {} + + const RootWindow* root_window_; + const gfx::Transform transform_; + + DISALLOW_COPY_AND_ASSIGN(SimpleRootWindowTransformer); +}; + +} // namespace + +RootWindow::CreateParams::CreateParams(const gfx::Rect& a_initial_bounds) + : initial_bounds(a_initial_bounds), + host(NULL) { +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, public: + +RootWindow::RootWindow(const CreateParams& params) + : Window(NULL), + host_(CreateHost(this, params)), + schedule_paint_factory_(this), + event_factory_(this), + touch_ids_down_(0), + last_cursor_(ui::kCursorNull), + mouse_pressed_handler_(NULL), + mouse_moved_handler_(NULL), + mouse_event_dispatch_target_(NULL), + event_dispatch_target_(NULL), + gesture_recognizer_(ui::GestureRecognizer::Create(this)), + synthesize_mouse_move_(false), + waiting_on_compositing_end_(false), + draw_on_compositing_end_(false), + defer_draw_scheduling_(false), + move_hold_count_(0), + held_event_factory_(this), + repostable_event_factory_(this) { + SetName("RootWindow"); + + compositor_.reset(new ui::Compositor(this, host_->GetAcceleratedWidget())); + DCHECK(compositor_.get()); + compositor_->AddObserver(this); + + prop_.reset(new ui::ViewProp(host_->GetAcceleratedWidget(), + kRootWindowForAcceleratedWidget, + this)); +} + +RootWindow::~RootWindow() { + compositor_->RemoveObserver(this); + // Make sure to destroy the compositor before terminating so that state is + // cleared and we don't hit asserts. + compositor_.reset(); + + // Tear down in reverse. Frees any references held by the host. + host_.reset(NULL); + + // An observer may have been added by an animation on the RootWindow. + layer()->GetAnimator()->RemoveObserver(this); +} + +// static +RootWindow* RootWindow::GetForAcceleratedWidget( + gfx::AcceleratedWidget widget) { + return reinterpret_cast<RootWindow*>( + ui::ViewProp::GetValue(widget, kRootWindowForAcceleratedWidget)); +} + +void RootWindow::Init() { + compositor()->SetScaleAndSize(GetDeviceScaleFactorFromDisplay(this), + host_->GetBounds().size()); + Window::Init(ui::LAYER_NOT_DRAWN); + compositor()->SetRootLayer(layer()); + transformer_.reset(new SimpleRootWindowTransformer(this, gfx::Transform())); + UpdateRootWindowSize(GetHostSize()); + Env::GetInstance()->NotifyRootWindowInitialized(this); + Show(); +} + +void RootWindow::ShowRootWindow() { + host_->Show(); +} + +void RootWindow::HideRootWindow() { + host_->Hide(); +} + +void RootWindow::PrepareForShutdown() { + host_->PrepareForShutdown(); + // discard synthesize event request as well. + synthesize_mouse_move_ = false; +} + +void RootWindow::RepostEvent(const ui::LocatedEvent& event) { + // We allow for only one outstanding repostable event. This is used + // in exiting context menus. A dropped repost request is allowed. + if (event.type() == ui::ET_MOUSE_PRESSED) { + held_repostable_event_.reset( + new ui::MouseEvent( + static_cast<const ui::MouseEvent&>(event), + static_cast<aura::Window*>(event.target()), + static_cast<aura::Window*>(this))); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RootWindow::DispatchHeldEvents, + repostable_event_factory_.GetWeakPtr())); + } else { + DCHECK(event.type() == ui::ET_GESTURE_TAP_DOWN); + held_repostable_event_.reset(); + // TODO(sschmitz): add similar code for gesture events. + } +} + +RootWindowHostDelegate* RootWindow::AsRootWindowHostDelegate() { + return this; +} + +void RootWindow::SetHostSize(const gfx::Size& size_in_pixel) { + DispatchHeldEvents(); + gfx::Rect bounds = host_->GetBounds(); + bounds.set_size(size_in_pixel); + host_->SetBounds(bounds); + + // Requery the location to constrain it within the new root window size. + gfx::Point point; + if (host_->QueryMouseLocation(&point)) + SetLastMouseLocation(this, ui::ConvertPointToDIP(layer(), point)); + + synthesize_mouse_move_ = false; +} + +gfx::Size RootWindow::GetHostSize() const { + return host_->GetBounds().size(); +} + +void RootWindow::SetHostBounds(const gfx::Rect& bounds_in_pixel) { + DCHECK(!bounds_in_pixel.IsEmpty()); + DispatchHeldEvents(); + host_->SetBounds(bounds_in_pixel); + synthesize_mouse_move_ = false; +} + +gfx::Point RootWindow::GetHostOrigin() const { + return host_->GetBounds().origin(); +} + +void RootWindow::SetCursor(gfx::NativeCursor cursor) { + last_cursor_ = cursor; + // A lot of code seems to depend on NULL cursors actually showing an arrow, + // so just pass everything along to the host. + host_->SetCursor(cursor); +} + +void RootWindow::OnCursorVisibilityChanged(bool show) { + host_->OnCursorVisibilityChanged(show); +} + +void RootWindow::OnMouseEventsEnableStateChanged(bool enabled) { + // Send entered / exited so that visual state can be updated to match + // mouse events state. + PostMouseMoveEventAfterWindowChange(); + // TODO(mazda): Add code to disable mouse events when |enabled| == false. +} + +void RootWindow::MoveCursorTo(const gfx::Point& location_in_dip) { + gfx::Point host_location(location_in_dip); + ConvertPointToHost(&host_location); + MoveCursorToInternal(location_in_dip, host_location); +} + +void RootWindow::MoveCursorToHostLocation(const gfx::Point& host_location) { + gfx::Point root_location(host_location); + ConvertPointFromHost(&root_location); + MoveCursorToInternal(root_location, host_location); +} + +bool RootWindow::ConfineCursorToWindow() { + // We would like to be able to confine the cursor to that window. However, + // currently, we do not have such functionality in X. So we just confine + // to the root window. This is ok because this option is currently only + // being used in fullscreen mode, so root_window bounds = window bounds. + return host_->ConfineCursorToRootWindow(); +} + +void RootWindow::Draw() { + defer_draw_scheduling_ = false; + if (waiting_on_compositing_end_) { + draw_on_compositing_end_ = true; + return; + } + waiting_on_compositing_end_ = true; + + TRACE_EVENT_ASYNC_BEGIN0("ui", "RootWindow::Draw", + compositor_->last_started_frame() + 1); + + compositor_->Draw(); +} + +void RootWindow::ScheduleFullRedraw() { + compositor_->ScheduleFullRedraw(); +} + +void RootWindow::ScheduleRedrawRect(const gfx::Rect& damage_rect) { + compositor_->ScheduleRedrawRect(damage_rect); +} + +Window* RootWindow::GetGestureTarget(ui::GestureEvent* event) { + Window* target = client::GetCaptureWindow(this); + if (!target) { + target = ConsumerToWindow( + gesture_recognizer_->GetTargetForGestureEvent(event)); + } + + return target; +} + +bool RootWindow::DispatchGestureEvent(ui::GestureEvent* event) { + DispatchHeldEvents(); + + Window* target = GetGestureTarget(event); + if (target) { + event->ConvertLocationToTarget(static_cast<Window*>(this), target); + ProcessEvent(target, event); + return event->handled(); + } + + return false; +} + +void RootWindow::OnWindowDestroying(Window* window) { + DispatchMouseExitToHidingWindow(window); + OnWindowHidden(window, WINDOW_DESTROYED); + + if (window->IsVisible() && + window->ContainsPointInRoot(GetLastMouseLocationInRoot())) { + PostMouseMoveEventAfterWindowChange(); + } +} + +void RootWindow::OnWindowBoundsChanged(Window* window, + bool contained_mouse_point) { + if (contained_mouse_point || + (window->IsVisible() && + window->ContainsPointInRoot(GetLastMouseLocationInRoot()))) { + PostMouseMoveEventAfterWindowChange(); + } +} + +void RootWindow::DispatchMouseExitToHidingWindow(Window* window) { + // The mouse capture is intentionally ignored. Think that a mouse enters + // to a window, the window sets the capture, the mouse exits the window, + // and then it releases the capture. In that case OnMouseExited won't + // be called. So it is natural not to emit OnMouseExited even though + // |window| is the capture window. + gfx::Point last_mouse_location = GetLastMouseLocationInRoot(); + if (window->Contains(mouse_moved_handler_) && + window->ContainsPointInRoot(last_mouse_location)) { + ui::MouseEvent event(ui::ET_MOUSE_EXITED, + last_mouse_location, + last_mouse_location, + ui::EF_NONE); + DispatchMouseEnterOrExit(event, ui::ET_MOUSE_EXITED); + } +} + +void RootWindow::OnWindowVisibilityChanged(Window* window, bool is_visible) { + if (!is_visible) + OnWindowHidden(window, WINDOW_HIDDEN); + + if (window->ContainsPointInRoot(GetLastMouseLocationInRoot())) + PostMouseMoveEventAfterWindowChange(); +} + +void RootWindow::OnWindowTransformed(Window* window, bool contained_mouse) { + if (contained_mouse || + (window->IsVisible() && + window->ContainsPointInRoot(GetLastMouseLocationInRoot()))) { + PostMouseMoveEventAfterWindowChange(); + } +} + +void RootWindow::OnKeyboardMappingChanged() { + FOR_EACH_OBSERVER(RootWindowObserver, observers_, + OnKeyboardMappingChanged(this)); +} + +void RootWindow::OnRootWindowHostCloseRequested() { + FOR_EACH_OBSERVER(RootWindowObserver, observers_, + OnRootWindowHostCloseRequested(this)); +} + +void RootWindow::AddRootWindowObserver(RootWindowObserver* observer) { + observers_.AddObserver(observer); +} + +void RootWindow::RemoveRootWindowObserver(RootWindowObserver* observer) { + observers_.RemoveObserver(observer); +} + +void RootWindow::PostNativeEvent(const base::NativeEvent& native_event) { +#if !defined(OS_MACOSX) + host_->PostNativeEvent(native_event); +#endif +} + +void RootWindow::ConvertPointToNativeScreen(gfx::Point* point) const { + ConvertPointToHost(point); + gfx::Point location = host_->GetLocationOnNativeScreen(); + point->Offset(location.x(), location.y()); +} + +void RootWindow::ConvertPointFromNativeScreen(gfx::Point* point) const { + gfx::Point location = host_->GetLocationOnNativeScreen(); + point->Offset(-location.x(), -location.y()); + ConvertPointFromHost(point); +} + +void RootWindow::ConvertPointToHost(gfx::Point* point) const { + gfx::Point3F point_3f(*point); + GetRootTransform().TransformPoint(point_3f); + *point = gfx::ToFlooredPoint(point_3f.AsPointF()); +} + +void RootWindow::ConvertPointFromHost(gfx::Point* point) const { + gfx::Point3F point_3f(*point); + GetInverseRootTransform().TransformPoint(point_3f); + *point = gfx::ToFlooredPoint(point_3f.AsPointF()); +} + +void RootWindow::ProcessedTouchEvent(ui::TouchEvent* event, + Window* window, + ui::EventResult result) { + scoped_ptr<ui::GestureRecognizer::Gestures> gestures; + gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture( + *event, result, window)); + ProcessGestures(gestures.get()); +} + +void RootWindow::SetGestureRecognizerForTesting(ui::GestureRecognizer* gr) { + gesture_recognizer_.reset(gr); +} + +gfx::AcceleratedWidget RootWindow::GetAcceleratedWidget() { + return host_->GetAcceleratedWidget(); +} + +void RootWindow::ToggleFullScreen() { + host_->ToggleFullScreen(); +} + +void RootWindow::HoldPointerMoves() { + if (!move_hold_count_) + held_event_factory_.InvalidateWeakPtrs(); + ++move_hold_count_; + TRACE_EVENT_ASYNC_BEGIN0("ui", "RootWindow::HoldPointerMoves", this); +} + +void RootWindow::ReleasePointerMoves() { + --move_hold_count_; + DCHECK_GE(move_hold_count_, 0); + if (!move_hold_count_ && held_move_event_) { + // We don't want to call DispatchHeldEvents directly, because this might be + // called from a deep stack while another event, in which case dispatching + // another one may not be safe/expected. Instead we post a task, that we + // may cancel if HoldPointerMoves is called again before it executes. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RootWindow::DispatchHeldEvents, + held_event_factory_.GetWeakPtr())); + } + TRACE_EVENT_ASYNC_END0("ui", "RootWindow::HoldPointerMoves", this); +} + +void RootWindow::SetFocusWhenShown(bool focused) { + host_->SetFocusWhenShown(focused); +} + +gfx::Point RootWindow::GetLastMouseLocationInRoot() const { + gfx::Point location = Env::GetInstance()->last_mouse_location(); + client::ScreenPositionClient* client = client::GetScreenPositionClient(this); + if (client) + client->ConvertPointFromScreen(this, &location); + return location; +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, Window overrides: + +RootWindow* RootWindow::GetRootWindow() { + return this; +} + +const RootWindow* RootWindow::GetRootWindow() const { + return this; +} + +void RootWindow::SetTransform(const gfx::Transform& transform) { + scoped_ptr<RootWindowTransformer> transformer( + new SimpleRootWindowTransformer(this, transform)); + SetRootWindowTransformer(transformer.Pass()); +} + +void RootWindow::SetRootWindowTransformer( + scoped_ptr<RootWindowTransformer> transformer) { + transformer_ = transformer.Pass(); + host_->SetInsets(transformer_->GetHostInsets()); + Window::SetTransform(transformer_->GetTransform()); + // If the layer is not animating, then we need to update the root window + // size immediately. + if (!layer()->GetAnimator()->is_animating()) + UpdateRootWindowSize(GetHostSize()); +} + +gfx::Transform RootWindow::GetRootTransform() const { + float scale = ui::GetDeviceScaleFactor(layer()); + gfx::Transform transform; + transform.Scale(scale, scale); + transform *= transformer_->GetTransform(); + return transform; +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, ui::EventTarget implementation: + +ui::EventTarget* RootWindow::GetParentTarget() { + return client::GetEventClient(this) ? + client::GetEventClient(this)->GetToplevelEventTarget() : + Env::GetInstance(); +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, ui::CompositorDelegate implementation: + +void RootWindow::ScheduleDraw() { + DCHECK(!ui::Compositor::WasInitializedWithThread()); + if (!defer_draw_scheduling_) { + defer_draw_scheduling_ = true; + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RootWindow::Draw, schedule_paint_factory_.GetWeakPtr())); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, ui::CompositorObserver implementation: + +void RootWindow::OnCompositingDidCommit(ui::Compositor*) { +} + +void RootWindow::OnCompositingStarted(ui::Compositor*, + base::TimeTicks start_time) { +} + +void RootWindow::OnCompositingEnded(ui::Compositor*) { + TRACE_EVENT_ASYNC_END0("ui", "RootWindow::Draw", + compositor_->last_ended_frame()); + waiting_on_compositing_end_ = false; + if (draw_on_compositing_end_) { + draw_on_compositing_end_ = false; + + // Call ScheduleDraw() instead of Draw() in order to allow other + // ui::CompositorObservers to be notified before starting another + // draw cycle. + ScheduleDraw(); + } +} + +void RootWindow::OnCompositingAborted(ui::Compositor*) { +} + +void RootWindow::OnCompositingLockStateChanged(ui::Compositor*) { +} + +void RootWindow::OnUpdateVSyncParameters(ui::Compositor* compositor, + base::TimeTicks timebase, + base::TimeDelta interval) { +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, ui::LayerDelegate implementation: + +void RootWindow::OnDeviceScaleFactorChanged( + float device_scale_factor) { + const bool cursor_is_in_bounds = + GetBoundsInScreen().Contains(Env::GetInstance()->last_mouse_location()); + bool cursor_visible = false; + client::CursorClient* cursor_client = client::GetCursorClient(this); + if (cursor_is_in_bounds && cursor_client) { + cursor_visible = cursor_client->IsCursorVisible(); + if (cursor_visible) + cursor_client->HideCursor(); + } + host_->OnDeviceScaleFactorChanged(device_scale_factor); + Window::OnDeviceScaleFactorChanged(device_scale_factor); + // Update the device scale factor of the cursor client only when the last + // mouse location is on this root window. + if (cursor_is_in_bounds) { + if (cursor_client) { + const gfx::Display& display = + gfx::Screen::GetScreenFor(this)->GetDisplayNearestWindow(this); + cursor_client->SetDisplay(display); + } + } + if (cursor_is_in_bounds && cursor_client && cursor_visible) + cursor_client->ShowCursor(); +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, overridden from aura::Window: + +bool RootWindow::CanFocus() const { + return IsVisible(); +} + +bool RootWindow::CanReceiveEvents() const { + return IsVisible(); +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, overridden from aura::client::CaptureDelegate: + +void RootWindow::UpdateCapture(Window* old_capture, + Window* new_capture) { + if (old_capture && old_capture->GetRootWindow() == this && + old_capture->delegate()) { + // Send a capture changed event with bogus location data. + ui::MouseEvent event(ui::ET_MOUSE_CAPTURE_CHANGED, gfx::Point(), + gfx::Point(), 0); + + ProcessEvent(old_capture, &event); + + old_capture->delegate()->OnCaptureLost(); + } + + // Reset the mouse_moved_handler_ if the mouse_moved_handler_ belongs + // to another root window when losing the capture. + if (mouse_moved_handler_ && old_capture && + old_capture->Contains(mouse_moved_handler_) && + old_capture->GetRootWindow() != this) { + mouse_moved_handler_ = NULL; + } + + if (new_capture) { + // Make all subsequent mouse events and touch go to the capture window. We + // shouldn't need to send an event here as OnCaptureLost should take care of + // that. + if (mouse_moved_handler_ || Env::GetInstance()->is_mouse_button_down()) + mouse_moved_handler_ = new_capture; + } else { + // Make sure mouse_moved_handler gets updated. + SynthesizeMouseMoveEvent(); + } + mouse_pressed_handler_ = NULL; +} + +void RootWindow::SetNativeCapture() { + host_->SetCapture(); +} + +void RootWindow::ReleaseNativeCapture() { + host_->ReleaseCapture(); +} + +bool RootWindow::QueryMouseLocationForTest(gfx::Point* point) const { + return host_->QueryMouseLocation(point); +} + +void RootWindow::ClearMouseHandlers() { + mouse_pressed_handler_ = NULL; + mouse_moved_handler_ = NULL; + mouse_event_dispatch_target_ = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, private: + +void RootWindow::TransformEventForDeviceScaleFactor(ui::LocatedEvent* event) { + event->UpdateForRootTransform(GetInverseRootTransform()); +} + +void RootWindow::MoveCursorToInternal(const gfx::Point& root_location, + const gfx::Point& host_location) { + host_->MoveCursorTo(host_location); + SetLastMouseLocation(this, root_location); + client::CursorClient* cursor_client = client::GetCursorClient(this); + if (cursor_client) { + const gfx::Display& display = + gfx::Screen::GetScreenFor(this)->GetDisplayNearestWindow(this); + cursor_client->SetDisplay(display); + } + synthesize_mouse_move_ = false; +} + +void RootWindow::HandleMouseMoved(const ui::MouseEvent& event, Window* target) { + if (target == mouse_moved_handler_) + return; + + DispatchMouseEnterOrExit(event, ui::ET_MOUSE_EXITED); + + if (mouse_event_dispatch_target_ != target) { + mouse_moved_handler_ = NULL; + return; + } + + mouse_moved_handler_ = target; + + DispatchMouseEnterOrExit(event, ui::ET_MOUSE_ENTERED); +} + +void RootWindow::DispatchMouseEnterOrExit(const ui::MouseEvent& event, + ui::EventType type) { + if (!mouse_moved_handler_ || !mouse_moved_handler_->delegate()) + return; + + ui::MouseEvent translated_event(event, + static_cast<Window*>(this), + mouse_moved_handler_, + type, + event.flags() | ui::EF_IS_SYNTHESIZED); + ProcessEvent(mouse_moved_handler_, &translated_event); +} + +void RootWindow::ProcessEvent(Window* target, ui::Event* event) { + Window* old_target = event_dispatch_target_; + event_dispatch_target_ = target; + if (DispatchEvent(target, event)) + event_dispatch_target_ = old_target; +} + +bool RootWindow::ProcessGestures(ui::GestureRecognizer::Gestures* gestures) { + if (!gestures || gestures->empty()) + return false; + + Window* target = GetGestureTarget(gestures->get().at(0)); + Window* old_target = event_dispatch_target_; + event_dispatch_target_ = target; + + bool handled = false; + for (size_t i = 0; i < gestures->size(); ++i) { + ui::GestureEvent* event = gestures->get().at(i); + event->ConvertLocationToTarget(static_cast<Window*>(this), target); + if (!DispatchEvent(target, event)) + return false; // |this| has been destroyed. + if (event->handled()) + handled = true; + if (event_dispatch_target_ != target) // |target| has been destroyed. + break; + } + event_dispatch_target_ = old_target; + return handled; +} + +void RootWindow::OnWindowRemovedFromRootWindow(Window* detached, + RootWindow* new_root) { + DCHECK(aura::client::GetCaptureWindow(this) != this); + + DispatchMouseExitToHidingWindow(detached); + OnWindowHidden(detached, new_root ? WINDOW_MOVING : WINDOW_HIDDEN); + + if (detached->IsVisible() && + detached->ContainsPointInRoot(GetLastMouseLocationInRoot())) { + PostMouseMoveEventAfterWindowChange(); + } +} + +void RootWindow::OnWindowHidden(Window* invisible, WindowHiddenReason reason) { + // TODO(beng): This should be removed once FocusController is turned on. + if (client::GetFocusClient(this)) { + client::GetFocusClient(this)->OnWindowHiddenInRootWindow( + invisible, this, reason == WINDOW_DESTROYED); + } + + // Do not clear the capture, and the |event_dispatch_target_| if the + // window is moving across root windows, because the target itself + // is actually still visible and clearing them stops further event + // processing, which can cause unexpected behaviors. See + // crbug.com/157583 + if (reason != WINDOW_MOVING) { + Window* capture_window = aura::client::GetCaptureWindow(this); + // If the ancestor of the capture window is hidden, + // release the capture. + if (invisible->Contains(capture_window) && invisible != this) + capture_window->ReleaseCapture(); + + if (invisible->Contains(event_dispatch_target_)) + event_dispatch_target_ = NULL; + } + + // If the ancestor of any event handler windows are invisible, release the + // pointer to those windows. + if (invisible->Contains(mouse_pressed_handler_)) + mouse_pressed_handler_ = NULL; + if (invisible->Contains(mouse_moved_handler_)) + mouse_moved_handler_ = NULL; + if (invisible->Contains(mouse_event_dispatch_target_)) + mouse_event_dispatch_target_ = NULL; + + CleanupGestureRecognizerState(invisible); +} + +void RootWindow::CleanupGestureRecognizerState(Window* window) { + gesture_recognizer_->CleanupStateForConsumer(window); + Windows windows = window->children(); + for (Windows::const_iterator iter = windows.begin(); + iter != windows.end(); + ++iter) { + CleanupGestureRecognizerState(*iter); + } +} + +void RootWindow::UpdateRootWindowSize(const gfx::Size& host_size) { + SetBounds(transformer_->GetRootWindowBounds(host_size)); +} + +void RootWindow::OnWindowAddedToRootWindow(Window* attached) { + if (attached->IsVisible() && + attached->ContainsPointInRoot(GetLastMouseLocationInRoot())) + PostMouseMoveEventAfterWindowChange(); +} + +bool RootWindow::CanDispatchToTarget(ui::EventTarget* target) { + return event_dispatch_target_ == target; +} + +bool RootWindow::DispatchLongPressGestureEvent(ui::GestureEvent* event) { + return DispatchGestureEvent(event); +} + +bool RootWindow::DispatchCancelTouchEvent(ui::TouchEvent* event) { + return OnHostTouchEvent(event); +} + +void RootWindow::OnLayerAnimationEnded( + ui::LayerAnimationSequence* animation) { + UpdateRootWindowSize(GetHostSize()); +} + +void RootWindow::OnLayerAnimationScheduled( + ui::LayerAnimationSequence* animation) { +} + +void RootWindow::OnLayerAnimationAborted( + ui::LayerAnimationSequence* animation) { +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, RootWindowHostDelegate implementation: + +bool RootWindow::OnHostKeyEvent(ui::KeyEvent* event) { + DispatchHeldEvents(); + if (event->key_code() == ui::VKEY_UNKNOWN) + return false; + client::EventClient* client = client::GetEventClient(GetRootWindow()); + Window* focused_window = client::GetFocusClient(this)->GetFocusedWindow(); + if (client && !client->CanProcessEventsWithinSubtree(focused_window)) { + client::GetFocusClient(this)->FocusWindow(NULL); + return false; + } + ProcessEvent(focused_window ? focused_window : this, event); + return event->handled(); +} + +bool RootWindow::OnHostMouseEvent(ui::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_DRAGGED || + (event->flags() & ui::EF_IS_SYNTHESIZED)) { + if (move_hold_count_) { + Window* null_window = static_cast<Window*>(NULL); + held_move_event_.reset( + new ui::MouseEvent(*event, null_window, null_window)); + return true; + } else { + // We may have a held event for a period between the time move_hold_count_ + // fell to 0 and the DispatchHeldEvents executes. Since we're going to + // dispatch the new event directly below, we can reset the old one. + held_move_event_.reset(); + } + } + DispatchHeldEvents(); + return DispatchMouseEventImpl(event); +} + +bool RootWindow::OnHostScrollEvent(ui::ScrollEvent* event) { + DispatchHeldEvents(); + + TransformEventForDeviceScaleFactor(event); + SetLastMouseLocation(this, event->location()); + synthesize_mouse_move_ = false; + + Window* target = mouse_pressed_handler_ ? + mouse_pressed_handler_ : client::GetCaptureWindow(this); + + if (!target) + target = GetEventHandlerForPoint(event->location()); + + if (!target) + target = this; + + event->ConvertLocationToTarget(static_cast<Window*>(this), target); + int flags = event->flags(); + if (IsNonClientLocation(target, event->location())) + flags |= ui::EF_IS_NON_CLIENT; + event->set_flags(flags); + + ProcessEvent(target, event); + return event->handled(); +} + +bool RootWindow::OnHostTouchEvent(ui::TouchEvent* event) { + if ((event->type() == ui::ET_TOUCH_MOVED)) { + if (move_hold_count_) { + Window* null_window = static_cast<Window*>(NULL); + held_move_event_.reset( + new ui::TouchEvent(*event, null_window, null_window)); + return true; + } else { + // We may have a held event for a period between the time move_hold_count_ + // fell to 0 and the DispatchHeldEvents executes. Since we're going to + // dispatch the new event directly below, we can reset the old one. + held_move_event_.reset(); + } + } + DispatchHeldEvents(); + return DispatchTouchEventImpl(event); +} + +void RootWindow::OnHostCancelMode() { + ui::CancelModeEvent event; + Window* focused_window = client::GetFocusClient(this)->GetFocusedWindow(); + ProcessEvent(focused_window ? focused_window : this, &event); +} + +void RootWindow::OnHostActivated() { + Env::GetInstance()->RootWindowActivated(this); +} + +void RootWindow::OnHostLostWindowCapture() { + Window* capture_window = client::GetCaptureWindow(this); + if (capture_window && capture_window->GetRootWindow() == this) + capture_window->ReleaseCapture(); +} + +void RootWindow::OnHostLostMouseGrab() { + ClearMouseHandlers(); +} + +void RootWindow::OnHostPaint(const gfx::Rect& damage_rect) { + compositor_->ScheduleRedrawRect(damage_rect); +} + +void RootWindow::OnHostMoved(const gfx::Point& origin) { + FOR_EACH_OBSERVER(RootWindowObserver, observers_, + OnRootWindowHostMoved(this, origin)); +} + +void RootWindow::OnHostResized(const gfx::Size& size) { + DispatchHeldEvents(); + // The compositor should have the same size as the native root window host. + // Get the latest scale from display because it might have been changed. + compositor_->SetScaleAndSize(GetDeviceScaleFactorFromDisplay(this), size); + + // The layer, and the observers should be notified of the + // transformed size of the root window. + UpdateRootWindowSize(size); + FOR_EACH_OBSERVER(RootWindowObserver, observers_, + OnRootWindowHostResized(this)); +} + +float RootWindow::GetDeviceScaleFactor() { + return compositor()->device_scale_factor(); +} + +RootWindow* RootWindow::AsRootWindow() { + return this; +} + +//////////////////////////////////////////////////////////////////////////////// +// RootWindow, private: + +bool RootWindow::DispatchMouseEventImpl(ui::MouseEvent* event) { + TransformEventForDeviceScaleFactor(event); + Window* target = mouse_pressed_handler_ ? + mouse_pressed_handler_ : client::GetCaptureWindow(this); + if (!target) + target = GetEventHandlerForPoint(event->location()); + return DispatchMouseEventToTarget(event, target); +} + +bool RootWindow::DispatchMouseEventRepost(ui::MouseEvent* event) { + if (event->type() != ui::ET_MOUSE_PRESSED) + return false; + mouse_pressed_handler_ = NULL; + Window* target = GetEventHandlerForPoint(event->location()); + return DispatchMouseEventToTarget(event, target); +} + +bool RootWindow::DispatchMouseEventToTarget(ui::MouseEvent* event, + Window* target) { + client::CursorClient* cursor_client = client::GetCursorClient(this); + if (cursor_client && + !cursor_client->IsMouseEventsEnabled() && + (event->flags() & ui::EF_IS_SYNTHESIZED)) + return false; + + static const int kMouseButtonFlagMask = + ui::EF_LEFT_MOUSE_BUTTON | + ui::EF_MIDDLE_MOUSE_BUTTON | + ui::EF_RIGHT_MOUSE_BUTTON; + base::AutoReset<Window*> reset(&mouse_event_dispatch_target_, target); + SetLastMouseLocation(this, event->location()); + synthesize_mouse_move_ = false; + switch (event->type()) { + case ui::ET_MOUSE_EXITED: + if (!target) { + DispatchMouseEnterOrExit(*event, ui::ET_MOUSE_EXITED); + mouse_moved_handler_ = NULL; + } + break; + case ui::ET_MOUSE_MOVED: + mouse_event_dispatch_target_ = target; + HandleMouseMoved(*event, target); + if (mouse_event_dispatch_target_ != target) + return false; + break; + case ui::ET_MOUSE_PRESSED: + // Don't set the mouse pressed handler for non client mouse down events. + // These are only sent by Windows and are not always followed with non + // client mouse up events which causes subsequent mouse events to be + // sent to the wrong target. + if (!(event->flags() & ui::EF_IS_NON_CLIENT) && !mouse_pressed_handler_) + mouse_pressed_handler_ = target; + Env::GetInstance()->set_mouse_button_flags( + event->flags() & kMouseButtonFlagMask); + break; + case ui::ET_MOUSE_RELEASED: + mouse_pressed_handler_ = NULL; + Env::GetInstance()->set_mouse_button_flags(event->flags() & + kMouseButtonFlagMask & ~event->changed_button_flags()); + break; + default: + break; + } + if (target) { + event->ConvertLocationToTarget(static_cast<Window*>(this), target); + if (IsNonClientLocation(target, event->location())) + event->set_flags(event->flags() | ui::EF_IS_NON_CLIENT); + ProcessEvent(target, event); + return event->handled(); + } + return false; +} + +bool RootWindow::DispatchTouchEventImpl(ui::TouchEvent* event) { + switch (event->type()) { + case ui::ET_TOUCH_PRESSED: + touch_ids_down_ |= (1 << event->touch_id()); + Env::GetInstance()->set_touch_down(touch_ids_down_ != 0); + break; + + // Handle ET_TOUCH_CANCELLED only if it has a native event. + case ui::ET_TOUCH_CANCELLED: + if (!event->HasNativeEvent()) + break; + // fallthrough + case ui::ET_TOUCH_RELEASED: + touch_ids_down_ = (touch_ids_down_ | (1 << event->touch_id())) ^ + (1 << event->touch_id()); + Env::GetInstance()->set_touch_down(touch_ids_down_ != 0); + break; + + default: + break; + } + TransformEventForDeviceScaleFactor(event); + bool handled = false; + Window* target = client::GetCaptureWindow(this); + if (!target) { + target = ConsumerToWindow( + gesture_recognizer_->GetTouchLockedTarget(event)); + if (!target) { + target = ConsumerToWindow( + gesture_recognizer_->GetTargetForLocation(event->location())); + } + } + + // The gesture recognizer processes touch events in the system coordinates. So + // keep a copy of the touch event here before possibly converting the event to + // a window's local coordinate system. + ui::TouchEvent event_for_gr(*event); + + ui::EventResult result = ui::ER_UNHANDLED; + if (!target && !bounds().Contains(event->location())) { + // If the initial touch is outside the root window, target the root. + target = this; + ProcessEvent(target ? target : NULL, event); + result = event->result(); + } else { + // We only come here when the first contact was within the root window. + if (!target) { + target = GetEventHandlerForPoint(event->location()); + if (!target) + return false; + } + + event->ConvertLocationToTarget(static_cast<Window*>(this), target); + ProcessEvent(target, event); + handled = event->handled(); + result = event->result(); + } + + // Get the list of GestureEvents from GestureRecognizer. + scoped_ptr<ui::GestureRecognizer::Gestures> gestures; + gestures.reset(gesture_recognizer_->ProcessTouchEventForGesture( + event_for_gr, result, target)); + + return ProcessGestures(gestures.get()) ? true : handled; +} + +void RootWindow::DispatchHeldEvents() { + if (held_repostable_event_) { + if (held_repostable_event_->type() == ui::ET_MOUSE_PRESSED) { + ui::MouseEvent mouse_event( + static_cast<const ui::MouseEvent&>(*held_repostable_event_.get())); + held_repostable_event_.reset(); // must be reset before dispatch + DispatchMouseEventRepost(&mouse_event); + } else { + DCHECK(held_repostable_event_->type() == ui::ET_GESTURE_TAP_DOWN); + // TODO(sschmitz): add similar code for gesture events + } + held_repostable_event_.reset(); + } + if (held_move_event_ && held_move_event_->IsMouseEvent()) { + // If a mouse move has been synthesized, the target location is suspect, + // so drop the held event. + if (!synthesize_mouse_move_) + DispatchMouseEventImpl( + static_cast<ui::MouseEvent*>(held_move_event_.get())); + held_move_event_.reset(); + } else if (held_move_event_ && held_move_event_->IsTouchEvent()) { + DispatchTouchEventImpl( + static_cast<ui::TouchEvent*>(held_move_event_.get())); + held_move_event_.reset(); + } +} + +void RootWindow::PostMouseMoveEventAfterWindowChange() { + if (synthesize_mouse_move_) + return; + synthesize_mouse_move_ = true; + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RootWindow::SynthesizeMouseMoveEvent, + event_factory_.GetWeakPtr())); +} + +void RootWindow::SynthesizeMouseMoveEvent() { + if (!synthesize_mouse_move_) + return; + synthesize_mouse_move_ = false; + gfx::Point root_mouse_location = GetLastMouseLocationInRoot(); + if (!bounds().Contains(root_mouse_location)) + return; + gfx::Point host_mouse_location = root_mouse_location; + ConvertPointToHost(&host_mouse_location); + + ui::MouseEvent event(ui::ET_MOUSE_MOVED, + host_mouse_location, + host_mouse_location, + ui::EF_IS_SYNTHESIZED); + OnHostMouseEvent(&event); +} + +gfx::Transform RootWindow::GetInverseRootTransform() const { + float scale = ui::GetDeviceScaleFactor(layer()); + gfx::Transform transform; + transform.Scale(1.0f / scale, 1.0f / scale); + return transformer_->GetInverseTransform() * transform; +} + +} // namespace aura diff --git a/chromium/ui/aura/root_window.h b/chromium/ui/aura/root_window.h new file mode 100644 index 00000000000..d9055175163 --- /dev/null +++ b/chromium/ui/aura/root_window.h @@ -0,0 +1,453 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_H_ +#define UI_AURA_ROOT_WINDOW_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/client/capture_delegate.h" +#include "ui/aura/root_window_host_delegate.h" +#include "ui/aura/window.h" +#include "ui/base/cursor/cursor.h" +#include "ui/base/events/event_constants.h" +#include "ui/base/events/event_dispatcher.h" +#include "ui/base/gestures/gesture_recognizer.h" +#include "ui/base/gestures/gesture_types.h" +#include "ui/compositor/compositor.h" +#include "ui/compositor/compositor_observer.h" +#include "ui/compositor/layer_animation_observer.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/point.h" +#include "ui/gfx/transform.h" + +namespace gfx { +class Size; +class Transform; +} + +namespace ui { +class GestureEvent; +class GestureRecognizer; +class KeyEvent; +class LayerAnimationSequence; +class MouseEvent; +class ScrollEvent; +class TouchEvent; +class ViewProp; +} + +namespace aura { +class TestScreen; +class RootWindow; +class RootWindowHost; +class RootWindowObserver; +class RootWindowTransformer; + +// RootWindow is responsible for hosting a set of windows. +class AURA_EXPORT RootWindow : public ui::CompositorDelegate, + public ui::CompositorObserver, + public Window, + public ui::EventDispatcherDelegate, + public ui::GestureEventHelper, + public ui::LayerAnimationObserver, + public aura::client::CaptureDelegate, + public aura::RootWindowHostDelegate { + public: + struct AURA_EXPORT CreateParams { + // CreateParams with initial_bounds and default host in pixel. + explicit CreateParams(const gfx::Rect& initial_bounds); + ~CreateParams() {} + + gfx::Rect initial_bounds; + + // A host to use in place of the default one that RootWindow will create. + // NULL by default. + RootWindowHost* host; + }; + + explicit RootWindow(const CreateParams& params); + virtual ~RootWindow(); + + // Returns the RootWindowHost for the specified accelerated widget, or NULL + // if there is none associated. + static RootWindow* GetForAcceleratedWidget(gfx::AcceleratedWidget widget); + + ui::Compositor* compositor() { return compositor_.get(); } + gfx::NativeCursor last_cursor() const { return last_cursor_; } + Window* mouse_pressed_handler() { return mouse_pressed_handler_; } + + // Initializes the root window. + void Init(); + + // Shows the root window host. + void ShowRootWindow(); + + // Hides the root window host. + void HideRootWindow(); + + // Stop listening events in preparation for shutdown. + void PrepareForShutdown(); + + // Repost event for re-processing. Used when exiting context menus. + void RepostEvent(const ui::LocatedEvent& event); + + RootWindowHostDelegate* AsRootWindowHostDelegate(); + + // Gets/sets the size of the host window. + void SetHostSize(const gfx::Size& size_in_pixel); + gfx::Size GetHostSize() const; + + // Sets the bounds of the host window. + void SetHostBounds(const gfx::Rect& size_in_pizel); + + // Returns where the RootWindow is on screen. + gfx::Point GetHostOrigin() const; + + // Sets the currently-displayed cursor. If the cursor was previously hidden + // via ShowCursor(false), it will remain hidden until ShowCursor(true) is + // called, at which point the cursor that was last set via SetCursor() will be + // used. + void SetCursor(gfx::NativeCursor cursor); + + // Invoked when the cursor's visibility has changed. + void OnCursorVisibilityChanged(bool visible); + + // Invoked when the mouse events get enabled or disabled. + void OnMouseEventsEnableStateChanged(bool enabled); + + // Moves the cursor to the specified location relative to the root window. + virtual void MoveCursorTo(const gfx::Point& location) OVERRIDE; + + // Moves the cursor to the |host_location| given in host coordinates. + void MoveCursorToHostLocation(const gfx::Point& host_location); + + // Clips the cursor movement to the root_window. + bool ConfineCursorToWindow(); + + // Draws the necessary set of windows. + void Draw(); + + // Draw the whole screen. + void ScheduleFullRedraw(); + + // Draw the damage_rect. + void ScheduleRedrawRect(const gfx::Rect& damage_rect); + + // Returns a target window for the given gesture event. + Window* GetGestureTarget(ui::GestureEvent* event); + + // Handles a gesture event. Returns true if handled. Unlike the other + // event-dispatching function (e.g. for touch/mouse/keyboard events), gesture + // events are dispatched from GestureRecognizer instead of RootWindowHost. + bool DispatchGestureEvent(ui::GestureEvent* event); + + // Invoked when |window| is being destroyed. + void OnWindowDestroying(Window* window); + + // Invoked when |window|'s bounds have changed. |contained_mouse| indicates if + // the bounds before change contained the |last_moust_location()|. + void OnWindowBoundsChanged(Window* window, bool contained_mouse); + + // Dispatches OnMouseExited to the |window| which is hiding if nessessary. + void DispatchMouseExitToHidingWindow(Window* window); + + // Invoked when |window|'s visibility has changed. + void OnWindowVisibilityChanged(Window* window, bool is_visible); + + // Invoked when |window|'s tranfrom has changed. |contained_mouse| + // indicates if the bounds before change contained the + // |last_moust_location()|. + void OnWindowTransformed(Window* window, bool contained_mouse); + + // Invoked when the keyboard mapping (in X11 terms: the mapping between + // keycodes and keysyms) has changed. + void OnKeyboardMappingChanged(); + + // The system windowing system has sent a request that we close our window. + void OnRootWindowHostCloseRequested(); + + // Add/remove observer. There is no need to remove the observer if + // the root window is being deleted. In particular, you SHOULD NOT remove + // in |WindowObserver::OnWindowDestroying| of the observer observing + // the root window because it is too late to remove it. + void AddRootWindowObserver(RootWindowObserver* observer); + void RemoveRootWindowObserver(RootWindowObserver* observer); + + // Posts |native_event| to the platform's event queue. + void PostNativeEvent(const base::NativeEvent& native_event); + + // Converts |point| from the root window's coordinate system to native + // screen's. + void ConvertPointToNativeScreen(gfx::Point* point) const; + + // Converts |point| from native screen coordinate system to the root window's. + void ConvertPointFromNativeScreen(gfx::Point* point) const; + + // Converts |point| from the root window's coordinate system to the + // host window's. + void ConvertPointToHost(gfx::Point* point) const; + + // Converts |point| from the host window's coordinate system to the + // root window's. + void ConvertPointFromHost(gfx::Point* point) const; + + // Gesture Recognition ------------------------------------------------------- + + // When a touch event is dispatched to a Window, it may want to process the + // touch event asynchronously. In such cases, the window should consume the + // event during the event dispatch. Once the event is properly processed, the + // window should let the RootWindow know about the result of the event + // processing, so that gesture events can be properly created and dispatched. + void ProcessedTouchEvent(ui::TouchEvent* event, + Window* window, + ui::EventResult result); + + ui::GestureRecognizer* gesture_recognizer() const { + return gesture_recognizer_.get(); + } + + // Provided only for testing: + void SetGestureRecognizerForTesting(ui::GestureRecognizer* gr); + + // Returns the accelerated widget from the RootWindowHost. + gfx::AcceleratedWidget GetAcceleratedWidget(); + + // Toggles the host's full screen state. + void ToggleFullScreen(); + + // These methods are used to defer the processing of mouse/touch events + // related to resize. A client (typically a RenderWidgetHostViewAura) can call + // HoldPointerMoves when an resize is initiated and then ReleasePointerMoves + // once the resize is completed. + // + // More than one hold can be invoked and each hold must be cancelled by a + // release before we resume normal operation. + void HoldPointerMoves(); + void ReleasePointerMoves(); + + // Sets if the window should be focused when shown. + void SetFocusWhenShown(bool focus_when_shown); + + // Gets the last location seen in a mouse event in this root window's + // coordinates. This may return a point outside the root window's bounds. + gfx::Point GetLastMouseLocationInRoot() const; + + // Overridden from Window: + virtual RootWindow* GetRootWindow() OVERRIDE; + virtual const RootWindow* GetRootWindow() const OVERRIDE; + virtual void SetTransform(const gfx::Transform& transform) OVERRIDE; + + // Overridden from ui::EventTarget: + virtual ui::EventTarget* GetParentTarget() OVERRIDE; + + // Overridden from ui::CompositorDelegate: + virtual void ScheduleDraw() OVERRIDE; + + // Overridden from ui::CompositorObserver: + virtual void OnCompositingDidCommit(ui::Compositor*) OVERRIDE; + virtual void OnCompositingStarted(ui::Compositor*, + base::TimeTicks start_time) OVERRIDE; + virtual void OnCompositingEnded(ui::Compositor*) OVERRIDE; + virtual void OnCompositingAborted(ui::Compositor*) OVERRIDE; + virtual void OnCompositingLockStateChanged(ui::Compositor*) OVERRIDE; + virtual void OnUpdateVSyncParameters(ui::Compositor* compositor, + base::TimeTicks timebase, + base::TimeDelta interval) OVERRIDE; + + // Overridden from ui::LayerDelegate: + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + + // Overridden from Window: + virtual bool CanFocus() const OVERRIDE; + virtual bool CanReceiveEvents() const OVERRIDE; + + // Overridden from aura::client::CaptureDelegate: + virtual void UpdateCapture(Window* old_capture, Window* new_capture) OVERRIDE; + virtual void SetNativeCapture() OVERRIDE; + virtual void ReleaseNativeCapture() OVERRIDE; + + // Exposes RootWindowHost::QueryMouseLocation() for test purposes. + bool QueryMouseLocationForTest(gfx::Point* point) const; + + // Clears internal mouse state (such as mouse ups should be sent to the same + // window that ate mouse downs). + void ClearMouseHandlers(); + + void SetRootWindowTransformer(scoped_ptr<RootWindowTransformer> transformer); + gfx::Transform GetRootTransform() const; + + private: + FRIEND_TEST_ALL_PREFIXES(RootWindowTest, KeepTranslatedEventInRoot); + + friend class Window; + friend class TestScreen; + + // The parameter for OnWindowHidden() to specify why window is hidden. + enum WindowHiddenReason { + WINDOW_DESTROYED, // Window is destroyed. + WINDOW_HIDDEN, // Window is hidden. + WINDOW_MOVING, // Window is temporarily marked as hidden due to move + // across root windows. + }; + + // Updates the event with the appropriate transform for the device scale + // factor. The RootWindowHostDelegate dispatches events in the physical pixel + // coordinate. But the event processing from RootWindow onwards happen in + // device-independent pixel coordinate. So it is necessary to update the event + // received from the host. + void TransformEventForDeviceScaleFactor(ui::LocatedEvent* event); + + // Moves the cursor to the specified location. This method is internally used + // by MoveCursorTo() and MoveCursorToHostLocation(). + void MoveCursorToInternal(const gfx::Point& root_location, + const gfx::Point& host_location); + + // Called whenever the mouse moves, tracks the current |mouse_moved_handler_|, + // sending exited and entered events as its value changes. + void HandleMouseMoved(const ui::MouseEvent& event, Window* target); + + // Dispatches the specified event type (intended for enter/exit) to the + // |mouse_moved_handler_|. + void DispatchMouseEnterOrExit(const ui::MouseEvent& event, + ui::EventType type); + + void ProcessEvent(Window* target, ui::Event* event); + + bool ProcessGestures(ui::GestureRecognizer::Gestures* gestures); + + // Called when a Window is attached or detached from the RootWindow. + void OnWindowAddedToRootWindow(Window* window); + void OnWindowRemovedFromRootWindow(Window* window, RootWindow* new_root); + + // Called when a window becomes invisible, either by being removed + // from root window hierarchy, via SetVisible(false) or being destroyed. + // |reason| specifies what triggered the hiding. + void OnWindowHidden(Window* invisible, WindowHiddenReason reason); + + // Cleans up the gesture recognizer for all windows in |window| (including + // |window| itself). + void CleanupGestureRecognizerState(Window* window); + + // Updates the root window's size using |host_size|, current + // transform and insets. + void UpdateRootWindowSize(const gfx::Size& host_size); + + // Overridden from ui::EventDispatcherDelegate. + virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE; + + // Overridden from ui::GestureEventHelper. + virtual bool DispatchLongPressGestureEvent(ui::GestureEvent* event) OVERRIDE; + virtual bool DispatchCancelTouchEvent(ui::TouchEvent* event) OVERRIDE; + + // Overridden from ui::LayerAnimationObserver: + virtual void OnLayerAnimationEnded( + ui::LayerAnimationSequence* animation) OVERRIDE; + virtual void OnLayerAnimationScheduled( + ui::LayerAnimationSequence* animation) OVERRIDE; + virtual void OnLayerAnimationAborted( + ui::LayerAnimationSequence* animation) OVERRIDE; + + // Overridden from aura::RootWindowHostDelegate: + virtual bool OnHostKeyEvent(ui::KeyEvent* event) OVERRIDE; + virtual bool OnHostMouseEvent(ui::MouseEvent* event) OVERRIDE; + virtual bool OnHostScrollEvent(ui::ScrollEvent* event) OVERRIDE; + virtual bool OnHostTouchEvent(ui::TouchEvent* event) OVERRIDE; + virtual void OnHostCancelMode() OVERRIDE; + virtual void OnHostActivated() OVERRIDE; + virtual void OnHostLostWindowCapture() OVERRIDE; + virtual void OnHostLostMouseGrab() OVERRIDE; + virtual void OnHostPaint(const gfx::Rect& damage_rect) OVERRIDE; + virtual void OnHostMoved(const gfx::Point& origin) OVERRIDE; + virtual void OnHostResized(const gfx::Size& size) OVERRIDE; + virtual float GetDeviceScaleFactor() OVERRIDE; + virtual RootWindow* AsRootWindow() OVERRIDE; + + // We hold and aggregate mouse drags and touch moves as a way of throttling + // resizes when HoldMouseMoves() is called. The following methods are used to + // dispatch held and newly incoming mouse and touch events, typically when an + // event other than one of these needs dispatching or a matching + // ReleaseMouseMoves()/ReleaseTouchMoves() is called. NOTE: because these + // methods dispatch events from RootWindowHost the coordinates are in terms of + // the root. + bool DispatchMouseEventImpl(ui::MouseEvent* event); + bool DispatchMouseEventRepost(ui::MouseEvent* event); + bool DispatchMouseEventToTarget(ui::MouseEvent* event, Window* target); + bool DispatchTouchEventImpl(ui::TouchEvent* event); + void DispatchHeldEvents(); + + // Parses the switch describing the initial size for the host window and + // returns bounds for the window. + gfx::Rect GetInitialHostWindowBounds() const; + + // Posts a task to send synthesized mouse move event if there + // is no a pending task. + void PostMouseMoveEventAfterWindowChange(); + + // Creates and dispatches synthesized mouse move event using the + // current mouse location. + void SynthesizeMouseMoveEvent(); + + gfx::Transform GetInverseRootTransform() const; + + scoped_ptr<ui::Compositor> compositor_; + + scoped_ptr<RootWindowHost> host_; + + // Used to schedule painting. + base::WeakPtrFactory<RootWindow> schedule_paint_factory_; + + // Use to post mouse move event. + base::WeakPtrFactory<RootWindow> event_factory_; + + // Touch ids that are currently down. + uint32 touch_ids_down_; + + // Last cursor set. Used for testing. + gfx::NativeCursor last_cursor_; + + ObserverList<RootWindowObserver> observers_; + + Window* mouse_pressed_handler_; + Window* mouse_moved_handler_; + Window* mouse_event_dispatch_target_; + Window* event_dispatch_target_; + + // The gesture_recognizer_ for this. + scoped_ptr<ui::GestureRecognizer> gesture_recognizer_; + + bool synthesize_mouse_move_; + bool waiting_on_compositing_end_; + bool draw_on_compositing_end_; + + bool defer_draw_scheduling_; + + // How many move holds are outstanding. We try to defer dispatching + // touch/mouse moves while the count is > 0. + int move_hold_count_; + // Used to schedule DispatchHeldEvents() when |move_hold_count_| goes to 0. + base::WeakPtrFactory<RootWindow> held_event_factory_; + scoped_ptr<ui::LocatedEvent> held_move_event_; + + // Allowing for reposting of events. Used when exiting context menus. + scoped_ptr<ui::LocatedEvent> held_repostable_event_; + base::WeakPtrFactory<RootWindow> repostable_event_factory_; + + scoped_ptr<ui::ViewProp> prop_; + + scoped_ptr<RootWindowTransformer> transformer_; + + DISALLOW_COPY_AND_ASSIGN(RootWindow); +}; + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_H_ diff --git a/chromium/ui/aura/root_window_host.h b/chromium/ui/aura/root_window_host.h new file mode 100644 index 00000000000..f1b52041707 --- /dev/null +++ b/chromium/ui/aura/root_window_host.h @@ -0,0 +1,114 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_HOST_H_ +#define UI_AURA_ROOT_WINDOW_HOST_H_ + +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "ui/aura/aura_export.h" +#include "ui/base/cursor/cursor.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Insets; +class Point; +class Rect; +class Size; +} + +namespace aura { + +class RootWindow; +class RootWindowHostDelegate; + +// RootWindowHost bridges between a native window and the embedded RootWindow. +// It provides the accelerated widget and maps events from the native os to +// aura. +class AURA_EXPORT RootWindowHost { + public: + virtual ~RootWindowHost() {} + + // Creates a new RootWindowHost. The caller owns the returned value. + static RootWindowHost* Create(const gfx::Rect& bounds); + + // Returns the actual size of the screen. + // (gfx::Screen only reports on the virtual desktop exposed by Aura.) + static gfx::Size GetNativeScreenSize(); + + // Sets the delegate, which is normally done by the root window. + virtual void SetDelegate(RootWindowHostDelegate* delegate) = 0; + + virtual RootWindow* GetRootWindow() = 0; + + // Returns the accelerated widget. + virtual gfx::AcceleratedWidget GetAcceleratedWidget() = 0; + + // Shows the RootWindowHost. + virtual void Show() = 0; + + // Hides the RootWindowHost. + virtual void Hide() = 0; + + // Toggles the host's full screen state. + virtual void ToggleFullScreen() = 0; + + // Gets/Sets the size of the RootWindowHost. + virtual gfx::Rect GetBounds() const = 0; + virtual void SetBounds(const gfx::Rect& bounds) = 0; + + // Sets/Gets the insets that specifies the effective root window area + // in the host window. + virtual gfx::Insets GetInsets() const = 0; + virtual void SetInsets(const gfx::Insets& insets) = 0; + + // Returns the location of the RootWindow on native screen. + virtual gfx::Point GetLocationOnNativeScreen() const = 0; + + // Sets the OS capture to the root window. + virtual void SetCapture() = 0; + + // Releases OS capture of the root window. + virtual void ReleaseCapture() = 0; + + // Sets the currently displayed cursor. + virtual void SetCursor(gfx::NativeCursor cursor) = 0; + + // Queries the mouse's current position relative to the host window and sets + // it in |location_return|. Returns true if the cursor is within the host + // window. The position set to |location_return| is constrained within the + // host window. If the cursor is disabled, returns false and (0, 0) is set to + // |location_return|. + // This method is expensive, instead use gfx::Screen::GetCursorScreenPoint(). + virtual bool QueryMouseLocation(gfx::Point* location_return) = 0; + + // Clips the cursor to the bounds of the root window until UnConfineCursor(). + virtual bool ConfineCursorToRootWindow() = 0; + virtual void UnConfineCursor() = 0; + + // Called when the cursor visibility has changed. + virtual void OnCursorVisibilityChanged(bool show) = 0; + + // Moves the cursor to the specified location relative to the root window. + virtual void MoveCursorTo(const gfx::Point& location) = 0; + + // Sets if the window should be focused when shown. + virtual void SetFocusWhenShown(bool focus_when_shown) = 0; + + // Posts |native_event| to the platform's event queue. +#if !defined(OS_MACOSX) + virtual void PostNativeEvent(const base::NativeEvent& native_event) = 0; +#endif + + // Called when the device scale factor of the root window has chagned. + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) = 0; + + // Stop listening events in preparation for shutdown. + virtual void PrepareForShutdown() = 0; +}; + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_HOST_H_ diff --git a/chromium/ui/aura/root_window_host_delegate.h b/chromium/ui/aura/root_window_host_delegate.h new file mode 100644 index 00000000000..a7876512354 --- /dev/null +++ b/chromium/ui/aura/root_window_host_delegate.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_HOST_DELEGATE_H_ +#define UI_AURA_ROOT_WINDOW_HOST_DELEGATE_H_ + +#include "ui/aura/aura_export.h" + +namespace gfx { +class Point; +class Rect; +class Size; +} + +namespace ui { +class Event; +class KeyEvent; +class MouseEvent; +class ScrollEvent; +class TouchEvent; +} + +namespace aura { + +class RootWindow; + +// A private interface used by RootWindowHost implementations to communicate +// with their owning RootWindow. +class AURA_EXPORT RootWindowHostDelegate { + public: + virtual bool OnHostKeyEvent(ui::KeyEvent* event) = 0; + virtual bool OnHostMouseEvent(ui::MouseEvent* event) = 0; + virtual bool OnHostScrollEvent(ui::ScrollEvent* event) = 0; + virtual bool OnHostTouchEvent(ui::TouchEvent* event) = 0; + virtual void OnHostCancelMode() = 0; + + // Called when the windowing system activates the window. + virtual void OnHostActivated() = 0; + + // Called when system focus is changed to another window. + virtual void OnHostLostWindowCapture() = 0; + + // Called when the windowing system has mouse grab because it's performing a + // window move on our behalf, but we should still paint as if we're active. + virtual void OnHostLostMouseGrab() = 0; + + virtual void OnHostPaint(const gfx::Rect& damage_rect) = 0; + + virtual void OnHostMoved(const gfx::Point& origin) = 0; + virtual void OnHostResized(const gfx::Size& size) = 0; + + virtual float GetDeviceScaleFactor() = 0; + + virtual RootWindow* AsRootWindow() = 0; + + protected: + virtual ~RootWindowHostDelegate() {} +}; + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_HOST_DELEGATE_H_ diff --git a/chromium/ui/aura/root_window_host_mac.h b/chromium/ui/aura/root_window_host_mac.h new file mode 100644 index 00000000000..e7c26c35482 --- /dev/null +++ b/chromium/ui/aura/root_window_host_mac.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_HOST_MAC_H_ +#define UI_AURA_ROOT_WINDOW_HOST_MAC_H_ + +#include "base/basictypes.h" +#include "base/event_types.h" + +namespace aura { + +// An interface establishing event dispatch from the Mac native window and the +// Aura host. +class RootWindowHostMacDelegate { + public: + RootWindowHostMacDelegate(); + virtual ~RootWindowHostMacDelegate(); + + // Route events from platform code to the RootWindowHost. + virtual void SendEvent(const base::NativeEvent& native_event) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(RootWindowHostMacDelegate); +}; + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_HOST_MAC_H_ diff --git a/chromium/ui/aura/root_window_host_mac.mm b/chromium/ui/aura/root_window_host_mac.mm new file mode 100644 index 00000000000..de00e9b244f --- /dev/null +++ b/chromium/ui/aura/root_window_host_mac.mm @@ -0,0 +1,215 @@ +// 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 "ui/aura/root_window_host_mac.h" + +#import <Cocoa/Cocoa.h> + +#include "base/compiler_specific.h" +#include "base/mac/bundle_locations.h" +#include "base/mac/scoped_nsobject.h" +#include "ui/aura/event.h" +#include "ui/aura/root_window.h" +#include "ui/aura/root_window_host.h" +#include "ui/aura/root_window_mac.h" +#include "ui/aura/root_window_view_mac.h" +#include "ui/base/events/event_utils.h" +#include "ui/gfx/point.h" + +namespace aura { + +// The Mac-specific implementation of the RootWindowHost interface. This class +// acts at an intermediary between the Aura shell and an NSWindow. The +// association between the Aura compositor and the native window's view is +// established with this class as is the association between the native window's +// event dispatch and the Aura event processing. +class RootWindowHostMac : public RootWindowHost, + public RootWindowHostMacDelegate { + public: + explicit RootWindowHostMac(const gfx::Rect& bounds); + virtual ~RootWindowHostMac(); + + // RootWindowHost: + virtual void SetRootWindow(RootWindow* root_window) OVERRIDE; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void ToggleFullScreen() OVERRIDE; + virtual gfx::Size GetSize() const OVERRIDE; + virtual void SetSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Point GetLocationOnNativeScreen() const OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor) OVERRIDE; + virtual void ShowCursor(bool show) OVERRIDE; + virtual bool QueryMouseLocation(gfx::Point* location_return) OVERRIDE; + virtual void MoveCursorTo(const gfx::Point& location) OVERRIDE; + virtual bool ConfineCursorToRootWindow() OVERRIDE; + virtual void UnConfineCursor() OVERRIDE; + // RootWindowHostMacDelegate: + virtual void SendEvent(const base::NativeEvent& native_event) OVERRIDE; + + // Set the initial location of the root window. The origin of |bounds| is + // top-left. This gets converted to bottom-left to match Mac coordinates on + // the main screen. + void SetLocation(const gfx::Rect& bounds); + + private: + // Weak reference. + RootWindow* root_window_; + + // The bounds of the Aura desktop. Relative to Aura's coordinate system. + // This is currently used only for size information, not location. + gfx::Rect bounds_; + + // An NSWindowController for the root window. Controls the actual Cocoa + // window on Mac. + base::scoped_nsobject<NSWindowController> controller_; + + DISALLOW_COPY_AND_ASSIGN(RootWindowHostMac); +}; + +RootWindowHostMacDelegate::RootWindowHostMacDelegate() { +} + +RootWindowHostMacDelegate::~RootWindowHostMacDelegate() { +} + +RootWindowHostMac::RootWindowHostMac(const gfx::Rect& bounds) + : root_window_(NULL), bounds_(bounds) { + NSString* nibpath = [base::mac::FrameworkBundle() + pathForResource:@"RootWindow" + ofType:@"nib"]; + NSWindowController* controller = [NSWindowController alloc]; + controller_.reset([controller initWithWindowNibPath:nibpath + owner:controller]); + SetSize(bounds.size()); + SetLocation(bounds); +} + +RootWindowHostMac::~RootWindowHostMac() { + RootWindowView* view = [[controller_ window] contentView]; + [view setCompositor:NULL]; + [controller_ close]; +} + +// static +RootWindowHost* RootWindowHost::Create(const gfx::Rect& bounds) { + return new RootWindowHostMac(bounds); +} + +// static +gfx::Size RootWindowHost::GetNativeScreenSize() { + NSRect screen = [[NSScreen mainScreen] visibleFrame]; + return gfx::Size(NSSizeToCGSize(screen.size)); +} + +void RootWindowHostMac::SetRootWindow(RootWindow* root_window) { + root_window_ = root_window; + + RootWindowView* view = [[controller_ window] contentView]; + DCHECK([view respondsToSelector:@selector(setCompositor:)]); + [view setCompositor:root_window->compositor()]; + + RootWindowMac* window = static_cast<RootWindowMac*>([controller_ window]); + DCHECK([window respondsToSelector:@selector(setHostDelegate:)]); + [window setHostDelegate:this]; +} + +gfx::AcceleratedWidget RootWindowHostMac::GetAcceleratedWidget() { + return [[controller_ window] contentView]; +} + +void RootWindowHostMac::Show() { + [controller_ showWindow:controller_]; +} + +void RootWindowHostMac::ToggleFullScreen() { +} + +gfx::Size RootWindowHostMac::GetSize() const { + NSSize size = [[[controller_ window] contentView] bounds].size; + return gfx::Size(NSSizeToCGSize(size)); +} + +void RootWindowHostMac::SetSize(const gfx::Size& size) { + NSSize nssize = NSSizeFromCGSize(size.ToCGSize()); + [[controller_ window] setContentSize:nssize]; + [[controller_ window] setContentMaxSize:nssize]; + [[controller_ window] setContentMinSize:nssize]; +} + +gfx::Point RootWindowHostMac::GetLocationOnNativeScreen() const { + return gfx::Point(); +} + +void RootWindowHostMac::SetCapture() { +} + +void RootWindowHostMac::ReleaseCapture() { +} + +void RootWindowHostMac::SetCursor(gfx::NativeCursor cursor) { +} + +void RootWindowHostMac::ShowCursor(bool show) { +} + +bool RootWindowHostMac::QueryMouseLocation(gfx::Point* location_return) { + *location_return = gfx::Point(); + return true; +} + +void RootWindowHostMac::MoveCursorTo(const gfx::Point& location) { +} + +bool RootWindowHostMac::ConfineCursorToRootWindow() { + return false; +} + +void RootWindowHostMac::UnConfineCursor() { +} + +void RootWindowHostMac::SendEvent(const base::NativeEvent& native_event) { + ui::EventType type = ui::EventTypeFromNative(native_event); + switch (type) { + case ui::ET_MOUSE_PRESSED: + case ui::ET_MOUSE_DRAGGED: + case ui::ET_MOUSE_RELEASED: + case ui::ET_MOUSE_MOVED: + case ui::ET_MOUSE_ENTERED: + case ui::ET_MOUSE_EXITED: { + MouseEvent mouse_event(native_event); + root_window_->DispatchMouseEvent(&mouse_event); + break; + } + case ui::ET_KEY_PRESSED: + case ui::ET_KEY_RELEASED: { + KeyEvent key_event(native_event, false); + root_window_->DispatchKeyEvent(&key_event); + break; + } + case ui::ET_MOUSEWHEEL: + case ui::ET_TOUCH_RELEASED: + case ui::ET_TOUCH_PRESSED: + case ui::ET_TOUCH_MOVED: + case ui::ET_TOUCH_STATIONARY: + case ui::ET_TOUCH_CANCELLED: + case ui::ET_DROP_TARGET_EVENT: + case ui::ET_FOCUS_CHANGE: + case ui::ET_SCROLL: + case ui::ET_UNKNOWN: + default: + break; + } +} + +void RootWindowHostMac::SetLocation(const gfx::Rect& bounds) { + NSRect screen = [[NSScreen mainScreen] visibleFrame]; + NSPoint origin = NSMakePoint(screen.origin.x + bounds.x(), + screen.origin.y + screen.size.height - + bounds.y() - bounds.height()); + [[controller_ window] setFrameOrigin:origin]; +} + +} // namespace aura diff --git a/chromium/ui/aura/root_window_host_ozone.cc b/chromium/ui/aura/root_window_host_ozone.cc new file mode 100644 index 00000000000..d3fe7c3865d --- /dev/null +++ b/chromium/ui/aura/root_window_host_ozone.cc @@ -0,0 +1,132 @@ +// Copyright 2013 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 "ui/aura/root_window_host_ozone.h" + +#include "ui/aura/root_window.h" +#include "ui/base/ozone/surface_factory_ozone.h" + +namespace aura { + +RootWindowHostOzone::RootWindowHostOzone(const gfx::Rect& bounds) + : delegate_(NULL), + widget_(0), + bounds_(bounds), + factory_(new ui::EventFactoryOzone()) { + factory_->CreateStartupEventConverters(); + ui::SurfaceFactoryOzone* surface_factory = + ui::SurfaceFactoryOzone::GetInstance(); + widget_ = surface_factory->GetAcceleratedWidget(); + + surface_factory->AttemptToResizeAcceleratedWidget(widget_, bounds_); + + base::MessagePumpOzone::Current()->AddDispatcherForRootWindow(this); +} + +RootWindowHostOzone::~RootWindowHostOzone() { + base::MessagePumpOzone::Current()->RemoveDispatcherForRootWindow(0); +} + +bool RootWindowHostOzone::Dispatch(const base::NativeEvent& ne) { + ui::Event* event = static_cast<ui::Event*>(ne); + if (event->IsTouchEvent()) { + ui::TouchEvent* touchev = static_cast<ui::TouchEvent*>(ne); + delegate_->OnHostTouchEvent(touchev); + } else if (event->IsKeyEvent()) { + ui::KeyEvent* keyev = static_cast<ui::KeyEvent*>(ne); + delegate_->OnHostKeyEvent(keyev); + } + return true; +} + +void RootWindowHostOzone::SetDelegate(RootWindowHostDelegate* delegate) { + delegate_ = delegate; +} + +RootWindow* RootWindowHostOzone::GetRootWindow() { + return delegate_->AsRootWindow(); +} + +gfx::AcceleratedWidget RootWindowHostOzone::GetAcceleratedWidget() { + return widget_; +} + +void RootWindowHostOzone::Show() { NOTIMPLEMENTED(); } + +void RootWindowHostOzone::Hide() { NOTIMPLEMENTED(); } + +void RootWindowHostOzone::ToggleFullScreen() { NOTIMPLEMENTED(); } + +gfx::Rect RootWindowHostOzone::GetBounds() const { return bounds_; } + +void RootWindowHostOzone::SetBounds(const gfx::Rect& bounds) { + NOTIMPLEMENTED(); +} + +gfx::Insets RootWindowHostOzone::GetInsets() const { return gfx::Insets(); } + +void RootWindowHostOzone::SetInsets(const gfx::Insets& insets) { + NOTIMPLEMENTED(); +} + +gfx::Point RootWindowHostOzone::GetLocationOnNativeScreen() const { + return bounds_.origin(); +} + +void RootWindowHostOzone::SetCapture() { NOTIMPLEMENTED(); } + +void RootWindowHostOzone::ReleaseCapture() { NOTIMPLEMENTED(); } + +void RootWindowHostOzone::SetCursor(gfx::NativeCursor cursor) { + NOTIMPLEMENTED(); +} + +bool RootWindowHostOzone::QueryMouseLocation(gfx::Point* location_return) { + NOTIMPLEMENTED(); + return false; +} + +bool RootWindowHostOzone::ConfineCursorToRootWindow() { + NOTIMPLEMENTED(); + return false; +} + +void RootWindowHostOzone::UnConfineCursor() { NOTIMPLEMENTED(); } + +void RootWindowHostOzone::OnCursorVisibilityChanged(bool show) { + NOTIMPLEMENTED(); +} + +void RootWindowHostOzone::MoveCursorTo(const gfx::Point& location) { + NOTIMPLEMENTED(); +} + +void RootWindowHostOzone::SetFocusWhenShown(bool focus_when_shown) { + NOTIMPLEMENTED(); +} + +void RootWindowHostOzone::PostNativeEvent( + const base::NativeEvent& native_event) { + NOTIMPLEMENTED(); +} + +void RootWindowHostOzone::OnDeviceScaleFactorChanged( + float device_scale_factor) { + NOTIMPLEMENTED(); +} + +void RootWindowHostOzone::PrepareForShutdown() { NOTIMPLEMENTED(); } + +// static +RootWindowHost* RootWindowHost::Create(const gfx::Rect& bounds) { + return new RootWindowHostOzone(bounds); +} + +// static +gfx::Size RootWindowHost::GetNativeScreenSize() { + NOTIMPLEMENTED(); + return gfx::Size(); +} + +} // namespace aura diff --git a/chromium/ui/aura/root_window_host_ozone.h b/chromium/ui/aura/root_window_host_ozone.h new file mode 100644 index 00000000000..f510d418678 --- /dev/null +++ b/chromium/ui/aura/root_window_host_ozone.h @@ -0,0 +1,67 @@ +// Copyright 2013 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. + +#ifndef UI_AURA_ROOT_WINDOW_HOST_OZONE_H_ +#define UI_AURA_ROOT_WINDOW_HOST_OZONE_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "ui/aura/root_window_host.h" +#include "ui/base/ozone/event_factory_ozone.h" +#include "ui/gfx/rect.h" + +namespace aura { + +class RootWindowHostOzone : public RootWindowHost, + public base::MessageLoop::Dispatcher { + public: + explicit RootWindowHostOzone(const gfx::Rect& bounds); + virtual ~RootWindowHostOzone(); + + private: + // Overridden from Dispatcher overrides: + virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE; + + // RootWindowHost Overrides. + virtual void SetDelegate(RootWindowHostDelegate* delegate) OVERRIDE; + virtual RootWindow* GetRootWindow() OVERRIDE; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void ToggleFullScreen() OVERRIDE; + virtual gfx::Rect GetBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual gfx::Insets GetInsets() const OVERRIDE; + virtual void SetInsets(const gfx::Insets& bounds) OVERRIDE; + virtual gfx::Point GetLocationOnNativeScreen() const OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor_type) OVERRIDE; + virtual bool QueryMouseLocation(gfx::Point* location_return) OVERRIDE; + virtual bool ConfineCursorToRootWindow() OVERRIDE; + virtual void UnConfineCursor() OVERRIDE; + virtual void OnCursorVisibilityChanged(bool show) OVERRIDE; + virtual void MoveCursorTo(const gfx::Point& location) OVERRIDE; + virtual void SetFocusWhenShown(bool focus_when_shown) OVERRIDE; + virtual void PostNativeEvent(const base::NativeEvent& event) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual void PrepareForShutdown() OVERRIDE; + + RootWindowHostDelegate* delegate_; + gfx::AcceleratedWidget widget_; + gfx::Rect bounds_; + + // EventFactoryOzone creates converters that obtain input events from the + // underlying input system and dispatch them as |ui::Event| instances into + // Aura. + scoped_ptr<ui::EventFactoryOzone> factory_; + + DISALLOW_COPY_AND_ASSIGN(RootWindowHostOzone); +}; + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_HOST_OZONE_H_ diff --git a/chromium/ui/aura/root_window_host_win.cc b/chromium/ui/aura/root_window_host_win.cc new file mode 100644 index 00000000000..723de57fe0a --- /dev/null +++ b/chromium/ui/aura/root_window_host_win.cc @@ -0,0 +1,318 @@ +// 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 "ui/aura/root_window_host_win.h" + +#include <windows.h> + +#include <algorithm> + +#include "base/message_loop/message_loop.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/client/cursor_client.h" +#include "ui/aura/root_window.h" +#include "ui/base/cursor/cursor_loader_win.h" +#include "ui/base/events/event.h" +#include "ui/base/view_prop.h" +#include "ui/gfx/display.h" +#include "ui/gfx/insets.h" +#include "ui/gfx/screen.h" + +using std::max; +using std::min; + +namespace aura { +namespace { + +bool use_popup_as_root_window_for_test = false; + +} // namespace + +// static +RootWindowHost* RootWindowHost::Create(const gfx::Rect& bounds) { + return new RootWindowHostWin(bounds); +} + +// static +gfx::Size RootWindowHost::GetNativeScreenSize() { + return gfx::Size(GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN)); +} + +RootWindowHostWin::RootWindowHostWin(const gfx::Rect& bounds) + : delegate_(NULL), + fullscreen_(false), + has_capture_(false), + saved_window_style_(0), + saved_window_ex_style_(0) { + if (use_popup_as_root_window_for_test) + set_window_style(WS_POPUP); + Init(NULL, bounds); + SetWindowText(hwnd(), L"aura::RootWindow!"); +} + +RootWindowHostWin::~RootWindowHostWin() { + DestroyWindow(hwnd()); +} + +void RootWindowHostWin::SetDelegate(RootWindowHostDelegate* delegate) { + delegate_ = delegate; +} + +RootWindow* RootWindowHostWin::GetRootWindow() { + return delegate_->AsRootWindow(); +} + +gfx::AcceleratedWidget RootWindowHostWin::GetAcceleratedWidget() { + return hwnd(); +} + +void RootWindowHostWin::Show() { + ShowWindow(hwnd(), SW_SHOWNORMAL); +} + +void RootWindowHostWin::Hide() { + NOTIMPLEMENTED(); +} + +void RootWindowHostWin::ToggleFullScreen() { + gfx::Rect target_rect; + if (!fullscreen_) { + fullscreen_ = true; + saved_window_style_ = GetWindowLong(hwnd(), GWL_STYLE); + saved_window_ex_style_ = GetWindowLong(hwnd(), GWL_EXSTYLE); + GetWindowRect(hwnd(), &saved_window_rect_); + SetWindowLong(hwnd(), GWL_STYLE, + saved_window_style_ & ~(WS_CAPTION | WS_THICKFRAME)); + SetWindowLong(hwnd(), GWL_EXSTYLE, + saved_window_ex_style_ & ~(WS_EX_DLGMODALFRAME | + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); + + MONITORINFO mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONEAREST), &mi); + target_rect = gfx::Rect(mi.rcMonitor); + } else { + fullscreen_ = false; + SetWindowLong(hwnd(), GWL_STYLE, saved_window_style_); + SetWindowLong(hwnd(), GWL_EXSTYLE, saved_window_ex_style_); + target_rect = gfx::Rect(saved_window_rect_); + } + SetWindowPos(hwnd(), + NULL, + target_rect.x(), + target_rect.y(), + target_rect.width(), + target_rect.height(), + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); +} + +gfx::Rect RootWindowHostWin::GetBounds() const { + RECT r; + GetClientRect(hwnd(), &r); + return gfx::Rect(r); +} + +void RootWindowHostWin::SetBounds(const gfx::Rect& bounds) { + if (fullscreen_) { + saved_window_rect_.right = saved_window_rect_.left + bounds.width(); + saved_window_rect_.bottom = saved_window_rect_.top + bounds.height(); + return; + } + RECT window_rect; + window_rect.left = bounds.x(); + window_rect.top = bounds.y(); + window_rect.right = bounds.right() ; + window_rect.bottom = bounds.bottom(); + AdjustWindowRectEx(&window_rect, + GetWindowLong(hwnd(), GWL_STYLE), + FALSE, + GetWindowLong(hwnd(), GWL_EXSTYLE)); + SetWindowPos( + hwnd(), + NULL, + window_rect.left, + window_rect.top, + window_rect.right - window_rect.left, + window_rect.bottom - window_rect.top, + SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOREPOSITION); + + // Explicity call OnHostResized when the scale has changed because + // the window size may not have changed. + float current_scale = delegate_->GetDeviceScaleFactor(); + float new_scale = gfx::Screen::GetScreenFor(delegate_->AsRootWindow())-> + GetDisplayNearestWindow(delegate_->AsRootWindow()).device_scale_factor(); + if (current_scale != new_scale) + delegate_->OnHostResized(bounds.size()); +} + +gfx::Insets RootWindowHostWin::GetInsets() const { + return gfx::Insets(); +} + +void RootWindowHostWin::SetInsets(const gfx::Insets& insets) { +} + +gfx::Point RootWindowHostWin::GetLocationOnNativeScreen() const { + RECT r; + GetClientRect(hwnd(), &r); + return gfx::Point(r.left, r.top); +} + + +void RootWindowHostWin::SetCursor(gfx::NativeCursor native_cursor) { + // Custom web cursors are handled directly. + if (native_cursor == ui::kCursorCustom) + return; + + ui::CursorLoaderWin cursor_loader; + cursor_loader.SetPlatformCursor(&native_cursor); + ::SetCursor(native_cursor.platform()); +} + +void RootWindowHostWin::SetCapture() { + if (!has_capture_) { + has_capture_ = true; + ::SetCapture(hwnd()); + } +} + +void RootWindowHostWin::ReleaseCapture() { + if (has_capture_) { + has_capture_ = false; + ::ReleaseCapture(); + } +} + +bool RootWindowHostWin::QueryMouseLocation(gfx::Point* location_return) { + client::CursorClient* cursor_client = + client::GetCursorClient(GetRootWindow()); + if (cursor_client && !cursor_client->IsMouseEventsEnabled()) { + *location_return = gfx::Point(0, 0); + return false; + } + + POINT pt; + GetCursorPos(&pt); + ScreenToClient(hwnd(), &pt); + const gfx::Size size = GetBounds().size(); + *location_return = + gfx::Point(max(0, min(size.width(), static_cast<int>(pt.x))), + max(0, min(size.height(), static_cast<int>(pt.y)))); + return (pt.x >= 0 && static_cast<int>(pt.x) < size.width() && + pt.y >= 0 && static_cast<int>(pt.y) < size.height()); +} + +bool RootWindowHostWin::ConfineCursorToRootWindow() { + RECT window_rect; + GetWindowRect(hwnd(), &window_rect); + return ClipCursor(&window_rect) != 0; +} + +void RootWindowHostWin::UnConfineCursor() { + ClipCursor(NULL); +} + +void RootWindowHostWin::OnCursorVisibilityChanged(bool show) { + NOTIMPLEMENTED(); +} + +void RootWindowHostWin::MoveCursorTo(const gfx::Point& location) { + // Deliberately not implemented. +} + +void RootWindowHostWin::SetFocusWhenShown(bool focus_when_shown) { + NOTIMPLEMENTED(); +} + +void RootWindowHostWin::PostNativeEvent(const base::NativeEvent& native_event) { + ::PostMessage( + hwnd(), native_event.message, native_event.wParam, native_event.lParam); +} + +void RootWindowHostWin::OnDeviceScaleFactorChanged( + float device_scale_factor) { + NOTIMPLEMENTED(); +} + +void RootWindowHostWin::PrepareForShutdown() { + NOTIMPLEMENTED(); +} + +void RootWindowHostWin::OnClose() { + // TODO: this obviously shouldn't be here. + base::MessageLoopForUI::current()->Quit(); +} + +LRESULT RootWindowHostWin::OnKeyEvent(UINT message, + WPARAM w_param, + LPARAM l_param) { + MSG msg = { hwnd(), message, w_param, l_param }; + ui::KeyEvent keyev(msg, message == WM_CHAR); + SetMsgHandled(delegate_->OnHostKeyEvent(&keyev)); + return 0; +} + +LRESULT RootWindowHostWin::OnMouseRange(UINT message, + WPARAM w_param, + LPARAM l_param) { + MSG msg = { hwnd(), message, w_param, l_param, 0, + { GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param) } }; + ui::MouseEvent event(msg); + bool handled = false; + if (!(event.flags() & ui::EF_IS_NON_CLIENT)) + handled = delegate_->OnHostMouseEvent(&event); + SetMsgHandled(handled); + return 0; +} + +LRESULT RootWindowHostWin::OnCaptureChanged(UINT message, + WPARAM w_param, + LPARAM l_param) { + if (has_capture_) { + has_capture_ = false; + delegate_->OnHostLostWindowCapture(); + } + return 0; +} + +LRESULT RootWindowHostWin::OnNCActivate(UINT message, + WPARAM w_param, + LPARAM l_param) { + if (!!w_param) + delegate_->OnHostActivated(); + return DefWindowProc(hwnd(), message, w_param, l_param); +} + +void RootWindowHostWin::OnMove(const CPoint& point) { + if (delegate_) + delegate_->OnHostMoved(gfx::Point(point.x, point.y)); +} + +void RootWindowHostWin::OnPaint(HDC dc) { + gfx::Rect damage_rect; + RECT update_rect = {0}; + if (GetUpdateRect(hwnd(), &update_rect, FALSE)) + damage_rect = gfx::Rect(update_rect); + delegate_->OnHostPaint(damage_rect); + ValidateRect(hwnd(), NULL); +} + +void RootWindowHostWin::OnSize(UINT param, const CSize& size) { + // Minimizing resizes the window to 0x0 which causes our layout to go all + // screwy, so we just ignore it. + if (delegate_ && param != SIZE_MINIMIZED) + delegate_->OnHostResized(gfx::Size(size.cx, size.cy)); +} + +namespace test { + +// static +void SetUsePopupAsRootWindowForTest(bool use) { + use_popup_as_root_window_for_test = use; +} + +} // namespace test + +} // namespace aura diff --git a/chromium/ui/aura/root_window_host_win.h b/chromium/ui/aura/root_window_host_win.h new file mode 100644 index 00000000000..e2a60ddf738 --- /dev/null +++ b/chromium/ui/aura/root_window_host_win.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_HOST_WIN_H_ +#define UI_AURA_ROOT_WINDOW_HOST_WIN_H_ + +#include "base/compiler_specific.h" +#include "ui/aura/root_window_host.h" +#include "ui/base/ui_export.h" +#include "ui/base/win/window_impl.h" + +namespace aura { + +class RootWindowHostWin : public RootWindowHost, public ui::WindowImpl { + public: + RootWindowHostWin(const gfx::Rect& bounds); + virtual ~RootWindowHostWin(); + // RootWindowHost: + virtual void SetDelegate(RootWindowHostDelegate* delegate) OVERRIDE; + virtual RootWindow* GetRootWindow() OVERRIDE; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void ToggleFullScreen() OVERRIDE; + virtual gfx::Rect GetBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual gfx::Insets GetInsets() const OVERRIDE; + virtual void SetInsets(const gfx::Insets& insets) OVERRIDE; + virtual gfx::Point GetLocationOnNativeScreen() const OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor) OVERRIDE; + virtual bool QueryMouseLocation(gfx::Point* location_return) OVERRIDE; + virtual bool ConfineCursorToRootWindow() OVERRIDE; + virtual void UnConfineCursor() OVERRIDE; + virtual void OnCursorVisibilityChanged(bool show) OVERRIDE; + virtual void MoveCursorTo(const gfx::Point& location) OVERRIDE; + virtual void SetFocusWhenShown(bool focus_when_shown) OVERRIDE; + virtual void PostNativeEvent(const base::NativeEvent& native_event) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual void PrepareForShutdown() OVERRIDE; + + private: + BEGIN_MSG_MAP_EX(RootWindowHostWin) + // Range handlers must go first! + MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) + MESSAGE_RANGE_HANDLER_EX(WM_NCMOUSEMOVE, WM_NCXBUTTONDBLCLK, OnMouseRange) + + // Mouse capture events. + MESSAGE_HANDLER_EX(WM_CAPTURECHANGED, OnCaptureChanged) + + // Key events. + MESSAGE_HANDLER_EX(WM_KEYDOWN, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_KEYUP, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_SYSKEYDOWN, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_SYSKEYUP, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_CHAR, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_SYSCHAR, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_IME_CHAR, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_NCACTIVATE, OnNCActivate) + + MSG_WM_CLOSE(OnClose) + MSG_WM_MOVE(OnMove) + MSG_WM_PAINT(OnPaint) + MSG_WM_SIZE(OnSize) + END_MSG_MAP() + + void OnClose(); + LRESULT OnKeyEvent(UINT message, WPARAM w_param, LPARAM l_param); + LRESULT OnMouseRange(UINT message, WPARAM w_param, LPARAM l_param); + LRESULT OnCaptureChanged(UINT message, WPARAM w_param, LPARAM l_param); + LRESULT OnNCActivate(UINT message, WPARAM w_param, LPARAM l_param); + void OnMove(const CPoint& point); + void OnPaint(HDC dc); + void OnSize(UINT param, const CSize& size); + + RootWindowHostDelegate* delegate_; + + bool fullscreen_; + bool has_capture_; + RECT saved_window_rect_; + DWORD saved_window_style_; + DWORD saved_window_ex_style_; + + DISALLOW_COPY_AND_ASSIGN(RootWindowHostWin); +}; + +namespace test { + +// Set true to let RootWindowHostWin use a popup window +// with no frame/title so that the window size and test's +// expectations matches. +AURA_EXPORT void SetUsePopupAsRootWindowForTest(bool use); + +} // namespace + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_HOST_WIN_H_ diff --git a/chromium/ui/aura/root_window_host_x11.cc b/chromium/ui/aura/root_window_host_x11.cc new file mode 100644 index 00000000000..783e960095d --- /dev/null +++ b/chromium/ui/aura/root_window_host_x11.cc @@ -0,0 +1,1096 @@ +// 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 "ui/aura/root_window_host_x11.h" + +#include <strings.h> +#include <X11/cursorfont.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/XInput2.h> +#include <X11/extensions/Xrandr.h> +#include <X11/Xatom.h> +#include <X11/Xcursor/Xcursor.h> +#include <X11/Xlib.h> + +#include <algorithm> +#include <limits> +#include <string> + +#include "base/command_line.h" +#include "base/debug/trace_event.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_pump_aurax11.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/client/cursor_client.h" +#include "ui/aura/client/screen_position_client.h" +#include "ui/aura/client/user_action_client.h" +#include "ui/aura/env.h" +#include "ui/aura/root_window.h" +#include "ui/base/cursor/cursor.h" +#include "ui/base/events/event.h" +#include "ui/base/events/event_utils.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/base/touch/touch_factory_x11.h" +#include "ui/base/ui_base_switches.h" +#include "ui/base/view_prop.h" +#include "ui/base/x/device_list_cache_x.h" +#include "ui/base/x/x11_util.h" +#include "ui/compositor/dip_util.h" +#include "ui/compositor/layer.h" +#include "ui/gfx/screen.h" + +#if defined(OS_CHROMEOS) +#include "base/chromeos/chromeos_version.h" +#endif + +using std::max; +using std::min; + +namespace aura { + +namespace { + +// Standard Linux mouse buttons for going back and forward. +const int kBackMouseButton = 8; +const int kForwardMouseButton = 9; + +const char* kAtomsToCache[] = { + "WM_DELETE_WINDOW", + "_NET_WM_PING", + "_NET_WM_PID", + "WM_S0", +#if defined(OS_CHROMEOS) + "Tap Paused", // Defined in the gestures library. +#endif + NULL +}; + +::Window FindEventTarget(const base::NativeEvent& xev) { + ::Window target = xev->xany.window; + if (xev->type == GenericEvent) + target = static_cast<XIDeviceEvent*>(xev->xcookie.data)->event; + return target; +} + +#if defined(USE_XI2_MT) +bool IsSideBezelsEnabled() { + static bool side_bezels_enabled = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTouchSideBezels) == "1"; + return side_bezels_enabled; +} +#endif + +void SelectEventsForRootWindow() { + Display* display = ui::GetXDisplay(); + ::Window root_window = ui::GetX11RootWindow(); + + // Receive resize events for the root-window so |x_root_bounds_| can be + // updated. + XWindowAttributes attr; + XGetWindowAttributes(display, root_window, &attr); + if (!(attr.your_event_mask & StructureNotifyMask)) { + XSelectInput(display, root_window, + StructureNotifyMask | attr.your_event_mask); + } + + if (!base::MessagePumpForUI::HasXInput2()) + return; + + unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {}; + memset(mask, 0, sizeof(mask)); + + XISetMask(mask, XI_HierarchyChanged); + XISetMask(mask, XI_KeyPress); + XISetMask(mask, XI_KeyRelease); + + XIEventMask evmask; + evmask.deviceid = XIAllDevices; + evmask.mask_len = sizeof(mask); + evmask.mask = mask; + XISelectEvents(display, root_window, &evmask, 1); + + // Selecting for touch events seems to fail on some cases (e.g. when logging + // in incognito). So select for non-touch events first, and then select for + // touch-events (but keep the other events in the mask, i.e. do not memset + // |mask| back to 0). + // TODO(sad): Figure out why this happens. http://crbug.com/153976 +#if defined(USE_XI2_MT) + XISetMask(mask, XI_TouchBegin); + XISetMask(mask, XI_TouchUpdate); + XISetMask(mask, XI_TouchEnd); + XISelectEvents(display, root_window, &evmask, 1); +#endif +} + +// We emulate Windows' WM_KEYDOWN and WM_CHAR messages. WM_CHAR events are only +// generated for certain keys; see +// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646268.aspx. Per +// discussion on http://crbug.com/108480, char events should furthermore not be +// generated for Tab, Escape, and Backspace. +bool ShouldSendCharEventForKeyboardCode(ui::KeyboardCode keycode) { + if ((keycode >= ui::VKEY_0 && keycode <= ui::VKEY_9) || + (keycode >= ui::VKEY_A && keycode <= ui::VKEY_Z) || + (keycode >= ui::VKEY_NUMPAD0 && keycode <= ui::VKEY_NUMPAD9)) { + return true; + } + + switch (keycode) { + case ui::VKEY_RETURN: + case ui::VKEY_SPACE: + // In addition to the keys listed at MSDN, we include other + // graphic-character and numpad keys. + case ui::VKEY_MULTIPLY: + case ui::VKEY_ADD: + case ui::VKEY_SUBTRACT: + case ui::VKEY_DECIMAL: + case ui::VKEY_DIVIDE: + case ui::VKEY_OEM_1: + case ui::VKEY_OEM_2: + case ui::VKEY_OEM_3: + case ui::VKEY_OEM_4: + case ui::VKEY_OEM_5: + case ui::VKEY_OEM_6: + case ui::VKEY_OEM_7: + case ui::VKEY_OEM_102: + case ui::VKEY_OEM_PLUS: + case ui::VKEY_OEM_COMMA: + case ui::VKEY_OEM_MINUS: + case ui::VKEY_OEM_PERIOD: + return true; + default: + return false; + } +} + +bool default_override_redirect = false; + +} // namespace + +namespace internal { + +// Accomplishes 2 tasks concerning touch event calibration: +// 1. Being a message-pump observer, +// routes all the touch events to the X root window, +// where they can be calibrated later. +// 2. Has the Calibrate method that does the actual bezel calibration, +// when invoked from X root window's event dispatcher. +class TouchEventCalibrate : public base::MessagePumpObserver { + public: + TouchEventCalibrate() + : left_(0), + right_(0), + top_(0), + bottom_(0) { + base::MessageLoopForUI::current()->AddObserver(this); +#if defined(USE_XI2_MT) + std::vector<std::string> parts; + if (Tokenize(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTouchCalibration), ",", &parts) >= 4) { + if (!base::StringToInt(parts[0], &left_)) + DLOG(ERROR) << "Incorrect left border calibration value passed."; + if (!base::StringToInt(parts[1], &right_)) + DLOG(ERROR) << "Incorrect right border calibration value passed."; + if (!base::StringToInt(parts[2], &top_)) + DLOG(ERROR) << "Incorrect top border calibration value passed."; + if (!base::StringToInt(parts[3], &bottom_)) + DLOG(ERROR) << "Incorrect bottom border calibration value passed."; + } +#endif // defined(USE_XI2_MT) + } + + virtual ~TouchEventCalibrate() { + base::MessageLoopForUI::current()->RemoveObserver(this); + } + +#if defined(USE_XI2_MT) + bool IsEventOnSideBezels( + const base::NativeEvent& xev, + const gfx::Rect& bounds) { + if (!left_ && !right_) + return false; + + gfx::Point location = ui::EventLocationFromNative(xev); + int x = location.x(); + return x < left_ || x > bounds.width() - right_; + } +#endif // defined(USE_XI2_MT) + + // Modify the location of the |event|, + // expanding it from |bounds| to (|bounds| + bezels). + // Required when touchscreen is bigger than screen (i.e. has bezels), + // because we receive events in touchscreen coordinates, + // which need to be expanded when converting to screen coordinates, + // so that location on bezels will be outside of screen area. + void Calibrate(ui::TouchEvent* event, const gfx::Rect& bounds) { +#if defined(USE_XI2_MT) + int x = event->x(); + int y = event->y(); + + if (!left_ && !right_ && !top_ && !bottom_) + return; + + const int resolution_x = bounds.width(); + const int resolution_y = bounds.height(); + // The "grace area" (10% in this case) is to make it easier for the user to + // navigate to the corner. + const double kGraceAreaFraction = 0.1; + if (left_ || right_) { + // Offset the x position to the real + x -= left_; + // Check if we are in the grace area of the left side. + // Note: We might not want to do this when the gesture is locked? + if (x < 0 && x > -left_ * kGraceAreaFraction) + x = 0; + // Check if we are in the grace area of the right side. + // Note: We might not want to do this when the gesture is locked? + if (x > resolution_x - left_ && + x < resolution_x - left_ + right_ * kGraceAreaFraction) + x = resolution_x - left_; + // Scale the screen area back to the full resolution of the screen. + x = (x * resolution_x) / (resolution_x - (right_ + left_)); + } + if (top_ || bottom_) { + // When there is a top bezel we add our border, + y -= top_; + + // Check if we are in the grace area of the top side. + // Note: We might not want to do this when the gesture is locked? + if (y < 0 && y > -top_ * kGraceAreaFraction) + y = 0; + + // Check if we are in the grace area of the bottom side. + // Note: We might not want to do this when the gesture is locked? + if (y > resolution_y - top_ && + y < resolution_y - top_ + bottom_ * kGraceAreaFraction) + y = resolution_y - top_; + // Scale the screen area back to the full resolution of the screen. + y = (y * resolution_y) / (resolution_y - (bottom_ + top_)); + } + + // Set the modified coordinate back to the event. + if (event->root_location() == event->location()) { + // Usually those will be equal, + // if not, I am not sure what the correct value should be. + event->set_root_location(gfx::Point(x, y)); + } + event->set_location(gfx::Point(x, y)); +#endif // defined(USE_XI2_MT) + } + + private: + // Overridden from base::MessagePumpObserver: + virtual base::EventStatus WillProcessEvent( + const base::NativeEvent& event) OVERRIDE { +#if defined(USE_XI2_MT) + if (event->type == GenericEvent && + (event->xgeneric.evtype == XI_TouchBegin || + event->xgeneric.evtype == XI_TouchUpdate || + event->xgeneric.evtype == XI_TouchEnd)) { + XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event->xcookie.data); + xievent->event = xievent->root; + xievent->event_x = xievent->root_x; + xievent->event_y = xievent->root_y; + } +#endif // defined(USE_XI2_MT) + return base::EVENT_CONTINUE; + } + + virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE { + } + + // The difference in screen's native resolution pixels between + // the border of the touchscreen and the border of the screen, + // aka bezel sizes. + int left_; + int right_; + int top_; + int bottom_; + + DISALLOW_COPY_AND_ASSIGN(TouchEventCalibrate); +}; + +} // namespace internal + +//////////////////////////////////////////////////////////////////////////////// +// RootWindowHostX11::MouseMoveFilter filters out the move events that +// jump back and forth between two points. This happens when sub pixel mouse +// move is enabled and mouse move events could be jumping between two neighbor +// pixels, e.g. move(0,0), move(1,0), move(0,0), move(1,0) and on and on. +// The filtering is done by keeping track of the last two event locations and +// provides a Filter method to find out whether a mouse event is in a different +// location and should be processed. + +class RootWindowHostX11::MouseMoveFilter { + public: + MouseMoveFilter() : insert_index_(0) { + for (size_t i = 0; i < kMaxEvents; ++i) { + const int int_max = std::numeric_limits<int>::max(); + recent_locations_[i] = gfx::Point(int_max, int_max); + } + } + ~MouseMoveFilter() {} + + // Returns true if |event| is known and should be ignored. + bool Filter(const base::NativeEvent& event) { + const gfx::Point& location = ui::EventLocationFromNative(event); + for (size_t i = 0; i < kMaxEvents; ++i) { + if (location == recent_locations_[i]) + return true; + } + + recent_locations_[insert_index_] = location; + insert_index_ = (insert_index_ + 1) % kMaxEvents; + return false; + } + + private: + static const size_t kMaxEvents = 2; + + gfx::Point recent_locations_[kMaxEvents]; + size_t insert_index_; + + DISALLOW_COPY_AND_ASSIGN(MouseMoveFilter); +}; + +//////////////////////////////////////////////////////////////////////////////// +// RootWindowHostX11 + +RootWindowHostX11::RootWindowHostX11(const gfx::Rect& bounds) + : delegate_(NULL), + xdisplay_(base::MessagePumpAuraX11::GetDefaultXDisplay()), + xwindow_(0), + x_root_window_(DefaultRootWindow(xdisplay_)), + current_cursor_(ui::kCursorNull), + window_mapped_(false), + bounds_(bounds), + is_internal_display_(false), + focus_when_shown_(false), + touch_calibrate_(new internal::TouchEventCalibrate), + mouse_move_filter_(new MouseMoveFilter), + atom_cache_(xdisplay_, kAtomsToCache) { + XSetWindowAttributes swa; + memset(&swa, 0, sizeof(swa)); + swa.background_pixmap = None; + swa.override_redirect = default_override_redirect; + xwindow_ = XCreateWindow( + xdisplay_, x_root_window_, + bounds.x(), bounds.y(), bounds.width(), bounds.height(), + 0, // border width + CopyFromParent, // depth + InputOutput, + CopyFromParent, // visual + CWBackPixmap | CWOverrideRedirect, + &swa); + base::MessagePumpAuraX11::Current()->AddDispatcherForWindow(this, xwindow_); + base::MessagePumpAuraX11::Current()->AddDispatcherForRootWindow(this); + + long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask | + KeyPressMask | KeyReleaseMask | + EnterWindowMask | LeaveWindowMask | + ExposureMask | VisibilityChangeMask | + StructureNotifyMask | PropertyChangeMask | + PointerMotionMask; + XSelectInput(xdisplay_, xwindow_, event_mask); + XFlush(xdisplay_); + + if (base::MessagePumpForUI::HasXInput2()) + ui::TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_); + + SelectEventsForRootWindow(); + + // Get the initial size of the X root window. + XWindowAttributes attrs; + XGetWindowAttributes(xdisplay_, x_root_window_, &attrs); + x_root_bounds_.SetRect(attrs.x, attrs.y, attrs.width, attrs.height); + + // TODO(erg): We currently only request window deletion events. We also + // should listen for activation events and anything else that GTK+ listens + // for, and do something useful. + ::Atom protocols[2]; + protocols[0] = atom_cache_.GetAtom("WM_DELETE_WINDOW"); + protocols[1] = atom_cache_.GetAtom("_NET_WM_PING"); + XSetWMProtocols(xdisplay_, xwindow_, protocols, 2); + + // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with + // the desktop environment. + XSetWMProperties(xdisplay_, xwindow_, NULL, NULL, NULL, 0, NULL, NULL, NULL); + + // Likewise, the X server needs to know this window's pid so it knows which + // program to kill if the window hangs. + pid_t pid = getpid(); + XChangeProperty(xdisplay_, + xwindow_, + atom_cache_.GetAtom("_NET_WM_PID"), + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<unsigned char*>(&pid), 1); + + XRRSelectInput(xdisplay_, x_root_window_, + RRScreenChangeNotifyMask | RROutputChangeNotifyMask); + Env::GetInstance()->AddObserver(this); +} + +RootWindowHostX11::~RootWindowHostX11() { + Env::GetInstance()->RemoveObserver(this); + base::MessagePumpAuraX11::Current()->RemoveDispatcherForRootWindow(this); + base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow(xwindow_); + + UnConfineCursor(); + + XDestroyWindow(xdisplay_, xwindow_); +} + +bool RootWindowHostX11::Dispatch(const base::NativeEvent& event) { + XEvent* xev = event; + + if (FindEventTarget(event) == x_root_window_) + return DispatchEventForRootWindow(event); + + switch (xev->type) { + case EnterNotify: { + ui::MouseEvent mouse_event(xev); + // EnterNotify creates ET_MOUSE_MOVE. Mark as synthesized as this is not + // real mouse move event. + mouse_event.set_flags(mouse_event.flags() | ui::EF_IS_SYNTHESIZED); + TranslateAndDispatchMouseEvent(&mouse_event); + break; + } + case LeaveNotify: { + ui::MouseEvent mouse_event(xev); + TranslateAndDispatchMouseEvent(&mouse_event); + break; + } + case Expose: { + gfx::Rect damage_rect(xev->xexpose.x, xev->xexpose.y, + xev->xexpose.width, xev->xexpose.height); + delegate_->AsRootWindow()->ScheduleRedrawRect(damage_rect); + break; + } + case KeyPress: { + ui::KeyEvent keydown_event(xev, false); + delegate_->OnHostKeyEvent(&keydown_event); + break; + } + case KeyRelease: { + ui::KeyEvent keyup_event(xev, false); + delegate_->OnHostKeyEvent(&keyup_event); + break; + } + case ButtonPress: { + if (static_cast<int>(xev->xbutton.button) == kBackMouseButton || + static_cast<int>(xev->xbutton.button) == kForwardMouseButton) { + client::UserActionClient* gesture_client = + client::GetUserActionClient(delegate_->AsRootWindow()); + if (gesture_client) { + gesture_client->OnUserAction( + static_cast<int>(xev->xbutton.button) == kBackMouseButton ? + client::UserActionClient::BACK : + client::UserActionClient::FORWARD); + } + break; + } + } // fallthrough + case ButtonRelease: { + ui::MouseEvent mouseev(xev); + TranslateAndDispatchMouseEvent(&mouseev); + break; + } + case FocusOut: + if (xev->xfocus.mode != NotifyGrab) + delegate_->OnHostLostWindowCapture(); + break; + case ConfigureNotify: { + DCHECK_EQ(xwindow_, xev->xconfigure.event); + DCHECK_EQ(xwindow_, xev->xconfigure.window); + // It's possible that the X window may be resized by some other means + // than from within aura (e.g. the X window manager can change the + // size). Make sure the root window size is maintained properly. + gfx::Rect bounds(xev->xconfigure.x, xev->xconfigure.y, + xev->xconfigure.width, xev->xconfigure.height); + bool size_changed = bounds_.size() != bounds.size(); + bool origin_changed = bounds_.origin() != bounds.origin(); + bounds_ = bounds; + UpdateIsInternalDisplay(); + // Always update barrier and mouse location because |bounds_| might + // have already been updated in |SetBounds|. + if (pointer_barriers_) { + UnConfineCursor(); + ConfineCursorToRootWindow(); + } + if (size_changed) + delegate_->OnHostResized(bounds.size()); + if (origin_changed) + delegate_->OnHostMoved(bounds_.origin()); + break; + } + case GenericEvent: + DispatchXI2Event(event); + break; + case MapNotify: { + // If there's no window manager running, we need to assign the X input + // focus to our host window. + if (!IsWindowManagerPresent() && focus_when_shown_) + XSetInputFocus(xdisplay_, xwindow_, RevertToNone, CurrentTime); + break; + } + case ClientMessage: { + Atom message_type = static_cast<Atom>(xev->xclient.data.l[0]); + if (message_type == atom_cache_.GetAtom("WM_DELETE_WINDOW")) { + // We have received a close message from the window manager. + delegate_->AsRootWindow()->OnRootWindowHostCloseRequested(); + } else if (message_type == atom_cache_.GetAtom("_NET_WM_PING")) { + XEvent reply_event = *xev; + reply_event.xclient.window = x_root_window_; + + XSendEvent(xdisplay_, + reply_event.xclient.window, + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &reply_event); + } + break; + } + case MappingNotify: { + switch (xev->xmapping.request) { + case MappingModifier: + case MappingKeyboard: + XRefreshKeyboardMapping(&xev->xmapping); + delegate_->AsRootWindow()->OnKeyboardMappingChanged(); + break; + case MappingPointer: + ui::UpdateButtonMap(); + break; + default: + NOTIMPLEMENTED() << " Unknown request: " << xev->xmapping.request; + break; + } + break; + } + case MotionNotify: { + // Discard all but the most recent motion event that targets the same + // window with unchanged state. + XEvent last_event; + while (XPending(xev->xany.display)) { + XEvent next_event; + XPeekEvent(xev->xany.display, &next_event); + if (next_event.type == MotionNotify && + next_event.xmotion.window == xev->xmotion.window && + next_event.xmotion.subwindow == xev->xmotion.subwindow && + next_event.xmotion.state == xev->xmotion.state) { + XNextEvent(xev->xany.display, &last_event); + xev = &last_event; + } else { + break; + } + } + + ui::MouseEvent mouseev(xev); + TranslateAndDispatchMouseEvent(&mouseev); + break; + } + } + return true; +} + +void RootWindowHostX11::SetDelegate(RootWindowHostDelegate* delegate) { + delegate_ = delegate; +} + +RootWindow* RootWindowHostX11::GetRootWindow() { + return delegate_->AsRootWindow(); +} + +gfx::AcceleratedWidget RootWindowHostX11::GetAcceleratedWidget() { + return xwindow_; +} + +void RootWindowHostX11::Show() { + if (!window_mapped_) { + // Before we map the window, set size hints. Otherwise, some window managers + // will ignore toplevel XMoveWindow commands. + XSizeHints size_hints; + size_hints.flags = PPosition | PWinGravity; + size_hints.x = bounds_.x(); + size_hints.y = bounds_.y(); + // Set StaticGravity so that the window position is not affected by the + // frame width when running with window manager. + size_hints.win_gravity = StaticGravity; + XSetWMNormalHints(xdisplay_, xwindow_, &size_hints); + + XMapWindow(xdisplay_, xwindow_); + + // We now block until our window is mapped. Some X11 APIs will crash and + // burn if passed |xwindow_| before the window is mapped, and XMapWindow is + // asynchronous. + base::MessagePumpAuraX11::Current()->BlockUntilWindowMapped(xwindow_); + window_mapped_ = true; + } +} + +void RootWindowHostX11::Hide() { + if (window_mapped_) { + XWithdrawWindow(xdisplay_, xwindow_, 0); + window_mapped_ = false; + } +} + +void RootWindowHostX11::ToggleFullScreen() { + NOTIMPLEMENTED(); +} + +gfx::Rect RootWindowHostX11::GetBounds() const { + return bounds_; +} + +void RootWindowHostX11::SetBounds(const gfx::Rect& bounds) { + // Even if the host window's size doesn't change, aura's root window + // size, which is in DIP, changes when the scale changes. + float current_scale = delegate_->GetDeviceScaleFactor(); + float new_scale = gfx::Screen::GetScreenFor(delegate_->AsRootWindow())-> + GetDisplayNearestWindow(delegate_->AsRootWindow()).device_scale_factor(); + bool origin_changed = bounds_.origin() != bounds.origin(); + bool size_changed = bounds_.size() != bounds.size(); + XWindowChanges changes = {0}; + unsigned value_mask = 0; + + if (size_changed) { + changes.width = bounds.width(); + changes.height = bounds.height(); + value_mask = CWHeight | CWWidth; + } + + if (origin_changed) { + changes.x = bounds.x(); + changes.y = bounds.y(); + value_mask |= CWX | CWY; + } + if (value_mask) + XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes); + + // Assume that the resize will go through as requested, which should be the + // case if we're running without a window manager. If there's a window + // manager, it can modify or ignore the request, but (per ICCCM) we'll get a + // (possibly synthetic) ConfigureNotify about the actual size and correct + // |bounds_| later. + bounds_ = bounds; + UpdateIsInternalDisplay(); + if (origin_changed) + delegate_->OnHostMoved(bounds.origin()); + if (size_changed || current_scale != new_scale) { + delegate_->OnHostResized(bounds.size()); + } else { + delegate_->AsRootWindow()->SchedulePaintInRect( + delegate_->AsRootWindow()->bounds()); + } +} + +gfx::Insets RootWindowHostX11::GetInsets() const { + return insets_; +} + +void RootWindowHostX11::SetInsets(const gfx::Insets& insets) { + insets_ = insets; + if (pointer_barriers_) { + UnConfineCursor(); + ConfineCursorToRootWindow(); + } +} + +gfx::Point RootWindowHostX11::GetLocationOnNativeScreen() const { + return bounds_.origin(); +} + +void RootWindowHostX11::SetCapture() { + // TODO(oshima): Grab x input. +} + +void RootWindowHostX11::ReleaseCapture() { + // TODO(oshima): Release x input. +} + +void RootWindowHostX11::SetCursor(gfx::NativeCursor cursor) { + if (cursor == current_cursor_) + return; + current_cursor_ = cursor; + SetCursorInternal(cursor); +} + +bool RootWindowHostX11::QueryMouseLocation(gfx::Point* location_return) { + client::CursorClient* cursor_client = + client::GetCursorClient(GetRootWindow()); + if (cursor_client && !cursor_client->IsMouseEventsEnabled()) { + *location_return = gfx::Point(0, 0); + return false; + } + + ::Window root_return, child_return; + int root_x_return, root_y_return, win_x_return, win_y_return; + unsigned int mask_return; + XQueryPointer(xdisplay_, + xwindow_, + &root_return, + &child_return, + &root_x_return, &root_y_return, + &win_x_return, &win_y_return, + &mask_return); + *location_return = gfx::Point(max(0, min(bounds_.width(), win_x_return)), + max(0, min(bounds_.height(), win_y_return))); + return (win_x_return >= 0 && win_x_return < bounds_.width() && + win_y_return >= 0 && win_y_return < bounds_.height()); +} + +bool RootWindowHostX11::ConfineCursorToRootWindow() { +#if XFIXES_MAJOR >= 5 + DCHECK(!pointer_barriers_.get()); + if (pointer_barriers_) + return false; + pointer_barriers_.reset(new XID[4]); + gfx::Rect bounds(bounds_); + bounds.Inset(insets_); + // Horizontal, top barriers. + pointer_barriers_[0] = XFixesCreatePointerBarrier( + xdisplay_, x_root_window_, + bounds.x(), bounds.y(), bounds.right(), bounds.y(), + BarrierPositiveY, + 0, XIAllDevices); + // Horizontal, bottom barriers. + pointer_barriers_[1] = XFixesCreatePointerBarrier( + xdisplay_, x_root_window_, + bounds.x(), bounds.bottom(), bounds.right(), bounds.bottom(), + BarrierNegativeY, + 0, XIAllDevices); + // Vertical, left barriers. + pointer_barriers_[2] = XFixesCreatePointerBarrier( + xdisplay_, x_root_window_, + bounds.x(), bounds.y(), bounds.x(), bounds.bottom(), + BarrierPositiveX, + 0, XIAllDevices); + // Vertical, right barriers. + pointer_barriers_[3] = XFixesCreatePointerBarrier( + xdisplay_, x_root_window_, + bounds.right(), bounds.y(), bounds.right(), bounds.bottom(), + BarrierNegativeX, + 0, XIAllDevices); +#endif + return true; +} + +void RootWindowHostX11::UnConfineCursor() { +#if XFIXES_MAJOR >= 5 + if (pointer_barriers_) { + XFixesDestroyPointerBarrier(xdisplay_, pointer_barriers_[0]); + XFixesDestroyPointerBarrier(xdisplay_, pointer_barriers_[1]); + XFixesDestroyPointerBarrier(xdisplay_, pointer_barriers_[2]); + XFixesDestroyPointerBarrier(xdisplay_, pointer_barriers_[3]); + pointer_barriers_.reset(); + } +#endif +} + +void RootWindowHostX11::OnCursorVisibilityChanged(bool show) { + SetCrOSTapPaused(!show); +} + +void RootWindowHostX11::MoveCursorTo(const gfx::Point& location) { + XWarpPointer(xdisplay_, None, x_root_window_, 0, 0, 0, 0, + bounds_.x() + location.x(), + bounds_.y() + location.y()); +} + +void RootWindowHostX11::SetFocusWhenShown(bool focus_when_shown) { + static const char* k_NET_WM_USER_TIME = "_NET_WM_USER_TIME"; + focus_when_shown_ = focus_when_shown; + if (IsWindowManagerPresent() && !focus_when_shown_) { + ui::SetIntProperty(xwindow_, + k_NET_WM_USER_TIME, + k_NET_WM_USER_TIME, + 0); + } +} + +void RootWindowHostX11::PostNativeEvent( + const base::NativeEvent& native_event) { + DCHECK(xwindow_); + DCHECK(xdisplay_); + XEvent xevent = *native_event; + xevent.xany.display = xdisplay_; + xevent.xany.window = xwindow_; + + switch (xevent.type) { + case EnterNotify: + case LeaveNotify: + case MotionNotify: + case KeyPress: + case KeyRelease: + case ButtonPress: + case ButtonRelease: { + // The fields used below are in the same place for all of events + // above. Using xmotion from XEvent's unions to avoid repeating + // the code. + xevent.xmotion.root = x_root_window_; + xevent.xmotion.time = CurrentTime; + + gfx::Point point(xevent.xmotion.x, xevent.xmotion.y); + delegate_->AsRootWindow()->ConvertPointToNativeScreen(&point); + xevent.xmotion.x_root = point.x(); + xevent.xmotion.y_root = point.y(); + } + default: + break; + } + XSendEvent(xdisplay_, xwindow_, False, 0, &xevent); +} + +void RootWindowHostX11::OnDeviceScaleFactorChanged( + float device_scale_factor) { +} + +void RootWindowHostX11::PrepareForShutdown() { + base::MessagePumpAuraX11::Current()->RemoveDispatcherForWindow(xwindow_); +} + +void RootWindowHostX11::OnWindowInitialized(Window* window) { +} + +void RootWindowHostX11::OnRootWindowInitialized(RootWindow* root_window) { + // UpdateIsInternalDisplay relies on: + // 1. delegate_ pointing to RootWindow - available after SetDelegate. + // 2. RootWindow's kDisplayIdKey property set - available by the time + // RootWindow::Init is called. + // (set in DisplayManager::CreateRootWindowForDisplay) + // Ready when NotifyRootWindowInitialized is called from RootWindow::Init. + if (!delegate_ || root_window != GetRootWindow()) + return; + UpdateIsInternalDisplay(); + + // We have to enable Tap-to-click by default because the cursor is set to + // visible in Shell::InitRootWindowController. + SetCrOSTapPaused(false); +} + +bool RootWindowHostX11::DispatchEventForRootWindow( + const base::NativeEvent& event) { + switch (event->type) { + case ConfigureNotify: + DCHECK_EQ(x_root_window_, event->xconfigure.event); + x_root_bounds_.SetRect(event->xconfigure.x, event->xconfigure.y, + event->xconfigure.width, event->xconfigure.height); + break; + + case GenericEvent: + DispatchXI2Event(event); + break; + } + + return true; +} + +void RootWindowHostX11::DispatchXI2Event(const base::NativeEvent& event) { + ui::TouchFactory* factory = ui::TouchFactory::GetInstance(); + XEvent* xev = event; + if (!factory->ShouldProcessXI2Event(xev)) + return; + + TRACE_EVENT1("input", "RootWindowHostX11::DispatchXI2Event", + "event_latency_us", + (ui::EventTimeForNow() - ui::EventTimeFromNative(event)). + InMicroseconds()); + + ui::EventType type = ui::EventTypeFromNative(xev); + XEvent last_event; + int num_coalesced = 0; + + switch (type) { + case ui::ET_TOUCH_MOVED: + case ui::ET_TOUCH_PRESSED: + case ui::ET_TOUCH_CANCELLED: + case ui::ET_TOUCH_RELEASED: { +#if defined(USE_XI2_MT) + // Ignore events from the bezel when the side bezel flag is not explicitly + // enabled. + if (!IsSideBezelsEnabled() && + touch_calibrate_->IsEventOnSideBezels(xev, bounds_)) { + break; + } +#endif // defined(USE_XI2_MT) + ui::TouchEvent touchev(xev); +#if defined(OS_CHROMEOS) + if (base::chromeos::IsRunningOnChromeOS()) { + if (!bounds_.Contains(touchev.location())) + break; + // X maps the touch-surface to the size of the X root-window. + // In multi-monitor setup, Coordinate Transformation Matrix + // repositions the touch-surface onto part of X root-window + // containing aura root-window corresponding to the touchscreen. + // However, if aura root-window has non-zero origin, + // we need to relocate the event into aura root-window coordinates. + touchev.Relocate(bounds_.origin()); +#if defined(USE_XI2_MT) + if (is_internal_display_) + touch_calibrate_->Calibrate(&touchev, bounds_); +#endif // defined(USE_XI2_MT) + } +#endif // defined(OS_CHROMEOS) + delegate_->OnHostTouchEvent(&touchev); + break; + } + case ui::ET_MOUSE_MOVED: + case ui::ET_MOUSE_DRAGGED: + case ui::ET_MOUSE_PRESSED: + case ui::ET_MOUSE_RELEASED: + case ui::ET_MOUSE_ENTERED: + case ui::ET_MOUSE_EXITED: { + if (type == ui::ET_MOUSE_MOVED || type == ui::ET_MOUSE_DRAGGED) { + // If this is a motion event, we want to coalesce all pending motion + // events that are at the top of the queue. + num_coalesced = ui::CoalescePendingMotionEvents(xev, &last_event); + if (num_coalesced > 0) + xev = &last_event; + + if (mouse_move_filter_ && mouse_move_filter_->Filter(xev)) + break; + } else if (type == ui::ET_MOUSE_PRESSED || + type == ui::ET_MOUSE_RELEASED) { + XIDeviceEvent* xievent = + static_cast<XIDeviceEvent*>(xev->xcookie.data); + int button = xievent->detail; + if (button == kBackMouseButton || button == kForwardMouseButton) { + if (type == ui::ET_MOUSE_RELEASED) + break; + client::UserActionClient* gesture_client = + client::GetUserActionClient(delegate_->AsRootWindow()); + if (gesture_client) { + bool reverse_direction = + ui::IsTouchpadEvent(xev) && ui::IsNaturalScrollEnabled(); + gesture_client->OnUserAction( + (button == kBackMouseButton && !reverse_direction) || + (button == kForwardMouseButton && reverse_direction) ? + client::UserActionClient::BACK : + client::UserActionClient::FORWARD); + } + break; + } + } + ui::MouseEvent mouseev(xev); + TranslateAndDispatchMouseEvent(&mouseev); + break; + } + case ui::ET_MOUSEWHEEL: { + ui::MouseWheelEvent mouseev(xev); + TranslateAndDispatchMouseEvent(&mouseev); + break; + } + case ui::ET_SCROLL_FLING_START: + case ui::ET_SCROLL_FLING_CANCEL: + case ui::ET_SCROLL: { + ui::ScrollEvent scrollev(xev); + delegate_->OnHostScrollEvent(&scrollev); + break; + } + case ui::ET_UMA_DATA: + break; + case ui::ET_UNKNOWN: + break; + default: + NOTREACHED(); + } + + // If we coalesced an event we need to free its cookie. + if (num_coalesced > 0) + XFreeEventData(xev->xgeneric.display, &last_event.xcookie); +} + +bool RootWindowHostX11::IsWindowManagerPresent() { + // Per ICCCM 2.8, "Manager Selections", window managers should take ownership + // of WM_Sn selections (where n is a screen number). + return XGetSelectionOwner( + xdisplay_, atom_cache_.GetAtom("WM_S0")) != None; +} + +void RootWindowHostX11::SetCursorInternal(gfx::NativeCursor cursor) { + XDefineCursor(xdisplay_, xwindow_, cursor.platform()); +} + +void RootWindowHostX11::TranslateAndDispatchMouseEvent( + ui::MouseEvent* event) { + RootWindow* root_window = GetRootWindow(); + client::ScreenPositionClient* screen_position_client = + GetScreenPositionClient(root_window); + gfx::Rect local(bounds_.size()); + + if (screen_position_client && !local.Contains(event->location())) { + gfx::Point location(event->location()); + // In order to get the correct point in screen coordinates + // during passive grab, we first need to find on which host window + // the mouse is on, and find out the screen coordinates on that + // host window, then convert it back to this host window's coordinate. + screen_position_client->ConvertHostPointToScreen(root_window, &location); + screen_position_client->ConvertPointFromScreen(root_window, &location); + root_window->ConvertPointToHost(&location); + event->set_location(location); + event->set_root_location(location); + } + delegate_->OnHostMouseEvent(event); +} + +void RootWindowHostX11::UpdateIsInternalDisplay() { + RootWindow* root_window = GetRootWindow(); + gfx::Screen* screen = gfx::Screen::GetScreenFor(root_window); + gfx::Display display = screen->GetDisplayNearestWindow(root_window); + is_internal_display_ = display.IsInternal(); +} + +void RootWindowHostX11::SetCrOSTapPaused(bool state) { +#if defined(OS_CHROMEOS) + // Temporarily pause tap-to-click when the cursor is hidden. + Atom prop = atom_cache_.GetAtom("Tap Paused"); + unsigned char value = state; + XIDeviceList dev_list = + ui::DeviceListCacheX::GetInstance()->GetXI2DeviceList(xdisplay_); + + // Only slave pointer devices could possibly have tap-paused property. + for (int i = 0; i < dev_list.count; i++) { + if (dev_list[i].use == XISlavePointer) { + Atom old_type; + int old_format; + unsigned long old_nvalues, bytes; + unsigned char* data; + int result = XIGetProperty(xdisplay_, dev_list[i].deviceid, prop, 0, 0, + False, AnyPropertyType, &old_type, &old_format, + &old_nvalues, &bytes, &data); + if (result != Success) + continue; + XFree(data); + XIChangeProperty(xdisplay_, dev_list[i].deviceid, prop, XA_INTEGER, 8, + PropModeReplace, &value, 1); + } + } +#endif +} + +// static +RootWindowHost* RootWindowHost::Create(const gfx::Rect& bounds) { + return new RootWindowHostX11(bounds); +} + +// static +gfx::Size RootWindowHost::GetNativeScreenSize() { + ::Display* xdisplay = base::MessagePumpAuraX11::GetDefaultXDisplay(); + return gfx::Size(DisplayWidth(xdisplay, 0), DisplayHeight(xdisplay, 0)); +} + +namespace test { + +void SetUseOverrideRedirectWindowByDefault(bool override_redirect) { + default_override_redirect = override_redirect; +} + +} // namespace test +} // namespace aura diff --git a/chromium/ui/aura/root_window_host_x11.h b/chromium/ui/aura/root_window_host_x11.h new file mode 100644 index 00000000000..ac9cc74933e --- /dev/null +++ b/chromium/ui/aura/root_window_host_x11.h @@ -0,0 +1,153 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_HOST_X11_H_ +#define UI_AURA_ROOT_WINDOW_HOST_X11_H_ + +#include <X11/Xlib.h> + +#include <vector> + +// Get rid of a macro from Xlib.h that conflicts with Aura's RootWindow class. +#undef RootWindow + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/env_observer.h" +#include "ui/aura/root_window_host.h" +#include "ui/base/x/x11_atom_cache.h" +#include "ui/base/x/x11_util.h" +#include "ui/gfx/insets.h" +#include "ui/gfx/rect.h" + +namespace ui { +class MouseEvent; +} + +namespace aura { + +namespace internal { +class TouchEventCalibrate; +} + +class RootWindowHostX11 : public RootWindowHost, + public base::MessageLoop::Dispatcher, + public EnvObserver { + public: + explicit RootWindowHostX11(const gfx::Rect& bounds); + virtual ~RootWindowHostX11(); + + // Overridden from Dispatcher overrides: + virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE; + + // RootWindowHost Overrides. + virtual void SetDelegate(RootWindowHostDelegate* delegate) OVERRIDE; + virtual RootWindow* GetRootWindow() OVERRIDE; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void ToggleFullScreen() OVERRIDE; + virtual gfx::Rect GetBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual gfx::Insets GetInsets() const OVERRIDE; + virtual void SetInsets(const gfx::Insets& insets) OVERRIDE; + virtual gfx::Point GetLocationOnNativeScreen() const OVERRIDE; + virtual void SetCapture() OVERRIDE; + virtual void ReleaseCapture() OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor_type) OVERRIDE; + virtual bool QueryMouseLocation(gfx::Point* location_return) OVERRIDE; + virtual bool ConfineCursorToRootWindow() OVERRIDE; + virtual void UnConfineCursor() OVERRIDE; + virtual void OnCursorVisibilityChanged(bool show) OVERRIDE; + virtual void MoveCursorTo(const gfx::Point& location) OVERRIDE; + virtual void SetFocusWhenShown(bool focus_when_shown) OVERRIDE; + virtual void PostNativeEvent(const base::NativeEvent& event) OVERRIDE; + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + virtual void PrepareForShutdown() OVERRIDE; + + // EnvObserver overrides. + virtual void OnWindowInitialized(Window* window) OVERRIDE; + virtual void OnRootWindowInitialized(RootWindow* root_window) OVERRIDE; + private: + class MouseMoveFilter; + + bool DispatchEventForRootWindow(const base::NativeEvent& event); + + // Dispatches XI2 events. Note that some events targetted for the X root + // window are dispatched to the aura root window (e.g. touch events after + // calibration). + void DispatchXI2Event(const base::NativeEvent& event); + + // Returns true if there's an X window manager present... in most cases. Some + // window managers (notably, ion3) don't implement enough of ICCCM for us to + // detect that they're there. + bool IsWindowManagerPresent(); + + // Sets the cursor on |xwindow_| to |cursor|. Does not check or update + // |current_cursor_|. + void SetCursorInternal(gfx::NativeCursor cursor); + + // Translates the native mouse location into screen coordinates and and + // dispatches the event to RootWindowHostDelegate. + void TranslateAndDispatchMouseEvent(ui::MouseEvent* event); + + // Update is_internal_display_ based on delegate_ state + void UpdateIsInternalDisplay(); + + // Set the CrOS touchpad "tap paused" property. It is used to temporarily + // turn off the Tap-to-click feature when the mouse pointer is invisible. + void SetCrOSTapPaused(bool state); + + RootWindowHostDelegate* delegate_; + + // The display and the native X window hosting the root window. + Display* xdisplay_; + ::Window xwindow_; + + // The native root window. + ::Window x_root_window_; + + // Current Aura cursor. + gfx::NativeCursor current_cursor_; + + // Is the window mapped to the screen? + bool window_mapped_; + + // The bounds of |xwindow_|. + gfx::Rect bounds_; + + // The insets that specifies the effective area within the |window_|. + gfx::Insets insets_; + + // The bounds of |x_root_window_|. + gfx::Rect x_root_bounds_; + + // True if the root host resides on the internal display + bool is_internal_display_; + + // True if the window should be focused when the window is shown. + bool focus_when_shown_; + + scoped_ptr<XID[]> pointer_barriers_; + + scoped_ptr<internal::TouchEventCalibrate> touch_calibrate_; + + scoped_ptr<MouseMoveFilter> mouse_move_filter_; + + ui::X11AtomCache atom_cache_; + + DISALLOW_COPY_AND_ASSIGN(RootWindowHostX11); +}; + +namespace test { + +// Set the default value of the override redirect flag used to +// create a X window for RootWindowHostX11. +AURA_EXPORT void SetUseOverrideRedirectWindowByDefault(bool override_redirect); + +} // namespace test +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_HOST_X11_H_ diff --git a/chromium/ui/aura/root_window_mac.h b/chromium/ui/aura/root_window_mac.h new file mode 100644 index 00000000000..13a044c21d2 --- /dev/null +++ b/chromium/ui/aura/root_window_mac.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_MAC_H_ +#define UI_AURA_ROOT_WINDOW_MAC_H_ + +#import <Cocoa/Cocoa.h> + +namespace aura { +class RootWindowHostMacDelegate; +} // aura + +// RootWindow routes NSWindow events back to the RootWindowHost for dispatch +// to the Aura event handling system. +@interface RootWindowMac : NSWindow { + @private + // Weak. May be NULL. The host delegate acts as a conduit for event routing + // back to the host. + aura::RootWindowHostMacDelegate* hostDelegate_; +} + +// Designated initializer. +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)deferCreation; + +// Sets the |hostDelegate_| +- (void)setHostDelegate:(aura::RootWindowHostMacDelegate*)hostDelegate; + +// Overrides main event dispatch to route NSWindow events to host delegate. +- (void)sendEvent:(NSEvent*)event; + +@end + +#endif // UI_AURA_ROOT_WINDOW_MAC_H_ diff --git a/chromium/ui/aura/root_window_mac.mm b/chromium/ui/aura/root_window_mac.mm new file mode 100644 index 00000000000..32e42b7ad9d --- /dev/null +++ b/chromium/ui/aura/root_window_mac.mm @@ -0,0 +1,35 @@ +// 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. + +#import "ui/aura/root_window_mac.h" + +#include "ui/aura/root_window_host_mac.h" + +@implementation RootWindowMac + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSUInteger)windowStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)deferCreation { + if ((self = [super initWithContentRect:contentRect + styleMask:windowStyle + backing:bufferingType + defer:deferCreation])) { + hostDelegate_ = NULL; + } + return self; +} + +- (void)setHostDelegate:(aura::RootWindowHostMacDelegate*)hostDelegate { + hostDelegate_ = hostDelegate; +} + +- (void)sendEvent:(NSEvent*)event { + // Allow both the Cocoa machinery and the Aura machinery to handle the event. + [super sendEvent:event]; + if (hostDelegate_) + hostDelegate_->SendEvent(event); +} + +@end diff --git a/chromium/ui/aura/root_window_observer.h b/chromium/ui/aura/root_window_observer.h new file mode 100644 index 00000000000..bfb8e65c518 --- /dev/null +++ b/chromium/ui/aura/root_window_observer.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_OBSERVER_H_ +#define UI_AURA_ROOT_WINDOW_OBSERVER_H_ + +#include "ui/aura/aura_export.h" + +namespace gfx { +class Point; +class Size; +} + +namespace aura { +class RootWindow; +class Window; + +class AURA_EXPORT RootWindowObserver { + public: + // Invoked after the RootWindow's host has been resized. + virtual void OnRootWindowHostResized(const RootWindow* root) {} + + // Invoked after the RootWindow's host has been moved on screen. + virtual void OnRootWindowHostMoved(const RootWindow* root, + const gfx::Point& new_origin) {} + + // Invoked when the native windowing system sends us a request to close our + // window. + virtual void OnRootWindowHostCloseRequested(const RootWindow* root) {} + + // Invoked when the keyboard mapping has changed. + virtual void OnKeyboardMappingChanged(const RootWindow* root) {} + + protected: + virtual ~RootWindowObserver() {} +}; + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_OBSERVER_H_ diff --git a/chromium/ui/aura/root_window_transformer.h b/chromium/ui/aura/root_window_transformer.h new file mode 100644 index 00000000000..89d67f28e6d --- /dev/null +++ b/chromium/ui/aura/root_window_transformer.h @@ -0,0 +1,45 @@ +// Copyright (c) 2013 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. + +#ifndef UI_AURA_ROOT_WINDOW_TRANSFORMER_H_ +#define UI_AURA_ROOT_WINDOW_TRANSFORMER_H_ + +#include "ui/aura/aura_export.h" + +namespace gfx { +class Insets; +class Rect; +class Size; +class Transform; +} + +namespace aura { + +// RootWindowTransformer controls how RootWindow should be placed and +// transformed inside the host window. +class AURA_EXPORT RootWindowTransformer { + public: + virtual ~RootWindowTransformer() {} + + // Returns the transform the root window in DIP. + virtual gfx::Transform GetTransform() const = 0; + + // Returns the inverse of the transform above. This method is to + // provie an accurate inverse of the transform because the result of + // |gfx::Transform::GetInverse| may contains computational error. + virtual gfx::Transform GetInverseTransform() const = 0; + + // Returns the root window's bounds for given host window size in DIP. + // This is necessary to handle the case where the root window's size + // is bigger than the host window. (Screen magnifier for example). + virtual gfx::Rect GetRootWindowBounds(const gfx::Size& host_size) const = 0; + + // Returns the insets that specifies the effective area of + // the host window. + virtual gfx::Insets GetHostInsets() const = 0; +}; + +} // namespace aura + +#endif // UI_AURA_ROOT_WINDOW_TRANSFORMER_H_ diff --git a/chromium/ui/aura/root_window_unittest.cc b/chromium/ui/aura/root_window_unittest.cc new file mode 100644 index 00000000000..739de66811f --- /dev/null +++ b/chromium/ui/aura/root_window_unittest.cc @@ -0,0 +1,885 @@ +// 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 "ui/aura/root_window.h" + +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/aura/client/event_client.h" +#include "ui/aura/env.h" +#include "ui/aura/focus_manager.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_cursor_client.h" +#include "ui/aura/test/test_event_handler.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window_tracker.h" +#include "ui/base/events/event.h" +#include "ui/base/events/event_handler.h" +#include "ui/base/events/event_utils.h" +#include "ui/base/gestures/gesture_configuration.h" +#include "ui/base/hit_test.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/gfx/point.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/transform.h" + +namespace aura { +namespace { + +// A delegate that always returns a non-client component for hit tests. +class NonClientDelegate : public test::TestWindowDelegate { + public: + NonClientDelegate() + : non_client_count_(0), + mouse_event_count_(0), + mouse_event_flags_(0x0) { + } + virtual ~NonClientDelegate() {} + + int non_client_count() const { return non_client_count_; } + gfx::Point non_client_location() const { return non_client_location_; } + int mouse_event_count() const { return mouse_event_count_; } + gfx::Point mouse_event_location() const { return mouse_event_location_; } + int mouse_event_flags() const { return mouse_event_flags_; } + + virtual int GetNonClientComponent(const gfx::Point& location) const OVERRIDE { + NonClientDelegate* self = const_cast<NonClientDelegate*>(this); + self->non_client_count_++; + self->non_client_location_ = location; + return HTTOPLEFT; + } + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + mouse_event_count_++; + mouse_event_location_ = event->location(); + mouse_event_flags_ = event->flags(); + event->SetHandled(); + } + + private: + int non_client_count_; + gfx::Point non_client_location_; + int mouse_event_count_; + gfx::Point mouse_event_location_; + int mouse_event_flags_; + + DISALLOW_COPY_AND_ASSIGN(NonClientDelegate); +}; + +// A simple event handler that consumes key events. +class ConsumeKeyHandler : public test::TestEventHandler { + public: + ConsumeKeyHandler() {} + virtual ~ConsumeKeyHandler() {} + + // Overridden from ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE { + test::TestEventHandler::OnKeyEvent(event); + event->StopPropagation(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ConsumeKeyHandler); +}; + +bool IsFocusedWindow(aura::Window* window) { + return client::GetFocusClient(window)->GetFocusedWindow() == window; +} + +} // namespace + +typedef test::AuraTestBase RootWindowTest; + +TEST_F(RootWindowTest, OnHostMouseEvent) { + // Create two non-overlapping windows so we don't have to worry about which + // is on top. + scoped_ptr<NonClientDelegate> delegate1(new NonClientDelegate()); + scoped_ptr<NonClientDelegate> delegate2(new NonClientDelegate()); + const int kWindowWidth = 123; + const int kWindowHeight = 45; + gfx::Rect bounds1(100, 200, kWindowWidth, kWindowHeight); + gfx::Rect bounds2(300, 400, kWindowWidth, kWindowHeight); + scoped_ptr<aura::Window> window1(CreateTestWindowWithDelegate( + delegate1.get(), -1234, bounds1, root_window())); + scoped_ptr<aura::Window> window2(CreateTestWindowWithDelegate( + delegate2.get(), -5678, bounds2, root_window())); + + // Send a mouse event to window1. + gfx::Point point(101, 201); + ui::MouseEvent event1( + ui::ET_MOUSE_PRESSED, point, point, ui::EF_LEFT_MOUSE_BUTTON); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(&event1); + + // Event was tested for non-client area for the target window. + EXPECT_EQ(1, delegate1->non_client_count()); + EXPECT_EQ(0, delegate2->non_client_count()); + // The non-client component test was in local coordinates. + EXPECT_EQ(gfx::Point(1, 1), delegate1->non_client_location()); + // Mouse event was received by target window. + EXPECT_EQ(1, delegate1->mouse_event_count()); + EXPECT_EQ(0, delegate2->mouse_event_count()); + // Event was in local coordinates. + EXPECT_EQ(gfx::Point(1, 1), delegate1->mouse_event_location()); + // Non-client flag was set. + EXPECT_TRUE(delegate1->mouse_event_flags() & ui::EF_IS_NON_CLIENT); +} + +TEST_F(RootWindowTest, RepostEvent) { + // Test RepostEvent in RootWindow. It only works for Mouse Press. + EXPECT_FALSE(Env::GetInstance()->is_mouse_button_down()); + gfx::Point point(10, 10); + ui::MouseEvent event( + ui::ET_MOUSE_PRESSED, point, point, ui::EF_LEFT_MOUSE_BUTTON); + root_window()->RepostEvent(event); + RunAllPendingInMessageLoop(); + EXPECT_TRUE(Env::GetInstance()->is_mouse_button_down()); +} + +// Check that we correctly track the state of the mouse buttons in response to +// button press and release events. +TEST_F(RootWindowTest, MouseButtonState) { + EXPECT_FALSE(Env::GetInstance()->is_mouse_button_down()); + + gfx::Point location; + scoped_ptr<ui::MouseEvent> event; + + // Press the left button. + event.reset(new ui::MouseEvent( + ui::ET_MOUSE_PRESSED, + location, + location, + ui::EF_LEFT_MOUSE_BUTTON)); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(event.get()); + EXPECT_TRUE(Env::GetInstance()->is_mouse_button_down()); + + // Additionally press the right. + event.reset(new ui::MouseEvent( + ui::ET_MOUSE_PRESSED, + location, + location, + ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(event.get()); + EXPECT_TRUE(Env::GetInstance()->is_mouse_button_down()); + + // Release the left button. + event.reset(new ui::MouseEvent( + ui::ET_MOUSE_RELEASED, + location, + location, + ui::EF_RIGHT_MOUSE_BUTTON)); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(event.get()); + EXPECT_TRUE(Env::GetInstance()->is_mouse_button_down()); + + // Release the right button. We should ignore the Shift-is-down flag. + event.reset(new ui::MouseEvent( + ui::ET_MOUSE_RELEASED, + location, + location, + ui::EF_SHIFT_DOWN)); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(event.get()); + EXPECT_FALSE(Env::GetInstance()->is_mouse_button_down()); + + // Press the middle button. + event.reset(new ui::MouseEvent( + ui::ET_MOUSE_PRESSED, + location, + location, + ui::EF_MIDDLE_MOUSE_BUTTON)); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(event.get()); + EXPECT_TRUE(Env::GetInstance()->is_mouse_button_down()); +} + +TEST_F(RootWindowTest, TranslatedEvent) { + scoped_ptr<Window> w1(test::CreateTestWindowWithDelegate(NULL, 1, + gfx::Rect(50, 50, 100, 100), root_window())); + + gfx::Point origin(100, 100); + ui::MouseEvent root(ui::ET_MOUSE_PRESSED, origin, origin, 0); + + EXPECT_EQ("100,100", root.location().ToString()); + EXPECT_EQ("100,100", root.root_location().ToString()); + + ui::MouseEvent translated_event( + root, static_cast<Window*>(root_window()), w1.get(), + ui::ET_MOUSE_ENTERED, root.flags()); + EXPECT_EQ("50,50", translated_event.location().ToString()); + EXPECT_EQ("100,100", translated_event.root_location().ToString()); +} + +namespace { + +class TestEventClient : public client::EventClient { + public: + static const int kNonLockWindowId = 100; + static const int kLockWindowId = 200; + + explicit TestEventClient(RootWindow* root_window) + : root_window_(root_window), + lock_(false) { + client::SetEventClient(root_window_, this); + Window* lock_window = + test::CreateTestWindowWithBounds(root_window_->bounds(), root_window_); + lock_window->set_id(kLockWindowId); + Window* non_lock_window = + test::CreateTestWindowWithBounds(root_window_->bounds(), root_window_); + non_lock_window->set_id(kNonLockWindowId); + } + virtual ~TestEventClient() { + client::SetEventClient(root_window_, NULL); + } + + // Starts/stops locking. Locking prevents windows other than those inside + // the lock container from receiving events, getting focus etc. + void Lock() { + lock_ = true; + } + void Unlock() { + lock_ = false; + } + + Window* GetLockWindow() { + return const_cast<Window*>( + static_cast<const TestEventClient*>(this)->GetLockWindow()); + } + const Window* GetLockWindow() const { + return root_window_->GetChildById(kLockWindowId); + } + Window* GetNonLockWindow() { + return root_window_->GetChildById(kNonLockWindowId); + } + + private: + // Overridden from client::EventClient: + virtual bool CanProcessEventsWithinSubtree( + const Window* window) const OVERRIDE { + return lock_ ? + window->Contains(GetLockWindow()) || GetLockWindow()->Contains(window) : + true; + } + + virtual ui::EventTarget* GetToplevelEventTarget() OVERRIDE { + return NULL; + } + + RootWindow* root_window_; + bool lock_; + + DISALLOW_COPY_AND_ASSIGN(TestEventClient); +}; + +} // namespace + +TEST_F(RootWindowTest, CanProcessEventsWithinSubtree) { + TestEventClient client(root_window()); + test::TestWindowDelegate d; + + test::TestEventHandler* nonlock_ef = new test::TestEventHandler; + test::TestEventHandler* lock_ef = new test::TestEventHandler; + client.GetNonLockWindow()->SetEventFilter(nonlock_ef); + client.GetLockWindow()->SetEventFilter(lock_ef); + + Window* w1 = test::CreateTestWindowWithBounds(gfx::Rect(10, 10, 20, 20), + client.GetNonLockWindow()); + w1->set_id(1); + Window* w2 = test::CreateTestWindowWithBounds(gfx::Rect(30, 30, 20, 20), + client.GetNonLockWindow()); + w2->set_id(2); + scoped_ptr<Window> w3( + test::CreateTestWindowWithDelegate(&d, 3, gfx::Rect(30, 30, 20, 20), + client.GetLockWindow())); + + w1->Focus(); + EXPECT_TRUE(IsFocusedWindow(w1)); + + client.Lock(); + + // Since we're locked, the attempt to focus w2 will be ignored. + w2->Focus(); + EXPECT_TRUE(IsFocusedWindow(w1)); + EXPECT_FALSE(IsFocusedWindow(w2)); + + { + // Attempting to send a key event to w1 (not in the lock container) should + // cause focus to be reset. + test::EventGenerator generator(root_window()); + generator.PressKey(ui::VKEY_SPACE, 0); + EXPECT_EQ(NULL, client::GetFocusClient(w1)->GetFocusedWindow()); + } + + { + // Events sent to a window not in the lock container will not be processed. + // i.e. never sent to the non-lock container's event filter. + test::EventGenerator generator(root_window(), w1); + generator.PressLeftButton(); + EXPECT_EQ(0, nonlock_ef->num_mouse_events()); + + // Events sent to a window in the lock container will be processed. + test::EventGenerator generator3(root_window(), w3.get()); + generator3.PressLeftButton(); + EXPECT_EQ(1, lock_ef->num_mouse_events()); + } + + // Prevent w3 from being deleted by the hierarchy since its delegate is owned + // by this scope. + w3->parent()->RemoveChild(w3.get()); +} + +TEST_F(RootWindowTest, IgnoreUnknownKeys) { + test::TestEventHandler* filter = new ConsumeKeyHandler; + root_window()->SetEventFilter(filter); // passes ownership + + ui::KeyEvent unknown_event(ui::ET_KEY_PRESSED, ui::VKEY_UNKNOWN, 0, false); + EXPECT_FALSE(root_window()->AsRootWindowHostDelegate()->OnHostKeyEvent( + &unknown_event)); + EXPECT_EQ(0, filter->num_key_events()); + + ui::KeyEvent known_event(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, false); + EXPECT_TRUE(root_window()->AsRootWindowHostDelegate()->OnHostKeyEvent( + &known_event)); + EXPECT_EQ(1, filter->num_key_events()); +} + +// Tests that touch-events that are beyond the bounds of the root-window do get +// propagated to the event filters correctly with the root as the target. +TEST_F(RootWindowTest, TouchEventsOutsideBounds) { + test::TestEventHandler* filter = new test::TestEventHandler; + root_window()->SetEventFilter(filter); // passes ownership + + gfx::Point position = root_window()->bounds().origin(); + position.Offset(-10, -10); + ui::TouchEvent press(ui::ET_TOUCH_PRESSED, position, 0, base::TimeDelta()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_EQ(1, filter->num_touch_events()); + + position = root_window()->bounds().origin(); + position.Offset(root_window()->bounds().width() + 10, + root_window()->bounds().height() + 10); + ui::TouchEvent release(ui::ET_TOUCH_RELEASED, position, 0, base::TimeDelta()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_EQ(2, filter->num_touch_events()); +} + +// Tests that scroll events are dispatched correctly. +TEST_F(RootWindowTest, ScrollEventDispatch) { + base::TimeDelta now = ui::EventTimeForNow(); + test::TestEventHandler* filter = new test::TestEventHandler; + root_window()->SetEventFilter(filter); + + test::TestWindowDelegate delegate; + scoped_ptr<Window> w1(CreateNormalWindow(1, root_window(), &delegate)); + w1->SetBounds(gfx::Rect(20, 20, 40, 40)); + + // A scroll event on the root-window itself is dispatched. + ui::ScrollEvent scroll1(ui::ET_SCROLL, + gfx::Point(10, 10), + now, + 0, + 0, -10, + 0, -10, + 2); + root_window()->AsRootWindowHostDelegate()->OnHostScrollEvent(&scroll1); + EXPECT_EQ(1, filter->num_scroll_events()); + + // Scroll event on a window should be dispatched properly. + ui::ScrollEvent scroll2(ui::ET_SCROLL, + gfx::Point(25, 30), + now, + 0, + -10, 0, + -10, 0, + 2); + root_window()->AsRootWindowHostDelegate()->OnHostScrollEvent(&scroll2); + EXPECT_EQ(2, filter->num_scroll_events()); +} + +namespace { + +// FilterFilter that tracks the types of events it's seen. +class EventFilterRecorder : public ui::EventHandler { + public: + typedef std::vector<ui::EventType> Events; + + EventFilterRecorder() {} + + Events& events() { return events_; } + + // ui::EventHandler overrides: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE { + events_.push_back(event->type()); + } + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + events_.push_back(event->type()); + } + + virtual void OnScrollEvent(ui::ScrollEvent* event) OVERRIDE { + events_.push_back(event->type()); + } + + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE { + events_.push_back(event->type()); + } + + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { + events_.push_back(event->type()); + } + + private: + Events events_; + + DISALLOW_COPY_AND_ASSIGN(EventFilterRecorder); +}; + +// Converts an EventType to a string. +std::string EventTypeToString(ui::EventType type) { + switch (type) { + case ui::ET_TOUCH_RELEASED: + return "TOUCH_RELEASED"; + + case ui::ET_TOUCH_PRESSED: + return "TOUCH_PRESSED"; + + case ui::ET_TOUCH_MOVED: + return "TOUCH_MOVED"; + + case ui::ET_MOUSE_PRESSED: + return "MOUSE_PRESSED"; + + case ui::ET_MOUSE_DRAGGED: + return "MOUSE_DRAGGED"; + + case ui::ET_MOUSE_RELEASED: + return "MOUSE_RELEASED"; + + case ui::ET_MOUSE_MOVED: + return "MOUSE_MOVED"; + + case ui::ET_MOUSE_ENTERED: + return "MOUSE_ENTERED"; + + case ui::ET_MOUSE_EXITED: + return "MOUSE_EXITED"; + + case ui::ET_GESTURE_SCROLL_BEGIN: + return "GESTURE_SCROLL_BEGIN"; + + case ui::ET_GESTURE_SCROLL_END: + return "GESTURE_SCROLL_END"; + + case ui::ET_GESTURE_SCROLL_UPDATE: + return "GESTURE_SCROLL_UPDATE"; + + case ui::ET_GESTURE_TAP: + return "GESTURE_TAP"; + + case ui::ET_GESTURE_TAP_DOWN: + return "GESTURE_TAP_DOWN"; + + case ui::ET_GESTURE_BEGIN: + return "GESTURE_BEGIN"; + + case ui::ET_GESTURE_END: + return "GESTURE_END"; + + default: + break; + } + return ""; +} + +std::string EventTypesToString(const EventFilterRecorder::Events& events) { + std::string result; + for (size_t i = 0; i < events.size(); ++i) { + if (i != 0) + result += " "; + result += EventTypeToString(events[i]); + } + return result; +} + +} // namespace + +TEST_F(RootWindowTest, MouseMovesHeld) { + EventFilterRecorder* filter = new EventFilterRecorder; + root_window()->SetEventFilter(filter); // passes ownership + + test::TestWindowDelegate delegate; + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + &delegate, 1, gfx::Rect(0, 0, 100, 100), root_window())); + + ui::MouseEvent mouse_move_event(ui::ET_MOUSE_MOVED, gfx::Point(0, 0), + gfx::Point(0, 0), 0); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_move_event); + // Discard MOUSE_ENTER. + filter->events().clear(); + + root_window()->HoldPointerMoves(); + + // Check that we don't immediately dispatch the MOUSE_DRAGGED event. + ui::MouseEvent mouse_dragged_event(ui::ET_MOUSE_DRAGGED, gfx::Point(0, 0), + gfx::Point(0, 0), 0); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_dragged_event); + EXPECT_TRUE(filter->events().empty()); + + // Check that we do dispatch the held MOUSE_DRAGGED event before another type + // of event. + ui::MouseEvent mouse_pressed_event(ui::ET_MOUSE_PRESSED, gfx::Point(0, 0), + gfx::Point(0, 0), 0); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_pressed_event); + EXPECT_EQ("MOUSE_DRAGGED MOUSE_PRESSED", + EventTypesToString(filter->events())); + filter->events().clear(); + + // Check that we coalesce held MOUSE_DRAGGED events. + ui::MouseEvent mouse_dragged_event2(ui::ET_MOUSE_DRAGGED, gfx::Point(1, 1), + gfx::Point(1, 1), 0); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_dragged_event); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_dragged_event2); + EXPECT_TRUE(filter->events().empty()); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_pressed_event); + EXPECT_EQ("MOUSE_DRAGGED MOUSE_PRESSED", + EventTypesToString(filter->events())); + filter->events().clear(); + + // Check that on ReleasePointerMoves, held events are not dispatched + // immediately, but posted instead. + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_dragged_event); + root_window()->ReleasePointerMoves(); + EXPECT_TRUE(filter->events().empty()); + RunAllPendingInMessageLoop(); + EXPECT_EQ("MOUSE_DRAGGED", EventTypesToString(filter->events())); + filter->events().clear(); + + // However if another message comes in before the dispatch, + // the Check that on ReleasePointerMoves, held events are not dispatched + // immediately, but posted instead. + root_window()->HoldPointerMoves(); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_dragged_event); + root_window()->ReleasePointerMoves(); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_pressed_event); + EXPECT_EQ("MOUSE_DRAGGED MOUSE_PRESSED", + EventTypesToString(filter->events())); + filter->events().clear(); + RunAllPendingInMessageLoop(); + EXPECT_TRUE(filter->events().empty()); + + // Check that if the other message is another MOUSE_DRAGGED, we still coalesce + // them. + root_window()->HoldPointerMoves(); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_dragged_event); + root_window()->ReleasePointerMoves(); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent( + &mouse_dragged_event2); + EXPECT_EQ("MOUSE_DRAGGED", EventTypesToString(filter->events())); + filter->events().clear(); + RunAllPendingInMessageLoop(); + EXPECT_TRUE(filter->events().empty()); +} + +TEST_F(RootWindowTest, TouchMovesHeld) { + EventFilterRecorder* filter = new EventFilterRecorder; + root_window()->SetEventFilter(filter); // passes ownership + + test::TestWindowDelegate delegate; + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + &delegate, 1, gfx::Rect(0, 0, 100, 100), root_window())); + + // Starting the touch and throwing out the first few events, since the system + // is going to generate synthetic mouse events that are not relevant to the + // test. + ui::TouchEvent touch_pressed_event(ui::ET_TOUCH_PRESSED, gfx::Point(0, 0), + 0, base::TimeDelta()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_pressed_event); + RunAllPendingInMessageLoop(); + filter->events().clear(); + + root_window()->HoldPointerMoves(); + + // Check that we don't immediately dispatch the TOUCH_MOVED event. + ui::TouchEvent touch_moved_event(ui::ET_TOUCH_MOVED, gfx::Point(0, 0), + 0, base::TimeDelta()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_moved_event); + EXPECT_TRUE(filter->events().empty()); + + // Check that on ReleasePointerMoves, held events are not dispatched + // immediately, but posted instead. + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_moved_event); + root_window()->ReleasePointerMoves(); + EXPECT_TRUE(filter->events().empty()); + RunAllPendingInMessageLoop(); + EXPECT_EQ("TOUCH_MOVED", EventTypesToString(filter->events())); + filter->events().clear(); + + // If another touch event occurs then the held touch should be dispatched + // immediately before it. + ui::TouchEvent touch_released_event(ui::ET_TOUCH_RELEASED, gfx::Point(0, 0), + 0, base::TimeDelta()); + filter->events().clear(); + root_window()->HoldPointerMoves(); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_moved_event); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent( + &touch_released_event); + EXPECT_EQ("TOUCH_MOVED TOUCH_RELEASED GESTURE_END", + EventTypesToString(filter->events())); + filter->events().clear(); + root_window()->ReleasePointerMoves(); + RunAllPendingInMessageLoop(); + EXPECT_TRUE(filter->events().empty()); +} + +// Tests that synthetic mouse events are ignored when mouse +// events are disabled. +TEST_F(RootWindowTest, DispatchSyntheticMouseEvents) { + EventFilterRecorder* filter = new EventFilterRecorder; + root_window()->SetEventFilter(filter); // passes ownership + + test::TestWindowDelegate delegate; + scoped_ptr<aura::Window> window(CreateTestWindowWithDelegate( + &delegate, 1234, gfx::Rect(5, 5, 100, 100), root_window())); + window->Show(); + window->SetCapture(); + + test::TestCursorClient cursor_client(root_window()); + + // Dispatch a non-synthetic mouse event when mouse events are enabled. + ui::MouseEvent mouse1(ui::ET_MOUSE_MOVED, gfx::Point(10, 10), + gfx::Point(10, 10), 0); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouse1); + EXPECT_FALSE(filter->events().empty()); + filter->events().clear(); + + // Dispatch a synthetic mouse event when mouse events are enabled. + ui::MouseEvent mouse2(ui::ET_MOUSE_MOVED, gfx::Point(10, 10), + gfx::Point(10, 10), ui::EF_IS_SYNTHESIZED); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouse2); + EXPECT_FALSE(filter->events().empty()); + filter->events().clear(); + + // Dispatch a synthetic mouse event when mouse events are disabled. + cursor_client.DisableMouseEvents(); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(&mouse2); + EXPECT_TRUE(filter->events().empty()); +} + +class DeletingEventFilter : public ui::EventHandler { + public: + DeletingEventFilter() + : delete_during_pre_handle_(false) {} + virtual ~DeletingEventFilter() {} + + void Reset(bool delete_during_pre_handle) { + delete_during_pre_handle_ = delete_during_pre_handle; + } + + private: + // Overridden from ui::EventHandler: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE { + if (delete_during_pre_handle_) + delete event->target(); + } + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + if (delete_during_pre_handle_) + delete event->target(); + } + + bool delete_during_pre_handle_; + + DISALLOW_COPY_AND_ASSIGN(DeletingEventFilter); +}; + +class DeletingWindowDelegate : public test::TestWindowDelegate { + public: + DeletingWindowDelegate() + : window_(NULL), + delete_during_handle_(false), + got_event_(false) {} + virtual ~DeletingWindowDelegate() {} + + void Reset(Window* window, bool delete_during_handle) { + window_ = window; + delete_during_handle_ = delete_during_handle; + got_event_ = false; + } + bool got_event() const { return got_event_; } + + private: + // Overridden from WindowDelegate: + virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE { + if (delete_during_handle_) + delete window_; + got_event_ = true; + } + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + if (delete_during_handle_) + delete window_; + got_event_ = true; + } + + Window* window_; + bool delete_during_handle_; + bool got_event_; + + DISALLOW_COPY_AND_ASSIGN(DeletingWindowDelegate); +}; + +TEST_F(RootWindowTest, DeleteWindowDuringDispatch) { + // Verifies that we can delete a window during each phase of event handling. + // Deleting the window should not cause a crash, only prevent further + // processing from occurring. + scoped_ptr<Window> w1(CreateNormalWindow(1, root_window(), NULL)); + DeletingWindowDelegate d11; + Window* w11 = CreateNormalWindow(11, w1.get(), &d11); + WindowTracker tracker; + DeletingEventFilter* w1_filter = new DeletingEventFilter; + w1->SetEventFilter(w1_filter); + client::GetFocusClient(w1.get())->FocusWindow(w11); + + test::EventGenerator generator(root_window(), w11); + + // First up, no one deletes anything. + tracker.Add(w11); + d11.Reset(w11, false); + + generator.PressLeftButton(); + EXPECT_TRUE(tracker.Contains(w11)); + EXPECT_TRUE(d11.got_event()); + generator.ReleaseLeftButton(); + + // Delegate deletes w11. This will prevent the post-handle step from applying. + w1_filter->Reset(false); + d11.Reset(w11, true); + generator.PressKey(ui::VKEY_A, 0); + EXPECT_FALSE(tracker.Contains(w11)); + EXPECT_TRUE(d11.got_event()); + + // Pre-handle step deletes w11. This will prevent the delegate and the post- + // handle steps from applying. + w11 = CreateNormalWindow(11, w1.get(), &d11); + w1_filter->Reset(true); + d11.Reset(w11, false); + generator.PressLeftButton(); + EXPECT_FALSE(tracker.Contains(w11)); + EXPECT_FALSE(d11.got_event()); +} + +namespace { + +// A window delegate that detaches the parent of the target's parent window when +// it receives a tap event. +class DetachesParentOnTapDelegate : public test::TestWindowDelegate { + public: + DetachesParentOnTapDelegate() {} + virtual ~DetachesParentOnTapDelegate() {} + + private: + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { + if (event->type() == ui::ET_GESTURE_TAP_DOWN) { + event->SetHandled(); + return; + } + + if (event->type() == ui::ET_GESTURE_TAP) { + Window* parent = static_cast<Window*>(event->target())->parent(); + parent->parent()->RemoveChild(parent); + event->SetHandled(); + } + } + + DISALLOW_COPY_AND_ASSIGN(DetachesParentOnTapDelegate); +}; + +} // namespace + +// Tests that the gesture recognizer is reset for all child windows when a +// window hides. No expectations, just checks that the test does not crash. +TEST_F(RootWindowTest, GestureRecognizerResetsTargetWhenParentHides) { + scoped_ptr<Window> w1(CreateNormalWindow(1, root_window(), NULL)); + DetachesParentOnTapDelegate delegate; + scoped_ptr<Window> parent(CreateNormalWindow(22, w1.get(), NULL)); + Window* child = CreateNormalWindow(11, parent.get(), &delegate); + test::EventGenerator generator(root_window(), child); + generator.GestureTapAt(gfx::Point(40, 40)); +} + +namespace { + +// A window delegate that processes nested gestures on tap. +class NestedGestureDelegate : public test::TestWindowDelegate { + public: + NestedGestureDelegate(test::EventGenerator* generator, + const gfx::Point tap_location) + : generator_(generator), + tap_location_(tap_location), + gesture_end_count_(0) {} + virtual ~NestedGestureDelegate() {} + + int gesture_end_count() const { return gesture_end_count_; } + + private: + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { + switch (event->type()) { + case ui::ET_GESTURE_TAP_DOWN: + event->SetHandled(); + break; + case ui::ET_GESTURE_TAP: + if (generator_) + generator_->GestureTapAt(tap_location_); + event->SetHandled(); + break; + case ui::ET_GESTURE_END: + ++gesture_end_count_; + break; + default: + break; + } + } + + test::EventGenerator* generator_; + const gfx::Point tap_location_; + int gesture_end_count_; + DISALLOW_COPY_AND_ASSIGN(NestedGestureDelegate); +}; + +} // namespace + +// Tests that gesture end is delivered after nested gesture processing. +TEST_F(RootWindowTest, GestureEndDeliveredAfterNestedGestures) { + NestedGestureDelegate d1(NULL, gfx::Point()); + scoped_ptr<Window> w1(CreateNormalWindow(1, root_window(), &d1)); + w1->SetBounds(gfx::Rect(0, 0, 100, 100)); + + test::EventGenerator nested_generator(root_window(), w1.get()); + NestedGestureDelegate d2(&nested_generator, w1->bounds().CenterPoint()); + scoped_ptr<Window> w2(CreateNormalWindow(1, root_window(), &d2)); + w2->SetBounds(gfx::Rect(100, 0, 100, 100)); + + // Tap on w2 which triggers nested gestures for w1. + test::EventGenerator generator(root_window(), w2.get()); + generator.GestureTapAt(w2->bounds().CenterPoint()); + + // Both windows should get their gesture end events. + EXPECT_EQ(1, d1.gesture_end_count()); + EXPECT_EQ(1, d2.gesture_end_count()); +} + +} // namespace aura diff --git a/chromium/ui/aura/root_window_view_mac.h b/chromium/ui/aura/root_window_view_mac.h new file mode 100644 index 00000000000..6617f36dad9 --- /dev/null +++ b/chromium/ui/aura/root_window_view_mac.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef UI_AURA_ROOT_WINDOW_VIEW_MAC_H_ +#define UI_AURA_ROOT_WINDOW_VIEW_MAC_H_ + +#import <Cocoa/Cocoa.h> + +#include "ui/compositor/compositor.h" + +// RootWindowView provides an NSView class that delegates drawing to a +// ui::Compositor delegate, setting up the NSOpenGLContext as required. +@interface RootWindowView : NSView { + @private + ui::Compositor* compositor_; +} +-(void)setCompositor:(ui::Compositor*)compositor; +@end + +#endif // UI_AURA_ROOT_WINDOW_VIEW_MAC_H_ diff --git a/chromium/ui/aura/root_window_view_mac.mm b/chromium/ui/aura/root_window_view_mac.mm new file mode 100644 index 00000000000..a82100e8434 --- /dev/null +++ b/chromium/ui/aura/root_window_view_mac.mm @@ -0,0 +1,16 @@ +// 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. + +#import "ui/aura/root_window_view_mac.h" + +@implementation RootWindowView +-(void)setCompositor:(ui::Compositor*)compositor { + compositor_ = compositor; +} + +- (void)drawRect:(NSRect)rect { + if (compositor_) + compositor_->Draw(false); +} +@end diff --git a/chromium/ui/aura/window.cc b/chromium/ui/aura/window.cc new file mode 100644 index 00000000000..e5289e46a29 --- /dev/null +++ b/chromium/ui/aura/window.cc @@ -0,0 +1,1137 @@ +// 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 "ui/aura/window.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/client/event_client.h" +#include "ui/aura/client/screen_position_client.h" +#include "ui/aura/client/stacking_client.h" +#include "ui/aura/client/visibility_client.h" +#include "ui/aura/env.h" +#include "ui/aura/focus_manager.h" +#include "ui/aura/layout_manager.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window_delegate.h" +#include "ui/aura/window_observer.h" +#include "ui/aura/window_tracker.h" +#include "ui/base/animation/multi_animation.h" +#include "ui/compositor/compositor.h" +#include "ui/compositor/layer.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/path.h" +#include "ui/gfx/screen.h" + +namespace aura { + +namespace { + +void MailboxReleaseCallback(scoped_ptr<base::SharedMemory> shared_memory, + unsigned sync_point, bool lost_resource) { + // NOTE: shared_memory will get released when we go out of scope. +} + +} // namespace + +Window::Window(WindowDelegate* delegate) + : type_(client::WINDOW_TYPE_UNKNOWN), + owned_by_parent_(true), + delegate_(delegate), + parent_(NULL), + transient_parent_(NULL), + visible_(false), + id_(-1), + transparent_(false), + user_data_(NULL), + ignore_events_(false), + // Don't notify newly added observers during notification. This causes + // problems for code that adds an observer as part of an observer + // notification (such as the workspace code). + observers_(ObserverList<WindowObserver>::NOTIFY_EXISTING_ONLY) { + set_target_handler(delegate_); +} + +Window::~Window() { + // layer_ can be NULL if Init() wasn't invoked, which can happen + // only in tests. + if (layer_) + layer_->SuppressPaint(); + + // Let the delegate know we're in the processing of destroying. + if (delegate_) + delegate_->OnWindowDestroying(); + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowDestroying(this)); + + // Let the root know so that it can remove any references to us. + RootWindow* root_window = GetRootWindow(); + if (root_window) + root_window->OnWindowDestroying(this); + + // Then destroy the children. + while (!children_.empty()) { + Window* child = children_[0]; + if (child->owned_by_parent_) { + delete child; + // Deleting the child so remove it from out children_ list. + DCHECK(std::find(children_.begin(), children_.end(), child) == + children_.end()); + } else { + // Even if we can't delete the child, we still need to remove it from the + // parent so that relevant bookkeeping (parent_ back-pointers etc) are + // updated. + RemoveChild(child); + } + } + + // Removes ourselves from our transient parent (if it hasn't been done by the + // RootWindow). + if (transient_parent_) + transient_parent_->RemoveTransientChild(this); + + // The window needs to be removed from the parent before calling the + // WindowDestroyed callbacks of delegate and the observers. + if (parent_) + parent_->RemoveChild(this); + + // And let the delegate do any post cleanup. + // TODO(beng): Figure out if this notification needs to happen here, or if it + // can be moved down adjacent to the observer notification. If it has to be + // done here, the reason why should be documented. + if (delegate_) + delegate_->OnWindowDestroyed(); + + // Destroy transient children, only after we've removed ourselves from our + // parent, as destroying an active transient child may otherwise attempt to + // refocus us. + Windows transient_children(transient_children_); + STLDeleteElements(&transient_children); + DCHECK(transient_children_.empty()); + + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowDestroyed(this)); + + // Clear properties. + for (std::map<const void*, Value>::const_iterator iter = prop_map_.begin(); + iter != prop_map_.end(); + ++iter) { + if (iter->second.deallocator) + (*iter->second.deallocator)(iter->second.value); + } + prop_map_.clear(); + + // If we have layer it will either be destroyed by layer_owner_'s dtor, or by + // whoever acquired it. We don't have a layer if Init() wasn't invoked, which + // can happen in tests. + if (layer_) + layer_->set_delegate(NULL); + layer_ = NULL; +} + +void Window::Init(ui::LayerType layer_type) { + layer_ = new ui::Layer(layer_type); + layer_owner_.reset(layer_); + layer_->SetVisible(false); + layer_->set_delegate(this); + UpdateLayerName(name_); + layer_->SetFillsBoundsOpaquely(!transparent_); + + Env::GetInstance()->NotifyWindowInitialized(this); +} + +ui::Layer* Window::RecreateLayer() { + // Disconnect the old layer, but don't delete it. + ui::Layer* old_layer = AcquireLayer(); + if (!old_layer) + return NULL; + + old_layer->set_delegate(NULL); + float mailbox_scale_factor; + cc::TextureMailbox old_mailbox = + old_layer->GetTextureMailbox(&mailbox_scale_factor); + scoped_refptr<ui::Texture> old_texture = old_layer->external_texture(); + if (delegate_ && old_texture.get()) + old_layer->SetExternalTexture(delegate_->CopyTexture().get()); + + layer_ = new ui::Layer(old_layer->type()); + layer_owner_.reset(layer_); + layer_->SetVisible(old_layer->visible()); + layer_->set_scale_content(old_layer->scale_content()); + layer_->set_delegate(this); + layer_->SetMasksToBounds(old_layer->GetMasksToBounds()); + // Move the original texture to the new layer if the old layer has a + // texture and we could copy it into the old layer, + // crbug.com/175211. + if (delegate_ && old_texture.get()) { + layer_->SetExternalTexture(old_texture.get()); + } else if (old_mailbox.IsSharedMemory()) { + base::SharedMemory* old_buffer = old_mailbox.shared_memory(); + const size_t size = old_mailbox.shared_memory_size_in_bytes(); + + scoped_ptr<base::SharedMemory> new_buffer(new base::SharedMemory); + new_buffer->CreateAndMapAnonymous(size); + + if (old_buffer->memory() && new_buffer->memory()) { + memcpy(new_buffer->memory(), old_buffer->memory(), size); + base::SharedMemory* new_buffer_raw_ptr = new_buffer.get(); + cc::TextureMailbox::ReleaseCallback callback = + base::Bind(MailboxReleaseCallback, Passed(&new_buffer)); + cc::TextureMailbox new_mailbox(new_buffer_raw_ptr, + old_mailbox.shared_memory_size(), + callback); + layer_->SetTextureMailbox(new_mailbox, mailbox_scale_factor); + } + } + + UpdateLayerName(name_); + layer_->SetFillsBoundsOpaquely(!transparent_); + // Install new layer as a sibling of the old layer, stacked below it. + if (old_layer->parent()) { + old_layer->parent()->Add(layer_); + old_layer->parent()->StackBelow(layer_, old_layer); + } + // Migrate all the child layers over to the new layer. Copy the list because + // the items are removed during iteration. + std::vector<ui::Layer*> children_copy = old_layer->children(); + for (std::vector<ui::Layer*>::const_iterator it = children_copy.begin(); + it != children_copy.end(); + ++it) { + ui::Layer* child = *it; + layer_->Add(child); + } + return old_layer; +} + +void Window::SetType(client::WindowType type) { + // Cannot change type after the window is initialized. + DCHECK(!layer()); + type_ = type; +} + +void Window::SetName(const std::string& name) { + name_ = name; + + if (layer()) + UpdateLayerName(name_); +} + +void Window::SetTransparent(bool transparent) { + // Cannot change transparent flag after the window is initialized. + DCHECK(!layer()); + transparent_ = transparent; +} + +RootWindow* Window::GetRootWindow() { + return const_cast<RootWindow*>( + static_cast<const Window*>(this)->GetRootWindow()); +} + +const RootWindow* Window::GetRootWindow() const { + return parent_ ? parent_->GetRootWindow() : NULL; +} + +void Window::Show() { + SetVisible(true); +} + +void Window::Hide() { + for (Windows::iterator it = transient_children_.begin(); + it != transient_children_.end(); ++it) { + (*it)->Hide(); + } + SetVisible(false); + ReleaseCapture(); +} + +bool Window::IsVisible() const { + // Layer visibility can be inconsistent with window visibility, for example + // when a Window is hidden, we want this function to return false immediately + // after, even though the client may decide to animate the hide effect (and + // so the layer will be visible for some time after Hide() is called). + return visible_ && layer_ && layer_->IsDrawn(); +} + +gfx::Rect Window::GetBoundsInRootWindow() const { + // TODO(beng): There may be a better way to handle this, and the existing code + // is likely wrong anyway in a multi-display world, but this will + // do for now. + if (!GetRootWindow()) + return bounds(); + gfx::Point origin = bounds().origin(); + ConvertPointToTarget(parent_, GetRootWindow(), &origin); + return gfx::Rect(origin, bounds().size()); +} + +gfx::Rect Window::GetBoundsInScreen() const { + gfx::Rect bounds(GetBoundsInRootWindow()); + const RootWindow* root = GetRootWindow(); + if (root) { + aura::client::ScreenPositionClient* screen_position_client = + aura::client::GetScreenPositionClient(root); + if (screen_position_client) { + gfx::Point origin = bounds.origin(); + screen_position_client->ConvertPointToScreen(root, &origin); + bounds.set_origin(origin); + } + } + return bounds; +} + +void Window::SetTransform(const gfx::Transform& transform) { + RootWindow* root_window = GetRootWindow(); + bool contained_mouse = IsVisible() && root_window && + ContainsPointInRoot(root_window->GetLastMouseLocationInRoot()); + layer()->SetTransform(transform); + if (root_window) + root_window->OnWindowTransformed(this, contained_mouse); +} + +void Window::SetLayoutManager(LayoutManager* layout_manager) { + if (layout_manager == layout_manager_) + return; + layout_manager_.reset(layout_manager); + if (!layout_manager) + return; + // If we're changing to a new layout manager, ensure it is aware of all the + // existing child windows. + for (Windows::const_iterator it = children_.begin(); + it != children_.end(); + ++it) + layout_manager_->OnWindowAddedToLayout(*it); +} + +void Window::SetBounds(const gfx::Rect& new_bounds) { + if (parent_ && parent_->layout_manager()) + parent_->layout_manager()->SetChildBounds(this, new_bounds); + else + SetBoundsInternal(new_bounds); +} + +void Window::SetBoundsInScreen(const gfx::Rect& new_bounds_in_screen, + const gfx::Display& dst_display) { + RootWindow* root = GetRootWindow(); + if (root) { + gfx::Point origin = new_bounds_in_screen.origin(); + aura::client::ScreenPositionClient* screen_position_client = + aura::client::GetScreenPositionClient(root); + screen_position_client->SetBounds(this, new_bounds_in_screen, dst_display); + return; + } + SetBounds(new_bounds_in_screen); +} + +gfx::Rect Window::GetTargetBounds() const { + return layer_->GetTargetBounds(); +} + +const gfx::Rect& Window::bounds() const { + return layer_->bounds(); +} + +void Window::SchedulePaintInRect(const gfx::Rect& rect) { + if (layer_->SchedulePaint(rect)) { + FOR_EACH_OBSERVER( + WindowObserver, observers_, OnWindowPaintScheduled(this, rect)); + } +} + +void Window::SetDefaultParentByRootWindow(RootWindow* root_window, + const gfx::Rect& bounds_in_screen) { + DCHECK(root_window); + + // Stacking clients are mandatory on RootWindow objects. + client::StackingClient* client = client::GetStackingClient(root_window); + DCHECK(client); + + aura::Window* default_parent = client->GetDefaultParent( + root_window, this, bounds_in_screen); + default_parent->AddChild(this); +} + +void Window::StackChildAtTop(Window* child) { + if (children_.size() <= 1 || child == children_.back()) + return; // In the front already. + StackChildAbove(child, children_.back()); +} + +void Window::StackChildAbove(Window* child, Window* target) { + StackChildRelativeTo(child, target, STACK_ABOVE); +} + +void Window::StackChildAtBottom(Window* child) { + if (children_.size() <= 1 || child == children_.front()) + return; // At the bottom already. + StackChildBelow(child, children_.front()); +} + +void Window::StackChildBelow(Window* child, Window* target) { + StackChildRelativeTo(child, target, STACK_BELOW); +} + +void Window::AddChild(Window* child) { + WindowObserver::HierarchyChangeParams params; + params.target = child; + params.new_parent = this; + params.old_parent = child->parent(); + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; + NotifyWindowHierarchyChange(params); + + RootWindow* old_root = child->GetRootWindow(); + + DCHECK(std::find(children_.begin(), children_.end(), child) == + children_.end()); + if (child->parent()) + child->parent()->RemoveChildImpl(child, this); + child->parent_ = this; + + layer_->Add(child->layer_); + + children_.push_back(child); + if (layout_manager_) + layout_manager_->OnWindowAddedToLayout(child); + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowAdded(child)); + child->OnParentChanged(); + + RootWindow* root_window = GetRootWindow(); + if (root_window && old_root != root_window) { + root_window->OnWindowAddedToRootWindow(child); + child->NotifyAddedToRootWindow(); + } + + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; + NotifyWindowHierarchyChange(params); +} + +void Window::RemoveChild(Window* child) { + WindowObserver::HierarchyChangeParams params; + params.target = child; + params.new_parent = NULL; + params.old_parent = this; + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; + NotifyWindowHierarchyChange(params); + + RemoveChildImpl(child, NULL); + + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; + NotifyWindowHierarchyChange(params); +} + +bool Window::Contains(const Window* other) const { + for (const Window* parent = other; parent; parent = parent->parent_) { + if (parent == this) + return true; + } + return false; +} + +void Window::AddTransientChild(Window* child) { + if (child->transient_parent_) + child->transient_parent_->RemoveTransientChild(child); + DCHECK(std::find(transient_children_.begin(), transient_children_.end(), + child) == transient_children_.end()); + transient_children_.push_back(child); + child->transient_parent_ = this; + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnAddTransientChild(this, child)); +} + +void Window::RemoveTransientChild(Window* child) { + Windows::iterator i = + std::find(transient_children_.begin(), transient_children_.end(), child); + DCHECK(i != transient_children_.end()); + transient_children_.erase(i); + if (child->transient_parent_ == this) + child->transient_parent_ = NULL; + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnRemoveTransientChild(this, child)); +} + +Window* Window::GetChildById(int id) { + return const_cast<Window*>(const_cast<const Window*>(this)->GetChildById(id)); +} + +const Window* Window::GetChildById(int id) const { + Windows::const_iterator i; + for (i = children_.begin(); i != children_.end(); ++i) { + if ((*i)->id() == id) + return *i; + const Window* result = (*i)->GetChildById(id); + if (result) + return result; + } + return NULL; +} + +// static +void Window::ConvertPointToTarget(const Window* source, + const Window* target, + gfx::Point* point) { + if (!source) + return; + if (source->GetRootWindow() != target->GetRootWindow()) { + client::ScreenPositionClient* source_client = + GetScreenPositionClient(source->GetRootWindow()); + source_client->ConvertPointToScreen(source, point); + + client::ScreenPositionClient* target_client = + GetScreenPositionClient(target->GetRootWindow()); + target_client->ConvertPointFromScreen(target, point); + } else { + ui::Layer::ConvertPointToLayer(source->layer(), target->layer(), point); + } +} + +void Window::MoveCursorTo(const gfx::Point& point_in_window) { + RootWindow* root_window = GetRootWindow(); + DCHECK(root_window); + gfx::Point point_in_root(point_in_window); + ConvertPointToTarget(this, root_window, &point_in_root); + root_window->MoveCursorTo(point_in_root); +} + +gfx::NativeCursor Window::GetCursor(const gfx::Point& point) const { + return delegate_ ? delegate_->GetCursor(point) : gfx::kNullCursor; +} + +void Window::SetEventFilter(ui::EventHandler* event_filter) { + if (event_filter_) + RemovePreTargetHandler(event_filter_.get()); + event_filter_.reset(event_filter); + if (event_filter) + AddPreTargetHandler(event_filter); +} + +void Window::AddObserver(WindowObserver* observer) { + observers_.AddObserver(observer); +} + +void Window::RemoveObserver(WindowObserver* observer) { + observers_.RemoveObserver(observer); +} + +bool Window::HasObserver(WindowObserver* observer) { + return observers_.HasObserver(observer); +} + +bool Window::ContainsPointInRoot(const gfx::Point& point_in_root) const { + const Window* root_window = GetRootWindow(); + if (!root_window) + return false; + gfx::Point local_point(point_in_root); + ConvertPointToTarget(root_window, this, &local_point); + return gfx::Rect(GetTargetBounds().size()).Contains(local_point); +} + +bool Window::ContainsPoint(const gfx::Point& local_point) const { + return gfx::Rect(bounds().size()).Contains(local_point); +} + +bool Window::HitTest(const gfx::Point& local_point) { + // Expand my bounds for hit testing (override is usually zero but it's + // probably cheaper to do the math every time than to branch). + gfx::Rect local_bounds(gfx::Point(), bounds().size()); + local_bounds.Inset(aura::Env::GetInstance()->is_touch_down() ? + hit_test_bounds_override_outer_touch_ : + hit_test_bounds_override_outer_mouse_); + + if (!delegate_ || !delegate_->HasHitTestMask()) + return local_bounds.Contains(local_point); + + gfx::Path mask; + delegate_->GetHitTestMask(&mask); + + SkRegion clip_region; + clip_region.setRect(local_bounds.x(), local_bounds.y(), + local_bounds.width(), local_bounds.height()); + SkRegion mask_region; + return mask_region.setPath(mask, clip_region) && + mask_region.contains(local_point.x(), local_point.y()); +} + +Window* Window::GetEventHandlerForPoint(const gfx::Point& local_point) { + return GetWindowForPoint(local_point, true, true); +} + +Window* Window::GetTopWindowContainingPoint(const gfx::Point& local_point) { + return GetWindowForPoint(local_point, false, false); +} + +Window* Window::GetToplevelWindow() { + Window* topmost_window_with_delegate = NULL; + for (aura::Window* window = this; window != NULL; window = window->parent()) { + if (window->delegate()) + topmost_window_with_delegate = window; + } + return topmost_window_with_delegate; +} + +void Window::Focus() { + client::FocusClient* client = client::GetFocusClient(this); + DCHECK(client); + client->FocusWindow(this); +} + +void Window::Blur() { + client::FocusClient* client = client::GetFocusClient(this); + DCHECK(client); + client->FocusWindow(NULL); +} + +bool Window::HasFocus() const { + client::FocusClient* client = client::GetFocusClient(this); + return client && client->GetFocusedWindow() == this; +} + +bool Window::CanFocus() const { + // NOTE: as part of focusing the window the ActivationClient may make the + // window visible (by way of making a hidden ancestor visible). For this + // reason we can't check visibility here and assume the client is doing it. + if (!parent_ || (delegate_ && !delegate_->CanFocus())) + return false; + + // The client may forbid certain windows from receiving focus at a given point + // in time. + client::EventClient* client = client::GetEventClient(GetRootWindow()); + if (client && !client->CanProcessEventsWithinSubtree(this)) + return false; + + return parent_->CanFocus(); +} + +bool Window::CanReceiveEvents() const { + // The client may forbid certain windows from receiving events at a given + // point in time. + client::EventClient* client = client::GetEventClient(GetRootWindow()); + if (client && !client->CanProcessEventsWithinSubtree(this)) + return false; + + return parent_ && IsVisible() && parent_->CanReceiveEvents(); +} + +void Window::SetCapture() { + if (!IsVisible()) + return; + + RootWindow* root_window = GetRootWindow(); + if (!root_window) + return; + client::GetCaptureClient(root_window)->SetCapture(this); +} + +void Window::ReleaseCapture() { + RootWindow* root_window = GetRootWindow(); + if (!root_window) + return; + client::GetCaptureClient(root_window)->ReleaseCapture(this); +} + +bool Window::HasCapture() { + RootWindow* root_window = GetRootWindow(); + return root_window && + client::GetCaptureClient(root_window)->GetCaptureWindow() == this; +} + +void Window::SuppressPaint() { + layer_->SuppressPaint(); +} + +// {Set,Get,Clear}Property are implemented in window_property.h. + +void Window::SetNativeWindowProperty(const char* key, void* value) { + SetPropertyInternal( + key, key, NULL, reinterpret_cast<int64>(value), 0); +} + +void* Window::GetNativeWindowProperty(const char* key) const { + return reinterpret_cast<void*>(GetPropertyInternal(key, 0)); +} + +void Window::OnDeviceScaleFactorChanged(float device_scale_factor) { + if (delegate_) + delegate_->OnDeviceScaleFactorChanged(device_scale_factor); +} + +#ifndef NDEBUG +std::string Window::GetDebugInfo() const { + return base::StringPrintf( + "%s<%d> bounds(%d, %d, %d, %d) %s %s opacity=%.1f", + name().empty() ? "Unknown" : name().c_str(), id(), + bounds().x(), bounds().y(), bounds().width(), bounds().height(), + visible_ ? "WindowVisible" : "WindowHidden", + layer_->GetTargetVisibility() ? "LayerVisible" : "LayerHidden", + layer_->opacity()); +} + +void Window::PrintWindowHierarchy(int depth) const { + printf("%*s%s\n", depth * 2, "", GetDebugInfo().c_str()); + for (Windows::const_iterator it = children_.begin(); + it != children_.end(); ++it) { + Window* child = *it; + child->PrintWindowHierarchy(depth + 1); + } +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Window, private: + +int64 Window::SetPropertyInternal(const void* key, + const char* name, + PropertyDeallocator deallocator, + int64 value, + int64 default_value) { + int64 old = GetPropertyInternal(key, default_value); + if (value == default_value) { + prop_map_.erase(key); + } else { + Value prop_value; + prop_value.name = name; + prop_value.value = value; + prop_value.deallocator = deallocator; + prop_map_[key] = prop_value; + } + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowPropertyChanged(this, key, old)); + return old; +} + +int64 Window::GetPropertyInternal(const void* key, + int64 default_value) const { + std::map<const void*, Value>::const_iterator iter = prop_map_.find(key); + if (iter == prop_map_.end()) + return default_value; + return iter->second.value; +} + +void Window::SetBoundsInternal(const gfx::Rect& new_bounds) { + gfx::Rect actual_new_bounds(new_bounds); + + // Ensure we don't go smaller than our minimum bounds. + if (delegate_) { + const gfx::Size& min_size = delegate_->GetMinimumSize(); + actual_new_bounds.set_width( + std::max(min_size.width(), actual_new_bounds.width())); + actual_new_bounds.set_height( + std::max(min_size.height(), actual_new_bounds.height())); + } + + gfx::Rect old_bounds = GetTargetBounds(); + + // Always need to set the layer's bounds -- even if it is to the same thing. + // This may cause important side effects such as stopping animation. + layer_->SetBounds(actual_new_bounds); + + // If we are currently not the layer's delegate, we will not get bounds + // changed notification from the layer (this typically happens after animating + // hidden). We must notify ourselves. + if (layer_->delegate() != this) + OnLayerBoundsChanged(old_bounds, ContainsMouse()); +} + +void Window::SetVisible(bool visible) { + if (visible == layer_->GetTargetVisibility()) + return; // No change. + + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowVisibilityChanging(this, visible)); + + RootWindow* root_window = GetRootWindow(); + if (root_window) + root_window->DispatchMouseExitToHidingWindow(this); + + client::VisibilityClient* visibility_client = + client::GetVisibilityClient(this); + if (visibility_client) + visibility_client->UpdateLayerVisibility(this, visible); + else + layer_->SetVisible(visible); + visible_ = visible; + SchedulePaint(); + if (parent_ && parent_->layout_manager_) + parent_->layout_manager_->OnChildWindowVisibilityChanged(this, visible); + + if (delegate_) + delegate_->OnWindowTargetVisibilityChanged(visible); + + NotifyWindowVisibilityChanged(this, visible); + + if (root_window) + root_window->OnWindowVisibilityChanged(this, visible); +} + +void Window::SchedulePaint() { + SchedulePaintInRect(gfx::Rect(0, 0, bounds().width(), bounds().height())); +} + +Window* Window::GetWindowForPoint(const gfx::Point& local_point, + bool return_tightest, + bool for_event_handling) { + if (!IsVisible()) + return NULL; + + if ((for_event_handling && !HitTest(local_point)) || + (!for_event_handling && !ContainsPoint(local_point))) + return NULL; + + // Check if I should claim this event and not pass it to my children because + // the location is inside my hit test override area. For details, see + // set_hit_test_bounds_override_inner(). + if (for_event_handling && !hit_test_bounds_override_inner_.empty()) { + gfx::Rect inset_local_bounds(gfx::Point(), bounds().size()); + inset_local_bounds.Inset(hit_test_bounds_override_inner_); + // We know we're inside the normal local bounds, so if we're outside the + // inset bounds we must be in the special hit test override area. + DCHECK(HitTest(local_point)); + if (!inset_local_bounds.Contains(local_point)) + return delegate_ ? this : NULL; + } + + if (!return_tightest && delegate_) + return this; + + for (Windows::const_reverse_iterator it = children_.rbegin(), + rend = children_.rend(); + it != rend; ++it) { + Window* child = *it; + + if (for_event_handling) { + if (child->ignore_events_) + continue; + // The client may not allow events to be processed by certain subtrees. + client::EventClient* client = client::GetEventClient(GetRootWindow()); + if (client && !client->CanProcessEventsWithinSubtree(child)) + continue; + if (delegate_ && !delegate_->ShouldDescendIntoChildForEventHandling( + child, local_point)) { + continue; + } + } + + gfx::Point point_in_child_coords(local_point); + ConvertPointToTarget(this, child, &point_in_child_coords); + Window* match = child->GetWindowForPoint(point_in_child_coords, + return_tightest, + for_event_handling); + if (match) + return match; + } + + return delegate_ ? this : NULL; +} + +void Window::RemoveChildImpl(Window* child, Window* new_parent) { + if (layout_manager_) + layout_manager_->OnWillRemoveWindowFromLayout(child); + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWillRemoveWindow(child)); + RootWindow* root_window = child->GetRootWindow(); + RootWindow* new_root_window = new_parent ? new_parent->GetRootWindow() : NULL; + if (root_window && root_window != new_root_window) { + root_window->OnWindowRemovedFromRootWindow(child, new_root_window); + child->NotifyRemovingFromRootWindow(); + } + child->parent_ = NULL; + // We should only remove the child's layer if the child still owns that layer. + // Someone else may have acquired ownership of it via AcquireLayer() and may + // expect the hierarchy to go unchanged as the Window is destroyed. + if (child->layer_owner_) + layer_->Remove(child->layer_); + Windows::iterator i = std::find(children_.begin(), children_.end(), child); + DCHECK(i != children_.end()); + children_.erase(i); + child->OnParentChanged(); + if (layout_manager_) + layout_manager_->OnWindowRemovedFromLayout(child); +} + +void Window::OnParentChanged() { + FOR_EACH_OBSERVER( + WindowObserver, observers_, OnWindowParentChanged(this, parent_)); +} + +void Window::StackChildRelativeTo(Window* child, + Window* target, + StackDirection direction) { + DCHECK_NE(child, target); + DCHECK(child); + DCHECK(target); + DCHECK_EQ(this, child->parent()); + DCHECK_EQ(this, target->parent()); + + const size_t target_i = + std::find(children_.begin(), children_.end(), target) - children_.begin(); + + // By convention we don't stack on top of windows with layers with NULL + // delegates. Walk backward to find a valid target window. + // See tests WindowTest.StackingMadrigal and StackOverClosingTransient + // for an explanation of this. + size_t final_target_i = target_i; + while (final_target_i > 0 && + children_[final_target_i]->layer()->delegate() == NULL) { + --final_target_i; + } + + // Allow stacking immediately below a window with a NULL layer. + if (direction == STACK_BELOW && target_i != final_target_i) + direction = STACK_ABOVE; + + Window* final_target = children_[final_target_i]; + + // If we couldn't find a valid target position, don't move anything. + if (final_target->layer()->delegate() == NULL) + return; + + // Don't try to stack a child above itself. + if (child == final_target) + return; + + // Move the child and all its transients. + StackChildRelativeToImpl(child, final_target, direction); +} + +void Window::StackChildRelativeToImpl(Window* child, + Window* target, + StackDirection direction) { + DCHECK_NE(child, target); + DCHECK(child); + DCHECK(target); + DCHECK_EQ(this, child->parent()); + DCHECK_EQ(this, target->parent()); + + const size_t child_i = + std::find(children_.begin(), children_.end(), child) - children_.begin(); + const size_t target_i = + std::find(children_.begin(), children_.end(), target) - children_.begin(); + + // Don't move the child if it is already in the right place. + if ((direction == STACK_ABOVE && child_i == target_i + 1) || + (direction == STACK_BELOW && child_i + 1 == target_i)) + return; + + const size_t dest_i = + direction == STACK_ABOVE ? + (child_i < target_i ? target_i : target_i + 1) : + (child_i < target_i ? target_i - 1 : target_i); + children_.erase(children_.begin() + child_i); + children_.insert(children_.begin() + dest_i, child); + + if (direction == STACK_ABOVE) + layer()->StackAbove(child->layer(), target->layer()); + else + layer()->StackBelow(child->layer(), target->layer()); + + // Stack any transient children that share the same parent to be in front of + // 'child'. + Window* last_transient = child; + for (Windows::iterator it = child->transient_children_.begin(); + it != child->transient_children_.end(); ++it) { + Window* transient_child = *it; + if (transient_child->parent_ == this) { + StackChildRelativeToImpl(transient_child, last_transient, STACK_ABOVE); + last_transient = transient_child; + } + } + + child->OnStackingChanged(); +} + +void Window::OnStackingChanged() { + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowStackingChanged(this)); +} + +void Window::NotifyRemovingFromRootWindow() { + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowRemovingFromRootWindow(this)); + for (Window::Windows::const_iterator it = children_.begin(); + it != children_.end(); ++it) { + (*it)->NotifyRemovingFromRootWindow(); + } +} + +void Window::NotifyAddedToRootWindow() { + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowAddedToRootWindow(this)); + for (Window::Windows::const_iterator it = children_.begin(); + it != children_.end(); ++it) { + (*it)->NotifyAddedToRootWindow(); + } +} + +void Window::NotifyWindowHierarchyChange( + const WindowObserver::HierarchyChangeParams& params) { + params.target->NotifyWindowHierarchyChangeDown(params); + switch (params.phase) { + case WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING: + if (params.old_parent) + params.old_parent->NotifyWindowHierarchyChangeUp(params); + break; + case WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED: + if (params.new_parent) + params.new_parent->NotifyWindowHierarchyChangeUp(params); + break; + default: + NOTREACHED(); + break; + } +} + +void Window::NotifyWindowHierarchyChangeDown( + const WindowObserver::HierarchyChangeParams& params) { + NotifyWindowHierarchyChangeAtReceiver(params); + for (Window::Windows::const_iterator it = children_.begin(); + it != children_.end(); ++it) { + (*it)->NotifyWindowHierarchyChangeDown(params); + } +} + +void Window::NotifyWindowHierarchyChangeUp( + const WindowObserver::HierarchyChangeParams& params) { + for (Window* window = this; window; window = window->parent()) + window->NotifyWindowHierarchyChangeAtReceiver(params); +} + +void Window::NotifyWindowHierarchyChangeAtReceiver( + const WindowObserver::HierarchyChangeParams& params) { + WindowObserver::HierarchyChangeParams local_params = params; + local_params.receiver = this; + + switch (params.phase) { + case WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING: + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowHierarchyChanging(local_params)); + break; + case WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED: + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowHierarchyChanged(local_params)); + break; + default: + NOTREACHED(); + break; + } +} + +void Window::NotifyWindowVisibilityChanged(aura::Window* target, + bool visible) { + if (!NotifyWindowVisibilityChangedDown(target, visible)) { + return; // |this| has been deleted. + } + NotifyWindowVisibilityChangedUp(target, visible); +} + +bool Window::NotifyWindowVisibilityChangedAtReceiver(aura::Window* target, + bool visible) { + // |this| may be deleted during a call to OnWindowVisibilityChanged() on one + // of the observers. We create an local observer for that. In that case we + // exit without further access to any members. + WindowTracker tracker; + tracker.Add(this); + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowVisibilityChanged(target, visible)); + return tracker.Contains(this); +} + +bool Window::NotifyWindowVisibilityChangedDown(aura::Window* target, + bool visible) { + if (!NotifyWindowVisibilityChangedAtReceiver(target, visible)) + return false; // |this| was deleted. + std::set<const Window*> child_already_processed; + bool child_destroyed = false; + do { + child_destroyed = false; + for (Window::Windows::const_iterator it = children_.begin(); + it != children_.end(); ++it) { + if (!child_already_processed.insert(*it).second) + continue; + if (!(*it)->NotifyWindowVisibilityChangedDown(target, visible)) { + // |*it| was deleted, |it| is invalid and |children_| has changed. + // We exit the current for-loop and enter a new one. + child_destroyed = true; + break; + } + } + } while (child_destroyed); + return true; +} + +void Window::NotifyWindowVisibilityChangedUp(aura::Window* target, + bool visible) { + for (Window* window = this; window; window = window->parent()) { + bool ret = window->NotifyWindowVisibilityChangedAtReceiver(target, visible); + DCHECK(ret); + } +} + +void Window::OnLayerBoundsChanged(const gfx::Rect& old_bounds, + bool contained_mouse) { + if (layout_manager_) + layout_manager_->OnWindowResized(); + if (delegate_) + delegate_->OnBoundsChanged(old_bounds, bounds()); + FOR_EACH_OBSERVER(WindowObserver, + observers_, + OnWindowBoundsChanged(this, old_bounds, bounds())); + RootWindow* root_window = GetRootWindow(); + if (root_window) + root_window->OnWindowBoundsChanged(this, contained_mouse); +} + +void Window::OnPaintLayer(gfx::Canvas* canvas) { + if (delegate_) + delegate_->OnPaint(canvas); +} + +base::Closure Window::PrepareForLayerBoundsChange() { + return base::Bind(&Window::OnLayerBoundsChanged, base::Unretained(this), + bounds(), ContainsMouse()); +} + +bool Window::CanAcceptEvent(const ui::Event& event) { + // The client may forbid certain windows from receiving events at a given + // point in time. + client::EventClient* client = client::GetEventClient(GetRootWindow()); + if (client && !client->CanProcessEventsWithinSubtree(this)) + return false; + + bool visible = event.dispatch_to_hidden_targets() || IsVisible(); + return visible && (!parent_ || parent_->CanAcceptEvent(event)); +} + +ui::EventTarget* Window::GetParentTarget() { + return parent_; +} + +void Window::UpdateLayerName(const std::string& name) { +#if !defined(NDEBUG) + DCHECK(layer()); + + std::string layer_name(name_); + if (layer_name.empty()) + layer_name.append("Unnamed Window"); + + if (id_ != -1) { + char id_buf[10]; + base::snprintf(id_buf, sizeof(id_buf), " %d", id_); + layer_name.append(id_buf); + } + layer()->set_name(layer_name); +#endif +} + +bool Window::ContainsMouse() { + bool contains_mouse = false; + if (IsVisible()) { + RootWindow* root_window = GetRootWindow(); + contains_mouse = root_window && + ContainsPointInRoot(root_window->GetLastMouseLocationInRoot()); + } + return contains_mouse; +} + +} // namespace aura diff --git a/chromium/ui/aura/window.h b/chromium/ui/aura/window.h new file mode 100644 index 00000000000..92dfa90b9c4 --- /dev/null +++ b/chromium/ui/aura/window.h @@ -0,0 +1,529 @@ +// 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. + +#ifndef UI_AURA_WINDOW_H_ +#define UI_AURA_WINDOW_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/strings/string16.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/client/window_types.h" +#include "ui/aura/window_observer.h" +#include "ui/base/events/event_constants.h" +#include "ui/base/events/event_target.h" +#include "ui/base/gestures/gesture_types.h" +#include "ui/compositor/layer_animator.h" +#include "ui/compositor/layer_delegate.h" +#include "ui/compositor/layer_owner.h" +#include "ui/compositor/layer_type.h" +#include "ui/gfx/insets.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" + +namespace gfx { +class Display; +class Transform; +} + +namespace ui { +class EventHandler; +class Layer; +class Texture; +} + +namespace aura { + +class LayoutManager; +class RootWindow; +class WindowDelegate; +class WindowObserver; + +// Defined in window_property.h (which we do not include) +template<typename T> +struct WindowProperty; + +namespace test { +class WindowTestApi; +} + +// Aura window implementation. Interesting events are sent to the +// WindowDelegate. +// TODO(beng): resolve ownership. +class AURA_EXPORT Window : public ui::LayerDelegate, + public ui::LayerOwner, + public ui::EventTarget, + public ui::GestureConsumer { + public: + typedef std::vector<Window*> Windows; + + explicit Window(WindowDelegate* delegate); + virtual ~Window(); + + // Initializes the window. This creates the window's layer. + void Init(ui::LayerType layer_type); + + // Creates a new layer for the window. Erases the layer-owned bounds, so the + // caller may wish to set new bounds and other state on the window/layer. + // Returns the old layer, which can be used for animations. Caller owns the + // memory for the returned layer and must delete it when animation completes. + // Returns NULL and does not recreate layer if window does not own its layer. + ui::Layer* RecreateLayer() WARN_UNUSED_RESULT; + + void set_owned_by_parent(bool owned_by_parent) { + owned_by_parent_ = owned_by_parent; + } + + // A type is used to identify a class of Windows and customize behavior such + // as event handling and parenting. This field should only be consumed by the + // shell -- Aura itself shouldn't contain type-specific logic. + client::WindowType type() const { return type_; } + void SetType(client::WindowType type); + + int id() const { return id_; } + void set_id(int id) { id_ = id; } + + const std::string& name() const { return name_; } + void SetName(const std::string& name); + + const base::string16 title() const { return title_; } + void set_title(const base::string16& title) { title_ = title; } + + bool transparent() const { return transparent_; } + void SetTransparent(bool transparent); + + WindowDelegate* delegate() { return delegate_; } + + const gfx::Rect& bounds() const; + + Window* parent() { return parent_; } + const Window* parent() const { return parent_; } + + // Returns the RootWindow that contains this Window or NULL if the Window is + // not contained by a RootWindow. + virtual RootWindow* GetRootWindow(); + virtual const RootWindow* GetRootWindow() const; + + // The Window does not own this object. + void set_user_data(void* user_data) { user_data_ = user_data; } + void* user_data() const { return user_data_; } + + // Changes the visibility of the window. + void Show(); + void Hide(); + // Returns true if this window and all its ancestors are visible. + bool IsVisible() const; + // Returns the visibility requested by this window. IsVisible() takes into + // account the visibility of the layer and ancestors, where as this tracks + // whether Show() without a Hide() has been invoked. + bool TargetVisibility() const { return visible_; } + + // Returns the window's bounds in root window's coordinates. + gfx::Rect GetBoundsInRootWindow() const; + + // Returns the window's bounds in screen coordinates. + // How the root window's coordinates is mapped to screen's coordinates + // is platform dependent and defined in the implementation of the + // |aura::client::ScreenPositionClient| interface. + gfx::Rect GetBoundsInScreen() const; + + virtual void SetTransform(const gfx::Transform& transform); + + // Assigns a LayoutManager to size and place child windows. + // The Window takes ownership of the LayoutManager. + void SetLayoutManager(LayoutManager* layout_manager); + LayoutManager* layout_manager() { return layout_manager_.get(); } + + // Changes the bounds of the window. If present, the window's parent's + // LayoutManager may adjust the bounds. + void SetBounds(const gfx::Rect& new_bounds); + + // Changes the bounds of the window in the screen coordintates. + // If present, the window's parent's LayoutManager may adjust the bounds. + void SetBoundsInScreen(const gfx::Rect& new_bounds_in_screen_coords, + const gfx::Display& dst_display); + + // Returns the target bounds of the window. If the window's layer is + // not animating, it simply returns the current bounds. + gfx::Rect GetTargetBounds() const; + + // Marks the a portion of window as needing to be painted. + void SchedulePaintInRect(const gfx::Rect& rect); + + // Places this window per |root_window|'s stacking client. The final location + // may be a RootWindow other than the one passed in. |root_window| may not be + // NULL. |bounds_in_screen| may be empty; it is more optional context that + // may, but isn't necessarily used. + void SetDefaultParentByRootWindow(RootWindow* root_window, + const gfx::Rect& bounds_in_screen); + + // Stacks the specified child of this Window at the front of the z-order. + void StackChildAtTop(Window* child); + + // Stacks |child| above |target|. Does nothing if |child| is already above + // |target|. Does not stack on top of windows with NULL layer delegates, + // see WindowTest.StackingMadrigal for details. + void StackChildAbove(Window* child, Window* target); + + // Stacks the specified child of this window at the bottom of the z-order. + void StackChildAtBottom(Window* child); + + // Stacks |child| below |target|. Does nothing if |child| is already below + // |target|. + void StackChildBelow(Window* child, Window* target); + + // Tree operations. + void AddChild(Window* child); + void RemoveChild(Window* child); + + const Windows& children() const { return children_; } + + // Returns true if this Window contains |other| somewhere in its children. + bool Contains(const Window* other) const; + + // Adds or removes |child| as a transient child of this window. Transient + // children get the following behavior: + // . The transient parent destroys any transient children when it is + // destroyed. This means a transient child is destroyed if either its parent + // or transient parent is destroyed. + // . If a transient child and its transient parent share the same parent, then + // transient children are always ordered above the transient parent. + // Transient windows are typically used for popups and menus. + void AddTransientChild(Window* child); + void RemoveTransientChild(Window* child); + + const Windows& transient_children() const { return transient_children_; } + + Window* transient_parent() { return transient_parent_; } + const Window* transient_parent() const { return transient_parent_; } + + // Retrieves the first-level child with the specified id, or NULL if no first- + // level child is found matching |id|. + Window* GetChildById(int id); + const Window* GetChildById(int id) const; + + // Converts |point| from |source|'s coordinates to |target|'s. If |source| is + // NULL, the function returns without modifying |point|. |target| cannot be + // NULL. + static void ConvertPointToTarget(const Window* source, + const Window* target, + gfx::Point* point); + + // Moves the cursor to the specified location relative to the window. + virtual void MoveCursorTo(const gfx::Point& point_in_window); + + // Returns the cursor for the specified point, in window coordinates. + gfx::NativeCursor GetCursor(const gfx::Point& point) const; + + // Sets an 'event filter' for the window. An 'event filter' for a Window is + // a pre-target event handler, where the window owns the handler. A window + // can have only one such event filter. Setting a new filter removes and + // destroys any previously installed filter. + void SetEventFilter(ui::EventHandler* event_filter); + + // Add/remove observer. + void AddObserver(WindowObserver* observer); + void RemoveObserver(WindowObserver* observer); + bool HasObserver(WindowObserver* observer); + + void set_ignore_events(bool ignore_events) { ignore_events_ = ignore_events; } + + // Sets the window to grab hits for mouse and touch to an area extending + // -|mouse_insets| and -|touch_insets| pixels outside its bounds. This can be + // used to create an invisible non-client area, for example if your windows + // have no visible frames but still need to have resize edges. + void SetHitTestBoundsOverrideOuter(const gfx::Insets& mouse_insets, + const gfx::Insets& touch_insets) { + hit_test_bounds_override_outer_mouse_ = mouse_insets; + hit_test_bounds_override_outer_touch_ = touch_insets; + } + + gfx::Insets hit_test_bounds_override_outer_touch() const { + return hit_test_bounds_override_outer_touch_; + } + + gfx::Insets hit_test_bounds_override_outer_mouse() const { + return hit_test_bounds_override_outer_mouse_; + } + + // Sets the window to grab hits for an area extending |insets| pixels inside + // its bounds (even if that inner region overlaps a child window). This can be + // used to create an invisible non-client area that overlaps the client area. + void set_hit_test_bounds_override_inner(const gfx::Insets& insets) { + hit_test_bounds_override_inner_ = insets; + } + gfx::Insets hit_test_bounds_override_inner() const { + return hit_test_bounds_override_inner_; + } + + // Returns true if the |point_in_root| in root window's coordinate falls + // within this window's bounds. Returns false if the window is detached + // from root window. + bool ContainsPointInRoot(const gfx::Point& point_in_root) const; + + // Returns true if relative-to-this-Window's-origin |local_point| falls + // within this Window's bounds. + bool ContainsPoint(const gfx::Point& local_point) const; + + // Returns true if the mouse pointer at relative-to-this-Window's-origin + // |local_point| can trigger an event for this Window. + // TODO(beng): A Window can supply a hit-test mask to cause some portions of + // itself to not trigger events, causing the events to fall through to the + // Window behind. + bool HitTest(const gfx::Point& local_point); + + // Returns the Window that most closely encloses |local_point| for the + // purposes of event targeting. + Window* GetEventHandlerForPoint(const gfx::Point& local_point); + + // Returns the topmost Window with a delegate containing |local_point|. + Window* GetTopWindowContainingPoint(const gfx::Point& local_point); + + // Returns this window's toplevel window (the highest-up-the-tree anscestor + // that has a delegate set). The toplevel window may be |this|. + Window* GetToplevelWindow(); + + // Claims or relinquishes the claim to focus. + void Focus(); + void Blur(); + + // Returns true if the Window is currently the focused window. + bool HasFocus() const; + + // Returns true if the Window can be focused. + virtual bool CanFocus() const; + + // Returns true if the Window can receive events. + virtual bool CanReceiveEvents() const; + + // Does a capture on the window. This does nothing if the window isn't showing + // (VISIBILITY_SHOWN) or isn't contained in a valid window hierarchy. + void SetCapture(); + + // Releases a capture. + void ReleaseCapture(); + + // Returns true if this window has capture. + bool HasCapture(); + + // Suppresses painting window content by disgarding damaged rect and ignoring + // new paint requests. + void SuppressPaint(); + + // Sets the |value| of the given window |property|. Setting to the default + // value (e.g., NULL) removes the property. The caller is responsible for the + // lifetime of any object set as a property on the Window. + template<typename T> + void SetProperty(const WindowProperty<T>* property, T value); + + // Returns the value of the given window |property|. Returns the + // property-specific default value if the property was not previously set. + template<typename T> + T GetProperty(const WindowProperty<T>* property) const; + + // Sets the |property| to its default value. Useful for avoiding a cast when + // setting to NULL. + template<typename T> + void ClearProperty(const WindowProperty<T>* property); + + // NativeWidget::[GS]etNativeWindowProperty use strings as keys, and this is + // difficult to change while retaining compatibility with other platforms. + // TODO(benrg): Find a better solution. + void SetNativeWindowProperty(const char* key, void* value); + void* GetNativeWindowProperty(const char* key) const; + + // Type of a function to delete a property that this window owns. + typedef void (*PropertyDeallocator)(int64 value); + + // Overridden from ui::LayerDelegate: + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE; + +#ifndef NDEBUG + // These methods are useful when debugging. + std::string GetDebugInfo() const; + void PrintWindowHierarchy(int depth) const; +#endif + + private: + friend class test::WindowTestApi; + friend class LayoutManager; + + // Used when stacking windows. + enum StackDirection { + STACK_ABOVE, + STACK_BELOW + }; + + // Called by the public {Set,Get,Clear}Property functions. + int64 SetPropertyInternal(const void* key, + const char* name, + PropertyDeallocator deallocator, + int64 value, + int64 default_value); + int64 GetPropertyInternal(const void* key, int64 default_value) const; + + // Changes the bounds of the window without condition. + void SetBoundsInternal(const gfx::Rect& new_bounds); + + // Updates the visible state of the layer, but does not make visible-state + // specific changes. Called from Show()/Hide(). + void SetVisible(bool visible); + + // Schedules a paint for the Window's entire bounds. + void SchedulePaint(); + + // Gets a Window (either this one or a subwindow) containing |local_point|. + // If |return_tightest| is true, returns the tightest-containing (i.e. + // furthest down the hierarchy) Window containing the point; otherwise, + // returns the loosest. If |for_event_handling| is true, then hit-test masks + // are honored; otherwise, only bounds checks are performed. + Window* GetWindowForPoint(const gfx::Point& local_point, + bool return_tightest, + bool for_event_handling); + + // Implementation of RemoveChild(). If |child| is being removed as the result + // of an add, |new_parent| is the new parent |child| is going to be parented + // to. + void RemoveChildImpl(Window* child, Window* new_parent); + + // Called when this window's parent has changed. + void OnParentChanged(); + + // Determines the real location for stacking |child| and invokes + // StackChildRelativeToImpl(). + void StackChildRelativeTo(Window* child, + Window* target, + StackDirection direction); + + // Implementation of StackChildRelativeTo(). + void StackChildRelativeToImpl(Window* child, + Window* target, + StackDirection direction); + + // Called when this window's stacking order among its siblings is changed. + void OnStackingChanged(); + + // Notifies observers registered with this Window (and its subtree) when the + // Window has been added or is about to be removed from a RootWindow. + void NotifyRemovingFromRootWindow(); + void NotifyAddedToRootWindow(); + + // Methods implementing hierarchy change notifications. See WindowObserver for + // more details. + void NotifyWindowHierarchyChange( + const WindowObserver::HierarchyChangeParams& params); + // Notifies this window and its child hierarchy. + void NotifyWindowHierarchyChangeDown( + const WindowObserver::HierarchyChangeParams& params); + // Notifies this window and its parent hierarchy. + void NotifyWindowHierarchyChangeUp( + const WindowObserver::HierarchyChangeParams& params); + // Notifies this window's observers. + void NotifyWindowHierarchyChangeAtReceiver( + const WindowObserver::HierarchyChangeParams& params); + + // Methods implementing visibility change notifications. See WindowObserver + // for more details. + void NotifyWindowVisibilityChanged(aura::Window* target, bool visible); + // Notifies this window's observers. Returns false if |this| was deleted + // during the call (by an observer), otherwise true. + bool NotifyWindowVisibilityChangedAtReceiver(aura::Window* target, + bool visible); + // Notifies this window and its child hierarchy. Returns false if + // |this| was deleted during the call (by an observer), otherwise + // true. + bool NotifyWindowVisibilityChangedDown(aura::Window* target, bool visible); + // Notifies this window and its parent hierarchy. + void NotifyWindowVisibilityChangedUp(aura::Window* target, bool visible); + + // Invoked from the closure returned by PrepareForLayerBoundsChange() after + // the bounds of the layer has changed. |old_bounds| is the previous bounds of + // the layer, and |contained_mouse| is true if the mouse was previously within + // the window's bounds. + void OnLayerBoundsChanged(const gfx::Rect& old_bounds, bool contained_mouse); + + // Overridden from ui::LayerDelegate: + virtual void OnPaintLayer(gfx::Canvas* canvas) OVERRIDE; + virtual base::Closure PrepareForLayerBoundsChange() OVERRIDE; + + // Overridden from ui::EventTarget: + virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE; + virtual EventTarget* GetParentTarget() OVERRIDE; + + // Updates the layer name with a name based on the window's name and id. + void UpdateLayerName(const std::string& name); + + // Returns true if the mouse is currently within our bounds. + bool ContainsMouse(); + + client::WindowType type_; + + // True if the Window is owned by its parent - i.e. it will be deleted by its + // parent during its parents destruction. True is the default. + bool owned_by_parent_; + + WindowDelegate* delegate_; + + // The Window's parent. + Window* parent_; + + // Child windows. Topmost is last. + Windows children_; + + // Transient windows. + Windows transient_children_; + + Window* transient_parent_; + + // The visibility state of the window as set by Show()/Hide(). This may differ + // from the visibility of the underlying layer, which may remain visible after + // the window is hidden (e.g. to animate its disappearance). + bool visible_; + + int id_; + std::string name_; + + base::string16 title_; + + // Whether layer is initialized as non-opaque. + bool transparent_; + + scoped_ptr<ui::EventHandler> event_filter_; + scoped_ptr<LayoutManager> layout_manager_; + + void* user_data_; + + // Makes the window pass all events through to any windows behind it. + bool ignore_events_; + + // See set_hit_test_outer_override(). + gfx::Insets hit_test_bounds_override_outer_mouse_; + gfx::Insets hit_test_bounds_override_outer_touch_; + gfx::Insets hit_test_bounds_override_inner_; + + ObserverList<WindowObserver> observers_; + + // Value struct to keep the name and deallocator for this property. + // Key cannot be used for this purpose because it can be char* or + // WindowProperty<>. + struct Value { + const char* name; + int64 value; + PropertyDeallocator deallocator; + }; + + std::map<const void*, Value> prop_map_; + + DISALLOW_COPY_AND_ASSIGN(Window); +}; + +} // namespace aura + +#endif // UI_AURA_WINDOW_H_ diff --git a/chromium/ui/aura/window_delegate.h b/chromium/ui/aura/window_delegate.h new file mode 100644 index 00000000000..bcbd2a6984c --- /dev/null +++ b/chromium/ui/aura/window_delegate.h @@ -0,0 +1,108 @@ +// 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. + +#ifndef UI_AURA_WINDOW_DELEGATE_H_ +#define UI_AURA_WINDOW_DELEGATE_H_ + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "ui/aura/aura_export.h" +#include "ui/base/events/event_constants.h" +#include "ui/base/events/event_handler.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Canvas; +class Path; +class Point; +class Rect; +class Size; +} + +namespace ui { +class GestureEvent; +class KeyEvent; +class MouseEvent; +class Texture; +class TouchEvent; +} + +namespace aura { + +// Delegate interface for aura::Window. +class AURA_EXPORT WindowDelegate : public ui::EventHandler { + public: + // Returns the window's minimum size, or size 0,0 if there is no limit. + virtual gfx::Size GetMinimumSize() const = 0; + + // Returns the window's maximum size, or size 0,0 if there is no limit. + virtual gfx::Size GetMaximumSize() const = 0; + + // Called when the Window's position and/or size changes. + virtual void OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) = 0; + + // Returns the native cursor for the specified point, in window coordinates, + // or NULL for the default cursor. + virtual gfx::NativeCursor GetCursor(const gfx::Point& point) = 0; + + // Returns the non-client component (see hit_test.h) containing |point|, in + // window coordinates. + virtual int GetNonClientComponent(const gfx::Point& point) const = 0; + + // Returns true if event handling should descend into |child|. |location| is + // in terms of the Window. + virtual bool ShouldDescendIntoChildForEventHandling( + Window* child, + const gfx::Point& location) = 0; + + // Returns true of the window can be focused. + virtual bool CanFocus() = 0; + + // Invoked when mouse capture is lost on the window. + virtual void OnCaptureLost() = 0; + + // Asks the delegate to paint window contents into the supplied canvas. + virtual void OnPaint(gfx::Canvas* canvas) = 0; + + // Called when the window's device scale factor has changed. + virtual void OnDeviceScaleFactorChanged(float device_scale_factor) = 0; + + // Called from Window's destructor before OnWindowDestroyed and before the + // children have been destroyed and the window has been removed from its + // parent. + virtual void OnWindowDestroying() = 0; + + // Called when the Window has been destroyed (i.e. from its destructor). This + // is called after OnWindowDestroying and after the children have been + // deleted and the window has been removed from its parent. + // The delegate can use this as an opportunity to delete itself if necessary. + virtual void OnWindowDestroyed() = 0; + + // Called when the TargetVisibility() of a Window changes. |visible| + // corresponds to the target visibility of the window. See + // Window::TargetVisibility() for details. + virtual void OnWindowTargetVisibilityChanged(bool visible) = 0; + + // Called from Window::HitTest to check if the window has a custom hit test + // mask. It works similar to the views counterparts. That is, if the function + // returns true, GetHitTestMask below will be called to get the mask. + // Otherwise, Window will hit-test against its bounds. + virtual bool HasHitTestMask() const = 0; + + // Called from Window::HitTest to retrieve hit test mask when HasHitTestMask + // above returns true. + virtual void GetHitTestMask(gfx::Path* mask) const = 0; + + // Called from RecreateLayer() if the layer the window is associated with has + // an external texture. + virtual scoped_refptr<ui::Texture> CopyTexture() = 0; + + protected: + virtual ~WindowDelegate() {} +}; + +} // namespace aura + +#endif // UI_AURA_WINDOW_DELEGATE_H_ diff --git a/chromium/ui/aura/window_observer.h b/chromium/ui/aura/window_observer.h new file mode 100644 index 00000000000..5008ac514d5 --- /dev/null +++ b/chromium/ui/aura/window_observer.h @@ -0,0 +1,112 @@ +// 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. + +#ifndef UI_AURA_WINDOW_OBSERVER_H_ +#define UI_AURA_WINDOW_OBSERVER_H_ + +#include "base/basictypes.h" +#include "ui/aura/aura_export.h" + +namespace gfx { +class Rect; +} // namespace gfx + +namespace aura { + +class Window; + +class AURA_EXPORT WindowObserver { + public: + struct HierarchyChangeParams { + enum HierarchyChangePhase { + HIERARCHY_CHANGING, + HIERARCHY_CHANGED + }; + + Window* target; // The window that was added or removed. + Window* new_parent; + Window* old_parent; + HierarchyChangePhase phase; + Window* receiver; // The window receiving the notification. + }; + + // Called when a window is added or removed. Notifications are sent to the + // following hierarchies in this order: + // 1. |target|. + // 2. |target|'s child hierarchy. + // 3. |target|'s parent hierarchy in its |old_parent| + // (only for Changing notifications). + // 3. |target|'s parent hierarchy in its |new_parent|. + // (only for Changed notifications). + // This sequence is performed via the Changing and Changed notifications below + // before and after the change is committed. + virtual void OnWindowHierarchyChanging(const HierarchyChangeParams& params) {} + virtual void OnWindowHierarchyChanged(const HierarchyChangeParams& params) {} + + // Invoked when |new_window| has been added as a child of this window. + virtual void OnWindowAdded(Window* new_window) {} + + // Invoked prior to removing |window| as a child of this window. + virtual void OnWillRemoveWindow(Window* window) {} + + // Invoked when this window's parent window changes. |parent| may be NULL. + virtual void OnWindowParentChanged(Window* window, Window* parent) {} + + // Invoked when SetProperty(), ClearProperty(), or + // NativeWidgetAura::SetNativeWindowProperty() is called on the window. + // |key| is either a WindowProperty<T>* (SetProperty, ClearProperty) + // or a const char* (SetNativeWindowProperty). Either way, it can simply be + // compared for equality with the property constant. |old| is the old property + // value, which must be cast to the appropriate type before use. + virtual void OnWindowPropertyChanged(Window* window, + const void* key, + intptr_t old) {} + + // Invoked when SetVisible() is invoked on a window. |visible| is the + // value supplied to SetVisible(). If |visible| is true, window->IsVisible() + // may still return false. See description in Window::IsVisible() for details. + virtual void OnWindowVisibilityChanging(Window* window, bool visible) {} + virtual void OnWindowVisibilityChanged(Window* window, bool visible) {} + + // Invoked when SetBounds() is invoked on |window|. |old_bounds| and + // |new_bounds| are in parent coordinates. + virtual void OnWindowBoundsChanged(Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) {} + + // Invoked when |window|'s position among its siblings in the stacking order + // has changed. + virtual void OnWindowStackingChanged(Window* window) {} + + // Invoked when a region of |window| is scheduled to be redrawn. + virtual void OnWindowPaintScheduled(Window* window, + const gfx::Rect& region) {} + + // Invoked when the Window is being destroyed (i.e. from the start of its + // destructor). This is called before the window is removed from its parent. + virtual void OnWindowDestroying(Window* window) {} + + // Invoked when the Window has been destroyed (i.e. at the end of its + // destructor). This is called after the window is removed from its parent. + virtual void OnWindowDestroyed(Window* window) {} + + // Called when a Window has been added to a RootWindow. + virtual void OnWindowAddedToRootWindow(Window* window) {} + + // Called when a Window is about to be removed from a RootWindow. + virtual void OnWindowRemovingFromRootWindow(Window* window) {} + + // Called when a transient child is added to |window|. + virtual void OnAddTransientChild(Window* window, Window* transient) {} + + // Called when a transient child is removed from |window|. + virtual void OnRemoveTransientChild(Window* window, Window* transient) {} + + protected: + virtual ~WindowObserver() {} +}; + +} // namespace aura + +#endif // UI_AURA_WINDOW_OBSERVER_H_ diff --git a/chromium/ui/aura/window_property.h b/chromium/ui/aura/window_property.h new file mode 100644 index 00000000000..0b1574e4f36 --- /dev/null +++ b/chromium/ui/aura/window_property.h @@ -0,0 +1,144 @@ +// 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. + +#ifndef UI_AURA_WINDOW_PROPERTY_H_ +#define UI_AURA_WINDOW_PROPERTY_H_ + +#include "base/basictypes.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/window.h" + +// This header should be included by code that defines WindowProperties. It +// should not be included by code that only gets and sets WindowProperties. +// +// To define a new WindowProperty: +// +// #include "foo/foo_export.h" +// #include "ui/aura/window_property.h" +// +// DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(FOO_EXPORT, MyType); +// namespace foo { +// // Use this to define an exported property that is premitive, +// // or a pointer you don't want automatically deleted. +// DEFINE_WINDOW_PROPERTY_KEY(MyType, kMyKey, MyDefault); +// +// // Use this to define an exported property whose value is a heap +// // allocated object, and has to be owned and freed by the window. +// DEFINE_OWNED_WINDOW_PROPERTY_KEY(gfx::Rect, kRestoreBoundsKey, NULL); +// +// // Use this to define a non exported property that is primitive, +// // or a pointer you don't want to automatically deleted, and is used +// // only in a specific file. This will define the property in an unnamed +// // namespace which cannot be accessed from another file. +// DEFINE_LOCAL_WINDOW_PROPERTY_KEY(MyType, kMyKey, MyDefault); +// +// } // foo namespace +// +// To define a new type used for WindowProperty. +// +// // outside all namespaces: +// DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(FOO_EXPORT, MyType) +// +// If a property type is not exported, use DECLARE_WINDOW_PROPERTY_TYPE(MyType) +// which is a shorthand for DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(, MyType). + +namespace aura { +namespace { + +// No single new-style cast works for every conversion to/from int64, so we +// need this helper class. A third specialization is needed for bool because +// MSVC warning C4800 (forcing value to bool) is not suppressed by an explicit +// cast (!). +template<typename T> +class WindowPropertyCaster { + public: + static int64 ToInt64(T x) { return static_cast<int64>(x); } + static T FromInt64(int64 x) { return static_cast<T>(x); } +}; +template<typename T> +class WindowPropertyCaster<T*> { + public: + static int64 ToInt64(T* x) { return reinterpret_cast<int64>(x); } + static T* FromInt64(int64 x) { return reinterpret_cast<T*>(x); } +}; +template<> +class WindowPropertyCaster<bool> { + public: + static int64 ToInt64(bool x) { return static_cast<int64>(x); } + static bool FromInt64(int64 x) { return x != 0; } +}; + +} // namespace + +template<typename T> +struct WindowProperty { + T default_value; + const char* name; + Window::PropertyDeallocator deallocator; +}; + +template<typename T> +void Window::SetProperty(const WindowProperty<T>* property, T value) { + int64 old = SetPropertyInternal( + property, + property->name, + value == property->default_value ? NULL : property->deallocator, + WindowPropertyCaster<T>::ToInt64(value), + WindowPropertyCaster<T>::ToInt64(property->default_value)); + if (property->deallocator && + old != WindowPropertyCaster<T>::ToInt64(property->default_value)) { + (*property->deallocator)(old); + } +} + +template<typename T> +T Window::GetProperty(const WindowProperty<T>* property) const { + return WindowPropertyCaster<T>::FromInt64(GetPropertyInternal( + property, WindowPropertyCaster<T>::ToInt64(property->default_value))); +} + +template<typename T> +void Window::ClearProperty(const WindowProperty<T>* property) { + SetProperty(property, property->default_value); +} + +} // namespace aura + +// Macros to instantiate the property getter/setter template functions. +#define DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(EXPORT, T) \ + template EXPORT void aura::Window::SetProperty( \ + const aura::WindowProperty<T >*, T); \ + template EXPORT T aura::Window::GetProperty( \ + const aura::WindowProperty<T >*) const; \ + template EXPORT void aura::Window::ClearProperty( \ + const aura::WindowProperty<T >*); +#define DECLARE_WINDOW_PROPERTY_TYPE(T) \ + DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(, T) + +#define DEFINE_WINDOW_PROPERTY_KEY(TYPE, NAME, DEFAULT) \ + COMPILE_ASSERT(sizeof(TYPE) <= sizeof(int64), property_type_too_large); \ + namespace { \ + const aura::WindowProperty<TYPE> NAME ## _Value = {DEFAULT, #NAME, NULL}; \ + } \ + const aura::WindowProperty<TYPE>* const NAME = & NAME ## _Value; + +#define DEFINE_LOCAL_WINDOW_PROPERTY_KEY(TYPE, NAME, DEFAULT) \ + COMPILE_ASSERT(sizeof(TYPE) <= sizeof(int64), property_type_too_large); \ + namespace { \ + const aura::WindowProperty<TYPE> NAME ## _Value = {DEFAULT, #NAME, NULL}; \ + const aura::WindowProperty<TYPE>* const NAME = & NAME ## _Value; \ + } + +#define DEFINE_OWNED_WINDOW_PROPERTY_KEY(TYPE, NAME, DEFAULT) \ + namespace { \ + void Deallocator ## NAME (int64 p) { \ + enum { type_must_be_complete = sizeof(TYPE) }; \ + delete aura::WindowPropertyCaster<TYPE*>::FromInt64(p); \ + } \ + const aura::WindowProperty<TYPE*> NAME ## _Value = \ + {DEFAULT,#NAME,&Deallocator ## NAME}; \ + } \ + const aura::WindowProperty<TYPE*>* const NAME = & NAME ## _Value; + +#endif // UI_AURA_WINDOW_PROPERTY_H_ diff --git a/chromium/ui/aura/window_tracker.cc b/chromium/ui/aura/window_tracker.cc new file mode 100644 index 00000000000..9653e5fcf9e --- /dev/null +++ b/chromium/ui/aura/window_tracker.cc @@ -0,0 +1,43 @@ +// 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 "ui/aura/window_tracker.h" + +#include "ui/aura/window.h" + +namespace aura { + +WindowTracker::WindowTracker() { +} + +WindowTracker::~WindowTracker() { + for (Windows::iterator i = windows_.begin(); i != windows_.end(); ++i) + (*i)->RemoveObserver(this); +} + +void WindowTracker::Add(Window* window) { + if (windows_.count(window)) + return; + + window->AddObserver(this); + windows_.insert(window); +} + +void WindowTracker::Remove(Window* window) { + if (windows_.count(window)) { + windows_.erase(window); + window->RemoveObserver(this); + } +} + +bool WindowTracker::Contains(Window* window) { + return windows_.count(window) > 0; +} + +void WindowTracker::OnWindowDestroying(Window* window) { + DCHECK_GT(windows_.count(window), 0u); + Remove(window); +} + +} // namespace aura diff --git a/chromium/ui/aura/window_tracker.h b/chromium/ui/aura/window_tracker.h new file mode 100644 index 00000000000..960549a0474 --- /dev/null +++ b/chromium/ui/aura/window_tracker.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef UI_AURA_WINDOW_LIFE_TRACKER_H_ +#define UI_AURA_WINDOW_LIFE_TRACKER_H_ + +#include <set> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "ui/aura/aura_export.h" +#include "ui/aura/window_observer.h" + +namespace aura { + +// This class keeps track of a set of windows. A Window is removed either +// explicitly by Remove(), or implicitly when the window is destroyed. +class AURA_EXPORT WindowTracker : public WindowObserver { + public: + typedef std::set<Window*> Windows; + + WindowTracker(); + virtual ~WindowTracker(); + + // Returns the set of windows being observed. + const std::set<Window*>& windows() const { return windows_; } + + // Adds |window| to the set of Windows being tracked. + void Add(Window* window); + + // Removes |window| from the set of windows being tracked. + void Remove(Window* window); + + // Returns true if |window| was previously added and has not been removed or + // deleted. + bool Contains(Window* window); + + // WindowObserver overrides: + virtual void OnWindowDestroying(Window* window) OVERRIDE; + + private: + Windows windows_; + + DISALLOW_COPY_AND_ASSIGN(WindowTracker); +}; + +} // namespace aura + +#endif // UI_AURA_WINDOW_LIFE_TRACKER_H_ diff --git a/chromium/ui/aura/window_unittest.cc b/chromium/ui/aura/window_unittest.cc new file mode 100644 index 00000000000..320d1a61f7b --- /dev/null +++ b/chromium/ui/aura/window_unittest.cc @@ -0,0 +1,3054 @@ +// 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 "ui/aura/window.h" + +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/aura/client/capture_client.h" +#include "ui/aura/client/focus_change_observer.h" +#include "ui/aura/client/stacking_client.h" +#include "ui/aura/client/visibility_client.h" +#include "ui/aura/layout_manager.h" +#include "ui/aura/root_window.h" +#include "ui/aura/root_window_host.h" +#include "ui/aura/root_window_observer.h" +#include "ui/aura/test/aura_test_base.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/test/window_test_api.h" +#include "ui/aura/window_delegate.h" +#include "ui/aura/window_observer.h" +#include "ui/aura/window_property.h" +#include "ui/base/events/event.h" +#include "ui/base/events/event_utils.h" +#include "ui/base/gestures/gesture_configuration.h" +#include "ui/base/hit_test.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/scoped_animation_duration_scale_mode.h" +#include "ui/compositor/scoped_layer_animation_settings.h" +#include "ui/compositor/test/test_layers.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/screen.h" + +DECLARE_WINDOW_PROPERTY_TYPE(const char*) +DECLARE_WINDOW_PROPERTY_TYPE(int) + +namespace aura { +namespace test { + +class WindowTest : public AuraTestBase { + public: + WindowTest() : max_separation_(0) { + } + + virtual void SetUp() OVERRIDE { + AuraTestBase::SetUp(); + // TODO: there needs to be an easier way to do this. + max_separation_ = ui::GestureConfiguration:: + max_separation_for_gesture_touches_in_pixels(); + ui::GestureConfiguration:: + set_max_separation_for_gesture_touches_in_pixels(0); + } + + virtual void TearDown() OVERRIDE { + AuraTestBase::TearDown(); + ui::GestureConfiguration:: + set_max_separation_for_gesture_touches_in_pixels(max_separation_); + } + + // Adds |window| to |root_window_|, through the StackingClient. + void SetDefaultParentByPrimaryRootWindow(aura::Window* window) { + window->SetDefaultParentByRootWindow(root_window(), gfx::Rect()); + } + + private: + int max_separation_; + + DISALLOW_COPY_AND_ASSIGN(WindowTest); +}; + +namespace { + +// Used for verifying destruction methods are invoked. +class DestroyTrackingDelegateImpl : public TestWindowDelegate { + public: + DestroyTrackingDelegateImpl() + : destroying_count_(0), + destroyed_count_(0), + in_destroying_(false) {} + + void clear_destroying_count() { destroying_count_ = 0; } + int destroying_count() const { return destroying_count_; } + + void clear_destroyed_count() { destroyed_count_ = 0; } + int destroyed_count() const { return destroyed_count_; } + + bool in_destroying() const { return in_destroying_; } + + virtual void OnWindowDestroying() OVERRIDE { + EXPECT_FALSE(in_destroying_); + in_destroying_ = true; + destroying_count_++; + } + + virtual void OnWindowDestroyed() OVERRIDE { + EXPECT_TRUE(in_destroying_); + in_destroying_ = false; + destroyed_count_++; + } + + private: + int destroying_count_; + int destroyed_count_; + bool in_destroying_; + + DISALLOW_COPY_AND_ASSIGN(DestroyTrackingDelegateImpl); +}; + +// Used to verify that when OnWindowDestroying is invoked the parent is also +// is in the process of being destroyed. +class ChildWindowDelegateImpl : public DestroyTrackingDelegateImpl { + public: + explicit ChildWindowDelegateImpl( + DestroyTrackingDelegateImpl* parent_delegate) + : parent_delegate_(parent_delegate) { + } + + virtual void OnWindowDestroying() OVERRIDE { + EXPECT_TRUE(parent_delegate_->in_destroying()); + DestroyTrackingDelegateImpl::OnWindowDestroying(); + } + + private: + DestroyTrackingDelegateImpl* parent_delegate_; + + DISALLOW_COPY_AND_ASSIGN(ChildWindowDelegateImpl); +}; + +// Used to verify that a Window is removed from its parent when +// OnWindowDestroyed is called. +class DestroyOrphanDelegate : public TestWindowDelegate { + public: + DestroyOrphanDelegate() : window_(NULL) { + } + + void set_window(Window* window) { window_ = window; } + + virtual void OnWindowDestroyed() OVERRIDE { + EXPECT_FALSE(window_->parent()); + } + + private: + Window* window_; + DISALLOW_COPY_AND_ASSIGN(DestroyOrphanDelegate); +}; + +// Used in verifying mouse capture. +class CaptureWindowDelegateImpl : public TestWindowDelegate { + public: + CaptureWindowDelegateImpl() { + ResetCounts(); + } + + void ResetCounts() { + capture_changed_event_count_ = 0; + capture_lost_count_ = 0; + mouse_event_count_ = 0; + touch_event_count_ = 0; + gesture_event_count_ = 0; + } + + int capture_changed_event_count() const { + return capture_changed_event_count_; + } + int capture_lost_count() const { return capture_lost_count_; } + int mouse_event_count() const { return mouse_event_count_; } + int touch_event_count() const { return touch_event_count_; } + int gesture_event_count() const { return gesture_event_count_; } + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED) + capture_changed_event_count_++; + mouse_event_count_++; + } + virtual void OnTouchEvent(ui::TouchEvent* event) OVERRIDE { + touch_event_count_++; + } + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { + gesture_event_count_++; + } + virtual void OnCaptureLost() OVERRIDE { + capture_lost_count_++; + } + + private: + int capture_changed_event_count_; + int capture_lost_count_; + int mouse_event_count_; + int touch_event_count_; + int gesture_event_count_; + + DISALLOW_COPY_AND_ASSIGN(CaptureWindowDelegateImpl); +}; + +// aura::WindowDelegate that tracks the window that was reported as having the +// focus before us. +class FocusDelegate : public TestWindowDelegate, + public aura::client::FocusChangeObserver { + public: + FocusDelegate() : previous_focused_window_(NULL) { + } + + aura::Window* previous_focused_window() const { + return previous_focused_window_; + } + + // Overridden from client::FocusChangeObserver: + virtual void OnWindowFocused(Window* gained_focus, + Window* lost_focus) OVERRIDE { + previous_focused_window_ = lost_focus; + } + + private: + aura::Window* previous_focused_window_; + + DISALLOW_COPY_AND_ASSIGN(FocusDelegate); +}; + +// Keeps track of the location of the gesture. +class GestureTrackPositionDelegate : public TestWindowDelegate { + public: + GestureTrackPositionDelegate() {} + + virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE { + position_ = event->location(); + event->StopPropagation(); + } + + const gfx::Point& position() const { return position_; } + + private: + gfx::Point position_; + + DISALLOW_COPY_AND_ASSIGN(GestureTrackPositionDelegate); +}; + +base::TimeDelta getTime() { + return ui::EventTimeForNow(); +} + +class SelfEventHandlingWindowDelegate : public TestWindowDelegate { + public: + SelfEventHandlingWindowDelegate() {} + + virtual bool ShouldDescendIntoChildForEventHandling( + Window* child, + const gfx::Point& location) OVERRIDE { + return false; + } + + private: + DISALLOW_COPY_AND_ASSIGN(SelfEventHandlingWindowDelegate); +}; + +// The delegate deletes itself when the window is being destroyed. +class DestroyWindowDelegate : public TestWindowDelegate { + public: + DestroyWindowDelegate() {} + + private: + virtual ~DestroyWindowDelegate() {} + + // Overridden from WindowDelegate. + virtual void OnWindowDestroyed() OVERRIDE { + delete this; + } + + DISALLOW_COPY_AND_ASSIGN(DestroyWindowDelegate); +}; + +} // namespace + +TEST_F(WindowTest, GetChildById) { + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> w11(CreateTestWindowWithId(11, w1.get())); + scoped_ptr<Window> w111(CreateTestWindowWithId(111, w11.get())); + scoped_ptr<Window> w12(CreateTestWindowWithId(12, w1.get())); + + EXPECT_EQ(NULL, w1->GetChildById(57)); + EXPECT_EQ(w12.get(), w1->GetChildById(12)); + EXPECT_EQ(w111.get(), w1->GetChildById(111)); +} + +// Make sure that Window::Contains correctly handles children, grandchildren, +// and not containing NULL or parents. +TEST_F(WindowTest, Contains) { + Window parent(NULL); + parent.Init(ui::LAYER_NOT_DRAWN); + Window child1(NULL); + child1.Init(ui::LAYER_NOT_DRAWN); + Window child2(NULL); + child2.Init(ui::LAYER_NOT_DRAWN); + + parent.AddChild(&child1); + child1.AddChild(&child2); + + EXPECT_TRUE(parent.Contains(&parent)); + EXPECT_TRUE(parent.Contains(&child1)); + EXPECT_TRUE(parent.Contains(&child2)); + + EXPECT_FALSE(parent.Contains(NULL)); + EXPECT_FALSE(child1.Contains(&parent)); + EXPECT_FALSE(child2.Contains(&child1)); +} + +TEST_F(WindowTest, ContainsPointInRoot) { + scoped_ptr<Window> w( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 5, 5), + root_window())); + EXPECT_FALSE(w->ContainsPointInRoot(gfx::Point(9, 9))); + EXPECT_TRUE(w->ContainsPointInRoot(gfx::Point(10, 10))); + EXPECT_TRUE(w->ContainsPointInRoot(gfx::Point(14, 14))); + EXPECT_FALSE(w->ContainsPointInRoot(gfx::Point(15, 15))); + EXPECT_FALSE(w->ContainsPointInRoot(gfx::Point(20, 20))); +} + +TEST_F(WindowTest, ContainsPoint) { + scoped_ptr<Window> w( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 5, 5), + root_window())); + EXPECT_TRUE(w->ContainsPoint(gfx::Point(0, 0))); + EXPECT_TRUE(w->ContainsPoint(gfx::Point(4, 4))); + EXPECT_FALSE(w->ContainsPoint(gfx::Point(5, 5))); + EXPECT_FALSE(w->ContainsPoint(gfx::Point(10, 10))); +} + +TEST_F(WindowTest, ConvertPointToWindow) { + // Window::ConvertPointToWindow is mostly identical to + // Layer::ConvertPointToLayer, except NULL values for |source| are permitted, + // in which case the function just returns. + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + gfx::Point reference_point(100, 100); + gfx::Point test_point = reference_point; + Window::ConvertPointToTarget(NULL, w1.get(), &test_point); + EXPECT_EQ(reference_point, test_point); +} + +TEST_F(WindowTest, MoveCursorTo) { + scoped_ptr<Window> w1( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), + root_window())); + scoped_ptr<Window> w11( + CreateTestWindow(SK_ColorGREEN, 11, gfx::Rect(5, 5, 100, 100), w1.get())); + scoped_ptr<Window> w111( + CreateTestWindow(SK_ColorCYAN, 111, gfx::Rect(5, 5, 75, 75), w11.get())); + scoped_ptr<Window> w1111( + CreateTestWindow(SK_ColorRED, 1111, gfx::Rect(5, 5, 50, 50), w111.get())); + + RootWindow* root = root_window(); + root->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("10,10", + gfx::Screen::GetScreenFor(root)->GetCursorScreenPoint().ToString()); + w1->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("20,20", + gfx::Screen::GetScreenFor(root)->GetCursorScreenPoint().ToString()); + w11->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("25,25", + gfx::Screen::GetScreenFor(root)->GetCursorScreenPoint().ToString()); + w111->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("30,30", + gfx::Screen::GetScreenFor(root)->GetCursorScreenPoint().ToString()); + w1111->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("35,35", + gfx::Screen::GetScreenFor(root)->GetCursorScreenPoint().ToString()); +} + +TEST_F(WindowTest, ContainsMouse) { + scoped_ptr<Window> w( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), + root_window())); + w->Show(); + WindowTestApi w_test_api(w.get()); + RootWindow* root = root_window(); + root->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_TRUE(w_test_api.ContainsMouse()); + root->MoveCursorTo(gfx::Point(9, 10)); + EXPECT_FALSE(w_test_api.ContainsMouse()); +} + +// Test Window::ConvertPointToWindow() with transform to root_window. +#if defined(USE_OZONE) +// TODO(rjkroege): Add cursor support in ozone: http://crbug.com/252315. +TEST_F(WindowTest, DISABLED_MoveCursorToWithTransformRootWindow) { +#else +TEST_F(WindowTest, MoveCursorToWithTransformRootWindow) { +#endif + RootWindow* root = root_window(); + gfx::Transform transform; + transform.Translate(100.0, 100.0); + transform.Rotate(90.0); + transform.Scale(2.0, 5.0); + root->SetTransform(transform); + root->MoveCursorTo(gfx::Point(10, 10)); +#if !defined(OS_WIN) + gfx::Point mouse_location; + EXPECT_TRUE(root->QueryMouseLocationForTest(&mouse_location)); + // TODO(yoshiki): fix this to build on Windows. See crbug.com/133413.OD + EXPECT_EQ("50,120", mouse_location.ToString()); +#endif + EXPECT_EQ("10,10", + gfx::Screen::GetScreenFor(root)->GetCursorScreenPoint().ToString()); +} + +// Tests Window::ConvertPointToWindow() with transform to non-root windows. +TEST_F(WindowTest, MoveCursorToWithTransformWindow) { + scoped_ptr<Window> w1( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), + root_window())); + + gfx::Transform transform1; + transform1.Scale(2, 2); + w1->SetTransform(transform1); + w1->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("30,30", + gfx::Screen::GetScreenFor(w1.get())->GetCursorScreenPoint().ToString()); + + gfx::Transform transform2; + transform2.Translate(-10, 20); + w1->SetTransform(transform2); + w1->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("10,40", + gfx::Screen::GetScreenFor(w1.get())->GetCursorScreenPoint().ToString()); + + gfx::Transform transform3; + transform3.Rotate(90.0); + w1->SetTransform(transform3); + w1->MoveCursorTo(gfx::Point(5, 5)); + EXPECT_EQ("5,15", + gfx::Screen::GetScreenFor(w1.get())->GetCursorScreenPoint().ToString()); + + gfx::Transform transform4; + transform4.Translate(100.0, 100.0); + transform4.Rotate(90.0); + transform4.Scale(2.0, 5.0); + w1->SetTransform(transform4); + w1->MoveCursorTo(gfx::Point(10, 10)); + EXPECT_EQ("60,130", + gfx::Screen::GetScreenFor(w1.get())->GetCursorScreenPoint().ToString()); +} + +// Test Window::ConvertPointToWindow() with complex transforms to both root and +// non-root windows. +// Test Window::ConvertPointToWindow() with transform to root_window. +#if defined(USE_OZONE) +// TODO(rjkroege): Add cursor support in ozone: http://crbug.com/252315. +TEST_F(WindowTest, DISABLED_MoveCursorToWithComplexTransform) { +#else +TEST_F(WindowTest, MoveCursorToWithComplexTransform) { +#endif + scoped_ptr<Window> w1( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), + root_window())); + scoped_ptr<Window> w11( + CreateTestWindow(SK_ColorGREEN, 11, gfx::Rect(5, 5, 100, 100), w1.get())); + scoped_ptr<Window> w111( + CreateTestWindow(SK_ColorCYAN, 111, gfx::Rect(5, 5, 75, 75), w11.get())); + scoped_ptr<Window> w1111( + CreateTestWindow(SK_ColorRED, 1111, gfx::Rect(5, 5, 50, 50), w111.get())); + + RootWindow* root = root_window(); + + // The root window expects transforms that produce integer rects. + gfx::Transform root_transform; + root_transform.Translate(60.0, 70.0); + root_transform.Rotate(-90.0); + root_transform.Translate(-50.0, -50.0); + root_transform.Scale(2.0, 3.0); + + gfx::Transform transform; + transform.Translate(10.0, 20.0); + transform.Rotate(10.0); + transform.Scale(0.3, 0.5); + root->SetTransform(root_transform); + w1->SetTransform(transform); + w11->SetTransform(transform); + w111->SetTransform(transform); + w1111->SetTransform(transform); + + w1111->MoveCursorTo(gfx::Point(10, 10)); + +#if !defined(OS_WIN) + // TODO(yoshiki): fix this to build on Windows. See crbug.com/133413. + gfx::Point mouse_location; + EXPECT_TRUE(root->QueryMouseLocationForTest(&mouse_location)); + EXPECT_EQ("169,80", mouse_location.ToString()); +#endif + EXPECT_EQ("20,53", + gfx::Screen::GetScreenFor(root)->GetCursorScreenPoint().ToString()); +} + +TEST_F(WindowTest, HitTest) { + Window w1(new ColorTestWindowDelegate(SK_ColorWHITE)); + w1.set_id(1); + w1.Init(ui::LAYER_TEXTURED); + w1.SetBounds(gfx::Rect(10, 20, 50, 60)); + w1.Show(); + SetDefaultParentByPrimaryRootWindow(&w1); + + // Points are in the Window's coordinates. + EXPECT_TRUE(w1.HitTest(gfx::Point(1, 1))); + EXPECT_FALSE(w1.HitTest(gfx::Point(-1, -1))); + + // We can expand the bounds slightly to track events outside our border. + w1.SetHitTestBoundsOverrideOuter(gfx::Insets(-1, -1, -1, -1), + gfx::Insets(-5, -5, -5, -5)); + EXPECT_TRUE(w1.HitTest(gfx::Point(-1, -1))); + EXPECT_FALSE(w1.HitTest(gfx::Point(-2, -2))); + + ui::TouchEvent pressed( + ui::ET_TOUCH_PRESSED, gfx::Point(50, 50), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&pressed); + EXPECT_TRUE(w1.HitTest(gfx::Point(-2, -2))); + EXPECT_TRUE(w1.HitTest(gfx::Point(-5, -5))); + EXPECT_FALSE(w1.HitTest(gfx::Point(-5, -6))); + ui::TouchEvent released( + ui::ET_TOUCH_RELEASED, gfx::Point(50, 50), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&released); + EXPECT_FALSE(w1.HitTest(gfx::Point(-2, -2))); + + // TODO(beng): clip Window to parent. +} + +TEST_F(WindowTest, HitTestMask) { + MaskedWindowDelegate d1(gfx::Rect(5, 6, 20, 30)); + Window w1(&d1); + w1.Init(ui::LAYER_NOT_DRAWN); + w1.SetBounds(gfx::Rect(10, 20, 50, 60)); + w1.Show(); + SetDefaultParentByPrimaryRootWindow(&w1); + + // Points inside the mask. + EXPECT_TRUE(w1.HitTest(gfx::Point(5, 6))); // top-left + EXPECT_TRUE(w1.HitTest(gfx::Point(15, 21))); // center + EXPECT_TRUE(w1.HitTest(gfx::Point(24, 35))); // bottom-right + + // Points outside the mask. + EXPECT_FALSE(w1.HitTest(gfx::Point(0, 0))); + EXPECT_FALSE(w1.HitTest(gfx::Point(60, 80))); + EXPECT_FALSE(w1.HitTest(gfx::Point(4, 6))); + EXPECT_FALSE(w1.HitTest(gfx::Point(5, 5))); + EXPECT_FALSE(w1.HitTest(gfx::Point(25, 36))); +} + +TEST_F(WindowTest, GetEventHandlerForPoint) { + scoped_ptr<Window> w1( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), + root_window())); + scoped_ptr<Window> w11( + CreateTestWindow(SK_ColorGREEN, 11, gfx::Rect(5, 5, 100, 100), w1.get())); + scoped_ptr<Window> w111( + CreateTestWindow(SK_ColorCYAN, 111, gfx::Rect(5, 5, 75, 75), w11.get())); + scoped_ptr<Window> w1111( + CreateTestWindow(SK_ColorRED, 1111, gfx::Rect(5, 5, 50, 50), w111.get())); + scoped_ptr<Window> w12( + CreateTestWindow(SK_ColorMAGENTA, 12, gfx::Rect(10, 420, 25, 25), + w1.get())); + scoped_ptr<Window> w121( + CreateTestWindow(SK_ColorYELLOW, 121, gfx::Rect(5, 5, 5, 5), w12.get())); + scoped_ptr<Window> w13( + CreateTestWindow(SK_ColorGRAY, 13, gfx::Rect(5, 470, 50, 50), w1.get())); + + Window* root = root_window(); + w1->parent()->SetBounds(gfx::Rect(500, 500)); + EXPECT_EQ(NULL, root->GetEventHandlerForPoint(gfx::Point(5, 5))); + EXPECT_EQ(w1.get(), root->GetEventHandlerForPoint(gfx::Point(11, 11))); + EXPECT_EQ(w11.get(), root->GetEventHandlerForPoint(gfx::Point(16, 16))); + EXPECT_EQ(w111.get(), root->GetEventHandlerForPoint(gfx::Point(21, 21))); + EXPECT_EQ(w1111.get(), root->GetEventHandlerForPoint(gfx::Point(26, 26))); + EXPECT_EQ(w12.get(), root->GetEventHandlerForPoint(gfx::Point(21, 431))); + EXPECT_EQ(w121.get(), root->GetEventHandlerForPoint(gfx::Point(26, 436))); + EXPECT_EQ(w13.get(), root->GetEventHandlerForPoint(gfx::Point(26, 481))); +} + +TEST_F(WindowTest, GetEventHandlerForPointWithOverride) { + // If our child is flush to our top-left corner he gets events just inside the + // window edges. + scoped_ptr<Window> parent( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 20, 400, 500), + root_window())); + scoped_ptr<Window> child( + CreateTestWindow(SK_ColorRED, 2, gfx::Rect(0, 0, 60, 70), parent.get())); + EXPECT_EQ(child.get(), parent->GetEventHandlerForPoint(gfx::Point(0, 0))); + EXPECT_EQ(child.get(), parent->GetEventHandlerForPoint(gfx::Point(1, 1))); + + // We can override the hit test bounds of the parent to make the parent grab + // events along that edge. + parent->set_hit_test_bounds_override_inner(gfx::Insets(1, 1, 1, 1)); + EXPECT_EQ(parent.get(), parent->GetEventHandlerForPoint(gfx::Point(0, 0))); + EXPECT_EQ(child.get(), parent->GetEventHandlerForPoint(gfx::Point(1, 1))); +} + +TEST_F(WindowTest, GetEventHandlerForPointWithOverrideDescendingOrder) { + scoped_ptr<SelfEventHandlingWindowDelegate> parent_delegate( + new SelfEventHandlingWindowDelegate); + scoped_ptr<Window> parent(CreateTestWindowWithDelegate( + parent_delegate.get(), 1, gfx::Rect(10, 20, 400, 500), root_window())); + scoped_ptr<Window> child( + CreateTestWindow(SK_ColorRED, 2, gfx::Rect(0, 0, 390, 480), + parent.get())); + + // We can override ShouldDescendIntoChildForEventHandling to make the parent + // grab all events. + EXPECT_EQ(parent.get(), parent->GetEventHandlerForPoint(gfx::Point(0, 0))); + EXPECT_EQ(parent.get(), parent->GetEventHandlerForPoint(gfx::Point(50, 50))); +} + +TEST_F(WindowTest, GetTopWindowContainingPoint) { + Window* root = root_window(); + root->SetBounds(gfx::Rect(0, 0, 300, 300)); + + scoped_ptr<Window> w1( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(10, 10, 100, 100), + root_window())); + scoped_ptr<Window> w11( + CreateTestWindow(SK_ColorGREEN, 11, gfx::Rect(0, 0, 120, 120), w1.get())); + + scoped_ptr<Window> w2( + CreateTestWindow(SK_ColorRED, 2, gfx::Rect(5, 5, 55, 55), + root_window())); + + scoped_ptr<Window> w3( + CreateTestWindowWithDelegate( + NULL, 3, gfx::Rect(200, 200, 100, 100), root_window())); + scoped_ptr<Window> w31( + CreateTestWindow(SK_ColorCYAN, 31, gfx::Rect(0, 0, 50, 50), w3.get())); + scoped_ptr<Window> w311( + CreateTestWindow(SK_ColorBLUE, 311, gfx::Rect(0, 0, 10, 10), w31.get())); + + EXPECT_EQ(NULL, root->GetTopWindowContainingPoint(gfx::Point(0, 0))); + EXPECT_EQ(w2.get(), root->GetTopWindowContainingPoint(gfx::Point(5, 5))); + EXPECT_EQ(w2.get(), root->GetTopWindowContainingPoint(gfx::Point(10, 10))); + EXPECT_EQ(w2.get(), root->GetTopWindowContainingPoint(gfx::Point(59, 59))); + EXPECT_EQ(w1.get(), root->GetTopWindowContainingPoint(gfx::Point(60, 60))); + EXPECT_EQ(w1.get(), root->GetTopWindowContainingPoint(gfx::Point(109, 109))); + EXPECT_EQ(NULL, root->GetTopWindowContainingPoint(gfx::Point(110, 110))); + EXPECT_EQ(w31.get(), root->GetTopWindowContainingPoint(gfx::Point(200, 200))); + EXPECT_EQ(w31.get(), root->GetTopWindowContainingPoint(gfx::Point(220, 220))); + EXPECT_EQ(NULL, root->GetTopWindowContainingPoint(gfx::Point(260, 260))); +} + +TEST_F(WindowTest, GetToplevelWindow) { + const gfx::Rect kBounds(0, 0, 10, 10); + TestWindowDelegate delegate; + + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> w11( + CreateTestWindowWithDelegate(&delegate, 11, kBounds, w1.get())); + scoped_ptr<Window> w111(CreateTestWindowWithId(111, w11.get())); + scoped_ptr<Window> w1111( + CreateTestWindowWithDelegate(&delegate, 1111, kBounds, w111.get())); + + EXPECT_TRUE(root_window()->GetToplevelWindow() == NULL); + EXPECT_TRUE(w1->GetToplevelWindow() == NULL); + EXPECT_EQ(w11.get(), w11->GetToplevelWindow()); + EXPECT_EQ(w11.get(), w111->GetToplevelWindow()); + EXPECT_EQ(w11.get(), w1111->GetToplevelWindow()); +} + +class AddedToRootWindowObserver : public WindowObserver { + public: + AddedToRootWindowObserver() : called_(false) {} + + virtual void OnWindowAddedToRootWindow(Window* window) OVERRIDE { + called_ = true; + } + + bool called() const { return called_; } + + private: + bool called_; + + DISALLOW_COPY_AND_ASSIGN(AddedToRootWindowObserver); +}; + +TEST_F(WindowTest, WindowAddedToRootWindowShouldNotifyChildAndNotParent) { + AddedToRootWindowObserver parent_observer; + AddedToRootWindowObserver child_observer; + scoped_ptr<Window> parent_window(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> child_window(new Window(NULL)); + child_window->Init(ui::LAYER_TEXTURED); + child_window->Show(); + + parent_window->AddObserver(&parent_observer); + child_window->AddObserver(&child_observer); + + parent_window->AddChild(child_window.get()); + + EXPECT_FALSE(parent_observer.called()); + EXPECT_TRUE(child_observer.called()); + + parent_window->RemoveObserver(&parent_observer); + child_window->RemoveObserver(&child_observer); +} + +// Various destruction assertions. +TEST_F(WindowTest, DestroyTest) { + DestroyTrackingDelegateImpl parent_delegate; + ChildWindowDelegateImpl child_delegate(&parent_delegate); + { + scoped_ptr<Window> parent( + CreateTestWindowWithDelegate(&parent_delegate, 0, gfx::Rect(), + root_window())); + CreateTestWindowWithDelegate(&child_delegate, 0, gfx::Rect(), parent.get()); + } + // Both the parent and child should have been destroyed. + EXPECT_EQ(1, parent_delegate.destroying_count()); + EXPECT_EQ(1, parent_delegate.destroyed_count()); + EXPECT_EQ(1, child_delegate.destroying_count()); + EXPECT_EQ(1, child_delegate.destroyed_count()); +} + +// Tests that a window is orphaned before OnWindowDestroyed is called. +TEST_F(WindowTest, OrphanedBeforeOnDestroyed) { + TestWindowDelegate parent_delegate; + DestroyOrphanDelegate child_delegate; + { + scoped_ptr<Window> parent( + CreateTestWindowWithDelegate(&parent_delegate, 0, gfx::Rect(), + root_window())); + scoped_ptr<Window> child(CreateTestWindowWithDelegate(&child_delegate, 0, + gfx::Rect(), parent.get())); + child_delegate.set_window(child.get()); + } +} + +// Make sure StackChildAtTop moves both the window and layer to the front. +TEST_F(WindowTest, StackChildAtTop) { + Window parent(NULL); + parent.Init(ui::LAYER_NOT_DRAWN); + Window child1(NULL); + child1.Init(ui::LAYER_NOT_DRAWN); + Window child2(NULL); + child2.Init(ui::LAYER_NOT_DRAWN); + + parent.AddChild(&child1); + parent.AddChild(&child2); + ASSERT_EQ(2u, parent.children().size()); + EXPECT_EQ(&child1, parent.children()[0]); + EXPECT_EQ(&child2, parent.children()[1]); + ASSERT_EQ(2u, parent.layer()->children().size()); + EXPECT_EQ(child1.layer(), parent.layer()->children()[0]); + EXPECT_EQ(child2.layer(), parent.layer()->children()[1]); + + parent.StackChildAtTop(&child1); + ASSERT_EQ(2u, parent.children().size()); + EXPECT_EQ(&child1, parent.children()[1]); + EXPECT_EQ(&child2, parent.children()[0]); + ASSERT_EQ(2u, parent.layer()->children().size()); + EXPECT_EQ(child1.layer(), parent.layer()->children()[1]); + EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); +} + +// Make sure StackChildBelow works. +TEST_F(WindowTest, StackChildBelow) { + Window parent(NULL); + parent.Init(ui::LAYER_NOT_DRAWN); + Window child1(NULL); + child1.Init(ui::LAYER_NOT_DRAWN); + child1.set_id(1); + Window child2(NULL); + child2.Init(ui::LAYER_NOT_DRAWN); + child2.set_id(2); + Window child3(NULL); + child3.Init(ui::LAYER_NOT_DRAWN); + child3.set_id(3); + + parent.AddChild(&child1); + parent.AddChild(&child2); + parent.AddChild(&child3); + EXPECT_EQ("1 2 3", ChildWindowIDsAsString(&parent)); + + parent.StackChildBelow(&child1, &child2); + EXPECT_EQ("1 2 3", ChildWindowIDsAsString(&parent)); + + parent.StackChildBelow(&child2, &child1); + EXPECT_EQ("2 1 3", ChildWindowIDsAsString(&parent)); + + parent.StackChildBelow(&child3, &child2); + EXPECT_EQ("3 2 1", ChildWindowIDsAsString(&parent)); + + parent.StackChildBelow(&child3, &child1); + EXPECT_EQ("2 3 1", ChildWindowIDsAsString(&parent)); +} + +// Various assertions for StackChildAbove. +TEST_F(WindowTest, StackChildAbove) { + Window parent(NULL); + parent.Init(ui::LAYER_NOT_DRAWN); + Window child1(NULL); + child1.Init(ui::LAYER_NOT_DRAWN); + Window child2(NULL); + child2.Init(ui::LAYER_NOT_DRAWN); + Window child3(NULL); + child3.Init(ui::LAYER_NOT_DRAWN); + + parent.AddChild(&child1); + parent.AddChild(&child2); + + // Move 1 in front of 2. + parent.StackChildAbove(&child1, &child2); + ASSERT_EQ(2u, parent.children().size()); + EXPECT_EQ(&child2, parent.children()[0]); + EXPECT_EQ(&child1, parent.children()[1]); + ASSERT_EQ(2u, parent.layer()->children().size()); + EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); + EXPECT_EQ(child1.layer(), parent.layer()->children()[1]); + + // Add 3, resulting in order [2, 1, 3], then move 2 in front of 1, resulting + // in [1, 2, 3]. + parent.AddChild(&child3); + parent.StackChildAbove(&child2, &child1); + ASSERT_EQ(3u, parent.children().size()); + EXPECT_EQ(&child1, parent.children()[0]); + EXPECT_EQ(&child2, parent.children()[1]); + EXPECT_EQ(&child3, parent.children()[2]); + ASSERT_EQ(3u, parent.layer()->children().size()); + EXPECT_EQ(child1.layer(), parent.layer()->children()[0]); + EXPECT_EQ(child2.layer(), parent.layer()->children()[1]); + EXPECT_EQ(child3.layer(), parent.layer()->children()[2]); + + // Move 1 in front of 3, resulting in [2, 3, 1]. + parent.StackChildAbove(&child1, &child3); + ASSERT_EQ(3u, parent.children().size()); + EXPECT_EQ(&child2, parent.children()[0]); + EXPECT_EQ(&child3, parent.children()[1]); + EXPECT_EQ(&child1, parent.children()[2]); + ASSERT_EQ(3u, parent.layer()->children().size()); + EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); + EXPECT_EQ(child3.layer(), parent.layer()->children()[1]); + EXPECT_EQ(child1.layer(), parent.layer()->children()[2]); + + // Moving 1 in front of 2 should lower it, resulting in [2, 1, 3]. + parent.StackChildAbove(&child1, &child2); + ASSERT_EQ(3u, parent.children().size()); + EXPECT_EQ(&child2, parent.children()[0]); + EXPECT_EQ(&child1, parent.children()[1]); + EXPECT_EQ(&child3, parent.children()[2]); + ASSERT_EQ(3u, parent.layer()->children().size()); + EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); + EXPECT_EQ(child1.layer(), parent.layer()->children()[1]); + EXPECT_EQ(child3.layer(), parent.layer()->children()[2]); +} + +// Various capture assertions. +TEST_F(WindowTest, CaptureTests) { + CaptureWindowDelegateImpl delegate; + scoped_ptr<Window> window(CreateTestWindowWithDelegate( + &delegate, 0, gfx::Rect(0, 0, 20, 20), root_window())); + EXPECT_FALSE(window->HasCapture()); + + delegate.ResetCounts(); + + // Do a capture. + window->SetCapture(); + EXPECT_TRUE(window->HasCapture()); + EXPECT_EQ(0, delegate.capture_lost_count()); + EXPECT_EQ(0, delegate.capture_changed_event_count()); + EventGenerator generator(root_window(), gfx::Point(50, 50)); + generator.PressLeftButton(); + EXPECT_EQ(1, delegate.mouse_event_count()); + generator.ReleaseLeftButton(); + + EXPECT_EQ(2, delegate.mouse_event_count()); + delegate.ResetCounts(); + + ui::TouchEvent touchev( + ui::ET_TOUCH_PRESSED, gfx::Point(50, 50), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&touchev); + EXPECT_EQ(1, delegate.touch_event_count()); + delegate.ResetCounts(); + + window->ReleaseCapture(); + EXPECT_FALSE(window->HasCapture()); + EXPECT_EQ(1, delegate.capture_lost_count()); + EXPECT_EQ(1, delegate.capture_changed_event_count()); + EXPECT_EQ(1, delegate.mouse_event_count()); + EXPECT_EQ(0, delegate.touch_event_count()); + + generator.PressLeftButton(); + EXPECT_EQ(1, delegate.mouse_event_count()); + + ui::TouchEvent touchev2( + ui::ET_TOUCH_PRESSED, gfx::Point(250, 250), 1, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&touchev2); + EXPECT_EQ(0, delegate.touch_event_count()); + + // Removing the capture window from parent should reset the capture window + // in the root window. + window->SetCapture(); + EXPECT_EQ(window.get(), aura::client::GetCaptureWindow(root_window())); + window->parent()->RemoveChild(window.get()); + EXPECT_FALSE(window->HasCapture()); + EXPECT_EQ(NULL, aura::client::GetCaptureWindow(root_window())); +} + +TEST_F(WindowTest, TouchCaptureCancelsOtherTouches) { + CaptureWindowDelegateImpl delegate1; + scoped_ptr<Window> w1(CreateTestWindowWithDelegate( + &delegate1, 0, gfx::Rect(0, 0, 50, 50), root_window())); + CaptureWindowDelegateImpl delegate2; + scoped_ptr<Window> w2(CreateTestWindowWithDelegate( + &delegate2, 0, gfx::Rect(50, 50, 50, 50), root_window())); + + // Press on w1. + ui::TouchEvent press( + ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. + EXPECT_EQ(2, delegate1.gesture_event_count()); + delegate1.ResetCounts(); + + // Capturing to w2 should cause the touch to be canceled. + w2->SetCapture(); + EXPECT_EQ(1, delegate1.touch_event_count()); + EXPECT_EQ(0, delegate2.touch_event_count()); + delegate1.ResetCounts(); + delegate2.ResetCounts(); + + // Events now go to w2. + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(10, 20), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + EXPECT_EQ(0, delegate1.gesture_event_count()); + EXPECT_EQ(0, delegate1.touch_event_count()); + EXPECT_EQ(0, delegate2.gesture_event_count()); + EXPECT_EQ(1, delegate2.touch_event_count()); + + ui::TouchEvent release( + ui::ET_TOUCH_RELEASED, gfx::Point(10, 20), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_EQ(0, delegate1.gesture_event_count()); + EXPECT_EQ(0, delegate2.gesture_event_count()); + + // A new press is captured by w2. + ui::TouchEvent press2( + ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press2); + EXPECT_EQ(0, delegate1.gesture_event_count()); + // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. + EXPECT_EQ(2, delegate2.gesture_event_count()); + delegate1.ResetCounts(); + delegate2.ResetCounts(); + + // And releasing capture changes nothing. + w2->ReleaseCapture(); + EXPECT_EQ(0, delegate1.gesture_event_count()); + EXPECT_EQ(0, delegate1.touch_event_count()); + EXPECT_EQ(0, delegate2.gesture_event_count()); + EXPECT_EQ(0, delegate2.touch_event_count()); +} + +TEST_F(WindowTest, TouchCaptureDoesntCancelCapturedTouches) { + CaptureWindowDelegateImpl delegate; + scoped_ptr<Window> window(CreateTestWindowWithDelegate( + &delegate, 0, gfx::Rect(0, 0, 50, 50), root_window())); + + ui::TouchEvent press( + ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + + // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. + EXPECT_EQ(2, delegate.gesture_event_count()); + EXPECT_EQ(1, delegate.touch_event_count()); + delegate.ResetCounts(); + + window->SetCapture(); + EXPECT_EQ(0, delegate.gesture_event_count()); + EXPECT_EQ(0, delegate.touch_event_count()); + delegate.ResetCounts(); + + // On move We will get TOUCH_MOVED, GESTURE_TAP_CANCEL, + // GESTURE_SCROLL_START and GESTURE_SCROLL_UPDATE. + ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(10, 20), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move); + EXPECT_EQ(1, delegate.touch_event_count()); + EXPECT_EQ(3, delegate.gesture_event_count()); + delegate.ResetCounts(); + + // Release capture shouldn't change anything. + window->ReleaseCapture(); + EXPECT_EQ(0, delegate.touch_event_count()); + EXPECT_EQ(0, delegate.gesture_event_count()); + delegate.ResetCounts(); + + // On move we still get TOUCH_MOVED and GESTURE_SCROLL_UPDATE. + ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(10, 30), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&move2); + EXPECT_EQ(1, delegate.touch_event_count()); + EXPECT_EQ(1, delegate.gesture_event_count()); + delegate.ResetCounts(); + + // And on release we get TOUCH_RELEASED, GESTURE_SCROLL_END, GESTURE_END + ui::TouchEvent release( + ui::ET_TOUCH_RELEASED, gfx::Point(10, 20), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&release); + EXPECT_EQ(1, delegate.touch_event_count()); + EXPECT_EQ(2, delegate.gesture_event_count()); +} + + +// Assertions around SetCapture() and touch/gestures. +TEST_F(WindowTest, TransferCaptureTouchEvents) { + // Touch on |w1|. + CaptureWindowDelegateImpl d1; + scoped_ptr<Window> w1(CreateTestWindowWithDelegate( + &d1, 0, gfx::Rect(0, 0, 20, 20), root_window())); + ui::TouchEvent p1(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&p1); + // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. + EXPECT_EQ(1, d1.touch_event_count()); + EXPECT_EQ(2, d1.gesture_event_count()); + d1.ResetCounts(); + + // Touch on |w2| with a different id. + CaptureWindowDelegateImpl d2; + scoped_ptr<Window> w2(CreateTestWindowWithDelegate( + &d2, 0, gfx::Rect(40, 0, 40, 20), root_window())); + ui::TouchEvent p2(ui::ET_TOUCH_PRESSED, gfx::Point(41, 10), 1, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&p2); + EXPECT_EQ(0, d1.touch_event_count()); + EXPECT_EQ(0, d1.gesture_event_count()); + // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN for new target window. + EXPECT_EQ(1, d2.touch_event_count()); + EXPECT_EQ(2, d2.gesture_event_count()); + d1.ResetCounts(); + d2.ResetCounts(); + + // Set capture on |w2|, this should send a cancel (TAP_CANCEL, END) to |w1| + // but not |w2|. + w2->SetCapture(); + EXPECT_EQ(1, d1.touch_event_count()); + EXPECT_EQ(2, d1.gesture_event_count()); + EXPECT_EQ(0, d2.touch_event_count()); + EXPECT_EQ(0, d2.gesture_event_count()); + d1.ResetCounts(); + d2.ResetCounts(); + + CaptureWindowDelegateImpl d3; + scoped_ptr<Window> w3(CreateTestWindowWithDelegate( + &d3, 0, gfx::Rect(0, 0, 100, 101), root_window())); + // Set capture on w3. No new events should be received. + // Note this difference in behavior between the first and second capture + // is confusing and error prone. http://crbug.com/236930 + w3->SetCapture(); + EXPECT_EQ(0, d1.touch_event_count()); + EXPECT_EQ(0, d1.gesture_event_count()); + EXPECT_EQ(0, d2.touch_event_count()); + EXPECT_EQ(0, d2.gesture_event_count()); + EXPECT_EQ(0, d3.touch_event_count()); + EXPECT_EQ(0, d3.gesture_event_count()); + + // Move touch id originally associated with |w2|. Since capture was transfered + // from 2 to 3 only |w3| should get the event. + ui::TouchEvent m3(ui::ET_TOUCH_MOVED, gfx::Point(110, 105), 1, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&m3); + EXPECT_EQ(0, d1.touch_event_count()); + EXPECT_EQ(0, d1.gesture_event_count()); + EXPECT_EQ(0, d2.touch_event_count()); + EXPECT_EQ(0, d2.gesture_event_count()); + // |w3| gets a TOUCH_MOVE, TAP_CANCEL and two scroll related events. + EXPECT_EQ(1, d3.touch_event_count()); + EXPECT_EQ(3, d3.gesture_event_count()); + d1.ResetCounts(); + d2.ResetCounts(); + d3.ResetCounts(); + + // When we release capture, no touches are canceled. + w3->ReleaseCapture(); + EXPECT_EQ(0, d1.touch_event_count()); + EXPECT_EQ(0, d1.gesture_event_count()); + EXPECT_EQ(0, d2.touch_event_count()); + EXPECT_EQ(0, d2.gesture_event_count()); + EXPECT_EQ(0, d3.touch_event_count()); + EXPECT_EQ(0, d3.gesture_event_count()); + + // And when we move the touch again, |w3| still gets the events. + ui::TouchEvent m4(ui::ET_TOUCH_MOVED, gfx::Point(120, 105), 1, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&m4); + EXPECT_EQ(0, d1.touch_event_count()); + EXPECT_EQ(0, d1.gesture_event_count()); + EXPECT_EQ(0, d2.touch_event_count()); + EXPECT_EQ(0, d2.gesture_event_count()); + EXPECT_EQ(1, d3.touch_event_count()); + EXPECT_EQ(1, d3.gesture_event_count()); + d1.ResetCounts(); + d2.ResetCounts(); + d3.ResetCounts(); +} + +// Changes capture while capture is already ongoing. +TEST_F(WindowTest, ChangeCaptureWhileMouseDown) { + CaptureWindowDelegateImpl delegate; + scoped_ptr<Window> window(CreateTestWindowWithDelegate( + &delegate, 0, gfx::Rect(0, 0, 20, 20), root_window())); + CaptureWindowDelegateImpl delegate2; + scoped_ptr<Window> w2(CreateTestWindowWithDelegate( + &delegate2, 0, gfx::Rect(20, 20, 20, 20), root_window())); + + // Execute the scheduled draws so that mouse events are not + // aggregated. + RunAllPendingInMessageLoop(); + + EXPECT_FALSE(window->HasCapture()); + + // Do a capture. + delegate.ResetCounts(); + window->SetCapture(); + EXPECT_TRUE(window->HasCapture()); + EXPECT_EQ(0, delegate.capture_lost_count()); + EXPECT_EQ(0, delegate.capture_changed_event_count()); + EventGenerator generator(root_window(), gfx::Point(50, 50)); + generator.PressLeftButton(); + EXPECT_EQ(0, delegate.capture_lost_count()); + EXPECT_EQ(0, delegate.capture_changed_event_count()); + EXPECT_EQ(1, delegate.mouse_event_count()); + + // Set capture to |w2|, should implicitly unset capture for |window|. + delegate.ResetCounts(); + delegate2.ResetCounts(); + w2->SetCapture(); + + generator.MoveMouseTo(gfx::Point(40, 40), 2); + EXPECT_EQ(1, delegate.capture_lost_count()); + EXPECT_EQ(1, delegate.capture_changed_event_count()); + EXPECT_EQ(1, delegate.mouse_event_count()); + EXPECT_EQ(2, delegate2.mouse_event_count()); +} + +// Verifies capture is reset when a window is destroyed. +TEST_F(WindowTest, ReleaseCaptureOnDestroy) { + CaptureWindowDelegateImpl delegate; + scoped_ptr<Window> window(CreateTestWindowWithDelegate( + &delegate, 0, gfx::Rect(0, 0, 20, 20), root_window())); + EXPECT_FALSE(window->HasCapture()); + + // Do a capture. + window->SetCapture(); + EXPECT_TRUE(window->HasCapture()); + + // Destroy the window. + window.reset(); + + // Make sure the root window doesn't reference the window anymore. + EXPECT_EQ(NULL, root_window()->mouse_pressed_handler()); + EXPECT_EQ(NULL, aura::client::GetCaptureWindow(root_window())); +} + +TEST_F(WindowTest, GetBoundsInRootWindow) { + scoped_ptr<Window> viewport(CreateTestWindowWithBounds( + gfx::Rect(0, 0, 300, 300), root_window())); + scoped_ptr<Window> child(CreateTestWindowWithBounds( + gfx::Rect(0, 0, 100, 100), viewport.get())); + // Sanity check. + EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); + + // The |child| window's screen bounds should move along with the |viewport|. + viewport->SetBounds(gfx::Rect(-100, -100, 300, 300)); + EXPECT_EQ("-100,-100 100x100", child->GetBoundsInRootWindow().ToString()); + + // The |child| window is moved to the 0,0 in screen coordinates. + // |GetBoundsInRootWindow()| should return 0,0. + child->SetBounds(gfx::Rect(100, 100, 100, 100)); + EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); +} + +class MouseEnterExitWindowDelegate : public TestWindowDelegate { + public: + MouseEnterExitWindowDelegate() : entered_(false), exited_(false) {} + + virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { + switch (event->type()) { + case ui::ET_MOUSE_ENTERED: + EXPECT_TRUE(event->flags() & ui::EF_IS_SYNTHESIZED); + entered_ = true; + break; + case ui::ET_MOUSE_EXITED: + EXPECT_TRUE(event->flags() & ui::EF_IS_SYNTHESIZED); + exited_ = true; + break; + default: + break; + } + } + + bool entered() const { return entered_; } + bool exited() const { return exited_; } + + // Clear the entered / exited states. + void ResetExpectations() { + entered_ = false; + exited_ = false; + } + + private: + bool entered_; + bool exited_; + + DISALLOW_COPY_AND_ASSIGN(MouseEnterExitWindowDelegate); +}; + + +// Verifies that the WindowDelegate receives MouseExit and MouseEnter events for +// mouse transitions from window to window. +TEST_F(WindowTest, MouseEnterExit) { + MouseEnterExitWindowDelegate d1; + scoped_ptr<Window> w1( + CreateTestWindowWithDelegate(&d1, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + MouseEnterExitWindowDelegate d2; + scoped_ptr<Window> w2( + CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(70, 70, 50, 50), + root_window())); + + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(w1.get()); + EXPECT_TRUE(d1.entered()); + EXPECT_FALSE(d1.exited()); + EXPECT_FALSE(d2.entered()); + EXPECT_FALSE(d2.exited()); + + generator.MoveMouseToCenterOf(w2.get()); + EXPECT_TRUE(d1.entered()); + EXPECT_TRUE(d1.exited()); + EXPECT_TRUE(d2.entered()); + EXPECT_FALSE(d2.exited()); +} + +// Verifies that the WindowDelegate receives MouseExit from ET_MOUSE_EXITED. +TEST_F(WindowTest, RootWindowHostExit) { + MouseEnterExitWindowDelegate d1; + scoped_ptr<Window> w1( + CreateTestWindowWithDelegate(&d1, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(w1.get()); + EXPECT_TRUE(d1.entered()); + EXPECT_FALSE(d1.exited()); + d1.ResetExpectations(); + + ui::MouseEvent exit_event( + ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(), 0); + root_window()->AsRootWindowHostDelegate()->OnHostMouseEvent(&exit_event); + EXPECT_FALSE(d1.entered()); + EXPECT_TRUE(d1.exited()); +} + +// Verifies that the WindowDelegate receives MouseExit and MouseEnter events for +// mouse transitions from window to window, even if the entered window sets +// and releases capture. +TEST_F(WindowTest, MouseEnterExitWithClick) { + MouseEnterExitWindowDelegate d1; + scoped_ptr<Window> w1( + CreateTestWindowWithDelegate(&d1, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + MouseEnterExitWindowDelegate d2; + scoped_ptr<Window> w2( + CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(70, 70, 50, 50), + root_window())); + + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(w1.get()); + EXPECT_TRUE(d1.entered()); + EXPECT_FALSE(d1.exited()); + EXPECT_FALSE(d2.entered()); + EXPECT_FALSE(d2.exited()); + + // Emmulate what Views does on a click by grabbing and releasing capture. + generator.PressLeftButton(); + w1->SetCapture(); + w1->ReleaseCapture(); + generator.ReleaseLeftButton(); + + generator.MoveMouseToCenterOf(w2.get()); + EXPECT_TRUE(d1.entered()); + EXPECT_TRUE(d1.exited()); + EXPECT_TRUE(d2.entered()); + EXPECT_FALSE(d2.exited()); +} + +TEST_F(WindowTest, MouseEnterExitWhenDeleteWithCapture) { + MouseEnterExitWindowDelegate delegate; + scoped_ptr<Window> window( + CreateTestWindowWithDelegate(&delegate, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(window.get()); + EXPECT_TRUE(delegate.entered()); + EXPECT_FALSE(delegate.exited()); + + // Emmulate what Views does on a click by grabbing and releasing capture. + generator.PressLeftButton(); + window->SetCapture(); + + delegate.ResetExpectations(); + generator.MoveMouseTo(0, 0); + EXPECT_FALSE(delegate.entered()); + EXPECT_FALSE(delegate.exited()); + + delegate.ResetExpectations(); + window.reset(); + EXPECT_FALSE(delegate.entered()); + EXPECT_FALSE(delegate.exited()); +} + +// Verifies that enter / exits are sent if windows appear and are deleted +// under the current mouse position.. +TEST_F(WindowTest, MouseEnterExitWithDelete) { + MouseEnterExitWindowDelegate d1; + scoped_ptr<Window> w1( + CreateTestWindowWithDelegate(&d1, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(w1.get()); + EXPECT_TRUE(d1.entered()); + EXPECT_FALSE(d1.exited()); + + MouseEnterExitWindowDelegate d2; + { + scoped_ptr<Window> w2( + CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(10, 10, 50, 50), + root_window())); + // Enters / exits can be send asynchronously. + RunAllPendingInMessageLoop(); + EXPECT_TRUE(d1.entered()); + EXPECT_TRUE(d1.exited()); + EXPECT_TRUE(d2.entered()); + EXPECT_FALSE(d2.exited()); + d1.ResetExpectations(); + } + // Enters / exits can be send asynchronously. + RunAllPendingInMessageLoop(); + EXPECT_TRUE(d2.exited()); + EXPECT_TRUE(d1.entered()); +} + +// Verifies that enter / exits are sent if windows appear and are hidden +// under the current mouse position.. +TEST_F(WindowTest, MouseEnterExitWithHide) { + MouseEnterExitWindowDelegate d1; + scoped_ptr<Window> w1( + CreateTestWindowWithDelegate(&d1, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(w1.get()); + EXPECT_TRUE(d1.entered()); + EXPECT_FALSE(d1.exited()); + + MouseEnterExitWindowDelegate d2; + scoped_ptr<Window> w2( + CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(10, 10, 50, 50), + root_window())); + // Enters / exits can be send asynchronously. + RunAllPendingInMessageLoop(); + EXPECT_TRUE(d1.entered()); + EXPECT_TRUE(d1.exited()); + EXPECT_TRUE(d2.entered()); + EXPECT_FALSE(d2.exited()); + + d1.ResetExpectations(); + w2->Hide(); + // Enters / exits can be send asynchronously. + RunAllPendingInMessageLoop(); + EXPECT_TRUE(d2.exited()); + EXPECT_TRUE(d1.entered()); +} + +TEST_F(WindowTest, MouseEnterExitWithParentHide) { + MouseEnterExitWindowDelegate d1; + scoped_ptr<Window> w1( + CreateTestWindowWithDelegate(&d1, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + MouseEnterExitWindowDelegate d2; + Window* w2 = CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(10, 10, 50, 50), + w1.get()); + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(w2); + // Enters / exits can be send asynchronously. + RunAllPendingInMessageLoop(); + EXPECT_TRUE(d2.entered()); + EXPECT_FALSE(d2.exited()); + + d2.ResetExpectations(); + w1->Hide(); + RunAllPendingInMessageLoop(); + EXPECT_FALSE(d2.entered()); + EXPECT_TRUE(d2.exited()); + + w1.reset(); +} + +TEST_F(WindowTest, MouseEnterExitWithParentDelete) { + MouseEnterExitWindowDelegate d1; + scoped_ptr<Window> w1( + CreateTestWindowWithDelegate(&d1, 1, gfx::Rect(10, 10, 50, 50), + root_window())); + MouseEnterExitWindowDelegate d2; + Window* w2 = CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(10, 10, 50, 50), + w1.get()); + test::EventGenerator generator(root_window()); + generator.MoveMouseToCenterOf(w2); + + // Enters / exits can be send asynchronously. + RunAllPendingInMessageLoop(); + EXPECT_TRUE(d2.entered()); + EXPECT_FALSE(d2.exited()); + + d2.ResetExpectations(); + w1.reset(); + RunAllPendingInMessageLoop(); + EXPECT_FALSE(d2.entered()); + EXPECT_TRUE(d2.exited()); +} + +// Creates a window with a delegate (w111) that can handle events at a lower +// z-index than a window without a delegate (w12). w12 is sized to fill the +// entire bounds of the container. This test verifies that +// GetEventHandlerForPoint() skips w12 even though its bounds contain the event, +// because it has no children that can handle the event and it has no delegate +// allowing it to handle the event itself. +TEST_F(WindowTest, GetEventHandlerForPoint_NoDelegate) { + TestWindowDelegate d111; + scoped_ptr<Window> w1(CreateTestWindowWithDelegate(NULL, 1, + gfx::Rect(0, 0, 500, 500), root_window())); + scoped_ptr<Window> w11(CreateTestWindowWithDelegate(NULL, 11, + gfx::Rect(0, 0, 500, 500), w1.get())); + scoped_ptr<Window> w111(CreateTestWindowWithDelegate(&d111, 111, + gfx::Rect(50, 50, 450, 450), w11.get())); + scoped_ptr<Window> w12(CreateTestWindowWithDelegate(NULL, 12, + gfx::Rect(0, 0, 500, 500), w1.get())); + + gfx::Point target_point = w111->bounds().CenterPoint(); + EXPECT_EQ(w111.get(), w1->GetEventHandlerForPoint(target_point)); +} + +class VisibilityWindowDelegate : public TestWindowDelegate { + public: + VisibilityWindowDelegate() + : shown_(0), + hidden_(0) { + } + + int shown() const { return shown_; } + int hidden() const { return hidden_; } + void Clear() { + shown_ = 0; + hidden_ = 0; + } + + virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE { + if (visible) + shown_++; + else + hidden_++; + } + + private: + int shown_; + int hidden_; + + DISALLOW_COPY_AND_ASSIGN(VisibilityWindowDelegate); +}; + +// Verifies show/hide propagate correctly to children and the layer. +TEST_F(WindowTest, Visibility) { + VisibilityWindowDelegate d; + VisibilityWindowDelegate d2; + scoped_ptr<Window> w1(CreateTestWindowWithDelegate(&d, 1, gfx::Rect(), + root_window())); + scoped_ptr<Window> w2( + CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(), w1.get())); + scoped_ptr<Window> w3(CreateTestWindowWithId(3, w2.get())); + + // Create shows all the windows. + EXPECT_TRUE(w1->IsVisible()); + EXPECT_TRUE(w2->IsVisible()); + EXPECT_TRUE(w3->IsVisible()); + EXPECT_EQ(1, d.shown()); + + d.Clear(); + w1->Hide(); + EXPECT_FALSE(w1->IsVisible()); + EXPECT_FALSE(w2->IsVisible()); + EXPECT_FALSE(w3->IsVisible()); + EXPECT_EQ(1, d.hidden()); + EXPECT_EQ(0, d.shown()); + + w2->Show(); + EXPECT_FALSE(w1->IsVisible()); + EXPECT_FALSE(w2->IsVisible()); + EXPECT_FALSE(w3->IsVisible()); + + w3->Hide(); + EXPECT_FALSE(w1->IsVisible()); + EXPECT_FALSE(w2->IsVisible()); + EXPECT_FALSE(w3->IsVisible()); + + d.Clear(); + w1->Show(); + EXPECT_TRUE(w1->IsVisible()); + EXPECT_TRUE(w2->IsVisible()); + EXPECT_FALSE(w3->IsVisible()); + EXPECT_EQ(0, d.hidden()); + EXPECT_EQ(1, d.shown()); + + w3->Show(); + EXPECT_TRUE(w1->IsVisible()); + EXPECT_TRUE(w2->IsVisible()); + EXPECT_TRUE(w3->IsVisible()); + + // Verify that if an ancestor isn't visible and we change the visibility of a + // child window that OnChildWindowVisibilityChanged() is still invoked. + w1->Hide(); + d2.Clear(); + w2->Hide(); + EXPECT_EQ(1, d2.hidden()); + EXPECT_EQ(0, d2.shown()); + d2.Clear(); + w2->Show(); + EXPECT_EQ(0, d2.hidden()); + EXPECT_EQ(1, d2.shown()); +} + +TEST_F(WindowTest, IgnoreEventsTest) { + TestWindowDelegate d11; + TestWindowDelegate d12; + TestWindowDelegate d111; + TestWindowDelegate d121; + scoped_ptr<Window> w1(CreateTestWindowWithDelegate(NULL, 1, + gfx::Rect(0, 0, 500, 500), root_window())); + scoped_ptr<Window> w11(CreateTestWindowWithDelegate(&d11, 11, + gfx::Rect(0, 0, 500, 500), w1.get())); + scoped_ptr<Window> w111(CreateTestWindowWithDelegate(&d111, 111, + gfx::Rect(50, 50, 450, 450), w11.get())); + scoped_ptr<Window> w12(CreateTestWindowWithDelegate(&d12, 12, + gfx::Rect(0, 0, 500, 500), w1.get())); + scoped_ptr<Window> w121(CreateTestWindowWithDelegate(&d121, 121, + gfx::Rect(150, 150, 50, 50), w12.get())); + + EXPECT_EQ(w12.get(), w1->GetEventHandlerForPoint(gfx::Point(10, 10))); + w12->set_ignore_events(true); + EXPECT_EQ(w11.get(), w1->GetEventHandlerForPoint(gfx::Point(10, 10))); + w12->set_ignore_events(false); + + EXPECT_EQ(w121.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); + w121->set_ignore_events(true); + EXPECT_EQ(w12.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); + w12->set_ignore_events(true); + EXPECT_EQ(w111.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); + w111->set_ignore_events(true); + EXPECT_EQ(w11.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); +} + +// Tests transformation on the root window. +TEST_F(WindowTest, Transform) { + gfx::Size size = root_window()->GetHostSize(); + EXPECT_EQ(gfx::Rect(size), + gfx::Screen::GetScreenFor(root_window())->GetDisplayNearestPoint( + gfx::Point()).bounds()); + + // Rotate it clock-wise 90 degrees. + gfx::Transform transform; + transform.Translate(size.height(), 0); + transform.Rotate(90.0); + root_window()->SetTransform(transform); + + // The size should be the transformed size. + gfx::Size transformed_size(size.height(), size.width()); + EXPECT_EQ(transformed_size.ToString(), + root_window()->bounds().size().ToString()); + EXPECT_EQ( + gfx::Rect(transformed_size).ToString(), + gfx::Screen::GetScreenFor(root_window())->GetDisplayNearestPoint( + gfx::Point()).bounds().ToString()); + + // Host size shouldn't change. + EXPECT_EQ(size.ToString(), + root_window()->GetHostSize().ToString()); +} + +TEST_F(WindowTest, TransformGesture) { + gfx::Size size = root_window()->GetHostSize(); + + scoped_ptr<GestureTrackPositionDelegate> delegate( + new GestureTrackPositionDelegate); + scoped_ptr<Window> window(CreateTestWindowWithDelegate(delegate.get(), -1234, + gfx::Rect(0, 0, 20, 20), root_window())); + + // Rotate the root-window clock-wise 90 degrees. + gfx::Transform transform; + transform.Translate(size.height(), 0.0); + transform.Rotate(90.0); + root_window()->SetTransform(transform); + + ui::TouchEvent press( + ui::ET_TOUCH_PRESSED, gfx::Point(size.height() - 10, 10), 0, getTime()); + root_window()->AsRootWindowHostDelegate()->OnHostTouchEvent(&press); + EXPECT_EQ(gfx::Point(10, 10).ToString(), delegate->position().ToString()); +} + +// Various assertions for transient children. +TEST_F(WindowTest, TransientChildren) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, parent.get())); + scoped_ptr<Window> w3(CreateTestWindowWithId(3, parent.get())); + Window* w2 = CreateTestWindowWithId(2, parent.get()); + w1->AddTransientChild(w2); // w2 is now owned by w1. + // Stack w1 at the top (end), this should force w2 to be last (on top of w1). + parent->StackChildAtTop(w1.get()); + ASSERT_EQ(3u, parent->children().size()); + EXPECT_EQ(w2, parent->children().back()); + + // Destroy w1, which should also destroy w3 (since it's a transient child). + w1.reset(); + w2 = NULL; + ASSERT_EQ(1u, parent->children().size()); + EXPECT_EQ(w3.get(), parent->children()[0]); + + w1.reset(CreateTestWindowWithId(4, parent.get())); + w2 = CreateTestWindowWithId(5, w3.get()); + w1->AddTransientChild(w2); + parent->StackChildAtTop(w3.get()); + // Stack w1 at the top (end), this shouldn't affect w2 since it has a + // different parent. + parent->StackChildAtTop(w1.get()); + ASSERT_EQ(2u, parent->children().size()); + EXPECT_EQ(w3.get(), parent->children()[0]); + EXPECT_EQ(w1.get(), parent->children()[1]); + + // Hiding parent should hide transient children. + EXPECT_TRUE(w2->IsVisible()); + w1->Hide(); + EXPECT_FALSE(w2->IsVisible()); +} + +// Tests that when a focused window is closed, its parent inherits the focus. +TEST_F(WindowTest, FocusedWindowTest) { + scoped_ptr<Window> parent(CreateTestWindowWithId(0, root_window())); + scoped_ptr<Window> child(CreateTestWindowWithId(1, parent.get())); + + parent->Show(); + + child->Focus(); + EXPECT_TRUE(child->HasFocus()); + EXPECT_FALSE(parent->HasFocus()); + + child.reset(); + EXPECT_TRUE(parent->HasFocus()); +} + +// Tests that the previously-focused window is passed to OnWindowFocused. +// TODO(beng): Remove once the FocusController lands. +TEST_F(WindowTest, OldFocusedWindowTest) { + const gfx::Rect kBounds(0, 0, 100, 100); + + FocusDelegate delegate1; + scoped_ptr<Window> window1( + CreateTestWindowWithDelegate(&delegate1, 0, kBounds, root_window())); + client::SetFocusChangeObserver(window1.get(), &delegate1); + window1->Focus(); + ASSERT_TRUE(window1->HasFocus()); + EXPECT_TRUE(delegate1.previous_focused_window() == NULL); + + FocusDelegate delegate2; + scoped_ptr<Window> window2( + CreateTestWindowWithDelegate(&delegate2, 1, kBounds, root_window())); + client::SetFocusChangeObserver(window2.get(), &delegate2); + window2->Focus(); + ASSERT_TRUE(window2->HasFocus()); + EXPECT_FALSE(window1->HasFocus()); + EXPECT_EQ(window1.get(), delegate2.previous_focused_window()); +} + +namespace { +DEFINE_WINDOW_PROPERTY_KEY(int, kIntKey, -2); +DEFINE_WINDOW_PROPERTY_KEY(const char*, kStringKey, "squeamish"); +} + +TEST_F(WindowTest, Property) { + scoped_ptr<Window> w(CreateTestWindowWithId(0, root_window())); + + static const char native_prop_key[] = "fnord"; + + // Non-existent properties should return the default values. + EXPECT_EQ(-2, w->GetProperty(kIntKey)); + EXPECT_EQ(std::string("squeamish"), w->GetProperty(kStringKey)); + EXPECT_EQ(NULL, w->GetNativeWindowProperty(native_prop_key)); + + // A set property value should be returned again (even if it's the default + // value). + w->SetProperty(kIntKey, INT_MAX); + EXPECT_EQ(INT_MAX, w->GetProperty(kIntKey)); + w->SetProperty(kIntKey, -2); + EXPECT_EQ(-2, w->GetProperty(kIntKey)); + w->SetProperty(kIntKey, INT_MIN); + EXPECT_EQ(INT_MIN, w->GetProperty(kIntKey)); + + w->SetProperty(kStringKey, static_cast<const char*>(NULL)); + EXPECT_EQ(NULL, w->GetProperty(kStringKey)); + w->SetProperty(kStringKey, "squeamish"); + EXPECT_EQ(std::string("squeamish"), w->GetProperty(kStringKey)); + w->SetProperty(kStringKey, "ossifrage"); + EXPECT_EQ(std::string("ossifrage"), w->GetProperty(kStringKey)); + + w->SetNativeWindowProperty(native_prop_key, &*w); + EXPECT_EQ(&*w, w->GetNativeWindowProperty(native_prop_key)); + w->SetNativeWindowProperty(native_prop_key, NULL); + EXPECT_EQ(NULL, w->GetNativeWindowProperty(native_prop_key)); + + // ClearProperty should restore the default value. + w->ClearProperty(kIntKey); + EXPECT_EQ(-2, w->GetProperty(kIntKey)); + w->ClearProperty(kStringKey); + EXPECT_EQ(std::string("squeamish"), w->GetProperty(kStringKey)); +} + +namespace { + +class TestProperty { + public: + TestProperty() {} + virtual ~TestProperty() { + last_deleted_ = this; + } + static TestProperty* last_deleted() { return last_deleted_; } + + private: + static TestProperty* last_deleted_; + DISALLOW_COPY_AND_ASSIGN(TestProperty); +}; + +TestProperty* TestProperty::last_deleted_ = NULL; + +DEFINE_OWNED_WINDOW_PROPERTY_KEY(TestProperty, kOwnedKey, NULL); + +} // namespace + +TEST_F(WindowTest, OwnedProperty) { + scoped_ptr<Window> w(CreateTestWindowWithId(0, root_window())); + EXPECT_EQ(NULL, w->GetProperty(kOwnedKey)); + TestProperty* p1 = new TestProperty(); + w->SetProperty(kOwnedKey, p1); + EXPECT_EQ(p1, w->GetProperty(kOwnedKey)); + EXPECT_EQ(NULL, TestProperty::last_deleted()); + + TestProperty* p2 = new TestProperty(); + w->SetProperty(kOwnedKey, p2); + EXPECT_EQ(p2, w->GetProperty(kOwnedKey)); + EXPECT_EQ(p1, TestProperty::last_deleted()); + + w->ClearProperty(kOwnedKey); + EXPECT_EQ(NULL, w->GetProperty(kOwnedKey)); + EXPECT_EQ(p2, TestProperty::last_deleted()); + + TestProperty* p3 = new TestProperty(); + w->SetProperty(kOwnedKey, p3); + EXPECT_EQ(p3, w->GetProperty(kOwnedKey)); + EXPECT_EQ(p2, TestProperty::last_deleted()); + w.reset(); + EXPECT_EQ(p3, TestProperty::last_deleted()); +} + +TEST_F(WindowTest, SetBoundsInternalShouldCheckTargetBounds) { + // We cannot short-circuit animations in this test. + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + + scoped_ptr<Window> w1( + CreateTestWindowWithBounds(gfx::Rect(0, 0, 100, 100), root_window())); + + EXPECT_FALSE(!w1->layer()); + w1->layer()->GetAnimator()->set_disable_timer_for_test(true); + ui::AnimationContainerElement* element = w1->layer()->GetAnimator(); + + EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); + EXPECT_EQ("0,0 100x100", w1->layer()->GetTargetBounds().ToString()); + + // Animate to a different position. + { + ui::ScopedLayerAnimationSettings settings(w1->layer()->GetAnimator()); + w1->SetBounds(gfx::Rect(100, 100, 100, 100)); + } + + EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); + EXPECT_EQ("100,100 100x100", w1->layer()->GetTargetBounds().ToString()); + + // Animate back to the first position. The animation hasn't started yet, so + // the current bounds are still (0, 0, 100, 100), but the target bounds are + // (100, 100, 100, 100). If we step the animator ahead, we should find that + // we're at (0, 0, 100, 100). That is, the second animation should be applied. + { + ui::ScopedLayerAnimationSettings settings(w1->layer()->GetAnimator()); + w1->SetBounds(gfx::Rect(0, 0, 100, 100)); + } + + EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); + EXPECT_EQ("0,0 100x100", w1->layer()->GetTargetBounds().ToString()); + + // Confirm that the target bounds are reached. + base::TimeTicks start_time = + w1->layer()->GetAnimator()->last_step_time(); + + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); +} + + +typedef std::pair<const void*, intptr_t> PropertyChangeInfo; + +class WindowObserverTest : public WindowTest, + public WindowObserver { + public: + struct VisibilityInfo { + bool window_visible; + bool visible_param; + }; + + WindowObserverTest() + : added_count_(0), + removed_count_(0), + destroyed_count_(0), + old_property_value_(-3) { + } + + virtual ~WindowObserverTest() {} + + const VisibilityInfo* GetVisibilityInfo() const { + return visibility_info_.get(); + } + + void ResetVisibilityInfo() { + visibility_info_.reset(); + } + + // Returns a description of the WindowObserver methods that have been invoked. + std::string WindowObserverCountStateAndClear() { + std::string result( + base::StringPrintf("added=%d removed=%d", + added_count_, removed_count_)); + added_count_ = removed_count_ = 0; + return result; + } + + int DestroyedCountAndClear() { + int result = destroyed_count_; + destroyed_count_ = 0; + return result; + } + + // Return a tuple of the arguments passed in OnPropertyChanged callback. + PropertyChangeInfo PropertyChangeInfoAndClear() { + PropertyChangeInfo result(property_key_, old_property_value_); + property_key_ = NULL; + old_property_value_ = -3; + return result; + } + + private: + virtual void OnWindowAdded(Window* new_window) OVERRIDE { + added_count_++; + } + + virtual void OnWillRemoveWindow(Window* window) OVERRIDE { + removed_count_++; + } + + virtual void OnWindowVisibilityChanged(Window* window, + bool visible) OVERRIDE { + visibility_info_.reset(new VisibilityInfo); + visibility_info_->window_visible = window->IsVisible(); + visibility_info_->visible_param = visible; + } + + virtual void OnWindowDestroyed(Window* window) OVERRIDE { + EXPECT_FALSE(window->parent()); + destroyed_count_++; + } + + virtual void OnWindowPropertyChanged(Window* window, + const void* key, + intptr_t old) OVERRIDE { + property_key_ = key; + old_property_value_ = old; + } + + int added_count_; + int removed_count_; + int destroyed_count_; + scoped_ptr<VisibilityInfo> visibility_info_; + const void* property_key_; + intptr_t old_property_value_; + + DISALLOW_COPY_AND_ASSIGN(WindowObserverTest); +}; + +// Various assertions for WindowObserver. +TEST_F(WindowObserverTest, WindowObserver) { + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + w1->AddObserver(this); + + // Create a new window as a child of w1, our observer should be notified. + scoped_ptr<Window> w2(CreateTestWindowWithId(2, w1.get())); + EXPECT_EQ("added=1 removed=0", WindowObserverCountStateAndClear()); + + // Delete w2, which should result in the remove notification. + w2.reset(); + EXPECT_EQ("added=0 removed=1", WindowObserverCountStateAndClear()); + + // Create a window that isn't parented to w1, we shouldn't get any + // notification. + scoped_ptr<Window> w3(CreateTestWindowWithId(3, root_window())); + EXPECT_EQ("added=0 removed=0", WindowObserverCountStateAndClear()); + + // Similarly destroying w3 shouldn't notify us either. + w3.reset(); + EXPECT_EQ("added=0 removed=0", WindowObserverCountStateAndClear()); + w1->RemoveObserver(this); +} + +// Test if OnWindowVisibilityChagned is invoked with expected +// parameters. +TEST_F(WindowObserverTest, WindowVisibility) { + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> w2(CreateTestWindowWithId(1, w1.get())); + w2->AddObserver(this); + + // Hide should make the window invisible and the passed visible + // parameter is false. + w2->Hide(); + EXPECT_FALSE(!GetVisibilityInfo()); + EXPECT_FALSE(!GetVisibilityInfo()); + if (!GetVisibilityInfo()) + return; + EXPECT_FALSE(GetVisibilityInfo()->window_visible); + EXPECT_FALSE(GetVisibilityInfo()->visible_param); + + // If parent isn't visible, showing window won't make the window visible, but + // passed visible value must be true. + w1->Hide(); + ResetVisibilityInfo(); + EXPECT_TRUE(!GetVisibilityInfo()); + w2->Show(); + EXPECT_FALSE(!GetVisibilityInfo()); + if (!GetVisibilityInfo()) + return; + EXPECT_FALSE(GetVisibilityInfo()->window_visible); + EXPECT_TRUE(GetVisibilityInfo()->visible_param); + + // If parent is visible, showing window will make the window + // visible and the passed visible value is true. + w1->Show(); + w2->Hide(); + ResetVisibilityInfo(); + w2->Show(); + EXPECT_FALSE(!GetVisibilityInfo()); + if (!GetVisibilityInfo()) + return; + EXPECT_TRUE(GetVisibilityInfo()->window_visible); + EXPECT_TRUE(GetVisibilityInfo()->visible_param); +} + +// Test if OnWindowDestroyed is invoked as expected. +TEST_F(WindowObserverTest, WindowDestroyed) { + // Delete a window should fire a destroyed notification. + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + w1->AddObserver(this); + w1.reset(); + EXPECT_EQ(1, DestroyedCountAndClear()); + + // Observe on child and delete parent window should fire a notification. + scoped_ptr<Window> parent(CreateTestWindowWithId(1, root_window())); + Window* child = CreateTestWindowWithId(1, parent.get()); // owned by parent + child->AddObserver(this); + parent.reset(); + EXPECT_EQ(1, DestroyedCountAndClear()); +} + +TEST_F(WindowObserverTest, PropertyChanged) { + // Setting property should fire a property change notification. + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + w1->AddObserver(this); + + static const WindowProperty<int> prop = {-2}; + static const char native_prop_key[] = "fnord"; + + w1->SetProperty(&prop, 1); + EXPECT_EQ(PropertyChangeInfo(&prop, -2), PropertyChangeInfoAndClear()); + w1->SetProperty(&prop, -2); + EXPECT_EQ(PropertyChangeInfo(&prop, 1), PropertyChangeInfoAndClear()); + w1->SetProperty(&prop, 3); + EXPECT_EQ(PropertyChangeInfo(&prop, -2), PropertyChangeInfoAndClear()); + w1->ClearProperty(&prop); + EXPECT_EQ(PropertyChangeInfo(&prop, 3), PropertyChangeInfoAndClear()); + + w1->SetNativeWindowProperty(native_prop_key, &*w1); + EXPECT_EQ(PropertyChangeInfo(native_prop_key, 0), + PropertyChangeInfoAndClear()); + w1->SetNativeWindowProperty(native_prop_key, NULL); + EXPECT_EQ(PropertyChangeInfo(native_prop_key, + reinterpret_cast<intptr_t>(&*w1)), + PropertyChangeInfoAndClear()); + + // Sanity check to see if |PropertyChangeInfoAndClear| really clears. + EXPECT_EQ(PropertyChangeInfo( + reinterpret_cast<const void*>(NULL), -3), PropertyChangeInfoAndClear()); +} + +TEST_F(WindowTest, AcquireLayer) { + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + ui::Layer* parent = window1->parent()->layer(); + EXPECT_EQ(2U, parent->children().size()); + + WindowTestApi window1_test_api(window1.get()); + WindowTestApi window2_test_api(window2.get()); + + EXPECT_TRUE(window1_test_api.OwnsLayer()); + EXPECT_TRUE(window2_test_api.OwnsLayer()); + + // After acquisition, window1 should not own its layer, but it should still + // be available to the window. + scoped_ptr<ui::Layer> window1_layer(window1->AcquireLayer()); + EXPECT_FALSE(window1_test_api.OwnsLayer()); + EXPECT_TRUE(window1_layer.get() == window1->layer()); + + // Upon destruction, window1's layer should still be valid, and in the layer + // hierarchy, but window2's should be gone, and no longer in the hierarchy. + window1.reset(); + window2.reset(); + + // This should be set by the window's destructor. + EXPECT_TRUE(window1_layer->delegate() == NULL); + EXPECT_EQ(1U, parent->children().size()); +} + +// Make sure that properties which should persist from the old layer to the new +// layer actually do. +TEST_F(WindowTest, RecreateLayer) { + // Set properties to non default values. + Window w(new ColorTestWindowDelegate(SK_ColorWHITE)); + w.set_id(1); + w.Init(ui::LAYER_SOLID_COLOR); + w.SetBounds(gfx::Rect(0, 0, 100, 100)); + + ui::Layer* layer = w.layer(); + layer->set_scale_content(false); + layer->SetVisible(false); + layer->SetMasksToBounds(true); + + ui::Layer child_layer; + layer->Add(&child_layer); + + scoped_ptr<ui::Layer> old_layer(w.RecreateLayer()); + layer = w.layer(); + EXPECT_EQ(ui::LAYER_SOLID_COLOR, layer->type()); + EXPECT_FALSE(layer->scale_content()); + EXPECT_FALSE(layer->visible()); + EXPECT_EQ(1u, layer->children().size()); + EXPECT_TRUE(layer->GetMasksToBounds()); +} + +// Verify that RecreateLayer() stacks the old layer above the newly creatd +// layer. +TEST_F(WindowTest, RecreateLayerZOrder) { + scoped_ptr<Window> w( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(0, 0, 100, 100), + root_window())); + scoped_ptr<ui::Layer> old_layer(w->RecreateLayer()); + + const std::vector<ui::Layer*>& child_layers = + root_window()->layer()->children(); + ASSERT_EQ(2u, child_layers.size()); + EXPECT_EQ(w->layer(), child_layers[0]); + EXPECT_EQ(old_layer.get(), child_layers[1]); +} + +// Ensure that acquiring a layer then recreating a layer does not crash +// and that RecreateLayer returns null. +TEST_F(WindowTest, AcquireThenRecreateLayer) { + scoped_ptr<Window> w( + CreateTestWindow(SK_ColorWHITE, 1, gfx::Rect(0, 0, 100, 100), + root_window())); + scoped_ptr<ui::Layer>acquired_layer(w->AcquireLayer()); + scoped_ptr<ui::Layer>doubly_acquired_layer(w->RecreateLayer()); + EXPECT_EQ(NULL, doubly_acquired_layer.get()); + + // Destroy window before layer gets destroyed. + w.reset(); +} + +TEST_F(WindowTest, StackWindowsWhoseLayersHaveNoDelegate) { + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + window1->layer()->set_name("1"); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + window2->layer()->set_name("2"); + scoped_ptr<Window> window3(CreateTestWindowWithId(3, root_window())); + window3->layer()->set_name("3"); + + // This brings |window1| (and its layer) to the front. + root_window()->StackChildAbove(window1.get(), window3.get()); + EXPECT_EQ("2 3 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("2 3 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); + + // Since |window1| does not have a delegate, |window2| should not move in + // front of it, nor should its layer. + window1->layer()->set_delegate(NULL); + root_window()->StackChildAbove(window2.get(), window1.get()); + EXPECT_EQ("3 2 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("3 2 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); + + // It should still be possible to stack |window3| immediately below |window1|. + root_window()->StackChildBelow(window3.get(), window1.get()); + EXPECT_EQ("2 3 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("2 3 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); + + // Since neither |window3| nor |window1| have a delegate, |window2| should + // not move in front of either. + window3->layer()->set_delegate(NULL); + root_window()->StackChildBelow(window2.get(), window1.get()); + EXPECT_EQ("2 3 1", ChildWindowIDsAsString(root_window())); + EXPECT_EQ("2 3 1", + ui::test::ChildLayerNamesAsString(*root_window()->layer())); +} + +TEST_F(WindowTest, StackTransientsWhoseLayersHaveNoDelegate) { + RootWindow* root = root_window(); + + // Create a window with several transients, then a couple windows on top. + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> window11(CreateTransientChild(11, window1.get())); + scoped_ptr<Window> window12(CreateTransientChild(12, window1.get())); + scoped_ptr<Window> window13(CreateTransientChild(13, window1.get())); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + scoped_ptr<Window> window3(CreateTestWindowWithId(3, root_window())); + + EXPECT_EQ("1 11 12 13 2 3", ChildWindowIDsAsString(root)); + + // Remove the delegates of a couple of transients, as if they are closing + // and animating out. + window11->layer()->set_delegate(NULL); + window13->layer()->set_delegate(NULL); + + // Move window1 to the front. All transients should move with it, and their + // order should be preserved. + root->StackChildAtTop(window1.get()); + + EXPECT_EQ("2 3 1 11 12 13", ChildWindowIDsAsString(root)); +} + +class TestVisibilityClient : public client::VisibilityClient { + public: + explicit TestVisibilityClient(RootWindow* root_window) + : ignore_visibility_changes_(false) { + client::SetVisibilityClient(root_window, this); + } + virtual ~TestVisibilityClient() { + } + + void set_ignore_visibility_changes(bool ignore_visibility_changes) { + ignore_visibility_changes_ = ignore_visibility_changes; + } + + // Overridden from client::VisibilityClient: + virtual void UpdateLayerVisibility(aura::Window* window, + bool visible) OVERRIDE { + if (!ignore_visibility_changes_) + window->layer()->SetVisible(visible); + } + + private: + bool ignore_visibility_changes_; + DISALLOW_COPY_AND_ASSIGN(TestVisibilityClient); +}; + +TEST_F(WindowTest, VisibilityClientIsVisible) { + TestVisibilityClient client(root_window()); + + scoped_ptr<Window> window(CreateTestWindowWithId(1, root_window())); + EXPECT_TRUE(window->IsVisible()); + EXPECT_TRUE(window->layer()->visible()); + + window->Hide(); + EXPECT_FALSE(window->IsVisible()); + EXPECT_FALSE(window->layer()->visible()); + window->Show(); + + client.set_ignore_visibility_changes(true); + window->Hide(); + EXPECT_FALSE(window->IsVisible()); + EXPECT_TRUE(window->layer()->visible()); +} + +// Tests mouse events on window change. +TEST_F(WindowTest, MouseEventsOnWindowChange) { + gfx::Size size = root_window()->GetHostSize(); + + EventGenerator generator(root_window()); + generator.MoveMouseTo(50, 50); + + EventCountDelegate d1; + scoped_ptr<Window> w1(CreateTestWindowWithDelegate(&d1, 1, + gfx::Rect(0, 0, 100, 100), root_window())); + RunAllPendingInMessageLoop(); + // The format of result is "Enter/Mouse/Leave". + EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); + + // Adding new window. + EventCountDelegate d11; + scoped_ptr<Window> w11(CreateTestWindowWithDelegate( + &d11, 1, gfx::Rect(0, 0, 100, 100), w1.get())); + RunAllPendingInMessageLoop(); + EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); + + // Move bounds. + w11->SetBounds(gfx::Rect(0, 0, 10, 10)); + RunAllPendingInMessageLoop(); + EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); + + w11->SetBounds(gfx::Rect(0, 0, 60, 60)); + RunAllPendingInMessageLoop(); + EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); + + // Detach, then re-attach. + w1->RemoveChild(w11.get()); + RunAllPendingInMessageLoop(); + EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); + // Window is detached, so no event is set. + EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); + + w1->AddChild(w11.get()); + RunAllPendingInMessageLoop(); + EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); + // Window is detached, so no event is set. + EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); + + // Visibility Change + w11->Hide(); + RunAllPendingInMessageLoop(); + EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); + + w11->Show(); + RunAllPendingInMessageLoop(); + EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); + + // Transform: move d11 by 100 100. + gfx::Transform transform; + transform.Translate(100, 100); + w11->SetTransform(transform); + RunAllPendingInMessageLoop(); + EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); + + w11->SetTransform(gfx::Transform()); + RunAllPendingInMessageLoop(); + EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); + + // Closing a window. + w11.reset(); + RunAllPendingInMessageLoop(); + EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); + + // Make sure we don't synthesize events if the mouse + // is outside of the root window. + generator.MoveMouseTo(-10, -10); + EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); + + // Adding new windows. + w11.reset(CreateTestWindowWithDelegate( + &d11, 1, gfx::Rect(0, 0, 100, 100), w1.get())); + RunAllPendingInMessageLoop(); + EXPECT_EQ("0 0 0", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); + + // Closing windows + w11.reset(); + RunAllPendingInMessageLoop(); + EXPECT_EQ("0 0 0", d1.GetMouseMotionCountsAndReset()); + EXPECT_EQ("0 0 0", d11.GetMouseMotionCountsAndReset()); +} + +class StackingMadrigalLayoutManager : public LayoutManager { + public: + explicit StackingMadrigalLayoutManager(RootWindow* root_window) + : root_window_(root_window) { + root_window_->SetLayoutManager(this); + } + virtual ~StackingMadrigalLayoutManager() { + } + + private: + // Overridden from LayoutManager: + virtual void OnWindowResized() OVERRIDE {} + virtual void OnWindowAddedToLayout(Window* child) OVERRIDE {} + virtual void OnWillRemoveWindowFromLayout(Window* child) OVERRIDE {} + virtual void OnWindowRemovedFromLayout(Window* child) OVERRIDE {} + virtual void OnChildWindowVisibilityChanged(Window* child, + bool visible) OVERRIDE { + Window::Windows::const_iterator it = root_window_->children().begin(); + Window* last_window = NULL; + for (; it != root_window_->children().end(); ++it) { + if (*it == child && last_window) { + if (!visible) + root_window_->StackChildAbove(last_window, *it); + else + root_window_->StackChildAbove(*it, last_window); + break; + } + last_window = *it; + } + } + virtual void SetChildBounds(Window* child, + const gfx::Rect& requested_bounds) OVERRIDE { + SetChildBoundsDirect(child, requested_bounds); + } + + RootWindow* root_window_; + + DISALLOW_COPY_AND_ASSIGN(StackingMadrigalLayoutManager); +}; + +class StackingMadrigalVisibilityClient : public client::VisibilityClient { + public: + explicit StackingMadrigalVisibilityClient(RootWindow* root_window) + : ignored_window_(NULL) { + client::SetVisibilityClient(root_window, this); + } + virtual ~StackingMadrigalVisibilityClient() { + } + + void set_ignored_window(Window* ignored_window) { + ignored_window_ = ignored_window; + } + + private: + // Overridden from client::VisibilityClient: + virtual void UpdateLayerVisibility(Window* window, bool visible) OVERRIDE { + if (!visible) { + if (window == ignored_window_) + window->layer()->set_delegate(NULL); + else + window->layer()->SetVisible(visible); + } else { + window->layer()->SetVisible(visible); + } + } + + Window* ignored_window_; + + DISALLOW_COPY_AND_ASSIGN(StackingMadrigalVisibilityClient); +}; + +// This test attempts to reconstruct a circumstance that can happen when the +// aura client attempts to manipulate the visibility and delegate of a layer +// independent of window visibility. +// A use case is where the client attempts to keep a window visible onscreen +// even after code has called Hide() on the window. The use case for this would +// be that window hides are animated (e.g. the window fades out). To prevent +// spurious updating the client code may also clear window's layer's delegate, +// so that the window cannot attempt to paint or update it further. The window +// uses the presence of a NULL layer delegate as a signal in stacking to note +// that the window is being manipulated by such a use case and its stacking +// should not be adjusted. +// One issue that can arise when a window opens two transient children, and the +// first is hidden. Subsequent attempts to activate the transient parent can +// result in the transient parent being stacked above the second transient +// child. A fix is made to Window::StackAbove to prevent this, and this test +// verifies this fix. +TEST_F(WindowTest, StackingMadrigal) { + new StackingMadrigalLayoutManager(root_window()); + StackingMadrigalVisibilityClient visibility_client(root_window()); + + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> window11(CreateTransientChild(11, window1.get())); + + visibility_client.set_ignored_window(window11.get()); + + window11->Show(); + window11->Hide(); + + // As a transient, window11 should still be stacked above window1, even when + // hidden. + EXPECT_TRUE(WindowIsAbove(window11.get(), window1.get())); + EXPECT_TRUE(LayerIsAbove(window11.get(), window1.get())); + + // A new transient should still be above window1. It will appear behind + // window11 because we don't stack windows on top of targets with NULL + // delegates. + scoped_ptr<Window> window12(CreateTransientChild(12, window1.get())); + window12->Show(); + + EXPECT_TRUE(WindowIsAbove(window12.get(), window1.get())); + EXPECT_TRUE(LayerIsAbove(window12.get(), window1.get())); + + // In earlier versions of the StackChildAbove() method, attempting to stack + // window1 above window12 at this point would actually restack the layers + // resulting in window12's layer being below window1's layer (though the + // windows themselves would still be correctly stacked, so events would pass + // through.) + root_window()->StackChildAbove(window1.get(), window12.get()); + + // Both window12 and its layer should be stacked above window1. + EXPECT_TRUE(WindowIsAbove(window12.get(), window1.get())); + EXPECT_TRUE(LayerIsAbove(window12.get(), window1.get())); +} + +// Test for an issue where attempting to stack a primary window on top of a +// transient with a NULL layer delegate causes that primary window to be moved, +// but the layer order not changed to match. http://crbug.com/112562 +TEST_F(WindowTest, StackOverClosingTransient) { + scoped_ptr<Window> window1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> transient1(CreateTransientChild(11, window1.get())); + scoped_ptr<Window> window2(CreateTestWindowWithId(2, root_window())); + scoped_ptr<Window> transient2(CreateTransientChild(21, window2.get())); + + // Both windows and layers are stacked in creation order. + RootWindow* root = root_window(); + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], transient1.get()); + EXPECT_EQ(root->children()[2], window2.get()); + EXPECT_EQ(root->children()[3], transient2.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], transient1->layer()); + EXPECT_EQ(root->layer()->children()[2], window2->layer()); + EXPECT_EQ(root->layer()->children()[3], transient2->layer()); + + // This brings window1 and its transient to the front. + root->StackChildAtTop(window1.get()); + + EXPECT_EQ(root->children()[0], window2.get()); + EXPECT_EQ(root->children()[1], transient2.get()); + EXPECT_EQ(root->children()[2], window1.get()); + EXPECT_EQ(root->children()[3], transient1.get()); + EXPECT_EQ(root->layer()->children()[0], window2->layer()); + EXPECT_EQ(root->layer()->children()[1], transient2->layer()); + EXPECT_EQ(root->layer()->children()[2], window1->layer()); + EXPECT_EQ(root->layer()->children()[3], transient1->layer()); + + // Pretend we're closing the top-most transient, then bring window2 to the + // front. This mimics activating a browser window while the status bubble + // is fading out. The transient should stay topmost. + transient1->layer()->set_delegate(NULL); + root->StackChildAtTop(window2.get()); + + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + EXPECT_EQ(root->children()[3], transient1.get()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + EXPECT_EQ(root->layer()->children()[3], transient1->layer()); + + // Close the transient. Remaining windows are stable. + transient1.reset(); + + ASSERT_EQ(3u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + ASSERT_EQ(3u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + + // Open another window on top. + scoped_ptr<Window> window3(CreateTestWindowWithId(3, root_window())); + + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + EXPECT_EQ(root->children()[3], window3.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + EXPECT_EQ(root->layer()->children()[3], window3->layer()); + + // Pretend we're closing the topmost non-transient window, then bring + // window2 to the top. It should not move. + window3->layer()->set_delegate(NULL); + root->StackChildAtTop(window2.get()); + + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window1.get()); + EXPECT_EQ(root->children()[1], window2.get()); + EXPECT_EQ(root->children()[2], transient2.get()); + EXPECT_EQ(root->children()[3], window3.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window1->layer()); + EXPECT_EQ(root->layer()->children()[1], window2->layer()); + EXPECT_EQ(root->layer()->children()[2], transient2->layer()); + EXPECT_EQ(root->layer()->children()[3], window3->layer()); + + // Bring window1 to the top. It should move ahead of window2, but not + // ahead of window3 (with NULL delegate). + root->StackChildAtTop(window1.get()); + + ASSERT_EQ(4u, root->children().size()); + EXPECT_EQ(root->children()[0], window2.get()); + EXPECT_EQ(root->children()[1], transient2.get()); + EXPECT_EQ(root->children()[2], window1.get()); + EXPECT_EQ(root->children()[3], window3.get()); + ASSERT_EQ(4u, root->layer()->children().size()); + EXPECT_EQ(root->layer()->children()[0], window2->layer()); + EXPECT_EQ(root->layer()->children()[1], transient2->layer()); + EXPECT_EQ(root->layer()->children()[2], window1->layer()); + EXPECT_EQ(root->layer()->children()[3], window3->layer()); +} + +class RootWindowAttachmentObserver : public WindowObserver { + public: + RootWindowAttachmentObserver() : added_count_(0), removed_count_(0) {} + virtual ~RootWindowAttachmentObserver() {} + + int added_count() const { return added_count_; } + int removed_count() const { return removed_count_; } + + void Clear() { + added_count_ = 0; + removed_count_ = 0; + } + + // Overridden from WindowObserver: + virtual void OnWindowAddedToRootWindow(Window* window) OVERRIDE { + ++added_count_; + } + virtual void OnWindowRemovingFromRootWindow(Window* window) OVERRIDE { + ++removed_count_; + } + + private: + int added_count_; + int removed_count_; + + DISALLOW_COPY_AND_ASSIGN(RootWindowAttachmentObserver); +}; + +TEST_F(WindowTest, RootWindowAttachment) { + RootWindowAttachmentObserver observer; + + // Test a direct add/remove from the RootWindow. + scoped_ptr<Window> w1(new Window(NULL)); + w1->Init(ui::LAYER_NOT_DRAWN); + w1->AddObserver(&observer); + + SetDefaultParentByPrimaryRootWindow(w1.get()); + EXPECT_EQ(1, observer.added_count()); + EXPECT_EQ(0, observer.removed_count()); + + w1.reset(); + EXPECT_EQ(1, observer.added_count()); + EXPECT_EQ(1, observer.removed_count()); + + observer.Clear(); + + // Test an indirect add/remove from the RootWindow. + w1.reset(new Window(NULL)); + w1->Init(ui::LAYER_NOT_DRAWN); + Window* w11 = new Window(NULL); + w11->Init(ui::LAYER_NOT_DRAWN); + w11->AddObserver(&observer); + w1->AddChild(w11); + EXPECT_EQ(0, observer.added_count()); + EXPECT_EQ(0, observer.removed_count()); + + SetDefaultParentByPrimaryRootWindow(w1.get()); + EXPECT_EQ(1, observer.added_count()); + EXPECT_EQ(0, observer.removed_count()); + + w1.reset(); // Deletes w11. + w11 = NULL; + EXPECT_EQ(1, observer.added_count()); + EXPECT_EQ(1, observer.removed_count()); + + observer.Clear(); + + // Test an indirect add/remove with nested observers. + w1.reset(new Window(NULL)); + w1->Init(ui::LAYER_NOT_DRAWN); + w11 = new Window(NULL); + w11->Init(ui::LAYER_NOT_DRAWN); + w11->AddObserver(&observer); + w1->AddChild(w11); + Window* w111 = new Window(NULL); + w111->Init(ui::LAYER_NOT_DRAWN); + w111->AddObserver(&observer); + w11->AddChild(w111); + + EXPECT_EQ(0, observer.added_count()); + EXPECT_EQ(0, observer.removed_count()); + + SetDefaultParentByPrimaryRootWindow(w1.get()); + EXPECT_EQ(2, observer.added_count()); + EXPECT_EQ(0, observer.removed_count()); + + w1.reset(); // Deletes w11 and w111. + w11 = NULL; + w111 = NULL; + EXPECT_EQ(2, observer.added_count()); + EXPECT_EQ(2, observer.removed_count()); +} + +TEST_F(WindowTest, OwnedByParentFalse) { + // By default, a window is owned by its parent. If this is set to false, the + // window will not be destroyed when its parent is. + + scoped_ptr<Window> w1(new Window(NULL)); + w1->Init(ui::LAYER_NOT_DRAWN); + scoped_ptr<Window> w2(new Window(NULL)); + w2->set_owned_by_parent(false); + w2->Init(ui::LAYER_NOT_DRAWN); + w1->AddChild(w2.get()); + + w1.reset(); + + // We should be able to deref w2 still, but its parent should now be NULL. + EXPECT_EQ(NULL, w2->parent()); +} + +namespace { + +// Used By DeleteWindowFromOnWindowDestroyed. Destroys a Window from +// OnWindowDestroyed(). +class OwningWindowDelegate : public TestWindowDelegate { + public: + OwningWindowDelegate() {} + + void SetOwnedWindow(Window* window) { + owned_window_.reset(window); + } + + virtual void OnWindowDestroyed() OVERRIDE { + owned_window_.reset(NULL); + } + + private: + scoped_ptr<Window> owned_window_; + + DISALLOW_COPY_AND_ASSIGN(OwningWindowDelegate); +}; + +} // namespace + +// Creates a window with two child windows. When the first child window is +// destroyed (WindowDelegate::OnWindowDestroyed) it deletes the second child. +// This synthesizes BrowserView and the status bubble. Both are children of the +// same parent and destroying BrowserView triggers it destroying the status +// bubble. +TEST_F(WindowTest, DeleteWindowFromOnWindowDestroyed) { + scoped_ptr<Window> parent(new Window(NULL)); + parent->Init(ui::LAYER_NOT_DRAWN); + OwningWindowDelegate delegate; + Window* c1 = new Window(&delegate); + c1->Init(ui::LAYER_NOT_DRAWN); + parent->AddChild(c1); + Window* c2 = new Window(NULL); + c2->Init(ui::LAYER_NOT_DRAWN); + parent->AddChild(c2); + delegate.SetOwnedWindow(c2); + parent.reset(); +} + +namespace { + +// Used by DelegateNotifiedAsBoundsChange to verify OnBoundsChanged() is +// invoked. +class BoundsChangeDelegate : public TestWindowDelegate { + public: + BoundsChangeDelegate() : bounds_changed_(false) {} + + void clear_bounds_changed() { bounds_changed_ = false; } + bool bounds_changed() const { + return bounds_changed_; + } + + // Window + virtual void OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) OVERRIDE { + bounds_changed_ = true; + } + + private: + // Was OnBoundsChanged() invoked? + bool bounds_changed_; + + DISALLOW_COPY_AND_ASSIGN(BoundsChangeDelegate); +}; + +} // namespace + +// Verifies the delegate is notified when the actual bounds of the layer +// change. +TEST_F(WindowTest, DelegateNotifiedAsBoundsChange) { + BoundsChangeDelegate delegate; + + // We cannot short-circuit animations in this test. + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + + scoped_ptr<Window> window( + CreateTestWindowWithDelegate(&delegate, 1, + gfx::Rect(0, 0, 100, 100), root_window())); + window->layer()->GetAnimator()->set_disable_timer_for_test(true); + + delegate.clear_bounds_changed(); + + // Animate to a different position. + { + ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); + window->SetBounds(gfx::Rect(100, 100, 100, 100)); + } + + // Bounds shouldn't immediately have changed. + EXPECT_EQ("0,0 100x100", window->bounds().ToString()); + EXPECT_FALSE(delegate.bounds_changed()); + + // Animate to the end, which should notify of the change. + base::TimeTicks start_time = + window->layer()->GetAnimator()->last_step_time(); + ui::AnimationContainerElement* element = window->layer()->GetAnimator(); + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + EXPECT_TRUE(delegate.bounds_changed()); + EXPECT_NE("0,0 100x100", window->bounds().ToString()); +} + +// Verifies the delegate is notified when the actual bounds of the layer +// change even when the window is not the layer's delegate +TEST_F(WindowTest, DelegateNotifiedAsBoundsChangeInHiddenLayer) { + BoundsChangeDelegate delegate; + + // We cannot short-circuit animations in this test. + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + + scoped_ptr<Window> window( + CreateTestWindowWithDelegate(&delegate, 1, + gfx::Rect(0, 0, 100, 100), root_window())); + window->layer()->GetAnimator()->set_disable_timer_for_test(true); + + delegate.clear_bounds_changed(); + + // Suppress paint on the window since it is hidden (should reset the layer's + // delegate to NULL) + window->SuppressPaint(); + EXPECT_EQ(NULL, window->layer()->delegate()); + + // Animate to a different position. + { + ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); + window->SetBounds(gfx::Rect(100, 100, 110, 100)); + } + + // Layer delegate is NULL but we should still get bounds changed notification. + EXPECT_EQ("100,100 110x100", window->GetTargetBounds().ToString()); + EXPECT_TRUE(delegate.bounds_changed()); + + delegate.clear_bounds_changed(); + + // Animate to the end: will *not* notify of the change since we are hidden. + base::TimeTicks start_time = + window->layer()->GetAnimator()->last_step_time(); + ui::AnimationContainerElement* element = window->layer()->GetAnimator(); + element->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); + + // No bounds changed notification at the end of animation since layer + // delegate is NULL. + EXPECT_FALSE(delegate.bounds_changed()); + EXPECT_NE("0,0 100x100", window->bounds().ToString()); +} + +namespace { + +// Used by AddChildNotifications to track notification counts. +class AddChildNotificationsObserver : public WindowObserver { + public: + AddChildNotificationsObserver() : added_count_(0), removed_count_(0) {} + + std::string CountStringAndReset() { + std::string result = base::IntToString(added_count_) + " " + + base::IntToString(removed_count_); + added_count_ = removed_count_ = 0; + return result; + } + + // WindowObserver overrides: + virtual void OnWindowAddedToRootWindow(Window* window) OVERRIDE { + added_count_++; + } + virtual void OnWindowRemovingFromRootWindow(Window* window) OVERRIDE { + removed_count_++; + } + + private: + int added_count_; + int removed_count_; + + DISALLOW_COPY_AND_ASSIGN(AddChildNotificationsObserver); +}; + +} // namespace + +// Assertions around when root window notifications are sent. +TEST_F(WindowTest, AddChildNotifications) { + AddChildNotificationsObserver observer; + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + scoped_ptr<Window> w2(CreateTestWindowWithId(1, root_window())); + w2->AddObserver(&observer); + w2->Focus(); + EXPECT_TRUE(w2->HasFocus()); + + // Move |w2| to be a child of |w1|. + w1->AddChild(w2.get()); + // Sine we moved in the same root, observer shouldn't be notified. + EXPECT_EQ("0 0", observer.CountStringAndReset()); + // |w2| should still have focus after moving. + EXPECT_TRUE(w2->HasFocus()); +} + +// Tests that a delegate that destroys itself when the window is destroyed does +// not break. +TEST_F(WindowTest, DelegateDestroysSelfOnWindowDestroy) { + scoped_ptr<Window> w1(CreateTestWindowWithDelegate( + new DestroyWindowDelegate(), + 0, + gfx::Rect(10, 20, 30, 40), + root_window())); +} + +class HierarchyObserver : public WindowObserver { + public: + HierarchyObserver(Window* target) : target_(target) { + target_->AddObserver(this); + } + virtual ~HierarchyObserver() { + target_->RemoveObserver(this); + } + + void ValidateState( + int index, + const WindowObserver::HierarchyChangeParams& params) const { + ParamsMatch(params_[index], params); + } + + void Reset() { + params_.clear(); + } + + private: + // Overridden from WindowObserver: + virtual void OnWindowHierarchyChanging( + const HierarchyChangeParams& params) OVERRIDE { + params_.push_back(params); + } + virtual void OnWindowHierarchyChanged( + const HierarchyChangeParams& params) OVERRIDE { + params_.push_back(params); + } + + void ParamsMatch(const WindowObserver::HierarchyChangeParams& p1, + const WindowObserver::HierarchyChangeParams& p2) const { + EXPECT_EQ(p1.phase, p2.phase); + EXPECT_EQ(p1.target, p2.target); + EXPECT_EQ(p1.new_parent, p2.new_parent); + EXPECT_EQ(p1.old_parent, p2.old_parent); + EXPECT_EQ(p1.receiver, p2.receiver); + } + + Window* target_; + std::vector<WindowObserver::HierarchyChangeParams> params_; + + DISALLOW_COPY_AND_ASSIGN(HierarchyObserver); +}; + +// Tests hierarchy change notifications. +TEST_F(WindowTest, OnWindowHierarchyChange) { + { + // Simple add & remove. + HierarchyObserver oroot(root_window()); + + scoped_ptr<Window> w1(CreateTestWindowWithId(1, NULL)); + HierarchyObserver o1(w1.get()); + + // Add. + root_window()->AddChild(w1.get()); + + WindowObserver::HierarchyChangeParams params; + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; + params.target = w1.get(); + params.old_parent = NULL; + params.new_parent = root_window(); + params.receiver = w1.get(); + o1.ValidateState(0, params); + + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; + params.receiver = w1.get(); + o1.ValidateState(1, params); + + params.receiver = root_window(); + oroot.ValidateState(0, params); + + // Remove. + o1.Reset(); + oroot.Reset(); + + root_window()->RemoveChild(w1.get()); + + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; + params.old_parent = root_window(); + params.new_parent = NULL; + params.receiver = w1.get(); + + o1.ValidateState(0, params); + + params.receiver = root_window(); + oroot.ValidateState(0, params); + + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; + params.receiver = w1.get(); + o1.ValidateState(1, params); + } + + { + // Add & remove of hierarchy. Tests notification order per documentation in + // WindowObserver. + HierarchyObserver o(root_window()); + scoped_ptr<Window> w1(CreateTestWindowWithId(1, NULL)); + Window* w11 = CreateTestWindowWithId(11, w1.get()); + w1->AddObserver(&o); + w11->AddObserver(&o); + + // Add. + root_window()->AddChild(w1.get()); + + // Dispatched to target first. + int index = 0; + WindowObserver::HierarchyChangeParams params; + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; + params.target = w1.get(); + params.old_parent = NULL; + params.new_parent = root_window(); + params.receiver = w1.get(); + o.ValidateState(index++, params); + + // Dispatched to target's children. + params.receiver = w11; + o.ValidateState(index++, params); + + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; + + // Now process the "changed" phase. + params.receiver = w1.get(); + o.ValidateState(index++, params); + params.receiver = w11; + o.ValidateState(index++, params); + params.receiver = root_window(); + o.ValidateState(index++, params); + + // Remove. + root_window()->RemoveChild(w1.get()); + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; + params.old_parent = root_window(); + params.new_parent = NULL; + params.receiver = w1.get(); + o.ValidateState(index++, params); + params.receiver = w11; + o.ValidateState(index++, params); + params.receiver = root_window(); + o.ValidateState(index++, params); + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; + params.receiver = w1.get(); + o.ValidateState(index++, params); + params.receiver = w11; + o.ValidateState(index++, params); + + w1.reset(); + } + + { + // Reparent. Tests notification order per documentation in WindowObserver. + scoped_ptr<Window> w1(CreateTestWindowWithId(1, root_window())); + Window* w11 = CreateTestWindowWithId(11, w1.get()); + Window* w111 = CreateTestWindowWithId(111, w11); + scoped_ptr<Window> w2(CreateTestWindowWithId(2, root_window())); + + HierarchyObserver o(root_window()); + w1->AddObserver(&o); + w11->AddObserver(&o); + w111->AddObserver(&o); + w2->AddObserver(&o); + + w2->AddChild(w11); + + // Dispatched to target first. + int index = 0; + WindowObserver::HierarchyChangeParams params; + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; + params.target = w11; + params.old_parent = w1.get(); + params.new_parent = w2.get(); + params.receiver = w11; + o.ValidateState(index++, params); + + // Then to target's children. + params.receiver = w111; + o.ValidateState(index++, params); + + // Then to target's old parent chain. + params.receiver = w1.get(); + o.ValidateState(index++, params); + params.receiver = root_window(); + o.ValidateState(index++, params); + + // "Changed" phase. + params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; + params.receiver = w11; + o.ValidateState(index++, params); + params.receiver = w111; + o.ValidateState(index++, params); + params.receiver = w2.get(); + o.ValidateState(index++, params); + params.receiver = root_window(); + o.ValidateState(index++, params); + + w1.reset(); + w2.reset(); + } + +} + +} // namespace test +} // namespace aura |