summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrouslan <rouslan@chromium.org>2015-01-21 17:54:14 -0800
committerCommit bot <commit-bot@chromium.org>2015-01-22 01:55:40 +0000
commitf7ebd883699112506893fc0500a65c5a10e563ec (patch)
tree9e19faae4ecf97f42adc6d2620b0903ab2837f74
parent196692c000558976906e11c7d184301c55aada20 (diff)
downloadchromium_src-f7ebd883699112506893fc0500a65c5a10e563ec.zip
chromium_src-f7ebd883699112506893fc0500a65c5a10e563ec.tar.gz
chromium_src-f7ebd883699112506893fc0500a65c5a10e563ec.tar.bz2
[Android] Show autofill popup after animation.
The renderer shows autofill popups immediately upon click or focus change. If the browser shows an IME (virtual keyboard), then it can zoom and scroll the webpage, which will hide the popup. This behavior causes a "flash" of autofill popup on platforms with IME (ChromeOS, Windows 8, and Android). This patch mostly fixes Android and paves the way for fixing Windows 8 and ChromeOS. The fix involves roughly 5 parts. 1) If the platform does not support a virtual keyboard (Mac, Linux, Windows 7), the renderer shows the autofill popup immediately. 2) If a virtual keyboard is supported, but disabled (ChromeOS without a virtual keyboard and Android with a hardware keyboard), then the browser notifies the renderer that the autofill popup can be shown. 3) If the virtual keyboard is already showing, then the browser notifies the renderer that the autofill popup can be shown. A corner case is the Android keyboard, which can change its own size when switching between input fields, thus resizing the webpage and hiding the autofill popup. The mitigation is to hide the autofill popup only if the input field moves due to the resize. This mitigation is not in this patch. 4) If the input field is already in a good position (visible on screen and legible size) after the keyboard is shown, then there will be no zoom and scroll animations. Then the renderer can show the autofill popup. 5) If the input field is zooming and scrolling into a good position (visible on screen and legible size) after the keyboard is shown, then the renderer waits until the compositor notifies it that the this animation has finished. Then the renderer can show the autofill popup. BUG=430318, 440161 Review URL: https://codereview.chromium.org/715733002 Cr-Commit-Position: refs/heads/master@{#312521}
-rw-r--r--android_webview/browser/hardware_renderer.h1
-rw-r--r--cc/test/fake_layer_tree_host_client.h1
-rw-r--r--cc/test/fake_layer_tree_host_impl_client.h1
-rw-r--r--cc/test/layer_tree_test.cc1
-rw-r--r--cc/trees/layer_tree_host.cc9
-rw-r--r--cc/trees/layer_tree_host.h7
-rw-r--r--cc/trees/layer_tree_host_client.h3
-rw-r--r--cc/trees/layer_tree_host_impl.cc1
-rw-r--r--cc/trees/layer_tree_host_impl.h3
-rw-r--r--cc/trees/layer_tree_host_impl_unittest.cc37
-rw-r--r--cc/trees/layer_tree_host_unittest_no_message_loop.cc1
-rw-r--r--cc/trees/single_thread_proxy.cc4
-rw-r--r--cc/trees/single_thread_proxy.h1
-rw-r--r--cc/trees/thread_proxy.cc12
-rw-r--r--cc/trees/thread_proxy.h2
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupWithKeyboardTest.java146
-rw-r--r--chrome/renderer/autofill/page_click_tracker_browsertest.cc101
-rw-r--r--chrome/renderer/autofill/password_generation_agent_browsertest.cc87
-rw-r--r--components/autofill/content/renderer/autofill_agent.cc25
-rw-r--r--components/autofill/content/renderer/autofill_agent.h2
-rw-r--r--components/autofill/content/renderer/form_autofill_util.cc2
-rw-r--r--components/autofill/content/renderer/form_autofill_util.h3
-rw-r--r--components/autofill/content/renderer/page_click_tracker.cc71
-rw-r--r--components/autofill/content/renderer/page_click_tracker.h10
-rw-r--r--content/browser/renderer_host/compositor_impl_android.h1
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java6
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java15
-rw-r--r--content/public/android/javatests/src/org/chromium/content/browser/input/AdapterInputConnectionTest.java3
-rw-r--r--content/public/renderer/render_view_observer.h5
-rw-r--r--content/public/test/render_view_test.cc18
-rw-r--r--content/public/test/render_view_test.h3
-rw-r--r--content/renderer/gpu/render_widget_compositor.cc4
-rw-r--r--content/renderer/gpu/render_widget_compositor.h1
-rw-r--r--content/renderer/render_view_impl.cc16
-rw-r--r--content/renderer/render_view_impl.h2
-rw-r--r--content/renderer/render_widget.cc19
-rw-r--r--content/renderer/render_widget.h7
-rw-r--r--content/test/web_layer_tree_view_impl_for_testing.h1
-rw-r--r--mojo/services/html_viewer/weblayertreeview_impl.h1
-rw-r--r--ui/compositor/compositor.h1
40 files changed, 514 insertions, 120 deletions
diff --git a/android_webview/browser/hardware_renderer.h b/android_webview/browser/hardware_renderer.h
index 7f0459d..671b3ca 100644
--- a/android_webview/browser/hardware_renderer.h
+++ b/android_webview/browser/hardware_renderer.h
@@ -59,6 +59,7 @@ class HardwareRenderer : public cc::LayerTreeHostClient,
virtual void DidCommit() override {}
virtual void DidCommitAndDrawFrame() override {}
virtual void DidCompleteSwapBuffers() override {}
+ virtual void DidCompletePageScaleAnimation() override {}
// cc::LayerTreeHostSingleThreadClient overrides.
virtual void DidPostSwapBuffers() override {}
diff --git a/cc/test/fake_layer_tree_host_client.h b/cc/test/fake_layer_tree_host_client.h
index f256ab1..5f7afa5 100644
--- a/cc/test/fake_layer_tree_host_client.h
+++ b/cc/test/fake_layer_tree_host_client.h
@@ -50,6 +50,7 @@ class FakeLayerTreeHostClient : public LayerTreeHostClient,
void DidCommit() override {}
void DidCommitAndDrawFrame() override {}
void DidCompleteSwapBuffers() override {}
+ void DidCompletePageScaleAnimation() override {}
// LayerTreeHostSingleThreadClient implementation.
void DidPostSwapBuffers() override {}
diff --git a/cc/test/fake_layer_tree_host_impl_client.h b/cc/test/fake_layer_tree_host_impl_client.h
index 98aafa0..d7a785b 100644
--- a/cc/test/fake_layer_tree_host_impl_client.h
+++ b/cc/test/fake_layer_tree_host_impl_client.h
@@ -39,6 +39,7 @@ class FakeLayerTreeHostImplClient : public LayerTreeHostImplClient {
base::TimeDelta delay) override {}
void DidActivateSyncTree() override {}
void DidPrepareTiles() override {}
+ void DidCompletePageScaleAnimationOnImplThread() override {}
};
} // namespace cc
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 9653d8d..a40a8c7 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -373,6 +373,7 @@ class LayerTreeHostClientForTesting : public LayerTreeHostClient,
void DidPostSwapBuffers() override {}
void DidAbortSwapBuffers() override {}
void ScheduleComposite() override { test_hooks_->ScheduleComposite(); }
+ void DidCompletePageScaleAnimation() override {}
private:
explicit LayerTreeHostClientForTesting(TestHooks* test_hooks)
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index b6e6473..a24ac172 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -130,6 +130,7 @@ LayerTreeHost::LayerTreeHost(
background_color_(SK_ColorWHITE),
has_transparent_background_(false),
partial_texture_update_requests_(0),
+ did_complete_scale_animation_(false),
in_paint_layer_contents_(false),
total_frames_used_for_lcd_text_metrics_(0),
id_(s_layer_tree_host_sequence_number.GetNext() + 1),
@@ -403,6 +404,10 @@ void LayerTreeHost::UpdateHudLayer() {
void LayerTreeHost::CommitComplete() {
source_frame_number_++;
client_->DidCommit();
+ if (did_complete_scale_animation_) {
+ client_->DidCompletePageScaleAnimation();
+ did_complete_scale_animation_ = false;
+ }
}
void LayerTreeHost::SetOutputSurface(scoped_ptr<OutputSurface> surface) {
@@ -788,6 +793,10 @@ bool LayerTreeHost::UpdateLayers(ResourceUpdateQueue* queue) {
return result || next_commit_forces_redraw_;
}
+void LayerTreeHost::DidCompletePageScaleAnimation() {
+ did_complete_scale_animation_ = true;
+}
+
static Layer* FindFirstScrollableLayer(Layer* layer) {
if (!layer)
return NULL;
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 9069b9d..74ecfa3 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -135,6 +135,9 @@ class CC_EXPORT LayerTreeHost {
void DeleteContentsTexturesOnImplThread(ResourceProvider* resource_provider);
bool UpdateLayers(ResourceUpdateQueue* queue);
+ // Called when the compositor completed page scale animation.
+ void DidCompletePageScaleAnimation();
+
LayerTreeHostClient* client() { return client_; }
const base::WeakPtr<InputHandler>& GetInputHandler() {
return input_handler_weak_ptr_;
@@ -449,6 +452,10 @@ class CC_EXPORT LayerTreeHost {
scoped_ptr<PendingPageScaleAnimation> pending_page_scale_animation_;
+ // If set, then page scale animation has completed, but the client hasn't been
+ // notified about it yet.
+ bool did_complete_scale_animation_;
+
bool in_paint_layer_contents_;
static const int kTotalFramesToUseForLCDTextMetrics = 50;
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index a09693f..ac025f2 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -49,6 +49,9 @@ class LayerTreeHostClient {
virtual void DidCommitAndDrawFrame() = 0;
virtual void DidCompleteSwapBuffers() = 0;
+ // Called when page scale animation has completed.
+ virtual void DidCompletePageScaleAnimation() = 0;
+
// TODO(simonhong): Makes this to pure virtual function when client
// implementation is ready.
virtual void SendBeginFramesToChildren(const BeginFrameArgs& args) {}
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 526d456..73ad980 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -3106,6 +3106,7 @@ void LayerTreeHostImpl::AnimatePageScale(base::TimeTicks monotonic_time) {
page_scale_animation_ = nullptr;
client_->SetNeedsCommitOnImplThread();
client_->RenewTreePriority();
+ client_->DidCompletePageScaleAnimationOnImplThread();
} else {
SetNeedsAnimate();
}
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 98d7e4b..9f146ca 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -108,6 +108,9 @@ class LayerTreeHostImplClient {
virtual void DidActivateSyncTree() = 0;
virtual void DidPrepareTiles() = 0;
+ // Called when page scale animation has completed on the impl thread.
+ virtual void DidCompletePageScaleAnimationOnImplThread() = 0;
+
protected:
virtual ~LayerTreeHostImplClient() {}
};
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index fb2fdb6..f03ab6b 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -92,6 +92,7 @@ class LayerTreeHostImplTest : public testing::Test,
did_request_redraw_(false),
did_request_animate_(false),
did_request_prepare_tiles_(false),
+ did_complete_page_scale_animation_(false),
reduce_memory_result_(true),
current_limit_bytes_(0),
current_priority_cutoff_value_(0) {
@@ -156,6 +157,9 @@ class LayerTreeHostImplTest : public testing::Test,
}
void DidActivateSyncTree() override {}
void DidPrepareTiles() override {}
+ void DidCompletePageScaleAnimationOnImplThread() override {
+ did_complete_page_scale_animation_ = true;
+ }
void set_reduce_memory_result(bool reduce_memory_result) {
reduce_memory_result_ = reduce_memory_result;
@@ -399,6 +403,7 @@ class LayerTreeHostImplTest : public testing::Test,
bool did_request_redraw_;
bool did_request_animate_;
bool did_request_prepare_tiles_;
+ bool did_complete_page_scale_animation_;
bool reduce_memory_result_;
base::Closure scrollbar_fade_start_;
base::TimeDelta requested_scrollbar_animation_delay_;
@@ -1346,6 +1351,38 @@ TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate) {
ExpectContains(*scroll_info, scroll_layer->id(), gfx::Vector2d(-50, -50));
}
+TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification) {
+ SetupScrollAndContentsLayers(gfx::Size(100, 100));
+ host_impl_->SetViewportSize(gfx::Size(50, 50));
+ DrawFrame();
+
+ LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
+ DCHECK(scroll_layer);
+
+ base::TimeTicks start_time =
+ base::TimeTicks() + base::TimeDelta::FromSeconds(1);
+ base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
+ base::TimeTicks halfway_through_animation = start_time + duration / 2;
+ base::TimeTicks end_time = start_time + duration;
+
+ host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
+ scroll_layer->SetScrollOffset(gfx::ScrollOffset(50, 50));
+
+ did_complete_page_scale_animation_ = false;
+ host_impl_->active_tree()->SetPendingPageScaleAnimation(
+ scoped_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
+ gfx::Vector2d(), false, 2.f, duration)));
+ host_impl_->ActivateSyncTree();
+ host_impl_->Animate(start_time);
+ EXPECT_FALSE(did_complete_page_scale_animation_);
+
+ host_impl_->Animate(halfway_through_animation);
+ EXPECT_FALSE(did_complete_page_scale_animation_);
+
+ host_impl_->Animate(end_time);
+ EXPECT_TRUE(did_complete_page_scale_animation_);
+}
+
class LayerTreeHostImplOverridePhysicalTime : public LayerTreeHostImpl {
public:
LayerTreeHostImplOverridePhysicalTime(
diff --git a/cc/trees/layer_tree_host_unittest_no_message_loop.cc b/cc/trees/layer_tree_host_unittest_no_message_loop.cc
index 3e87b55..75eda92 100644
--- a/cc/trees/layer_tree_host_unittest_no_message_loop.cc
+++ b/cc/trees/layer_tree_host_unittest_no_message_loop.cc
@@ -78,6 +78,7 @@ class LayerTreeHostNoMessageLoopTest
void DidCommit() override { did_commit_ = true; }
void DidCommitAndDrawFrame() override { did_commit_and_draw_frame_ = true; }
void DidCompleteSwapBuffers() override {}
+ void DidCompletePageScaleAnimation() override {}
// LayerTreeHostSingleThreadClient overrides.
void DidPostSwapBuffers() override {}
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index adc42c9..e29f0d1 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -447,6 +447,10 @@ void SingleThreadProxy::DidPrepareTiles() {
scheduler_on_impl_thread_->DidPrepareTiles();
}
+void SingleThreadProxy::DidCompletePageScaleAnimationOnImplThread() {
+ layer_tree_host_->DidCompletePageScaleAnimation();
+}
+
void SingleThreadProxy::UpdateRendererCapabilitiesOnImplThread() {
DCHECK(IsImplThread());
renderer_capabilities_for_main_thread_ =
diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h
index bdc1030..a6c348a 100644
--- a/cc/trees/single_thread_proxy.h
+++ b/cc/trees/single_thread_proxy.h
@@ -106,6 +106,7 @@ class CC_EXPORT SingleThreadProxy : public Proxy,
base::TimeDelta delay) override {}
void DidActivateSyncTree() override;
void DidPrepareTiles() override;
+ void DidCompletePageScaleAnimationOnImplThread() override;
void SetDebugState(const LayerTreeDebugState& debug_state) override {}
void RequestNewOutputSurface();
diff --git a/cc/trees/thread_proxy.cc b/cc/trees/thread_proxy.cc
index 243e603..4b360a4 100644
--- a/cc/trees/thread_proxy.cc
+++ b/cc/trees/thread_proxy.cc
@@ -267,6 +267,11 @@ void ThreadProxy::SendCommitRequestToImplThreadIfNeeded() {
impl_thread_weak_ptr_));
}
+void ThreadProxy::DidCompletePageScaleAnimation() {
+ DCHECK(IsMainThread());
+ layer_tree_host()->DidCompletePageScaleAnimation();
+}
+
const RendererCapabilities& ThreadProxy::GetRendererCapabilities() const {
DCHECK(IsMainThread());
DCHECK(!layer_tree_host()->output_surface_lost());
@@ -1353,4 +1358,11 @@ void ThreadProxy::DidPrepareTiles() {
impl().scheduler->DidPrepareTiles();
}
+void ThreadProxy::DidCompletePageScaleAnimationOnImplThread() {
+ DCHECK(IsImplThread());
+ Proxy::MainThreadTaskRunner()->PostTask(
+ FROM_HERE, base::Bind(&ThreadProxy::DidCompletePageScaleAnimation,
+ main_thread_weak_ptr_));
+}
+
} // namespace cc
diff --git a/cc/trees/thread_proxy.h b/cc/trees/thread_proxy.h
index f8808f9..0d15b5a 100644
--- a/cc/trees/thread_proxy.h
+++ b/cc/trees/thread_proxy.h
@@ -209,6 +209,7 @@ class CC_EXPORT ThreadProxy : public Proxy,
base::TimeDelta delay) override;
void DidActivateSyncTree() override;
void DidPrepareTiles() override;
+ void DidCompletePageScaleAnimationOnImplThread() override;
// SchedulerClient implementation
void WillBeginImplFrame(const BeginFrameArgs& args) override;
@@ -251,6 +252,7 @@ class CC_EXPORT ThreadProxy : public Proxy,
void DidInitializeOutputSurface(bool success,
const RendererCapabilities& capabilities);
void SendCommitRequestToImplThreadIfNeeded();
+ void DidCompletePageScaleAnimation();
// Called on impl thread.
struct SchedulerStateRequest;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupWithKeyboardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupWithKeyboardTest.java
new file mode 100644
index 0000000..e023517
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/AutofillPopupWithKeyboardTest.java
@@ -0,0 +1,146 @@
+// Copyright 2015 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.
+
+package org.chromium.chrome.browser.autofill;
+
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.ViewGroup;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.UrlUtils;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
+import org.chromium.chrome.shell.ChromeShellTestBase;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.CriteriaHelper;
+import org.chromium.content.browser.test.util.DOMUtils;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.UiUtils;
+import org.chromium.ui.autofill.AutofillPopup;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Integration tests for interaction of the AutofillPopup and a keyboard.
+ */
+public class AutofillPopupWithKeyboardTest extends ChromeShellTestBase {
+ /**
+ * Test that showing autofill popup and keyboard will not hide the autofill popup.
+ */
+ @MediumTest
+ @Feature({"autofill-keyboard"})
+ public void testShowAutofillPopupAndKeyboardimultaneously()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ launchChromeShellWithUrl(UrlUtils.encodeHtmlDataUri("<html><head>"
+ + "<meta name=\"viewport\""
+ + "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>"
+ + "<body><form method=\"POST\">"
+ + "<input type=\"text\" id=\"fn\" autocomplete=\"given-name\" /><br>"
+ + "<input type=\"text\" id=\"ln\" autocomplete=\"family-name\" /><br>"
+ + "<textarea id=\"sa\" autocomplete=\"street-address\"></textarea><br>"
+ + "<input type=\"text\" id=\"a1\" autocomplete=\"address-line1\" /><br>"
+ + "<input type=\"text\" id=\"a2\" autocomplete=\"address-line2\" /><br>"
+ + "<input type=\"text\" id=\"ct\" autocomplete=\"locality\" /><br>"
+ + "<input type=\"text\" id=\"zc\" autocomplete=\"postal-code\" /><br>"
+ + "<input type=\"text\" id=\"em\" autocomplete=\"email\" /><br>"
+ + "<input type=\"text\" id=\"ph\" autocomplete=\"tel\" /><br>"
+ + "<input type=\"text\" id=\"fx\" autocomplete=\"fax\" /><br>"
+ + "<select id=\"co\" autocomplete=\"country\"><br>"
+ + "<option value=\"BR\">Brazil</option>"
+ + "<option value=\"US\">United States</option>"
+ + "</select>"
+ + "<input type=\"submit\" />"
+ + "</form></body></html>"));
+ assertTrue(waitForActiveShellToBeDoneLoading());
+ new AutofillTestHelper().setProfile(new AutofillProfile("", "https://www.example.com",
+ "John Smith", "Acme Inc", "1 Main\nApt A", "CA", "San Francisco", "", "94102", "",
+ "US", "(415) 888-9999", "john@acme.inc", "en"));
+ final AtomicReference<ContentViewCore> viewCoreRef = new AtomicReference<ContentViewCore>();
+ final AtomicReference<WebContents> webContentsRef = new AtomicReference<WebContents>();
+ final AtomicReference<ViewGroup> viewRef = new AtomicReference<ViewGroup>();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ viewCoreRef.set(getActivity().getActiveContentViewCore());
+ webContentsRef.set(viewCoreRef.get().getWebContents());
+ viewRef.set(viewCoreRef.get().getContainerView());
+ }
+ });
+ assertTrue(DOMUtils.waitForNonZeroNodeBounds(webContentsRef.get(), "fn"));
+
+ // Click on the unfocused input element for the first time to focus on it. This brings up
+ // the keyboard, but does not show the autofill popup.
+ DOMUtils.clickNode(this, viewCoreRef.get(), "fn");
+ waitForKeyboardShown(true);
+
+ // Hide the keyboard while still keeping the focus on the input field.
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ UiUtils.hideKeyboard(viewRef.get());
+ }
+ });
+ waitForKeyboardShown(false);
+
+ // Click on the focused input element for the second time. This brings up the autofill popup
+ // and shows the keyboard at the same time. Showing the keyboard should not hide the
+ // autofill popup.
+ DOMUtils.clickNode(this, viewCoreRef.get(), "fn");
+ waitForKeyboardShown(true);
+
+ // Verify that the autofill popup is showing.
+ assertTrue("Autofill Popup anchor view was never added.",
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return viewRef.get().findViewById(R.id.dropdown_popup_window) != null;
+ }
+ }));
+ Object popupObject = ThreadUtils.runOnUiThreadBlocking(new Callable<Object>() {
+ @Override
+ public Object call() {
+ return viewRef.get().findViewById(R.id.dropdown_popup_window).getTag();
+ }
+ });
+ assertTrue(popupObject instanceof AutofillPopup);
+ final AutofillPopup popup = (AutofillPopup) popupObject;
+ assertTrue("Autofill Popup was never shown.",
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return popup.isShowing();
+ }
+ }));
+ }
+
+ /**
+ * Wait until the keyboard is showing and notify the ContentViewCore that its height was changed
+ * on the UI thread.
+ */
+ private void waitForKeyboardShown(final boolean shown) throws InterruptedException {
+ assertTrue((shown ? "Keyboard was never shown" : "Keyboard was never hidden"),
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return shown == UiUtils.isKeyboardShowing(
+ getActivity(),
+ getActivity().getActiveContentViewCore().getContainerView());
+ }
+ }));
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ ContentViewCore viewCore = getActivity().getActiveContentViewCore();
+ viewCore.onSizeChanged(viewCore.getViewportWidthPix(),
+ viewCore.getViewportHeightPix() + (shown ? -100 : 100),
+ viewCore.getViewportWidthPix(), viewCore.getViewportHeightPix());
+ }
+ });
+ }
+}
diff --git a/chrome/renderer/autofill/page_click_tracker_browsertest.cc b/chrome/renderer/autofill/page_click_tracker_browsertest.cc
index 6495254..84231f6 100644
--- a/chrome/renderer/autofill/page_click_tracker_browsertest.cc
+++ b/chrome/renderer/autofill/page_click_tracker_browsertest.cc
@@ -8,11 +8,13 @@
#include "components/autofill/content/renderer/page_click_tracker.h"
#include "content/public/renderer/render_view.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/WebFloatPoint.h"
+#include "third_party/WebKit/public/platform/WebSize.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebInputElement.h"
+#include "third_party/WebKit/public/web/WebSettings.h"
#include "third_party/WebKit/public/web/WebTextAreaElement.h"
#include "third_party/WebKit/public/web/WebView.h"
-#include "third_party/WebKit/public/platform/WebSize.h"
#include "ui/events/keycodes/keyboard_codes.h"
namespace autofill {
@@ -50,12 +52,17 @@ class PageClickTrackerTest : public ChromeRenderViewTest {
// Rather than make it do so for the test, we create a new object.
page_click_tracker_.reset(new PageClickTracker(view_, &test_listener_));
+ // Must be set before loading HTML.
+ view_->GetWebView()->setDefaultPageScaleLimits(1, 4);
+ view_->GetWebView()->settings()->setPinchVirtualViewportEnabled(true);
+
LoadHTML("<form>"
" <input type='text' id='text_1'></input><br>"
" <input type='text' id='text_2'></input><br>"
" <textarea id='textarea_1'></textarea><br>"
" <textarea id='textarea_2'></textarea><br>"
" <input type='button' id='button'></input><br>"
+ " <input type='button' id='button_2' disabled></input><br>"
"</form>");
GetWebWidget()->resize(blink::WebSize(500, 500));
GetWebWidget()->setFocus(true);
@@ -64,6 +71,10 @@ class PageClickTrackerTest : public ChromeRenderViewTest {
textarea_ = document.getElementById("textarea_1");
ASSERT_FALSE(text_.isNull());
ASSERT_FALSE(textarea_.isNull());
+
+ // Enable show-ime event when element is focused by indicating that a user
+ // gesture has been processed since load.
+ EXPECT_TRUE(SimulateElementClick("button"));
}
void TearDown() override {
@@ -74,29 +85,6 @@ class PageClickTrackerTest : public ChromeRenderViewTest {
ChromeRenderViewTest::TearDown();
}
- // Simulates a click on the given element and then waits for the stack
- // to unwind.
- void SendElementClick(const std::string& element_id) {
- EXPECT_TRUE(SimulateElementClick(element_id));
- ProcessPendingMessages();
- }
-
- // Send all the messages required for a complete key press.
- void SendKeyPress(int key_code) {
- blink::WebKeyboardEvent keyboard_event;
- keyboard_event.windowsKeyCode = key_code;
- keyboard_event.setKeyIdentifierFromWindowsKeyCode();
-
- keyboard_event.type = blink::WebInputEvent::RawKeyDown;
- SendWebKeyboardEvent(keyboard_event);
-
- keyboard_event.type = blink::WebInputEvent::Char;
- SendWebKeyboardEvent(keyboard_event);
-
- keyboard_event.type = blink::WebInputEvent::KeyUp;
- SendWebKeyboardEvent(keyboard_event);
- }
-
scoped_ptr<PageClickTracker> page_click_tracker_;
TestPageClickListener test_listener_;
blink::WebElement text_;
@@ -108,43 +96,96 @@ class PageClickTrackerTest : public ChromeRenderViewTest {
TEST_F(PageClickTrackerTest, PageClickTrackerInputClicked) {
EXPECT_NE(text_, text_.document().focusedElement());
// Click the text field once.
- SendElementClick("text_1");
+ EXPECT_TRUE(SimulateElementClick("text_1"));
EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
EXPECT_FALSE(test_listener_.was_focused_);
EXPECT_TRUE(text_ == test_listener_.form_control_element_clicked_);
test_listener_.ClearResults();
// Click the text field again to test that was_focused_ is set correctly.
- SendElementClick("text_1");
+ EXPECT_TRUE(SimulateElementClick("text_1"));
EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
EXPECT_TRUE(test_listener_.was_focused_);
EXPECT_TRUE(text_ == test_listener_.form_control_element_clicked_);
test_listener_.ClearResults();
// Click the button, no notification should happen (this is not a text-input).
- SendElementClick("button");
+ EXPECT_TRUE(SimulateElementClick("button"));
EXPECT_FALSE(test_listener_.form_control_element_clicked_called_);
}
+TEST_F(PageClickTrackerTest, PageClickTrackerInputFocusedAndClicked) {
+ // Focus the text field without a click.
+ ExecuteJavaScript("document.getElementById('text_1').focus();");
+ EXPECT_FALSE(test_listener_.form_control_element_clicked_called_);
+ test_listener_.ClearResults();
+
+ // Click the focused text field to test that was_focused_ is set correctly.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
+ EXPECT_TRUE(test_listener_.was_focused_);
+ EXPECT_TRUE(text_ == test_listener_.form_control_element_clicked_);
+}
+
// Tests that PageClickTracker does notify correctly when a textarea
// node is clicked.
TEST_F(PageClickTrackerTest, PageClickTrackerTextAreaClicked) {
// Click the textarea field once.
- SendElementClick("textarea_1");
+ EXPECT_TRUE(SimulateElementClick("textarea_1"));
EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
EXPECT_FALSE(test_listener_.was_focused_);
EXPECT_TRUE(textarea_ == test_listener_.form_control_element_clicked_);
test_listener_.ClearResults();
// Click the textarea field again to test that was_focused_ is set correctly.
- SendElementClick("textarea_1");
+ EXPECT_TRUE(SimulateElementClick("textarea_1"));
EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
EXPECT_TRUE(test_listener_.was_focused_);
EXPECT_TRUE(textarea_ == test_listener_.form_control_element_clicked_);
test_listener_.ClearResults();
// Click the button, no notification should happen (this is not a text-input).
- SendElementClick("button");
+ EXPECT_TRUE(SimulateElementClick("button"));
+ EXPECT_FALSE(test_listener_.form_control_element_clicked_called_);
+}
+
+TEST_F(PageClickTrackerTest, PageClickTrackerTextAreaFocusedAndClicked) {
+ // Focus the textarea without a click.
+ ExecuteJavaScript("document.getElementById('textarea_1').focus();");
+ EXPECT_FALSE(test_listener_.form_control_element_clicked_called_);
+ test_listener_.ClearResults();
+
+ // Click the focused text field to test that was_focused_ is set correctly.
+ EXPECT_TRUE(SimulateElementClick("textarea_1"));
+ EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
+ EXPECT_TRUE(test_listener_.was_focused_);
+ EXPECT_TRUE(textarea_ == test_listener_.form_control_element_clicked_);
+ test_listener_.ClearResults();
+}
+
+TEST_F(PageClickTrackerTest, PageClickTrackerScaledTextareaClicked) {
+ EXPECT_NE(text_, text_.document().focusedElement());
+ view_->GetWebView()->setPageScaleFactor(3);
+ view_->GetWebView()->setPinchViewportOffset(blink::WebFloatPoint(50, 50));
+
+ // Click textarea_1.
+ SimulatePointClick(gfx::Point(30, 30));
+ EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
+ EXPECT_FALSE(test_listener_.was_focused_);
+ EXPECT_TRUE(textarea_ == test_listener_.form_control_element_clicked_);
+}
+
+TEST_F(PageClickTrackerTest, PageClickTrackerDisabledInputClickedNoEvent) {
+ EXPECT_NE(text_, text_.document().focusedElement());
+ // Click the text field once.
+ EXPECT_TRUE(SimulateElementClick("text_1"));
+ EXPECT_TRUE(test_listener_.form_control_element_clicked_called_);
+ EXPECT_FALSE(test_listener_.was_focused_);
+ EXPECT_TRUE(text_ == test_listener_.form_control_element_clicked_);
+ test_listener_.ClearResults();
+
+ // Click the disabled element.
+ EXPECT_TRUE(SimulateElementClick("button_2"));
EXPECT_FALSE(test_listener_.form_control_element_clicked_called_);
}
diff --git a/chrome/renderer/autofill/password_generation_agent_browsertest.cc b/chrome/renderer/autofill/password_generation_agent_browsertest.cc
index b93cff6..a093f32 100644
--- a/chrome/renderer/autofill/password_generation_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_generation_agent_browsertest.cc
@@ -85,6 +85,14 @@ class PasswordGenerationAgentTest : public ChromeRenderViewTest {
password_generation_->clear_messages();
}
+ void LoadHTMLWithUserGesture(const char* html) {
+ LoadHTML(html);
+
+ // Enable show-ime event when element is focused by indicating that a user
+ // gesture has been processed since load.
+ EXPECT_TRUE(SimulateElementClick("dummy"));
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(PasswordGenerationAgentTest);
};
@@ -93,6 +101,7 @@ const char kSigninFormHTML[] =
"<FORM name = 'blah' action = 'http://www.random.com/'> "
" <INPUT type = 'text' id = 'username'/> "
" <INPUT type = 'password' id = 'password'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
" <INPUT type = 'submit' value = 'LOGIN' />"
"</FORM>";
@@ -103,6 +112,19 @@ const char kAccountCreationFormHTML[] =
" autocomplete = 'off' size = 5/>"
" <INPUT type = 'password' id = 'second_password' size = 5/> "
" <INPUT type = 'text' id = 'address'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
+ " <INPUT type = 'submit' value = 'LOGIN' />"
+ "</FORM>";
+
+const char kDisabledElementAccountCreationFormHTML[] =
+ "<FORM name = 'blah' action = 'http://www.random.com/'> "
+ " <INPUT type = 'text' id = 'username'/> "
+ " <INPUT type = 'password' id = 'first_password' "
+ " autocomplete = 'off' size = 5/>"
+ " <INPUT type = 'password' id = 'second_password' size = 5/> "
+ " <INPUT type = 'text' id = 'address'/> "
+ " <INPUT type = 'text' id = 'disabled' disabled/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
" <INPUT type = 'submit' value = 'LOGIN' />"
"</FORM>";
@@ -111,6 +133,7 @@ const char kHiddenPasswordAccountCreationFormHTML[] =
" <INPUT type = 'text' id = 'username'/> "
" <INPUT type = 'password' id = 'first_password'/> "
" <INPUT type = 'password' id = 'second_password' style='display:none'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
" <INPUT type = 'submit' value = 'LOGIN' />"
"</FORM>";
@@ -119,6 +142,7 @@ const char kInvalidActionAccountCreationFormHTML[] =
" <INPUT type = 'text' id = 'username'/> "
" <INPUT type = 'password' id = 'first_password'/> "
" <INPUT type = 'password' id = 'second_password'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
" <INPUT type = 'submit' value = 'LOGIN' />"
"</FORM>";
@@ -127,6 +151,7 @@ const char kMultipleAccountCreationFormHTML[] =
" <INPUT type = 'text' id = 'random'/> "
" <INPUT type = 'text' id = 'username'/> "
" <INPUT type = 'password' id = 'password'/> "
+ " <INPUT type = 'button' id = 'dummy'/> "
" <INPUT type = 'submit' value = 'LOGIN' />"
"</FORM>"
"<FORM name = 'signup' action = 'http://www.random.com/signup'> "
@@ -152,29 +177,29 @@ const char ChangeDetectionScript[] =
TEST_F(PasswordGenerationAgentTest, DetectionTest) {
// Don't shown the icon for non account creation forms.
- LoadHTML(kSigninFormHTML);
+ LoadHTMLWithUserGesture(kSigninFormHTML);
ExpectPasswordGenerationAvailable("password", false);
// We don't show the decoration yet because the feature isn't enabled.
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
ExpectPasswordGenerationAvailable("first_password", false);
// Pretend like We have received message indicating site is not blacklisted,
// and we have received message indicating the form is classified as
// ACCOUNT_CREATION_FORM form Autofill server. We should show the icon.
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", true);
// Hidden fields are not treated differently.
- LoadHTML(kHiddenPasswordAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kHiddenPasswordAccountCreationFormHTML);
SetNotBlacklistedMessage(kHiddenPasswordAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", true);
// This doesn't trigger because the form action is invalid.
- LoadHTML(kInvalidActionAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kInvalidActionAccountCreationFormHTML);
SetNotBlacklistedMessage(kInvalidActionAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", false);
@@ -184,7 +209,7 @@ TEST_F(PasswordGenerationAgentTest, FillTest) {
// Make sure that we are enabled before loading HTML.
std::string html = std::string(kAccountCreationFormHTML) +
ChangeDetectionScript;
- LoadHTML(html.c_str());
+ LoadHTMLWithUserGesture(html.c_str());
SetNotBlacklistedMessage(html.c_str());
SetAccountCreationFormsDetectedMessage(0);
@@ -234,7 +259,7 @@ TEST_F(PasswordGenerationAgentTest, FillTest) {
}
TEST_F(PasswordGenerationAgentTest, EditingTest) {
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
@@ -284,27 +309,27 @@ TEST_F(PasswordGenerationAgentTest, EditingTest) {
TEST_F(PasswordGenerationAgentTest, BlacklistedTest) {
// Did not receive not blacklisted message. Don't show password generation
// icon.
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", false);
// Receive one not blacklisted message for non account creation form. Don't
// show password generation icon.
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kSigninFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", false);
- // Receive one not blackliste message for account creation form. Show password
- // generation icon.
- LoadHTML(kAccountCreationFormHTML);
+ // Receive one not blacklisted message for account creation form. Show
+ // password generation icon.
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", true);
// Receive two not blacklisted messages, one is for account creation form and
// the other is not. Show password generation icon.
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kSigninFormHTML);
SetAccountCreationFormsDetectedMessage(0);
@@ -312,15 +337,15 @@ TEST_F(PasswordGenerationAgentTest, BlacklistedTest) {
}
TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) {
- // Did not receive account creation forms detected messege. Don't show
+ // Did not receive account creation forms detected message. Don't show
// password generation icon.
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
ExpectPasswordGenerationAvailable("first_password", false);
// Receive the account creation forms detected message. Show password
// generation icon.
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", true);
@@ -329,7 +354,7 @@ TEST_F(PasswordGenerationAgentTest, AccountCreationFormsDetectedTest) {
TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) {
base::HistogramTester histogram_tester;
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
ExpectPasswordGenerationAvailable("first_password", true);
@@ -397,14 +422,14 @@ TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) {
// Focusing the password field will bring up the generation UI again.
ExecuteJavaScript("document.getElementById('first_password').focus();");
- EXPECT_EQ(1u, password_generation_->messages().size());
+ ASSERT_EQ(1u, password_generation_->messages().size());
EXPECT_EQ(AutofillHostMsg_ShowPasswordGenerationPopup::ID,
password_generation_->messages()[0]->type());
password_generation_->clear_messages();
// Loading a different page triggers UMA stat upload. Verify that only one
// display event is sent even though
- LoadHTML(kSigninFormHTML);
+ LoadHTMLWithUserGesture(kSigninFormHTML);
histogram_tester.ExpectBucketCount(
"PasswordGeneration.Event",
@@ -413,7 +438,7 @@ TEST_F(PasswordGenerationAgentTest, MaximumOfferSize) {
}
TEST_F(PasswordGenerationAgentTest, DynamicFormTest) {
- LoadHTML(kSigninFormHTML);
+ LoadHTMLWithUserGesture(kSigninFormHTML);
SetNotBlacklistedMessage(kSigninFormHTML);
ExecuteJavaScript(
@@ -438,7 +463,7 @@ TEST_F(PasswordGenerationAgentTest, DynamicFormTest) {
// This needs to come after the DOM has been modified.
SetAccountCreationFormsDetectedMessage(1);
- // TODO(gcasto): I'm slighty worried about flakes in this test where
+ // TODO(gcasto): I'm slightly worried about flakes in this test where
// didAssociateFormControls() isn't called. If this turns out to be a problem
// adding a call to OnDynamicFormsSeen(GetMainFrame()) will fix it, though
// it will weaken the test.
@@ -448,7 +473,7 @@ TEST_F(PasswordGenerationAgentTest, DynamicFormTest) {
TEST_F(PasswordGenerationAgentTest, MultiplePasswordFormsTest) {
// If two forms on the page looks like possible account creation forms, make
// sure to trigger on the one that is specified from Autofill.
- LoadHTML(kMultipleAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kMultipleAccountCreationFormHTML);
SetNotBlacklistedMessage(kMultipleAccountCreationFormHTML);
// Should trigger on the second form.
@@ -459,7 +484,7 @@ TEST_F(PasswordGenerationAgentTest, MultiplePasswordFormsTest) {
}
TEST_F(PasswordGenerationAgentTest, MessagesAfterAccountSignupFormFound) {
- LoadHTML(kAccountCreationFormHTML);
+ LoadHTMLWithUserGesture(kAccountCreationFormHTML);
SetNotBlacklistedMessage(kAccountCreationFormHTML);
SetAccountCreationFormsDetectedMessage(0);
@@ -475,4 +500,20 @@ TEST_F(PasswordGenerationAgentTest, MessagesAfterAccountSignupFormFound) {
ExpectPasswordGenerationAvailable("first_password", true);
}
+// Losing focus should not trigger a password generation popup.
+TEST_F(PasswordGenerationAgentTest, BlurTest) {
+ LoadHTMLWithUserGesture(kDisabledElementAccountCreationFormHTML);
+ SetNotBlacklistedMessage(kDisabledElementAccountCreationFormHTML);
+ SetAccountCreationFormsDetectedMessage(0);
+
+ // Focus on the first password field: password generation popup should show
+ // up.
+ ExpectPasswordGenerationAvailable("first_password", true);
+
+ // Remove focus from everywhere by clicking an unfocusable element: password
+ // generation popup should not show up.
+ EXPECT_TRUE(SimulateElementClick("disabled"));
+ EXPECT_EQ(0u, password_generation_->messages().size());
+}
+
} // namespace autofill
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 2c48d11..93d06956 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -49,6 +49,7 @@
using blink::WebAutofillClient;
using blink::WebConsoleMessage;
+using blink::WebDocument;
using blink::WebElement;
using blink::WebElementCollection;
using blink::WebFormControlElement;
@@ -215,12 +216,6 @@ void AutofillAgent::FocusedNodeChanged(const WebNode& node) {
if (node.document().frame() != render_frame()->GetWebFrame())
return;
- if (password_generation_agent_ &&
- password_generation_agent_->FocusedNodeHasChanged(node)) {
- is_popup_possibly_visible_ = true;
- return;
- }
-
WebElement web_element = node.toConst<WebElement>();
if (!web_element.document().frame())
@@ -229,12 +224,24 @@ void AutofillAgent::FocusedNodeChanged(const WebNode& node) {
const WebInputElement* element = toWebInputElement(&web_element);
if (!element || !element->isEnabled() || element->isReadOnly() ||
- !element->isTextField() || element->isPasswordField())
+ !element->isTextField())
return;
element_ = *element;
}
+void AutofillAgent::FocusChangeComplete() {
+ WebDocument doc = render_frame()->GetWebFrame()->document();
+ WebElement focused_element;
+ if (!doc.isNull())
+ focused_element = doc.focusedElement();
+
+ if (!focused_element.isNull() && password_generation_agent_ &&
+ password_generation_agent_->FocusedNodeHasChanged(focused_element)) {
+ is_popup_possibly_visible_ = true;
+ }
+}
+
void AutofillAgent::didRequestAutocomplete(
const WebFormElement& form) {
DCHECK_EQ(form.document().frame(), render_frame()->GetWebFrame());
@@ -783,4 +790,8 @@ void AutofillAgent::LegacyAutofillAgent::FocusedNodeChanged(
agent_->FocusedNodeChanged(node);
}
+void AutofillAgent::LegacyAutofillAgent::FocusChangeComplete() {
+ agent_->FocusChangeComplete();
+}
+
} // namespace autofill
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index 13c35ad..c03ce87 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -67,6 +67,7 @@ class AutofillAgent : public content::RenderFrameObserver,
// content::RenderViewObserver:
void OnDestruct() override;
void FocusedNodeChanged(const blink::WebNode& node) override;
+ void FocusChangeComplete() override;
AutofillAgent* agent_;
@@ -117,6 +118,7 @@ class AutofillAgent : public content::RenderFrameObserver,
// Pass-through from LegacyAutofillAgent. This correlates with the
// RenderViewObserver method.
void FocusedNodeChanged(const blink::WebNode& node);
+ void FocusChangeComplete();
// PageClickListener:
void FormControlElementClicked(const blink::WebFormControlElement& element,
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 8c62c8b7..93aeaa0 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -1364,7 +1364,7 @@ bool IsWebElementEmpty(const blink::WebElement& element) {
return true;
}
-gfx::RectF GetScaledBoundingBox(float scale, WebFormControlElement* element) {
+gfx::RectF GetScaledBoundingBox(float scale, WebElement* element) {
gfx::Rect bounding_box(element->boundsInViewportSpace());
return gfx::RectF(bounding_box.x() * scale,
bounding_box.y() * scale,
diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h
index 061332b..b3a299d 100644
--- a/components/autofill/content/renderer/form_autofill_util.h
+++ b/components/autofill/content/renderer/form_autofill_util.h
@@ -186,8 +186,7 @@ bool IsWebpageEmpty(const blink::WebFrame* frame);
bool IsWebElementEmpty(const blink::WebElement& element);
// Return a gfx::RectF that is the bounding box for |element| scaled by |scale|.
-gfx::RectF GetScaledBoundingBox(float scale,
- blink::WebFormControlElement* element);
+gfx::RectF GetScaledBoundingBox(float scale, blink::WebElement* element);
} // namespace autofill
diff --git a/components/autofill/content/renderer/page_click_tracker.cc b/components/autofill/content/renderer/page_click_tracker.cc
index 14ceda8..4e54f9e 100644
--- a/components/autofill/content/renderer/page_click_tracker.cc
+++ b/components/autofill/content/renderer/page_click_tracker.cc
@@ -4,13 +4,9 @@
#include "components/autofill/content/renderer/page_click_tracker.h"
-#include "base/bind.h"
-#include "base/message_loop/message_loop.h"
#include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/page_click_listener.h"
#include "content/public/renderer/render_view.h"
-#include "third_party/WebKit/public/platform/WebString.h"
-#include "third_party/WebKit/public/web/WebDOMMouseEvent.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebInputElement.h"
#include "third_party/WebKit/public/web/WebInputEvent.h"
@@ -18,19 +14,13 @@
#include "third_party/WebKit/public/web/WebTextAreaElement.h"
#include "third_party/WebKit/public/web/WebView.h"
-using blink::WebDOMEvent;
-using blink::WebDOMMouseEvent;
using blink::WebElement;
-using blink::WebFormControlElement;
-using blink::WebFrame;
using blink::WebGestureEvent;
using blink::WebInputElement;
using blink::WebInputEvent;
using blink::WebMouseEvent;
using blink::WebNode;
-using blink::WebString;
using blink::WebTextAreaElement;
-using blink::WebView;
namespace {
@@ -68,8 +58,7 @@ PageClickTracker::PageClickTracker(content::RenderView* render_view,
PageClickListener* listener)
: content::RenderViewObserver(render_view),
was_focused_before_now_(false),
- listener_(listener),
- weak_ptr_factory_(this) {
+ listener_(listener) {
}
PageClickTracker::~PageClickTracker() {
@@ -88,50 +77,48 @@ void PageClickTracker::DidHandleMouseEvent(const WebMouseEvent& event) {
PotentialActivationAt(event.x, event.y);
}
-void PageClickTracker::DidHandleGestureEvent(
- const blink::WebGestureEvent& event) {
- if (event.type != blink::WebGestureEvent::GestureTap)
+void PageClickTracker::DidHandleGestureEvent(const WebGestureEvent& event) {
+ if (event.type != WebGestureEvent::GestureTap)
return;
PotentialActivationAt(event.x, event.y);
}
-void PageClickTracker::FocusedNodeChanged(const blink::WebNode& node) {
+void PageClickTracker::FocusedNodeChanged(const WebNode& node) {
was_focused_before_now_ = false;
- // If the focus change was a result of handling a click or tap, we'll soon get
- // an associated event. Reset |was_focused_before_now_| to true only after the
- // message loop unwinds.
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(&PageClickTracker::SetWasFocused,
- weak_ptr_factory_.GetWeakPtr()));
}
-void PageClickTracker::PotentialActivationAt(int x, int y) {
- blink::WebNode focused_node = render_view()->GetFocusedElement();
- if (focused_node.isNull())
- return;
+void PageClickTracker::FocusChangeComplete() {
+ if (!clicked_node_.isNull()) {
+ const WebInputElement input_element = GetTextWebInputElement(clicked_node_);
+ if (!input_element.isNull()) {
+ listener_->FormControlElementClicked(input_element,
+ was_focused_before_now_);
+ } else {
+ const WebTextAreaElement textarea_element =
+ GetWebTextAreaElement(clicked_node_);
+ if (!textarea_element.isNull()) {
+ listener_->FormControlElementClicked(textarea_element,
+ was_focused_before_now_);
+ }
+ }
+ }
- if (!render_view()->NodeContainsPoint(focused_node, gfx::Point(x, y)))
- return;
+ clicked_node_.reset();
+ was_focused_before_now_ = true;
+}
- const WebInputElement input_element = GetTextWebInputElement(focused_node);
- if (!input_element.isNull()) {
- listener_->FormControlElementClicked(input_element,
- was_focused_before_now_);
+void PageClickTracker::PotentialActivationAt(int x, int y) {
+ WebElement focused_element = render_view()->GetFocusedElement();
+ if (focused_element.isNull())
return;
- }
- const WebTextAreaElement textarea_element =
- GetWebTextAreaElement(focused_node);
- if (!textarea_element.isNull()) {
- listener_->FormControlElementClicked(textarea_element,
- was_focused_before_now_);
+ if (!GetScaledBoundingBox(render_view()->GetWebView()->pageScaleFactor(),
+ &focused_element).Contains(x, y)) {
+ return;
}
-}
-void PageClickTracker::SetWasFocused() {
- was_focused_before_now_ = true;
+ clicked_node_ = focused_element;
}
} // namespace autofill
diff --git a/components/autofill/content/renderer/page_click_tracker.h b/components/autofill/content/renderer/page_click_tracker.h
index 0bcc0f6..9cb6353 100644
--- a/components/autofill/content/renderer/page_click_tracker.h
+++ b/components/autofill/content/renderer/page_click_tracker.h
@@ -16,7 +16,7 @@ namespace autofill {
class PageClickListener;
-// This class is responsible notifiying the associated listener when a node is
+// This class is responsible notifying the associated listener when a node is
// clicked or tapped. It also tracks whether a node was focused before the event
// was handled.
//
@@ -39,12 +39,14 @@ class PageClickTracker : public content::RenderViewObserver {
void DidHandleMouseEvent(const blink::WebMouseEvent& event) override;
void DidHandleGestureEvent(const blink::WebGestureEvent& event) override;
void FocusedNodeChanged(const blink::WebNode& node) override;
+ void FocusChangeComplete() override;
// Called there is a tap or click at |x|, |y|.
void PotentialActivationAt(int x, int y);
- // Sets |was_focused_before_now_| to true.
- void SetWasFocused();
+ // The node that was clicked. Non-null only when the animations caused by
+ // focus change are still ongoing.
+ blink::WebNode clicked_node_;
// This is set to false when the focus changes, then set back to true soon
// afterwards. This helps track whether an event happened after a node was
@@ -54,8 +56,6 @@ class PageClickTracker : public content::RenderViewObserver {
// The listener getting the actual notifications.
PageClickListener* listener_;
- base::WeakPtrFactory<PageClickTracker> weak_ptr_factory_;
-
DISALLOW_COPY_AND_ASSIGN(PageClickTracker);
};
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 0b60c23..9bffa4b 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -84,6 +84,7 @@ class CONTENT_EXPORT CompositorImpl
virtual void DidCommit() override;
virtual void DidCommitAndDrawFrame() override {}
virtual void DidCompleteSwapBuffers() override;
+ virtual void DidCompletePageScaleAnimation() override {}
// LayerTreeHostSingleThreadClient implementation.
virtual void ScheduleComposite() override;
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
index c45d61d..674f172 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
@@ -660,6 +660,12 @@ public class ContentViewCore
}
@Override
+ public void onKeyboardBoundsUnchanged() {
+ assert mWebContents != null;
+ mWebContents.scrollFocusedEditableNodeIntoView();
+ }
+
+ @Override
public View getAttachedView() {
return mContainerView;
}
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java
index 6f49bb4..a77aaa3 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java
@@ -4,6 +4,7 @@
package org.chromium.content.browser.input;
+import android.content.res.Configuration;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.SystemClock;
@@ -67,6 +68,11 @@ public class ImeAdapter {
void onDismissInput();
/**
+ * Called when the keyboard could not be shown due to the hardware keyboard being present.
+ */
+ void onKeyboardBoundsUnchanged();
+
+ /**
* @return View that the keyboard should be attached to.
*/
View getAttachedView();
@@ -274,8 +280,13 @@ public class ImeAdapter {
private void showKeyboard() {
mIsShowWithoutHideOutstanding = true;
- mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0,
- mViewEmbedder.getNewShowKeyboardReceiver());
+ if (mViewEmbedder.getAttachedView().getResources().getConfiguration().keyboard
+ == Configuration.KEYBOARD_NOKEYS) {
+ mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0,
+ mViewEmbedder.getNewShowKeyboardReceiver());
+ } else {
+ mViewEmbedder.onKeyboardBoundsUnchanged();
+ }
}
private void dismissInput(boolean unzoomIfNeeded) {
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/AdapterInputConnectionTest.java b/content/public/android/javatests/src/org/chromium/content/browser/input/AdapterInputConnectionTest.java
index 96a52d2..deb151e 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/input/AdapterInputConnectionTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/AdapterInputConnectionTest.java
@@ -193,6 +193,9 @@ public class AdapterInputConnectionTest extends ContentShellTestBase {
public void onDismissInput() {}
@Override
+ public void onKeyboardBoundsUnchanged() {}
+
+ @Override
public View getAttachedView() {
return null;
}
diff --git a/content/public/renderer/render_view_observer.h b/content/public/renderer/render_view_observer.h
index 9b8c71e..6952735 100644
--- a/content/public/renderer/render_view_observer.h
+++ b/content/public/renderer/render_view_observer.h
@@ -90,6 +90,11 @@ class CONTENT_EXPORT RenderViewObserver : public IPC::Listener,
virtual void Navigate(const GURL& url) {}
virtual void ClosePage() {}
+ // This indicates that animations to scroll the focused element into view (if
+ // any) have completed. May be called more than once for a single focus. Can
+ // be called from browser, renderer, or compositor.
+ virtual void FocusChangeComplete() {}
+
virtual void OnStop() {}
// IPC::Listener implementation.
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index abda557..549dd9f 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -322,17 +322,23 @@ bool RenderViewTest::SimulateElementClick(const std::string& element_id) {
gfx::Rect bounds = GetElementBounds(element_id);
if (bounds.IsEmpty())
return false;
+ SimulatePointClick(bounds.CenterPoint());
+ return true;
+}
+
+void RenderViewTest::SimulatePointClick(const gfx::Point& point) {
WebMouseEvent mouse_event;
mouse_event.type = WebInputEvent::MouseDown;
mouse_event.button = WebMouseEvent::ButtonLeft;
- mouse_event.x = bounds.CenterPoint().x();
- mouse_event.y = bounds.CenterPoint().y();
+ mouse_event.x = point.x();
+ mouse_event.y = point.y();
mouse_event.clickCount = 1;
- scoped_ptr<IPC::Message> input_message(
- new InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), false));
RenderViewImpl* impl = static_cast<RenderViewImpl*>(view_);
- impl->OnMessageReceived(*input_message);
- return true;
+ impl->OnMessageReceived(
+ InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), false));
+ mouse_event.type = WebInputEvent::MouseUp;
+ impl->OnMessageReceived(
+ InputMsg_HandleInputEvent(0, &mouse_event, ui::LatencyInfo(), false));
}
void RenderViewTest::SetFocused(const blink::WebNode& node) {
diff --git a/content/public/test/render_view_test.h b/content/public/test/render_view_test.h
index 7f1709e..936c329 100644
--- a/content/public/test/render_view_test.h
+++ b/content/public/test/render_view_test.h
@@ -107,6 +107,9 @@ class RenderViewTest : public testing::Test {
// the element was not found).
bool SimulateElementClick(const std::string& element_id);
+ // Sends a left mouse click at the |point|.
+ void SimulatePointClick(const gfx::Point& point);
+
// Simulates |node| being focused.
void SetFocused(const blink::WebNode& node);
diff --git a/content/renderer/gpu/render_widget_compositor.cc b/content/renderer/gpu/render_widget_compositor.cc
index c5986e8..51cf48e 100644
--- a/content/renderer/gpu/render_widget_compositor.cc
+++ b/content/renderer/gpu/render_widget_compositor.cc
@@ -858,6 +858,10 @@ void RenderWidgetCompositor::DidCompleteSwapBuffers() {
widget_->OnSwapBuffersComplete();
}
+void RenderWidgetCompositor::DidCompletePageScaleAnimation() {
+ widget_->DidCompletePageScaleAnimation();
+}
+
void RenderWidgetCompositor::ScheduleAnimation() {
widget_->scheduleAnimation();
}
diff --git a/content/renderer/gpu/render_widget_compositor.h b/content/renderer/gpu/render_widget_compositor.h
index d1e1e76..c36a945 100644
--- a/content/renderer/gpu/render_widget_compositor.h
+++ b/content/renderer/gpu/render_widget_compositor.h
@@ -152,6 +152,7 @@ class CONTENT_EXPORT RenderWidgetCompositor
void DidCommit() override;
void DidCommitAndDrawFrame() override;
void DidCompleteSwapBuffers() override;
+ void DidCompletePageScaleAnimation() override;
void RateLimitSharedMainThreadContext() override;
// cc::LayerTreeHostSingleThreadClient implementation.
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 851e81c..79dce61 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1423,15 +1423,20 @@ void RenderViewImpl::OnScrollFocusedEditableNodeIntoRect(
const gfx::Rect& rect) {
if (has_scrolled_focused_editable_node_into_rect_ &&
rect == rect_for_scrolled_focused_editable_node_) {
+ FocusChangeComplete();
return;
}
blink::WebElement element = GetFocusedElement();
+ bool will_animate = false;
if (!element.isNull() && IsEditableNode(element)) {
rect_for_scrolled_focused_editable_node_ = rect;
has_scrolled_focused_editable_node_into_rect_ = true;
- webview()->scrollFocusedNodeIntoRect(rect);
+ will_animate = webview()->scrollFocusedNodeIntoRect(rect);
}
+
+ if (!will_animate)
+ FocusChangeComplete();
}
void RenderViewImpl::OnSetEditCommandsForNextKeyEvent(
@@ -3601,6 +3606,11 @@ void RenderViewImpl::GetSelectionBounds(gfx::Rect* start, gfx::Rect* end) {
RenderWidget::GetSelectionBounds(start, end);
}
+void RenderViewImpl::FocusChangeComplete() {
+ RenderWidget::FocusChangeComplete();
+ FOR_EACH_OBSERVER(RenderViewObserver, observers_, FocusChangeComplete());
+}
+
void RenderViewImpl::GetCompositionCharacterBounds(
std::vector<gfx::Rect>* bounds) {
DCHECK(bounds);
@@ -3687,6 +3697,10 @@ void RenderViewImpl::InstrumentWillComposite() {
webview()->devToolsAgent()->willComposite();
}
+void RenderViewImpl::DidCompletePageScaleAnimation() {
+ FocusChangeComplete();
+}
+
void RenderViewImpl::SetScreenMetricsEmulationParameters(
float device_scale_factor,
const gfx::Point& root_layer_offset,
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index f3d55fa..c5029e4 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -497,6 +497,7 @@ class CONTENT_EXPORT RenderViewImpl
void OnOrientationChange() override;
ui::TextInputType GetTextInputType() override;
void GetSelectionBounds(gfx::Rect* start, gfx::Rect* end) override;
+ void FocusChangeComplete() override;
void GetCompositionCharacterBounds(
std::vector<gfx::Rect>* character_bounds) override;
void GetCompositionRange(gfx::Range* range) override;
@@ -506,6 +507,7 @@ class CONTENT_EXPORT RenderViewImpl
void InstrumentDidBeginFrame() override;
void InstrumentDidCancelFrame() override;
void InstrumentWillComposite() override;
+ void DidCompletePageScaleAnimation() override;
protected:
explicit RenderViewImpl(const ViewMsg_New_Params& params);
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index c2eefbd..a22d374 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -1230,8 +1230,9 @@ void RenderWidget::OnHandleInputEvent(const blink::WebInputEvent* input_event,
// Show the virtual keyboard if enabled and a user gesture triggers a focus
// change.
if (processed && (input_event->type == WebInputEvent::TouchEnd ||
- input_event->type == WebInputEvent::MouseUp))
+ input_event->type == WebInputEvent::MouseUp)) {
UpdateTextInputState(SHOW_IME_IF_NEEDED, FROM_IME);
+ }
#endif
if (!prevent_default) {
@@ -1242,6 +1243,16 @@ void RenderWidget::OnHandleInputEvent(const blink::WebInputEvent* input_event,
if (WebInputEvent::isTouchEventType(input_event->type))
DidHandleTouchEvent(*(static_cast<const WebTouchEvent*>(input_event)));
}
+
+// TODO(rouslan): Fix ChromeOS and Windows 8 behavior of autofill popup with
+// virtual keyboard.
+#if !defined(OS_ANDROID)
+ // Virtual keyboard is not supported, so react to focus change immediately.
+ if (processed && (input_event->type == WebInputEvent::TouchEnd ||
+ input_event->type == WebInputEvent::MouseUp)) {
+ FocusChangeComplete();
+ }
+#endif
}
void RenderWidget::OnCursorVisibilityChange(bool is_visible) {
@@ -1697,6 +1708,12 @@ void RenderWidget::OnShowImeIfNeeded() {
#if defined(OS_ANDROID) || defined(USE_AURA)
UpdateTextInputState(SHOW_IME_IF_NEEDED, FROM_NON_IME);
#endif
+
+// TODO(rouslan): Fix ChromeOS and Windows 8 behavior of autofill popup with
+// virtual keyboard.
+#if !defined(OS_ANDROID)
+ FocusChangeComplete();
+#endif
}
#if defined(OS_ANDROID)
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 093ea8d9..a9491cc 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -243,6 +243,9 @@ class CONTENT_EXPORT RenderWidget
virtual void InstrumentDidCancelFrame() {}
virtual void InstrumentWillComposite() {}
+ // Called by the compositor when page scale animation completed.
+ virtual void DidCompletePageScaleAnimation() {}
+
// When paused in debugger, we send ack for mouse event early. This ensures
// that we continue receiving mouse moves and pass them to debugger. Returns
// whether we are paused in mouse move event and have sent the ack.
@@ -306,6 +309,10 @@ class CONTENT_EXPORT RenderWidget
void UpdateTextInputState(ShowIme show_ime, ChangeSource change_source);
#endif
+ // Called when animations due to focus change have completed (if any). Can be
+ // called from the renderer, browser, or compositor.
+ virtual void FocusChangeComplete() {}
+
// Checks if the composition range or composition character bounds have been
// changed. If they are changed, the new value will be sent to the browser
// process. This method does nothing when the browser process is not able to
diff --git a/content/test/web_layer_tree_view_impl_for_testing.h b/content/test/web_layer_tree_view_impl_for_testing.h
index 2d316c9..9dabf4c 100644
--- a/content/test/web_layer_tree_view_impl_for_testing.h
+++ b/content/test/web_layer_tree_view_impl_for_testing.h
@@ -84,6 +84,7 @@ class WebLayerTreeViewImplForTesting
void DidCommit() override {}
void DidCommitAndDrawFrame() override {}
void DidCompleteSwapBuffers() override {}
+ void DidCompletePageScaleAnimation() override {}
// cc::LayerTreeHostSingleThreadClient implementation.
void DidPostSwapBuffers() override {}
diff --git a/mojo/services/html_viewer/weblayertreeview_impl.h b/mojo/services/html_viewer/weblayertreeview_impl.h
index 3212643..02cb979 100644
--- a/mojo/services/html_viewer/weblayertreeview_impl.h
+++ b/mojo/services/html_viewer/weblayertreeview_impl.h
@@ -67,6 +67,7 @@ class WebLayerTreeViewImpl : public blink::WebLayerTreeView,
void DidCommit() override;
void DidCommitAndDrawFrame() override;
void DidCompleteSwapBuffers() override;
+ void DidCompletePageScaleAnimation() override {}
void RateLimitSharedMainThreadContext() override {}
// blink::WebLayerTreeView implementation.
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index 94cd1a91..20a8da1 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -264,6 +264,7 @@ class COMPOSITOR_EXPORT Compositor
void DidCommit() override;
void DidCommitAndDrawFrame() override;
void DidCompleteSwapBuffers() override;
+ void DidCompletePageScaleAnimation() override {}
// cc::LayerTreeHostSingleThreadClient implementation.
void ScheduleComposite() override;