Bug 229925 - Do not dispatch event to label target if interactive content is found between the event target and the label. r=smaug

This commit is contained in:
Tooru Fujisawa 2015-01-21 05:39:28 +09:00
parent f7a9fc17d7
commit b8a0cc4e4e
24 changed files with 265 additions and 11 deletions

View File

@ -1803,6 +1803,12 @@ Element::IsLabelable() const
return false;
}
bool
Element::IsInteractiveHTMLContent() const
{
return false;
}
css::StyleRule*
Element::GetInlineStyleRule()
{

View File

@ -284,6 +284,11 @@ public:
*/
virtual bool IsLabelable() const;
/**
* Returns if the element is interactive content as per HTML specification.
*/
virtual bool IsInteractiveHTMLContent() const;
/**
* Is the attribute named stored in the mapped attributes?
*

View File

@ -42,6 +42,12 @@ public:
virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
virtual bool Draggable() const MOZ_OVERRIDE;
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE
{
return true;
}
// nsIDOMHTMLAnchorElement
NS_DECL_NSIDOMHTMLANCHORELEMENT

View File

@ -40,6 +40,12 @@ HTMLAudioElement::~HTMLAudioElement()
{
}
bool
HTMLAudioElement::IsInteractiveHTMLContent() const
{
return HasAttr(kNameSpaceID_None, nsGkAtoms::controls);
}
already_AddRefed<HTMLAudioElement>
HTMLAudioElement::Audio(const GlobalObject& aGlobal,
const Optional<nsAString>& aSrc,

View File

@ -23,6 +23,9 @@ public:
explicit HTMLAudioElement(already_AddRefed<NodeInfo>& aNodeInfo);
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE;
// nsIDOMHTMLMediaElement
using HTMLMediaElement::GetPaused;

View File

@ -36,6 +36,12 @@ public:
NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLButtonElement, button)
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE
{
return true;
}
// nsIDOMHTMLButtonElement
NS_DECL_NSIDOMHTMLBUTTONELEMENT

View File

@ -26,6 +26,12 @@ public:
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE
{
return true;
}
// nsIDOMHTMLIFrameElement
NS_DECL_NSIDOMHTMLIFRAMEELEMENT

View File

@ -148,6 +148,12 @@ NS_IMPL_STRING_ATTR(HTMLImageElement, Srcset, srcset)
NS_IMPL_STRING_ATTR(HTMLImageElement, UseMap, usemap)
NS_IMPL_INT_ATTR(HTMLImageElement, Vspace, vspace)
bool
HTMLImageElement::IsInteractiveHTMLContent() const
{
return HasAttr(kNameSpaceID_None, nsGkAtoms::usemap);
}
bool
HTMLImageElement::IsSrcsetEnabled()
{

View File

@ -46,6 +46,9 @@ public:
virtual bool Draggable() const MOZ_OVERRIDE;
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE;
// nsIDOMHTMLImageElement
NS_DECL_NSIDOMHTMLIMAGEELEMENT

View File

@ -3211,6 +3211,12 @@ HTMLInputElement::Focus(ErrorResult& aError)
return;
}
bool
HTMLInputElement::IsInteractiveHTMLContent() const
{
return mType != NS_FORM_INPUT_HIDDEN;
}
NS_IMETHODIMP
HTMLInputElement::Select()
{

View File

@ -119,6 +119,9 @@ public:
virtual void Blur(ErrorResult& aError) MOZ_OVERRIDE;
virtual void Focus(ErrorResult& aError) MOZ_OVERRIDE;
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE;
// nsIDOMHTMLInputElement
NS_DECL_NSIDOMHTMLINPUTELEMENT

View File

@ -83,19 +83,14 @@ HTMLLabelElement::Focus(ErrorResult& aError)
}
static bool
EventTargetIn(WidgetEvent* aEvent, nsIContent* aChild, nsIContent* aStop)
InInteractiveHTMLContent(nsIContent* aContent, nsIContent* aStop)
{
nsCOMPtr<nsIContent> c = do_QueryInterface(aEvent->target);
nsIContent *content = c;
while (content) {
if (content == aChild) {
nsIContent* content = aContent;
while (content && content != aStop) {
if (content->IsElement() &&
content->AsElement()->IsInteractiveHTMLContent()) {
return true;
}
if (content == aStop) {
break;
}
content = content->GetParent();
}
return false;
@ -115,10 +110,15 @@ HTMLLabelElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
return NS_OK;
}
nsCOMPtr<nsIContent> target = do_QueryInterface(aVisitor.mEvent->target);
if (InInteractiveHTMLContent(target, this)) {
return NS_OK;
}
// Strong ref because event dispatch is going to happen.
nsRefPtr<Element> content = GetLabeledElement();
if (content && !EventTargetIn(aVisitor.mEvent, content, this)) {
if (content) {
mHandlingEvent = true;
switch (aVisitor.mEvent->message) {
case NS_MOUSE_BUTTON_DOWN:

View File

@ -32,6 +32,12 @@ public:
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE
{
return true;
}
// nsIDOMHTMLLabelElement
NS_DECL_NSIDOMHTMLLABELELEMENT

View File

@ -45,6 +45,12 @@ HTMLObjectElement::~HTMLObjectElement()
DestroyImageLoadingContent();
}
bool
HTMLObjectElement::IsInteractiveHTMLContent() const
{
return HasAttr(kNameSpaceID_None, nsGkAtoms::usemap);
}
bool
HTMLObjectElement::IsDoneAddingChildren()
{

View File

@ -30,6 +30,9 @@ public:
virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE;
// nsIDOMHTMLObjectElement
NS_DECL_NSIDOMHTMLOBJECTELEMENT

View File

@ -147,6 +147,12 @@ public:
virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE
{
return true;
}
// nsIDOMHTMLSelectElement
NS_DECL_NSIDOMHTMLSELECTELEMENT

View File

@ -53,6 +53,12 @@ public:
virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE
{
return true;
}
// nsIDOMHTMLTextAreaElement
NS_DECL_NSIDOMHTMLTEXTAREAELEMENT

View File

@ -126,6 +126,12 @@ nsresult HTMLVideoElement::SetAcceptHeader(nsIHttpChannel* aChannel)
false);
}
bool
HTMLVideoElement::IsInteractiveHTMLContent() const
{
return HasAttr(kNameSpaceID_None, nsGkAtoms::controls);
}
uint32_t HTMLVideoElement::MozParsedFrames() const
{
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread.");

View File

@ -49,6 +49,9 @@ public:
virtual nsresult SetAcceptHeader(nsIHttpChannel* aChannel) MOZ_OVERRIDE;
// Element
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE;
// WebIDL
uint32_t Width() const

View File

@ -1791,6 +1791,15 @@ nsGenericHTMLElement::IsLabelable() const
Tag() == nsGkAtoms::meter;
}
bool
nsGenericHTMLElement::IsInteractiveHTMLContent() const
{
return Tag() == nsGkAtoms::details ||
Tag() == nsGkAtoms::embed ||
Tag() == nsGkAtoms::keygen ||
HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex);
}
already_AddRefed<UndoManager>
nsGenericHTMLElement::GetUndoManager()
{

View File

@ -904,6 +904,7 @@ public:
}
virtual bool IsLabelable() const MOZ_OVERRIDE;
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE;
static bool TouchEventsEnabled(JSContext* /* unused */, JSObject* /* unused */);

