Bug 754830 - calculate link states separately, r=tbsaunde
authorAlexander Surkov <surkov.alexander@gmail.com>
Thu, 17 May 2012 18:37:37 +0900
changeset 94216 bb07c2f3427ffe784d4a4784a8623da3187bea07
parent 94215 12f13acb5ea84c633b07153f09ccd8a27561d130
child 94217 e288dfb36d73fd9a25d71f38fcbf2af973c71472
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerstbsaunde
bugs754830
milestone15.0a1
Bug 754830 - calculate link states separately, r=tbsaunde
accessible/src/base/nsAccessible.cpp
accessible/src/base/nsAccessible.h
accessible/src/base/nsBaseWidgetAccessible.cpp
accessible/src/base/nsBaseWidgetAccessible.h
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsDocAccessible.h
accessible/src/generic/ARIAGridAccessible.cpp
accessible/src/generic/ARIAGridAccessible.h
accessible/src/generic/ApplicationAccessible.cpp
accessible/src/generic/ApplicationAccessible.h
accessible/src/html/HTMLFormControlAccessible.cpp
accessible/src/html/HTMLFormControlAccessible.h
accessible/src/html/nsHTMLImageMapAccessible.cpp
accessible/src/html/nsHTMLImageMapAccessible.h
accessible/src/html/nsHTMLLinkAccessible.cpp
accessible/src/html/nsHTMLLinkAccessible.h
accessible/src/xul/XULFormControlAccessible.cpp
accessible/src/xul/XULFormControlAccessible.h
accessible/src/xul/nsXULTextAccessible.cpp
accessible/src/xul/nsXULTextAccessible.h
accessible/tests/mochitest/states/test_link.html
--- a/accessible/src/base/nsAccessible.cpp
+++ b/accessible/src/base/nsAccessible.cpp
@@ -741,23 +741,31 @@ nsAccessible::NativeState()
   if (frame && (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW))
     state |= states::FLOATING;
 
   // Check if a XUL element has the popup attribute (an attached popup menu).
   if (mContent->IsXUL())
     if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
       state |= states::HASPOPUP;
 
-  // Add 'linked' state for simple xlink.
-  if (nsCoreUtils::IsXLink(mContent))
-    state |= states::LINKED;
+  // Bypass the link states specialization for non links.
+  if (!mRoleMapEntry || mRoleMapEntry->roleRule == kUseNativeRole ||
+      mRoleMapEntry->role == roles::LINK)
+    state |= NativeLinkState();
 
   return state;
 }
 
+PRUint64
+nsAccessible::NativeLinkState() const
+{
+  // Expose linked state for simple xlink.
+  return nsCoreUtils::IsXLink(mContent) ? states::LINKED : 0;
+}
+
   /* readonly attribute boolean focusedChild; */
 NS_IMETHODIMP
 nsAccessible::GetFocusedChild(nsIAccessible** aChild)
 {
   NS_ENSURE_ARG_POINTER(aChild);
   *aChild = nsnull;
 
   if (IsDefunct())
@@ -1613,17 +1621,17 @@ nsAccessible::State()
       state |= states::HORIZONTAL;
     }
   }
 
   return state;
 }
 
 void
