Bug 947146 - Remove hover states from elements that get touch tapped, plus some tests. r=mbrubeck a=lsblakk
authorJim Mathies <jmathies@mozilla.com>
Sat, 14 Dec 2013 14:40:56 -0600
changeset 175382 2ebab851cb335cd3502cdfaaf6f498f40000c485
parent 175381 cb28a42ecacfa500ca92a6c54fff60c0de618f0f
child 175383 04a669b65fb3d5037bc0440025dd058bfc64b317
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck, lsblakk
bugs947146
milestone28.0a2
Bug 947146 - Remove hover states from elements that get touch tapped, plus some tests. r=mbrubeck a=lsblakk
browser/metro/base/tests/mochitest/browser_menu_hoverstate.js
browser/metro/base/tests/mochitest/metro.ini
widget/windows/winrt/MetroInput.cpp
widget/windows/winrt/MetroWidget.cpp
widget/windows/winrt/MetroWidget.h
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/tests/mochitest/browser_menu_hoverstate.js
@@ -0,0 +1,147 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+  if (!isLandscapeMode()) {
+    todo(false, "browser_snapped_tests need landscape mode to run.");
+    return;
+  }
+
+  runTests();
+}
+let tabAdded = false;
+
+function setUp() {
+  if (!tabAdded) {
+    yield addTab(chromeRoot + "res/textdivs01.html");
+    tabAdded = true;
+  }
+  yield hideContextUI();
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "gDOMUtils",
+  "@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
+
+const kActiveState = 0x00000001;
+const kHoverState = 0x00000004;
+
+gTests.push({
+  desc: "hover states of menus",
+  setUp: setUp,
+  run: function() {
+    // Clicking on menu items should not leave the clicked menu item
+    // in the :active or :hover state.
+
+    let typesArray = [
+      "copy",
+      "paste"
+    ];
+
+    let promise = waitForEvent(document, "popupshown");
+    ContextMenuUI.showContextMenu({
+      target: null,
+      json: {
+        types: typesArray,
+        string: '',
+        xPos: 1,
+        yPos: 1,
+        leftAligned: true,
+        bottomAligned: true
+    }});
+    yield promise;
+
+    // should be visible
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
+
+    let menuItem = document.getElementById("context-copy");
+    promise = waitForEvent(document, "popuphidden");
+    sendNativeTap(menuItem);
+    yield promise;
+
+    for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+      let item = ContextMenuUI.commands.childNodes[idx];
+      let state = gDOMUtils.getContentState(item);
+      if ((state & kHoverState) || (state & kActiveState)) {
+        ok(false, "found invalid state on context menu item (" + state.toString(2) + ")");
+      }
+    }
+
+    // Do it again, but this time check the visible menu too and
+    // click a different menu item.
+    promise = waitForEvent(document, "popupshown");
+    ContextMenuUI.showContextMenu({
+      target: null,
+      json: {
+        types: typesArray,
+        string: '',
+        xPos: 1,
+        yPos: 1,
+        leftAligned: true,
+        bottomAligned: true
+    }});
+    yield promise;
+
+    // should be visible
+    ok(ContextMenuUI._menuPopup.visible, "is visible");
+
+    for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+      let item = ContextMenuUI.commands.childNodes[idx];
+      let state = gDOMUtils.getContentState(item);
+      if ((state & kHoverState) || (state & kActiveState)) {
+        ok(false, "found invalid state on context menu item (" + state.toString(2) + ")");
+      }
+    }
+
+    menuItem = document.getElementById("context-paste");
+    promise = waitForEvent(document, "popuphidden");
+    sendNativeTap(menuItem);
+    yield promise;
+
+    for (let idx = 0; idx < ContextMenuUI.commands.childNodes.length; idx++) {
+      let item = ContextMenuUI.commands.childNodes[idx];
+      let state = gDOMUtils.getContentState(item);
+      if ((state & kHoverState) || (state & kActiveState)) {
+        ok(false, "found invalid state on context menu item (" + state.toString(2) + ")");
+      }
+    }
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
+
+gTests.push({
+  desc: "hover states of nav bar buttons",
+  setUp: setUp,
+  run: function() {
+    // show nav bar
+    yield showNavBar();
+
+    // tap bookmark button
+    sendNativeTap(Appbar.starButton);
+    yield waitForMs(100);
+    
+    // check hover state
+    let state = gDOMUtils.getContentState(Appbar.starButton);
+    if ((state & kHoverState) || (state & kActiveState)) {
+      ok(false, "found invalid state on star button (" + state.toString(2) + ")");
+    }
+
+    // tap bookmark button
+    sendNativeTap(Appbar.starButton);
+    yield waitForMs(100);
+    
+    // check hover state
+    let state = gDOMUtils.getContentState(Appbar.starButton);
+    if ((state & kHoverState) || (state & kActiveState)) {
+      ok(false, "found invalid state on star button (" + state.toString(2) + ")");
+    }
+  },
+  tearDown: function () {
+    clearNativeTouchSequence();
+  }
+});
+
--- a/browser/metro/base/tests/mochitest/metro.ini
+++ b/browser/metro/base/tests/mochitest/metro.ini
@@ -52,16 +52,17 @@ support-files =
 [browser_tabs.js]
 [browser_test.js]
 [browser_tiles.js]
 [browser_topsites.js]
 [browser_urlbar.js]
 [browser_urlbar_highlightURLs.js]
 [browser_urlbar_trimURLs.js]
 [browser_apzc_basic.js]
+[browser_menu_hoverstate.js]
 
 # These tests have known failures in debug builds
 [browser_selection_basic.js]
 skip-if = debug
 [browser_selection_textarea.js]
 skip-if = debug
 [browser_selection_frame_content.js]
 skip-if = debug
--- a/widget/windows/winrt/MetroInput.cpp
+++ b/widget/windows/winrt/MetroInput.cpp
@@ -11,16 +11,18 @@
 #include "nsTArray.h" // Touch lists
 #include "nsIDOMSimpleGestureEvent.h" // Constants for gesture events
 #include "InputData.h"
 #include "UIABridgePrivate.h"
 #include "MetroAppShell.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "WinUtils.h"
+#include "nsIPresShell.h"
+#include "nsEventStateManager.h"
 
 // System headers (alphabetical)
 #include <windows.ui.core.h> // ABI::Window::UI::Core namespace
 #include <windows.ui.input.h> // ABI::Window::UI::Input namespace
 
 //#define DEBUG_INPUT
 
 // Using declarations
@@ -961,26 +963,24 @@ MetroInput::HandleTap(const Foundation::
 
   LayoutDeviceIntPoint refPoint;
   bool hitTestChrome = TransformRefPoint(aPoint, refPoint);
   if (!hitTestChrome) {
     // Let APZC handle tap/doubletap detection for content.
     return;
   }
 
-  // send mousemove
   WidgetMouseEvent* mouseEvent =
     new WidgetMouseEvent(true, NS_MOUSE_MOVE, mWidget.Get(),
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   mouseEvent->refPoint = refPoint;
   mouseEvent->clickCount = aTapCount;
   mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   DispatchAsyncEventIgnoreStatus(mouseEvent);
 
-  // Send the mousedown
   mouseEvent =
     new WidgetMouseEvent(true, NS_MOUSE_BUTTON_DOWN, mWidget.Get(),
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   mouseEvent->refPoint = refPoint;
   mouseEvent->clickCount = aTapCount;
   mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   mouseEvent->button = WidgetMouseEvent::buttonType::eLeftButton;
   DispatchAsyncEventIgnoreStatus(mouseEvent);
@@ -988,32 +988,16 @@ MetroInput::HandleTap(const Foundation::
   mouseEvent =
     new WidgetMouseEvent(true, NS_MOUSE_BUTTON_UP, mWidget.Get(),
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   mouseEvent->refPoint = refPoint;
   mouseEvent->clickCount = aTapCount;
   mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   mouseEvent->button = WidgetMouseEvent::buttonType::eLeftButton;
   DispatchAsyncEventIgnoreStatus(mouseEvent);
-
-  // Send one more mousemove to avoid getting a hover state.
-  // In the Metro environment for any application, a tap does not imply a
-  // mouse cursor move.  In desktop environment for any application a tap
-  // does imply a cursor move.
-  POINT point;
-  if (GetCursorPos(&point)) {
-    ScreenToClient((HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW), &point);
-    mouseEvent =
-      new WidgetMouseEvent(true, NS_MOUSE_MOVE, mWidget.Get(),
-                           WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
-    mouseEvent->refPoint = LayoutDeviceIntPoint(point.x, point.y);
-    mouseEvent->clickCount = aTapCount;
-    mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
-    DispatchAsyncEventIgnoreStatus(mouseEvent);
-  }
 }
 
 void
 MetroInput::HandleLongTap(const Foundation::Point& aPoint)
 {
 #ifdef DEBUG_INPUT
   LogFunction();
 #endif
@@ -1043,21 +1027,37 @@ MetroInput::DispatchAsyncEventIgnoreStat
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this, &MetroInput::DeliverNextQueuedEventIgnoreStatus);
   NS_DispatchToCurrentThread(runnable);
 }
 
 void
 MetroInput::DeliverNextQueuedEventIgnoreStatus()
 {
-  WidgetGUIEvent* event =
+  nsAutoPtr<WidgetGUIEvent> event =
     static_cast<WidgetGUIEvent*>(mInputEventQueue.PopFront());
-  MOZ_ASSERT(event);
-  DispatchEventIgnoreStatus(event);
-  delete event;
+  MOZ_ASSERT(event.get());
+  DispatchEventIgnoreStatus(event.get());
+
+  // Clear :hover/:active states for mouse events generated by HandleTap
+  WidgetMouseEvent* mouseEvent = event.get()->AsMouseEvent();
+  if (!mouseEvent) {
+    return;
+  }
+  if (mouseEvent->message != NS_MOUSE_BUTTON_UP ||
+      mouseEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
+    return;
+  }
+  nsCOMPtr<nsIPresShell> presShell = mWidget->GetPresShell();
+  if (presShell) {
+    nsEventStateManager* esm = presShell->GetPresContext()->EventStateManager();
+    if (esm) {
+      esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER);
+    }
+  }
 }
 
 void
 MetroInput::DispatchAsyncTouchEvent(WidgetTouchEvent* aEvent)
 {
   aEvent->time = ::GetMessageTime();
   mModifierKeyState.Update();
   mModifierKeyState.InitInputEvent(*aEvent);
--- a/widget/windows/winrt/MetroWidget.cpp
+++ b/widget/windows/winrt/MetroWidget.cpp
@@ -1341,34 +1341,46 @@ LayoutDeviceIntPoint
 MetroWidget::CSSIntPointToLayoutDeviceIntPoint(const CSSIntPoint &aCSSPoint)
 {
   CSSToLayoutDeviceScale scale = GetDefaultScale();
   LayoutDeviceIntPoint devPx(int32_t(NS_round(scale.scale * aCSSPoint.x)),
                              int32_t(NS_round(scale.scale * aCSSPoint.y)));
   return devPx;
 }
 
-float MetroWidget::GetDPI()
+float
+MetroWidget::GetDPI()
 {
   if (!mView) {
     return 96.0;
   }
   return mView->GetDPI();
 }
 
-void MetroWidget::ChangedDPI()
+void
+MetroWidget::ChangedDPI()
 {
   if (mWidgetListener) {
     nsIPresShell* presShell = mWidgetListener->GetPresShell();
     if (presShell) {
       presShell->BackingScaleFactorChanged();
     }
   }
 }
 
+already_AddRefed<nsIPresShell>
+MetroWidget::GetPresShell()
+{
+  if (mWidgetListener) {
+    nsCOMPtr<nsIPresShell> ps = mWidgetListener->GetPresShell();
+    return ps.forget();
+  }
+  return nullptr;
+}
+
 NS_IMETHODIMP
 MetroWidget::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY)
 {
   return NS_OK;
 }
 
 void
 MetroWidget::SizeModeChanged()
--- a/widget/windows/winrt/MetroWidget.h
+++ b/widget/windows/winrt/MetroWidget.h
@@ -170,16 +170,18 @@ public:
   void Paint(const nsIntRegion& aInvalidRegion); 
 
   MetroWidget* MetroWidget::GetTopLevelWindow(bool aStopOnDialogOrPopup) { return this; }
   virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations);
   virtual void* GetNativeData(uint32_t aDataType);
   virtual void  FreeNativeData(void * data, uint32_t aDataType);
   virtual nsIntPoint WidgetToScreenOffset();
 
+  already_AddRefed<nsIPresShell> GetPresShell();
+
   void UserActivity();
 
 #ifdef ACCESSIBILITY
   mozilla::a11y::Accessible* DispatchAccessibleEvent(uint32_t aEventType);
   mozilla::a11y::Accessible* GetAccessible();
 #endif // ACCESSIBILITY
 
   // needed for current nsIFilePicker