Bug 1776339, add a browsing context field that syncs the isUnderHiddenEmbedderElement state, r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D168440
This commit is contained in:
Neil Deakin 2023-05-31 19:46:49 +00:00
parent 3d63da8c0c
commit 18f5e1220a
18 changed files with 123 additions and 125 deletions

View File

@ -73,6 +73,7 @@
#include "nsDocShellLoadState.h"
#include "nsFocusManager.h"
#include "nsGlobalWindowOuter.h"
#include "PresShell.h"
#include "nsIObserverService.h"
#include "nsISHistory.h"
#include "nsContentUtils.h"
@ -3487,6 +3488,52 @@ bool BrowsingContext::CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue,
return IsTop();
}
bool BrowsingContext::CanSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
const bool& aIsUnderHiddenEmbedderElement,
ContentParent* aSource) {
return true;
}
void BrowsingContext::DidSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
bool aOldValue) {
nsIDocShell* shell = GetDocShell();
if (!shell) {
return;
}
const bool newValue = IsUnderHiddenEmbedderElement();
if (NS_WARN_IF(aOldValue == newValue)) {
return;
}
if (PresShell* presShell = shell->GetPresShell()) {
presShell->SetIsUnderHiddenEmbedderElement(newValue);
}
// Propagate to children.
for (BrowsingContext* child : Children()) {
Element* embedderElement = child->GetEmbedderElement();
if (!embedderElement) {
// TODO: We shouldn't need to null check here since `child` and the
// element returned by `child->GetEmbedderElement()` are in our
// process (the actual browsing context represented by `child` may not
// be, but that doesn't matter). However, there are currently a very
// small number of crashes due to `embedderElement` being null, somehow
// - see bug 1551241. For now we wallpaper the crash.
continue;
}
bool embedderFrameIsHidden = true;
if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
}
bool hidden = IsUnderHiddenEmbedderElement() || embedderFrameIsHidden;
if (child->IsUnderHiddenEmbedderElement() != hidden) {
Unused << child->SetIsUnderHiddenEmbedderElement(hidden);
}
}
}
bool BrowsingContext::IsPopupAllowed() {
for (auto* context = GetCurrentWindowContext(); context;
context = context->GetParentWindowContext()) {

View File

@ -264,7 +264,9 @@ struct EmbedderColorSchemes {
/* If true, this document is embedded within a content document, either \
* loaded in the parent (e.g. about:addons or the devtools toolbox), or in \
* a content process. */ \
FIELD(EmbeddedInContentDocument, bool)
FIELD(EmbeddedInContentDocument, bool) \
/* If true, this browsing context is within a hidden embedded document. */ \
FIELD(IsUnderHiddenEmbedderElement, bool)
// BrowsingContext, in this context, is the cross process replicated
// environment in which information about documents is stored. In
@ -942,6 +944,10 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
bool IsAppTab() { return GetIsAppTab(); }
bool HasSiblings() { return GetHasSiblings(); }
bool IsUnderHiddenEmbedderElement() const {
return GetIsUnderHiddenEmbedderElement();
}
protected:
virtual ~BrowsingContext();
BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,
@ -1221,6 +1227,10 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
bool CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue,
ContentParent* aSource);
bool CanSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
const bool& aIsUnderHiddenEmbedderElement,
ContentParent* aSource);
bool CanSet(FieldIndex<IDX_EmbeddedInContentDocument>, bool,
ContentParent* aSource) {
return CheckOnlyEmbedderCanSet(aSource);
@ -1248,6 +1258,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
void DidSet(FieldIndex<IDX_SyntheticDocumentContainer>);
void DidSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>, bool aOldValue);
// Allow if the process attemping to set field is the same as the owning
// process. Deprecated. New code that might use this should generally be moved
// to WindowContext or be settable only by the parent process.

View File

@ -329,6 +329,7 @@ void CanonicalBrowsingContext::ReplacedBy(
txn.SetSuspendMediaWhenInactive(GetSuspendMediaWhenInactive());
txn.SetDisplayMode(GetDisplayMode());
txn.SetForceDesktopViewport(GetForceDesktopViewport());
txn.SetIsUnderHiddenEmbedderElement(GetIsUnderHiddenEmbedderElement());
// Propagate the default load flags so that the TRR mode flags are forwarded
// to the new browsing context. See bug 1828643.

View File

@ -2464,16 +2464,6 @@ nsresult nsFrameLoader::UpdatePositionAndSize(nsSubDocumentFrame* aIFrame) {
return NS_OK;
}
void nsFrameLoader::SendIsUnderHiddenEmbedderElement(
bool aIsUnderHiddenEmbedderElement) {
MOZ_ASSERT(IsRemoteFrame());
if (auto* browserBridgeChild = GetBrowserBridgeChild()) {
browserBridgeChild->SetIsUnderHiddenEmbedderElement(
aIsUnderHiddenEmbedderElement);
}
}
void nsFrameLoader::PropagateIsUnderHiddenEmbedderElement(
bool aIsUnderHiddenEmbedderElement) {
bool isUnderHiddenEmbedderElement = true;
@ -2484,12 +2474,12 @@ void nsFrameLoader::PropagateIsUnderHiddenEmbedderElement(
}
isUnderHiddenEmbedderElement |= aIsUnderHiddenEmbedderElement;
if (nsDocShell* docShell = GetExistingDocShell()) {
if (PresShell* presShell = docShell->GetPresShell()) {
presShell->SetIsUnderHiddenEmbedderElement(isUnderHiddenEmbedderElement);
}
} else {
SendIsUnderHiddenEmbedderElement(isUnderHiddenEmbedderElement);
BrowsingContext* browsingContext = GetExtantBrowsingContext();
if (browsingContext && browsingContext->IsUnderHiddenEmbedderElement() !=
isUnderHiddenEmbedderElement) {
Unused << browsingContext->SetIsUnderHiddenEmbedderElement(
isUnderHiddenEmbedderElement);
}
}

View File

@ -143,7 +143,6 @@ class nsFrameLoader final : public nsStubMutationObserver,
return mChildMessageManager;
}
nsresult UpdatePositionAndSize(nsSubDocumentFrame* aIFrame);
void SendIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement);
void PropagateIsUnderHiddenEmbedderElement(
bool aIsUnderHiddenEmbedderElement);

View File

@ -413,6 +413,12 @@ interface CanonicalBrowsingContext : BrowsingContext {
unsigned long aPresShellId);
readonly attribute nsISHEntry? mostRecentLoadingSessionHistoryEntry;
/**
* Indicates if the embedder element or an ancestor has hidden
* visibility, or no frame.
*/
readonly attribute boolean isUnderHiddenEmbedderElement;
};
[Exposed=Window, ChromeOnly]

