Bug 816340 - Propagate events to chrome even if there is a disabled form control in the event target chain, r=jst,gavin

This commit is contained in:
Olli Pettay 2013-01-03 17:17:36 +02:00
parent eaed7d2195
commit 44eadd5185
21 changed files with 278 additions and 50 deletions

View File

@ -432,7 +432,8 @@ nsContextMenu.prototype = {
setTarget: function (aNode, aRangeParent, aRangeOffset) {
const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if (aNode.namespaceURI == xulNS ||
aNode.nodeType == Node.DOCUMENT_NODE) {
aNode.nodeType == Node.DOCUMENT_NODE ||
this.isDisabledForEvents(aNode)) {
this.shouldDisplay = false;
return;
}
@ -1290,6 +1291,16 @@ nsContextMenu.prototype = {
"contextMenu.hasBGImage = " + this.hasBGImage + "\n";
},
isDisabledForEvents: function(aNode) {
let ownerDoc = aNode.ownerDocument;
return
ownerDoc.defaultView &&
ownerDoc.defaultView
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.isNodeDisabledForEvents(aNode);
},
isTargetATextBox: function(node) {
if (node instanceof HTMLInputElement)
return node.mozIsTextField(false);

View File

@ -95,6 +95,9 @@ ContentAreaDropListener.prototype =
canDropLink: function(aEvent, aAllowSameDocument)
{
if (this._eventTargetIsDisabled(aEvent))
return false;
let dataTransfer = aEvent.dataTransfer;
let types = dataTransfer.types;
if (!types.contains("application/x-moz-file") &&
@ -131,6 +134,8 @@ ContentAreaDropListener.prototype =
dropLink: function(aEvent, aName, aDisallowInherit)
{
aName.value = "";
if (this._eventTargetIsDisabled(aEvent))
return "";
let dataTransfer = aEvent.dataTransfer;
let [url, name] = this._getDropURL(dataTransfer);
@ -147,6 +152,18 @@ ContentAreaDropListener.prototype =
aName.value = name;
return url;
},
_eventTargetIsDisabled: function(aEvent)
{
let ownerDoc = aEvent.originalTarget.ownerDocument;
if (!ownerDoc || !ownerDoc.defaultView)
return false;
return ownerDoc.defaultView
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.isNodeDisabledForEvents(aEvent.originalTarget);
}
};

View File

@ -49,6 +49,8 @@ MOCHITEST_CHROME_FILES = \
test_csp_bug768029.html \
test_bug800386.xul \
test_csp_bug773891.html \
test_bug816340.xul \
file_bug816340.xul \
test_domparsing.xul \
test_bug814638.xul \
host_bug814638.xul \

View File

@ -0,0 +1,70 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=816340
-->
<window title="Mozilla Bug 816340"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="start();">
<label value="Mozilla Bug 816340"/>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
function ok(val, msg) {
opener.wrappedJSObject.ok(val, msg);
}
var elems =
[
"input",
"textarea",
"select",
"fieldset",
"button",
];
var chromeDidGetEvent = false;
function chromeListener() {
chromeDidGetEvent = true;
}
function testElement(el, disabled, contentShouldGetEvent) {
chromeDidGetEvent = false;
var b = document.getElementById("browser");
b.contentDocument.body.innerHTML = null;
var e = b.contentDocument.createElement(el);
if (disabled) {
e.setAttribute("disabled", "true");
}
b.contentDocument.body.appendChild(e);
var contentDidGetEvent = false;
b.contentDocument.body.addEventListener("foo",
function() { contentDidGetEvent = true }, true);
b.addEventListener("foo", chromeListener, true);
e.dispatchEvent(new Event("foo"));
b.removeEventListener("foo", chromeListener, true);
ok(contentDidGetEvent == contentShouldGetEvent, "content: " + el + (disabled ? " disabled" : ""));
ok(chromeDidGetEvent, "chrome: " + el + (disabled ? " disabled" : ""));
}
function start() {
// Test common element.
testElement("div", false, true);
testElement("div", true, true);
for (var i = 0; i < elems.length; ++i) {
testElement(elems[i], false, true);
testElement(elems[i], true, false);
}
ok(true, "done");
opener.setTimeout("done()", 0);
window.close();
}
]]></script>
<browser id="browser" type="content" src="about:blank"/>
</window>

View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=816340
-->
<window title="Mozilla Bug 816340"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=816340"
target="_blank">Mozilla Bug 816340</a>
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
SimpleTest.waitForExplicitFinish();
function done() {
SimpleTest.finish();
}
addLoadEvent(function() {
window.open("file_bug816340.xul", "", "chrome");
});
]]></script>
</window>

