summaryrefslogtreecommitdiffstats
path: root/layout/base
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/base/AccessibleCaretManager.cpp3
-rw-r--r--layout/base/PositionedEventTargeting.cpp56
-rw-r--r--layout/base/PresShell.cpp26
-rw-r--r--layout/base/RestyleManager.cpp46
-rw-r--r--layout/base/ViewportUtils.cpp3
-rw-r--r--layout/base/ZoomConstraintsClient.cpp16
-rw-r--r--layout/base/crashtests/crashtests.list8
-rw-r--r--layout/base/metrics.yaml16
-rw-r--r--layout/base/nsCSSFrameConstructor.cpp28
-rw-r--r--layout/base/nsCSSFrameConstructor.h3
-rw-r--r--layout/base/nsDocumentViewer.cpp5
-rw-r--r--layout/base/nsGenConList.cpp13
-rw-r--r--layout/base/nsLayoutUtils.cpp82
-rw-r--r--layout/base/nsLayoutUtils.h55
-rw-r--r--layout/base/nsRefreshDriver.cpp30
-rw-r--r--layout/base/tests/browser.toml6
-rw-r--r--layout/base/tests/browser_animatedImageLeak.js226
-rw-r--r--layout/base/tests/bug1896051-ref.html29
-rw-r--r--layout/base/tests/bug1896051.html38
-rw-r--r--layout/base/tests/helper_animatedImageLeak.html10
-rw-r--r--layout/base/tests/helper_bug1733509.html30
-rw-r--r--layout/base/tests/mochitest.toml3
-rw-r--r--layout/base/tests/test_event_target_radius.html56
-rw-r--r--layout/base/tests/test_reftests_with_caret.html1
24 files changed, 619 insertions, 170 deletions
diff --git a/layout/base/AccessibleCaretManager.cpp b/layout/base/AccessibleCaretManager.cpp
index 99155133cd..7c125cf023 100644
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -19,6 +19,7 @@
#include "mozilla/dom/NodeFilterBinding.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/TreeWalker.h"
+#include "mozilla/FocusModel.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/PresShell.h"
@@ -879,7 +880,7 @@ nsIFrame* AccessibleCaretManager::GetFocusableFrame(nsIFrame* aFrame) const {
// Look for the nearest enclosing focusable frame.
nsIFrame* focusableFrame = aFrame;
while (focusableFrame) {
- if (focusableFrame->IsFocusable(/* aWithMouse = */ true)) {
+ if (focusableFrame->IsFocusable(IsFocusableFlags::WithMouse)) {
break;
}
focusableFrame = focusableFrame->GetParent();
diff --git a/layout/base/PositionedEventTargeting.cpp b/layout/base/PositionedEventTargeting.cpp
index a975372dea..9af132d96d 100644
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -6,6 +6,7 @@
#include "PositionedEventTargeting.h"
+#include "Units.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/Preferences.h"
@@ -13,18 +14,23 @@
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/ToString.h"
+#include "mozilla/ViewportUtils.h"
#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/layers/LayersTypes.h"
#include "nsContainerFrame.h"
+#include "nsCoord.h"
#include "nsFrameList.h" // for DEBUG_FRAME_DUMP
#include "nsHTMLParts.h"
#include "nsLayoutUtils.h"
#include "nsGkAtoms.h"
#include "nsFontMetrics.h"
+#include "nsIContentInlines.h"
+#include "nsPresContext.h"
#include "nsPrintfCString.h"
#include "mozilla/dom/Element.h"
#include "nsRegion.h"
#include "nsDeviceContext.h"
-#include "nsIContentInlines.h"
#include "nsIFrame.h"
#include <algorithm>
@@ -276,15 +282,40 @@ static nsIContent* GetClickableAncestor(
return nullptr;
}
-static nscoord AppUnitsFromMM(RelativeTo aFrame, uint32_t aMM) {
- nsPresContext* pc = aFrame.mFrame->PresContext();
- float result = float(aMM) * (pc->DeviceContext()->AppUnitsPerPhysicalInch() /
- MM_PER_INCH_FLOAT);
- if (aFrame.mViewportType == ViewportType::Layout) {
+static Scale2D AppUnitsToMMScale(RelativeTo aFrame) {
+ nsPresContext* presContext = aFrame.mFrame->PresContext();
+
+ const int32_t appUnitsPerInch =
+ presContext->DeviceContext()->AppUnitsPerPhysicalInch();
+ const float appUnits =
+ static_cast<float>(appUnitsPerInch) / MM_PER_INCH_FLOAT;
+
+ // Visual coordinates are only used for quantities relative to the
+ // cross-process root content document's root frame. There should
+ // not be an enclosing resolution or transform scale above that.
+ if (aFrame.mViewportType != ViewportType::Layout) {
+ const nscoord scale = NSToCoordRound(appUnits);
+ return Scale2D{static_cast<float>(scale), static_cast<float>(scale)};
+ }
+
+ Scale2D localResolution{1.0f, 1.0f};
+ Scale2D enclosingResolution{1.0f, 1.0f};
+
+ if (auto* pc = presContext->GetInProcessRootContentDocumentPresContext()) {
PresShell* presShell = pc->PresShell();
- result = result / presShell->GetResolution();
+ localResolution = {presShell->GetResolution(), presShell->GetResolution()};
+ enclosingResolution = ViewportUtils::TryInferEnclosingResolution(presShell);
}
- return NSToCoordRound(result);
+
+ const gfx::MatrixScales parentScale =
+ nsLayoutUtils::GetTransformToAncestorScale(aFrame.mFrame);
+ const Scale2D resolution =
+ localResolution * parentScale * enclosingResolution;
+
+ const nscoord scaleX = NSToCoordRound(appUnits / resolution.xScale);
+ const nscoord scaleY = NSToCoordRound(appUnits / resolution.yScale);
+
+ return {static_cast<float>(scaleX), static_cast<float>(scaleY)};
}
/**
@@ -303,10 +334,11 @@ static nsRect GetTargetRect(RelativeTo aRootFrame,
const nsPoint& aPointRelativeToRootFrame,
const nsIFrame* aRestrictToDescendants,
const EventRadiusPrefs& aPrefs, uint32_t aFlags) {
- nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs.mRadiusTopmm),
- AppUnitsFromMM(aRootFrame, aPrefs.mRadiusRightmm),
- AppUnitsFromMM(aRootFrame, aPrefs.mRadiusBottommm),
- AppUnitsFromMM(aRootFrame, aPrefs.mRadiusLeftmm));
+ const Scale2D scale = AppUnitsToMMScale(aRootFrame);
+ nsMargin m(aPrefs.mRadiusTopmm * scale.yScale,
+ aPrefs.mRadiusRightmm * scale.xScale,
+ aPrefs.mRadiusBottommm * scale.yScale,
+ aPrefs.mRadiusLeftmm * scale.xScale);
nsRect r(aPointRelativeToRootFrame, nsSize(0, 0));
r.Inflate(m);
if (!(aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)) {
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
index 7674abb3d0..cd41da3730 100644
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -3931,11 +3931,9 @@ void PresShell::ScheduleViewManagerFlush() {
return;
}
- nsPresContext* presContext = GetPresContext();
- if (presContext) {
+ if (nsPresContext* presContext = GetPresContext()) {
presContext->RefreshDriver()->ScheduleViewManagerFlush();
}
- SetNeedLayoutFlush();
}
void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) {
@@ -6566,7 +6564,7 @@ void PresShell::PaintInternal(nsView* aViewToPaint, PaintInternalFlags aFlags) {
// We also force sync-decoding via pref for reftests.
if (aFlags & PaintInternalFlags::PaintSyncDecodeImages ||
mDocument->IsStaticDocument() ||
- StaticPrefs::image_decode_sync_enabled()) {
+ StaticPrefs::image_testing_decode_sync_enabled()) {
flags |= PaintFrameFlags::SyncDecodeImages;
}
if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
@@ -11260,10 +11258,8 @@ void PresShell::SetIsActive(bool aIsActive) {
#if defined(MOZ_WIDGET_ANDROID)
if (!aIsActive && presContext &&
presContext->IsRootContentDocumentCrossProcess()) {
- if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
- // Reset the dynamic toolbar offset state.
- presContext->UpdateDynamicToolbarOffset(0);
- }
+ // Reset the dynamic toolbar offset state.
+ presContext->UpdateDynamicToolbarOffset(0);
}
#endif
}
@@ -11849,10 +11845,9 @@ void PresShell::SyncWindowProperties(bool aSync) {
canvas ? canvas : rootFrame, rootFrame);
windowWidget->SetTransparencyMode(mode);
- // For macOS, apply color scheme overrides to the top level window widget.
- if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
- windowWidget->SetColorScheme(scheme);
- }
+ // For macOS, apply color scheme to the top level window widget.
+ windowWidget->SetColorScheme(
+ Some(LookAndFeel::ColorSchemeForFrame(rootFrame)));
}
if (!weak.IsAlive()) {
@@ -12102,13 +12097,6 @@ void PresShell::EventHandler::EventTargetData::UpdateWheelEventTarget(
return;
}
- // If the browsing context is no longer the same as the context of the
- // current wheel transaction, do not override the event target.
- if (!groupFrame->PresContext() || !groupFrame->PresShell() ||
- groupFrame->PresContext() != GetPresContext()) {
- return;
- }
-
// If dom.event.wheel-event-groups.enabled is set and whe have a stored
// event target from the wheel transaction, override the event target.
SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent);
diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp
index 81ffebf89a..e00493467f 100644
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -3246,6 +3246,12 @@ void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
// construction).
nsTArray<RefPtr<Element>> anchorsToSuppress;
+ // Unfortunately, the frame constructor and ProcessPostTraversal can
+ // generate new change hints while processing existing ones. We redirect
+ // those into a secondary queue and iterate until there's nothing left.
+ ReentrantChangeList newChanges;
+ mReentrantChanges = &newChanges;
+
{
DocumentStyleRootIterator iter(doc->GetServoRestyleRoot());
while (Element* root = iter.GetNextStyleRoot()) {
@@ -3271,34 +3277,26 @@ void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
doc->ClearServoRestyleRoot();
ClearSnapshots();
- // Process the change hints.
- //
- // Unfortunately, the frame constructor can generate new change hints while
- // processing existing ones. We redirect those into a secondary queue and
- // iterate until there's nothing left.
- {
- ReentrantChangeList newChanges;
- mReentrantChanges = &newChanges;
- while (!currentChanges.IsEmpty()) {
- ProcessRestyledFrames(currentChanges);
- MOZ_ASSERT(currentChanges.IsEmpty());
- for (ReentrantChange& change : newChanges) {
- if (!(change.mHint & nsChangeHint_ReconstructFrame) &&
- !change.mContent->GetPrimaryFrame()) {
- // SVG Elements post change hints without ensuring that the primary
- // frame will be there after that (see bug 1366142).
- //
- // Just ignore those, since we can't really process them.
- continue;
- }
- currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
- change.mContent, change.mHint);
+ while (!currentChanges.IsEmpty()) {
+ ProcessRestyledFrames(currentChanges);
+ MOZ_ASSERT(currentChanges.IsEmpty());
+ for (ReentrantChange& change : newChanges) {
+ if (!(change.mHint & nsChangeHint_ReconstructFrame) &&
+ !change.mContent->GetPrimaryFrame()) {
+ // SVG Elements post change hints without ensuring that the primary
+ // frame will be there after that (see bug 1366142).
+ //
+ // Just ignore those, since we can't really process them.
+ continue;
}
- newChanges.Clear();
+ currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
+ change.mContent, change.mHint);
}
- mReentrantChanges = nullptr;
+ newChanges.Clear();
}
+ mReentrantChanges = nullptr;
+
// Suppress adjustments in the after-change scroll anchors if needed, now
// that we're done reframing everything.
for (Element* element : anchorsToSuppress) {
diff --git a/layout/base/ViewportUtils.cpp b/layout/base/ViewportUtils.cpp
index f0d18510bf..8b8e4a4d27 100644
--- a/layout/base/ViewportUtils.cpp
+++ b/layout/base/ViewportUtils.cpp
@@ -260,6 +260,9 @@ const nsIFrame* ViewportUtils::IsZoomedContentRoot(const nsIFrame* aFrame) {
}
Scale2D ViewportUtils::TryInferEnclosingResolution(PresShell* aShell) {
+ if (!XRE_IsContentProcess()) {
+ return {1.0f, 1.0f};
+ }
MOZ_ASSERT(aShell && aShell->GetPresContext());
MOZ_ASSERT(!aShell->GetPresContext()->GetParentPresContext(),
"TryInferEnclosingResolution can only be called for a root pres "
diff --git a/layout/base/ZoomConstraintsClient.cpp b/layout/base/ZoomConstraintsClient.cpp
index e695c7755e..e9be0c2073 100644
--- a/layout/base/ZoomConstraintsClient.cpp
+++ b/layout/base/ZoomConstraintsClient.cpp
@@ -53,9 +53,20 @@ static nsIWidget* GetWidget(PresShell* aPresShell) {
return nullptr;
}
if (nsIFrame* rootFrame = aPresShell->GetRootFrame()) {
+#if defined(MOZ_WIDGET_ANDROID)
+ // On Android in cases of about:XX pages loaded in the browser parent
+ // process we need to return the nearest widget since it's the widget owning
+ // an IAPZCTreeManager to communicate with the APZCTreeManager for the
+ // browser.
+ // In bug 1648427 we will apply this code to desktops as well to make
+ // about pages zoomable on desktops, but it will be involving more works,
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1648427#c7 .
+ return rootFrame->GetNearestWidget();
+#else
if (nsView* view = rootFrame->GetView()) {
return view->GetWidget();
}
+#endif
}
return nullptr;
}
@@ -199,6 +210,11 @@ void ZoomConstraintsClient::RefreshZoomConstraints() {
return;
}
+ // Ignore documents which has been removed from the doc shell.
+ if (!mDocument->IsActive()) {
+ return;
+ }
+
uint32_t presShellId = 0;
ScrollableLayerGuid::ViewID viewId = ScrollableLayerGuid::NULL_SCROLL_ID;
bool scrollIdentifiersValid =
diff --git a/layout/base/crashtests/crashtests.list b/layout/base/crashtests/crashtests.list
index 3b1ed1e3d4..a379b981b6 100644
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -385,11 +385,7 @@ load 836990-1.html
load 840480.html
load 842114.html
load 847242.html
-# This test is slow, because it uses setTimeout a lot.
-# After bug 1875100, on windows, it times out because there's a left-over
-# window or so, and that bug causes us to throttle timers. Disable window
-# occlusion to work around this for now, see bug 1864255.
-pref(widget.windows.window_occlusion_tracking.enabled,false) skip-if(ThreadSanitizer) load 852293.html
+skip-if(ThreadSanitizer) load 852293.html
pref(layers.force-active,true) load 859526-1.html
pref(layers.force-active,true) load 859630-1.html
load 860579-1.html
@@ -528,7 +524,7 @@ load 1539303.html
load 1541679.html
load 1547261.html
load 1547391.html
-pref(widget.windows.window_occlusion_tracking.enabled,false) load 1548057.html # Bug 1819154
+load 1548057.html
load 1549867.html
load 1553874.html
load 1560328.html
diff --git a/layout/base/metrics.yaml b/layout/base/metrics.yaml
index 856ff77965..631200bd48 100644
--- a/layout/base/metrics.yaml
+++ b/layout/base/metrics.yaml
@@ -29,6 +29,22 @@ performance.pageload:
- perf-telemetry-alerts@mozilla.com
expires: never
+ async_sheet_load:
+ type: timing_distribution
+ time_unit: millisecond
+ description: >
+ Time spent in milliseconds since a style sheet started loading async
+ until it finished.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1892660
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1892660
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - emilio@mozilla.com
+ - perf-telemetry-alerts@mozilla.com
+ expires: 132
performance.responsiveness:
req_anim_frame_callback:
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
index c0f35c278d..ba43eb70b9 100644
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1537,12 +1537,11 @@ already_AddRefed<nsIContent> nsCSSFrameConstructor::CreateGenConTextNode(
void nsCSSFrameConstructor::CreateGeneratedContent(
nsFrameConstructorState& aState, Element& aOriginatingElement,
- ComputedStyle& aPseudoStyle, uint32_t aContentIndex,
- const FunctionRef<void(nsIContent*)> aAddChild) {
+ ComputedStyle& aPseudoStyle, const StyleContentItem& aItem,
+ size_t aContentIndex, const FunctionRef<void(nsIContent*)> aAddChild) {
using Type = StyleContentItem::Tag;
// Get the content value
- const auto& item = aPseudoStyle.StyleContent()->ContentAt(aContentIndex);
- const Type type = item.tag;
+ const Type type = aItem.tag;
switch (type) {
case Type::Image: {
@@ -1552,7 +1551,7 @@ void nsCSSFrameConstructor::CreateGeneratedContent(
}
case Type::String: {
- const auto string = item.AsString().AsString();
+ const auto string = aItem.AsString().AsString();
if (string.IsEmpty()) {
return;
}
@@ -1563,7 +1562,7 @@ void nsCSSFrameConstructor::CreateGeneratedContent(
}
case Type::Attr: {
- const auto& attr = item.AsAttr();
+ const auto& attr = aItem.AsAttr();
RefPtr<nsAtom> attrName = attr.attribute.AsAtom();
int32_t attrNameSpace = kNameSpaceID_None;
RefPtr<nsAtom> ns = attr.namespace_url.AsAtom();
@@ -1592,11 +1591,11 @@ void nsCSSFrameConstructor::CreateGeneratedContent(
CounterStylePtr ptr;
nsString separator;
if (type == Type::Counter) {
- auto& counter = item.AsCounter();
+ auto& counter = aItem.AsCounter();
name = counter._0.AsAtom();
ptr = CounterStylePtr::FromStyle(counter._1);
} else {
- auto& counters = item.AsCounters();
+ auto& counters = aItem.AsCounters();
name = counters._0.AsAtom();
CopyUTF8toUTF16(counters._1.AsString(), separator);
ptr = CounterStylePtr::FromStyle(counters._2);
@@ -1947,13 +1946,14 @@ void nsCSSFrameConstructor::CreateGeneratedContentItem(
mPresShell->StyleSet()->StyleNewSubtree(childElement);
}
};
- const uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount();
- for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) {
- CreateGeneratedContent(aState, aOriginatingElement, *pseudoStyle,
- contentIndex, AppendChild);
+ auto items = pseudoStyle->StyleContent()->NonAltContentItems();
+ size_t index = 0;
+ for (const auto& item : items) {
+ CreateGeneratedContent(aState, aOriginatingElement, *pseudoStyle, item,
+ index++, AppendChild);
}
// If a ::marker has no 'content' then generate it from its 'list-style-*'.
- if (contentCount == 0 && aPseudoElement == PseudoStyleType::marker) {
+ if (index == 0 && aPseudoElement == PseudoStyleType::marker) {
CreateGeneratedContentFromListStyle(aState, aOriginatingElement,
*pseudoStyle, AppendChild);
}
@@ -5283,7 +5283,7 @@ void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
aFlags.contains(ItemFlag::AllowPageBreak) &&
aState.mPresContext->IsPaginated() &&
!display.IsAbsolutelyPositionedStyle() &&
- !(aParentFrame && aParentFrame->IsGridContainerFrame()) &&
+ !(aParentFrame && aParentFrame->IsFlexOrGridContainer()) &&
!(bits & FCDATA_IS_TABLE_PART) && !(bits & FCDATA_IS_SVG_TEXT);
if (canHavePageBreak && display.BreakBefore()) {
AppendPageBreakItem(aContent, aItems);
diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h
index 51d5fe32d0..5fdea315b8 100644
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -477,7 +477,8 @@ class nsCSSFrameConstructor final : public nsFrameManager {
*/
void CreateGeneratedContent(
nsFrameConstructorState& aState, Element& aOriginatingElement,
- ComputedStyle& aPseudoStyle, uint32_t aContentIndex,
+ ComputedStyle& aPseudoStyle, const mozilla::StyleContentItem& aItem,
+ size_t aContentIndex,
const mozilla::FunctionRef<void(nsIContent*)> aAddChild);
/**
diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp
index 8ae0e001b8..bbd43a830e 100644
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2426,7 +2426,7 @@ NS_IMETHODIMP nsDocumentViewer::CopyLinkLocation() {
NS_ENSURE_SUCCESS(rv, rv);
// copy the href onto the clipboard
- return clipboard->CopyString(locationText);
+ return clipboard->CopyString(locationText, mDocument->GetWindowContext());
}
NS_IMETHODIMP nsDocumentViewer::CopyImage(int32_t aCopyFlags) {
@@ -2436,7 +2436,8 @@ NS_IMETHODIMP nsDocumentViewer::CopyImage(int32_t aCopyFlags) {
NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
nsCOMPtr<nsILoadContext> loadContext(mContainer);
- return nsCopySupport::ImageCopy(node, loadContext, aCopyFlags);
+ return nsCopySupport::ImageCopy(node, loadContext, aCopyFlags,
+ mDocument->GetWindowContext());
}
NS_IMETHODIMP nsDocumentViewer::GetCopyable(bool* aCopyable) {
diff --git a/layout/base/nsGenConList.cpp b/layout/base/nsGenConList.cpp
index b770ee539a..175e6fe7d5 100644
--- a/layout/base/nsGenConList.cpp
+++ b/layout/base/nsGenConList.cpp
@@ -12,12 +12,13 @@
#include "nsIFrame.h"
void nsGenConNode::CheckFrameAssertions() {
- NS_ASSERTION(
- mContentIndex < int32_t(mPseudoFrame->StyleContent()->ContentCount()) ||
- // Special-case for the USE node created for the legacy markers,
- // which don't use the content property.
- mContentIndex == 0,
- "index out of range");
+ NS_ASSERTION(mContentIndex < int32_t(mPseudoFrame->StyleContent()
+ ->NonAltContentItems()
+ .Length()) ||
+ // Special-case for the USE node created for the legacy
+ // markers, which don't use the content property.
+ mContentIndex == 0,
+ "index out of range");
// We allow negative values of mContentIndex for 'counter-reset' and
// 'counter-increment'.
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
index 429ad335cc..3f84a97250 100644
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -900,7 +900,7 @@ void nsLayoutUtils::GetMarkerSpokenText(const nsIContent* aContent,
return;
}
- if (frame->StyleContent()->ContentCount() > 0) {
+ if (!frame->StyleContent()->NonAltContentItems().IsEmpty()) {
for (nsIFrame* child : frame->PrincipalChildList()) {
nsIFrame::RenderedText text = child->GetRenderedText();
aText += text.mString;
@@ -2131,6 +2131,7 @@ const nsIFrame* nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(
bool nsLayoutUtils::AuthorSpecifiedBorderBackgroundDisablesTheming(
StyleAppearance aAppearance) {
return aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::PasswordInput ||
aAppearance == StyleAppearance::Button ||
aAppearance == StyleAppearance::Textfield ||
aAppearance == StyleAppearance::Textarea ||
@@ -3498,7 +3499,7 @@ nsIFrame* nsLayoutUtils::GetFirstNonAnonymousFrame(nsIFrame* aFrame) {
struct BoxToRect : public nsLayoutUtils::BoxCallback {
const nsIFrame* mRelativeTo;
RectCallback* mCallback;
- uint32_t mFlags;
+ nsLayoutUtils::GetAllInFlowRectsFlags mFlags;
// If the frame we're measuring relative to is the root, we know all frames
// are descendants of it, so we don't need to compute the common ancestor
// between a frame and mRelativeTo.
@@ -3510,7 +3511,8 @@ struct BoxToRect : public nsLayoutUtils::BoxCallback {
bool mRelativeToIsTarget;
BoxToRect(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
- RectCallback* aCallback, uint32_t aFlags)
+ RectCallback* aCallback,
+ nsLayoutUtils::GetAllInFlowRectsFlags aFlags)
: mRelativeTo(aRelativeTo),
mCallback(aCallback),
mFlags(aFlags),
@@ -3523,21 +3525,34 @@ struct BoxToRect : public nsLayoutUtils::BoxCallback {
const bool usingSVGOuterFrame = !!outer;
if (!outer) {
outer = aFrame;
- switch (mFlags & nsLayoutUtils::RECTS_WHICH_BOX_MASK) {
- case nsLayoutUtils::RECTS_USE_CONTENT_BOX:
- r = aFrame->GetContentRectRelativeToSelf();
- break;
- case nsLayoutUtils::RECTS_USE_PADDING_BOX:
- r = aFrame->GetPaddingRectRelativeToSelf();
- break;
- case nsLayoutUtils::RECTS_USE_MARGIN_BOX:
- r = aFrame->GetMarginRectRelativeToSelf();
- break;
- default: // Use the border box
- r = aFrame->GetRectRelativeToSelf();
+ if (mFlags.contains(
+ nsLayoutUtils::GetAllInFlowRectsFlag::UseContentBox)) {
+ r = aFrame->GetContentRectRelativeToSelf();
+ } else if (mFlags.contains(
+ nsLayoutUtils::GetAllInFlowRectsFlag::UsePaddingBox)) {
+ r = aFrame->GetPaddingRectRelativeToSelf();
+ } else if (mFlags.contains(
+ nsLayoutUtils::GetAllInFlowRectsFlag::UseMarginBox)) {
+ r = aFrame->GetMarginRectRelativeToSelf();
+ } else if (mFlags.contains(nsLayoutUtils::GetAllInFlowRectsFlag::
+ UseMarginBoxWithAutoResolvedAsZero)) {
+ r = aFrame->GetRectRelativeToSelf();
+ const auto& styleMargin = aFrame->StyleMargin()->mMargin;
+ nsMargin usedMargin =
+ aFrame->GetUsedMargin().ApplySkipSides(aFrame->GetSkipSides());
+ for (const Side side : AllPhysicalSides()) {
+ if (styleMargin.Get(side).IsAuto()) {
+ usedMargin.Side(side) = 0;
+ }
+ }
+ r.Inflate(usedMargin);
+ } else {
+ // Use the border-box.
+ r = aFrame->GetRectRelativeToSelf();
}
}
- if (mFlags & nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS) {
+ if (mFlags.contains(
+ nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms)) {
const bool isAncestorKnown = [&] {
if (mRelativeToIsRoot) {
return true;
@@ -3568,7 +3583,7 @@ struct MOZ_RAII BoxToRectAndText : public BoxToRect {
BoxToRectAndText(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
RectCallback* aCallback, Sequence<nsString>* aTextList,
- uint32_t aFlags)
+ nsLayoutUtils::GetAllInFlowRectsFlags aFlags)
: BoxToRect(aTargetFrame, aRelativeTo, aCallback, aFlags),
mTextList(aTextList) {}
@@ -3609,7 +3624,7 @@ struct MOZ_RAII BoxToRectAndText : public BoxToRect {
void nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame,
const nsIFrame* aRelativeTo,
RectCallback* aCallback,
- uint32_t aFlags) {
+ GetAllInFlowRectsFlags aFlags) {
BoxToRect converter(aFrame, aRelativeTo, aCallback, aFlags);
GetAllInFlowBoxes(aFrame, &converter);
}
@@ -3618,7 +3633,7 @@ void nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame,
const nsIFrame* aRelativeTo,
RectCallback* aCallback,
Sequence<nsString>* aTextList,
- uint32_t aFlags) {
+ GetAllInFlowRectsFlags aFlags) {
BoxToRectAndText converter(aFrame, aRelativeTo, aCallback, aTextList, aFlags);
GetAllInFlowBoxes(aFrame, &converter);
}
@@ -3649,7 +3664,7 @@ nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame) {
nsRect nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame,
const nsIFrame* aRelativeTo,
- uint32_t aFlags) {
+ GetAllInFlowRectsFlags aFlags) {
RectAccumulator accumulator;
GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags);
return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
@@ -4610,7 +4625,7 @@ static nscoord AddIntrinsicSizeOffset(
LayoutDeviceIntSize devSize = pc->Theme()->GetMinimumWidgetSize(
pc, aFrame, disp->EffectiveAppearance());
nscoord themeSize = pc->DevPixelsToAppUnits(
- aAxis == eAxisVertical ? devSize.height : devSize.width);
+ aAxis == PhysicalAxis::Vertical ? devSize.height : devSize.width);
// GetMinimumWidgetSize() returns a border-box width.
themeSize += aOffsets.margin;
if (themeSize > result) {
@@ -4641,7 +4656,7 @@ nscoord nsLayoutUtils::IntrinsicForAxis(
aPercentageBasis.isSome(),
"grid layout should always pass a percentage basis");
- const bool horizontalAxis = MOZ_LIKELY(aAxis == eAxisHorizontal);
+ const bool horizontalAxis = MOZ_LIKELY(aAxis == PhysicalAxis::Horizontal);
#ifdef DEBUG_INTRINSIC_WIDTH
nsIFrame::IndentBy(stderr, gNoiseIndent);
aFrame->ListTag(stderr);
@@ -5048,10 +5063,11 @@ nscoord nsLayoutUtils::MinSizeContributionForAxis(
// Note: this method is only meant for grid/flex items.
const nsStylePosition* const stylePos = aFrame->StylePosition();
- StyleSize size =
- aAxis == eAxisHorizontal ? stylePos->mMinWidth : stylePos->mMinHeight;
- StyleMaxSize maxSize =
- aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight;
+ StyleSize size = aAxis == PhysicalAxis::Horizontal ? stylePos->mMinWidth
+ : stylePos->mMinHeight;
+ StyleMaxSize maxSize = aAxis == PhysicalAxis::Horizontal
+ ? stylePos->mMaxWidth
+ : stylePos->mMaxHeight;
auto childWM = aFrame->GetWritingMode();
PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(LogicalAxis::Inline);
// According to the spec, max-content and min-content should behave as the
@@ -5077,7 +5093,8 @@ nscoord nsLayoutUtils::MinSizeContributionForAxis(
minSize = 0;
fixedMinSize = &minSize;
} else {
- size = aAxis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight;
+ size = aAxis == PhysicalAxis::Horizontal ? stylePos->mWidth
+ : stylePos->mHeight;
// This is same as above: keywords should behaves as property's initial
// values in block axis.
if (aAxis != ourInlineAxis && size.BehavesLikeInitialValueOnBlockAxis()) {
@@ -8896,13 +8913,7 @@ ScrollMetadata nsLayoutUtils::ComputeScrollMetadata(
}
if (primaryFrame) {
WritingMode writingModeOfRootScrollFrame = primaryFrame->GetWritingMode();
- WritingMode::BlockDir blockDirOfRootScrollFrame =
- writingModeOfRootScrollFrame.GetBlockDir();
- WritingMode::InlineDir inlineDirOfRootScrollFrame =
- writingModeOfRootScrollFrame.GetInlineDir();
- if (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockRL ||
- (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockTB &&
- inlineDirOfRootScrollFrame == WritingMode::InlineDir::eInlineRTL)) {
+ if (writingModeOfRootScrollFrame.IsPhysicalRTL()) {
metadata.SetIsAutoDirRootContentRTL(true);
}
}
@@ -9223,7 +9234,8 @@ CSSRect nsLayoutUtils::GetBoundingFrameRect(
CSSRect result;
nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame();
result = CSSRect::FromAppUnits(nsLayoutUtils::GetAllInFlowRectsUnion(
- aFrame, relativeTo, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
+ aFrame, relativeTo,
+ nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms));
// If the element is contained in a scrollable frame that is not
// the root scroll frame, make sure to clip the result so that it is
diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h
index 135a9ad9ab..6b6e45b3e4 100644
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1241,52 +1241,57 @@ class nsLayoutUtils {
static nsIFrame* GetContainingBlockForClientRect(nsIFrame* aFrame);
- enum {
- RECTS_ACCOUNT_FOR_TRANSFORMS = 0x01,
- // Two bits for specifying which box type to use.
- // With neither bit set (default), use the border box.
- RECTS_USE_CONTENT_BOX = 0x02,
- RECTS_USE_PADDING_BOX = 0x04,
- RECTS_USE_MARGIN_BOX = 0x06, // both bits set
- RECTS_WHICH_BOX_MASK = 0x06 // bitmask for these two bits
- };
/**
* Collect all CSS boxes (content, padding, border, or margin) associated
* with aFrame and its continuations, "drilling down" through table wrapper
* frames and some anonymous blocks since they're not real CSS boxes.
+ *
* The boxes are positioned relative to aRelativeTo (taking scrolling
* into account) and passed to the callback in frame-tree order.
* If aFrame is null, no boxes are returned.
+ *
* For SVG frames, returns one rectangle, the bounding box.
- * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
- * the boxes into aRelativeTo coordinates, transforms (including CSS
- * and SVG transforms) are taken into account.
- * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
- * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
- * Otherwise (by default), the border box is used.
- */
+ *
+ * If aFlags includes 'AccountForTransforms', then when converting the boxes
+ * into aRelativeTo coordinates, transforms (including CSS and SVG transforms)
+ * are taken into account.
+ *
+ * If aFlags includes one of 'UseContentBox', 'UsePaddingBox', 'UseMarginBox',
+ * or 'UseMarginBoxWithAutoResolvedAsZero', the corresponding type of box is
+ * used. Otherwise (by default), the border box is used. Note that these "Box"
+ * flags are meant to be mutually exclusive, though we don't enforce that. If
+ * multiple "Box" flags are used, we'll gracefully just use the first one in
+ * the order of the enum.
+ */
+ enum class GetAllInFlowRectsFlag : uint8_t {
+ AccountForTransforms,
+ UseContentBox,
+ UsePaddingBox,
+ UseMarginBox,
+ // Similar to UseMarginBox, but the 'auto' margins are resolved as zero.
+ UseMarginBoxWithAutoResolvedAsZero,
+ };
+ using GetAllInFlowRectsFlags = mozilla::EnumSet<GetAllInFlowRectsFlag>;
static void GetAllInFlowRects(nsIFrame* aFrame, const nsIFrame* aRelativeTo,
mozilla::RectCallback* aCallback,
- uint32_t aFlags = 0);
+ GetAllInFlowRectsFlags aFlags = {});
static void GetAllInFlowRectsAndTexts(
nsIFrame* aFrame, const nsIFrame* aRelativeTo,
mozilla::RectCallback* aCallback,
- mozilla::dom::Sequence<nsString>* aTextList, uint32_t aFlags = 0);
+ mozilla::dom::Sequence<nsString>* aTextList,
+ GetAllInFlowRectsFlags aFlags = {});
/**
* Computes the union of all rects returned by GetAllInFlowRects. If
* the union is empty, returns the first rect.
- * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
- * the boxes into aRelativeTo coordinates, transforms (including CSS
- * and SVG transforms) are taken into account.
- * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
- * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
- * Otherwise (by default), the border box is used.
+ *
+ * See GetAllInFlowRects() documentation for the meaning of aRelativeTo and
+ * aFlags.
*/
static nsRect GetAllInFlowRectsUnion(nsIFrame* aFrame,
const nsIFrame* aRelativeTo,
- uint32_t aFlags = 0);
+ GetAllInFlowRectsFlags aFlags = {});
enum { EXCLUDE_BLUR_SHADOWS = 0x01 };
/**
diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp
index 7885d6b8db..218d158a47 100644
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -831,11 +831,11 @@ class VsyncRefreshDriverTimer : public RefreshDriverTimer {
const TimeDuration previousRate = mVsyncRate;
const TimeDuration rate = GetTimerRate();
- hal::PerformanceHintSession* const performanceHintSession =
- GetPerformanceHintSession();
- if (performanceHintSession && rate != previousRate) {
- performanceHintSession->UpdateTargetWorkDuration(
- ContentChild::GetPerformanceHintTarget(rate));
+ if (rate != previousRate) {
+ if (auto* const performanceHintSession = GetPerformanceHintSession()) {
+ performanceHintSession->UpdateTargetWorkDuration(
+ ContentChild::GetPerformanceHintTarget(rate));
+ }
}
if (TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval()) >
@@ -862,7 +862,7 @@ class VsyncRefreshDriverTimer : public RefreshDriverTimer {
TimeStamp tickEnd = TimeStamp::Now();
- if (performanceHintSession) {
+ if (auto* const performanceHintSession = GetPerformanceHintSession()) {
performanceHintSession->ReportActualWorkDuration(tickEnd - tickStart);
}
@@ -2228,23 +2228,7 @@ void nsRefreshDriver::RunFullscreenSteps() {
void nsRefreshDriver::UpdateIntersectionObservations(TimeStamp aNowTime) {
AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Compute intersections", LAYOUT);
-
- AutoTArray<RefPtr<Document>, 32> documents;
-
- if (mPresContext->Document()->HasIntersectionObservers()) {
- documents.AppendElement(mPresContext->Document());
- }
-
- mPresContext->Document()->CollectDescendantDocuments(
- documents, [](const Document* document) -> bool {
- return document->HasIntersectionObservers();
- });
-
- for (const auto& doc : documents) {
- doc->UpdateIntersectionObservations(aNowTime);
- doc->ScheduleIntersectionObserverNotification();
- }
-
+ mPresContext->Document()->UpdateIntersections(aNowTime);
mNeedToUpdateIntersectionObservations = false;
}
diff --git a/layout/base/tests/browser.toml b/layout/base/tests/browser.toml
index a5b279145f..cc8df52306 100644
--- a/layout/base/tests/browser.toml
+++ b/layout/base/tests/browser.toml
@@ -3,6 +3,12 @@ prefs = [
"layout.css.properties-and-values.enabled=true",
]
+["browser_animatedImageLeak.js"]
+skip-if = ["!debug"]
+support-files = [
+ "helper_animatedImageLeak.html"
+]
+
["browser_bug617076.js"]
["browser_bug1701027-1.js"]
diff --git a/layout/base/tests/browser_animatedImageLeak.js b/layout/base/tests/browser_animatedImageLeak.js
new file mode 100644
index 0000000000..2c34ed9d89
--- /dev/null
+++ b/layout/base/tests/browser_animatedImageLeak.js
@@ -0,0 +1,226 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+requestLongerTimeout(4);
+
+/*
+ * This tests that when we have an animated image in a minimized window we
+ * don't leak.
+ * We've encountered this bug in 3 different ways:
+ * -bug 1830753 - images in top level chrome processes
+ * (we avoid processing them due to their CompositorBridgeChild being paused)
+ * -bug 1839109 - images in content processes
+ * (we avoid processing them due to their refresh driver being throttled, this
+ * would also fix the above case)
+ * -bug 1875100 - images that are in a content iframe that is not the content
+ * of a tab, so something like an extension iframe in the sidebar
+ * (this was fixed by making the content of a tab declare that it manually
+ * manages its activeness and having all other iframes inherit their
+ * activeness from their parent)
+ * In order to hit this bug we require
+ * -the same image to be in a minimized window and in a non-mininmized window
+ * so that the image is animated.
+ * -the animated image to go over the
+ * image.animated.decode-on-demand.threshold-kb threshold so that we do not
+ * keep all of its frames around (if we keep all its frame around then we
+ * don't try to keep allocating frames and not freeing the old ones)
+ * -it has to be the same Image object in memory, not just the same uri
+ * Then the visible copy of the image keeps generating new frames, those frames
+ * get sent to the minimized copies of the image but they never get processed
+ * or marked displayed so they can never be freed/reused.
+ *
+ * Note that due to bug 1889840, in order to test this we can't use an image
+ * loaded at the top level (see the last point above). We must use an html page
+ * that contains the image.
+ */
+
+// this test is based in part on https://searchfox.org/mozilla-central/rev/c09764753ea40725eb50decad2c51edecbd33308/browser/components/extensions/test/browser/browser_ext_sidebarAction.js
+
+async function pushPrefs1() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["image.animated.decode-on-demand.threshold-kb", 1],
+ ["image.animated.decode-on-demand.batch-size", 2],
+ ],
+ });
+}
+
+async function openWindowsAndMinimize(taskToPerformBeforeMinimize) {
+ let wins = [null, null, null, null];
+ for (let i = 0; i < wins.length; i++) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await win.delayedStartupPromise;
+ await taskToPerformBeforeMinimize(win);
+
+ // Leave the last window un-minimized.
+ if (i < wins.length - 1) {
+ let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
+ win,
+ "sizemodechange"
+ );
+ win.minimize();
+ await promiseSizeModeChange;
+ }
+
+ wins[i] = win;
+ }
+ return wins;
+}
+
+async function pushPrefs2() {
+ // wait so that at least one frame of the animation has been shown while the
+ // below pref is not set so that the counter gets reset.
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["gfx.testing.assert-render-textures-increase", 75]],
+ });
+}
+
+async function waitForEnoughFrames() {
+ // we want to wait for over 75 frames of the image, it has a delay of 200ms
+ // Windows debug test machines seem to animate at about 10 fps though
+ await new Promise(resolve => setTimeout(resolve, 20000));
+}
+
+async function closeWindows(wins) {
+ for (let i = 0; i < wins.length; i++) {
+ await BrowserTestUtils.closeWindow(wins[i]);
+ }
+}
+
+async function popPrefs() {
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+}
+
+add_task(async () => {
+ async function runTest(theTestPath) {
+ await pushPrefs1();
+
+ let wins = await openWindowsAndMinimize(async function (win) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ theTestPath
+ );
+ });
+
+ await pushPrefs2();
+
+ await waitForEnoughFrames();
+
+ await closeWindows(wins);
+
+ await popPrefs();
+
+ ok(true, "got here without assserting");
+ }
+
+ function fileURL(filename) {
+ let ifile = getChromeDir(getResolvedURI(gTestPath));
+ ifile.append(filename);
+ return Services.io.newFileURI(ifile).spec;
+ }
+
+ // This tests the image in content process case
+ await runTest(fileURL("helper_animatedImageLeak.html"));
+ // This tests the image in chrome process case
+ await runTest(getRootDirectory(gTestPath) + "helper_animatedImageLeak.html");
+});
+
+// Now we test the image in a sidebar loaded via an extension case.
+
+/*
+ * The data uri below is a 2kb apng that is 3000x200 with 22 frames with delay
+ * of 200ms, it just toggles the color of one pixel from black to red so it's
+ * tiny. We use the same data uri (although that is not important to this test)
+ * in helper_animatedImageLeak.html.
+ */
+
+/*
+ * This is just data to create a simple extension that creates a sidebar with
+ * an image in it.
+ */
+let extData = {
+ manifest: {
+ sidebar_action: {
+ default_panel: "sidebar.html",
+ },
+ },
+ useAddonManager: "temporary",
+
+ files: {
+ "sidebar.html": `
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <script src="sidebar.js"></script>
+ </head>
+ <body><p>Sidebar</p>
+ <img src="data:image/png;base64,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"/>
+ </body>
+</html>
+ `,
+
+ "sidebar.js": function () {
+ window.onload = () => {
+ browser.test.sendMessage("sidebar");
+ };
+ },
+ },
+};
+
+function getExtData(manifestUpdates = {}) {
+ return {
+ ...extData,
+ manifest: {
+ ...extData.manifest,
+ ...manifestUpdates,
+ },
+ };
+}
+
+async function sendMessage(ext, msg, data = undefined) {
+ ext.sendMessage({ msg, data });
+ await ext.awaitMessage("done");
+}
+
+add_task(async function sidebar_initial_install() {
+ await pushPrefs1();
+
+ ok(
+ document.getElementById("sidebar-box").hidden,
+ "sidebar box is not visible"
+ );
+
+ let extension = ExtensionTestUtils.loadExtension(getExtData());
+ await extension.startup();
+ await extension.awaitMessage("sidebar");
+
+ // Test sidebar is opened on install
+ ok(!document.getElementById("sidebar-box").hidden, "sidebar box is visible");
+
+ // the sidebar appears on all new windows automatically.
+ let wins = await openWindowsAndMinimize(async function (win) {
+ await extension.awaitMessage("sidebar");
+ });
+
+ await pushPrefs2();
+
+ await waitForEnoughFrames();
+
+ await extension.unload();
+ // Test that the sidebar was closed on unload.
+ ok(
+ document.getElementById("sidebar-box").hidden,
+ "sidebar box is not visible"
+ );
+
+ await closeWindows(wins);
+
+ await popPrefs();
+
+ ok(true, "got here without assserting");
+});
diff --git a/layout/base/tests/bug1896051-ref.html b/layout/base/tests/bug1896051-ref.html
new file mode 100644
index 0000000000..39b26f148d
--- /dev/null
+++ b/layout/base/tests/bug1896051-ref.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html class="reftest-wait">
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ textarea {
+ font: 13px / 1 monospace;
+ border: 1px solid;
+ padding: 0;
+ overflow: hidden;
+ resize: none;
+ }
+</style>
+<textarea rows=5>
+a
+b
+c
+d
+</textarea>
+<script>
+SimpleTest.waitForFocus(function() {
+ let textarea = document.querySelector("textarea");
+ textarea.focus();
+ textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
+ setTimeout(() => {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+});
+</script>
diff --git a/layout/base/tests/bug1896051.html b/layout/base/tests/bug1896051.html
new file mode 100644
index 0000000000..ba35339475
--- /dev/null
+++ b/layout/base/tests/bug1896051.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html class="reftest-wait">
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ textarea {
+ font: 13px / 1 monospace;
+ border: 1px solid;
+ padding: 0;
+ overflow: hidden;
+ resize: none;
+ }
+</style>
+<textarea rows=5>
+a
+b
+c
+d
+</textarea>
+<script>
+SimpleTest.waitForFocus(function() {
+ let textarea = document.querySelector("textarea");
+ textarea.focus();
+ if (navigator.platform.startsWith("Mac")) {
+ // On mac there is no page down key that moves the selection, afaict.
+ // (Fn+Arrow moves the scroll position but not selection).
+ // Do the next thing which would be something like Cmd+Down to move to the
+ // end. That tests a different code-path altogether, but for this test it
+ // doesn't matter.
+ synthesizeKey("KEY_ArrowDown", { metaKey: true });
+ } else {
+ synthesizeKey("KEY_PageDown");
+ }
+ setTimeout(() => {
+ document.documentElement.removeAttribute("class")
+ }, 0);
+});
+</script>
diff --git a/layout/base/tests/helper_animatedImageLeak.html b/layout/base/tests/helper_animatedImageLeak.html
new file mode 100644
index 0000000000..a5555f1fbb
--- /dev/null
+++ b/layout/base/tests/helper_animatedImageLeak.html
@@ -0,0 +1,10 @@
+<html>
+<!--
+ The data uri below is a 2kb apng that is 3000x200 with 22 frames with delay
+ of 200ms, it just toggles the color of one pixel from black to red so it's
+ tiny. We use the same data uri (although that is not important to this test)
+ in browser_animatedImageLeak.js.
+-->
+
+<img src="data:image/png;base64,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"/>
+</html>
diff --git a/layout/base/tests/helper_bug1733509.html b/layout/base/tests/helper_bug1733509.html
new file mode 100644
index 0000000000..4fbdacdb46
--- /dev/null
+++ b/layout/base/tests/helper_bug1733509.html
@@ -0,0 +1,30 @@
+<html>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
+<style>
+ body {
+ margin: 0;
+ padding: 20;
+ background-color: blueviolet;
+ }
+
+ button {
+ margin: 0;
+ }
+</style>
+
+<body>
+ <button id="btn">Click me</button>
+</body>
+
+<script>
+ // Silence SimpleTest warning about missing assertions by having it wait
+ // indefinitely. We don't need to give it an explicit finish because the
+ // entire window this test runs in will be closed after the main browser test
+ // finished.
+ SimpleTest.waitForExplicitFinish();
+</script>
+
+</html>
diff --git a/layout/base/tests/mochitest.toml b/layout/base/tests/mochitest.toml
index 24924809c0..f74c6b030d 100644
--- a/layout/base/tests/mochitest.toml
+++ b/layout/base/tests/mochitest.toml
@@ -247,6 +247,7 @@ support-files = ["file_dynamic_toolbar_max_height.html"]
["test_emulate_color_scheme.html"]
["test_event_target_radius.html"]
+support-files = ["helper_bug1733509.html"]
skip-if = ["xorigin"] # JavaScript error: resource://specialpowers/SpecialPowersChild.sys.mjs, line 73: SecurityError: Permission denied to access property "windowUtils" on cross-origin object
["test_frame_reconstruction_body_table.html"]
@@ -558,6 +559,8 @@ support-files = [
"bug1518339-2-ref.html",
"bug1529492-1.html",
"bug1529492-1-ref.html",
+ "bug1896051.html",
+ "bug1896051-ref.html",
"chrome/blue-32x32.png",
]
diff --git a/layout/base/tests/test_event_target_radius.html b/layout/base/tests/test_event_target_radius.html
index caf046cf99..a1e8d9c16c 100644
--- a/layout/base/tests/test_event_target_radius.html
+++ b/layout/base/tests/test_event_target_radius.html
@@ -2,14 +2,19 @@
<html id="html" style="height:100%">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=780847
+https://bugzilla.mozilla.org/show_bug.cgi?id=1733509
-->
<head>
<title>Test radii for mouse events</title>
- <script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
.target { position:absolute; left:100px; top:100px; width:100px; height:100px; background:blue; }
+ .scaled { background: green; transform: scale(0.5); }
+ iframe { margin:0; padding:0; width:50; height:50; border:1px solid black; background:yellowgreen; }
</style>
</head>
<body id="body" onload="setTimeout(startTest, 0)" style="margin:0; width:100%; height:100%; overflow:hidden">
@@ -73,6 +78,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=780847
<div id="t13_touchlistener" style="width: 50px; height: 50px; background:red" ontouchend="x=1"></div>
<div id="t13_notouchlistener" style="width: 50px; height: 50px; background:green"></div>
</div>
+
+ <div id="t14" class="target scaled" hidden>
+ <iframe id="t14iframe"></iframe>
+ </div>
</div>
<pre id="test">
<script type="application/javascript">
@@ -414,7 +423,50 @@ function testTouchable() {
testTouch("t13", 10, 50 - (2*mm), "t13_touchlistener", "touch inside t13_touchlistener bottom edge");
setShowing("t13", false);
- endTest();
+ test4();
+}
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1733509
+function test4() {
+ // Skip non-Fission for now because of bug 1890522
+ if (SpecialPowers.Services.appinfo.fissionAutostart) {
+ waitUntilApzStable().then(async () => doTest4()).then(endTest);
+ } else {
+ endTest();
+ }
+}
+
+async function doTest4() {
+ setShowing("t14", true);
+
+ let iframeURL = SimpleTest.getTestFileURL("helper_bug1733509.html");
+ // todo: Also perform this test for an in-process iframe
+ // once bug 1890522 is fixed.
+ const iframe = document.querySelector("#t14iframe");
+ iframeURL = iframeURL.replace(window.location.origin, "https://example.com");
+ await setupIframe(iframe, iframeURL);
+
+ var result = await SpecialPowers.spawn(iframe, [], async () => {
+ await content.wrappedJSObject.waitUntilApzStable();
+ var iframeEventTarget = null;
+ content.window.onmousedown = function (event) { iframeEventTarget = event.target; };
+ content.wrappedJSObject.synthesizeMouse(content.document.documentElement, 2, 2, {});
+ return iframeEventTarget && iframeEventTarget.id === "btn";
+ });
+
+ ok(result, "Failed to target button inside iframe");
+ setShowing("t14", false);
+}
+
+async function setupIframe(aIFrame, aURL) {
+ const iframeLoadPromise = promiseOneEvent(aIFrame, "load", null);
+ aIFrame.src = aURL;
+ await iframeLoadPromise;
+
+ await SpecialPowers.spawn(aIFrame, [], async () => {
+ await content.wrappedJSObject.waitUntilApzStable();
+ await SpecialPowers.contentTransformsReceived(content);
+ });
}
</script>
</pre>
diff --git a/layout/base/tests/test_reftests_with_caret.html b/layout/base/tests/test_reftests_with_caret.html
index 3935380e5d..d134bd2eb9 100644
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -112,6 +112,7 @@ var tests = [
[ 'bug613807-1.html' , 'bug613807-1-ref.html' ] ,
[ 'bug1082486-1.html', 'bug1082486-1-ref.html'] ,
[ 'bug1082486-2.html', 'bug1082486-2-ref.html'] ,
+ [ 'bug1896051.html', 'bug1896051-ref.html'],
// The following test cases uses mouse events. We need to make
// AccessibleCaret unhide for them.
function() {SpecialPowers.pushPrefEnv({'set': [['layout.accessiblecaret.hide_carets_for_mouse_input', false]]}, nextTest);} ,