View File

@ -78,11 +78,6 @@ void BrowserBridgeChild::Deactivate(bool aWindowLowering, uint64_t aActionId) {
Unused << SendDeactivate(aWindowLowering, aActionId);
}
void BrowserBridgeChild::SetIsUnderHiddenEmbedderElement(
bool aIsUnderHiddenEmbedderElement) {
Unused << SendSetIsUnderHiddenEmbedderElement(aIsUnderHiddenEmbedderElement);
}
/*static*/
BrowserBridgeChild* BrowserBridgeChild::GetFrom(nsFrameLoader* aFrameLoader) {
if (!aFrameLoader) {

View File

@ -47,8 +47,6 @@ class BrowserBridgeChild : public PBrowserBridgeChild {
void Deactivate(bool aWindowLowering, uint64_t aActionId);
void SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement);
already_AddRefed<BrowserBridgeHost> FinishInit(nsFrameLoader* aFrameLoader);
#if defined(ACCESSIBILITY)

View File

@ -250,13 +250,6 @@ IPCResult BrowserBridgeParent::RecvDeactivate(const bool& aWindowLowering,
return IPC_OK();
}
IPCResult BrowserBridgeParent::RecvSetIsUnderHiddenEmbedderElement(
const bool& aIsUnderHiddenEmbedderElement) {
Unused << mBrowserParent->SendSetIsUnderHiddenEmbedderElement(
aIsUnderHiddenEmbedderElement);
return IPC_OK();
}
mozilla::ipc::IPCResult BrowserBridgeParent::RecvUpdateRemoteStyle(
const StyleImageRendering& aImageRendering) {
Unused << mBrowserParent->SendUpdateRemoteStyle(aImageRendering);

View File

@ -98,9 +98,6 @@ class BrowserBridgeParent : public PBrowserBridgeParent {
mozilla::ipc::IPCResult RecvDeactivate(const bool& aWindowLowering,
uint64_t aActionId);
mozilla::ipc::IPCResult RecvSetIsUnderHiddenEmbedderElement(
const bool& aIsUnderHiddenEmbedderElement);
mozilla::ipc::IPCResult RecvUpdateRemoteStyle(
const StyleImageRendering& aImageRendering);

View File

@ -1221,14 +1221,6 @@ mozilla::ipc::IPCResult BrowserChild::RecvChildToParentMatrix(
return IPC_OK();
}
mozilla::ipc::IPCResult BrowserChild::RecvSetIsUnderHiddenEmbedderElement(
const bool& aIsUnderHiddenEmbedderElement) {
if (RefPtr<PresShell> presShell = GetTopLevelPresShell()) {
presShell->SetIsUnderHiddenEmbedderElement(aIsUnderHiddenEmbedderElement);
}
return IPC_OK();
}
mozilla::ipc::IPCResult BrowserChild::RecvUpdateRemoteStyle(
const StyleImageRendering& aImageRendering) {
BrowsingContext* context = GetBrowsingContext();

View File

@ -397,9 +397,6 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
mozilla::ipc::IPCResult RecvNormalPrioritySelectionEvent(
const mozilla::WidgetSelectionEvent& aEvent);
mozilla::ipc::IPCResult RecvSetIsUnderHiddenEmbedderElement(
const bool& aIsUnderHiddenEmbedderElement);
mozilla::ipc::IPCResult RecvInsertText(const nsAString& aStringToInsert);
mozilla::ipc::IPCResult RecvUpdateRemoteStyle(

View File

@ -696,8 +696,6 @@ child:
async ChildToParentMatrix(Matrix4x4? aMatrix,
ScreenRect aRemoteDocumentRect);
async SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement);
async UpdateRemoteStyle(StyleImageRendering aImageRendering);
async DynamicToolbarMaxHeightChanged(ScreenIntCoord height);

View File

@ -116,8 +116,6 @@ parent:
async Deactivate(bool aWindowLowering, uint64_t aActionId);
async SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement);
async UpdateRemoteStyle(StyleImageRendering aImageRendering);
async WillChangeProcess();

View File

@ -1002,22 +1002,8 @@ void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
}
if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
BrowsingContext* bc = docShell->GetBrowsingContext();
bool embedderFrameIsHidden = true;
if (Element* embedderElement = bc->GetEmbedderElement()) {
if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
}
}
if (BrowsingContext* parent = bc->GetParent()) {
if (nsCOMPtr<nsIDocShell> parentDocShell = parent->GetDocShell()) {
if (PresShell* parentPresShell = parentDocShell->GetPresShell()) {
mUnderHiddenEmbedderElement =
parentPresShell->IsUnderHiddenEmbedderElement() ||
embedderFrameIsHidden;
}
}
if (BrowsingContext* bc = docShell->GetBrowsingContext()) {
mUnderHiddenEmbedderElement = bc->IsUnderHiddenEmbedderElement();
}
}
}
@ -11610,52 +11596,6 @@ void PresShell::NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet,
mDocument->ApplicableStylesChanged();
}
void PresShell::SetIsUnderHiddenEmbedderElement(
bool aUnderHiddenEmbedderElement) {
if (mUnderHiddenEmbedderElement == aUnderHiddenEmbedderElement) {
return;
}
mUnderHiddenEmbedderElement = aUnderHiddenEmbedderElement;
if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
BrowsingContext* bc = docShell->GetBrowsingContext();
// Propagate to children.
for (BrowsingContext* child : bc->Children()) {
Element* embedderElement = child->GetEmbedderElement();
if (!embedderElement) {
// TODO: We shouldn't need to null check here since `child` and the
// element returned by `child->GetEmbedderElement()` are in our
// process (the actual browsing context represented by `child` may not
// be, but that doesn't matter). However, there are currently a very
// small number of crashes due to `embedderElement` being null, somehow
// - see bug 1551241. For now we wallpaper the crash.
continue;
}
bool embedderFrameIsHidden = true;
if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
}
if (nsIDocShell* childDocShell = child->GetDocShell()) {
PresShell* presShell = childDocShell->GetPresShell();
if (!presShell) {
continue;
}
presShell->SetIsUnderHiddenEmbedderElement(
aUnderHiddenEmbedderElement || embedderFrameIsHidden);
} else {
BrowserBridgeChild* bridgeChild =
BrowserBridgeChild::GetFrom(embedderElement);
bridgeChild->SetIsUnderHiddenEmbedderElement(
aUnderHiddenEmbedderElement || embedderFrameIsHidden);
}
}
}
}
nsIContent* PresShell::EventHandler::GetOverrideClickTarget(
WidgetGUIEvent* aGUIEvent, nsIFrame* aFrame) {
if (aGUIEvent->mMessage != eMouseUp) {

View File

@ -1034,7 +1034,10 @@ class PresShell final : public nsStubDocumentObserver,
bool IsUnderHiddenEmbedderElement() const {
return mUnderHiddenEmbedderElement;
}
void SetIsUnderHiddenEmbedderElement(bool aUnderHiddenEmbedderElement);
void SetIsUnderHiddenEmbedderElement(bool aUnderHiddenEmbedderElement) {
mUnderHiddenEmbedderElement = aUnderHiddenEmbedderElement;
}
MOZ_CAN_RUN_SCRIPT
void DispatchSynthMouseMove(WidgetGUIEvent* aEvent);

View File

@ -8,6 +8,16 @@
<![CDATA[
const baseURL = "chrome://mochitests/content/chrome/layout/base/tests/chrome/";
function checkHiddenEmbeddederState(window1, window2, expected1, expected2)
{
ok(!window1.browsingContext.isUnderHiddenEmbedderElement, "window1 visible state");
ok(!window2.browsingContext.isUnderHiddenEmbedderElement, "window2 visible state");
is(window1.document.querySelector("browser").contentWindow.browsingContext.isUnderHiddenEmbedderElement, !expected1,
"window1 child visible state");
is(window2.document.querySelector("browser").contentWindow.browsingContext.isUnderHiddenEmbedderElement, !expected2,
"window2 child visible state");
}
// Tests that browser visibility is updated when it's swapped.
add_task(async () => {
// Open two new windows to swap iframes.
@ -25,11 +35,15 @@ add_task(async () => {
await Promise.all([ loadWindow1, loadWindow2 ]);
checkHiddenEmbeddederState(window1, window2, true, true);
// Hide the parent of browser2.
let parent = window2.document.getElementById("parent");
parent.style.visibility = "hidden";
parent.getBoundingClientRect();
checkHiddenEmbeddederState(window1, window2, true, false);
const browser2 = window2.document.querySelector("browser");
let target = browser2.contentDocument.getElementById("button");
target.focus();
@ -43,6 +57,8 @@ add_task(async () => {
parent.style.visibility = "";
parent.getBoundingClientRect();
checkHiddenEmbeddederState(window1, window2, true, true);
target.focus();
// browser2 is visible now, so focus() should work.
@ -66,10 +82,14 @@ add_task(async () => {
isnot(browser1.contentDocument.activeElement, target,
"Element.focus() shouldn't work in invisible browser");
checkHiddenEmbeddederState(window1, window2, false, true);
parent = window1.document.getElementById("parent");
parent.style.visibility = "visible";
parent.getBoundingClientRect();
checkHiddenEmbeddederState(window1, window2, true, true);
target.focus();
// Now browser1 is in a visibility:visible element, so that
@ -103,6 +123,10 @@ add_task(async () => {
let target = grandChildBrowser.contentDocument.getElementById("button");
target.focus();
ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
ok(!childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is visible");
ok(!grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is visible");
is(grandChildBrowser.contentDocument.activeElement, target,
"Element.focus() should work in visible browser");
target.blur();
@ -112,6 +136,10 @@ add_task(async () => {
parent.style.visibility = "hidden";
parent.getBoundingClientRect();
ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
ok(!childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is visible");
ok(grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is not visible");
target.focus();
isnot(grandChildBrowser.contentDocument.activeElement, target,
@ -124,6 +152,10 @@ add_task(async () => {
target.focus();
ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
ok(childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is not visible");
ok(grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is not visible");
isnot(grandChildBrowser.contentDocument.activeElement, target,
"Element.focus() shouldn't work in invisible iframe");
@ -131,6 +163,10 @@ add_task(async () => {
parent.style.visibility = "visible";
parent.getBoundingClientRect();
ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
ok(!childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is visible");
ok(grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is not visible");
target.focus();
// Even if the child iframe is visible, but still the grand child is
@ -160,6 +196,9 @@ add_task(async () => {
let target = iframe.contentDocument.getElementById("button");
target.focus();
ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
ok(iframe.browsingContext.isUnderHiddenEmbedderElement, "iframe is not visible");
isnot(iframe.contentDocument.activeElement, target,
"Element.focus() shouldn't work in invisible iframe");

View File

@ -164,22 +164,15 @@ void nsSubDocumentFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
void nsSubDocumentFrame::PropagateIsUnderHiddenEmbedderElementToSubView(
bool aIsUnderHiddenEmbedderElement) {
if (mFrameLoader && mFrameLoader->IsRemoteFrame()) {
mFrameLoader->SendIsUnderHiddenEmbedderElement(
aIsUnderHiddenEmbedderElement);
if (!mFrameLoader) {
return;
}
if (!mInnerView) {
return;
}
nsView* subdocView = mInnerView->GetFirstChild();
while (subdocView) {
if (mozilla::PresShell* presShell = subdocView->GetPresShell()) {
presShell->SetIsUnderHiddenEmbedderElement(aIsUnderHiddenEmbedderElement);
if (BrowsingContext* bc = mFrameLoader->GetExtantBrowsingContext()) {
if (bc->IsUnderHiddenEmbedderElement() != aIsUnderHiddenEmbedderElement) {
Unused << bc->SetIsUnderHiddenEmbedderElement(
aIsUnderHiddenEmbedderElement);
}
subdocView = subdocView->GetNextSibling();
}
}