Bug 1663931 - Avoid moving focus when changing iframe remoteness. r=nika,mccr8

Differential Revision: https://phabricator.services.mozilla.com/D99215
This commit is contained in:
Henri Sivonen 2021-02-22 10:51:51 +00:00
Родитель 1d1e362316
Коммит 9d6d02b341
24 изменённых файлов: 412 добавлений и 43 удалений

Просмотреть файл

@ -19,6 +19,7 @@
#include "mozilla/BasePrincipal.h"
#include "mozilla/Components.h"
#include "mozilla/ProfilerLabels.h"
#include "nsFocusManager.h"
namespace mozilla {
@ -52,6 +53,8 @@ NS_IMETHODIMP
WindowDestroyedEvent::Run() {
AUTO_PROFILER_LABEL("WindowDestroyedEvent::Run", OTHER);
nsCOMPtr<nsPIDOMWindowOuter> nukedOuter;
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (!observerService) {
return NS_OK;
@ -105,6 +108,7 @@ WindowDestroyedEvent::Run() {
nsGlobalWindowOuter* outer =
nsGlobalWindowOuter::FromSupports(window);
currentInner = outer->GetCurrentInnerWindowInternal();
nukedOuter = outer;
}
NS_ENSURE_TRUE(currentInner, NS_OK);
@ -139,6 +143,13 @@ WindowDestroyedEvent::Run() {
} break;
}
if (nukedOuter) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
fm->WasNuked(nukedOuter);
}
}
return NS_OK;
}

Просмотреть файл