View File

@ -64,6 +64,7 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
skip-if = buildapp == 'mulet'
[test_input_untrusted_key_events.html]
[test_input_url.html]
[test_interactive_content_in_label.html]
[test_label_control_attribute.html]
[test_label_input_controls.html]
[test_max_attribute.html]
@ -81,6 +82,7 @@ skip-if = e10s
[test_output_element.html]
[test_pattern_attribute.html]
[test_progress_element.html]
[test_radio_in_label.html]
[test_radio_radionodelist.html]
[test_required_attribute.html]
skip-if = e10s

View File

@ -0,0 +1,99 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=229925
-->
<head>
<title>Test for Bug 229925</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/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=229925">Mozilla Bug 229925</a>
<p id="display"></p>
<form action="#">
<label>
<span id="text">label</span>
<input type="button" id="target" value="target">
<a id="yes1" href="#">a</a>
<audio id="yes2" controls></audio>
<button id="yes3">button</button>
<details id="yes4">details</details>
<embed id="yes5">embed</embed>
<iframe id="yes6" src="data:text/plain," style="width: 16px; height: 16px;"></iframe>
<img id="yes7" src="data:image/png," usemap="#map">
<input id="yes8" type="text" size="4">
<keygen id="yes9">
<label id="yes10">label</label>
<object id="yes11" usemap="#map">object</object>
<select id="yes12"><option>select</option></select>
<textarea id="yes13" cols="1" rows="1"></textarea>
<video id="yes14" controls></video>
<span id="yes15" tabindex="1">tabindex</span>
<audio id="no1"></audio>
<img id="no2" src="data:image/png,">
<input id="no3" type="hidden">
<object id="no4">object</object>
<video id="no5"></video>
</label>
</form>
<script class="testbody" type="text/javascript">
/** Test for Bug 229925 **/
var target = document.getElementById("target");
var yes_nodes = [
document.getElementById("yes1"),
document.getElementById("yes2"),
document.getElementById("yes3"),
document.getElementById("yes4"),
document.getElementById("yes5"),
document.getElementById("yes6"),
document.getElementById("yes7"),
document.getElementById("yes8"),
document.getElementById("yes9"),
document.getElementById("yes10"),
document.getElementById("yes11"),
document.getElementById("yes12"),
document.getElementById("yes13"),
document.getElementById("yes14"),
document.getElementById("yes15"),
];
var no_nodes = [
document.getElementById("text"),
document.getElementById("no1"),
document.getElementById("no2"),
document.getElementById("no3"),
document.getElementById("no4"),
document.getElementById("no5"),
];
var target_clicked = false;
target.addEventListener("click", function() {
target_clicked = true;
});
var node;
for (node of yes_nodes) {
target_clicked = false;
node.click();
is(target_clicked, false, "mouse click on interactive content " + node.nodeName + " shouldn't dispatch event to label target");
}
for (node of no_nodes) {
target_clicked = false;
node.click();
is(target_clicked, true, "mouse click on non interactive content " + node.nodeName + " should dispatch event to label target");
}
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,51 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=229925
-->
<head>
<title>Test for Bug 229925</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/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=229925">Mozilla Bug 229925</a>
<p id="display"></p>
<form>
<label>
<span id="s1">LABEL</span>
<input type="radio" name="rdo" value="1" id="r1" onmousedown="document.body.appendChild(document.createTextNode('down'));">
<input type="radio" name="rdo" value="2" id="r2" checked="checked">
</label>
</form>
<script class="testbody" type="text/javascript">
/** Test for Bug 229925 **/
var r1 = document.getElementById("r1");
var r2 = document.getElementById("r2");
var s1 = document.getElementById("s1");
r1.click();
ok(r1.checked,
"The first radio input element should be checked by clicking the element");
r2.click();
ok(r2.checked,
"The second radio input element should be checked by clicking the element");
s1.click();
ok(r1.checked,
"The first radio input element should be checked by clicking other element");
r1.focus();
synthesizeKey("VK_LEFT", {});
ok(r2.checked,
"The second radio input element should be checked by key");
synthesizeKey("VK_LEFT", {});
ok(r1.checked,
"The first radio input element should be checked by key");
</script>
</pre>
</body>
</html>