--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -2016,44 +2016,112 @@ 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,
+ nsPIDOMWindowOuter* aOriginalFocusedWindow,
+ nsIContent* aOriginalFocusedContent,
+ EventTarget* aRelatedTarget)
+ : mTarget(aTarget)
+ , mContext(aContext)
+ , mEventMessage(aEventMessage)
+ , mOriginalFocusedWindow(aOriginalFocusedWindow)
+ , mOriginalFocusedContent(aOriginalFocusedContent)
+ , mRelatedTarget(aRelatedTarget)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIContent> originalWindowFocus = mOriginalFocusedWindow ?
+ mOriginalFocusedWindow->GetFocusedNode() :
+ nullptr;
+ // Blink does not check that focus is the same after blur, but WebKit does.
+ // Opt to follow Blink's behavior (see bug 687787).
+ if (mEventMessage == eFocusOut ||
+ originalWindowFocus == mOriginalFocusedContent) {
+ InternalFocusEvent event(true, mEventMessage);
+ event.mFlags.mBubbles = true;
+ event.mFlags.mCancelable = false;
+ event.mRelatedTarget = mRelatedTarget;
+ return EventDispatcher::Dispatch(mTarget, mContext, &event);
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> mTarget;
+ RefPtr<nsPresContext> mContext;
+ EventMessage mEventMessage;
+ nsCOMPtr<nsPIDOMWindowOuter> mOriginalFocusedWindow;
+ nsCOMPtr<nsIContent> mOriginalFocusedContent;
+ 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,
+ nsPIDOMWindowOuter* aCurrentFocusedWindow,
+ nsIContent* aCurrentFocusedContent,
+ EventTarget* aRelatedTarget)
+{
+ NS_ASSERTION(aEventMessage == eFocusIn || aEventMessage == eFocusOut,
+ "Wrong event type for SendFocusInOrOutEvent");
+
+ nsContentUtils::AddScriptRunner(
+ new FocusInOutEvent(
+ aTarget,
+ aEventMessage,
+ aPresShell->GetPresContext(),
+ aCurrentFocusedWindow,
+ aCurrentFocusedContent,
+ 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<nsPIDOMWindowInner> targetWindow = do_QueryInterface(aTarget);
+ nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(aTarget);
+ 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 +2162,29 @@ nsFocusManager::SendFocusOrBlurEvent(Eve
}
}
#endif
if (!dontDispatchEvent) {
nsContentUtils::AddScriptRunner(
new FocusBlurEvent(aTarget, aEventMessage, aPresShell->GetPresContext(),
aWindowRaised, aIsRefocus, aRelatedTarget));
+
+ // Check that the target is not a window or document before firing
+ // focusin/focusout. Other browsers do not fire focusin/focusout on window,
+ // despite being required in the spec, so follow their behavior.
+ //
+ // As for document, we should not even fire focus/blur, but until then, we
+ // need this check. targetDocument should be removed once bug 1228802 is
+ // resolved.
+ if (!targetWindow && !targetDocument) {
+ EventMessage focusInOrOutMessage = aEventMessage == eFocus ? eFocusIn : eFocusOut;
+ SendFocusInOrOutEvent(focusInOrOutMessage, aPresShell, aTarget,
+ currentWindow, currentFocusedContent, aRelatedTarget);
+ }
}
}
void
nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
nsIContent* aContent,
uint32_t aFlags)
{
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -292,16 +292,40 @@ 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 that gained
+ * focus for focusin, the object blurred for focusout).
+ *
+ * aCurrentFocusedWindow is the window focused before the focus/blur event
+ * was fired.
+ *
+ * aCurrentFocusedContent is the content focused before the focus/blur event
+ * was fired.
+ *
+ * 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,
+ nsPIDOMWindowOuter* aCurrentFocusedWindow,
+ nsIContent* aCurrentFocusedContent,
+ 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
@@ -789,16 +789,18 @@ GK_ATOM(onenabled, "onenabled")
GK_ATOM(onenterpincodereq, "onenterpincodereq")
GK_ATOM(onemergencycbmodechange, "onemergencycbmodechange")
GK_ATOM(onerror, "onerror")
GK_ATOM(onevicted, "onevicted")
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
@@ -498,16 +498,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
@@ -197,8 +197,9 @@ support-files =
[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_messageEvent_init.html]
+[test_bug687787.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug687787.html
@@ -0,0 +1,617 @@
+<!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 _callback(e){
+ var event = {'type' : e.type, 'target' : e.target, 'relatedTarget' : e.relatedTarget }
+ eventStack.push(event);
+}
+
+function clearEventStack(){
+ eventStack = [];
+}
+
+window.addEventListener("focus", _callback, true);
+window.addEventListener("focusin", _callback, true);
+window.addEventListener("focusout", _callback, true);
+window.addEventListener("blur", _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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+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.addEventListener('focusout', 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.addEventListener('focusin', 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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+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.addEventListener('focusout', 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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+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.addEventListener('focusin', 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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+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},
+ {'type' : 'focusout',
+ 'target' : input1,
+ '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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+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", _callback, true);
+ iframe1.contentDocument.addEventListener("focusin", _callback, true);
+ iframe1.contentDocument.addEventListener("focusout", _callback, true);
+ iframe1.contentDocument.addEventListener("blur", _callback, true);
+
+ expectedEventOrder = [
+ {'type' : 'blur',
+ 'target' : input1,
+ 'relatedTarget' : null},
+ {'type' : 'focusout',
+ 'target' : input1,
+ 'relatedTarget' : null},
+ {'type' : 'blur',
+ 'target' : document,
+ 'relatedTarget' : null},
+ {'type' : 'blur',
+ 'target' : window,
+ 'relatedTarget' : null},
+ {'type' : 'focus',
+ '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) + '), '
+ + 'Actual ('
+ + eventStack[i].type + ','
+ + (eventStack[i].target ? eventStack[i].target.id : null) + ','
+ + (eventStack[i].relatedTarget ? eventStack[i].relatedTarget.id : null) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+
+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.addEventListener('focusout', function () {
+ iframe1.contentDocument.body.appendChild(input2);
+ });
+
+ content.appendChild(input1);
+ content.appendChild(input2);
+ content.appendChild(iframe1);
+ content.style.display = 'block'
+
+ iframe1.contentDocument.addEventListener("focus", _callback, true);
+ iframe1.contentDocument.addEventListener("focusin", _callback, true);
+ iframe1.contentDocument.addEventListener("focusout", _callback, true);
+ iframe1.contentDocument.addEventListener("blur", _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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+function TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput() {
+
+ 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' : 'focus',
+ 'target' : window,
+ '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) + ')');
+ }
+
+ content.innerHTML = '';
+}
+
+TestEventOrderNormal();
+TestEventOrderNormalFiresAtRightTime();
+TestFocusOutRedirectsFocus();
+TestFocusInRedirectsFocus();
+TestBlurRedirectsFocus();
+TestFocusRedirectsFocus();
+TestFocusOutMovesTarget();
+TestEventOrderDifferentDocument();
+TestBlurWindowAndRefocusInputOnlyFiresFocusInOnInput();
+
+</script>
+</pre>
+</body>
+</html>
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -38688,16 +38688,22 @@
}
],
"secure-contexts/shared-worker-secure-first.https.html": [
{
"path": "secure-contexts/shared-worker-secure-first.https.html",
"url": "/secure-contexts/shared-worker-secure-first.https.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"
+ }
+ ],
"webaudio/the-audio-api/the-constantsourcenode-interface/test-constantsourcenode.html": [
{
"path": "webaudio/the-audio-api/the-constantsourcenode-interface/test-constantsourcenode.html",
"url": "/webaudio/the-audio-api/the-constantsourcenode-interface/test-constantsourcenode.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,170 @@
+<!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
@@ -124,16 +124,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
@@ -3042,16 +3042,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(eFocusIn,"eFocusIn");
+ _ASSIGN_eventName(eFocusOut,"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");