@ -960,8 +960,17 @@ void nsFocusManager::WindowShown(mozIDOMWindowProxy* aWindow,
}
}
if (mFocusedWindow != window) {
return;
if (XRE_IsParentProcess()) {
if (mFocusedWindow != window) {
return;
}
} else {
BrowsingContext* bc = window->GetBrowsingContext();
if (!bc || mFocusedBrowsingContextInContent != bc) {
return;
}
// Sync the window for a newly-created OOP iframe
SetFocusedWindowInternal(window, false);
}
if (aNeedsFocus) {
@ -1056,13 +1065,51 @@ void nsFocusManager::WindowHidden(mozIDOMWindowProxy* aWindow,
SetCaretVisible(presShell, false, nullptr);
}
// If a window is being "hidden" because its BrowsingContext is changing
// remoteness, we don't want to handle docshell destruction by moving focus.
// Instead, the focused browsing context should stay the way it is (so that
// the newly "shown" window in the other process knows to take focus) and
// we should just null out the process-local field.
nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
// Check if we're currently hiding a non-remote nsDocShell due to its
// BrowsingContext navigating to become remote. Normally, when a focused
// subframe is hidden, focus is moved to the frame element, but focus should
// stay with the BrowsingContext when performing a process switch. We don't
// need to consider process switches where the hiding docshell is already
// remote (ie. GetEmbedderElement is nullptr), as shifting remoteness to the
// frame element is handled elsewhere.
if (nsDocShell::Cast(docShellBeingHidden)->WillChangeProcess() &&
docShellBeingHidden->GetBrowsingContext()->GetEmbedderElement()) {
if (mFocusedWindow != window) {
// The window being hidden is an ancestor of the focused window.
#ifdef DEBUG
BrowsingContext* ancestor = window->GetBrowsingContext();
BrowsingContext* bc = mFocusedWindow->GetBrowsingContext();
for (;;) {
if (!bc) {
MOZ_ASSERT(false, "Should have found ancestor");
}
bc = bc->GetParent();
if (ancestor == bc) {
break;
}
}
#endif
// This call adjusts the focused browsing context and window.
// The latter gets nulled out immediately below.
SetFocusedWindowInternal(window);
}
mFocusedWindow = nullptr;
window->SetFocusedElement(nullptr);
return;
}
// if the docshell being hidden is being destroyed, then we want to move
// focus somewhere else. Call ClearFocus on the toplevel window, which
// will have the effect of clearing the focus and moving the focused window
// to the toplevel window. But if the window isn't being destroyed, we are
// likely just loading a new document in it, so we want to maintain the
// focused window so that the new document gets properly focused.
nsCOMPtr<nsIDocShell> docShellBeingHidden = window->GetDocShell();
bool beingDestroyed = !docShellBeingHidden;
if (docShellBeingHidden) {
docShellBeingHidden->IsBeingDestroyed(&beingDestroyed);
@ -1150,6 +1197,17 @@ void nsFocusManager::FireDelayedEvents(Document* aDocument) {
}
}
void nsFocusManager::WasNuked(nsPIDOMWindowOuter* aWindow) {
MOZ_ASSERT(aWindow, "Expected non-null window.");
MOZ_ASSERT(aWindow != mActiveWindow,
"How come we're nuking a window that's still active?");
if (aWindow == mFocusedWindow) {
mFocusedWindow = nullptr;
SetFocusedBrowsingContext(nullptr);
mFocusedElement = nullptr;
}
}
nsresult nsFocusManager::FocusPlugin(Element* aPlugin) {
NS_ENSURE_ARG(aPlugin);
SetFocusInner(aPlugin, 0, true, false, GenerateFocusActionId());
@ -1384,8 +1442,6 @@ void nsFocusManager::SetFocusInner(Element* aNewContent, int32_t aFlags,
nsCOMPtr<nsPIDOMWindowOuter> newWindow;
nsCOMPtr<nsPIDOMWindowOuter> subWindow = GetContentWindow(elementToFocus);
if (subWindow) {
// XXX What if this is an out-of-process iframe?
// https://bugzilla.mozilla.org/show_bug.cgi?id=1613054
elementToFocus = GetFocusedDescendant(subWindow, eIncludeAllDescendants,
getter_AddRefs(newWindow));
@ -4882,7 +4938,8 @@ static bool IsInPointerLockContext(nsPIDOMWindowOuter* aWin) {
: nullptr);
}
void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow) {
void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow,
bool aSyncBrowsingContext) {
if (XRE_IsParentProcess() && !PointerUnlocker::sActiveUnlocker &&
IsInPointerLockContext(mFocusedWindow) &&
!IsInPointerLockContext(aWindow)) {
@ -4900,7 +4957,13 @@ void nsFocusManager::SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow) {
}
mFocusedWindow = aWindow;
SetFocusedBrowsingContext(aWindow ? aWindow->GetBrowsingContext() : nullptr);
BrowsingContext* bc = aWindow ? aWindow->GetBrowsingContext() : nullptr;
if (aSyncBrowsingContext) {
SetFocusedBrowsingContext(bc);
} else if (XRE_IsContentProcess()) {
MOZ_ASSERT(mFocusedBrowsingContextInContent == bc,
"Not syncing BrowsingContext even when different.");
}
}
void nsFocusManager::NotifyOfReFocus(nsIContent& aContent) {

Просмотреть файл

@ -258,6 +258,8 @@ class nsFocusManager final : public nsIFocusManager,
*/
void FireDelayedEvents(Document* aDocument);
void WasNuked(nsPIDOMWindowOuter* aWindow);
/**
* Indicate that a plugin wishes to take the focus. This is similar to a
* normal focus except that the widget focus is not changed. Updating the
@ -752,7 +754,8 @@ class nsFocusManager final : public nsIFocusManager,
int32_t aFlags, bool aGettingFocus,
bool aShouldShowFocusRing);
void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow);
void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow,
bool aSyncBrowsingContext = true);
bool TryDocumentNavigation(nsIContent* aCurrentContent,
bool* aCheckSubDocument,

Просмотреть файл

@ -46,7 +46,7 @@ support-files = test_bug336682.js
[test_bug402089.html]
[test_bug405632.html]
[test_bug409604.html]
skip-if = toolkit == 'android' #TIMED_OUT
skip-if = xorigin || toolkit == 'android' # xorigin: Bug 1682519, android: TIMED_OUT
[test_bug412567.html]
[test_bug418986-3.html]
[test_bug422132.html]

Просмотреть файл

@ -91,7 +91,7 @@ support-files =
file_bug842853-frame.html
skip-if = toolkit == 'android' # Bug 1355821
[test_bug842853-2.html]
skip-if = toolkit == 'android' # Bug 1355821
skip-if = xorigin || toolkit == 'android' # Bug 1682493, 1355821
[test_bug849219.html]
[test_bug851445.html]
skip-if = toolkit == 'android' # Bug 1355821

Просмотреть файл

@ -1,4 +0,0 @@
[activeelement-after-immediately-focusing-different-site-iframe-contentwindow.html]
[Check trailing events]
expected:
if not fission: FAIL

Просмотреть файл

@ -1,3 +0,0 @@
[activeelement-after-immediately-focusing-same-site-iframe-contentwindow.html]
[Check trailing events]
expected: FAIL

Просмотреть файл

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<title>focus() before iframe loaded different site</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
setup({explicit_done:true});
window.onmessage = function(e) {
test(function() {
assert_equals(e.data, "PASS", "Check verdict");
}, "Check result");
w.close();
done();
};
var w = window.open("support/focus-before-iframe-loaded-different-site-outer.sub.html");
</script>

Просмотреть файл

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<title>focus() before iframe loaded same site</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
setup({explicit_done:true});
window.onmessage = function(e) {
test(function() {
assert_equals(e.data, "PASS", "Check verdict");
}, "Check result");
w.close();
done();
};
var w = window.open("support/focus-before-iframe-loaded-same-site-outer.html");
</script>

Просмотреть файл

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<title>focus() from next tick before iframe loaded different site</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
setup({explicit_done:true});
window.onmessage = function(e) {
test(function() {
assert_equals(e.data, "PASS", "Check verdict");
}, "Check result");
w.close();
done();
};
var w = window.open("support/focus-next-tick-before-iframe-loaded-different-site-outer.sub.html");
</script>

Просмотреть файл

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<title>focus() from next tick before iframe loaded same site</title>
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script>
setup({explicit_done:true});
window.onmessage = function(e) {
test(function() {
assert_equals(e.data, "PASS", "Check verdict");
}, "Check result");
w.close();
done();
};
var w = window.open("support/focus-next-tick-before-iframe-loaded-same-site-outer.html");
</script>

Просмотреть файл

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>focus() before iframe loaded different site</title>
</head>
<body>
<script>
window.onload = function() {
parent.postMessage("onload", "*");
}
document.body.onfocus = function() {
parent.postMessage("onfocus", "*");
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>focus() before iframe loaded different site</title>
</head>
<body>
<script>
var pendingTimeout = false;
window.onmessage = function(e) {
if (e.data == "onload") {
if (pendingTimeout) {
return;
}
pendingTimeout = opener.step_timeout(function() {
opener.postMessage("FAIL missing onfocus", "*");
}, 2000);
return;
}
if (pendingTimeout) {
clearTimeout(pendingTimeout);
}
pendingTimeout = true;
if (e.data == "onfocus") {
// Test not upstreamed, because this even is a Firefoxism
// https://github.com/whatwg/html/issues/6209
opener.postMessage("PASS", "*");
return;
}
opener.postMessage("FAIL " + e.data, "*");
}
var iframe = document.createElement("iframe");
iframe.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/focus-before-iframe-loaded-different-site-inner.html";
document.body.appendChild(iframe);
iframe.focus();
if (document.activeElement != iframe) {
pendingTimeout = true;
opener.postMessage("FAIL activeElement", "*");
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>focus() before iframe loaded same site</title>
</head>
<body>
<script>
window.onload = function() {
parent.postMessage("onload", "*");
}
document.body.onfocus = function() {
parent.postMessage("onfocus", "*");
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>focus() before iframe loaded same site</title>
</head>
<body>
<script>
var pendingTimeout = false;
window.onmessage = function(e) {
if (e.data == "onload") {
if (pendingTimeout) {
return;
}
pendingTimeout = opener.step_timeout(function() {
opener.postMessage("FAIL missing onfocus", "*");
}, 2000);
return;
}
if (pendingTimeout) {
clearTimeout(pendingTimeout);
}
pendingTimeout = true;
if (e.data == "onfocus") {
// Test not upstreamed, because this even is a Firefoxism
// https://github.com/whatwg/html/issues/6209
opener.postMessage("PASS", "*");
return;
}
opener.postMessage("FAIL " + e.data, "*");
}
var iframe = document.createElement("iframe");
iframe.src = "focus-before-iframe-loaded-same-site-inner.html";
document.body.appendChild(iframe);
iframe.focus();
if (document.activeElement != iframe) {
pendingTimeout = true;
opener.postMessage("FAIL activeElement", "*");
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>focus() from next tick before iframe loaded different site</title>
</head>
<body>
<script>
window.onload = function() {
parent.postMessage("onload", "*");
}
document.body.onfocus = function() {
parent.postMessage("onfocus", "*");
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<title>focus() from next tick before iframe loaded different site</title>
</head>
<body>
<script>
var pendingTimeout = false;
window.onmessage = function(e) {
if (e.data == "tick") {
iframe.focus();
if (document.activeElement != iframe) {
if (pendingTimeout) {
clearTimeout(pendingTimeout);
}
pendingTimeout = true;
opener.postMessage("FAIL activeElement", "*");
}
return;
}
if (e.data == "onload") {
if (pendingTimeout) {
return;
}
pendingTimeout = opener.step_timeout(function() {
opener.postMessage("FAIL missing onfocus", "*");
}, 2000);
return;
}
if (pendingTimeout) {
clearTimeout(pendingTimeout);
}
pendingTimeout = true;
if (e.data == "onfocus") {
// Test not upstreamed, because this even is a Firefoxism
// https://github.com/whatwg/html/issues/6209
opener.postMessage("PASS", "*");
return;
}
opener.postMessage("FAIL " + e.data, "*");
}
var iframe = document.createElement("iframe");
iframe.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/_mozilla/focus/support/focus-next-tick-before-iframe-loaded-different-site-inner.html";
document.body.appendChild(iframe);
window.postMessage("tick", "*");
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>focus() from next tick before iframe loaded same site</title>
</head>
<body>
<script>
window.onload = function() {
parent.postMessage("onload", "*");
}
document.body.onfocus = function() {
parent.postMessage("onfocus", "*");
}
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<title>focus() from next tick before iframe loaded same site</title>
</head>
<body>
<script>
var pendingTimeout = false;
window.onmessage = function(e) {
if (e.data == "tick") {
iframe.focus();
if (document.activeElement != iframe) {
if (pendingTimeout) {
clearTimeout(pendingTimeout);
}
pendingTimeout = true;
opener.postMessage("FAIL activeElement", "*");
}
return;
}
if (e.data == "onload") {
if (pendingTimeout) {
return;
}
pendingTimeout = opener.step_timeout(function() {
opener.postMessage("FAIL missing onfocus", "*");
}, 2000);
return;
}
if (pendingTimeout) {
clearTimeout(pendingTimeout);
}
pendingTimeout = true;
if (e.data == "onfocus") {
// Test not upstreamed, because this even is a Firefoxism
// https://github.com/whatwg/html/issues/6209
opener.postMessage("PASS", "*");
return;
}
opener.postMessage("FAIL " + e.data, "*");
}
var iframe = document.createElement("iframe");
iframe.src = "focus-next-tick-before-iframe-loaded-same-site-inner.html";
document.body.appendChild(iframe);
window.postMessage("tick", "*");
</script>
</body>
</html>

Просмотреть файл

@ -6,19 +6,8 @@
<script>
setup({explicit_done:true});
window.onmessage = function(e) {
var actual = e.data;
test(function() {
// Make the difference between Firefox and Chrome visible separately
// from the comparison of the entire log string failing to match.
var endedWith = false;
if (actual.endsWith(",willspineventloop,")) {
endedWith = true;
actual += "innerbodyfocus,";
}
assert_true(endedWith, "Should not have gotten innerbodyfocus after willspineventloop");
}, "Check trailing events");
test(function() {
assert_equals(actual, "outerparser,activeElement:BODY,willfocusiframe,didfocusiframe,activeElement:IFRAME,willbluriframe,didbluriframe,activeElement:IFRAME,willspineventloop,innerbodyfocus,", 'Check log');
assert_equals(e.data, "outerparser,activeElement:BODY,willfocusiframe,didfocusiframe,activeElement:IFRAME,willbluriframe,didbluriframe,activeElement:IFRAME,willspineventloop,", 'Check log');
}, "Check result");
w.close();
done();

Просмотреть файл

@ -6,19 +6,8 @@
<script>
setup({explicit_done:true});
window.onmessage = function(e) {
var actual = e.data;
test(function() {
// Make the difference between Firefox and Chrome visible separately
// from the comparison of the entire log string failing to match.
var endedWith = false;
if (actual.endsWith(",willspineventloop,")) {
endedWith = true;
actual += "innerbodyfocus,";
}
assert_true(endedWith, "Should not have gotten innerbodyfocus after willspineventloop");
}, "Check trailing events");
test(function() {
assert_equals(actual, "outerparse,activeElement:BODY,willfocusiframe,didfocusiframe,activeElement:IFRAME,willbluriframe,didbluriframe,activeElement:IFRAME,willspineventloop,innerbodyfocus,", 'Check log');
assert_equals(e.data, "outerparse,activeElement:BODY,willfocusiframe,didfocusiframe,activeElement:IFRAME,willbluriframe,didbluriframe,activeElement:IFRAME,willspineventloop,", 'Check log');
}, "Check result");
w.close();
done();

Просмотреть файл

@ -8,7 +8,9 @@
<h1>Inner</h1>
<script>
document.body.onfocus = function() {
parent.postMessage("innerbodyfocus,", "*");
// Commented out pending resolution of
// https://github.com/whatwg/html/issues/6209
// parent.postMessage("innerbodyfocus,", "*");
}
document.body.onblur = function() {
parent.postMessage("innerbodyblur,", "*");

Просмотреть файл

@ -8,7 +8,9 @@
<h1>Inner</h1>
<script>
document.body.onfocus = function() {
parent.postMessage("innerbodyfocus,", "*");
// Commented out pending resolution of
// https://github.com/whatwg/html/issues/6209
// parent.postMessage("innerbodyfocus,", "*");
}
document.body.onblur = function() {
parent.postMessage("innerbodyblur,", "*");

Просмотреть файл

@ -157,6 +157,7 @@ scheme = https
[test_input_events.html]
skip-if = xorigin
[test_input_events_for_identical_values.html]
skip-if = xorigin # Bug 1682517
[test_LoginManagerContent_passwordEditedOrGenerated.html]
scheme = https
skip-if = toolkit == 'android' # password generation