Bug 1659383 - Check if focus() is allowed even when Window_Binding::focus() delegates to BrowsingContext::Focus(). r=nika

Differential Revision: https://phabricator.services.mozilla.com/D100005
This commit is contained in:
Henri Sivonen 2021-01-18 12:48:42 +00:00
parent 652e43b038
commit 0e936b95f1
21 changed files with 352 additions and 139 deletions

View File

@ -1957,15 +1957,148 @@ void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) {
}
}
/*
* Examine the current document state to see if we're in a way that is
* typically abused by web designers. The window.open code uses this
* routine to determine whether to allow the new window.
* Returns a value from the PopupControlState enum.
*/
PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel(
PopupBlocker::PopupControlState aControl) {
if (!IsContent()) {
return PopupBlocker::openAllowed;
}
PopupBlocker::PopupControlState abuse = aControl;
switch (abuse) {
case PopupBlocker::openControlled:
case PopupBlocker::openBlocked:
case PopupBlocker::openOverridden:
if (IsPopupAllowed()) {
abuse = PopupBlocker::PopupControlState(abuse - 1);
}
break;
case PopupBlocker::openAbused:
if (IsPopupAllowed()) {
// Skip PopupBlocker::openBlocked
abuse = PopupBlocker::openControlled;
}
break;
case PopupBlocker::openAllowed:
break;
default:
NS_WARNING("Strange PopupControlState!");
}
// limit the number of simultaneously open popups
if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked ||
abuse == PopupBlocker::openControlled) {
int32_t popupMax = StaticPrefs::dom_popup_maximum();
if (popupMax >= 0 &&
PopupBlocker::GetOpenPopupSpamCount() >= (uint32_t)popupMax) {
abuse = PopupBlocker::openOverridden;
}
}
// If we're currently in-process, attempt to consume transient user gesture
// activations.
if (RefPtr<Document> doc = GetExtantDocument()) {
// HACK: Some pages using bogus library + UA sniffing call window.open()
// from a blank iframe, only on Firefox, see bug 1685056.
//
// This is a hack-around to preserve behavior in that particular and
// specific case, by consuming activation on the parent document, so we
// don't care about the InProcessParent bits not being fission-safe or what
// not.
auto ConsumeTransientUserActivationForMultiplePopupBlocking =
[&]() -> bool {
if (doc->ConsumeTransientUserGestureActivation()) {
return true;
}
if (!doc->IsInitialDocument()) {
return false;
}
Document* parentDoc = doc->GetInProcessParentDocument();
if (!parentDoc ||
!parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) {
return false;
}
return parentDoc->ConsumeTransientUserGestureActivation();
};
// If this popup is allowed, let's block any other for this event, forcing
// PopupBlocker::openBlocked state.
if ((abuse == PopupBlocker::openAllowed ||
abuse == PopupBlocker::openControlled) &&
StaticPrefs::dom_block_multiple_popups() && !IsPopupAllowed() &&
!ConsumeTransientUserActivationForMultiplePopupBlocking()) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
doc, nsContentUtils::eDOM_PROPERTIES,
"MultiplePopupsBlockedNoUserActivation");
abuse = PopupBlocker::openBlocked;
}
}
return abuse;
}
std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
return {false, false};
}
nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr;
RefPtr<BrowsingContext> openerBC = GetOpener();
MOZ_DIAGNOSTIC_ASSERT(!openerBC || openerBC->Group() == Group());
// Enforce dom.disable_window_flip (for non-chrome), but still allow the
// window which opened us to raise us at times when popups are allowed
// (bugs 355482 and 369306).
bool canFocus = aCallerType == CallerType::System ||
!Preferences::GetBool("dom.disable_window_flip", true);
if (!canFocus && openerBC == callerBC) {
canFocus = (RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
PopupBlocker::openBlocked);
}
bool isActive = false;
if (XRE_IsParentProcess()) {
RefPtr<CanonicalBrowsingContext> chromeTop =
Canonical()->TopCrossChromeBoundary();
nsCOMPtr<nsPIDOMWindowOuter> activeWindow = fm->GetActiveWindow();
isActive = (activeWindow == chromeTop->GetDOMWindow());
} else {
isActive = (fm->GetActiveBrowsingContext() == Top());
}
return {canFocus, isActive};
}
void BrowsingContext::Focus(CallerType aCallerType, ErrorResult& aError) {
// These checks need to happen before the RequestFrameFocus call, which
// is why they are done in an untrusted process. If we wanted to enforce
// these in the parent, we'd need to do the checks there _also_.
// These should be kept in sync with nsGlobalWindowOuter::FocusOuter.
auto [canFocus, isActive] = CanFocusCheck(aCallerType);
if (!(canFocus || isActive)) {
return;
}
// Permission check passed
if (mEmbedderElement) {
// Make the activeElement in this process update synchronously.
nsContentUtils::RequestFrameFocus(*mEmbedderElement, true, aCallerType);
}
uint64_t actionId = nsFocusManager::GenerateFocusActionId();
if (ContentChild* cc = ContentChild::GetSingleton()) {
cc->SendWindowFocus(this, aCallerType);
cc->SendWindowFocus(this, aCallerType, actionId);
} else if (ContentParent* cp = Canonical()->GetContentParent()) {
Unused << cp->SendWindowFocus(this, aCallerType);
Unused << cp->SendWindowFocus(this, aCallerType, actionId);
}
}