-nsAccessible::ApplyARIAState(PRUint64* aState)
+nsAccessible::ApplyARIAState(PRUint64* aState) const
 {
   if (!mContent->IsElement())
     return;
 
   dom::Element* element = mContent->AsElement();
 
   // Test for universal states first
   *aState |= nsARIAMap::UniversalStatesFor(element);
--- a/accessible/src/base/nsAccessible.h
+++ b/accessible/src/base/nsAccessible.h
@@ -180,17 +180,17 @@ public:
 
   /**
    * Maps ARIA state attributes to state of accessible. Note the given state
    * argument should hold states for accessible before you pass it into this
    * method.
    *
    * @param  [in/out] where to fill the states into.
    */
-  virtual void ApplyARIAState(PRUint64* aState);
+  virtual void ApplyARIAState(PRUint64* aState) const;
 
   /**
    * Returns the accessible name provided by native markup. It doesn't take
    * into account ARIA markup used to specify the name.
    *
    * @param  aName             [out] the accessible name
    *
    * @return NS_OK_EMPTY_NAME  points empty name was specified by native markup
@@ -223,22 +223,37 @@ public:
   virtual mozilla::a11y::role NativeRole();
 
   /**
    * Return all states of accessible (including ARIA states).
    */
   virtual PRUint64 State();
 
   /**
+   * Return link states present on the accessible.
+   */
+  PRUint64 LinkState() const
+  {
+    PRUint64 state = NativeLinkState();
+    ApplyARIAState(&state);
+    return state;
+  }
+
+  /**
    * Return the states of accessible, not taking into account ARIA states.
    * Use State() to get complete set of states.
    */
   virtual PRUint64 NativeState();
 
   /**
+   * Return native link states present on the accessible.
+   */
+  virtual PRUint64 NativeLinkState() const;
+
+  /**
    * Return bit set of invisible and offscreen states.
    */
   PRUint64 VisibilityState();
 
   /**
    * Returns attributes for accessible without explicitly setted ARIA
    * attributes.
    */
--- a/accessible/src/base/nsBaseWidgetAccessible.cpp
+++ b/accessible/src/base/nsBaseWidgetAccessible.cpp
@@ -108,26 +108,22 @@ NS_IMPL_ISUPPORTS_INHERITED0(nsLinkableA
 
 NS_IMETHODIMP
 nsLinkableAccessible::TakeFocus()
 {
   return mActionAcc ? mActionAcc->TakeFocus() : nsAccessibleWrap::TakeFocus();
 }
 
 PRUint64
-nsLinkableAccessible::NativeState()
+nsLinkableAccessible::NativeLinkState() const
 {
-  PRUint64 states = nsAccessibleWrap::NativeState();
-  if (mIsLink) {
-    states |= states::LINKED;
-    if (mActionAcc->State() & states::TRAVERSED)
-      states |= states::TRAVERSED;
-  }
+  if (mIsLink)
+    return states::LINKED | (mActionAcc->LinkState() & states::TRAVERSED);
 
-  return states;
+  return 0;
 }
 
 void
 nsLinkableAccessible::Value(nsString& aValue)
 {
   aValue.Truncate();
 
   nsAccessible::Value(aValue);
@@ -230,21 +226,20 @@ nsLinkableAccessible::BindToParent(nsAcc
     return;
   }
 
   // XXX: The logic looks broken since the click listener may be registered
   // on non accessible node in parent chain but this node is skipped when tree
   // is traversed.
   nsAccessible* walkUpAcc = this;
   while ((walkUpAcc = walkUpAcc->Parent()) && !walkUpAcc->IsDoc()) {
-    if (walkUpAcc->Role() == roles::LINK &&
-        walkUpAcc->State() & states::LINKED) {
-        mIsLink = true;
-        mActionAcc = walkUpAcc;
-        return;
+    if (walkUpAcc->LinkState() & states::LINKED) {
+      mIsLink = true;
+      mActionAcc = walkUpAcc;
+      return;
     }
 
     if (nsCoreUtils::HasClickListener(walkUpAcc->GetContent())) {
       mActionAcc = walkUpAcc;
       mIsOnclick = true;
       return;
     }
   }
--- a/accessible/src/base/nsBaseWidgetAccessible.h
+++ b/accessible/src/base/nsBaseWidgetAccessible.h
@@ -91,17 +91,17 @@ public:
   NS_IMETHOD DoAction(PRUint8 index);
   NS_IMETHOD TakeFocus();
 
   // nsAccessNode
   virtual void Shutdown();
 
   // nsAccessible
   virtual void Value(nsString& aValue);
-  virtual PRUint64 NativeState();
+  virtual PRUint64 NativeLinkState() const;
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
   virtual KeyBinding AccessKey() const;
 
   // HyperLinkAccessible
   virtual already_AddRefed<nsIURI> AnchorURIAt(PRUint32 aAnchorIndex);
 
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -336,17 +336,17 @@ nsDocAccessible::NativeState()
   nsCOMPtr<nsIEditor> editor = GetEditor();
   state |= editor ? states::EDITABLE : states::READONLY;
 
   return state;
 }
 
 // nsAccessible public method
 void
-nsDocAccessible::ApplyARIAState(PRUint64* aState)
+nsDocAccessible::ApplyARIAState(PRUint64* aState) const
 {
   // Combine with states from outer doc
   // 
   nsAccessible::ApplyARIAState(aState);
 
   // Allow iframe/frame etc. to have final state override via ARIA
   if (mParent)
     mParent->ApplyARIAState(aState);
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -110,17 +110,17 @@ public:
   virtual nsIDocument* GetDocumentNode() const { return mDocument; }
 
   // nsAccessible
   virtual mozilla::a11y::ENameValueFlag Name(nsString& aName);
   virtual void Description(nsString& aDescription);
   virtual nsAccessible* FocusedChild();
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
-  virtual void ApplyARIAState(PRUint64* aState);
+  virtual void ApplyARIAState(PRUint64* aState) const;
 
   virtual void SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry);
 
 #ifdef DEBUG_ACCDOCMGR
   virtual nsresult HandleAccEvent(AccEvent* aAccEvent);
 #endif
 
   virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
--- a/accessible/src/generic/ARIAGridAccessible.cpp
+++ b/accessible/src/generic/ARIAGridAccessible.cpp
@@ -1040,17 +1040,17 @@ ARIAGridCellAccessible::IsSelected(bool*
   *aIsSelected = true;
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessible
 
 void
-ARIAGridCellAccessible::ApplyARIAState(PRUint64* aState)
+ARIAGridCellAccessible::ApplyARIAState(PRUint64* aState) const
 {
   nsHyperTextAccessibleWrap::ApplyARIAState(aState);
 
   // Return if the gridcell has aria-selected="true".
   if (*aState & states::SELECTED)
     return;
 
   // Check aria-selected="true" on the row.
--- a/accessible/src/generic/ARIAGridAccessible.h
+++ b/accessible/src/generic/ARIAGridAccessible.h
@@ -133,16 +133,16 @@ public:
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessibleTableCell
   NS_DECL_NSIACCESSIBLETABLECELL
 
   // nsAccessible
-  virtual void ApplyARIAState(PRUint64* aState);
+  virtual void ApplyARIAState(PRUint64* aState) const;
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/src/generic/ApplicationAccessible.cpp
+++ b/accessible/src/generic/ApplicationAccessible.cpp
@@ -328,17 +328,17 @@ ApplicationAccessible::IsPrimaryForNode(
 {
   return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessible public methods
 
 void
-ApplicationAccessible::ApplyARIAState(PRUint64* aState)
+ApplicationAccessible::ApplyARIAState(PRUint64* aState) const
 {
 }
 
 role
 ApplicationAccessible::NativeRole()
 {
   return roles::APP_ROOT;
 }
--- a/accessible/src/generic/ApplicationAccessible.h
+++ b/accessible/src/generic/ApplicationAccessible.h
@@ -99,17 +99,17 @@ public:
 
   // nsAccessNode
   virtual bool Init();
   virtual void Shutdown();
   virtual bool IsPrimaryForNode() const;
 
   // nsAccessible
   virtual mozilla::a11y::ENameValueFlag Name(nsString& aName);
-  virtual void ApplyARIAState(PRUint64* aState);
+  virtual void ApplyARIAState(PRUint64* aState) const;
   virtual void Description(nsString& aDescription);
   virtual void Value(nsString& aValue);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 State();
   virtual PRUint64 NativeState();
   virtual Relation RelationByType(PRUint32 aRelType);
 
   virtual nsAccessible* ChildAtPoint(PRInt32 aX, PRInt32 aY,
--- a/accessible/src/html/HTMLFormControlAccessible.cpp
+++ b/accessible/src/html/HTMLFormControlAccessible.cpp
@@ -432,17 +432,17 @@ HTMLTextFieldAccessible::Value(nsString&
   
   nsCOMPtr<nsIDOMHTMLInputElement> inputElement(do_QueryInterface(mContent));
   if (inputElement) {
     inputElement->GetValue(aValue);
   }
 }
 
 void
-HTMLTextFieldAccessible::ApplyARIAState(PRUint64* aState)
+HTMLTextFieldAccessible::ApplyARIAState(PRUint64* aState) const
 {
   nsHyperTextAccessibleWrap::ApplyARIAState(aState);
 
   aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
 }
 
 PRUint64
 HTMLTextFieldAccessible::State()
--- a/accessible/src/html/HTMLFormControlAccessible.h
+++ b/accessible/src/html/HTMLFormControlAccessible.h
@@ -140,17 +140,17 @@ public:
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 index);
 
   // nsHyperTextAccessible
   virtual already_AddRefed<nsIEditor> GetEditor() const;
 
   // nsAccessible
   virtual void Value(nsString& aValue);
-  virtual void ApplyARIAState(PRUint64* aState);
+  virtual void ApplyARIAState(PRUint64* aState) const;
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 State();
   virtual PRUint64 NativeState();
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
--- a/accessible/src/html/nsHTMLImageMapAccessible.cpp
+++ b/accessible/src/html/nsHTMLImageMapAccessible.cpp
@@ -226,29 +226,16 @@ nsHTMLAreaAccessible::IsPrimaryForNode()
   // Make HTML area DOM element not accessible. HTML image map accessible
   // manages its tree itself.
   return false;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTMLAreaAccessible: nsAccessible public
 
-PRUint64
-nsHTMLAreaAccessible::NativeState()
-{
-  // Bypass the link states specialization for non links.
-  if (mRoleMapEntry &&
-      mRoleMapEntry->role != roles::NOTHING &&
-      mRoleMapEntry->role != roles::LINK) {
-    return nsAccessible::NativeState();
-  }
-
-  return nsHTMLLinkAccessible::NativeState();
-}
-
 nsAccessible*
 nsHTMLAreaAccessible::ChildAtPoint(PRInt32 aX, PRInt32 aY,
                                    EWhichChildAtPoint aWhichChild)
 {
   // Don't walk into area accessibles.
   return this;
 }
 
--- a/accessible/src/html/nsHTMLImageMapAccessible.h
+++ b/accessible/src/html/nsHTMLImageMapAccessible.h
@@ -97,17 +97,16 @@ public:
   nsHTMLAreaAccessible(nsIContent* aContent, nsDocAccessible* aDoc);
 
   // nsAccessNode
   virtual bool IsPrimaryForNode() const;
 
   // nsAccessible
   virtual void Description(nsString& aDescription);
   virtual nsresult GetNameInternal(nsAString& aName);
-  virtual PRUint64 NativeState();
   virtual nsAccessible* ChildAtPoint(PRInt32 aX, PRInt32 aY,
                                      EWhichChildAtPoint aWhichChild);
   virtual void GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame);
 
   // HyperLinkAccessible
   virtual PRUint32 StartOffset();
   virtual PRUint32 EndOffset();
 
--- a/accessible/src/html/nsHTMLLinkAccessible.cpp
+++ b/accessible/src/html/nsHTMLLinkAccessible.cpp
@@ -81,34 +81,33 @@ nsHTMLLinkAccessible::NativeState()
 
   if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name)) {
     // This is how we indicate it is a named anchor
     // In other words, this anchor can be selected as a location :)
     // There is no other better state to use to indicate this.
     states |= states::SELECTABLE;
   }
 
-  nsEventStates state = mContent->AsElement()->State();
-  if (state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED |
-                                  NS_EVENT_STATE_UNVISITED)) {
-    states |= states::LINKED;
+  return states;
+}
 
-    if (state.HasState(NS_EVENT_STATE_VISITED))
-      states |= states::TRAVERSED;
+PRUint64
+nsHTMLLinkAccessible::NativeLinkState() const
+{
+  nsEventStates eventState = mContent->AsElement()->State();
+  if (eventState.HasState(NS_EVENT_STATE_UNVISITED))
+    return states::LINKED;
 
-    return states;
-  }
+  if (eventState.HasState(NS_EVENT_STATE_VISITED))
+    return states::LINKED | states::TRAVERSED;
 
   // This is a either named anchor (a link with also a name attribute) or
   // it doesn't have any attributes. Check if 'click' event handler is
   // registered, otherwise bail out.
-  if (nsCoreUtils::HasClickListener(mContent))
-    states |= states::LINKED;
-
-  return states;
+  return nsCoreUtils::HasClickListener(mContent) ? states::LINKED : 0;
 }
 
 void
 nsHTMLLinkAccessible::Value(nsString& aValue)
 {
   aValue.Truncate();
 
   nsHyperTextAccessible::Value(aValue);
--- a/accessible/src/html/nsHTMLLinkAccessible.h
+++ b/accessible/src/html/nsHTMLLinkAccessible.h
@@ -52,16 +52,17 @@ public:
   // nsIAccessible
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 aIndex);
 
   // nsAccessible
   virtual void Value(nsString& aValue);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeLinkState() const;
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // HyperLinkAccessible
   virtual bool IsLink();
   virtual already_AddRefed<nsIURI> AnchorURIAt(PRUint32 aAnchorIndex);
 
--- a/accessible/src/xul/XULFormControlAccessible.cpp
+++ b/accessible/src/xul/XULFormControlAccessible.cpp
@@ -749,22 +749,21 @@ XULTextFieldAccessible::Value(nsString& 
   }
 
   nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
   if (menuList)
     menuList->GetLabel(aValue);
 }
 
 void
-XULTextFieldAccessible::ApplyARIAState(PRUint64* aState)
+XULTextFieldAccessible::ApplyARIAState(PRUint64* aState) const
 {
   nsHyperTextAccessibleWrap::ApplyARIAState(aState);
 
   aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
-
 }
 
 PRUint64
 XULTextFieldAccessible::NativeState()
 {
   PRUint64 state = nsHyperTextAccessibleWrap::NativeState();
 
   nsCOMPtr<nsIContent> inputField(GetInputField());
--- a/accessible/src/xul/XULFormControlAccessible.h
+++ b/accessible/src/xul/XULFormControlAccessible.h
@@ -260,17 +260,17 @@ public:
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 index);
 
   // nsHyperTextAccessible
   virtual already_AddRefed<nsIEditor> GetEditor() const;
 
   // nsAccessible
   virtual void Value(nsString& aValue);
-  virtual void ApplyARIAState(PRUint64* aState);
+  virtual void ApplyARIAState(PRUint64* aState) const;
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
   virtual bool CanHaveAnonChildren();
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
 protected:
--- a/accessible/src/xul/nsXULTextAccessible.cpp
+++ b/accessible/src/xul/nsXULTextAccessible.cpp
@@ -173,19 +173,19 @@ nsXULLinkAccessible::GetNameInternal(nsA
 role
 nsXULLinkAccessible::NativeRole()
 {
   return roles::LINK;
 }
 
 
 PRUint64
-nsXULLinkAccessible::NativeState()
+nsXULLinkAccessible::NativeLinkState() const
 {
-  return nsHyperTextAccessible::NativeState() | states::LINKED;
+  return states::LINKED;
 }
 
 PRUint8
 nsXULLinkAccessible::ActionCount()
 {
   return 1;
 }
 
--- a/accessible/src/xul/nsXULTextAccessible.h
+++ b/accessible/src/xul/nsXULTextAccessible.h
@@ -83,17 +83,17 @@ public:
   // nsIAccessible
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 aIndex);
 
   // nsAccessible
   virtual void Value(nsString& aValue);
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
-  virtual PRUint64 NativeState();
+  virtual PRUint64 NativeLinkState() const;
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // HyperLinkAccessible
   virtual bool IsLink();
   virtual PRUint32 StartOffset();
   virtual PRUint32 EndOffset();
--- a/accessible/tests/mochitest/states/test_link.html
+++ b/accessible/tests/mochitest/states/test_link.html
@@ -3,54 +3,99 @@
 <head>
   <title>HTML link states testing</title>
 
   <link rel="stylesheet" type="text/css"
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
 
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
-      testStates("link1", STATE_LINKED);
-      testStates("link2", STATE_LINKED);
-      testStates("link3", STATE_LINKED);
-      testStates("link4", STATE_LINKED);
-      testStates("link5", 0, 0, STATE_LINKED);
+      // a@href and its text node
+      testStates("link_href", STATE_LINKED);
+      testStates(getAccessible("link_href").firstChild, STATE_LINKED);
+
+      // a@onclick
+      testStates("link_click", STATE_LINKED);
+
+      // a@onmousedown
+      testStates("link_mousedown", STATE_LINKED);
+
+      // a@onmouseup
+      testStates("link_mouseup", STATE_LINKED);
+
+      // a@role="link"
+      testStates("link_arialink", STATE_LINKED);
+
+      // a@role="button"
+      testStates("link_ariabutton", 0, 0, STATE_LINKED);
 
-      SimpleTest.finish();
+      // a (no @href, no click event listener)
+      testStates("link_notlink", 0, 0, STATE_LINKED);
+
+      // a: traversal state
+      testStates("link_traversed", 0, 0, STATE_TRAVERSED);
+      registerA11yEventListener(EVENT_DOCUMENT_LOAD_COMPLETE,
+                                traversedLinkTester);
+
+      synthesizeMouse(getNode("link_traversed"), 1, 1, { shiftKey: true });
     }
 
