--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -2016,44 +2016,90 @@ public:
nsCOMPtr<nsISupports> mTarget;
RefPtr<nsPresContext> mContext;
EventMessage mEventMessage;
bool mWindowRaised;
bool mIsRefocus;
nsCOMPtr<EventTarget> mRelatedTarget;
};
+class FocusInOutEvent : public Runnable
+{
+public:
+ FocusInOutEvent(nsISupports* aTarget, EventMessage aEventMessage,
+ nsPresContext* aContext, EventTarget* aRelatedTarget)
+ : mTarget(aTarget)
+ , mContext(aContext)
+ , mEventMessage(aEventMessage)
+ , mRelatedTarget(aRelatedTarget)
+ {
+ }
+
+ NS_IMETHOD Run()
+ {
+ InternalFocusEvent event(true, mEventMessage);
+ event.mFlags.mBubbles = true;
+ event.mFlags.mCancelable = false;
+ event.mRelatedTarget = mRelatedTarget;
+ return EventDispatcher::Dispatch(mTarget, mContext, &event);
+ }
+
+ nsCOMPtr<nsISupports> mTarget;
+ RefPtr<nsPresContext> mContext;
+ EventMessage mEventMessage;
+ nsCOMPtr<EventTarget> mRelatedTarget;
+};
+
static nsIDocument*
GetDocumentHelper(EventTarget* aTarget)
{
nsCOMPtr<nsINode> node = do_QueryInterface(aTarget);
if (!node) {
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aTarget);
return win ? win->GetExtantDoc() : nullptr;
}
return node->OwnerDoc();
}
+void nsFocusManager::SendFocusInOrOutEvent(EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsISupports* aTarget,
+ EventTarget* aRelatedTarget)
+{
+ NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
+ "Wrong event type for SendFocusInOrOutEvent");
+
+ nsContentUtils::AddScriptRunner(
+ new FocusInOutEvent(
+ aTarget,
+ aEventMessage,
+ aPresShell->GetPresContext(),
+ aRelatedTarget));
+}
+
void
nsFocusManager::SendFocusOrBlurEvent(EventMessage aEventMessage,
nsIPresShell* aPresShell,
nsIDocument* aDocument,
nsISupports* aTarget,
uint32_t aFocusMethod,
bool aWindowRaised,
bool aIsRefocus,
EventTarget* aRelatedTarget)
{
NS_ASSERTION(aEventMessage == eFocus || aEventMessage == eBlur,
"Wrong event type for SendFocusOrBlurEvent");
nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
nsCOMPtr<nsIDocument> eventTargetDoc = GetDocumentHelper(eventTarget);
nsCOMPtr<nsIDocument> relatedTargetDoc = GetDocumentHelper(aRelatedTarget);
+ nsCOMPtr<nsPIDOMWindowOuter> currentWindow = mFocusedWindow;
+ nsCOMPtr<nsIContent> currentFocusedContent = currentWindow ?
+ currentWindow->GetFocusedNode() : nullptr;
// set aRelatedTarget to null if it's not in the same document as eventTarget
if (eventTargetDoc != relatedTargetDoc) {
aRelatedTarget = nullptr;
}
bool dontDispatchEvent =
eventTargetDoc && nsContentUtils::IsUserFocusIgnored(eventTargetDoc);
@@ -2094,16 +2140,30 @@ nsFocusManager::SendFocusOrBlurEvent(Eve
}
}
#endif
if (!dontDispatchEvent) {
nsContentUtils::AddScriptRunner(
new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
aWindowRaised, aIsRefocus, aRelatedTarget));
+ nsCOMPtr<nsIContent> newFocus = currentWindow ? currentWindow->GetFocusedNode() : nullptr;
+
+ if (currentFocusedContent == newFocus) {
+ EventMessage focusInOrOutMessage;
+
+ if (aEventMessage == eFocus) {
+ focusInOrOutMessage = eFocusIn;
+ }
+ else {
+ focusInOrOutMessage = eFocusOut;
+ }
+ SendFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
+ aRelatedTarget);
+ }
}
}
void
nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
nsIContent* aContent,
uint32_t aFlags)
{
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -292,16 +292,32 @@ protected:
nsIDocument* aDocument,
nsISupports* aTarget,
uint32_t aFocusMethod,
bool aWindowRaised,
bool aIsRefocus = false,
mozilla::dom::EventTarget* aRelatedTarget = nullptr);
/**
+ * Send a focusin or focusout event
+ *
+ * aEventMessage should be either eFocusIn or eFocusOut.
+ *
+ * aTarget is the content the event will fire on (the object to be
+ * focused for focusin, the object to be blurred for focusout).
+ *
+ * aRelatedTarget is the content related to the event (the object
+ * losing focus for focusin, the object getting focus for focusout).
+ */
+ void SendFocusInOrOutEvent(mozilla::EventMessage aEventMessage,
+ nsIPresShell* aPresShell,
+ nsISupports* aTarget,
+ mozilla::dom::EventTarget* aRelatedTarget = nullptr);
+
+ /**
* Scrolls aContent into view unless the FLAG_NOSCROLL flag is set.
*/
void ScrollIntoView(nsIPresShell* aPresShell,
nsIContent* aContent,
uint32_t aFlags);
/**
* Raises the top-level window aWindow at the widget level.
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -790,16 +790,18 @@ GK_ATOM(onenterpincodereq, "onenterpinco
GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
GK_ATOM(onerror, "onerror")
GK_ATOM(onevicted, "onevicted")
GK_ATOM(onfacesdetected, "onfacesdetected")
GK_ATOM(onfailed, "onfailed")
GK_ATOM(onfetch, "onfetch")
GK_ATOM(onfinish, "onfinish")
GK_ATOM(onfocus, "onfocus")
+GK_ATOM(onfocusin, "onfocusin")
+GK_ATOM(onfocusout, "onfocusout")
GK_ATOM(onfrequencychange, "onfrequencychange")
GK_ATOM(onfullscreenchange, "onfullscreenchange")
GK_ATOM(onfullscreenerror, "onfullscreenerror")
GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange")
GK_ATOM(onget, "onget")
GK_ATOM(ongroupchange, "ongroupchange")
GK_ATOM(onhashchange, "onhashchange")
GK_ATOM(onheadphoneschange, "onheadphoneschange")
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -494,16 +494,24 @@ FORWARDED_EVENT(blur,
ERROR_EVENT(error,
eLoadError,
EventNameType_All,
eBasicEventClass)
FORWARDED_EVENT(focus,
eFocus,
EventNameType_HTMLXUL,
eFocusEventClass)
+FORWARDED_EVENT(focusin,
+ eFocusIn,
+ EventNameType_HTMLXUL,
+ eFocusEventClass)
+FORWARDED_EVENT(focusout,
+ eFocusOut,
+ EventNameType_HTMLXUL,
+ eFocusEventClass)
FORWARDED_EVENT(load,
eLoad,
EventNameType_All,
eBasicEventClass)
FORWARDED_EVENT(resize,
eResize,
EventNameType_All,
eBasicEventClass)
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -196,8 +196,9 @@ support-files =
[test_eventhandler_scoping.html]
[test_bug1013412.html]
skip-if = buildapp == 'b2g' # no wheel events on b2g
[test_dom_activate_event.html]
[test_bug1264380.html]
run-if = (e10s && os != "win") # Bug 1270043, crash at windows platforms; Bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode.
[test_passive_listeners.html]
[test_paste_image.html]
+[test_bug687787.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug687787.html
@@ -0,0 +1,636 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+-->
+<head>
+ <title>Test for Bug 687787</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=687787">Mozilla Bug 687787</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var content = document.getElementById('content');
+var eventStack = [];
+
+function _doc_callback(e){
+ var event = {'type' : e.type, 'target' : e.target, 'relatedTarget' : e.relatedTarget }
+ eventStack.push(event);
+}
+
+function clearEventStack(){
+ eventStack = [];
+}
+
+document.addEventListener("focus", _doc_callback, true);
+document.addEventListener("focusin", _doc_callback, true);
+document.addEventListener("focusout", _doc_callback, true);
+document.addEventListener("blur", _doc_callback, true);
+
+function CompareEventToExpected(e, expected) {
+ if (expected == null || e == null)
+ return false;
+ if (e.type == expected.type && e.target == expected.target && e.relatedTarget == expected.relatedTarget)
+ return true;
+ return false;
+}
+
+function TestEventOrderNormal() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var input3 = document.createElement('input');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ input3.setAttribute('id', 'input3');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+ input3.setAttribute('type', 'text');
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.appendChild(input3);
+ content.style.display = 'block'
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focus',
+ 'target' : input2,
+ 'relatedTarget' : input1},
+ {'type' : 'focusin',
+ 'target' : input2,
+ 'relatedTarget' : input1},
+ {'type' : 'blur',
+ 'target' : input2,
+ 'relatedTarget' : input3},
+ {'type' : 'focusout',
+ 'target' : input2,
+ 'relatedTarget' : input3},
+ {'type' : 'focus',
+ 'target' : input3,
+ 'relatedTarget' : input2},
+ {'type' : 'focusin',
+ 'target' : input3,
+ 'relatedTarget' : input2},
+ ]
+
+ input1.focus();
+ clearEventStack();
+
+ input2.focus();
+ input3.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+function TestEventOrderNormalFiresAtRightTime() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var input3 = document.createElement('input');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ input3.setAttribute('id', 'input3');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+ input3.setAttribute('type', 'text');
+
+ input1.onblur = function(e)
+ {
+ ok(document.activeElement == document.body, 'input1: not focused when blur fires')
+ }
+
+ input1.onfocusout = function(e)
+ {
+ ok(document.activeElement == document.body, 'input1: not focused when focusout fires')
+ }
+
+ input2.onfocus = function(e)
+ {
+ ok(document.activeElement == input2, 'input2: focused when focus fires')
+ }
+
+ input2.onfocusin = function(e)
+ {
+ ok(document.activeElement == input2, 'input2: focused when focusin fires')
+ }
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.style.display = 'block'
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focus',
+ 'target' : input2,
+ 'relatedTarget' : input1},
+ {'type' : 'focusin',
+ 'target' : input2,
+ 'relatedTarget' : input1},
+ ]
+
+ input1.focus();
+ clearEventStack();
+
+ input2.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length ; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+function TestFocusOutRedirectsFocus() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var input3 = document.createElement('input');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ input3.setAttribute('id', 'input3');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+ input3.setAttribute('type', 'text');
+ input1.onfocusout = function () {
+ input3.focus();
+ }
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.appendChild(input3);
+ content.style.display = 'block'
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focus',
+ 'target' : input3,
+ 'relatedTarget' : null},
+ {'type' : 'focusin',
+ 'target' : input3,
+ 'relatedTarget' : null},
+ ]
+
+ input1.focus();
+ clearEventStack();
+ input2.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+function TestFocusInRedirectsFocus() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var input3 = document.createElement('input');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ input3.setAttribute('id', 'input3');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+ input3.setAttribute('type', 'text');
+ input2.onfocusin = function () {
+ input3.focus();
+ }
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.appendChild(input3);
+ content.style.display = 'block'
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focus',
+ 'target' : input2,
+ 'relatedTarget' : input1},
+ {'type' : 'focusin',
+ 'target' : input2,
+ 'relatedTarget' : input1},
+ {'type' : 'blur',
+ 'target' : input2,
+ 'relatedTarget' : input3},
+ {'type' : 'focusout',
+ 'target' : input2,
+ 'relatedTarget' : input3},
+ {'type' : 'focus',
+ 'target' : input3,
+ 'relatedTarget' : input2},
+ {'type' : 'focusin',
+ 'target' : input3,
+ 'relatedTarget' : input2},
+ ]
+
+ input1.focus();
+ clearEventStack();
+ input2.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+function TestBlurRedirectsFocus() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var input3 = document.createElement('input');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ input3.setAttribute('id', 'input3');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+ input3.setAttribute('type', 'text');
+ input1.onblur = function () {
+ input3.focus();
+ }
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.appendChild(input3);
+ content.style.display = 'block'
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focus',
+ 'target' : input3,
+ 'relatedTarget' : null},
+ {'type' : 'focusin',
+ 'target' : input3,
+ 'relatedTarget' : null},
+ ]
+
+ input1.focus();
+ clearEventStack();
+ input2.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+function TestFocusRedirectsFocus() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var input3 = document.createElement('input');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ input3.setAttribute('id', 'input3');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+ input3.setAttribute('type', 'text');
+ input2.onfocus = function () {
+ input3.focus();
+ }
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.appendChild(input3);
+ content.style.display = 'block'
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focus',
+ 'target' : input2,
+ 'relatedTarget' : input1},
+ {'type' : 'blur',
+ 'target' : input2,
+ 'relatedTarget' : input3},
+ {'type' : 'focusout',
+ 'target' : input2,
+ 'relatedTarget' : input3},
+ {'type' : 'focus',
+ 'target' : input3,
+ 'relatedTarget' : input2},
+ {'type' : 'focusin',
+ 'target' : input3,
+ 'relatedTarget' : input2},
+ ]
+
+ input1.focus();
+ clearEventStack();
+ input2.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+function TestEventOrderDifferentDocument() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var iframe1 = document.createElement('iframe');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ iframe1.setAttribute('id', 'iframe1');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+
+ content.appendChild(input1);
+ content.appendChild(iframe1);
+ iframe1.contentDocument.body.appendChild(input2);
+ content.style.display = 'block'
+
+ iframe1.contentDocument.addEventListener("focus", _doc_callback, true);
+ iframe1.contentDocument.addEventListener("focusin", _doc_callback, true);
+ iframe1.contentDocument.addEventListener("focusout", _doc_callback, true);
+ iframe1.contentDocument.addEventListener("blur", _doc_callback, true);
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : null},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : null},
+ {'type' : 'blur',
+ 'target' : document,
+ 'relatedTarget' : null},
+ {'type' : 'focusout',
+ 'target' : document,
+ 'relatedTarget' : null},
+ {'type' : 'focus',
+ 'target' : iframe1.contentDocument,
+ 'relatedTarget' : null},
+ {'type' : 'focusin',
+ 'target' : iframe1.contentDocument,
+ 'relatedTarget' : null},
+ {'type' : 'focus',
+ 'target' : input2,
+ 'relatedTarget' : null},
+ {'type' : 'focusin',
+ 'target' : input2,
+ 'relatedTarget' : null},
+ ]
+
+ input1.focus();
+ clearEventStack();
+ input2.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + expectedEventOrder[i].target
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+
+function TestFocusOutMovesTarget() {
+
+ var input1 = document.createElement('input');
+ var input2 = document.createElement('input');
+ var iframe1 = document.createElement('iframe');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input2.setAttribute('id', 'input2');
+ iframe1.setAttribute('id', 'iframe1');
+ input1.setAttribute('type', 'text');
+ input2.setAttribute('type', 'text');
+
+ input1.onfocusout = function () {
+ iframe1.contentDocument.body.appendChild(input2);
+ }
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.appendChild(iframe1);
+ content.style.display = 'block'
+
+ iframe1.contentDocument.addEventListener("focus", _doc_callback, true);
+ iframe1.contentDocument.addEventListener("focusin", _doc_callback, true);
+ iframe1.contentDocument.addEventListener("focusout", _doc_callback, true);
+ iframe1.contentDocument.addEventListener("blur", _doc_callback, true);
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : input2},
+ {'type' : 'focus',
+ 'target' : input2,
+ 'relatedTarget' : null},
+ {'type' : 'focusin',
+ 'target' : input2,
+ 'relatedTarget' : null},
+ ]
+
+ input1.focus();
+ clearEventStack();
+ input2.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+function TestFocusFromNothingFiresFocusIn() {
+
+ var input1 = document.createElement('input');
+ var content = document.getElementById('content');
+
+ input1.setAttribute('id', 'input1');
+ input1.setAttribute('type', 'text');
+
+ content.appendChild(input1);
+
+ expectedEventOrder = [
+ {'type' : 'focus',
+ 'target' : document,
+ 'relatedTarget' : null},
+ {'type' : 'focusin',
+ 'target' : document,
+ 'relatedTarget' : null},
+ {'type' : 'focus',
+ 'target' : input1,
+ 'relatedTarget' : null},
+ {'type' : 'focusin',
+ 'target' : input1,
+ 'relatedTarget' : null},
+ ]
+
+ window.blur();
+ clearEventStack();
+ input1.focus();
+
+ for (var i = 0; i < expectedEventOrder.length || i < eventStack.length; i++) {
+ ok(CompareEventToExpected(expectedEventOrder[i], eventStack[i]), 'Normal event order is correct: Event ' + i + ': '
+ + 'Expected ('
+ + expectedEventOrder[i].type + ','
+ + (expectedEventOrder[i].target ? expectedEventOrder[i].target.id : null) + ','
+ + (expectedEventOrder[i].relatedTarget ? expectedEventOrder[i].relatedTarget.id : null) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ while (content.firstChild) {
+ content.removeChild(content.firstChild);
+ }
+}
+
+TestEventOrderNormal();
+TestEventOrderNormalFiresAtRightTime();
+TestFocusOutRedirectsFocus();
+TestFocusInRedirectsFocus();
+TestBlurRedirectsFocus();
+TestFocusRedirectsFocus();
+TestFocusOutMovesTarget();
+TestEventOrderDifferentDocument();
+TestFocusFromNothingFiresFocusIn();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/EventHandler.webidl
+++ b/dom/webidl/EventHandler.webidl
@@ -27,16 +27,18 @@ typedef OnErrorEventHandlerNonNull? OnEr
[NoInterfaceObject]
interface GlobalEventHandlers {
attribute EventHandler onabort;
attribute EventHandler onblur;
// We think the spec is wrong here. See OnErrorEventHandlerForNodes/Window
// below.
// attribute OnErrorEventHandler onerror;
attribute EventHandler onfocus;
+ attribute EventHandler onfocusin;
+ attribute EventHandler onfocusout;
//(Not implemented)attribute EventHandler oncancel;
attribute EventHandler oncanplay;
attribute EventHandler oncanplaythrough;
attribute EventHandler onchange;
attribute EventHandler onclick;
//(Not implemented)attribute EventHandler onclose;
attribute EventHandler oncontextmenu;
//(Not implemented)attribute EventHandler oncuechange;
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -37533,16 +37533,22 @@
}
],
"svg/linking/scripted/href-script-element.html": [
{
"path": "svg/linking/scripted/href-script-element.html",
"url": "/svg/linking/scripted/href-script-element.html"
}
],
+ "uievents/order-of-events/focus-events/focus-automated-blink-webkit.html": [
+ {
+ "path": "uievents/order-of-events/focus-events/focus-automated-blink-webkit.html",
+ "url": "/uievents/order-of-events/focus-events/focus-automated-blink-webkit.html"
+ }
+ ],
"web-animations/interfaces/Animation/effect.html": [
{
"path": "web-animations/interfaces/Animation/effect.html",
"url": "/web-animations/interfaces/Animation/effect.html"
}
],
"web-animations/interfaces/KeyframeEffect/iterationComposite.html": [
{
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/uievents/order-of-events/focus-events/focus-automated-blink-webkit.html
@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+<!-- Modified from Chris Rebert's manual version -->
+<!-- This documents the behavior according to blink's implementation -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Focus-related events should fire in the correct order</title>
+ <link rel="help" href="https://w3c.github.io/uievents/#events-focusevent-event-order">
+ <meta name="flags" content="interact">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body id="body">
+ <input type="text" id="a" value="First">
+ <input type="text" id="b" value="Second">
+ <br>
+ <input type="text" id="c" value="Third">
+ <iframe id="iframe">
+ </iframe>
+ <br>
+ <script>
+
+ var test_id = 0;
+ var tests = ['normal', 'iframe']
+
+ function record(evt) {
+ if (done && (evt.type == 'focusin' || evt.type == 'focus') && (evt.target == c)) {
+ startNext();
+ }
+ if (!done) {
+ var activeElement = document.activeElement ?
+ (document.activeElement.tagName === 'IFRAME' ?
+ document.activeElement.contentDocument.activeElement.id :
+ document.activeElement.id) : null;
+ events[tests[test_id]].push(evt.type);
+ targets[tests[test_id]].push(evt.target.id);
+ focusedElements[tests[test_id]].push(activeElement);
+ relatedTargets[tests[test_id]].push(evt.relatedTarget ? evt.relatedTarget.id : null);
+ }
+ }
+ function startNext() {
+ done = false;
+ test_id++;
+ }
+ function finish() {
+ done = true;
+ }
+ var relevantEvents = [
+ 'focus',
+ 'blur',
+ 'focusin',
+ 'focusout'
+ ];
+
+ var iframe = document.getElementById('iframe');
+ var a = document.getElementById('a');
+ var b = document.getElementById('b');
+ var c = document.getElementById('c');
+ var d = document.createElement('input');
+
+ d.setAttribute('id', 'd');
+ d.setAttribute('type', 'text');
+ d.setAttribute('value', 'Fourth');
+
+ var events = {'normal': [], 'iframe': []};
+ var targets = {'normal': [], 'iframe': []};
+ var focusedElements = {'normal': [], 'iframe': []};
+ var relatedTargets = {'normal': [], 'iframe': []};
+ var done = false;
+
+ var async_test_normal = async_test('Focus-related events should fire in the correct order (same DocumentOwner)');
+ var async_test_iframe_static = async_test('Focus-related events should fire in the correct order (different DocumentOwner)');
+
+ window.onload = function(evt) {
+
+ iframe.contentDocument.body.appendChild(d);
+
+ var inputs = [a, b, c, d];
+
+ for (var i = 0; i < inputs.length; i++) {
+ for (var k = 0; k < relevantEvents.length; k++) {
+ inputs[i].addEventListener(relevantEvents[k], record, false);
+ }
+ }
+
+ a.addEventListener('focusin', function() { b.focus(); }, false);
+ b.addEventListener('focusin', function() {
+ console.log(events['normal']);
+ console.log(targets['normal']);
+ console.log(relatedTargets['normal']);
+ console.log(focusedElements['normal']);
+
+ async_test_normal.step( function() {
+ assert_array_equals(
+ events['normal'],
+ ['focus', 'focusin', 'blur', 'focusout', 'focus', 'focusin'],
+ 'Focus-related events should fire in this order: focusin, focus, focusout, focusin, blur, focus'
+ );
+
+ assert_array_equals(
+ targets['normal'],
+ [ 'a', 'a', 'a', 'a', 'b', 'b'],
+ 'Focus-related events should fire at the correct targets'
+ );
+
+ assert_array_equals(
+ relatedTargets['normal'],
+ [ null, null, 'b', 'b', 'a', 'a'],
+ 'Focus-related events should reference correct relatedTargets'
+ );
+
+ assert_array_equals(
+ focusedElements['normal'],
+ [ 'a', 'a', 'body', 'body', 'b', 'b'],
+ 'Focus-related events should fire at the correct time relative to actual focus changes'
+ );
+
+ async_test_normal.done();
+ });
+
+ b.addEventListener('focusout', function() { finish(); c.focus(); });
+ b.blur();
+
+ }, false);
+
+ c.addEventListener('focusin', function() {d.focus();});
+ d.addEventListener('focusin', function() {
+ console.log(events['iframe']);
+ console.log(targets['iframe']);
+ console.log(relatedTargets['iframe']);
+ console.log(focusedElements['iframe']);
+
+ async_test_iframe_static.step(function() {
+ assert_array_equals(
+ events['iframe'],
+ ['focus', 'focusin', 'blur', 'focusout', 'focus', 'focusin'],
+ 'Focus-related events should fire in this order: focusin, focus, focusout, focusin, blur, focus'
+ );
+
+ assert_array_equals(
+ targets['iframe'],
+ [ 'c', 'c', 'c', 'c', 'd', 'd'],
+ 'Focus-related events should fire at the correct targets'
+ );
+
+ assert_array_equals(
+ relatedTargets['iframe'],
+ [ null, null, null, null, null, null],
+ 'Focus-related events should reference correct relatedTargets'
+ );
+
+ assert_array_equals(
+ focusedElements['iframe'],
+ [ 'c', 'c', 'body', 'body', 'd', 'd'],
+ 'Focus-related events should fire at the correct time relative to actual focus changes'
+ );
+
+ async_test_iframe_static.done();
+ });
+
+ d.addEventListener('focusout', function() { finish();});
+
+ }, false);
+
+ a.focus();
+
+ }
+
+ </script>
+ </body>
+</html>
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -123,16 +123,18 @@ NS_EVENT_MESSAGE(eFormSubmit)
NS_EVENT_MESSAGE(eFormReset)
NS_EVENT_MESSAGE(eFormChange)
NS_EVENT_MESSAGE(eFormSelect)
NS_EVENT_MESSAGE(eFormInvalid)
//Need separate focus/blur notifications for non-native widgets
NS_EVENT_MESSAGE(eFocus)
NS_EVENT_MESSAGE(eBlur)
+NS_EVENT_MESSAGE(eFocusIn)
+NS_EVENT_MESSAGE(eFocusOut)
NS_EVENT_MESSAGE(eDragEnter)
NS_EVENT_MESSAGE(eDragOver)
NS_EVENT_MESSAGE(eDragExit)
NS_EVENT_MESSAGE(eDrag)
NS_EVENT_MESSAGE(eDragEnd)
NS_EVENT_MESSAGE(eDragStart)
NS_EVENT_MESSAGE(eDrop)
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -3046,16 +3046,18 @@ case _value: eventName.AssignLiteral(_na
{
_ASSIGN_eventName(eBlur,"eBlur");
_ASSIGN_eventName(eDrop,"eDrop");
_ASSIGN_eventName(eDragEnter,"eDragEnter");
_ASSIGN_eventName(eDragExit,"eDragExit");
_ASSIGN_eventName(eDragOver,"eDragOver");
_ASSIGN_eventName(eEditorInput,"eEditorInput");
_ASSIGN_eventName(eFocus,"eFocus");
+ _ASSIGN_eventName(FocusIn,"eFocusIn");
+ _ASSIGN_eventName(FocusOut,"eFocusOut");
_ASSIGN_eventName(eFormSelect,"eFormSelect");
_ASSIGN_eventName(eFormChange,"eFormChange");
_ASSIGN_eventName(eFormReset,"eFormReset");
_ASSIGN_eventName(eFormSubmit,"eFormSubmit");
_ASSIGN_eventName(eImageAbort,"eImageAbort");
_ASSIGN_eventName(eLoadError,"eLoadError");
_ASSIGN_eventName(eKeyDown,"eKeyDown");
_ASSIGN_eventName(eKeyPress,"eKeyPress");