View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_BrowsingContext_h
#define mozilla_dom_BrowsingContext_h
#include <tuple>
#include "GVAutoplayRequestUtils.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/HalScreenConfiguration.h"
@ -18,6 +19,7 @@
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/LocationBase.h"
#include "mozilla/dom/MaybeDiscarded.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/BrowsingContextBinding.h"
#include "mozilla/dom/ScreenOrientationBinding.h"
@ -776,6 +778,12 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
mozilla::dom::DisplayMode DisplayMode() { return Top()->GetDisplayMode(); }
// Returns canFocus, isActive
std::tuple<bool, bool> CanFocusCheck(CallerType aCallerType);
PopupBlocker::PopupControlState RevisePopupAbuseLevel(
PopupBlocker::PopupControlState aControl);
protected:
virtual ~BrowsingContext();
BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,

View File

@ -3723,7 +3723,9 @@ void nsGlobalWindowInner::Prompt(const nsAString& aMessage,
}
void nsGlobalWindowInner::Focus(CallerType aCallerType, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(FocusOuter, (aCallerType), aError, );
FORWARD_TO_OUTER_OR_THROW(
FocusOuter, (aCallerType, nsFocusManager::GenerateFocusActionId()),
aError, );
}
nsresult nsGlobalWindowInner::Focus(CallerType aCallerType) {

View File

@ -5053,47 +5053,14 @@ void nsGlobalWindowOuter::PromptOuter(const nsAString& aMessage,
}
}
void nsGlobalWindowOuter::FocusOuter(CallerType aCallerType) {
void nsGlobalWindowOuter::FocusOuter(CallerType aCallerType,
uint64_t aActionId) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
return;
}
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell);
if (!baseWin) {
return;
}
nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
nsPIDOMWindowOuter* callerOuter = caller ? caller->GetOuterWindow() : nullptr;
BrowsingContext* callerBC =
callerOuter ? callerOuter->GetBrowsingContext() : nullptr;
RefPtr<BrowsingContext> openerBC = GetOpenerBrowsingContext();
// Enforce dom.disable_window_flip (for non-chrome), but still allow the
// window which opened us to raise us at times when popups are allowed
// (bugs 355482 and 369306).
bool canFocus = CanSetProperty("dom.disable_window_flip") ||
(openerBC == callerBC &&
RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
PopupBlocker::openBlocked);
bool isActive = false;
if (XRE_IsParentProcess()) {
nsCOMPtr<nsPIDOMWindowOuter> activeWindow = fm->GetActiveWindow();
nsCOMPtr<nsIDocShellTreeItem> rootItem;
mDocShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
nsCOMPtr<nsPIDOMWindowOuter> rootWin =
rootItem ? rootItem->GetWindow() : nullptr;
isActive = (rootWin == activeWindow);
} else {
BrowsingContext* activeBrowsingContext = fm->GetActiveBrowsingContext();
BrowsingContext* bc = GetBrowsingContext();
if (bc) {
isActive = (activeBrowsingContext == bc->Top());
}
}
auto [canFocus, isActive] = GetBrowsingContext()->CanFocusCheck(aCallerType);
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
if (treeOwnerAsWin && (canFocus || isActive)) {
@ -5124,11 +5091,10 @@ void nsGlobalWindowOuter::FocusOuter(CallerType aCallerType) {
parent = bc->Canonical()->GetParentCrossChromeBoundary();
}
}
uint64_t actionId = nsFocusManager::GenerateFocusActionId();
if (parent) {
if (!parent->IsInProcess()) {
if (isActive) {
fm->WindowRaised(this, actionId);
fm->WindowRaised(this, aActionId);
// XXX we still need to notify framer about the focus moves to OOP
// iframe to generate corresponding blur event for bug 1677474.
} else {
@ -5153,7 +5119,7 @@ void nsGlobalWindowOuter::FocusOuter(CallerType aCallerType) {
// if there is no parent, this must be a toplevel window, so raise the
// window if canFocus is true. If this is a child process, the raise
// window request will get forwarded to the parent by the puppet widget.
fm->RaiseWindow(this, aCallerType, actionId);
fm->RaiseWindow(this, aCallerType, aActionId);
}
}
@ -5700,91 +5666,6 @@ bool nsGlobalWindowOuter::CanSetProperty(const char* aPrefName) {
return !Preferences::GetBool(aPrefName, true);
}
bool nsGlobalWindowOuter::IsPopupAllowed() {
return mBrowsingContext->IsPopupAllowed();
}
/*
* Examine the current document state to see if we're in a way that is
* typically abused by web designers. The window.open code uses this
* routine to determine whether to allow the new window.
* Returns a value from the PopupControlState enum.
*/
PopupBlocker::PopupControlState nsGlobalWindowOuter::RevisePopupAbuseLevel(
PopupBlocker::PopupControlState aControl) {
NS_ASSERTION(mDocShell, "Must have docshell");
if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) {
return PopupBlocker::openAllowed;
}
PopupBlocker::PopupControlState abuse = aControl;
switch (abuse) {
case PopupBlocker::openControlled:
case PopupBlocker::openBlocked:
case PopupBlocker::openOverridden:
if (IsPopupAllowed()) {
abuse = PopupBlocker::PopupControlState(abuse - 1);
}
break;
case PopupBlocker::openAbused:
if (IsPopupAllowed()) {
// Skip PopupBlocker::openBlocked
abuse = PopupBlocker::openControlled;
}
break;
case PopupBlocker::openAllowed:
break;
default:
NS_WARNING("Strange PopupControlState!");
}
// limit the number of simultaneously open popups
if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked ||
abuse == PopupBlocker::openControlled) {
int32_t popupMax = StaticPrefs::dom_popup_maximum();
if (popupMax >= 0 &&
PopupBlocker::GetOpenPopupSpamCount() >= (uint32_t)popupMax) {
abuse = PopupBlocker::openOverridden;
}
}
// HACK: Some pages using bogus library + UA sniffing call window.open() from
// a blank iframe, only on Firefox, see bug 1685056.
//
// This is a hack-around to preserve behavior in that particular and specific
// case, by consuming activation on the parent document, so we don't care
// about the InProcessParent bits not being fission-safe or what not.
auto ConsumeTransientUserActivationForMultiplePopupBlocking = [&]() -> bool {
if (mDoc->ConsumeTransientUserGestureActivation()) {
return true;
}
if (!mDoc->IsInitialDocument()) {
return false;
}
Document* parentDoc = mDoc->GetInProcessParentDocument();
if (!parentDoc ||
!parentDoc->NodePrincipal()->Equals(mDoc->NodePrincipal())) {
return false;
}
return parentDoc->ConsumeTransientUserGestureActivation();
};
// If this popup is allowed, let's block any other for this event, forcing
// PopupBlocker::openBlocked state.
if ((abuse == PopupBlocker::openAllowed ||
abuse == PopupBlocker::openControlled) &&
StaticPrefs::dom_block_multiple_popups() && !IsPopupAllowed() &&
!ConsumeTransientUserActivationForMultiplePopupBlocking()) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, mDoc,
nsContentUtils::eDOM_PROPERTIES,
"MultiplePopupsBlockedNoUserActivation");
abuse = PopupBlocker::openBlocked;
}
return abuse;
}
/* If a window open is blocked, fire the appropriate DOM events. */
void nsGlobalWindowOuter::FireAbuseEvents(
const nsAString& aPopupURL, const nsAString& aPopupWindowName,
@ -7114,7 +6995,7 @@ nsresult nsGlobalWindowOuter::OpenInternal(
PopupBlocker::PopupControlState abuseLevel =
PopupBlocker::GetPopupControlState();
if (checkForPopup) {
abuseLevel = RevisePopupAbuseLevel(abuseLevel);
abuseLevel = mBrowsingContext->RevisePopupAbuseLevel(abuseLevel);
if (abuseLevel >= PopupBlocker::openBlocked) {
if (!aCalledNoScript) {
// If script in some other window is doing a window.open on us and

View File

@ -532,7 +532,7 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
bool GetClosedOuter();
bool Closed() override;
void StopOuter(mozilla::ErrorResult& aError);
void FocusOuter(mozilla::dom::CallerType aCallerType);
void FocusOuter(mozilla::dom::CallerType aCallerType, uint64_t aActionId);
nsresult Focus(mozilla::dom::CallerType aCallerType) override;
void BlurOuter();
mozilla::dom::WindowProxyHolder GetFramesOuter();
@ -1061,9 +1061,6 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
virtual mozilla::AbstractThread* AbstractMainThreadFor(
mozilla::TaskCategory aCategory) override;
private:
bool IsPopupAllowed();
protected:
bool mFullscreen : 1;
bool mFullscreenMode : 1;

View File

@ -3721,7 +3721,8 @@ mozilla::ipc::IPCResult ContentChild::RecvWindowClose(
}
mozilla::ipc::IPCResult ContentChild::RecvWindowFocus(
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType) {
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType,
uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
("ChildIPC: Trying to send a message to dead or detached context"));
@ -3735,7 +3736,7 @@ mozilla::ipc::IPCResult ContentChild::RecvWindowFocus(
("ChildIPC: Trying to send a message to a context without a window"));
return IPC_OK();
}
nsGlobalWindowOuter::Cast(window)->FocusOuter(aCallerType);
nsGlobalWindowOuter::Cast(window)->FocusOuter(aCallerType, aActionId);
return IPC_OK();
}

View File

@ -714,7 +714,8 @@ class ContentChild final : public PContentChild,
mozilla::ipc::IPCResult RecvWindowClose(
const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller);
mozilla::ipc::IPCResult RecvWindowFocus(
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType);
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType,
uint64_t aActionId);
mozilla::ipc::IPCResult RecvWindowBlur(
const MaybeDiscarded<BrowsingContext>& aContext);
mozilla::ipc::IPCResult RecvRaiseWindow(

View File

@ -6736,7 +6736,8 @@ mozilla::ipc::IPCResult ContentParent::RecvWindowClose(
}
mozilla::ipc::IPCResult ContentParent::RecvWindowFocus(
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType) {
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType,
uint64_t aActionId) {
if (aContext.IsNullOrDiscarded()) {
MOZ_LOG(
BrowsingContext::GetLog(), LogLevel::Debug,
@ -6748,7 +6749,7 @@ mozilla::ipc::IPCResult ContentParent::RecvWindowFocus(
ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
ContentParent* cp =
cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId()));
Unused << cp->SendWindowFocus(context, aCallerType);
Unused << cp->SendWindowFocus(context, aCallerType, aActionId);
return IPC_OK();
}

View File

@ -675,7 +675,8 @@ class ContentParent final
mozilla::ipc::IPCResult RecvWindowClose(
const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller);
mozilla::ipc::IPCResult RecvWindowFocus(
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType);
const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType,
uint64_t aActionId);
mozilla::ipc::IPCResult RecvWindowBlur(
const MaybeDiscarded<BrowsingContext>& aContext);
mozilla::ipc::IPCResult RecvRaiseWindow(

View File

@ -1801,7 +1801,7 @@ both:
async WindowClose(MaybeDiscardedBrowsingContext aContext,
bool aTrustedCaller);
async WindowFocus(MaybeDiscardedBrowsingContext aContext,
CallerType aCallerType);
CallerType aCallerType, uint64_t aActionId);
async WindowBlur(MaybeDiscardedBrowsingContext aContext);
async RaiseWindow(MaybeDiscardedBrowsingContext aContext, CallerType aCallerType, uint64_t aActionId);
async ClearFocus(MaybeDiscardedBrowsingContext aContext);

View File

@ -0,0 +1,3 @@
[iframe-focuses-parent-different-site.html]
disabled:
if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1687280

View File

@ -0,0 +1,3 @@
[iframe-focuses-parent-same-site.html]
disabled:
if (os == "android"): https://bugzilla.mozilla.org/show_bug.cgi?id=1687280

View File

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

View File

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

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>iframe focuses parent different site inner</title>
</head>
<body>
<script>
window.onmessage = function() {
parent.focus();
setTimeout(function() {
parent.postMessage("finished", "*");
}, 500);
}
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>iframe focuses parent different site other</title>
</head>
<body>
<script>
window.onload = function() {
opener.postMessage("ready", "*");
}
</script>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>iframe focuses parent different site outer</title>
</head>
<body>
<iframe src="http://{{hosts[alt][www]}}:{{ports[http][0]}}/focus/support/iframe-focuses-parent-different-site-inner.html"></iframe>
<script>
var w = null;
var focusDisallowed = false;
var failed = false;
window.onmessage = function(e) {
if (failed) {
return;
}
if (e.data == "ready") {
focusDisallowed = true;
document.getElementsByTagName("iframe")[0].contentWindow.postMessage("focus", "*");
return;
}
focusDisallowed = false;
if (w) {
w.close();
w = null;
}
opener.postMessage(failed ? "FAIL" : "PASS", "*");
}
window.onload = function() {
w = window.open("iframe-focuses-parent-different-site-other.html");
}
document.body.onfocus = function() {
if (focusDisallowed) {
failed = true;
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>iframe focuses parent same site inner</title>
</head>
<body>
<script>
window.onmessage = function() {
parent.focus();
setTimeout(function() {
parent.postMessage("finished", "*");
}, 500);
}
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>iframe focuses parent same site other</title>
</head>
<body>
<script>
window.onload = function() {
opener.postMessage("ready", "*");
}
</script>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>iframe focuses parent same site outer</title>
</head>
<body>
<iframe src="iframe-focuses-parent-same-site-inner.html"></iframe>
<script>
var w = null;
var focusDisallowed = false;
var failed = false;
window.onmessage = function(e) {
if (failed) {
return;
}
if (e.data == "ready") {
focusDisallowed = true;
document.getElementsByTagName("iframe")[0].contentWindow.postMessage("focus", "*");
return;
}
focusDisallowed = false;
if (w) {
w.close();
w = null;
}
opener.postMessage(failed ? "FAIL" : "PASS", "*");
}
window.onload = function() {
w = window.open("iframe-focuses-parent-same-site-other.html");
}
document.body.onfocus = function() {
if (focusDisallowed) {
failed = true;
}
}
</script>
</body>
</html>

View File

@ -218,6 +218,8 @@ SET TIMEOUT: focus/support/iframe-focus-with-different-site-intermediate-frame-o
SET TIMEOUT: focus/support/iframe-focus-with-different-site-intermediate-frame-middle.sub.html
SET TIMEOUT: focus/support/iframe-contentwindow-focus-with-different-site-intermediate-frame-outer.sub.html
SET TIMEOUT: focus/support/iframe-contentwindow-focus-with-different-site-intermediate-frame-middle.sub.html
SET TIMEOUT: focus/support/iframe-focuses-parent-different-site-inner.html
SET TIMEOUT: focus/support/iframe-focuses-parent-same-site-inner.html
# generate_tests implementation and sample usage
GENERATE_TESTS: resources/test/tests/functional/generate-callback.html