View File

@ -100,15 +100,17 @@ public:
nsEventStatus aEventStatus,
bool aIsInAnon)
: nsEventChainVisitor(aPresContext, aEvent, aDOMEvent, aEventStatus),
mCanHandle(true), mForceContentDispatch(false),
mRelatedTargetIsInAnon(false), mOriginalTargetIsInAnon(aIsInAnon),
mWantsWillHandleEvent(false), mMayHaveListenerManager(true),
mParentTarget(nullptr), mEventTargetAtParent(nullptr) {}
mCanHandle(true), mAutomaticChromeDispatch(true),
mForceContentDispatch(false), mRelatedTargetIsInAnon(false),
mOriginalTargetIsInAnon(aIsInAnon), mWantsWillHandleEvent(false),
mMayHaveListenerManager(true), mParentTarget(nullptr),
mEventTargetAtParent(nullptr) {}
void Reset() {
mItemFlags = 0;
mItemData = nullptr;
mCanHandle = true;
mAutomaticChromeDispatch = true;
mForceContentDispatch = false;
mWantsWillHandleEvent = false;
mMayHaveListenerManager = true;
@ -124,6 +126,12 @@ public:
*/
bool mCanHandle;
/**
* If mCanHandle is false and mAutomaticChromeDispatch is also false
* event will not be dispatched to the chrome event handler.
*/
bool mAutomaticChromeDispatch;
/**
* If mForceContentDispatch is set to true,
* content dispatching is not disabled for this event target.

View File

@ -197,7 +197,7 @@ public:
static void ResetMaxEtciCount()
{
NS_ASSERTION(!sCurrentEtciCount, "Wrong time to call ResetMaxEtciCount()!");
MOZ_ASSERT(!sCurrentEtciCount, "Wrong time to call ResetMaxEtciCount()!");
sMaxEtciCount = 0;
}
@ -438,6 +438,30 @@ int32_t ChainItemPool::sEtciPoolUsers = 0;
void NS_ShutdownChainItemPool() { ChainItemPool::Shutdown(); }
nsEventTargetChainItem*
EventTargetChainItemForChromeTarget(ChainItemPool& aPool,
nsINode* aNode,
nsEventTargetChainItem* aChild = nullptr)
{
if (!aNode->IsInDoc()) {
return nullptr;
}
nsPIDOMWindow* win = aNode->OwnerDoc()->GetInnerWindow();
nsIDOMEventTarget* piTarget = win ? win->GetParentTarget() : nullptr;
NS_ENSURE_TRUE(piTarget, nullptr);
nsEventTargetChainItem* etci =
nsEventTargetChainItem::Create(aPool.GetPool(),
piTarget->GetTargetForEventTargetChain(),
aChild);
NS_ENSURE_TRUE(etci, nullptr);
if (!etci->IsValid()) {
nsEventTargetChainItem::Destroy(aPool.GetPool(), etci);
return nullptr;
}
return etci;
}
/* static */ nsresult
nsEventDispatcher::Dispatch(nsISupports* aTarget,
nsPresContext* aPresContext,
@ -577,6 +601,13 @@ nsEventDispatcher::Dispatch(nsISupports* aTarget,
isInAnon);
targetEtci->PreHandleEvent(preVisitor);
if (!preVisitor.mCanHandle && preVisitor.mAutomaticChromeDispatch && content) {
// Event target couldn't handle the event. Try to propagate to chrome.
nsEventTargetChainItem::Destroy(pool.GetPool(), targetEtci);
targetEtci = EventTargetChainItemForChromeTarget(pool, content);
NS_ENSURE_STATE(targetEtci);
targetEtci->PreHandleEvent(preVisitor);
}
if (preVisitor.mCanHandle) {
// At least the original target can handle the event.
// Setting the retarget to the |target| simplifies retargeting code.
@ -584,6 +615,7 @@ nsEventDispatcher::Dispatch(nsISupports* aTarget,
targetEtci->SetNewTarget(t);
nsEventTargetChainItem* topEtci = targetEtci;
while (preVisitor.mParentTarget) {
nsIDOMEventTarget* parentTarget = preVisitor.mParentTarget;
nsEventTargetChainItem* parentEtci =
nsEventTargetChainItem::Create(pool.GetPool(), preVisitor.mParentTarget,
topEtci);
@ -610,6 +642,24 @@ nsEventDispatcher::Dispatch(nsISupports* aTarget,
} else {
nsEventTargetChainItem::Destroy(pool.GetPool(), parentEtci);
parentEtci = nullptr;
if (preVisitor.mAutomaticChromeDispatch && content) {
// Even if the current target can't handle the event, try to
// propagate to chrome.
nsCOMPtr<nsINode> disabledTarget = do_QueryInterface(parentTarget);
if (disabledTarget) {
parentEtci = EventTargetChainItemForChromeTarget(pool,
disabledTarget,
topEtci);
if (parentEtci) {
parentEtci->PreHandleEvent(preVisitor);
if (preVisitor.mCanHandle) {
targetEtci->SetNewTarget(parentTarget);
topEtci = parentEtci;
continue;
}
}
}
}
break;
}
}

View File

@ -70,8 +70,8 @@ PR_STATIC_ASSERT((uint32_t)eButtonElementTypesMax < (uint32_t)NS_FORM_INPUT_ELEM
PR_STATIC_ASSERT((uint32_t)eInputElementTypesMax < 1<<8);
#define NS_IFORMCONTROL_IID \
{ 0xbc53dcf5, 0xbd4f, 0x4991, \
{ 0xa1, 0x87, 0xc4, 0x57, 0x98, 0x54, 0xda, 0x6e } }
{ 0x4b89980c, 0x4dcd, 0x428f, \
{ 0xb7, 0xad, 0x43, 0x5b, 0x93, 0x29, 0x79, 0xec } }
/**
* Interface which all form controls (e.g. buttons, checkboxes, text,
@ -182,6 +182,10 @@ public:
*/
inline bool AllowDraggableChildren() const;
virtual bool IsDisabledForEvents(uint32_t aMessage)
{
return false;
}
protected:
/**

View File

@ -1178,7 +1178,7 @@ protected:
void* aData);
// Returns true if the event should not be handled from PreHandleEvent
virtual bool IsElementDisabledForEvents(uint32_t aMessage, nsIFrame* aFrame);
bool IsElementDisabledForEvents(uint32_t aMessage, nsIFrame* aFrame);
// The focusability state of this form control. eUnfocusable means that it
// shouldn't be focused at all, eInactiveWindow means it's in an inactive

View File

@ -82,6 +82,7 @@ public:
NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
NS_IMETHOD SaveState();
bool RestoreState(nsPresState* aState);
virtual bool IsDisabledForEvents(uint32_t aMessage);
nsEventStates IntrinsicState() const;
@ -251,17 +252,22 @@ nsHTMLButtonElement::ParseAttribute(int32_t aNamespaceID,
aResult);
}
nsresult
nsHTMLButtonElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
bool
nsHTMLButtonElement::IsDisabledForEvents(uint32_t aMessage)
{
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
nsIFrame* formFrame = NULL;
if (formControlFrame) {
formFrame = do_QueryFrame(formControlFrame);
}
return IsElementDisabledForEvents(aMessage, formFrame);
}
nsresult
nsHTMLButtonElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
{
aVisitor.mCanHandle = false;
if (IsElementDisabledForEvents(aVisitor.mEvent->message, formFrame)) {
if (IsDisabledForEvents(aVisitor.mEvent->message)) {
return NS_OK;
}

View File

@ -74,13 +74,19 @@ NS_IMPL_STRING_ATTR(nsHTMLFieldSetElement, Name, name)
// nsIConstraintValidation
NS_IMPL_NSICONSTRAINTVALIDATION(nsHTMLFieldSetElement)
bool
nsHTMLFieldSetElement::IsDisabledForEvents(uint32_t aMessage)
{
return IsElementDisabledForEvents(aMessage, nullptr);
}
// nsIContent
nsresult
nsHTMLFieldSetElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
{
// Do not process any DOM events if the element is disabled.
aVisitor.mCanHandle = false;
if (IsElementDisabledForEvents(aVisitor.mEvent->message, NULL)) {
if (IsDisabledForEvents(aVisitor.mEvent->message)) {
return NS_OK;
}

View File

@ -51,6 +51,7 @@ public:
NS_IMETHOD_(uint32_t) GetType() const { return NS_FORM_FIELDSET; }
NS_IMETHOD Reset();
NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
virtual bool IsDisabledForEvents(uint32_t aMessage);
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
virtual nsXPCClassInfo* GetClassInfo();
virtual nsIDOMNode* AsDOMNode() { return this; }

View File

@ -2243,12 +2243,18 @@ nsHTMLInputElement::NeedToInitializeEditorForEvent(nsEventChainPreVisitor& aVisi
}
}
bool
nsHTMLInputElement::IsDisabledForEvents(uint32_t aMessage)
{
return IsElementDisabledForEvents(aMessage, GetPrimaryFrame());
}
nsresult
nsHTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
{
// Do not process any DOM events if the element is disabled
aVisitor.mCanHandle = false;
if (IsElementDisabledForEvents(aVisitor.mEvent->message, GetPrimaryFrame())) {
if (IsDisabledForEvents(aVisitor.mEvent->message)) {
return NS_OK;
}

View File

@ -104,6 +104,7 @@ public:
NS_IMETHOD SaveState();
virtual bool RestoreState(nsPresState* aState);
virtual bool AllowDrop();
virtual bool IsDisabledForEvents(uint32_t aMessage);
virtual void FieldSetDisabledChanged(bool aNotify);

View File

@ -1483,18 +1483,22 @@ nsHTMLSelectElement::GetAttributeMappingFunction() const
return &MapAttributesIntoRule;
}
nsresult
nsHTMLSelectElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
bool
nsHTMLSelectElement::IsDisabledForEvents(uint32_t aMessage)
{
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
nsIFrame* formFrame = nullptr;
if (formControlFrame) {
formFrame = do_QueryFrame(formControlFrame);
}
return IsElementDisabledForEvents(aMessage, formFrame);
}
nsresult
nsHTMLSelectElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
{
aVisitor.mCanHandle = false;
if (IsElementDisabledForEvents(aVisitor.mEvent->message, formFrame)) {
if (IsDisabledForEvents(aVisitor.mEvent->message)) {
return NS_OK;
}

View File

@ -271,6 +271,7 @@ public:
NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
NS_IMETHOD SaveState();
virtual bool RestoreState(nsPresState* aState);
virtual bool IsDisabledForEvents(uint32_t aMessage);
virtual void FieldSetDisabledChanged(bool aNotify);

View File

@ -97,6 +97,7 @@ public:
NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
NS_IMETHOD SaveState();
virtual bool RestoreState(nsPresState* aState);
virtual bool IsDisabledForEvents(uint32_t aMessage);
virtual void FieldSetDisabledChanged(bool aNotify);
@ -665,17 +666,22 @@ nsHTMLTextAreaElement::GetAttributeMappingFunction() const
return &MapAttributesIntoRule;
}
nsresult
nsHTMLTextAreaElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
bool
nsHTMLTextAreaElement::IsDisabledForEvents(uint32_t aMessage)
{
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
nsIFrame* formFrame = NULL;
if (formControlFrame) {
formFrame = do_QueryFrame(formControlFrame);
}
return IsElementDisabledForEvents(aMessage, formFrame);
}
nsresult
nsHTMLTextAreaElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
{
aVisitor.mCanHandle = false;
if (IsElementDisabledForEvents(aVisitor.mEvent->message, formFrame)) {
if (IsDisabledForEvents(aVisitor.mEvent->message)) {
return NS_OK;
}

View File

@ -1126,6 +1126,7 @@ nsXULElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
// Stop building the event target chain for the original event.
// We don't want it to propagate to any DOM nodes.
aVisitor.mCanHandle = false;
aVisitor.mAutomaticChromeDispatch = false;
// XXX sXBL/XBL2 issue! Owner or current document?
nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(GetCurrentDoc()));

View File

@ -67,6 +67,7 @@
#include "nsIDOMFileHandle.h"
#include "nsPrintfCString.h"
#include "nsViewportInfo.h"
#include "nsIFormControl.h"
using namespace mozilla;
using namespace mozilla::dom;
@ -3090,3 +3091,26 @@ nsDOMWindowUtils::AllowScriptsToClose()
static_cast<nsGlobalWindow*>(window.get())->AllowScriptsToClose();
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::IsNodeDisabledForEvents(nsIDOMNode* aNode, bool* aRetVal)
{
*aRetVal = false;
if (!nsContentUtils::IsCallerChrome()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsINode> n = do_QueryInterface(aNode);
nsINode* node = n;
while (node) {
if (node->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
nsCOMPtr<nsIFormControl> fc = do_QueryInterface(node);
if (fc && fc->IsDisabledForEvents(NS_EVENT_NULL)) {
*aRetVal = true;
break;
}
}
node = node->GetParentNode();
}
return NS_OK;
}

View File

@ -40,7 +40,7 @@ interface nsIDOMTouch;
interface nsIDOMClientRect;
interface nsIURI;
[scriptable, uuid(C98B7275-93C4-4EAD-B7CF-573D872C1071)]
[scriptable, uuid(2196a216-ed3c-46dd-aa24-9f5b3ac17539)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -1285,4 +1285,11 @@ interface nsIDOMWindowUtils : nsISupports {
* was created can be closed using scripts.
*/
void allowScriptsToClose();
/**
* In certain cases the event handling of nodes, form controls in practice,
* may be disabled. Such cases are for example the existence of disabled
* attribute or -moz-user-input: none/disabled.
*/
boolean isNodeDisabledForEvents(in nsIDOMNode aNode);
};

View File

@ -6563,34 +6563,6 @@ PresShell::HandleEventInternal(nsEvent* aEvent, nsEventStatus* aStatus)
}
}
if (eventTarget) {
#ifdef MOZ_B2G
// Horrible hack for B2G to propagate events even from
// disabled form elements to chrome. See bug 804811.
// See also nsGenericHTMLFormElement::IsElementDisabledForEvents.
if (aEvent->message != NS_MOUSE_MOVE) {
nsINode* possibleFormElement = eventTarget->ChromeOnlyAccess() ?
static_cast<nsIContent*>(eventTarget.get())->
FindFirstNonChromeOnlyAccessContent() :
eventTarget;
if (possibleFormElement &&
possibleFormElement->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) {
nsEvent event(true, NS_EVENT_TYPE_NULL);
nsCOMArray<nsIDOMEventTarget> targets;
nsEventDispatcher::Dispatch(eventTarget, nullptr, &event, nullptr,
nullptr, nullptr, &targets);
nsCOMPtr<nsIContent> last;
if (targets.Count()) {
last = do_QueryInterface(targets[targets.Count() - 1]);
}
if (!targets.Count() ||
(last &&
nsContentUtils::ContentIsDescendantOf(last,
possibleFormElement))) {
aEvent->mFlags.mOnlyChromeDispatch = true;
}
}
}
#endif
if (aEvent->eventStructType == NS_COMPOSITION_EVENT ||
aEvent->eventStructType == NS_TEXT_EVENT) {
nsIMEStateManager::DispatchCompositionEvent(eventTarget,