зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1178765
- Part 3: Add backdrop-filter display items to Gecko r=mstange
Differential Revision: https://phabricator.services.mozilla.com/D39099 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
dbce0f3810
Коммит
04d4ece0af
|
@ -1039,6 +1039,25 @@ void DisplayListBuilder::PushClearRectWithComplexRegion(
|
|||
&spaceAndClip);
|
||||
}
|
||||
|
||||
void DisplayListBuilder::PushBackdropFilter(
|
||||
const wr::LayoutRect& aBounds, const wr::ComplexClipRegion& aRegion,
|
||||
const nsTArray<wr::FilterOp>& aFilters,
|
||||
const nsTArray<wr::WrFilterData>& aFilterDatas, bool aIsBackfaceVisible) {
|
||||
wr::LayoutRect clip = MergeClipLeaf(aBounds);
|
||||
WRDL_LOG("PushBackdropFilter b=%s c=%s\n", mWrState,
|
||||
Stringify(aBounds).c_str(), Stringify(clip).c_str());
|
||||
|
||||
AutoTArray<wr::ComplexClipRegion, 1> clips;
|
||||
clips.AppendElement(aRegion);
|
||||
auto clipId = DefineClip(Nothing(), aBounds, &clips, nullptr);
|
||||
auto spaceAndClip = WrSpaceAndClip{mCurrentSpaceAndClipChain.space, clipId};
|
||||
|
||||
wr_dp_push_backdrop_filter_with_parent_clip(
|
||||
mWrState, aBounds, clip, aIsBackfaceVisible, &spaceAndClip,
|
||||
aFilters.Elements(), aFilters.Length(), aFilterDatas.Elements(),
|
||||
aFilterDatas.Length());
|
||||
}
|
||||
|
||||
void DisplayListBuilder::PushLinearGradient(
|
||||
const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip,
|
||||
bool aIsBackfaceVisible, const wr::LayoutPoint& aStartPoint,
|
||||
|
|
|
@ -450,6 +450,12 @@ class DisplayListBuilder final {
|
|||
void PushClearRectWithComplexRegion(const wr::LayoutRect& aBounds,
|
||||
const wr::ComplexClipRegion& aRegion);
|
||||
|
||||
void PushBackdropFilter(const wr::LayoutRect& aBounds,
|
||||
const wr::ComplexClipRegion& aRegion,
|
||||
const nsTArray<wr::FilterOp>& aFilters,
|
||||
const nsTArray<wr::WrFilterData>& aFilterDatas,
|
||||
bool aIsBackfaceVisible);
|
||||
|
||||
void PushLinearGradient(const wr::LayoutRect& aBounds,
|
||||
const wr::LayoutRect& aClip, bool aIsBackfaceVisible,
|
||||
const wr::LayoutPoint& aStartPoint,
|
||||
|
|
|
@ -2432,6 +2432,60 @@ pub extern "C" fn wr_dp_push_rect_with_parent_clip(
|
|||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_dp_push_backdrop_filter_with_parent_clip(
|
||||
state: &mut WrState,
|
||||
rect: LayoutRect,
|
||||
clip: LayoutRect,
|
||||
is_backface_visible: bool,
|
||||
parent: &WrSpaceAndClip,
|
||||
filters: *const FilterOp,
|
||||
filter_count: usize,
|
||||
filter_datas: *const WrFilterData,
|
||||
filter_datas_count: usize,
|
||||
) {
|
||||
debug_assert!(unsafe { !is_in_render_thread() });
|
||||
|
||||
let c_filters = unsafe { make_slice(filters, filter_count) };
|
||||
let filters : Vec<FilterOp> = c_filters.iter()
|
||||
.map(|c_filter| { c_filter.clone() })
|
||||
.collect();
|
||||
|
||||
let c_filter_datas = unsafe { make_slice(filter_datas, filter_datas_count) };
|
||||
let filter_datas : Vec<FilterData> = c_filter_datas.iter().map(|c_filter_data| {
|
||||
FilterData {
|
||||
func_r_type: c_filter_data.funcR_type,
|
||||
r_values: unsafe { make_slice(c_filter_data.R_values, c_filter_data.R_values_count).to_vec() },
|
||||
func_g_type: c_filter_data.funcG_type,
|
||||
g_values: unsafe { make_slice(c_filter_data.G_values, c_filter_data.G_values_count).to_vec() },
|
||||
func_b_type: c_filter_data.funcB_type,
|
||||
b_values: unsafe { make_slice(c_filter_data.B_values, c_filter_data.B_values_count).to_vec() },
|
||||
func_a_type: c_filter_data.funcA_type,
|
||||
a_values: unsafe { make_slice(c_filter_data.A_values, c_filter_data.A_values_count).to_vec() },
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let space_and_clip = parent.to_webrender(state.pipeline_id);
|
||||
|
||||
let clip_rect = clip.intersection(&rect);
|
||||
if clip_rect.is_none() { return; }
|
||||
|
||||
let prim_info = CommonItemProperties {
|
||||
clip_rect: clip_rect.unwrap(),
|
||||
clip_id: space_and_clip.clip_id,
|
||||
spatial_id: space_and_clip.spatial_id,
|
||||
is_backface_visible,
|
||||
hit_info: state.current_tag,
|
||||
};
|
||||
|
||||
state.frame_builder.dl_builder.push_backdrop_filter(
|
||||
&prim_info,
|
||||
&filters,
|
||||
&filter_datas,
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn wr_dp_push_clear_rect(state: &mut WrState,
|
||||
rect: LayoutRect,
|
||||
|
|
|
@ -2531,6 +2531,35 @@ inline static bool IsSVGContentWithCSSClip(const nsIFrame* aFrame) {
|
|||
nsGkAtoms::foreignObject);
|
||||
}
|
||||
|
||||
bool nsIFrame::FormsBackdropRoot(const nsStyleDisplay* aStyleDisplay,
|
||||
const nsStyleEffects* aStyleEffects,
|
||||
const nsStyleSVGReset* aStyleSVGReset) {
|
||||
// Check if this is a root frame.
|
||||
if (!GetParent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for filter effects.
|
||||
if (aStyleEffects->HasFilters() || aStyleEffects->HasBackdropFilters() ||
|
||||
aStyleEffects->HasMixBlendMode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for opacity.
|
||||
if (HasOpacity(aStyleDisplay, aStyleEffects)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for mask or clip path.
|
||||
if (aStyleSVGReset->HasMask() || aStyleSVGReset->HasClipPath()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(cbrewster): Check will-change attributes
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<nsRect> nsIFrame::GetClipPropClipRect(const nsStyleDisplay* aDisp,
|
||||
const nsStyleEffects* aEffects,
|
||||
const nsSize& aSize) const {
|
||||
|
@ -3092,10 +3121,18 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
|||
}
|
||||
}
|
||||
|
||||
bool usingFilter = StyleEffects()->HasFilters();
|
||||
bool backdropFilterEnabled =
|
||||
StaticPrefs::layout_css_backdrop_filter_enabled();
|
||||
bool usingBackdropFilter =
|
||||
backdropFilterEnabled && effects->HasBackdropFilters() &&
|
||||
nsDisplayBackdropFilters::CanCreateWebRenderCommands(aBuilder, this);
|
||||
bool usingFilter = effects->HasFilters();
|
||||
bool usingMask = nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(this);
|
||||
bool usingSVGEffects = usingFilter || usingMask;
|
||||
|
||||
bool formsBackdropRoot = backdropFilterEnabled &&
|
||||
FormsBackdropRoot(disp, effects, StyleSVGReset());
|
||||
|
||||
nsRect visibleRectOutsideSVGEffects = visibleRect;
|
||||
nsDisplayList hoistedScrollInfoItemsStorage;
|
||||
if (usingSVGEffects) {
|
||||
|
@ -3347,6 +3384,23 @@ void nsIFrame::BuildDisplayListForStackingContext(
|
|||
ct.TrackContainer(resultList.GetTop());
|
||||
}
|
||||
|
||||
if (formsBackdropRoot) {
|
||||
DisplayListClipState::AutoSaveRestore backdropRootContainerClipState(
|
||||
aBuilder);
|
||||
resultList.AppendNewToTop<nsDisplayBackdropRootContainer>(
|
||||
aBuilder, this, &resultList, containerItemASR);
|
||||
ct.TrackContainer(resultList.GetTop());
|
||||
}
|
||||
|
||||
if (usingBackdropFilter) {
|
||||
DisplayListClipState::AutoSaveRestore clipState(aBuilder);
|
||||
nsRect backdropRect =
|
||||
GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
|
||||
resultList.AppendNewToTop<nsDisplayBackdropFilters>(
|
||||
aBuilder, this, &resultList, backdropRect);
|
||||
ct.TrackContainer(resultList.GetTop());
|
||||
}
|
||||
|
||||
/* If there are any SVG effects, wrap the list up in an SVG effects item
|
||||
* (which also handles CSS group opacity). Note that we create an SVG effects
|
||||
* item even if resultList is empty, since a filter can produce graphical
|
||||
|
@ -10779,7 +10833,8 @@ bool nsIFrame::IsStackingContext(const nsStyleDisplay* aStyleDisplay,
|
|||
aStylePosition->mZIndex.IsInteger())) ||
|
||||
(aStyleDisplay->mWillChange.bits &
|
||||
StyleWillChangeBits_STACKING_CONTEXT) ||
|
||||
aStyleDisplay->mIsolation != NS_STYLE_ISOLATION_AUTO;
|
||||
aStyleDisplay->mIsolation != NS_STYLE_ISOLATION_AUTO ||
|
||||
aStyleEffects->HasBackdropFilters();
|
||||
}
|
||||
|
||||
bool nsIFrame::IsStackingContext() {
|
||||
|
|
|
@ -1808,6 +1808,14 @@ class nsIFrame : public nsQueryFrame {
|
|||
virtual bool IsSVGTransformed(Matrix* aOwnTransforms = nullptr,
|
||||
Matrix* aFromParentTransforms = nullptr) const;
|
||||
|
||||
/**
|
||||
* Return true if this frame should form a backdrop root container.
|
||||
* See: https://drafts.fxtf.org/filter-effects-2/#BackdropRootTriggers
|
||||
*/
|
||||
bool FormsBackdropRoot(const nsStyleDisplay* aStyleDisplay,
|
||||
const nsStyleEffects* aStyleEffects,
|
||||
const nsStyleSVGReset* aStyleSvgReset);
|
||||
|
||||
/**
|
||||
* Returns whether this frame will attempt to extend the 3d transforms of its
|
||||
* children. This requires transform-style: preserve-3d, as well as no
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
DECLARE_DISPLAY_ITEM_TYPE(ALT_FEEDBACK, 0)
|
||||
DECLARE_DISPLAY_ITEM_TYPE(ASYNC_ZOOM,
|
||||
TYPE_RENDERS_NO_IMAGES | TYPE_IS_CONTAINER)
|
||||
DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_FILTER, TYPE_IS_CONTAINER)
|
||||
DECLARE_DISPLAY_ITEM_TYPE(BACKDROP_ROOT_CONTAINER, TYPE_IS_CONTAINER)
|
||||
DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND, TYPE_IS_CONTENTFUL)
|
||||
DECLARE_DISPLAY_ITEM_TYPE(BACKGROUND_COLOR, TYPE_RENDERS_NO_IMAGES)
|
||||
DECLARE_DISPLAY_ITEM_TYPE(BLEND_CONTAINER,
|
||||
|
|
|
@ -10188,6 +10188,185 @@ void nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo) {
|
|||
}
|
||||
#endif
|
||||
|
||||
static float ClampStdDeviation(float aStdDeviation) {
|
||||
// Cap software blur radius for performance reasons.
|
||||
return std::min(std::max(0.0f, aStdDeviation), 100.0f);
|
||||
}
|
||||
|
||||
static bool CreateWebRenderCSSFilters(Span<const StyleFilter> aFilters,
|
||||
nsIFrame* aFrame,
|
||||
WrFiltersHolder& aWrFilters) {
|
||||
// All CSS filters are supported by WebRender. SVG filters are not fully
|
||||
// supported, those use NS_STYLE_FILTER_URL and are handled separately.
|
||||
|
||||
// If there are too many filters to render, then just pretend that we
|
||||
// succeeded, and don't render any of them.
|
||||
if (aFilters.Length() >
|
||||
StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
|
||||
return true;
|
||||
}
|
||||
aWrFilters.filters.SetCapacity(aFilters.Length());
|
||||
auto& wrFilters = aWrFilters.filters;
|
||||
for (const StyleFilter& filter : aFilters) {
|
||||
switch (filter.tag) {
|
||||
case StyleFilter::Tag::Brightness:
|
||||
wrFilters.AppendElement(
|
||||
wr::FilterOp::Brightness(filter.AsBrightness()));
|
||||
break;
|
||||
case StyleFilter::Tag::Contrast:
|
||||
wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
|
||||
break;
|
||||
case StyleFilter::Tag::Grayscale:
|
||||
wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
|
||||
break;
|
||||
case StyleFilter::Tag::Invert:
|
||||
wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
|
||||
break;
|
||||
case StyleFilter::Tag::Opacity: {
|
||||
float opacity = filter.AsOpacity();
|
||||
wrFilters.AppendElement(wr::FilterOp::Opacity(
|
||||
wr::PropertyBinding<float>::Value(opacity), opacity));
|
||||
break;
|
||||
}
|
||||
case StyleFilter::Tag::Saturate:
|
||||
wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
|
||||
break;
|
||||
case StyleFilter::Tag::Sepia:
|
||||
wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
|
||||
break;
|
||||
case StyleFilter::Tag::HueRotate: {
|
||||
wrFilters.AppendElement(
|
||||
wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
|
||||
break;
|
||||
}
|
||||
case StyleFilter::Tag::Blur: {
|
||||
// TODO(emilio): we should go directly from css pixels -> device pixels.
|
||||
float appUnitsPerDevPixel =
|
||||
aFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
wrFilters.AppendElement(mozilla::wr::FilterOp::Blur(
|
||||
ClampStdDeviation(NSAppUnitsToFloatPixels(
|
||||
filter.AsBlur().ToAppUnits(), appUnitsPerDevPixel))));
|
||||
break;
|
||||
}
|
||||
case StyleFilter::Tag::DropShadow: {
|
||||
float appUnitsPerDevPixel =
|
||||
aFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
const StyleSimpleShadow& shadow = filter.AsDropShadow();
|
||||
nscolor color = shadow.color.CalcColor(aFrame);
|
||||
|
||||
wr::Shadow wrShadow;
|
||||
wrShadow.offset = {
|
||||
NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
|
||||
appUnitsPerDevPixel),
|
||||
NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
|
||||
appUnitsPerDevPixel)};
|
||||
wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
|
||||
appUnitsPerDevPixel);
|
||||
wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
|
||||
NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
|
||||
wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
already_AddRefed<Layer> nsDisplayBackdropRootContainer::BuildLayer(
|
||||
nsDisplayListBuilder* aBuilder, LayerManager* aManager,
|
||||
const ContainerLayerParameters& aContainerParameters) {
|
||||
RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor(
|
||||
aBuilder, aManager, mFrame, this, &mList, aContainerParameters, nullptr);
|
||||
if (!container) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return container.forget();
|
||||
}
|
||||
|
||||
LayerState nsDisplayBackdropRootContainer::GetLayerState(
|
||||
nsDisplayListBuilder* aBuilder, LayerManager* aManager,
|
||||
const ContainerLayerParameters& aParameters) {
|
||||
return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList,
|
||||
GetAnimatedGeometryRoot());
|
||||
}
|
||||
|
||||
bool nsDisplayBackdropRootContainer::CreateWebRenderCommands(
|
||||
mozilla::wr::DisplayListBuilder& aBuilder,
|
||||
mozilla::wr::IpcResourceUpdateQueue& aResources,
|
||||
const StackingContextHelper& aSc,
|
||||
mozilla::layers::RenderRootStateManager* aManager,
|
||||
nsDisplayListBuilder* aDisplayListBuilder) {
|
||||
wr::StackingContextParams params;
|
||||
params.is_backdrop_root = true;
|
||||
params.clip =
|
||||
wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
|
||||
StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
|
||||
params);
|
||||
|
||||
nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
|
||||
aDisplayListBuilder);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsDisplayBackdropFilters::CanCreateWebRenderCommands(
|
||||
nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) {
|
||||
WrFiltersHolder wrFilters;
|
||||
Maybe<nsRect> filterClip;
|
||||
auto filterChain = aFrame->StyleEffects()->mBackdropFilters.AsSpan();
|
||||
return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) ||
|
||||
nsSVGIntegrationUtils::BuildWebRenderFilters(aFrame, filterChain,
|
||||
wrFilters, filterClip);
|
||||
}
|
||||
|
||||
bool nsDisplayBackdropFilters::CreateWebRenderCommands(
|
||||
mozilla::wr::DisplayListBuilder& aBuilder,
|
||||
mozilla::wr::IpcResourceUpdateQueue& aResources,
|
||||
const StackingContextHelper& aSc,
|
||||
mozilla::layers::RenderRootStateManager* aManager,
|
||||
nsDisplayListBuilder* aDisplayListBuilder) {
|
||||
WrFiltersHolder wrFilters;
|
||||
Maybe<nsRect> filterClip;
|
||||
auto filterChain = mFrame->StyleEffects()->mBackdropFilters.AsSpan();
|
||||
if (!CreateWebRenderCSSFilters(filterChain, mFrame, wrFilters) &&
|
||||
!nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
|
||||
wrFilters, filterClip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCSSRendering::ImageLayerClipState clip;
|
||||
nsCSSRendering::GetImageLayerClip(
|
||||
mFrame->StyleBackground()->BottomLayer(), mFrame, *mFrame->StyleBorder(),
|
||||
mBackdropRect, mBackdropRect, false,
|
||||
mFrame->PresContext()->AppUnitsPerDevPixel(), &clip);
|
||||
|
||||
LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
|
||||
mBackdropRect, mFrame->PresContext()->AppUnitsPerDevPixel());
|
||||
wr::LayoutRect roundedRect = wr::ToRoundedLayoutRect(bounds);
|
||||
|
||||
wr::ComplexClipRegion region =
|
||||
wr::ToComplexClipRegion(clip.mBGClipArea, clip.mRadii,
|
||||
mFrame->PresContext()->AppUnitsPerDevPixel());
|
||||
|
||||
aBuilder.PushBackdropFilter(roundedRect, region, wrFilters.filters,
|
||||
wrFilters.filter_datas, !BackfaceIsHidden());
|
||||
|
||||
wr::StackingContextParams params;
|
||||
params.clip =
|
||||
wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId());
|
||||
StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder,
|
||||
params);
|
||||
|
||||
nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager,
|
||||
aDisplayListBuilder);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder,
|
||||
nsIFrame* aFrame, nsDisplayList* aList)
|
||||
: nsDisplayEffectsBase(aBuilder, aFrame, aList),
|
||||
|
@ -10276,99 +10455,14 @@ void nsDisplayFilters::PaintAsLayer(nsDisplayListBuilder* aBuilder,
|
|||
nsDisplayFiltersGeometry::UpdateDrawResult(this, imgParams.result);
|
||||
}
|
||||
|
||||
static float ClampStdDeviation(float aStdDeviation) {
|
||||
// Cap software blur radius for performance reasons.
|
||||
return std::min(std::max(0.0f, aStdDeviation), 100.0f);
|
||||
}
|
||||
|
||||
bool nsDisplayFilters::CreateWebRenderCSSFilters(WrFiltersHolder& aWrFilters) {
|
||||
// All CSS filters are supported by WebRender. SVG filters are not fully
|
||||
// supported, those use NS_STYLE_FILTER_URL and are handled separately.
|
||||
Span<const StyleFilter> filters = mFrame->StyleEffects()->mFilters.AsSpan();
|
||||
|
||||
// If there are too many filters to render, then just pretend that we
|
||||
// succeeded, and don't render any of them.
|
||||
if (filters.Length() >
|
||||
StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
|
||||
return true;
|
||||
}
|
||||
aWrFilters.filters.SetCapacity(filters.Length());
|
||||
auto& wrFilters = aWrFilters.filters;
|
||||
for (const StyleFilter& filter : filters) {
|
||||
switch (filter.tag) {
|
||||
case StyleFilter::Tag::Brightness:
|
||||
wrFilters.AppendElement(
|
||||
wr::FilterOp::Brightness(filter.AsBrightness()));
|
||||
break;
|
||||
case StyleFilter::Tag::Contrast:
|
||||
wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
|
||||
break;
|
||||
case StyleFilter::Tag::Grayscale:
|
||||
wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
|
||||
break;
|
||||
case StyleFilter::Tag::Invert:
|
||||
wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
|
||||
break;
|
||||
case StyleFilter::Tag::Opacity: {
|
||||
float opacity = filter.AsOpacity();
|
||||
wrFilters.AppendElement(wr::FilterOp::Opacity(
|
||||
wr::PropertyBinding<float>::Value(opacity), opacity));
|
||||
break;
|
||||
}
|
||||
case StyleFilter::Tag::Saturate:
|
||||
wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
|
||||
break;
|
||||
case StyleFilter::Tag::Sepia:
|
||||
wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
|
||||
break;
|
||||
case StyleFilter::Tag::HueRotate: {
|
||||
wrFilters.AppendElement(
|
||||
wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
|
||||
break;
|
||||
}
|
||||
case StyleFilter::Tag::Blur: {
|
||||
// TODO(emilio): we should go directly from css pixels -> device pixels.
|
||||
float appUnitsPerDevPixel =
|
||||
mFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
wrFilters.AppendElement(mozilla::wr::FilterOp::Blur(
|
||||
ClampStdDeviation(NSAppUnitsToFloatPixels(
|
||||
filter.AsBlur().ToAppUnits(), appUnitsPerDevPixel))));
|
||||
break;
|
||||
}
|
||||
case StyleFilter::Tag::DropShadow: {
|
||||
float appUnitsPerDevPixel =
|
||||
mFrame->PresContext()->AppUnitsPerDevPixel();
|
||||
const StyleSimpleShadow& shadow = filter.AsDropShadow();
|
||||
nscolor color = shadow.color.CalcColor(mFrame);
|
||||
|
||||
wr::Shadow wrShadow;
|
||||
wrShadow.offset = {
|
||||
NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
|
||||
appUnitsPerDevPixel),
|
||||
NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
|
||||
appUnitsPerDevPixel)};
|
||||
wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
|
||||
appUnitsPerDevPixel);
|
||||
wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
|
||||
NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
|
||||
wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nsDisplayFilters::CanCreateWebRenderCommands(
|
||||
nsDisplayListBuilder* aBuilder) {
|
||||
WrFiltersHolder wrFilters;
|
||||
Maybe<nsRect> filterClip;
|
||||
if (!CreateWebRenderCSSFilters(wrFilters) &&
|
||||
!nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, wrFilters,
|
||||
filterClip)) {
|
||||
auto filterChain = mFrame->StyleEffects()->mFilters.AsSpan();
|
||||
if (!CreateWebRenderCSSFilters(filterChain, mFrame, wrFilters) &&
|
||||
!nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
|
||||
wrFilters, filterClip)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -10384,9 +10478,10 @@ bool nsDisplayFilters::CreateWebRenderCommands(
|
|||
|
||||
WrFiltersHolder wrFilters;
|
||||
Maybe<nsRect> filterClip;
|
||||
if (!CreateWebRenderCSSFilters(wrFilters) &&
|
||||
!nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, wrFilters,
|
||||
filterClip)) {
|
||||
auto filterChain = mFrame->StyleEffects()->mFilters.AsSpan();
|
||||
if (!CreateWebRenderCSSFilters(filterChain, mFrame, wrFilters) &&
|
||||
!nsSVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain,
|
||||
wrFilters, filterClip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -6688,6 +6688,77 @@ class nsDisplayMasksAndClipPaths : public nsDisplayEffectsBase {
|
|||
nsTArray<nsRect> mDestRects;
|
||||
};
|
||||
|
||||
class nsDisplayBackdropRootContainer : public nsDisplayWrapList {
|
||||
public:
|
||||
nsDisplayBackdropRootContainer(nsDisplayListBuilder* aBuilder,
|
||||
nsIFrame* aFrame, nsDisplayList* aList,
|
||||
const ActiveScrolledRoot* aActiveScrolledRoot)
|
||||
: nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true) {
|
||||
MOZ_COUNT_CTOR(nsDisplayBackdropRootContainer);
|
||||
}
|
||||
|
||||
#ifdef NS_BUILD_REFCNT_LOGGING
|
||||
~nsDisplayBackdropRootContainer() override {
|
||||
MOZ_COUNT_DTOR(nsDisplayBackdropRootContainer);
|
||||
}
|
||||
#endif
|
||||
|
||||
NS_DISPLAY_DECL_NAME("BackdropRootContainer", TYPE_BACKDROP_ROOT_CONTAINER)
|
||||
|
||||
already_AddRefed<Layer> BuildLayer(
|
||||
nsDisplayListBuilder* aBuilder, LayerManager* aManager,
|
||||
const ContainerLayerParameters& aContainerParameters) override;
|
||||
LayerState GetLayerState(
|
||||
nsDisplayListBuilder* aBuilder, LayerManager* aManager,
|
||||
const ContainerLayerParameters& aParameters) override;
|
||||
|
||||
bool CreateWebRenderCommands(
|
||||
mozilla::wr::DisplayListBuilder& aBuilder,
|
||||
mozilla::wr::IpcResourceUpdateQueue& aResources,
|
||||
const StackingContextHelper& aSc,
|
||||
mozilla::layers::RenderRootStateManager* aManager,
|
||||
nsDisplayListBuilder* aDisplayListBuilder) override;
|
||||
|
||||
bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
|
||||
return !aBuilder->IsPaintingForWebRender();
|
||||
}
|
||||
};
|
||||
|
||||
class nsDisplayBackdropFilters : public nsDisplayWrapList {
|
||||
public:
|
||||
nsDisplayBackdropFilters(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
|
||||
nsDisplayList* aList, const nsRect& aBackdropRect)
|
||||
: nsDisplayWrapList(aBuilder, aFrame, aList),
|
||||
mBackdropRect(aBackdropRect) {
|
||||
MOZ_COUNT_CTOR(nsDisplayBackdropFilters);
|
||||
}
|
||||
|
||||
#ifdef NS_BUILD_REFCNT_LOGGING
|
||||
~nsDisplayBackdropFilters() override {
|
||||
MOZ_COUNT_DTOR(nsDisplayBackdropFilters);
|
||||
}
|
||||
#endif
|
||||
|
||||
NS_DISPLAY_DECL_NAME("BackdropFilter", TYPE_BACKDROP_FILTER)
|
||||
|
||||
bool CreateWebRenderCommands(
|
||||
mozilla::wr::DisplayListBuilder& aBuilder,
|
||||
mozilla::wr::IpcResourceUpdateQueue& aResources,
|
||||
const StackingContextHelper& aSc,
|
||||
mozilla::layers::RenderRootStateManager* aManager,
|
||||
nsDisplayListBuilder* aDisplayListBuilder) override;
|
||||
|
||||
static bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder,
|
||||
nsIFrame* aFrame);
|
||||
|
||||
bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
|
||||
return !aBuilder->IsPaintingForWebRender();
|
||||
}
|
||||
|
||||
private:
|
||||
nsRect mBackdropRect;
|
||||
};
|
||||
|
||||
/**
|
||||
* A display item to paint a stacking context with filter effects set by the
|
||||
* stacking context root frame's style.
|
||||
|
@ -6767,8 +6838,6 @@ class nsDisplayFilters : public nsDisplayEffectsBase {
|
|||
nsDisplayListBuilder* aDisplayListBuilder) override;
|
||||
bool CanCreateWebRenderCommands(nsDisplayListBuilder* aBuilder);
|
||||
|
||||
bool CreateWebRenderCSSFilters(WrFiltersHolder& wrFilters);
|
||||
|
||||
private:
|
||||
NS_DISPLAY_ALLOW_CLONING()
|
||||
|
||||
|
|
|
@ -3802,6 +3802,11 @@ nsChangeHint nsStyleEffects::CalcDifference(
|
|||
hint |= nsChangeHint_RepaintFrame;
|
||||
}
|
||||
|
||||
if (HasBackdropFilters() != aNewData.HasBackdropFilters()) {
|
||||
// A change from/to being a containing block for position:fixed.
|
||||
hint |= nsChangeHint_UpdateContainingBlock;
|
||||
}
|
||||
|
||||
if (mBackdropFilters != aNewData.mBackdropFilters) {
|
||||
hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
|
||||
}
|
||||
|
|
|
@ -2376,6 +2376,8 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleEffects {
|
|||
|
||||
bool HasFilters() const { return !mFilters.IsEmpty(); }
|
||||
|
||||
bool HasBackdropFilters() const { return !mBackdropFilters.IsEmpty(); }
|
||||
|
||||
bool HasBoxShadowWithInset(bool aInset) const {
|
||||
for (auto& shadow : mBoxShadow.AsSpan()) {
|
||||
if (shadow.inset == aInset) {
|
||||
|
@ -2385,6 +2387,10 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleEffects {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool HasMixBlendMode() const {
|
||||
return mMixBlendMode != NS_STYLE_BLEND_NORMAL;
|
||||
}
|
||||
|
||||
mozilla::StyleOwnedSlice<mozilla::StyleFilter> mFilters;
|
||||
mozilla::StyleOwnedSlice<mozilla::StyleBoxShadow> mBoxShadow;
|
||||
mozilla::StyleOwnedSlice<mozilla::StyleFilter> mBackdropFilters;
|
||||
|
|
|
@ -135,7 +135,8 @@ bool nsStyleDisplay::IsFixedPosContainingBlockForNonSVGTextFrames(
|
|||
return true;
|
||||
}
|
||||
|
||||
return aStyle.StyleEffects()->HasFilters();
|
||||
return aStyle.StyleEffects()->HasFilters() ||
|
||||
aStyle.StyleEffects()->HasBackdropFilters();
|
||||
}
|
||||
|
||||
bool nsStyleDisplay::
|
||||
|
|
|
@ -1706,7 +1706,8 @@ already_AddRefed<nsIURI> SVGObserverUtils::GetBaseURLForLocalRef(
|
|||
|
||||
already_AddRefed<URLAndReferrerInfo> SVGObserverUtils::GetFilterURI(
|
||||
nsIFrame* aFrame, const StyleFilter& aFilter) {
|
||||
MOZ_ASSERT(!aFrame->StyleEffects()->mFilters.IsEmpty());
|
||||
MOZ_ASSERT(!aFrame->StyleEffects()->mFilters.IsEmpty() ||
|
||||
!aFrame->StyleEffects()->mBackdropFilters.IsEmpty());
|
||||
MOZ_ASSERT(aFilter.IsUrl());
|
||||
return ResolveURLUsingLocalRef(aFrame, aFilter.AsUrl());
|
||||
}
|
||||
|
|
|
@ -116,13 +116,13 @@ static mozilla::wr::ComponentTransferFuncType FuncTypeToWr(uint8_t aFuncType) {
|
|||
}
|
||||
|
||||
bool nsFilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
|
||||
Span<const StyleFilter> aFilters,
|
||||
WrFiltersHolder& aWrFilters,
|
||||
Maybe<nsRect>& aPostFilterClip) {
|
||||
aWrFilters.filters.Clear();
|
||||
aWrFilters.filter_datas.Clear();
|
||||
aWrFilters.values.Clear();
|
||||
|
||||
auto filterChain = aFilteredFrame->StyleEffects()->mFilters.AsSpan();
|
||||
UniquePtr<UserSpaceMetrics> metrics =
|
||||
UserSpaceMetricsForFrame(aFilteredFrame);
|
||||
|
||||
|
@ -136,7 +136,7 @@ bool nsFilterInstance::BuildWebRenderFilters(nsIFrame* aFilteredFrame,
|
|||
// read the rendered contents of aFilteredFrame.
|
||||
bool inputIsTainted = true;
|
||||
nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
|
||||
*metrics, filterChain, inputIsTainted, nullptr,
|
||||
*metrics, aFilters, inputIsTainted, nullptr,
|
||||
scaleMatrixInDevUnits, nullptr, nullptr, nullptr,
|
||||
nullptr);
|
||||
|
||||
|
|
|
@ -126,9 +126,10 @@ class nsFilterInstance {
|
|||
* Try to build WebRender filters for a frame if the filters applied to it are
|
||||
* supported.
|
||||
*/
|
||||
static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame,
|
||||
WrFiltersHolder& aWrFilters,
|
||||
mozilla::Maybe<nsRect>& aPostFilterClip);
|
||||
static bool BuildWebRenderFilters(
|
||||
nsIFrame* aFilteredFrame,
|
||||
mozilla::Span<const mozilla::StyleFilter> aFilters,
|
||||
WrFiltersHolder& aWrFilters, mozilla::Maybe<nsRect>& aPostFilterClip);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
|
|
@ -160,8 +160,11 @@ bool nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) {
|
|||
// checking the SDL prefs here, since we don't know if we're being called for
|
||||
// painting or hit-testing anyway.
|
||||
const nsStyleSVGReset* style = aFrame->StyleSVGReset();
|
||||
return aFrame->StyleEffects()->HasFilters() || style->HasClipPath() ||
|
||||
style->HasMask();
|
||||
const nsStyleEffects* effects = aFrame->StyleEffects();
|
||||
// TODO(cbrewster): remove backdrop-filter from this list once it is supported
|
||||
// in preserve-3d cases.
|
||||
return effects->HasFilters() || effects->HasBackdropFilters() ||
|
||||
style->HasClipPath() || style->HasMask();
|
||||
}
|
||||
|
||||
bool nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(
|
||||
|
@ -1103,10 +1106,10 @@ void nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams) {
|
|||
}
|
||||
|
||||
bool nsSVGIntegrationUtils::BuildWebRenderFilters(
|
||||
nsIFrame* aFilteredFrame, WrFiltersHolder& aWrFilters,
|
||||
Maybe<nsRect>& aPostFilterClip) {
|
||||
return nsFilterInstance::BuildWebRenderFilters(aFilteredFrame, aWrFilters,
|
||||
aPostFilterClip);
|
||||
nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters,
|
||||
WrFiltersHolder& aWrFilters, Maybe<nsRect>& aPostFilterClip) {
|
||||
return nsFilterInstance::BuildWebRenderFilters(aFilteredFrame, aFilters,
|
||||
aWrFilters, aPostFilterClip);
|
||||
}
|
||||
|
||||
class PaintFrameCallback : public gfxDrawingCallback {
|
||||
|
|
|
@ -209,9 +209,10 @@ class nsSVGIntegrationUtils final {
|
|||
* Try to build WebRender filters for a frame if the filters applied to it are
|
||||
* supported.
|
||||
*/
|
||||
static bool BuildWebRenderFilters(nsIFrame* aFilteredFrame,
|
||||
WrFiltersHolder& aWrFilters,
|
||||
mozilla::Maybe<nsRect>& aPostFilterClip);
|
||||
static bool BuildWebRenderFilters(
|
||||
nsIFrame* aFilteredFrame,
|
||||
mozilla::Span<const mozilla::StyleFilter> aFilters,
|
||||
WrFiltersHolder& aWrFilters, mozilla::Maybe<nsRect>& aPostFilterClip);
|
||||
|
||||
/**
|
||||
* @param aRenderingContext the target rendering context in which the paint
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filter-basic-background-color.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=2426
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filter-basic-opacity-2.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=10894
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[backdrop-filter-basic-opacity.html]
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=916
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filter-basic.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=2507
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filter-clipped.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=622
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[backdrop-filter-edge-clipping.html]
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=5211
|
|
@ -0,0 +1,3 @@
|
|||
[backdrop-filter-edge-pixels.html]
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=3169
|
|
@ -1,2 +1,4 @@
|
|||
[backdrop-filter-fixed-clip.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filter-isolation-fixed.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=3623
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filter-isolation-isolate.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=3623
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
[backdrop-filter-isolation.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[backdrop-filter-paint-order.html]
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=5211
|
|
@ -1,2 +1,4 @@
|
|||
[backdrop-filter-plus-filter.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[backdrop-filter-plus-opacity.html]
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=625
|
|
@ -0,0 +1,4 @@
|
|||
[backdrop-filter-reference-filter.html]
|
||||
expected:
|
||||
if not webrender: PASS
|
||||
if webrender: FAIL
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filter-update.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=622
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filters-brightness.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=2188
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filters-contrast.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=2029
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filters-grayscale.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=1508
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filters-hue-rotate.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=1942
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filters-invert.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=2029
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[backdrop-filters-opacity.html]
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=1437
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filters-saturate.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=2206
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
[backdrop-filters-sepia.html]
|
||||
expected: FAIL
|
||||
expected:
|
||||
if not webrender: FAIL
|
||||
if webrender: PASS
|
||||
fuzzy:
|
||||
if os == "win" and webrender: maxDifference=92;totalPixels=1508
|
||||
|
|
Загрузка…
Ссылка в новой задаче