Bug 375008 - Clicking a disabled element should not cause a focus change. r=Enn
authorMounir Lamouri <mounir.lamouri@gmail.com>
Wed, 27 Feb 2013 14:17:43 +0000
changeset 123152 6c59b73c8c4b908e2defbafce28b75636563da75
parent 123151 f223a4961926e7feed750ea41df1466c39fef185
child 123153 ebbcf3fc9240804d10da3164cad6e2612e5db3ef
push id24373
push userryanvm@gmail.com
push dateThu, 28 Feb 2013 01:36:21 +0000
treeherdermozilla-central@8cb9d6981978 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersEnn
bugs375008
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 375008 - Clicking a disabled element should not cause a focus change. r=Enn
content/events/src/nsEventStateManager.cpp
content/events/test/Makefile.in
content/events/test/test_focus_disabled.html
layout/style/forms.css
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -3126,18 +3126,48 @@ nsEventStateManager::PostHandleEvent(nsP
 
       nsCOMPtr<nsIContent> activeContent;
       if (nsEventStatus_eConsumeNoDefault != *aStatus) {
         nsCOMPtr<nsIContent> newFocus;      
         bool suppressBlur = false;
         if (mCurrentTarget) {
           mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus));
           const nsStyleUserInterface* ui = mCurrentTarget->StyleUserInterface();
+          activeContent = mCurrentTarget->GetContent();
+
+          // In some cases, we do not want to even blur the current focused
+          // element. Those cases are:
+          // 1. -moz-user-focus CSS property is set to 'ignore';
+          // 2. Element with NS_EVENT_STATE_DISABLED
+          //    (aka :disabled pseudo-class for HTML element);
+          // 3. XUL control element has the disabled property set to 'true'.
+          //
+          // We can't use nsIFrame::IsFocusable() because we want to blur when
+          // we click on a visibility: none element.
+          // We can't use nsIContent::IsFocusable() because we want to blur when
+          // we click on a non-focusable element like a <div>.
+          // We have to use |aEvent->target| to not make sure we do not check an
+          // anonymous node of the targeted element.
           suppressBlur = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE);
-          activeContent = mCurrentTarget->GetContent();
+
+          if (!suppressBlur) {
+            nsCOMPtr<Element> element = do_QueryInterface(aEvent->target);
+            suppressBlur = element &&
+                           element->State().HasState(NS_EVENT_STATE_DISABLED);
+          }
+
+          if (!suppressBlur) {
+            nsCOMPtr<nsIDOMXULControlElement> xulControl =
+              do_QueryInterface(aEvent->target);
+            if (xulControl) {
+              bool disabled;
+              xulControl->GetDisabled(&disabled);
+              suppressBlur = disabled;
+            }
+          }
         }
 
         nsIFrame* currFrame = mCurrentTarget;
 
         // When a root content which isn't editable but has an editable HTML
         // <body> element is clicked, we should redirect the focus to the
         // the <body> element.  E.g., when an user click bottom of the editor
         // where is outside of the <body> element, the <body> should be focused
--- a/content/events/test/Makefile.in
+++ b/content/events/test/Makefile.in
@@ -96,16 +96,17 @@ MOCHITEST_FILES = \
 		test_wheel_default_action.html \
 		window_wheel_default_action.html \
 		test_bug603008.html \
 		test_bug716822.html \
 		test_bug742376.html \
 		test_dragstart.html \
 		test_bug812744.html \
 		test_addEventListenerExtraArg.html \
+		test_focus_disabled.html \
 		$(NULL)
 
 MOCHITEST_CHROME_FILES = \
 		test_bug336682_2.xul \
 		test_bug336682.js \
 		test_bug586961.xul \
 		test_bug415498.xul \
 		bug415498-doc1.html \
new file mode 100644
--- /dev/null
+++ b/content/events/test/test_focus_disabled.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=375008
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 375008</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=375008">Mozilla Bug 375008</a>
+<p id="display"></p>
+<div id="content">
+  <div>
+    <input id='witness'>
+  </div>
+
+  <div id='not-focusable'>
+    <!-- Disabled elements -->
+    <button hidden disabled>foo</button>
+    <input hidden disabled>
+    <fieldset hidden disabled>foo</fieldset>
+    <select hidden disabled><option>foo</option></select>
+    <textarea hidden disabled></textarea>
+    <optgroup hidden disabled><option>foo</option></optgroup>
+    <option hidden disabled>foo</option>
+  </div>
+
+  <div id='focusable'>
+    <button hidden>foo</button>
+    <input hidden>
+    <select hidden><option>foo</option></select>
+    <textarea hidden></textarea>
+
+    <!-- Those elements are not focusable by default. -->
+    <fieldset tabindex=1 hidden>foo</fieldset>
+    <optgroup tabindex=1 hidden><option>foo</option></optgroup>
+    <option tabindex=1 hidden>foo</option>
+  </div>
+
+  <!-- Hidden elements, they have a frame but focus will go through them. -->
+  <div id='hidden' style='visibility: hidden;'>
+    <button hidden>foo</button>
+    <input hidden>
+    <fieldset hidden>foo</fieldset>
+    <select hidden><option>foo</option></select>
+    <textarea hidden></textarea>
+    <optgroup hidden><option>foo</option></optgroup>
+    <option hidden>foo</option>
+  </div>
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 375008 **/
+
+/*
+ * This test is divided in three parts:
+ * - cases where focus isn't doable but blur should not happen;
+ * - cases where focus is doable;
+ * - cases where focus isn't doable but blur should still happen.
+ */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  var witness = document.getElementById('witness');
+  witness.focus();
+
+  var notFocusableElements = document.getElementById('not-focusable').children;
+  for (var i=0; i<notFocusableElements.length; ++i) {
+    var element = notFocusableElements[i];
+    element.hidden = false;
+    synthesizeMouseAtCenter(element, {});
+    is(document.activeElement, witness,
+       "[" + element.tagName + "] witness should still be focused");
+
+    // Cleanup.
+    witness.focus();
+  }
+
+  var focusableElements = document.getElementById('focusable').children;
+  for (var i=0; i<focusableElements.length; ++i) {
+    var element = focusableElements[i];
+    element.hidden = false;
+    synthesizeMouseAtCenter(element, {});
+    is(document.activeElement, element, "focus should have moved to " + element);
+
+    // Cleanup.
+    element.hidden = true;
+    witness.focus();
+  }
+
+  var hiddenElements = document.getElementById('hidden').children;
+  for (var i=0; i<hiddenElements.length; ++i) {
+    var element = hiddenElements[i];
+    element.hidden = false;
+    synthesizeMouseAtCenter(element, {});
+    is(document.activeElement, document.body,
+       "focus should have moved to the body");
+
+    // Cleanup.
+    element.hidden = true;
+    witness.focus();
+  }
+
+  SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -357,17 +357,16 @@ optgroup:before {
 input:disabled,
 textarea:disabled,
 option:disabled,
 optgroup:disabled,
 select:disabled:disabled /* Need the pseudo-class twice to have the specificity
                             be at least the same as select[size][multiple] above */
 {
   -moz-user-input: disabled;
-  -moz-user-focus: ignore;
   color: GrayText;
   background-color: ThreeDFace;
   cursor: inherit;
 }
 
 input:disabled > .anonymous-div,
 textarea:disabled > .anonymous-div {
   cursor: default;