+    var traversedLinkTester = {
+      handleEvent: function traversedLinkTester_handleEvent(aEvent) {
+        unregisterA11yEventListener(EVENT_DOCUMENT_LOAD_COMPLETE,
+                                    traversedLinkTester);
+        aEvent.accessible.rootDocument.window.close();
+
+        testStates("link_traversed", STATE_TRAVERSED);
+        SimpleTest.finish();
+      }
+    };
+
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 
 </head>
 
 <body>
 
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409"
      title="Expose click action if mouseup and mousedown are registered">
     Mozilla Bug 423409
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=754830"
+     title="Calculate link states separately">
+    Mozilla Bug 754830
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
-  <a id="link1" href="http://mozilla.org">link</a>
-  <a id="link2" onclick="">link</a>
-  <a id="link3" onmousedown="">link</a>
-  <a id="link4" onmouseup="">link</a>
-  <a id="link5">not link</a>
+  <a id="link_href" href="http://mozilla.org">link</a>
+  <a id="link_click" onclick="">link</a>
+  <a id="link_mousedown" onmousedown="">link</a>
+  <a id="link_mouseup" onmouseup="">link</a>
+  <a id="link_arialink" role="link">aria link</a>
+  <a id="link_ariabutton" role="button">aria button</a>
+  <a id="link_notlink">not link</a>
+
+  <a id="link_traversed" href="http://www.example.com" target="_top">example.com</a>
 
 </body>
 </html>